From fc4d1e4ea03541e03b4c1f6b7e317844f51c4549 Mon Sep 17 00:00:00 2001 From: Manuel Leduc Date: Fri, 8 Nov 2024 14:17:11 +0100 Subject: [PATCH 1/9] --wip-- [skip ci] --- api/src/api/cristalApp.ts | 4 + core/model/model-api/package.json | 36 ++++++ core/model/model-api/src/index.ts | 100 ++++++++++++++++ core/model/model-api/tsconfig.json | 12 ++ core/model/model-api/tsdoc.json | 4 + core/model/model-api/vite.config.ts | 23 ++++ core/model/model-click-listener/package.json | 41 +++++++ .../src/DefaultClickListener.ts | 59 ++++++++++ .../model-click-listener/src/clickListener.ts | 27 +++++ .../model-click-listener/src/componentInit.ts | 31 +++++ core/model/model-click-listener/src/index.ts | 23 ++++ core/model/model-click-listener/tsconfig.json | 12 ++ core/model/model-click-listener/tsdoc.json | 4 + .../model/model-click-listener/vite.config.ts | 23 ++++ core/model/model-local-url/package.json | 44 +++++++ .../defaultLocalURLSerializer.test.ts | 58 +++++++++ .../model-local-url/src/componentInit.ts | 35 ++++++ .../src/defaultLocalURLParser.ts | 27 +++++ .../src/defaultLocalURLSerializer.ts | 70 +++++++++++ core/model/model-local-url/src/index.ts | 20 ++++ .../model-local-url/src/localURLParser.ts | 29 +++++ .../model-local-url/src/localURLSerializer.ts | 29 +++++ core/model/model-local-url/tsconfig.json | 12 ++ core/model/model-local-url/tsdoc.json | 4 + core/model/model-local-url/vite.config.ts | 23 ++++ .../model-remote-url-api/package.json | 41 +++++++ .../model-remote-url-api/src/componentInit.ts | 39 +++++++ .../src/defaultRemoteURLParserProvider.ts | 46 ++++++++ .../src/defaultRemoteURLSerializerProvider.ts | 51 ++++++++ .../model-remote-url-api/src/index.ts | 32 +++++ .../src/remoteURLParser.ts | 30 +++++ .../src/remoteURLParserProvider.ts | 29 +++++ .../src/remoteURLSerializer.ts | 29 +++++ .../src/remoteURLSerializerProvider.ts | 29 +++++ .../model-remote-url-api/tsconfig.json | 12 ++ .../model-remote-url-api/tsdoc.json | 4 + .../model-remote-url-api/vite.config.ts | 23 ++++ .../model-remote-url-nextcloud/package.json | 37 ++++++ .../model-remote-url-nextcloud/tsconfig.json | 12 ++ .../model-remote-url-nextcloud/tsdoc.json | 4 + .../model-remote-url-nextcloud/vite.config.ts | 23 ++++ .../model-remote-url-xwiki/package.json | 42 +++++++ .../src/componentInit.ts | 32 +++++ .../model-remote-url-xwiki/src/index.ts | 19 +++ .../src/xWikiRemoteURLParser.ts | 76 ++++++++++++ .../model-remote-url-xwiki/tsconfig.json | 12 ++ .../model-remote-url-xwiki/tsdoc.json | 4 + .../model-remote-url-xwiki/vite.config.ts | 23 ++++ lib/package.json | 1 + lib/src/components/DefaultCristalApp.ts | 4 + lib/src/staticBuild.ts | 2 + pnpm-lock.yaml | 110 +++++++++++------- pnpm-workspace.yaml | 5 + skin/package.json | 1 + skin/src/vue/contentTools.ts | 22 +++- tsdoc.json | 4 +- 56 files changed, 1499 insertions(+), 49 deletions(-) create mode 100644 core/model/model-api/package.json create mode 100644 core/model/model-api/src/index.ts create mode 100644 core/model/model-api/tsconfig.json create mode 100644 core/model/model-api/tsdoc.json create mode 100644 core/model/model-api/vite.config.ts create mode 100644 core/model/model-click-listener/package.json create mode 100644 core/model/model-click-listener/src/DefaultClickListener.ts create mode 100644 core/model/model-click-listener/src/clickListener.ts create mode 100644 core/model/model-click-listener/src/componentInit.ts create mode 100644 core/model/model-click-listener/src/index.ts create mode 100644 core/model/model-click-listener/tsconfig.json create mode 100644 core/model/model-click-listener/tsdoc.json create mode 100644 core/model/model-click-listener/vite.config.ts create mode 100644 core/model/model-local-url/package.json create mode 100644 core/model/model-local-url/src/__tests__/defaultLocalURLSerializer.test.ts create mode 100644 core/model/model-local-url/src/componentInit.ts create mode 100644 core/model/model-local-url/src/defaultLocalURLParser.ts create mode 100644 core/model/model-local-url/src/defaultLocalURLSerializer.ts create mode 100644 core/model/model-local-url/src/index.ts create mode 100644 core/model/model-local-url/src/localURLParser.ts create mode 100644 core/model/model-local-url/src/localURLSerializer.ts create mode 100644 core/model/model-local-url/tsconfig.json create mode 100644 core/model/model-local-url/tsdoc.json create mode 100644 core/model/model-local-url/vite.config.ts create mode 100644 core/model/model-remote-url/model-remote-url-api/package.json create mode 100644 core/model/model-remote-url/model-remote-url-api/src/componentInit.ts create mode 100644 core/model/model-remote-url/model-remote-url-api/src/defaultRemoteURLParserProvider.ts create mode 100644 core/model/model-remote-url/model-remote-url-api/src/defaultRemoteURLSerializerProvider.ts create mode 100644 core/model/model-remote-url/model-remote-url-api/src/index.ts create mode 100644 core/model/model-remote-url/model-remote-url-api/src/remoteURLParser.ts create mode 100644 core/model/model-remote-url/model-remote-url-api/src/remoteURLParserProvider.ts create mode 100644 core/model/model-remote-url/model-remote-url-api/src/remoteURLSerializer.ts create mode 100644 core/model/model-remote-url/model-remote-url-api/src/remoteURLSerializerProvider.ts create mode 100644 core/model/model-remote-url/model-remote-url-api/tsconfig.json create mode 100644 core/model/model-remote-url/model-remote-url-api/tsdoc.json create mode 100644 core/model/model-remote-url/model-remote-url-api/vite.config.ts create mode 100644 core/model/model-remote-url/model-remote-url-nextcloud/package.json create mode 100644 core/model/model-remote-url/model-remote-url-nextcloud/tsconfig.json create mode 100644 core/model/model-remote-url/model-remote-url-nextcloud/tsdoc.json create mode 100644 core/model/model-remote-url/model-remote-url-nextcloud/vite.config.ts create mode 100644 core/model/model-remote-url/model-remote-url-xwiki/package.json create mode 100644 core/model/model-remote-url/model-remote-url-xwiki/src/componentInit.ts create mode 100644 core/model/model-remote-url/model-remote-url-xwiki/src/index.ts create mode 100644 core/model/model-remote-url/model-remote-url-xwiki/src/xWikiRemoteURLParser.ts create mode 100644 core/model/model-remote-url/model-remote-url-xwiki/tsconfig.json create mode 100644 core/model/model-remote-url/model-remote-url-xwiki/tsdoc.json create mode 100644 core/model/model-remote-url/model-remote-url-xwiki/vite.config.ts diff --git a/api/src/api/cristalApp.ts b/api/src/api/cristalApp.ts index b4f5da3fa..30100e2c6 100644 --- a/api/src/api/cristalApp.ts +++ b/api/src/api/cristalApp.ts @@ -76,6 +76,10 @@ export interface CristalApp { */ setContentRef(ref: Ref): void; + /** + * + * @deprecated + */ loadPageFromURL(url: string): Promise; loadPage(options?: { requeue: boolean }): Promise; diff --git a/core/model/model-api/package.json b/core/model/model-api/package.json new file mode 100644 index 000000000..d790aac8b --- /dev/null +++ b/core/model/model-api/package.json @@ -0,0 +1,36 @@ +{ + "name": "@xwiki/cristal-model-api", + "version": "0.11.0", + "license": "LGPL 2.1", + "author": "XWiki Org Community ", + "homepage": "https://cristal.xwiki.org/", + "repository": { + "type": "git", + "directory": "core/model/model-api", + "url": "https://github.com/xwiki-contrib/cristal/" + }, + "bugs": { + "url": "https://jira.xwiki.org/projects/CRISTAL/" + }, + "type": "module", + "exports": { + ".": "./src/index.ts" + }, + "main": "./src/index.ts", + "scripts": { + "build": "tsc --project tsconfig.json && vite build", + "clean": "rimraf dist", + "lint": "eslint \"./src/**/*.{ts,tsx}\" --max-warnings=0" + }, + "publishConfig": { + "exports": { + ".": { + "import": "./dist/index.es.js", + "require": "./dist/index.umd.js", + "types": "./dist/index.d.ts" + } + }, + "main": "./dist/index.es.js", + "types": "./dist/index.d.ts" + } +} diff --git a/core/model/model-api/src/index.ts b/core/model/model-api/src/index.ts new file mode 100644 index 000000000..8a9d3a03a --- /dev/null +++ b/core/model/model-api/src/index.ts @@ -0,0 +1,100 @@ +/* + * See the LICENSE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/** + * @since 0.12 + */ +enum EntityType { + WIKI, + SPACE, + DOCUMENT, + ATTACHMENT, +} + +/** + * @since 0.12 + */ +interface EntityReference { + type: EntityType; +} + +/** + * @since 0.12 + */ +class WikiReference implements EntityReference { + type: EntityType = EntityType.WIKI; + + name: string; + + constructor(name: string) { + this.name = name; + } +} + +/** + * @since 0.12 + */ +class SpaceReference implements EntityReference { + type: EntityType = EntityType.SPACE; + wiki?: WikiReference; + names: string[]; + + constructor(wiki?: WikiReference, ...names: string[]) { + this.wiki = wiki; + this.names = names; + } +} + +/** + * @since 0.12 + */ +class DocumentReference implements EntityReference { + type: EntityType = EntityType.DOCUMENT; + space?: SpaceReference; + name: string; + + constructor(name: string, space?: SpaceReference) { + this.space = space; + this.name = name; + } +} + +/** + * @since 0.12 + */ +class AttachmentReference implements EntityReference { + type: EntityType = EntityType.SPACE; + name: string; + document: DocumentReference; + + constructor(name: string, document: DocumentReference) { + this.name = name; + this.document = document; + } +} + +export { + type EntityReference, + WikiReference, + SpaceReference, + DocumentReference, + AttachmentReference, + EntityType, +}; diff --git a/core/model/model-api/tsconfig.json b/core/model/model-api/tsconfig.json new file mode 100644 index 000000000..9ea278a11 --- /dev/null +++ b/core/model/model-api/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "resolveJsonModule": true + }, + "extends": "../../../tsconfig.json", + "include": [ + "./src/index.ts", + "./src/**/*" + ] +} \ No newline at end of file diff --git a/core/model/model-api/tsdoc.json b/core/model/model-api/tsdoc.json new file mode 100644 index 000000000..81c5a8a2a --- /dev/null +++ b/core/model/model-api/tsdoc.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json", + "extends": ["../../../tsdoc.json"] +} diff --git a/core/model/model-api/vite.config.ts b/core/model/model-api/vite.config.ts new file mode 100644 index 000000000..c31aed033 --- /dev/null +++ b/core/model/model-api/vite.config.ts @@ -0,0 +1,23 @@ +/* + * See the LICENSE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +import { generateConfig } from "../../../vite.config"; + +export default generateConfig(import.meta.url); diff --git a/core/model/model-click-listener/package.json b/core/model/model-click-listener/package.json new file mode 100644 index 000000000..11fd4f878 --- /dev/null +++ b/core/model/model-click-listener/package.json @@ -0,0 +1,41 @@ +{ + "name": "@xwiki/cristal-model-click-listener", + "version": "0.11.0", + "license": "LGPL 2.1", + "author": "XWiki Org Community ", + "homepage": "https://cristal.xwiki.org/", + "repository": { + "type": "git", + "directory": "core/model/model-click-listener", + "url": "https://github.com/xwiki-contrib/cristal/" + }, + "bugs": { + "url": "https://jira.xwiki.org/projects/CRISTAL/" + }, + "type": "module", + "exports": { + ".": "./src/index.ts" + }, + "main": "./src/index.ts", + "scripts": { + "build": "tsc --project tsconfig.json && vite build", + "clean": "rimraf dist", + "lint": "eslint \"./src/**/*.{ts,tsx}\" --max-warnings=0" + }, + "dependencies": { + "@xwiki/cristal-model-api": "workspace:*", + "@xwiki/cristal-remote-url-api": "workspace:*", + "inversify": "6.0.3" + }, + "publishConfig": { + "exports": { + ".": { + "import": "./dist/index.es.js", + "require": "./dist/index.umd.js", + "types": "./dist/index.d.ts" + } + }, + "main": "./dist/index.es.js", + "types": "./dist/index.d.ts" + } +} diff --git a/core/model/model-click-listener/src/DefaultClickListener.ts b/core/model/model-click-listener/src/DefaultClickListener.ts new file mode 100644 index 000000000..2c6dabd0b --- /dev/null +++ b/core/model/model-click-listener/src/DefaultClickListener.ts @@ -0,0 +1,59 @@ +/* + * See the LICENSE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +import { ClickListener } from "./clickListener"; +import { inject, injectable } from "inversify"; +import { type RemoteURLParserProvider } from "@xwiki/cristal-remote-url-api"; +import { EntityType } from "@xwiki/cristal-model-api"; + +@injectable() +class DefaultClickListener implements ClickListener { + constructor( + @inject("RemoteURLParserProvider") + private readonly remoteURLParserProvider: RemoteURLParserProvider, + ) {} + + handle(element: HTMLElement): void { + const remoteURLParser = this.remoteURLParserProvider.get(); + element.addEventListener( + "click", + function handleClick(event) { + // If no parser is found, we let the click event go through. + if (remoteURLParser) { + const url = (event.target as HTMLLinkElement)?.href; + try { + const entityReference = remoteURLParser.parse(url); + + if (entityReference.type == EntityType.DOCUMENT) { + event.preventDefault(); + } else if (entityReference.type == EntityType.ATTACHMENT) { + // TODO: see how to handle the attachment modal opening. + } + } catch (e) { + console.log(`Failed to parse [${url}] `, e); + } + } + }, + true, + ); + } +} + +export { DefaultClickListener }; diff --git a/core/model/model-click-listener/src/clickListener.ts b/core/model/model-click-listener/src/clickListener.ts new file mode 100644 index 000000000..f9f2c92a7 --- /dev/null +++ b/core/model/model-click-listener/src/clickListener.ts @@ -0,0 +1,27 @@ +/* + * See the LICENSE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +/** + * @since 0.12 + */ +interface ClickListener { + handle(element: HTMLElement): void; +} + +export { type ClickListener }; diff --git a/core/model/model-click-listener/src/componentInit.ts b/core/model/model-click-listener/src/componentInit.ts new file mode 100644 index 000000000..c9970ff81 --- /dev/null +++ b/core/model/model-click-listener/src/componentInit.ts @@ -0,0 +1,31 @@ +/* + * See the LICENSE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +import { ClickListener } from "./clickListener"; +import { DefaultClickListener } from "./DefaultClickListener"; +import { Container } from "inversify"; + +export class ComponentInit { + constructor(container: Container) { + container + .bind("ClickListener") + .to(DefaultClickListener) + .inSingletonScope(); + } +} diff --git a/core/model/model-click-listener/src/index.ts b/core/model/model-click-listener/src/index.ts new file mode 100644 index 000000000..8bd37dee5 --- /dev/null +++ b/core/model/model-click-listener/src/index.ts @@ -0,0 +1,23 @@ +/* + * See the LICENSE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +import { ComponentInit } from "./componentInit"; +import { type ClickListener } from "./clickListener"; + +export { ComponentInit, type ClickListener }; diff --git a/core/model/model-click-listener/tsconfig.json b/core/model/model-click-listener/tsconfig.json new file mode 100644 index 000000000..9ea278a11 --- /dev/null +++ b/core/model/model-click-listener/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "resolveJsonModule": true + }, + "extends": "../../../tsconfig.json", + "include": [ + "./src/index.ts", + "./src/**/*" + ] +} \ No newline at end of file diff --git a/core/model/model-click-listener/tsdoc.json b/core/model/model-click-listener/tsdoc.json new file mode 100644 index 000000000..81c5a8a2a --- /dev/null +++ b/core/model/model-click-listener/tsdoc.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json", + "extends": ["../../../tsdoc.json"] +} diff --git a/core/model/model-click-listener/vite.config.ts b/core/model/model-click-listener/vite.config.ts new file mode 100644 index 000000000..c31aed033 --- /dev/null +++ b/core/model/model-click-listener/vite.config.ts @@ -0,0 +1,23 @@ +/* + * See the LICENSE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +import { generateConfig } from "../../../vite.config"; + +export default generateConfig(import.meta.url); diff --git a/core/model/model-local-url/package.json b/core/model/model-local-url/package.json new file mode 100644 index 000000000..36c78a45c --- /dev/null +++ b/core/model/model-local-url/package.json @@ -0,0 +1,44 @@ +{ + "name": "@xwiki/cristal-model-local-url", + "version": "0.11.0", + "license": "LGPL 2.1", + "author": "XWiki Org Community ", + "homepage": "https://cristal.xwiki.org/", + "repository": { + "type": "git", + "directory": "core/model/model-local-url", + "url": "https://github.com/xwiki-contrib/cristal/" + }, + "bugs": { + "url": "https://jira.xwiki.org/projects/CRISTAL/" + }, + "type": "module", + "exports": { + ".": "./src/index.ts" + }, + "main": "./src/index.ts", + "scripts": { + "build": "tsc --project tsconfig.json && vite build", + "clean": "rimraf dist", + "lint": "eslint \"./src/**/*.{ts,tsx}\" --max-warnings=0", + "test": "vitest --run" + }, + "dependencies": { + "@xwiki/cristal-model-api": "workspace:*", + "inversify": "6.0.3" + }, + "publishConfig": { + "exports": { + ".": { + "import": "./dist/index.es.js", + "require": "./dist/index.umd.js", + "types": "./dist/index.d.ts" + } + }, + "main": "./dist/index.es.js", + "types": "./dist/index.d.ts" + }, + "devDependencies": { + "vitest": "2.1.4" + } +} diff --git a/core/model/model-local-url/src/__tests__/defaultLocalURLSerializer.test.ts b/core/model/model-local-url/src/__tests__/defaultLocalURLSerializer.test.ts new file mode 100644 index 000000000..efc4ca089 --- /dev/null +++ b/core/model/model-local-url/src/__tests__/defaultLocalURLSerializer.test.ts @@ -0,0 +1,58 @@ +/* + * See the LICENSE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +import { DefaultLocalURLSerializer } from "../defaultLocalURLSerializer"; +import { describe, it, expect } from "vitest"; +import { + DocumentReference, + SpaceReference, + WikiReference, +} from "@xwiki/cristal-model-api"; + +describe("defaultLocalURLSerializer", () => { + const serializer = new DefaultLocalURLSerializer(); + it("serialize a wiki", () => { + expect(serializer.serialize(new WikiReference("wiki"))).to.eq("wiki"); + }); + it("serialize a space", () => { + expect( + serializer.serialize( + new SpaceReference(new WikiReference("wiki"), "S1", "S2"), + ), + ).to.eq("wiki:S1.S2"); + }); + it("serialize a space with no wiki", () => { + expect( + serializer.serialize(new SpaceReference(undefined, "S1", "S2")), + ).to.eq("S1.S2"); + }); + it("serialize a document reference", () => { + expect( + serializer.serialize( + new DocumentReference("Page", new SpaceReference(undefined, "Space")), + ), + ).to.eq("Space.Page"); + }); + it("serialize a document reference without a space", () => { + expect(serializer.serialize(new DocumentReference("Page"))).to.eq("Page"); + }); + it("serialize a document reference without a space", () => { + expect(serializer.serialize(new DocumentReference("Page"))).to.eq("Page"); + }); +}); diff --git a/core/model/model-local-url/src/componentInit.ts b/core/model/model-local-url/src/componentInit.ts new file mode 100644 index 000000000..230d00725 --- /dev/null +++ b/core/model/model-local-url/src/componentInit.ts @@ -0,0 +1,35 @@ +/* + * See the LICENSE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +import { LocalURLSerializer } from "./localURLSerializer"; +import { DefaultLocalURLSerializer } from "./defaultLocalURLSerializer"; +import { LocalURLParser } from "./localURLParser"; +import { DefaultLocalURLParser } from "./defaultLocalURLParser"; +import { Container } from "inversify"; + +class ComponentInit { + constructor(container: Container) { + container + .bind("LocalURLSerializer") + .to(DefaultLocalURLSerializer); + container.bind("LocalURLParser").to(DefaultLocalURLParser); + } +} + +export { ComponentInit }; diff --git a/core/model/model-local-url/src/defaultLocalURLParser.ts b/core/model/model-local-url/src/defaultLocalURLParser.ts new file mode 100644 index 000000000..4514405fc --- /dev/null +++ b/core/model/model-local-url/src/defaultLocalURLParser.ts @@ -0,0 +1,27 @@ +/* + * See the LICENSE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +import { LocalURLParser } from "./localURLParser"; +import type { EntityReference } from "@xwiki/cristal-model-api"; + +export class DefaultLocalURLParser implements LocalURLParser { + parse(reference: string): EntityReference { + throw new Error("Method not implemented."); + } +} diff --git a/core/model/model-local-url/src/defaultLocalURLSerializer.ts b/core/model/model-local-url/src/defaultLocalURLSerializer.ts new file mode 100644 index 000000000..6eef9e142 --- /dev/null +++ b/core/model/model-local-url/src/defaultLocalURLSerializer.ts @@ -0,0 +1,70 @@ +/* + * See the LICENSE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +import { LocalURLSerializer } from "./localURLSerializer"; +import { + AttachmentReference, + DocumentReference, + type EntityReference, + EntityType, + SpaceReference, + WikiReference, +} from "@xwiki/cristal-model-api"; + +export class DefaultLocalURLSerializer implements LocalURLSerializer { + serialize(reference?: EntityReference): string | undefined { + if (!reference) { + return undefined; + } + const type = reference.type; + const { SPACE, ATTACHMENT, DOCUMENT, WIKI } = EntityType; + switch (type) { + case WIKI: + return (reference as WikiReference).name; + case SPACE: { + const spaceReference = reference as SpaceReference; + const wiki = this.serialize(spaceReference.wiki); + const spaces = spaceReference.names.join("."); + if (wiki === undefined) { + return spaces; + } else { + return `${wiki}:${spaces}`; + } + } + case DOCUMENT: { + const documentReference = reference as DocumentReference; + const spaces = this.serialize(documentReference.space); + const name = documentReference.name; + if (spaces === undefined) { + return name; + } else { + return `${spaces}.${name}`; + } + } + case ATTACHMENT: { + const attachmentReference = reference as AttachmentReference; + const document = this.serialize(attachmentReference.document); + const name = attachmentReference.name; + return `${document}@${name}`; + } + default: + throw new Error(`Unknown reference type [${type}]`); + } + } +} diff --git a/core/model/model-local-url/src/index.ts b/core/model/model-local-url/src/index.ts new file mode 100644 index 000000000..5378caa4c --- /dev/null +++ b/core/model/model-local-url/src/index.ts @@ -0,0 +1,20 @@ +/* + * See the LICENSE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +export { ComponentInit } from "./componentInit"; diff --git a/core/model/model-local-url/src/localURLParser.ts b/core/model/model-local-url/src/localURLParser.ts new file mode 100644 index 000000000..549a9df7e --- /dev/null +++ b/core/model/model-local-url/src/localURLParser.ts @@ -0,0 +1,29 @@ +/* + * See the LICENSE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +import type { EntityReference } from "@xwiki/cristal-model-api"; + +/** + * @since 0.12 + */ +interface LocalURLParser { + parse(reference: string): EntityReference; +} + +export { type LocalURLParser }; diff --git a/core/model/model-local-url/src/localURLSerializer.ts b/core/model/model-local-url/src/localURLSerializer.ts new file mode 100644 index 000000000..944816da9 --- /dev/null +++ b/core/model/model-local-url/src/localURLSerializer.ts @@ -0,0 +1,29 @@ +/* + * See the LICENSE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +import type { EntityReference } from "@xwiki/cristal-model-api"; + +/** + * @since 0.12 + */ +interface LocalURLSerializer { + serialize(reference?: EntityReference): string | undefined; +} + +export { type LocalURLSerializer }; diff --git a/core/model/model-local-url/tsconfig.json b/core/model/model-local-url/tsconfig.json new file mode 100644 index 000000000..9ea278a11 --- /dev/null +++ b/core/model/model-local-url/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "resolveJsonModule": true + }, + "extends": "../../../tsconfig.json", + "include": [ + "./src/index.ts", + "./src/**/*" + ] +} \ No newline at end of file diff --git a/core/model/model-local-url/tsdoc.json b/core/model/model-local-url/tsdoc.json new file mode 100644 index 000000000..81c5a8a2a --- /dev/null +++ b/core/model/model-local-url/tsdoc.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json", + "extends": ["../../../tsdoc.json"] +} diff --git a/core/model/model-local-url/vite.config.ts b/core/model/model-local-url/vite.config.ts new file mode 100644 index 000000000..c31aed033 --- /dev/null +++ b/core/model/model-local-url/vite.config.ts @@ -0,0 +1,23 @@ +/* + * See the LICENSE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +import { generateConfig } from "../../../vite.config"; + +export default generateConfig(import.meta.url); diff --git a/core/model/model-remote-url/model-remote-url-api/package.json b/core/model/model-remote-url/model-remote-url-api/package.json new file mode 100644 index 000000000..104669566 --- /dev/null +++ b/core/model/model-remote-url/model-remote-url-api/package.json @@ -0,0 +1,41 @@ +{ + "name": "@xwiki/cristal-remote-url-api", + "version": "0.11.0", + "license": "LGPL 2.1", + "author": "XWiki Org Community ", + "homepage": "https://cristal.xwiki.org/", + "repository": { + "type": "git", + "directory": "core/model-remote-url/model-remote-url-api", + "url": "https://github.com/xwiki-contrib/cristal/" + }, + "bugs": { + "url": "https://jira.xwiki.org/projects/CRISTAL/" + }, + "type": "module", + "exports": { + ".": "./src/index.ts" + }, + "main": "./src/index.ts", + "scripts": { + "build": "tsc --project tsconfig.json && vite build", + "clean": "rimraf dist", + "lint": "eslint \"./src/**/*.{ts,tsx}\" --max-warnings=0" + }, + "dependencies": { + "@xwiki/cristal-api": "workspace:*", + "@xwiki/cristal-model-api": "workspace:*", + "inversify": "6.0.3" + }, + "publishConfig": { + "exports": { + ".": { + "import": "./dist/index.es.js", + "require": "./dist/index.umd.js", + "types": "./dist/index.d.ts" + } + }, + "main": "./dist/index.es.js", + "types": "./dist/index.d.ts" + } +} diff --git a/core/model/model-remote-url/model-remote-url-api/src/componentInit.ts b/core/model/model-remote-url/model-remote-url-api/src/componentInit.ts new file mode 100644 index 000000000..0277df189 --- /dev/null +++ b/core/model/model-remote-url/model-remote-url-api/src/componentInit.ts @@ -0,0 +1,39 @@ +/* + * See the LICENSE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +import { DefaultRemoteURLParserProvider } from "./defaultRemoteURLParserProvider"; +import { RemoteURLParserProvider } from "./remoteURLParserProvider"; +import { DefaultRemoteURLSerializerProvider } from "./defaultRemoteURLSerializerProvider"; +import { RemoteURLSerializerProvider } from "./remoteURLSerializerProvider"; +import type { Container } from "inversify"; + +class ComponentInit { + constructor(container: Container) { + container + .bind("RemoteURLParserProvider") + .to(DefaultRemoteURLParserProvider) + .inSingletonScope(); + container + .bind("RemoteURLSerializerProvider") + .to(DefaultRemoteURLSerializerProvider) + .inSingletonScope(); + } +} + +export { ComponentInit }; diff --git a/core/model/model-remote-url/model-remote-url-api/src/defaultRemoteURLParserProvider.ts b/core/model/model-remote-url/model-remote-url-api/src/defaultRemoteURLParserProvider.ts new file mode 100644 index 000000000..2ea08a22c --- /dev/null +++ b/core/model/model-remote-url/model-remote-url-api/src/defaultRemoteURLParserProvider.ts @@ -0,0 +1,46 @@ +/* + * See the LICENSE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +import { RemoteURLParser } from "./remoteURLParser"; +import { RemoteURLParserProvider } from "./remoteURLParserProvider"; +import { type CristalApp } from "@xwiki/cristal-api"; +import { inject, injectable } from "inversify"; + +@injectable() +class DefaultRemoteURLParserProvider implements RemoteURLParserProvider { + constructor( + @inject("CristalApp") private cristalApp: CristalApp, + ) {} + + get(type?: string): RemoteURLParser | undefined { + const resolvedType = type || this.cristalApp.getWikiConfig().getType(); + try { + return this.cristalApp + .getContainer() + .getNamed("RemoteURLParser", resolvedType); + } catch (e) { + this.cristalApp + .getLogger("remote-url.api") + .warn(`Couldn't resolve RemoteURLParser for type=[${resolvedType}]`, e); + return undefined; + } + } +} + +export { DefaultRemoteURLParserProvider }; diff --git a/core/model/model-remote-url/model-remote-url-api/src/defaultRemoteURLSerializerProvider.ts b/core/model/model-remote-url/model-remote-url-api/src/defaultRemoteURLSerializerProvider.ts new file mode 100644 index 000000000..d5464b5c1 --- /dev/null +++ b/core/model/model-remote-url/model-remote-url-api/src/defaultRemoteURLSerializerProvider.ts @@ -0,0 +1,51 @@ +/* + * See the LICENSE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +import { RemoteURLSerializer } from "./remoteURLSerializer"; +import { RemoteURLSerializerProvider } from "./remoteURLSerializerProvider"; +import { type CristalApp } from "@xwiki/cristal-api"; +import { inject, injectable } from "inversify"; + +@injectable() +class DefaultRemoteURLSerializerProvider + implements RemoteURLSerializerProvider +{ + constructor( + @inject("CristalApp") private cristalApp: CristalApp, + ) {} + + get(type?: string): RemoteURLSerializer | undefined { + const resolvedType = type || this.cristalApp.getWikiConfig().getType(); + try { + return this.cristalApp + .getContainer() + .getNamed("RemoteURLSerializer", resolvedType); + } catch (e) { + this.cristalApp + .getLogger("remote-url.api") + .warn( + `Couldn't resolve RemoteURLSerializer for type=[${resolvedType}]`, + e, + ); + return undefined; + } + } +} + +export { DefaultRemoteURLSerializerProvider }; diff --git a/core/model/model-remote-url/model-remote-url-api/src/index.ts b/core/model/model-remote-url/model-remote-url-api/src/index.ts new file mode 100644 index 000000000..b2e5872ab --- /dev/null +++ b/core/model/model-remote-url/model-remote-url-api/src/index.ts @@ -0,0 +1,32 @@ +/* + * See the LICENSE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +import { type RemoteURLParser } from "./remoteURLParser"; +import { type RemoteURLSerializer } from "./remoteURLSerializer"; +import { type RemoteURLParserProvider } from "./remoteURLParserProvider"; +import { type RemoteURLSerializerProvider } from "./remoteURLSerializerProvider"; +import { ComponentInit } from "./componentInit"; + +export { + type RemoteURLParser, + type RemoteURLSerializer, + type RemoteURLParserProvider, + type RemoteURLSerializerProvider, + ComponentInit, +}; diff --git a/core/model/model-remote-url/model-remote-url-api/src/remoteURLParser.ts b/core/model/model-remote-url/model-remote-url-api/src/remoteURLParser.ts new file mode 100644 index 000000000..d9f419c1b --- /dev/null +++ b/core/model/model-remote-url/model-remote-url-api/src/remoteURLParser.ts @@ -0,0 +1,30 @@ +/* + * See the LICENSE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +import { EntityReference } from "@xwiki/cristal-model-api"; + +/** + * @since 0.12 + * @throws {@link Error} in case of issue when parsing the url + */ +interface RemoteURLParser { + parse(url: string): EntityReference; +} + +export { type RemoteURLParser }; diff --git a/core/model/model-remote-url/model-remote-url-api/src/remoteURLParserProvider.ts b/core/model/model-remote-url/model-remote-url-api/src/remoteURLParserProvider.ts new file mode 100644 index 000000000..6935c0f92 --- /dev/null +++ b/core/model/model-remote-url/model-remote-url-api/src/remoteURLParserProvider.ts @@ -0,0 +1,29 @@ +/* + * See the LICENSE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +import { RemoteURLParser } from "./remoteURLParser"; + +/** + * @since 0.12 + */ +interface RemoteURLParserProvider { + get(type?: string): RemoteURLParser | undefined; +} + +export { type RemoteURLParserProvider }; diff --git a/core/model/model-remote-url/model-remote-url-api/src/remoteURLSerializer.ts b/core/model/model-remote-url/model-remote-url-api/src/remoteURLSerializer.ts new file mode 100644 index 000000000..dfa2887b9 --- /dev/null +++ b/core/model/model-remote-url/model-remote-url-api/src/remoteURLSerializer.ts @@ -0,0 +1,29 @@ +/* + * See the LICENSE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +import { EntityReference } from "@xwiki/cristal-model-api"; + +/** + * @since 0.12 + */ +interface RemoteURLSerializer { + serialize(reference: EntityReference): string; +} + +export { type RemoteURLSerializer }; diff --git a/core/model/model-remote-url/model-remote-url-api/src/remoteURLSerializerProvider.ts b/core/model/model-remote-url/model-remote-url-api/src/remoteURLSerializerProvider.ts new file mode 100644 index 000000000..ab1750719 --- /dev/null +++ b/core/model/model-remote-url/model-remote-url-api/src/remoteURLSerializerProvider.ts @@ -0,0 +1,29 @@ +/* + * See the LICENSE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +import { RemoteURLSerializer } from "./remoteURLSerializer"; + +/** + * @since 0.12 + */ +interface RemoteURLSerializerProvider { + get(type?: string): RemoteURLSerializer | undefined; +} + +export { type RemoteURLSerializerProvider }; diff --git a/core/model/model-remote-url/model-remote-url-api/tsconfig.json b/core/model/model-remote-url/model-remote-url-api/tsconfig.json new file mode 100644 index 000000000..6f99d4405 --- /dev/null +++ b/core/model/model-remote-url/model-remote-url-api/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "resolveJsonModule": true + }, + "extends": "../../../../tsconfig.json", + "include": [ + "./src/index.ts", + "./src/**/*" + ] +} diff --git a/core/model/model-remote-url/model-remote-url-api/tsdoc.json b/core/model/model-remote-url/model-remote-url-api/tsdoc.json new file mode 100644 index 000000000..76f784c0f --- /dev/null +++ b/core/model/model-remote-url/model-remote-url-api/tsdoc.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json", + "extends": ["../../../../tsdoc.json"] +} diff --git a/core/model/model-remote-url/model-remote-url-api/vite.config.ts b/core/model/model-remote-url/model-remote-url-api/vite.config.ts new file mode 100644 index 000000000..c1788fde2 --- /dev/null +++ b/core/model/model-remote-url/model-remote-url-api/vite.config.ts @@ -0,0 +1,23 @@ +/* + * See the LICENSE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +import { generateConfig } from "../../../../vite.config"; + +export default generateConfig(import.meta.url); diff --git a/core/model/model-remote-url/model-remote-url-nextcloud/package.json b/core/model/model-remote-url/model-remote-url-nextcloud/package.json new file mode 100644 index 000000000..557c8d2bb --- /dev/null +++ b/core/model/model-remote-url/model-remote-url-nextcloud/package.json @@ -0,0 +1,37 @@ +{ + "name": "@xwiki/cristal-remote-url-nextcloud", + "version": "0.11.0", + "license": "LGPL 2.1", + "author": "XWiki Org Community ", + "homepage": "https://cristal.xwiki.org/", + "repository": { + "type": "git", + "directory": "core/model-remote-url/model-remote-url-nextcloud", + "url": "https://github.com/xwiki-contrib/cristal/" + }, + "bugs": { + "url": "https://jira.xwiki.org/projects/CRISTAL/" + }, + "type": "module", + "exports": { + ".": "./src/index.ts" + }, + "main": "./src/index.ts", + "scripts": { + "build": "tsc --project tsconfig.json && vite build", + "clean": "rimraf dist", + "lint": "eslint \"./src/**/*.{ts,tsx}\" --max-warnings=0" + }, + "dependencies": {}, + "publishConfig": { + "exports": { + ".": { + "import": "./dist/index.es.js", + "require": "./dist/index.umd.js", + "types": "./dist/index.d.ts" + } + }, + "main": "./dist/index.es.js", + "types": "./dist/index.d.ts" + } +} diff --git a/core/model/model-remote-url/model-remote-url-nextcloud/tsconfig.json b/core/model/model-remote-url/model-remote-url-nextcloud/tsconfig.json new file mode 100644 index 000000000..6f99d4405 --- /dev/null +++ b/core/model/model-remote-url/model-remote-url-nextcloud/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "resolveJsonModule": true + }, + "extends": "../../../../tsconfig.json", + "include": [ + "./src/index.ts", + "./src/**/*" + ] +} diff --git a/core/model/model-remote-url/model-remote-url-nextcloud/tsdoc.json b/core/model/model-remote-url/model-remote-url-nextcloud/tsdoc.json new file mode 100644 index 000000000..76f784c0f --- /dev/null +++ b/core/model/model-remote-url/model-remote-url-nextcloud/tsdoc.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json", + "extends": ["../../../../tsdoc.json"] +} diff --git a/core/model/model-remote-url/model-remote-url-nextcloud/vite.config.ts b/core/model/model-remote-url/model-remote-url-nextcloud/vite.config.ts new file mode 100644 index 000000000..c1788fde2 --- /dev/null +++ b/core/model/model-remote-url/model-remote-url-nextcloud/vite.config.ts @@ -0,0 +1,23 @@ +/* + * See the LICENSE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +import { generateConfig } from "../../../../vite.config"; + +export default generateConfig(import.meta.url); diff --git a/core/model/model-remote-url/model-remote-url-xwiki/package.json b/core/model/model-remote-url/model-remote-url-xwiki/package.json new file mode 100644 index 000000000..8b39729cd --- /dev/null +++ b/core/model/model-remote-url/model-remote-url-xwiki/package.json @@ -0,0 +1,42 @@ +{ + "name": "@xwiki/cristal-remote-url-nextcloud", + "version": "0.11.0", + "license": "LGPL 2.1", + "author": "XWiki Org Community ", + "homepage": "https://cristal.xwiki.org/", + "repository": { + "type": "git", + "directory": "core/model-remote-url/model-remote-url-nextcloud", + "url": "https://github.com/xwiki-contrib/cristal/" + }, + "bugs": { + "url": "https://jira.xwiki.org/projects/CRISTAL/" + }, + "type": "module", + "exports": { + ".": "./src/index.ts" + }, + "main": "./src/index.ts", + "scripts": { + "build": "tsc --project tsconfig.json && vite build", + "clean": "rimraf dist", + "lint": "eslint \"./src/**/*.{ts,tsx}\" --max-warnings=0" + }, + "dependencies": { + "@xwiki/cristal-api": "workspace:*", + "@xwiki/cristal-model-api": "workspace:*", + "@xwiki/cristal-remote-url-api": "workspace:*", + "inversify": "6.0.3" + }, + "publishConfig": { + "exports": { + ".": { + "import": "./dist/index.es.js", + "require": "./dist/index.umd.js", + "types": "./dist/index.d.ts" + } + }, + "main": "./dist/index.es.js", + "types": "./dist/index.d.ts" + } +} diff --git a/core/model/model-remote-url/model-remote-url-xwiki/src/componentInit.ts b/core/model/model-remote-url/model-remote-url-xwiki/src/componentInit.ts new file mode 100644 index 000000000..640ef5e87 --- /dev/null +++ b/core/model/model-remote-url/model-remote-url-xwiki/src/componentInit.ts @@ -0,0 +1,32 @@ +/* + * See the LICENSE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +import { XWikiRemoteURLParser } from "./xWikiRemoteURLParser"; +import { Container } from "inversify"; +import { RemoteURLParser } from "@xwiki/cristal-remote-url-api"; + +export class ComponentInit { + constructor(container: Container) { + container + .bind("RemoteURLParser") + .to(XWikiRemoteURLParser) + .inSingletonScope() + .whenTargetNamed("XWiki"); + } +} diff --git a/core/model/model-remote-url/model-remote-url-xwiki/src/index.ts b/core/model/model-remote-url/model-remote-url-xwiki/src/index.ts new file mode 100644 index 000000000..c980b4f91 --- /dev/null +++ b/core/model/model-remote-url/model-remote-url-xwiki/src/index.ts @@ -0,0 +1,19 @@ +/* + * See the LICENSE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ diff --git a/core/model/model-remote-url/model-remote-url-xwiki/src/xWikiRemoteURLParser.ts b/core/model/model-remote-url/model-remote-url-xwiki/src/xWikiRemoteURLParser.ts new file mode 100644 index 000000000..6f1ce0dd1 --- /dev/null +++ b/core/model/model-remote-url/model-remote-url-xwiki/src/xWikiRemoteURLParser.ts @@ -0,0 +1,76 @@ +/* + * See the LICENSE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +import { RemoteURLParser } from "@xwiki/cristal-remote-url-api"; +import { inject, injectable } from "inversify"; +import { + AttachmentReference, + DocumentReference, + type EntityReference, + SpaceReference, +} from "@xwiki/cristal-model-api"; +import { type CristalApp } from "@xwiki/cristal-api"; + +@injectable() +class XWikiRemoteURLParser implements RemoteURLParser { + constructor( + @inject("CristalApp") private readonly cristalApp: CristalApp, + ) {} + + parse(urlStr: string): EntityReference { + const baseURLstr = this.cristalApp.getWikiConfig().baseURL; + if (!urlStr.startsWith(baseURLstr)) + throw new Error( + `[${urlStr}] does not start with base url [${baseURLstr}]`, + ); + const baseURL = new URL(baseURLstr); + const url = new URL(urlStr); + + const endPath = url.pathname.replace(baseURL.pathname, ""); + let segments = endPath.split("/"); + if (segments[0] === "") segments = segments.slice(1); + const [bin, action] = segments; + // TODO: the current approach is easy but does not work if some url rewriting is done in front of XWiki. + if (bin == "bin" && action == "view") { + segments = segments.slice(2); + const pageName = segments[segments.length - 1]; + const spaces = segments.slice(0, segments.length - 1); + return new DocumentReference( + pageName, + new SpaceReference(undefined, ...spaces), + ); + } else if (bin == "bin" && action == "download") { + segments = segments.slice(2); + const attachmentName = segments[segments.length - 1]; + const pageName = segments[segments.length - 2]; + const spaces = segments.slice(0, segments.length - 2); + return new AttachmentReference( + attachmentName, + new DocumentReference( + pageName, + new SpaceReference(undefined, ...spaces), + ), + ); + } else { + throw new Error(`Impossible to resolve [${urlStr}] to an entity`); + } + } +} + +export { XWikiRemoteURLParser }; diff --git a/core/model/model-remote-url/model-remote-url-xwiki/tsconfig.json b/core/model/model-remote-url/model-remote-url-xwiki/tsconfig.json new file mode 100644 index 000000000..6f99d4405 --- /dev/null +++ b/core/model/model-remote-url/model-remote-url-xwiki/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "resolveJsonModule": true + }, + "extends": "../../../../tsconfig.json", + "include": [ + "./src/index.ts", + "./src/**/*" + ] +} diff --git a/core/model/model-remote-url/model-remote-url-xwiki/tsdoc.json b/core/model/model-remote-url/model-remote-url-xwiki/tsdoc.json new file mode 100644 index 000000000..76f784c0f --- /dev/null +++ b/core/model/model-remote-url/model-remote-url-xwiki/tsdoc.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json", + "extends": ["../../../../tsdoc.json"] +} diff --git a/core/model/model-remote-url/model-remote-url-xwiki/vite.config.ts b/core/model/model-remote-url/model-remote-url-xwiki/vite.config.ts new file mode 100644 index 000000000..c1788fde2 --- /dev/null +++ b/core/model/model-remote-url/model-remote-url-xwiki/vite.config.ts @@ -0,0 +1,23 @@ +/* + * See the LICENSE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +import { generateConfig } from "../../../../vite.config"; + +export default generateConfig(import.meta.url); diff --git a/lib/package.json b/lib/package.json index b8074ad35..14efd33db 100644 --- a/lib/package.json +++ b/lib/package.json @@ -52,6 +52,7 @@ "@xwiki/cristal-link-suggest-nextcloud": "workspace:*", "@xwiki/cristal-link-suggest-xwiki": "workspace:*", "@xwiki/cristal-macros": "workspace:*", + "@xwiki/cristal-model-click-listener": "workspace:*", "@xwiki/cristal-page-actions-default": "workspace:*", "@xwiki/cristal-page-actions-ui": "workspace:*", "@xwiki/cristal-rendering": "workspace:*", diff --git a/lib/src/components/DefaultCristalApp.ts b/lib/src/components/DefaultCristalApp.ts index fb5714479..fef1360fd 100644 --- a/lib/src/components/DefaultCristalApp.ts +++ b/lib/src/components/DefaultCristalApp.ts @@ -319,6 +319,10 @@ export class DefaultCristalApp implements CristalApp { } } + /** + * + * @deprecated + */ async loadPageFromURL(url: string): Promise { this.logger?.debug("Trying to load", url); const page = this.getWikiConfig().storage.getPageFromViewURL(url); diff --git a/lib/src/staticBuild.ts b/lib/src/staticBuild.ts index a2ced60a5..922a2cd91 100644 --- a/lib/src/staticBuild.ts +++ b/lib/src/staticBuild.ts @@ -45,6 +45,7 @@ import { ComponentInit as DocumentComponentInit } from "@xwiki/cristal-document- import { ComponentInit as AlertsDefaultComponentInit } from "@xwiki/cristal-alerts-default"; import { ComponentInit as ActionsPagesComponentInit } from "@xwiki/cristal-page-actions-default"; import { ComponentInit as ActionsPagesUIComponentInit } from "@xwiki/cristal-page-actions-ui"; +import { ComponentInit as ClickListenerComponentInit } from "@xwiki/cristal-model-click-listener"; import type { Container } from "inversify"; export class StaticBuild { @@ -84,6 +85,7 @@ export class StaticBuild { new AlertsDefaultComponentInit(container); new ActionsPagesComponentInit(container); new ActionsPagesUIComponentInit(container); + new ClickListenerComponentInit(container); } if (additionalComponents) { additionalComponents(container); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d769ccd6d..017f16207 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -645,6 +645,62 @@ importers: specifier: 6.0.3 version: 6.0.3 + core/model/model-api: {} + + core/model/model-click-listener: + dependencies: + '@xwiki/cristal-model-api': + specifier: workspace:* + version: link:../model-api + '@xwiki/cristal-remote-url-api': + specifier: workspace:* + version: link:../model-remote-url/model-remote-url-api + inversify: + specifier: 6.0.3 + version: 6.0.3 + + core/model/model-local-url: + dependencies: + '@xwiki/cristal-model-api': + specifier: workspace:* + version: link:../model-api + inversify: + specifier: 6.0.3 + version: 6.0.3 + devDependencies: + vitest: + specifier: 2.1.4 + version: 2.1.4(@types/node@22.8.7)(happy-dom@14.12.0) + + core/model/model-remote-url/model-remote-url-api: + dependencies: + '@xwiki/cristal-api': + specifier: workspace:* + version: link:../../../../api + '@xwiki/cristal-model-api': + specifier: workspace:* + version: link:../../model-api + inversify: + specifier: 6.0.3 + version: 6.0.3 + + core/model/model-remote-url/model-remote-url-nextcloud: {} + + core/model/model-remote-url/model-remote-url-xwiki: + dependencies: + '@xwiki/cristal-api': + specifier: workspace:* + version: link:../../../../api + '@xwiki/cristal-model-api': + specifier: workspace:* + version: link:../../model-api + '@xwiki/cristal-remote-url-api': + specifier: workspace:* + version: link:../model-remote-url-api + inversify: + specifier: 6.0.3 + version: 6.0.3 + core/navigation-tree/navigation-tree-api: dependencies: '@xwiki/cristal-api': @@ -1355,6 +1411,9 @@ importers: '@xwiki/cristal-macros': specifier: workspace:* version: link:../macros + '@xwiki/cristal-model-click-listener': + specifier: workspace:* + version: link:../core/model/model-click-listener '@xwiki/cristal-page-actions-default': specifier: workspace:* version: link:../core/page-actions/page-actions-default @@ -1574,6 +1633,9 @@ importers: '@xwiki/cristal-info-actions-ui': specifier: workspace:* version: link:../core/info-actions/info-actions-ui + '@xwiki/cristal-model-click-listener': + specifier: workspace:* + version: link:../core/model/model-click-listener '@xwiki/cristal-navigation-tree-api': specifier: workspace:* version: link:../core/navigation-tree/navigation-tree-api @@ -6542,37 +6604,6 @@ packages: terser: optional: true - vite@5.4.9: - resolution: {integrity: sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 - less: '*' - lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - vitest@2.1.4: resolution: {integrity: sha512-eDjxbVAJw1UJJCHr5xr/xM86Zx+YxIEXGAR+bmnEID7z9qWfoxpHw0zdobz+TQAFOLT+nEXz3+gx6nUJ7RgmlQ==} engines: {node: ^18.0.0 || >=20.0.0} @@ -8332,13 +8363,13 @@ snapshots: chai: 5.1.2 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.4(vite@5.4.9(@types/node@22.8.7))': + '@vitest/mocker@2.1.4(vite@5.4.10(@types/node@22.8.7))': dependencies: '@vitest/spy': 2.1.4 estree-walker: 3.0.3 magic-string: 0.30.12 optionalDependencies: - vite: 5.4.9(@types/node@22.8.7) + vite: 5.4.10(@types/node@22.8.7) '@vitest/pretty-format@2.1.4': dependencies: @@ -12211,19 +12242,10 @@ snapshots: '@types/node': 22.8.7 fsevents: 2.3.3 - vite@5.4.9(@types/node@22.8.7): - dependencies: - esbuild: 0.21.5 - postcss: 8.4.47 - rollup: 4.24.0 - optionalDependencies: - '@types/node': 22.8.7 - fsevents: 2.3.3 - vitest@2.1.4(@types/node@22.8.7)(happy-dom@14.12.0): dependencies: '@vitest/expect': 2.1.4 - '@vitest/mocker': 2.1.4(vite@5.4.9(@types/node@22.8.7)) + '@vitest/mocker': 2.1.4(vite@5.4.10(@types/node@22.8.7)) '@vitest/pretty-format': 2.1.4 '@vitest/runner': 2.1.4 '@vitest/snapshot': 2.1.4 @@ -12239,7 +12261,7 @@ snapshots: tinyexec: 0.3.1 tinypool: 1.0.1 tinyrainbow: 1.2.0 - vite: 5.4.9(@types/node@22.8.7) + vite: 5.4.10(@types/node@22.8.7) vite-node: 2.1.4(@types/node@22.8.7) why-is-node-running: 2.3.0 optionalDependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 4fb6b9c44..b9141dd90 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -28,6 +28,11 @@ packages: - "core/icons" - "core/info-actions/*" - "core/link-suggest/*" + - "core/model/model-api" + - "core/model/model-click-listener" + - "core/model/model-local-url" + - "core/model/model-reference/*" + - "core/model/model-remote-url/*" - "core/navigation-tree/*" - "core/page-actions/*" - "core/uiextension/*" diff --git a/skin/package.json b/skin/package.json index 7964fc112..97ee19644 100644 --- a/skin/package.json +++ b/skin/package.json @@ -36,6 +36,7 @@ "@xwiki/cristal-icons": "workspace:*", "@xwiki/cristal-info-actions-api": "workspace:*", "@xwiki/cristal-info-actions-ui": "workspace:*", + "@xwiki/cristal-model-click-listener": "workspace:*", "@xwiki/cristal-navigation-tree-api": "workspace:*", "@xwiki/cristal-page-actions-ui": "workspace:*", "@xwiki/cristal-uiextension-api": "workspace:*", diff --git a/skin/src/vue/contentTools.ts b/skin/src/vue/contentTools.ts index 3413f081a..3ef578062 100644 --- a/skin/src/vue/contentTools.ts +++ b/skin/src/vue/contentTools.ts @@ -22,6 +22,7 @@ import { createVNode, render } from "vue"; import type { CristalApp, Logger } from "@xwiki/cristal-api"; import type { MacroProvider } from "../api/macroProvider"; import type { App, Component, VNode } from "vue"; +import { ClickListener } from "@xwiki/cristal-model-click-listener"; // import { DefaultMacroData } from "../components/defaultMacroData"; @@ -32,10 +33,23 @@ export class ContentTools { this.logger = cristal?.getLogger("skin.vue.contenttools"); } - /* - Method to intercept clicks in the HTML content and load the page using Cristal Wiki - */ - public static listenToClicks( + /** + * Method to intercept clicks in the HTML content and load the page using Cristal Wiki + */ public static listenToClicks( + element: HTMLElement, + cristal: CristalApp | undefined, + ): void { + const clickListener = cristal + ?.getContainer() + .get("ClickListener"); + clickListener?.handle(element); + } + + /** + * Method to intercept clicks in the HTML content and load the page using Cristal Wiki + * @deprecated + */ + public static listenToClicksOld( element: HTMLElement, cristal: CristalApp | undefined, ): void { diff --git a/tsdoc.json b/tsdoc.json index 34c6173a0..ff79f890b 100644 --- a/tsdoc.json +++ b/tsdoc.json @@ -8,6 +8,8 @@ "@since": true, "@param": true, "@returns": true, - "@deprecated": true + "@deprecated": true, + "@throws": true, + "@link": true } } From 133c9bb0e9cf36733d92e9e968d214aa9dbbfba4 Mon Sep 17 00:00:00 2001 From: Manuel Leduc Date: Fri, 8 Nov 2024 15:05:16 +0100 Subject: [PATCH 2/9] --wip-- [skip ci] --- core/model/model-click-listener/package.json | 2 +- .../src/DefaultClickListener.ts | 2 +- core/model/model-local-url/package.json | 6 +- .../model-remote-url-api/package.json | 2 +- .../model-remote-url-nextcloud/package.json | 2 +- .../model-remote-url-nextcloud/src/index.ts | 19 ++++++ .../model-remote-url-xwiki/package.json | 4 +- .../src/componentInit.ts | 2 +- .../model-remote-url-xwiki/src/index.ts | 4 ++ .../src/xWikiRemoteURLParser.ts | 3 +- lib/package.json | 2 + lib/src/staticBuild.ts | 4 ++ pnpm-lock.yaml | 10 ++- skin/src/vue/contentTools.ts | 68 ++----------------- 14 files changed, 53 insertions(+), 77 deletions(-) create mode 100644 core/model/model-remote-url/model-remote-url-nextcloud/src/index.ts diff --git a/core/model/model-click-listener/package.json b/core/model/model-click-listener/package.json index 11fd4f878..4d8d8fdae 100644 --- a/core/model/model-click-listener/package.json +++ b/core/model/model-click-listener/package.json @@ -24,7 +24,7 @@ }, "dependencies": { "@xwiki/cristal-model-api": "workspace:*", - "@xwiki/cristal-remote-url-api": "workspace:*", + "@xwiki/cristal-model-remote-url-api": "workspace:*", "inversify": "6.0.3" }, "publishConfig": { diff --git a/core/model/model-click-listener/src/DefaultClickListener.ts b/core/model/model-click-listener/src/DefaultClickListener.ts index 2c6dabd0b..d09e659f9 100644 --- a/core/model/model-click-listener/src/DefaultClickListener.ts +++ b/core/model/model-click-listener/src/DefaultClickListener.ts @@ -20,7 +20,7 @@ import { ClickListener } from "./clickListener"; import { inject, injectable } from "inversify"; -import { type RemoteURLParserProvider } from "@xwiki/cristal-remote-url-api"; +import { type RemoteURLParserProvider } from "@xwiki/cristal-model-remote-url-api"; import { EntityType } from "@xwiki/cristal-model-api"; @injectable() diff --git a/core/model/model-local-url/package.json b/core/model/model-local-url/package.json index 36c78a45c..213f165d4 100644 --- a/core/model/model-local-url/package.json +++ b/core/model/model-local-url/package.json @@ -27,6 +27,9 @@ "@xwiki/cristal-model-api": "workspace:*", "inversify": "6.0.3" }, + "devDependencies": { + "vitest": "2.1.4" + }, "publishConfig": { "exports": { ".": { @@ -37,8 +40,5 @@ }, "main": "./dist/index.es.js", "types": "./dist/index.d.ts" - }, - "devDependencies": { - "vitest": "2.1.4" } } diff --git a/core/model/model-remote-url/model-remote-url-api/package.json b/core/model/model-remote-url/model-remote-url-api/package.json index 104669566..bdc6beda5 100644 --- a/core/model/model-remote-url/model-remote-url-api/package.json +++ b/core/model/model-remote-url/model-remote-url-api/package.json @@ -1,5 +1,5 @@ { - "name": "@xwiki/cristal-remote-url-api", + "name": "@xwiki/cristal-model-remote-url-api", "version": "0.11.0", "license": "LGPL 2.1", "author": "XWiki Org Community ", diff --git a/core/model/model-remote-url/model-remote-url-nextcloud/package.json b/core/model/model-remote-url/model-remote-url-nextcloud/package.json index 557c8d2bb..877adba65 100644 --- a/core/model/model-remote-url/model-remote-url-nextcloud/package.json +++ b/core/model/model-remote-url/model-remote-url-nextcloud/package.json @@ -1,5 +1,5 @@ { - "name": "@xwiki/cristal-remote-url-nextcloud", + "name": "@xwiki/cristal-model-remote-url-nextcloud", "version": "0.11.0", "license": "LGPL 2.1", "author": "XWiki Org Community ", diff --git a/core/model/model-remote-url/model-remote-url-nextcloud/src/index.ts b/core/model/model-remote-url/model-remote-url-nextcloud/src/index.ts new file mode 100644 index 000000000..c980b4f91 --- /dev/null +++ b/core/model/model-remote-url/model-remote-url-nextcloud/src/index.ts @@ -0,0 +1,19 @@ +/* + * See the LICENSE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ diff --git a/core/model/model-remote-url/model-remote-url-xwiki/package.json b/core/model/model-remote-url/model-remote-url-xwiki/package.json index 8b39729cd..689d06b87 100644 --- a/core/model/model-remote-url/model-remote-url-xwiki/package.json +++ b/core/model/model-remote-url/model-remote-url-xwiki/package.json @@ -1,5 +1,5 @@ { - "name": "@xwiki/cristal-remote-url-nextcloud", + "name": "@xwiki/cristal-model-remote-url-xwiki", "version": "0.11.0", "license": "LGPL 2.1", "author": "XWiki Org Community ", @@ -25,7 +25,7 @@ "dependencies": { "@xwiki/cristal-api": "workspace:*", "@xwiki/cristal-model-api": "workspace:*", - "@xwiki/cristal-remote-url-api": "workspace:*", + "@xwiki/cristal-model-remote-url-api": "workspace:*", "inversify": "6.0.3" }, "publishConfig": { diff --git a/core/model/model-remote-url/model-remote-url-xwiki/src/componentInit.ts b/core/model/model-remote-url/model-remote-url-xwiki/src/componentInit.ts index 640ef5e87..1d114061a 100644 --- a/core/model/model-remote-url/model-remote-url-xwiki/src/componentInit.ts +++ b/core/model/model-remote-url/model-remote-url-xwiki/src/componentInit.ts @@ -19,7 +19,7 @@ */ import { XWikiRemoteURLParser } from "./xWikiRemoteURLParser"; import { Container } from "inversify"; -import { RemoteURLParser } from "@xwiki/cristal-remote-url-api"; +import { RemoteURLParser } from "@xwiki/cristal-model-remote-url-api"; export class ComponentInit { constructor(container: Container) { diff --git a/core/model/model-remote-url/model-remote-url-xwiki/src/index.ts b/core/model/model-remote-url/model-remote-url-xwiki/src/index.ts index c980b4f91..3571707e7 100644 --- a/core/model/model-remote-url/model-remote-url-xwiki/src/index.ts +++ b/core/model/model-remote-url/model-remote-url-xwiki/src/index.ts @@ -17,3 +17,7 @@ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ + +import { ComponentInit } from "./componentInit"; + +export { ComponentInit }; diff --git a/core/model/model-remote-url/model-remote-url-xwiki/src/xWikiRemoteURLParser.ts b/core/model/model-remote-url/model-remote-url-xwiki/src/xWikiRemoteURLParser.ts index 6f1ce0dd1..425bb3784 100644 --- a/core/model/model-remote-url/model-remote-url-xwiki/src/xWikiRemoteURLParser.ts +++ b/core/model/model-remote-url/model-remote-url-xwiki/src/xWikiRemoteURLParser.ts @@ -17,7 +17,7 @@ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ -import { RemoteURLParser } from "@xwiki/cristal-remote-url-api"; +import { RemoteURLParser } from "@xwiki/cristal-model-remote-url-api"; import { inject, injectable } from "inversify"; import { AttachmentReference, @@ -45,6 +45,7 @@ class XWikiRemoteURLParser implements RemoteURLParser { const endPath = url.pathname.replace(baseURL.pathname, ""); let segments = endPath.split("/"); if (segments[0] === "") segments = segments.slice(1); + if (segments[segments.length - 1] === "") segments.pop(); const [bin, action] = segments; // TODO: the current approach is easy but does not work if some url rewriting is done in front of XWiki. if (bin == "bin" && action == "view") { diff --git a/lib/package.json b/lib/package.json index 14efd33db..f544de7ca 100644 --- a/lib/package.json +++ b/lib/package.json @@ -53,6 +53,8 @@ "@xwiki/cristal-link-suggest-xwiki": "workspace:*", "@xwiki/cristal-macros": "workspace:*", "@xwiki/cristal-model-click-listener": "workspace:*", + "@xwiki/cristal-model-remote-url-api": "workspace:*", + "@xwiki/cristal-model-remote-url-xwiki": "workspace:*", "@xwiki/cristal-page-actions-default": "workspace:*", "@xwiki/cristal-page-actions-ui": "workspace:*", "@xwiki/cristal-rendering": "workspace:*", diff --git a/lib/src/staticBuild.ts b/lib/src/staticBuild.ts index 922a2cd91..f454828cf 100644 --- a/lib/src/staticBuild.ts +++ b/lib/src/staticBuild.ts @@ -46,6 +46,8 @@ import { ComponentInit as AlertsDefaultComponentInit } from "@xwiki/cristal-aler import { ComponentInit as ActionsPagesComponentInit } from "@xwiki/cristal-page-actions-default"; import { ComponentInit as ActionsPagesUIComponentInit } from "@xwiki/cristal-page-actions-ui"; import { ComponentInit as ClickListenerComponentInit } from "@xwiki/cristal-model-click-listener"; +import { ComponentInit as ModelRemoteURLAPIComponentInit } from "@xwiki/cristal-model-remote-url-api"; +import { ComponentInit as ModelRemoteURLXWikiComponentInit } from "@xwiki/cristal-model-remote-url-xwiki"; import type { Container } from "inversify"; export class StaticBuild { @@ -86,6 +88,8 @@ export class StaticBuild { new ActionsPagesComponentInit(container); new ActionsPagesUIComponentInit(container); new ClickListenerComponentInit(container); + new ModelRemoteURLAPIComponentInit(container); + new ModelRemoteURLXWikiComponentInit(container); } if (additionalComponents) { additionalComponents(container); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 017f16207..91e940112 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -652,7 +652,7 @@ importers: '@xwiki/cristal-model-api': specifier: workspace:* version: link:../model-api - '@xwiki/cristal-remote-url-api': + '@xwiki/cristal-model-remote-url-api': specifier: workspace:* version: link:../model-remote-url/model-remote-url-api inversify: @@ -694,7 +694,7 @@ importers: '@xwiki/cristal-model-api': specifier: workspace:* version: link:../../model-api - '@xwiki/cristal-remote-url-api': + '@xwiki/cristal-model-remote-url-api': specifier: workspace:* version: link:../model-remote-url-api inversify: @@ -1414,6 +1414,12 @@ importers: '@xwiki/cristal-model-click-listener': specifier: workspace:* version: link:../core/model/model-click-listener + '@xwiki/cristal-model-remote-url-api': + specifier: workspace:* + version: link:../core/model/model-remote-url/model-remote-url-api + '@xwiki/cristal-model-remote-url-xwiki': + specifier: workspace:* + version: link:../core/model/model-remote-url/model-remote-url-xwiki '@xwiki/cristal-page-actions-default': specifier: workspace:* version: link:../core/page-actions/page-actions-default diff --git a/skin/src/vue/contentTools.ts b/skin/src/vue/contentTools.ts index 3ef578062..1cbdd39ca 100644 --- a/skin/src/vue/contentTools.ts +++ b/skin/src/vue/contentTools.ts @@ -19,12 +19,10 @@ */ import { createVNode, render } from "vue"; +import { ClickListener } from "@xwiki/cristal-model-click-listener"; import type { CristalApp, Logger } from "@xwiki/cristal-api"; import type { MacroProvider } from "../api/macroProvider"; import type { App, Component, VNode } from "vue"; -import { ClickListener } from "@xwiki/cristal-model-click-listener"; - -// import { DefaultMacroData } from "../components/defaultMacroData"; export class ContentTools { static logger: Logger; @@ -34,8 +32,9 @@ export class ContentTools { } /** - * Method to intercept clicks in the HTML content and load the page using Cristal Wiki - */ public static listenToClicks( + * Method to intercept clicks in the HTML content and load the page using Cristal Wiki. + */ + public static listenToClicks( element: HTMLElement, cristal: CristalApp | undefined, ): void { @@ -45,65 +44,6 @@ export class ContentTools { clickListener?.handle(element); } - /** - * Method to intercept clicks in the HTML content and load the page using Cristal Wiki - * @deprecated - */ - public static listenToClicksOld( - element: HTMLElement, - cristal: CristalApp | undefined, - ): void { - element.addEventListener( - `click`, - // TODO get rid of any - // eslint-disable-next-line @typescript-eslint/no-explicit-any - function handleClick(event: any) { - // We cannot use `closest()` because of possible shadow roots. - const origin = event - .composedPath() - .find((e: HTMLElement) => e.nodeName === "A"); - - if (origin?.href) { - ContentTools.logger?.debug("You clicked", origin.href); - ContentTools.logger?.debug(event.target); - ContentTools.logger?.debug(event.target.href); - ContentTools.logger?.debug(location); - ContentTools.logger?.debug(location.origin); - ContentTools.logger?.debug(location.hostname); - event.preventDefault(); - // Case 1: the link is relative and/or points to the current host. - if (origin.href.startsWith(location.origin)) { - ContentTools.logger?.debug("URL is relative URL"); - const page = origin.href.replace( - location.origin + location.pathname, - "", - ); - if (!page.startsWith("#")) { - ContentTools.logger?.debug("New page should be", page); - cristal?.setCurrentPage(page, "view"); - cristal?.loadPage().then(); - } else { - ContentTools.logger?.debug("Leaving alone page", page); - location = page; - return; - } - } else { - // Case 2: the link points to an external server, in this case we try to resolve it to a known page. - // Otherwise, the link is considered as external. - if (cristal != null) { - cristal.loadPageFromURL(origin.href).then(); - } else { - ContentTools.logger?.error( - "cristal object not injected properly in c-content.vue", - ); - } - } - } - }, - true, - ); - } - /* Method to load CSS sent by XWiki page */ From 6e5ecdff695ff5ff5d96d64d8f3623bfaf49b175 Mon Sep 17 00:00:00 2001 From: Manuel Leduc Date: Fri, 8 Nov 2024 15:22:02 +0100 Subject: [PATCH 3/9] --wip-- [skip ci] --- api/src/api/cristalApp.ts | 2 +- core/model/model-click-listener/package.json | 1 + .../src/DefaultClickListener.ts | 16 ++++- pnpm-lock.yaml | 3 + skin/src/vue/contentTools.ts | 60 ++++++++++++++++++- 5 files changed, 78 insertions(+), 4 deletions(-) diff --git a/api/src/api/cristalApp.ts b/api/src/api/cristalApp.ts index 30100e2c6..f7c42905a 100644 --- a/api/src/api/cristalApp.ts +++ b/api/src/api/cristalApp.ts @@ -68,7 +68,7 @@ export interface CristalApp { */ getCurrentSyntax(): string; - setCurrentPage(page: string, mode: string): void; + setCurrentPage(page: string, mode?: string): void; /** * @deprecated use the document-api instead diff --git a/core/model/model-click-listener/package.json b/core/model/model-click-listener/package.json index 4d8d8fdae..93d65cd0a 100644 --- a/core/model/model-click-listener/package.json +++ b/core/model/model-click-listener/package.json @@ -23,6 +23,7 @@ "lint": "eslint \"./src/**/*.{ts,tsx}\" --max-warnings=0" }, "dependencies": { + "@xwiki/cristal-api": "workspace:*", "@xwiki/cristal-model-api": "workspace:*", "@xwiki/cristal-model-remote-url-api": "workspace:*", "inversify": "6.0.3" diff --git a/core/model/model-click-listener/src/DefaultClickListener.ts b/core/model/model-click-listener/src/DefaultClickListener.ts index d09e659f9..35e6523b3 100644 --- a/core/model/model-click-listener/src/DefaultClickListener.ts +++ b/core/model/model-click-listener/src/DefaultClickListener.ts @@ -20,18 +20,27 @@ import { ClickListener } from "./clickListener"; import { inject, injectable } from "inversify"; -import { type RemoteURLParserProvider } from "@xwiki/cristal-model-remote-url-api"; +import { + type RemoteURLParserProvider, + type RemoteURLSerializerProvider, +} from "@xwiki/cristal-model-remote-url-api"; import { EntityType } from "@xwiki/cristal-model-api"; +import { type CristalApp } from "@xwiki/cristal-api"; @injectable() class DefaultClickListener implements ClickListener { constructor( @inject("RemoteURLParserProvider") private readonly remoteURLParserProvider: RemoteURLParserProvider, + @inject("RemoteURLSerializerProvider") + private readonly remoteURLSerializerProvider: RemoteURLSerializerProvider, + @inject("CristalApp") private readonly cristal: CristalApp, ) {} handle(element: HTMLElement): void { const remoteURLParser = this.remoteURLParserProvider.get(); + const remoteURLSerializer = this.remoteURLSerializerProvider.get()!; + const cristal = this.cristal; element.addEventListener( "click", function handleClick(event) { @@ -40,9 +49,12 @@ class DefaultClickListener implements ClickListener { const url = (event.target as HTMLLinkElement)?.href; try { const entityReference = remoteURLParser.parse(url); - if (entityReference.type == EntityType.DOCUMENT) { event.preventDefault(); + cristal.setCurrentPage( + // TODO: implement a serializer for XWiki references. + remoteURLSerializer.serialize(entityReference), + ); } else if (entityReference.type == EntityType.ATTACHMENT) { // TODO: see how to handle the attachment modal opening. } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 91e940112..b8d1881c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -649,6 +649,9 @@ importers: core/model/model-click-listener: dependencies: + '@xwiki/cristal-api': + specifier: workspace:* + version: link:../../../api '@xwiki/cristal-model-api': specifier: workspace:* version: link:../model-api diff --git a/skin/src/vue/contentTools.ts b/skin/src/vue/contentTools.ts index 1cbdd39ca..1566f05e7 100644 --- a/skin/src/vue/contentTools.ts +++ b/skin/src/vue/contentTools.ts @@ -34,7 +34,7 @@ export class ContentTools { /** * Method to intercept clicks in the HTML content and load the page using Cristal Wiki. */ - public static listenToClicks( + public static listenToClicksNew( element: HTMLElement, cristal: CristalApp | undefined, ): void { @@ -44,6 +44,64 @@ export class ContentTools { clickListener?.handle(element); } + /* + Method to intercept clicks in the HTML content and load the page using Cristal Wiki + */ + public static listenToClicks( + element: HTMLElement, + cristal: CristalApp | undefined, + ): void { + element.addEventListener( + `click`, + // TODO get rid of any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + function handleClick(event: any) { + // We cannot use `closest()` because of possible shadow roots. + const origin = event + .composedPath() + .find((e: HTMLElement) => e.nodeName === "A"); + + if (origin?.href) { + ContentTools.logger?.debug("You clicked", origin.href); + ContentTools.logger?.debug(event.target); + ContentTools.logger?.debug(event.target.href); + ContentTools.logger?.debug(location); + ContentTools.logger?.debug(location.origin); + ContentTools.logger?.debug(location.hostname); + event.preventDefault(); + // Case 1: the link is relative and/or points to the current host. + if (origin.href.startsWith(location.origin)) { + ContentTools.logger?.debug("URL is relative URL"); + const page = origin.href.replace( + location.origin + location.pathname, + "", + ); + if (!page.startsWith("#")) { + ContentTools.logger?.debug("New page should be", page); + cristal?.setCurrentPage(page, "view"); + cristal?.loadPage().then(); + } else { + ContentTools.logger?.debug("Leaving alone page", page); + location = page; + return; + } + } else { + // Case 2: the link points to an external server, in this case we try to resolve it to a known page. + // Otherwise, the link is considered as external. + if (cristal != null) { + cristal.loadPageFromURL(origin.href).then(); + } else { + ContentTools.logger?.error( + "cristal object not injected properly in c-content.vue", + ); + } + } + } + }, + true, + ); + } + /* Method to load CSS sent by XWiki page */ From 5f3dd0061fee27c5727f736cc3c31556a7b1820d Mon Sep 17 00:00:00 2001 From: Manuel Leduc Date: Tue, 12 Nov 2024 17:44:13 +0100 Subject: [PATCH 4/9] --wip-- [skip ci] --- .../src/DefaultClickListener.ts | 3 +- .../src/remoteURLSerializer.ts | 2 +- .../src/componentInit.ts | 11 ++- .../src/xWikiRemoteURLParser.ts | 8 +- .../src/xWikiRemoteURLSerializer.ts | 74 +++++++++++++++++++ skin/src/vue/contentTools.ts | 4 +- 6 files changed, 94 insertions(+), 8 deletions(-) create mode 100644 core/model/model-remote-url/model-remote-url-xwiki/src/xWikiRemoteURLSerializer.ts diff --git a/core/model/model-click-listener/src/DefaultClickListener.ts b/core/model/model-click-listener/src/DefaultClickListener.ts index f0871aad6..2a8dc64cd 100644 --- a/core/model/model-click-listener/src/DefaultClickListener.ts +++ b/core/model/model-click-listener/src/DefaultClickListener.ts @@ -52,8 +52,7 @@ class DefaultClickListener implements ClickListener { if (entityReference.type == EntityType.DOCUMENT) { event.preventDefault(); cristal.setCurrentPage( - // TODO: implement a serializer for XWiki references. - remoteURLSerializer.serialize(entityReference), + remoteURLSerializer.serialize(entityReference) || "", ); } else if (entityReference.type == EntityType.ATTACHMENT) { // TODO: see how to handle the attachment modal opening. diff --git a/core/model/model-remote-url/model-remote-url-api/src/remoteURLSerializer.ts b/core/model/model-remote-url/model-remote-url-api/src/remoteURLSerializer.ts index dfa2887b9..97ca3877a 100644 --- a/core/model/model-remote-url/model-remote-url-api/src/remoteURLSerializer.ts +++ b/core/model/model-remote-url/model-remote-url-api/src/remoteURLSerializer.ts @@ -23,7 +23,7 @@ import { EntityReference } from "@xwiki/cristal-model-api"; * @since 0.12 */ interface RemoteURLSerializer { - serialize(reference: EntityReference): string; + serialize(reference?: EntityReference): string | undefined; } export { type RemoteURLSerializer }; diff --git a/core/model/model-remote-url/model-remote-url-xwiki/src/componentInit.ts b/core/model/model-remote-url/model-remote-url-xwiki/src/componentInit.ts index 79c6df9a0..2040e5715 100644 --- a/core/model/model-remote-url/model-remote-url-xwiki/src/componentInit.ts +++ b/core/model/model-remote-url/model-remote-url-xwiki/src/componentInit.ts @@ -18,7 +18,11 @@ * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ import { XWikiRemoteURLParser } from "./xWikiRemoteURLParser"; -import { RemoteURLParser } from "@xwiki/cristal-model-remote-url-api"; +import { XWikiRemoteURLSerializer } from "./xWikiRemoteURLSerializer"; +import { + RemoteURLParser, + RemoteURLSerializer, +} from "@xwiki/cristal-model-remote-url-api"; import { Container } from "inversify"; export class ComponentInit { @@ -28,5 +32,10 @@ export class ComponentInit { .to(XWikiRemoteURLParser) .inSingletonScope() .whenTargetNamed("XWiki"); + container + .bind("RemoteURLSerializer") + .to(XWikiRemoteURLSerializer) + .inSingletonScope() + .whenTargetNamed("XWiki"); } } diff --git a/core/model/model-remote-url/model-remote-url-xwiki/src/xWikiRemoteURLParser.ts b/core/model/model-remote-url/model-remote-url-xwiki/src/xWikiRemoteURLParser.ts index 5344c692e..2021d8f94 100644 --- a/core/model/model-remote-url/model-remote-url-xwiki/src/xWikiRemoteURLParser.ts +++ b/core/model/model-remote-url/model-remote-url-xwiki/src/xWikiRemoteURLParser.ts @@ -44,8 +44,12 @@ class XWikiRemoteURLParser implements RemoteURLParser { const endPath = url.pathname.replace(baseURL.pathname, ""); let segments = endPath.split("/"); - if (segments[0] === "") segments = segments.slice(1); - if (segments[segments.length - 1] === "") segments.pop(); + if (segments[0] === "") { + segments = segments.slice(1); + } + if (segments[segments.length - 1] === "") { + segments[segments.length - 1] = "WebHome"; + } const [bin, action] = segments; // TODO: the current approach is easy but does not work if some url rewriting is done in front of XWiki. if (bin == "bin" && action == "view") { diff --git a/core/model/model-remote-url/model-remote-url-xwiki/src/xWikiRemoteURLSerializer.ts b/core/model/model-remote-url/model-remote-url-xwiki/src/xWikiRemoteURLSerializer.ts new file mode 100644 index 000000000..93cceae45 --- /dev/null +++ b/core/model/model-remote-url/model-remote-url-xwiki/src/xWikiRemoteURLSerializer.ts @@ -0,0 +1,74 @@ +/* + * See the LICENSE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +import { + AttachmentReference, + DocumentReference, + EntityType, + SpaceReference, + WikiReference, +} from "@xwiki/cristal-model-api"; +import { RemoteURLSerializer } from "@xwiki/cristal-model-remote-url-api"; +import { injectable } from "inversify"; +import type { EntityReference } from "@xwiki/cristal-model-api"; + +@injectable() +class XWikiRemoteURLSerializer implements RemoteURLSerializer { + serialize(reference?: EntityReference): string | undefined { + if (!reference) { + return undefined; + } + const type = reference.type; + const { SPACE, ATTACHMENT, DOCUMENT, WIKI } = EntityType; + switch (type) { + case WIKI: + return (reference as WikiReference).name; + case SPACE: { + const spaceReference = reference as SpaceReference; + const wiki = this.serialize(spaceReference.wiki); + const spaces = spaceReference.names.join("."); + if (wiki === undefined) { + return spaces; + } else { + return `${wiki}:${spaces}`; + } + } + case DOCUMENT: { + const documentReference = reference as DocumentReference; + const spaces = this.serialize(documentReference.space); + const name = documentReference.name; + if (spaces === undefined || spaces === "") { + return name; + } else { + return `${spaces}.${name}`; + } + } + case ATTACHMENT: { + const attachmentReference = reference as AttachmentReference; + const document = this.serialize(attachmentReference.document); + const name = attachmentReference.name; + return `${document}@${name}`; + } + default: + throw new Error(`Unknown reference type [${type}]`); + } + } +} + +export { XWikiRemoteURLSerializer }; diff --git a/skin/src/vue/contentTools.ts b/skin/src/vue/contentTools.ts index 19977d118..26990eab0 100644 --- a/skin/src/vue/contentTools.ts +++ b/skin/src/vue/contentTools.ts @@ -34,7 +34,7 @@ export class ContentTools { /** * Method to intercept clicks in the HTML content and load the page using Cristal Wiki. */ - public static listenToClicksNew( + public static listenToClicks( element: HTMLElement, cristal: CristalApp | undefined, ): void { @@ -47,7 +47,7 @@ export class ContentTools { /* Method to intercept clicks in the HTML content and load the page using Cristal Wiki */ - public static listenToClicks( + public static listenToClicksOld( element: HTMLElement, cristal: CristalApp | undefined, ): void { From b87f690b3a3b2a0435282c95097c16a1b1cc19e5 Mon Sep 17 00:00:00 2001 From: Manuel Leduc Date: Wed, 13 Nov 2024 10:11:41 +0100 Subject: [PATCH 5/9] --wip-- [skip ci] --- skin/src/vue/contentTools.ts | 102 ++++++++--------------------------- 1 file changed, 22 insertions(+), 80 deletions(-) diff --git a/skin/src/vue/contentTools.ts b/skin/src/vue/contentTools.ts index 26990eab0..fca3b2255 100644 --- a/skin/src/vue/contentTools.ts +++ b/skin/src/vue/contentTools.ts @@ -44,67 +44,9 @@ export class ContentTools { clickListener?.handle(element); } - /* - Method to intercept clicks in the HTML content and load the page using Cristal Wiki - */ - public static listenToClicksOld( - element: HTMLElement, - cristal: CristalApp | undefined, - ): void { - element.addEventListener( - `click`, - // TODO get rid of any - // eslint-disable-next-line @typescript-eslint/no-explicit-any - function handleClick(event: any) { - // We cannot use `closest()` because of possible shadow roots. - const origin = event - .composedPath() - .find((e: HTMLElement) => e.nodeName === "A"); - - if (origin?.href) { - ContentTools.logger?.debug("You clicked", origin.href); - ContentTools.logger?.debug(event.target); - ContentTools.logger?.debug(event.target.href); - ContentTools.logger?.debug(location); - ContentTools.logger?.debug(location.origin); - ContentTools.logger?.debug(location.hostname); - event.preventDefault(); - // Case 1: the link is relative and/or points to the current host. - if (origin.href.startsWith(location.origin)) { - ContentTools.logger?.debug("URL is relative URL"); - const page = origin.href.replace( - location.origin + location.pathname, - "", - ); - if (!page.startsWith("#")) { - ContentTools.logger?.debug("New page should be", page); - cristal?.setCurrentPage(page, "view"); - cristal?.loadPage().then(); - } else { - ContentTools.logger?.debug("Leaving alone page", page); - location = page; - return; - } - } else { - // Case 2: the link points to an external server, in this case we try to resolve it to a known page. - // Otherwise, the link is considered as external. - if (cristal != null) { - cristal.loadPageFromURL(origin.href).then(); - } else { - ContentTools.logger?.error( - "cristal object not injected properly in c-content.vue", - ); - } - } - } - }, - true, - ); - } - - /* - Method to load CSS sent by XWiki page - */ + /** + * Method to load CSS sent by XWiki page + */ public static loadCSS(css: string[]): void { if (css && css.length > 0) { // check new css @@ -125,10 +67,10 @@ export class ContentTools { } } - /* - Method to load JS send by XWiki page - THis code is not working - */ + /** + * Method to load JS send by XWiki page + * THis code is not working + */ public static loadJS(js: string[]): void { if (js && js.length > 0) { ContentTools.logger?.debug("Loading JS code for content"); @@ -222,9 +164,9 @@ export class ContentTools { } } - /* - Experimental function to transform scripts - */ + /** + * Experimental function to transform scripts + */ public static transformScripts(): void { const transformScript = function (scriptEl: HTMLScriptElement) { const srcItem = scriptEl.attributes.getNamedItem("src"); @@ -306,18 +248,18 @@ export class ContentTools { return destroy; } - /* - Method to look for Macros in client side rendering - Macros are inserted by the WikiModel parser using the following syntax -
-            
-          
- - Example with warning macro: -
-            
-          
- */ + /** + * Method to look for Macros in client side rendering + * Macros are inserted by the WikiModel parser using the following syntax + *
+   *     
+   *   
+ * + * Example with warning macro: + *
+   *     
+   *   
+ */ public static transformMacros( element: HTMLElement, cristal: CristalApp, From 0f217f506d468dab0f4e3ed4f8e69747848f215a Mon Sep 17 00:00:00 2001 From: Manuel Leduc Date: Wed, 13 Nov 2024 10:58:06 +0100 Subject: [PATCH 6/9] --wip-- [skip ci] --- core/attachments/attachments-api/package.json | 1 + .../attachments-api/src/attachment.ts | 11 +++++ .../attachments-api/src/attachmentsPreview.ts | 10 +++++ .../attachments-api/src/attachmentsService.ts | 34 ++++++++++++++ core/attachments/attachments-api/src/index.ts | 44 ++----------------- .../attachments-default/package.json | 1 + .../src/defaultAttachmentPreview.ts | 10 +++++ .../attachments-default/src/index.ts | 10 ++++- .../src/vue/AttachmentPreview.vue | 5 +++ .../attachments-ui/src/vue/AttachmentsTab.vue | 4 +- .../src/vue/AttachmentsTable.vue | 2 +- core/model/model-click-listener/package.json | 1 + .../src/DefaultClickListener.ts | 8 +++- pnpm-lock.yaml | 9 ++++ 14 files changed, 104 insertions(+), 46 deletions(-) create mode 100644 core/attachments/attachments-api/src/attachment.ts create mode 100644 core/attachments/attachments-api/src/attachmentsPreview.ts create mode 100644 core/attachments/attachments-api/src/attachmentsService.ts create mode 100644 core/attachments/attachments-default/src/defaultAttachmentPreview.ts create mode 100644 core/attachments/attachments-ui/src/vue/AttachmentPreview.vue diff --git a/core/attachments/attachments-api/package.json b/core/attachments/attachments-api/package.json index 565021ba8..c74b4d3b8 100644 --- a/core/attachments/attachments-api/package.json +++ b/core/attachments/attachments-api/package.json @@ -22,6 +22,7 @@ "lint": "eslint \"./src/**/*.{ts,tsx}\" --max-warnings=0" }, "dependencies": { + "@xwiki/cristal-model-api": "workspace:*", "vue": "3.5.12" }, "publishConfig": { diff --git a/core/attachments/attachments-api/src/attachment.ts b/core/attachments/attachments-api/src/attachment.ts new file mode 100644 index 000000000..8608fa4d2 --- /dev/null +++ b/core/attachments/attachments-api/src/attachment.ts @@ -0,0 +1,11 @@ +/** + * @since 0.9 + */ +interface Attachment { + id: string; + name: string; + mimetype: string; + href: string; +} + +export { type Attachment }; diff --git a/core/attachments/attachments-api/src/attachmentsPreview.ts b/core/attachments/attachments-api/src/attachmentsPreview.ts new file mode 100644 index 000000000..7e1d9a6a9 --- /dev/null +++ b/core/attachments/attachments-api/src/attachmentsPreview.ts @@ -0,0 +1,10 @@ +import { AttachmentReference } from "@xwiki/cristal-model-api"; + +/** + * @since 0.12 + */ +interface AttachmentPreview { + preview(attachment: AttachmentReference): void; +} + +export { type AttachmentPreview }; diff --git a/core/attachments/attachments-api/src/attachmentsService.ts b/core/attachments/attachments-api/src/attachmentsService.ts new file mode 100644 index 000000000..3ae4bf967 --- /dev/null +++ b/core/attachments/attachments-api/src/attachmentsService.ts @@ -0,0 +1,34 @@ +import { Attachment } from "./attachment"; +import { Ref } from "vue"; + +/** + * @since 0.9 + */ +interface AttachmentsService { + list(): Ref; + + count(): Ref; + + isLoading(): Ref; + + /** + * True while an attachment is uploading. + */ + isUploading(): Ref; + + getError(): Ref; + + /** + * Load the initial state of the attachments. + */ + refresh(page: string): Promise; + + /** + * Upload the provided list of files to a given page + * @param page - the page where to save the files + * @param files - the list of files to upload + */ + upload(page: string, files: File[]): Promise; +} + +export { type AttachmentsService }; diff --git a/core/attachments/attachments-api/src/index.ts b/core/attachments/attachments-api/src/index.ts index 3f2d5fdbf..c1dd4048b 100644 --- a/core/attachments/attachments-api/src/index.ts +++ b/core/attachments/attachments-api/src/index.ts @@ -17,44 +17,8 @@ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ +import { Attachment } from "./attachment"; +import { AttachmentPreview } from "./attachmentsPreview"; +import { AttachmentsService } from "./attachmentsService"; -import { Ref } from "vue"; - -/** - * @since 0.9 - */ -interface Attachment { - id: string; - name: string; - mimetype: string; - href: string; -} - -/** - * @since 0.9 - */ -interface AttachmentsService { - list(): Ref; - count(): Ref; - isLoading(): Ref; - - /** - * True while an attachment is uploading. - */ - isUploading(): Ref; - getError(): Ref; - - /** - * Load the initial state of the attachments. - */ - refresh(page: string): Promise; - - /** - * Upload the provided list of files to a given page - * @param page - the page where to save the files - * @param files - the list of files to upload - */ - upload(page: string, files: File[]): Promise; -} - -export type { Attachment, AttachmentsService }; +export type { Attachment, AttachmentPreview, AttachmentsService }; diff --git a/core/attachments/attachments-default/package.json b/core/attachments/attachments-default/package.json index 275bb5763..ab6de48bb 100644 --- a/core/attachments/attachments-default/package.json +++ b/core/attachments/attachments-default/package.json @@ -24,6 +24,7 @@ "dependencies": { "@xwiki/cristal-api": "workspace:*", "@xwiki/cristal-attachments-api": "workspace:*", + "@xwiki/cristal-model-api": "workspace:*", "inversify": "6.0.3", "pinia": "2.2.6", "vue": "3.5.12" diff --git a/core/attachments/attachments-default/src/defaultAttachmentPreview.ts b/core/attachments/attachments-default/src/defaultAttachmentPreview.ts new file mode 100644 index 000000000..c585b5a5a --- /dev/null +++ b/core/attachments/attachments-default/src/defaultAttachmentPreview.ts @@ -0,0 +1,10 @@ +import { AttachmentPreview } from "@xwiki/cristal-attachments-api"; +import { AttachmentReference } from "@xwiki/cristal-model-api"; + +class DefaultAttachmentPreview implements AttachmentPreview { + preview(attachment: AttachmentReference): void { + console.log("attachment", attachment); + } +} + +export { DefaultAttachmentPreview }; diff --git a/core/attachments/attachments-default/src/index.ts b/core/attachments/attachments-default/src/index.ts index 5c4f808cf..7513078dc 100644 --- a/core/attachments/attachments-default/src/index.ts +++ b/core/attachments/attachments-default/src/index.ts @@ -18,9 +18,13 @@ * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ +import { DefaultAttachmentPreview } from "./defaultAttachmentPreview"; import { DefaultAttachmentsService } from "./defaultAttachmentsService"; import { Container } from "inversify"; -import type { AttachmentsService } from "@xwiki/cristal-attachments-api"; +import type { + AttachmentPreview, + AttachmentsService, +} from "@xwiki/cristal-attachments-api"; export class ComponentInit { constructor(container: Container) { @@ -28,5 +32,9 @@ export class ComponentInit { .bind("AttachmentsService") .to(DefaultAttachmentsService) .inSingletonScope(); + container + .bind("AttachmentPreview") + .to(DefaultAttachmentPreview) + .inSingletonScope(); } } diff --git a/core/attachments/attachments-ui/src/vue/AttachmentPreview.vue b/core/attachments/attachments-ui/src/vue/AttachmentPreview.vue new file mode 100644 index 000000000..d879ed071 --- /dev/null +++ b/core/attachments/attachments-ui/src/vue/AttachmentPreview.vue @@ -0,0 +1,5 @@ + + + + + diff --git a/core/attachments/attachments-ui/src/vue/AttachmentsTab.vue b/core/attachments/attachments-ui/src/vue/AttachmentsTab.vue index a6566641a..92977deb1 100644 --- a/core/attachments/attachments-ui/src/vue/AttachmentsTab.vue +++ b/core/attachments/attachments-ui/src/vue/AttachmentsTab.vue @@ -18,11 +18,11 @@ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF site: http://www.fsf.org. --> + + + diff --git a/core/model/model-api/src/index.ts b/core/model/model-api/src/index.ts index 543c3669d..5b6a72247 100644 --- a/core/model/model-api/src/index.ts +++ b/core/model/model-api/src/index.ts @@ -80,7 +80,7 @@ class DocumentReference implements EntityReference { * @since 0.12 */ class AttachmentReference implements EntityReference { - type: EntityType = EntityType.SPACE; + type: EntityType = EntityType.ATTACHMENT; name: string; document: DocumentReference; diff --git a/core/model/model-click-listener/src/DefaultClickListener.ts b/core/model/model-click-listener/src/DefaultClickListener.ts index 5fb1827d1..21562a8ec 100644 --- a/core/model/model-click-listener/src/DefaultClickListener.ts +++ b/core/model/model-click-listener/src/DefaultClickListener.ts @@ -28,6 +28,9 @@ import type { RemoteURLSerializerProvider, } from "@xwiki/cristal-model-remote-url-api"; +/** + * @since 0.12 + */ @injectable() class DefaultClickListener implements ClickListener { constructor( @@ -59,6 +62,7 @@ class DefaultClickListener implements ClickListener { remoteURLSerializer.serialize(entityReference) || "", ); } else if (entityReference.type == EntityType.ATTACHMENT) { + event.preventDefault(); attachmentPreview.preview(entityReference as AttachmentReference); } } catch (e) { diff --git a/core/page-actions/page-actions-ui/src/vue/DeletePage.vue b/core/page-actions/page-actions-ui/src/vue/DeletePage.vue index e82a22381..f56af4624 100644 --- a/core/page-actions/page-actions-ui/src/vue/DeletePage.vue +++ b/core/page-actions/page-actions-ui/src/vue/DeletePage.vue @@ -19,15 +19,16 @@ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA --> +