diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ee67ea6..7d8688b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,9 @@ Since all contributors would need to start the application anyway, an exhaustive contributors guide is provided in-app after starting in development mode. +If you are serious about contributing to this repository, you can find a quick way to start the application in development mode below. Please be aware of the [Pull Request Rules](#pull-request-rules). Breaking these rules will result in reactions such as your Pull Request to be rejected or your account restricted in this repository. + +### Quickstart + This contributors guide is short and will only help you run a minimal working Torii app. First make sure you have the [Rust Programming Language](https://rust-lang.org/) (which comes with [cargo](https://doc.rust-lang.org/cargo/)) and [NodeJS](https://nodejs.org/en) (which comes with [npm](https://docs.npmjs.com/cli/v11/commands/npm) and [npx](https://docs.npmjs.com/cli/v11/commands/npx)) installed. This guide assumes you have some prior familiarity with these tools. First, install all client dependencies with the following command. You will have to run this command everytime when client dependencies change (e.g. after pulling from remote). This will generate the `node_modules` directory, which contains all client dependencies. @@ -22,3 +26,17 @@ Now you should see the application winow which comes with at least two default w cd torii-api go run src/main.go serve ``` --> + +### Pull Request Rules + +- **No vibes.** You are allowed to use smart, contextless autocompletion and ask smart assistants for planning and code review. However code which has been subject to agent generation is categorically not allowed in this repository. + +- **English Codebase.** We allow to interact with this repositories issuesses, discussions and comments in different languages. However excluded are all commit messages, source code and the title and first message in a Pull Request, which have to be in English. + +- **No obfuscastions.** Code using obfuscations for the sake of being unreadable is not allowed. Performance-optimized code has to be clearly documented with comments. + +- **Citations.** Code retrieved from external websites like StackOverflow have to be cited in the codebase. A citation is a comment with a link above the cited code segment. Links that helped you fix bugs or helped you choose an algorithm or approach are welcomed. + +- **Documentation.** All code is subject to exhaustive documentation. Make sure the formatting is not broken for documentation generators. For functions include example and usage. We try to keep a 1:1 to 1:2 (code lines:documentation) ratio. + +- **Rules are non-exhaustive.** The administrative team of this codebase reserves the right to make up new written rules when "unspoken rules" are broken. The "unspoken rules" include: Be Nice, Keep the Code Clean, Follow Existing Patterns or Evolve New Maintainable Patterns. diff --git a/ROADMAP.md b/ROADMAP.md index 6e008b7..3097f96 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -89,6 +89,18 @@ The first stage of the project is to create a simple file browser like applicati ### `v0.0.0-dev9` [NEXT] +- [ ] Client Refactoring (vue app and plugins first steps) + - [ ] Phase 1: Plugin API Core + - [x] `ToriiPlugin`: A separate library module defining plugin. + - [ ] Define plugin lifecycle hooks (`onInit`, `onRecordOpen`, `onComponentUpdate`, etc.) + - [ ] Define anchor point constants. Anchor points are views which can be extended by plugins. + - [ ] `PluginManager` with `register` new plugins and `getAnchorView()` to render an anchor screen. + - [ ] Phase 2: Call Plugin API + - [ ] Define a Vue anchor component (renderer) and place some in the app. + - [ ] Pass workspace context to plugins. + - [ ] Phase 3: Plugin Extraction + - [ ] `word-count` + - [ ] `recent-projects` - [x] Client Documentation (Typedoc Infrastructure) - [ ] Breadcrumb Header - [x] Expand Footer with Edit and View mode. @@ -107,7 +119,6 @@ The first stage of the project is to create a simple file browser like applicati ### `v0.0.0-dev10` -- [ ] Client Refactoring (vue app and plugins first steps) - [ ] Client Documentation (Github CI) - [ ] [clean-jsdoc-theme](https://www.npmjs.com/package/@clean-jsdoc-theme/typedoc) - [ ] Remove Naive UI dependency and implement custom vue components diff --git a/package-lock.json b/package-lock.json index f8f3f83..b9c5af8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "": { "name": "Torii", "workspaces": [ - "torii-client" + "torii-client/**" ], "devDependencies": { "@tauri-apps/cli": "^2.11.1", @@ -349,10 +349,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.29.7", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.29.7" + "@babel/types": "^7.29.8" }, "bin": { "parser": "bin/babel-parser.js" @@ -493,7 +495,9 @@ } }, "node_modules/@babel/types": { - "version": "7.29.7", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.29.7", @@ -1484,6 +1488,18 @@ "vue": "^3.0.0" } }, + "node_modules/@torii/plugin-core": { + "resolved": "torii-client/plugin-core", + "link": true + }, + "node_modules/@torii/plugin-debug": { + "resolved": "torii-client/plugin-debug", + "link": true + }, + "node_modules/@torii/webapp": { + "resolved": "torii-client/webapp", + "link": true + }, "node_modules/@types/body-parser": { "version": "1.19.6", "dev": true, @@ -2272,45 +2288,53 @@ "license": "ISC" }, "node_modules/@vue/compiler-core": { - "version": "3.5.39", + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.41.tgz", + "integrity": "sha512-q0Xtv/F9w2YO/7htQhtiL+Ev2WCJbe5N2hc+XfgyKkEKqWpSxknmT8QOuGdEKNdjPq0c3F7rNpFkTo3Kfrm7pg==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.7", - "@vue/shared": "3.5.39", + "@babel/parser": "^7.29.8", + "@vue/shared": "3.5.41", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-dom": { - "version": "3.5.39", + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.41.tgz", + "integrity": "sha512-oKacVfNglLvGjnS6BXOlGL7EyG2h8X03pqXCjzotRZUaXGjbrTJUnVAQjrCqUnS+lyu31nwQjZY/d817GmCnfw==", "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.5.39", - "@vue/shared": "3.5.39" + "@vue/compiler-core": "3.5.41", + "@vue/shared": "3.5.41" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.39", + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.41.tgz", + "integrity": "sha512-XJhip7R2wy6vX3knCxdZN4KracFaZUef58s1KYewqluedHIJaPIVfXoYT7MF1F8nCvv6k8bWWxDC8opMkg1VTQ==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.7", - "@vue/compiler-core": "3.5.39", - "@vue/compiler-dom": "3.5.39", - "@vue/compiler-ssr": "3.5.39", - "@vue/shared": "3.5.39", + "@babel/parser": "^7.29.8", + "@vue/compiler-core": "3.5.41", + "@vue/compiler-dom": "3.5.41", + "@vue/compiler-ssr": "3.5.41", + "@vue/shared": "3.5.41", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", - "postcss": "^8.5.15", + "postcss": "^8.5.19", "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.39", + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.41.tgz", + "integrity": "sha512-U3v5OejKEGqOI0Wy0+Sz7hGuIFZHA4LSXzrNM3IMIeDyJEBBfTpX26n3SDgToRpP2bLc9FfI2j/kSgcJ8Emq5A==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.39", - "@vue/shared": "3.5.39" + "@vue/compiler-dom": "3.5.41", + "@vue/shared": "3.5.41" } }, "node_modules/@vue/component-compiler-utils": { @@ -2448,43 +2472,51 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.5.39", + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.41.tgz", + "integrity": "sha512-rznsqKM0np0x18EjzF8x88MpEhdNsffbvFbckLL5+oUKz1BxAImEmO7J1ArRYSyo6aQaVoBDp7jEkT91OOxydA==", "license": "MIT", "dependencies": { - "@vue/shared": "3.5.39" + "@vue/shared": "3.5.41" } }, "node_modules/@vue/runtime-core": { - "version": "3.5.39", + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.41.tgz", + "integrity": "sha512-Vcry58hiAKwGen9Z1jUZE0feFsNArPCMOImYI8el48A9Idf6DuQYD0U05zZIF2Iad1hGhPSvcbBbAOhNr55fhg==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.39", - "@vue/shared": "3.5.39" + "@vue/reactivity": "3.5.41", + "@vue/shared": "3.5.41" } }, "node_modules/@vue/runtime-dom": { - "version": "3.5.39", + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.41.tgz", + "integrity": "sha512-3vVBahVBS9+U6cmXBLyb8nE6/yYo4J/CGI9eVFs3KiMc0YHuudwKyShTD65jtJy/L9PUUxNAFu4cj4LiJ0UFbw==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.39", - "@vue/runtime-core": "3.5.39", - "@vue/shared": "3.5.39", + "@vue/reactivity": "3.5.41", + "@vue/runtime-core": "3.5.41", + "@vue/shared": "3.5.41", "csstype": "^3.2.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.5.39", + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.41.tgz", + "integrity": "sha512-n6hx/pNFfbD6SuyeuMVkvqox8bwf/ET9JlA/kAz/imw8sw++wkqKe2mHX5KutjPpbKE4Z56yTHszoOjGMI9igQ==", "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.5.39", - "@vue/shared": "3.5.39" - }, - "peerDependencies": { - "vue": "3.5.39" + "@vue/compiler-ssr": "3.5.41", + "@vue/runtime-dom": "3.5.41", + "@vue/shared": "3.5.41" } }, "node_modules/@vue/shared": { - "version": "3.5.39", + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.41.tgz", + "integrity": "sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA==", "license": "MIT" }, "node_modules/@vue/vue-loader-v15": { @@ -10024,10 +10056,6 @@ "node": ">=0.6" } }, - "node_modules/torii-client": { - "resolved": "torii-client", - "link": true - }, "node_modules/totalist": { "version": "3.0.1", "dev": true, @@ -10581,14 +10609,16 @@ } }, "node_modules/vue": { - "version": "3.5.39", + "version": "3.5.41", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.41.tgz", + "integrity": "sha512-2laE0p+aK+/AOPG/XL/WepOs/GlK755LJ1XECi9kDUrz1FKNw8rb2Xzlw9JS1rqEV55nb0ttsKxVlTCcd+R5cg==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.39", - "@vue/compiler-sfc": "3.5.39", - "@vue/runtime-dom": "3.5.39", - "@vue/server-renderer": "3.5.39", - "@vue/shared": "3.5.39" + "@vue/compiler-dom": "3.5.41", + "@vue/compiler-sfc": "3.5.41", + "@vue/runtime-dom": "3.5.41", + "@vue/server-renderer": "3.5.41", + "@vue/shared": "3.5.41" }, "peerDependencies": { "typescript": "*" @@ -11580,6 +11610,63 @@ "peerDependencies": { "vue": "^3.2.0" } + }, + "torii-client/plugin-core": { + "name": "@torii/plugin-core", + "version": "0.1.0", + "dependencies": { + "vue": "^3.5.41" + } + }, + "torii-client/plugin-debug": { + "name": "@torii/plugin-debug", + "version": "0.1.0", + "dependencies": { + "@torii/plugin-core": "file:../plugin-core", + "vue": "^3.5.41" + } + }, + "torii-client/webapp": { + "name": "@torii/webapp", + "version": "0.1.0", + "dependencies": { + "@tauri-apps/api": "^2.11.0", + "@tauri-apps/plugin-dialog": "^2.7.1", + "@tauri-apps/plugin-opener": "^2.5.4", + "@tauri-apps/plugin-store": "^2.4.3", + "@tiptap/extension-list": "^3.27.1", + "@tiptap/markdown": "^3.27.1", + "@tiptap/pm": "^3.27.1", + "@tiptap/starter-kit": "^3.27.1", + "@tiptap/vue-3": "^3.27.1", + "@torii/plugin-core": "file:../plugin-core", + "pinia": "^3.0.4", + "typedoc": "^0.28.20", + "typedoc-plugin-vue": "^1.5.1", + "vue": "^3.2.13", + "vue-i18n": "^11.4.5", + "vue-router": "~4.0.3" + }, + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^5.4.0", + "@typescript-eslint/parser": "^5.4.0", + "@vicons/ionicons5": "^0.13.0", + "@vicons/utils": "^0.1.4", + "@vue/cli-plugin-eslint": "^5.0.9", + "@vue/cli-plugin-router": "~5.0.0", + "@vue/cli-plugin-typescript": "^5.0.9", + "@vue/cli-service": "~5.0.0", + "@vue/eslint-config-typescript": "^9.1.0", + "eslint": "^7.32.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-prettier": "^4.0.0", + "eslint-plugin-vue": "^8.0.3", + "naive-ui": "^2.44.1", + "prettier": "^2.4.1", + "sass": "^1.101.0", + "sass-loader": "^17.0.0", + "typescript": "~4.5.5" + } } } } diff --git a/package.json b/package.json index 1da485e..7ad38e3 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "Torii", "type": "module", "workspaces": [ - "torii-client" + "torii-client/**" ], "devDependencies": { "@tauri-apps/cli": "^2.11.1", diff --git a/torii-client/README.md b/torii-client/README.md index ed94801..bf1ee7e 100644 --- a/torii-client/README.md +++ b/torii-client/README.md @@ -1,24 +1,3 @@ -# torii-client +# Torii Client Infrastructure -## Project setup -``` -npm install -``` - -### Compiles and hot-reloads for development -``` -npm run serve -``` - -### Compiles and minifies for production -``` -npm run build -``` - -### Lints and fixes files -``` -npm run lint -``` - -### Customize configuration -See [Configuration Reference](https://cli.vuejs.org/config/). +- [webapp](./webapp/): The main "executable". This is what the Torii application "sees". diff --git a/torii-client/.prettierrc b/torii-client/plugin-core/.prettierrc similarity index 100% rename from torii-client/.prettierrc rename to torii-client/plugin-core/.prettierrc diff --git a/torii-client/plugin-core/package.json b/torii-client/plugin-core/package.json new file mode 100644 index 0000000..0f625ed --- /dev/null +++ b/torii-client/plugin-core/package.json @@ -0,0 +1,9 @@ +{ + "name": "@torii/plugin-core", + "version": "0.1.0", + "private": true, + "main": "src/lib.ts", + "dependencies": { + "vue": "^3.5.41" + } +} diff --git a/torii-client/plugin-core/src/lib.ts b/torii-client/plugin-core/src/lib.ts new file mode 100644 index 0000000..dac7dfb --- /dev/null +++ b/torii-client/plugin-core/src/lib.ts @@ -0,0 +1,9 @@ +import ToriiPlugin from "./plugin"; +import ToriiPluginLoader from "./plugin-loader"; +import ToriiPluginEvents from "./plugin-events"; + +export { + ToriiPlugin, + ToriiPluginLoader, + type ToriiPluginEvents +}; diff --git a/torii-client/plugin-core/src/plugin-events.ts b/torii-client/plugin-core/src/plugin-events.ts new file mode 100644 index 0000000..a26fcc8 --- /dev/null +++ b/torii-client/plugin-core/src/plugin-events.ts @@ -0,0 +1,32 @@ +/** + * @file plugin-events.ts + * @brief Defines all internally-understood Torii plugin events. + */ + +/** + * List of internallly-understood Torii events. A plugin can always emit custom + * events, but these will be ignored by internal Torii plugins. + */ +export type ToriiPluginEvents = { + // plugin lifecycle + ['plugin-activate']: () => void; + ['plugin-deactivate']: () => void; + + // workspace lifecycle + ['workspace-open']: (path: string) => void, + ['workspace-close']: (path: string) => void, + + // record lifecycle + ['record-open']: () => void; + ['record-close']: () => void; + ['record-create']: () => void; + ['record-rename']: () => void; + ['record-delete']: () => void; + + // records' individual component lifecycle + ['record-component-create']: () => void; + ['record-component-update']: () => void; + ['record-component-delete']: () => void; +}; + +export default ToriiPluginEvents; diff --git a/torii-client/plugin-core/src/plugin-loader.ts b/torii-client/plugin-core/src/plugin-loader.ts new file mode 100644 index 0000000..ed187cb --- /dev/null +++ b/torii-client/plugin-core/src/plugin-loader.ts @@ -0,0 +1,42 @@ +/** + * @file plugin-loader.ts + */ + +import ToriiPlugin from './plugin'; +import ToriiPluginEvents from './plugin-events'; + +/** + * @brief A Torii plugin loader is a scope of code. + */ +export class ToriiPluginLoader { + /** + * @brief Currently installed plugins. + */ + private readonly plugins: ToriiPlugin[] = []; + + /** + * @todo + */ + constructor() {} + + /** + * @brief Install a plugin. + */ + public install(plugin: ToriiPlugin) { + this.plugins.push(plugin); + plugin.emit('plugin-activate'); + } + + /** + * @brief Emits an event. + */ + public emit( + e: Event, + ...args: Parameters + ): this { + this.plugins.forEach((plugin) => plugin.emit(e, ...args)); + return this; + } +} + +export default ToriiPluginLoader; diff --git a/torii-client/plugin-core/src/plugin.ts b/torii-client/plugin-core/src/plugin.ts new file mode 100644 index 0000000..31a9fe6 --- /dev/null +++ b/torii-client/plugin-core/src/plugin.ts @@ -0,0 +1,285 @@ +/** + * @file plugin.ts + * @brief Defines the Torii Plugin extensible. + */ + +import ToriiPluginEvents from './plugin-events'; + +/** + * Quick-creation settings for the static {@link ToriiPlugin.create()} + * method. + */ +export interface ToriiPluginSettings { + /** + * @brief The unique identifier of the plugin, consisting of letters, + * numerics and dashes. You can prefix the plugin with a custom scope + * if needed. For example, `my-custom-scope:custom-plugin` would be an + * allowed plugin name. + * + * @note The prefix `core:` should only be used by libraries internal + * to the Torii project. + * + * @example + * + * { + * id: "core:markdown:hyperlinks", + * } + */ + id: string; + + /** + * Semver string of the plugin version, consisting of at least major, + * minor and optionally patch. + * + * + * @example + * + * { + * version: "1.2.0-dev4", + * } + */ + version: string; + + // /** + // * @brief Callback to be invoked when the plugin is initialized. This + // * can happen only once if the plugin is initialized at start. + // * + // * @note You should **not** compute anything in this function that + // * "activates" the plugin. This function is explicitly only for logic + // * that should only run once in the lifetime of the plugin. + // * + // * For setup behaviour when a plugin (that was previously deactivated) + // * is enabled again see {@link ToriiPluginSettings.onActivate|onActivate}. + // */ + // onInit?: () => void; + + /** + * @brief Callback to be invoked when the plugin is activated. This + * can happen multiple times over the runtime of the plugin. + * + * For destructor behaviour when the plugin is deactivated, you should + * use {@link ToriiPluginSettings.onDeactivate|onDeactivate}. + */ + onActivate?: () => void; + + /** + * @brief Callback to be invoked when the plugin is deactivated, either + * by application closing or manual plugin deactivation by the user. + * + * For constructor behaviour when the plugin is activated, you should + * use {@link ToriiPluginSettings.onActivate|onActivate}. + */ + onDeactivate?: () => void; + + // onDestroy?: () => void; +} + +/** + * @brief A Torii plugin is a scope of code, with its' own lifecycle, that + * can be loaded, interacted with and offloaded by Toriis' internal plugin + * loader. + */ +export class ToriiPlugin { + // + // ATTRIBUTES [INTERNAL PLUGIN CONSTANTS] + // + + /** + * @brief The unique identifier of the plugin, consisting of letters, + * numerics and dashes. You can prefix the plugin with a custom scope + * if needed. For example, `my-custom-scope:custom-plugin` would be an + * allowed plugin name. + * + * @note The prefix `core:` should only be used by libraries internal + * to the Torii project. + * + * @example + * + * { + * id: "core:markdown:hyperlinks", + * } + */ + public readonly pluginId: string; + + /** + * Semver string of the plugin version, consisting of at least major, + * minor and optionally patch. + * + * @example + * + * { + * version: "1.2.0-dev4", + * } + */ + public pluginVersion: string = '0.1.0'; + + // + // Handles + // + + /** + * List of registered event handlers. The key is any understood Torii + * event name, and the handler is an array of functions which listen + * to the event. + */ + #handlers: Map< + keyof ToriiPluginEvents, + ToriiPluginEvents[keyof ToriiPluginEvents][] + > = new Map(); + + // + // ATTRIBUTES [CALLBACKS] + // + + // + // CONSTRUCTOR + // + constructor(pluginId: string) { + this.pluginId = pluginId; + } + + // + // STATIC FUNCTIONS + // + + /** + * @brief Quickly create a new, small Torii plugin from the provided + * settings object. For more complex Torii types, consider extending + * the class. + * + * @example + * + * ToriiPlugin.create({ + * // TODO + * }); + * + * @returns Newly created Torii Plugin. + */ + public static create(settings: ToriiPluginSettings): ToriiPlugin { + const plugin = new ToriiPlugin(settings.id); + + if (settings.onActivate) + plugin.on('plugin-activate', settings.onActivate); + if (settings.onDeactivate) + plugin.on('plugin-deactivate', settings.onDeactivate); + + return plugin; + } + + // + // METHODS + // + + // TODO + + // + // EVENTS + // + + /** + * @brief Register a callback to be invoked when the plugin is activated. + * This can happen multiple times over the runtime of the plugin. + * + * For destructor behaviour when the plugin is deactivated, you should + * use {@link ToriiPluginSettings.onDeactivate|onDeactivate}. + */ + on(e: 'plugin-activate', callback: () => void): this; + + /** + * @brief Register a callback to be invoked when the plugin is deactivated, + * either by application closing or manual plugin deactivation by the user. + * + * For constructor behaviour when the plugin is activated, you should + * use {@link ToriiPluginSettings.onActivate|onActivate}. + */ + on(e: 'plugin-deactivate', callback: () => void): this; + + // TODO workspace-open + // TODO workspace-close + + /** + * @brief Register a callback to be invoked when a record was opened. + * + * For behaviour when the record is closed, you should use + * {@link ToriiPluginSettings.onRecordClose|onRecordClose}. + */ + on(e: 'record-open', callback: () => void): this; + + /** + * @brief Register a callback to be invoked when a record was closed. When + * a new record is opened instead, the old record will be closed first. + * + * For behaviour when the record is opened, you should use + * {@link ToriiPluginSettings.onRecordOpen|onRecordOpen}. + */ + on(e: 'record-close', callback: () => void): this; + + /** + * @brief Register a callback to be invoked when a record was created. + */ + on(e: 'record-create', callback: () => void): this; + + /** + * @brief Register a callback to be invoked when a record was renamed. + */ + on(e: 'record-rename', callback: () => void): this; + + /** + * @brief Register a callback to be invoked when a record was deleted. + */ + on(e: 'record-delete', callback: () => void): this; + + /** + * @brief Register a callback to be invoked when a record was attached + * with a new component. + */ + on(e: 'component-create', callback: () => void): this; + + /** + * @brief Register a callback to be invoked when a records' component was + * modified. + */ + on(e: 'component-update', callback: () => void): this; + + /** + * @brief Register a callback to be invoked when a records' component was + * deleted. + */ + on(e: 'component-delete', callback: () => void): this; + + /** + * @brief Registers a new event handler. + * @param e Name of the event to listen for. When the plugin loader receives + * this event, the callback function will be invoked. + */ + public on( + e: Event, + callback: ToriiPluginEvents[Event] + ): this { + switch (this.#handlers.has(e)) { + case true: + this.#handlers.get(e)!.push(callback); + return this; + case false: + this.#handlers.set(e, [callback]); + return this; + } + } + + /** + * @brief Registers a new event handler. + * @param e Name of the event to listen for. When the plugin loader receives + * this event, the callback function will be invoked. + */ + public emit( + e: Event, + ...args: Parameters + ): this { + this.#handlers.get(e)?.forEach((callback: (...e: any) => void) => { + callback(...args); + }); + return this; + } +} + +export default ToriiPlugin; diff --git a/torii-client/tsconfig.json b/torii-client/plugin-core/tsconfig.json similarity index 100% rename from torii-client/tsconfig.json rename to torii-client/plugin-core/tsconfig.json diff --git a/torii-client/plugin-debug/.prettierrc b/torii-client/plugin-debug/.prettierrc new file mode 100644 index 0000000..a24df35 --- /dev/null +++ b/torii-client/plugin-debug/.prettierrc @@ -0,0 +1,6 @@ +{ + "semi": true, + "tabWidth": 4, + "singleQuote": true, + "endOfLine": "auto" +} \ No newline at end of file diff --git a/torii-client/plugin-debug/package.json b/torii-client/plugin-debug/package.json new file mode 100644 index 0000000..c8efa35 --- /dev/null +++ b/torii-client/plugin-debug/package.json @@ -0,0 +1,10 @@ +{ + "name": "@torii/plugin-debug", + "version": "0.1.0", + "private": true, + "main": "src/lib.ts", + "dependencies": { + "vue": "^3.5.41", + "@torii/plugin-core": "file:../plugin-core" + } +} diff --git a/torii-client/plugin-debug/src/lib.ts b/torii-client/plugin-debug/src/lib.ts new file mode 100644 index 0000000..df1966d --- /dev/null +++ b/torii-client/plugin-debug/src/lib.ts @@ -0,0 +1,23 @@ +import { ToriiPlugin, ToriiPluginEvents } from '@torii/plugin-core'; + +class ToriiDebugPlugin extends ToriiPlugin { + public override pluginVersion = '0.1.0'; + + constructor() { + super('core:debug:plugin-debug'); + } + + /** + * @brief Print the received event information to the console. + */ + public override emit( + e: Event, + ...args: Parameters + ): this { + // super.emit(e, ...args); + console.debug(`[plugin-debug] Event '${e}':`, ...args); + return this; + } +} + +export default new ToriiDebugPlugin(); diff --git a/torii-client/plugin-debug/tsconfig.json b/torii-client/plugin-debug/tsconfig.json new file mode 100644 index 0000000..4795d0b --- /dev/null +++ b/torii-client/plugin-debug/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + // general + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "preserve", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + + // file handling + + // "baseUrl": "src", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] +} diff --git a/torii-client/.browserslistrc b/torii-client/webapp/.browserslistrc similarity index 100% rename from torii-client/.browserslistrc rename to torii-client/webapp/.browserslistrc diff --git a/torii-client/.eslintrc.js b/torii-client/webapp/.eslintrc.js similarity index 100% rename from torii-client/.eslintrc.js rename to torii-client/webapp/.eslintrc.js diff --git a/torii-client/.gitignore b/torii-client/webapp/.gitignore similarity index 100% rename from torii-client/.gitignore rename to torii-client/webapp/.gitignore diff --git a/torii-client/webapp/.prettierrc b/torii-client/webapp/.prettierrc new file mode 100644 index 0000000..a24df35 --- /dev/null +++ b/torii-client/webapp/.prettierrc @@ -0,0 +1,6 @@ +{ + "semi": true, + "tabWidth": 4, + "singleQuote": true, + "endOfLine": "auto" +} \ No newline at end of file diff --git a/torii-client/webapp/README.md b/torii-client/webapp/README.md new file mode 100644 index 0000000..ed94801 --- /dev/null +++ b/torii-client/webapp/README.md @@ -0,0 +1,24 @@ +# torii-client + +## Project setup +``` +npm install +``` + +### Compiles and hot-reloads for development +``` +npm run serve +``` + +### Compiles and minifies for production +``` +npm run build +``` + +### Lints and fixes files +``` +npm run lint +``` + +### Customize configuration +See [Configuration Reference](https://cli.vuejs.org/config/). diff --git a/torii-client/index.html b/torii-client/webapp/index.html similarity index 100% rename from torii-client/index.html rename to torii-client/webapp/index.html diff --git a/torii-client/package.json b/torii-client/webapp/package.json similarity index 91% rename from torii-client/package.json rename to torii-client/webapp/package.json index 159ad08..2f23d06 100644 --- a/torii-client/package.json +++ b/torii-client/webapp/package.json @@ -1,5 +1,5 @@ { - "name": "torii-client", + "name": "@torii/webapp", "version": "0.1.0", "private": true, "main": "src/main.ts", @@ -19,6 +19,8 @@ "@tiptap/pm": "^3.27.1", "@tiptap/starter-kit": "^3.27.1", "@tiptap/vue-3": "^3.27.1", + "@torii/plugin-core": "file:../plugin-core", + "@torii/plugin-debug": "file:../plugin-debug", "pinia": "^3.0.4", "typedoc": "^0.28.20", "typedoc-plugin-vue": "^1.5.1", diff --git a/torii-client/public/favicon.ico b/torii-client/webapp/public/favicon.ico similarity index 100% rename from torii-client/public/favicon.ico rename to torii-client/webapp/public/favicon.ico diff --git a/torii-client/public/index.html b/torii-client/webapp/public/index.html similarity index 100% rename from torii-client/public/index.html rename to torii-client/webapp/public/index.html diff --git a/torii-client/src/App.vue b/torii-client/webapp/src/App.vue similarity index 87% rename from torii-client/src/App.vue rename to torii-client/webapp/src/App.vue index 017983a..1420904 100644 --- a/torii-client/src/App.vue +++ b/torii-client/webapp/src/App.vue @@ -6,6 +6,7 @@ import { onMounted, onUnmounted } from 'vue'; import { watchLocale } from './composables/watch-locale'; import { loadSettings } from './stores/settings.ts'; +import { usePlugins } from './services/plugins.ts'; watchLocale(); loadSettings(); @@ -17,6 +18,7 @@ function preventDefault(e: { preventDefault: () => void }) { onMounted(() => { document.addEventListener('dragover', preventDefault); document.addEventListener('drop', preventDefault); + usePlugins(); // activate singleton }); onUnmounted(() => { diff --git a/torii-client/src/assets/logo.png b/torii-client/webapp/src/assets/logo.png similarity index 100% rename from torii-client/src/assets/logo.png rename to torii-client/webapp/src/assets/logo.png diff --git a/torii-client/src/components/LanguageSelect.vue b/torii-client/webapp/src/components/LanguageSelect.vue similarity index 100% rename from torii-client/src/components/LanguageSelect.vue rename to torii-client/webapp/src/components/LanguageSelect.vue diff --git a/torii-client/src/components/article/MarkdownEditor.vue b/torii-client/webapp/src/components/article/MarkdownEditor.vue similarity index 99% rename from torii-client/src/components/article/MarkdownEditor.vue rename to torii-client/webapp/src/components/article/MarkdownEditor.vue index c490e63..57fbd9a 100644 --- a/torii-client/src/components/article/MarkdownEditor.vue +++ b/torii-client/webapp/src/components/article/MarkdownEditor.vue @@ -37,7 +37,7 @@ import { type SuggestionItem, } from './autocomplete-extension'; import MarkdownEditorAutocomplete from './MarkdownEditorAutocomplete.vue'; -import { Record } from 'types'; +import type { Record } from '../../types.d.ts'; const props = defineProps<{ record: Record; diff --git a/torii-client/src/components/article/MarkdownEditorAutocomplete.vue b/torii-client/webapp/src/components/article/MarkdownEditorAutocomplete.vue similarity index 100% rename from torii-client/src/components/article/MarkdownEditorAutocomplete.vue rename to torii-client/webapp/src/components/article/MarkdownEditorAutocomplete.vue diff --git a/torii-client/src/components/article/autocomplete-extension.ts b/torii-client/webapp/src/components/article/autocomplete-extension.ts similarity index 100% rename from torii-client/src/components/article/autocomplete-extension.ts rename to torii-client/webapp/src/components/article/autocomplete-extension.ts diff --git a/torii-client/src/components/image/ImageEditor.vue b/torii-client/webapp/src/components/image/ImageEditor.vue similarity index 99% rename from torii-client/src/components/image/ImageEditor.vue rename to torii-client/webapp/src/components/image/ImageEditor.vue index 0b5a5b8..3866ea1 100644 --- a/torii-client/src/components/image/ImageEditor.vue +++ b/torii-client/webapp/src/components/image/ImageEditor.vue @@ -51,7 +51,7 @@ import { invoke } from '@tauri-apps/api/core'; import { open as openFileDialog } from '@tauri-apps/plugin-dialog'; import { NIcon, NSpin } from 'naive-ui'; import { CloseOutline, ImageOutline } from '@vicons/ionicons5'; -import { Record } from 'types'; +import type { Record } from '../../types.d.ts'; import UIDropRegion from '../../ui/UIDropRegion.vue'; const props = defineProps<{ diff --git a/torii-client/src/composables/recentProjects.ts b/torii-client/webapp/src/composables/recentProjects.ts similarity index 100% rename from torii-client/src/composables/recentProjects.ts rename to torii-client/webapp/src/composables/recentProjects.ts diff --git a/torii-client/src/composables/settings-window.ts b/torii-client/webapp/src/composables/settings-window.ts similarity index 100% rename from torii-client/src/composables/settings-window.ts rename to torii-client/webapp/src/composables/settings-window.ts diff --git a/torii-client/src/composables/watch-locale.ts b/torii-client/webapp/src/composables/watch-locale.ts similarity index 100% rename from torii-client/src/composables/watch-locale.ts rename to torii-client/webapp/src/composables/watch-locale.ts diff --git a/torii-client/src/locales/en.json b/torii-client/webapp/src/locales/en.json similarity index 100% rename from torii-client/src/locales/en.json rename to torii-client/webapp/src/locales/en.json diff --git a/torii-client/src/locales/jp.json b/torii-client/webapp/src/locales/jp.json similarity index 100% rename from torii-client/src/locales/jp.json rename to torii-client/webapp/src/locales/jp.json diff --git a/torii-client/src/locales/zh.json b/torii-client/webapp/src/locales/zh.json similarity index 100% rename from torii-client/src/locales/zh.json rename to torii-client/webapp/src/locales/zh.json diff --git a/torii-client/src/main.ts b/torii-client/webapp/src/main.ts similarity index 100% rename from torii-client/src/main.ts rename to torii-client/webapp/src/main.ts diff --git a/torii-client/src/router/index.ts b/torii-client/webapp/src/router/index.ts similarity index 100% rename from torii-client/src/router/index.ts rename to torii-client/webapp/src/router/index.ts diff --git a/torii-client/webapp/src/services/plugins.ts b/torii-client/webapp/src/services/plugins.ts new file mode 100644 index 0000000..549d644 --- /dev/null +++ b/torii-client/webapp/src/services/plugins.ts @@ -0,0 +1,13 @@ +import { ToriiPluginLoader } from '@torii/plugin-core'; +import ToriiDebugPlugin from '@torii/plugin-debug' + +let pluginLoader: ToriiPluginLoader | undefined; + +export function usePlugins(): ToriiPluginLoader { + if (pluginLoader) return pluginLoader; + + pluginLoader = new ToriiPluginLoader(); + pluginLoader.install(ToriiDebugPlugin); + + return pluginLoader; +} diff --git a/torii-client/src/shims-vue.d.ts b/torii-client/webapp/src/shims-vue.d.ts similarity index 100% rename from torii-client/src/shims-vue.d.ts rename to torii-client/webapp/src/shims-vue.d.ts diff --git a/torii-client/src/stores/settings.ts b/torii-client/webapp/src/stores/settings.ts similarity index 100% rename from torii-client/src/stores/settings.ts rename to torii-client/webapp/src/stores/settings.ts diff --git a/torii-client/src/styles/global.scss b/torii-client/webapp/src/styles/global.scss similarity index 100% rename from torii-client/src/styles/global.scss rename to torii-client/webapp/src/styles/global.scss diff --git a/torii-client/src/types.d.ts b/torii-client/webapp/src/types.d.ts similarity index 100% rename from torii-client/src/types.d.ts rename to torii-client/webapp/src/types.d.ts diff --git a/torii-client/src/ui/UIDropRegion.vue b/torii-client/webapp/src/ui/UIDropRegion.vue similarity index 100% rename from torii-client/src/ui/UIDropRegion.vue rename to torii-client/webapp/src/ui/UIDropRegion.vue diff --git a/torii-client/src/ui/UIFileTree.vue b/torii-client/webapp/src/ui/UIFileTree.vue similarity index 100% rename from torii-client/src/ui/UIFileTree.vue rename to torii-client/webapp/src/ui/UIFileTree.vue diff --git a/torii-client/src/ui/UISelect.vue b/torii-client/webapp/src/ui/UISelect.vue similarity index 100% rename from torii-client/src/ui/UISelect.vue rename to torii-client/webapp/src/ui/UISelect.vue diff --git a/torii-client/src/ui/UISpanInput.vue b/torii-client/webapp/src/ui/UISpanInput.vue similarity index 100% rename from torii-client/src/ui/UISpanInput.vue rename to torii-client/webapp/src/ui/UISpanInput.vue diff --git a/torii-client/src/ui/UITree.vue b/torii-client/webapp/src/ui/UITree.vue similarity index 100% rename from torii-client/src/ui/UITree.vue rename to torii-client/webapp/src/ui/UITree.vue diff --git a/torii-client/src/views/AboutView.vue b/torii-client/webapp/src/views/AboutView.vue similarity index 100% rename from torii-client/src/views/AboutView.vue rename to torii-client/webapp/src/views/AboutView.vue diff --git a/torii-client/src/views/HomeView.vue b/torii-client/webapp/src/views/HomeView.vue similarity index 100% rename from torii-client/src/views/HomeView.vue rename to torii-client/webapp/src/views/HomeView.vue diff --git a/torii-client/src/views/RecentProjectsView.vue b/torii-client/webapp/src/views/RecentProjectsView.vue similarity index 98% rename from torii-client/src/views/RecentProjectsView.vue rename to torii-client/webapp/src/views/RecentProjectsView.vue index cda812e..9af6c25 100644 --- a/torii-client/src/views/RecentProjectsView.vue +++ b/torii-client/webapp/src/views/RecentProjectsView.vue @@ -47,6 +47,7 @@ import { open } from '@tauri-apps/plugin-dialog'; import { NIcon, NDropdown, DropdownOption } from 'naive-ui'; import { EllipsisVertical } from '@vicons/ionicons5'; import { useRouter } from 'vue-router'; +import { usePlugins } from '@/services/plugins'; const router = useRouter(); const recentProjects = listRecentProjects(); diff --git a/torii-client/src/views/project/ProjectView.vue b/torii-client/webapp/src/views/project/ProjectView.vue similarity index 97% rename from torii-client/src/views/project/ProjectView.vue rename to torii-client/webapp/src/views/project/ProjectView.vue index 337c60b..63067f9 100644 --- a/torii-client/src/views/project/ProjectView.vue +++ b/torii-client/webapp/src/views/project/ProjectView.vue @@ -114,7 +114,7 @@