├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ ├── feature_request_ja.md │ └── issue-.md ├── dependabot.yml └── workflows │ └── pages.yaml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTORS ├── LICENSE ├── build.js ├── csscomb.json ├── favicon.svg ├── modules ├── codemirror │ ├── README.md │ ├── package.json │ ├── src │ │ ├── editor-field.ts │ │ ├── gutters.ts │ │ ├── indent-hack.ts │ │ ├── index.ts │ │ ├── languages.ts │ │ ├── misc.ts │ │ ├── print-tree.ts │ │ └── svelte │ │ │ ├── svelte-dom.ts │ │ │ ├── svelte-lifecycle-element.ts │ │ │ └── svelte-panel.ts │ ├── tests │ │ └── adapters.ts │ └── tsconfig.json ├── comlink │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.ts │ └── tsconfig.json ├── dom │ ├── README.md │ ├── package.json │ ├── src │ │ ├── base-button.ts │ │ ├── base-tooltip-button.ts │ │ ├── custom-elements.ts │ │ ├── focus.ts │ │ ├── gesture.ts │ │ ├── held.ts │ │ ├── hover.ts │ │ ├── index.ts │ │ ├── key-handling.ts │ │ ├── observe.ts │ │ ├── scrolling.ts │ │ ├── svelte-adapters.ts │ │ ├── swipe.ts │ │ ├── useragent.ts │ │ ├── validation.ts │ │ └── visibility.ts │ ├── tests │ │ ├── focus.ts │ │ └── misc.ts │ └── tsconfig.json ├── ftml-components │ ├── README.md │ ├── package.json │ ├── src │ │ ├── _base.scss │ │ ├── _default.scss │ │ ├── components │ │ │ ├── code │ │ │ │ ├── _code.scss │ │ │ │ └── code.ts │ │ │ ├── collapsible │ │ │ │ ├── _collapsible.scss │ │ │ │ └── collapsible.ts │ │ │ ├── footnotes │ │ │ │ ├── _footnotes.scss │ │ │ │ └── footnotes.ts │ │ │ ├── math │ │ │ │ ├── _math.scss │ │ │ │ └── math.ts │ │ │ ├── tabview │ │ │ │ ├── _tabview.scss │ │ │ │ └── tabview.ts │ │ │ └── user-info │ │ │ │ └── _user-info.scss │ │ ├── index.scss │ │ ├── index.ts │ │ └── theme │ │ │ ├── _base.scss │ │ │ ├── _code.scss │ │ │ ├── _collapsible.scss │ │ │ ├── _error.scss │ │ │ ├── _footnotes.scss │ │ │ ├── _math.scss │ │ │ ├── _tabview.scss │ │ │ ├── _tokens.scss │ │ │ └── _user-info.scss │ └── tsconfig.json ├── prism │ ├── README.md │ ├── package.json │ ├── src │ │ ├── ftml.ts │ │ ├── index.ts │ │ └── worker.ts │ ├── tests │ │ └── prism.skipped │ ├── tsconfig.json │ └── vendor │ │ ├── prism-langs.js │ │ ├── prism-min.js │ │ ├── prism-svelte.js │ │ └── prism.js ├── sheaf │ ├── README.md │ ├── package.json │ ├── src │ │ ├── components │ │ │ ├── CodeDisplay.svelte │ │ │ ├── MorphingWikitext.svelte │ │ │ ├── PaneEditor.svelte │ │ │ ├── PaneEditorTopbar.svelte │ │ │ ├── PanePreview.svelte │ │ │ ├── SettingsMenu.svelte │ │ │ ├── Sheaf.svelte │ │ │ └── SheafPanel.svelte │ │ ├── context.ts │ │ ├── core.ts │ │ ├── extensions │ │ │ ├── bindings.ts │ │ │ ├── keymap.ts │ │ │ └── theme.ts │ │ ├── index.ts │ │ └── state.ts │ └── tsconfig.json └── util │ ├── README.md │ ├── package.json │ ├── src │ ├── decorators.ts │ ├── html.ts │ ├── index.ts │ ├── pref.ts │ └── timeout.ts │ ├── tests │ └── wj-util.ts │ ├── tsconfig.json │ └── vendor │ └── request-idle-callback-polyfill.js ├── package.json ├── plugins ├── vite-plugin-bundled-worker.js └── vite-plugin-cross-platform.js ├── pnpm-lock.yaml ├── readme.md ├── resources └── css │ ├── _abstracts.scss │ ├── abstracts │ ├── _functions.scss │ ├── _media.scss │ ├── _mixins.scss │ └── _variables.scss │ ├── base │ ├── _a11y.scss │ ├── _base.scss │ ├── _font-faces-cjk.scss │ ├── _font-faces.scss │ ├── _reset.scss │ └── _typography.scss │ ├── components │ ├── _code.scss │ ├── _dialog.scss │ ├── _links.scss │ ├── _navbar-element.scss │ ├── _scrollbar.scss │ ├── _sheaf.scss │ ├── _sidebar-nav-element.scss │ ├── _tippy.scss │ └── _wikitext.scss │ ├── layout │ ├── _app.scss │ ├── _footer.scss │ ├── _header.scss │ ├── _main.scss │ ├── _navbar.scss │ └── _sidebar.scss │ ├── main.scss │ ├── pages │ ├── _auth.scss │ └── verify-email-link.scss │ ├── themes │ ├── _code.scss │ ├── _colors.scss │ ├── _layout.scss │ ├── _rhythm.scss │ └── _typography.scss │ └── wiki │ └── page │ ├── _breadcrumbs.scss │ ├── _info.scss │ ├── _page.scss │ └── _tags.scss ├── src ├── css │ ├── code.css │ ├── collapsible.css │ ├── init.css │ └── wikidot.css ├── ftml.threads.worker.js ├── ftml.web.worker.js ├── image │ ├── Twitter-icon-50.png │ ├── Twitter-icon.png │ ├── black.png │ ├── blank.png │ ├── default.png │ ├── expand.png │ ├── forum.png │ ├── help.png │ ├── home.png │ ├── main.png │ └── series.png ├── index.html ├── lib │ └── ftml-wasm │ │ └── esm │ │ └── wj-ftml-wasm.esm.js ├── main.ts ├── public │ ├── files--static │ │ └── media │ │ │ ├── bad-avatar.png │ │ │ ├── black.png │ │ │ ├── body_bg.png │ │ │ ├── default-avatar.png │ │ │ ├── discord_icon.png │ │ │ ├── karma.svg │ │ │ ├── social-facebook.png │ │ │ ├── social-instagram.png │ │ │ ├── social-reddit.png │ │ │ ├── social-tiktok.png │ │ │ ├── social-twitch.png │ │ │ ├── social-twitter.png │ │ │ ├── twitter-icon.png │ │ │ └── ui.svg │ └── locales │ │ ├── cn │ │ ├── messages.yaml │ │ ├── side.ftml │ │ ├── theme.css │ │ └── top.ftml │ │ ├── en │ │ ├── messages.yaml │ │ ├── side.ftml │ │ ├── theme.css │ │ └── top.ftml │ │ ├── fr │ │ ├── messages.yaml │ │ ├── side.ftml │ │ ├── theme.css │ │ └── top.ftml │ │ ├── index.yaml │ │ ├── ja │ │ ├── messages.yaml │ │ ├── side.ftml │ │ ├── theme.css │ │ └── top.ftml │ │ ├── pl │ │ ├── messages.yaml │ │ ├── side.ftml │ │ ├── theme.css │ │ └── top.ftml │ │ ├── tr │ │ ├── messages.yaml │ │ ├── side.ftml │ │ ├── theme.css │ │ └── top.ftml │ │ ├── vi │ │ ├── messages.yaml │ │ ├── side.ftml │ │ ├── theme.css │ │ └── top.ftml │ │ └── zh-tr │ │ ├── messages.yaml │ │ ├── side.ftml │ │ ├── theme.css │ │ └── top.ftml ├── script │ ├── api.ts │ ├── elements.ts │ ├── eventHandlers.ts │ ├── helper.ts │ ├── include.ts │ ├── loader.ts │ ├── locales.ts │ ├── module.ts │ ├── styles.ts │ ├── utils.ts │ └── worker.ts └── vite-env.d.ts ├── test └── ftml-test.ftml ├── tsconfig.json └── vite.config.js /.gitattributes: -------------------------------------------------------------------------------- 1 | *.css linguist-vendored 2 | *.html linguist-vendored 3 | *.js linguist-vendored -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FEAT]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request_ja.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 機能要望 3 | about: このプロジェクトに提案を行う 4 | title: "[FEAT]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **問題点** 11 | 何が問題なのか,明確かつ簡潔な説明. 12 | 13 | **解決策** 14 | あなたがしたいことの明確かつ簡潔な説明. 15 | 16 | **検討した代替案** 17 | あなたが検討した代替案や機能についての明確かつ簡潔な説明. 18 | 19 | **追加コンテンツ** 20 | 機能リクエストに関するその他のコンテンツやスクリーンショットをここに追加. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue-.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'issue ' 3 | about: my template 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Overview 11 | Please provide an overview of the issue you are creating. 12 | ex) I want to adapt the style of the header. 13 | 14 | ## Purpose 15 | Describe the purpose of this issue 16 | ex) To style it. 17 | 18 | ## Task 19 | Break down and manage your tasks. 20 | - [ ] XXXX 21 | - [ ] XXXX 22 | - [ ] XXXX 23 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | labels: 13 | - "dependencies" 14 | -------------------------------------------------------------------------------- /.github/workflows/pages.yaml: -------------------------------------------------------------------------------- 1 | name: deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths-ignore: 8 | - '**/readme.md' 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: pnpm/action-setup@v2.0.1 16 | with: 17 | version: 7.0.0 18 | 19 | - name: Setup Node.js 20 | uses: actions/setup-node@v2 21 | with: 22 | cache: pnpm 23 | 24 | - name: Install dependencies 25 | run: pnpm install --no-frozen-lockfile 26 | 27 | - name: Build App 28 | run: pnpm run compile 29 | 30 | - name: Deploy to GitHub Pages 31 | uses: peaceiris/actions-gh-pages@v3 32 | with: 33 | github_token: ${{ secrets.GITHUB_TOKEN }} 34 | publish_dir: ./dist 35 | cname: wd.scp-jp.org 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 0.1.0: Beta 1 4 | 5 | This is the first public beta release while Wikidot is hacked. 6 | 7 | - Allows FTML to be added to HTML renderings 8 | - Added automatic backup functionality 9 | - Added save functionality 10 | - updated ftml version to 1.16.2 11 | 12 | ## 0.1.1: Beta 2 13 | 14 | - Multilingual support 15 | - Reduced render speed to 1 sec/times. 16 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | H-Lekter 2 | Manganian7potasu 3 | urexpect 4 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/node 2 | console.time('Bundling time'); 3 | const { build } = require('vite'); 4 | const { join, dirname } = require('path'); 5 | const fs = require('fs'); 6 | 7 | /** @type 'production' | 'development' | 'test' */ 8 | const mode = process.env.MODE || 'production'; 9 | 10 | const configs = [ 11 | join(process.cwd(), 'vite.config.js'), 12 | // join(process.cwd(), 'scripts/config.browser.vite.js'), 13 | ]; 14 | 15 | /** 16 | * Run `vite build` for config file 17 | * @param {string} configFile 18 | * @return {Promise} 19 | */ 20 | const buildByConfig = (configFile) => build({ configFile, mode }); 21 | 22 | /** 23 | * Ensure directory exists 24 | * @param {string} filePath 25 | */ 26 | const ensureDirExists = (filePath) => { 27 | const dir = dirname(filePath); 28 | if (!fs.existsSync(dir)) { 29 | fs.mkdirSync(dir, { recursive: true }); 30 | } 31 | }; 32 | 33 | Promise.all(configs.map(buildByConfig)) 34 | .then(() => { 35 | const distDir = join(process.cwd(), 'dist'); 36 | 37 | // Paths for the files to copy 38 | const workerSrc = join(process.cwd(), 'src/ftml.web.worker.js'); 39 | const workerDest = join(distDir, 'ftml.web.worker.js'); 40 | 41 | const wasmSrc = join(process.cwd(), 'src/lib/ftml-wasm/esm/wj-ftml-wasm.esm.js'); 42 | const wasmDest = join(distDir, 'lib/ftml-wasm/esm/wj-ftml-wasm.esm.js'); 43 | 44 | // Ensure destination directories exist 45 | ensureDirExists(workerDest); 46 | ensureDirExists(wasmDest); 47 | 48 | // Copy the worker file to the dist directory 49 | fs.copyFileSync(workerSrc, workerDest); 50 | 51 | // Copy the wasm file to the dist directory 52 | fs.copyFileSync(wasmSrc, wasmDest); 53 | 54 | // Copy index.html to 404.html for GitHub Pages 55 | fs.copyFileSync(join(distDir, 'index.html'), join(distDir, '404.html')); 56 | 57 | console.timeEnd('Bundling time'); 58 | }) 59 | .catch(e => { 60 | console.error(e); 61 | process.exit(1); 62 | }); 63 | -------------------------------------------------------------------------------- /csscomb.json: -------------------------------------------------------------------------------- 1 | { 2 | "remove-empty-rulesets": true, 3 | "always-semicolon": true, 4 | "color-case": "lower", 5 | "block-indent": " ", 6 | "color-shorthand": false, 7 | "element-case": "lower", 8 | "eof-newline": true, 9 | "leading-zero": false, 10 | "quotes": "double", 11 | "sort-order-fallback": "abc", 12 | "space-before-colon": "", 13 | "space-after-colon": " ", 14 | "space-before-combinator": " ", 15 | "space-after-combinator": " ", 16 | "space-between-declarations": "\n", 17 | "space-before-opening-brace": " ", 18 | "space-after-opening-brace": "\n", 19 | "space-after-selector-delimiter": " ", 20 | "space-before-selector-delimiter": "", 21 | "space-before-closing-brace": "\n", 22 | "strip-spaces": true, 23 | "tab-size": true, 24 | "unitless-zero": true, 25 | "vendor-prefix-align": true 26 | } -------------------------------------------------------------------------------- /favicon.svg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r74tech/ftml-web/1ebf02a66b99adb70599e312754122a6d3ef79c6/favicon.svg -------------------------------------------------------------------------------- /modules/codemirror/README.md: -------------------------------------------------------------------------------- 1 | # @wikijump/codemirror 2 | 3 | A package containing consolidated exports for CodeMirror. 4 | -------------------------------------------------------------------------------- /modules/codemirror/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@wikijump/codemirror", 3 | "license": "agpl-3.0-or-later", 4 | "description": "Helper package that consolidates CodeMirror exports.", 5 | "version": "0.0.0", 6 | "keywords": [ 7 | "wikijump" 8 | ], 9 | "private": true, 10 | "scripts": {}, 11 | "type": "module", 12 | "main": "src/index.ts", 13 | "dependencies": { 14 | "@codemirror/autocomplete": "^0.20.1", 15 | "@codemirror/commands": "^0.20.0", 16 | "@codemirror/lang-css": "^0.20.0", 17 | "@codemirror/lang-html": "^0.20.0", 18 | "@codemirror/language": "^0.20.2", 19 | "@codemirror/language-data": "^0.20.0", 20 | "@codemirror/legacy-modes": "^0.20.0", 21 | "@codemirror/lint": "^0.20.2", 22 | "@codemirror/search": "^0.20.1", 23 | "@codemirror/state": "^0.20.0", 24 | "@codemirror/view": "^0.20.6", 25 | "@lezer/common": "^0.16.0", 26 | "@lezer/lr": "^0.16.3", 27 | "@lezer/highlight": "^0.16.0", 28 | "@wikijump/components": "workspace:*", 29 | "@wikijump/util": "workspace:*", 30 | "svelte": "^3.48.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /modules/codemirror/src/gutters.ts: -------------------------------------------------------------------------------- 1 | import { foldGutter } from "@codemirror/language" 2 | import { lineNumbers } from "@codemirror/view" 3 | import { EditorField } from "./editor-field" 4 | 5 | /** 6 | * `EditorField` extension that enables a field that controls whether or 7 | * not the editor gutter is mounted. 8 | */ 9 | export const Gutters = new EditorField({ 10 | default: true, 11 | reconfigure: state => (state ? [lineNumbers(), foldGutter()] : null) 12 | }) 13 | -------------------------------------------------------------------------------- /modules/codemirror/src/indent-hack.ts: -------------------------------------------------------------------------------- 1 | import { EditorState, RangeSetBuilder, type Line } from "@codemirror/state" 2 | import { 3 | Decoration, 4 | ViewPlugin, 5 | type DecorationSet, 6 | type EditorView, 7 | type ViewUpdate 8 | } from "@codemirror/view" 9 | 10 | const WHITESPACE_REGEX = /^\s+/ 11 | 12 | /** 13 | * Extension that makes it so that lines which wrap onto new lines preserve 14 | * their indentation. Called a "hack" because this is done through CSS 15 | * trickery, and not through any sort of complex DOM arrangement. 16 | */ 17 | export const IndentHack = ViewPlugin.fromClass( 18 | class { 19 | decorations: DecorationSet 20 | constructor(view: EditorView) { 21 | this.decorations = generateIndentDecorations(view) 22 | } 23 | update(update: ViewUpdate) { 24 | if (update.docChanged || update.viewportChanged) { 25 | this.decorations = generateIndentDecorations(update.view) 26 | } 27 | } 28 | }, 29 | { decorations: v => v.decorations } 30 | ) 31 | 32 | function generateIndentDecorations(view: EditorView) { 33 | // get every line of the visible ranges 34 | const lines = new Set() 35 | for (const { from, to } of view.visibleRanges) { 36 | for (let pos = from; pos <= to; ) { 37 | let line = view.state.doc.lineAt(pos) 38 | lines.add(line) 39 | pos = line.to + 1 40 | } 41 | } 42 | 43 | // get the indentation of every line 44 | // and create an offset hack decoration if it has any 45 | const tabInSpaces = " ".repeat(view.state.facet(EditorState.tabSize)) 46 | const builder = new RangeSetBuilder() 47 | for (const line of lines) { 48 | const WS = WHITESPACE_REGEX.exec(line.text)?.[0] 49 | const col = WS?.replaceAll("\t", tabInSpaces).length 50 | if (col) { 51 | builder.add( 52 | line.from, 53 | line.from, 54 | Decoration.line({ 55 | attributes: { style: `padding-left: ${col}ch; text-indent: -${col}ch` } 56 | }) 57 | ) 58 | } 59 | } 60 | 61 | return builder.finish() 62 | } 63 | -------------------------------------------------------------------------------- /modules/codemirror/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./editor-field" 2 | export * from "./gutters" 3 | export * from "./indent-hack" 4 | export * from "./languages" 5 | export * from "./misc" 6 | export * from "./print-tree" 7 | export * from "./svelte/svelte-dom" 8 | export * from "./svelte/svelte-lifecycle-element" 9 | export * from "./svelte/svelte-panel" 10 | -------------------------------------------------------------------------------- /modules/codemirror/src/languages.ts: -------------------------------------------------------------------------------- 1 | import type { LanguageDescription } from "@codemirror/language" 2 | import { languages } from "@codemirror/language-data" 3 | import { Facet } from "@codemirror/state" 4 | 5 | /** 6 | * A `Facet` that holds a list of `LanguageDescription` instances. 7 | * Languages can be added to this facet in the editor, so that a plugin may 8 | * retrieve a list of languages in common use by the editor and its plugins. 9 | */ 10 | export const languageList = Facet.define() 11 | 12 | /** Returns an extension for every `LanguageDescription` provided. */ 13 | export function addLanguages(...languages: LanguageDescription[]) { 14 | return languages.map(language => languageList.of(language)) 15 | } 16 | 17 | /** 18 | * A list of extensions that adds every language from the 19 | * `@codemirror/language-data` package into the {@link languageList} facet. 20 | */ 21 | export const defaultLanguages = addLanguages(...languages) 22 | -------------------------------------------------------------------------------- /modules/codemirror/src/print-tree.ts: -------------------------------------------------------------------------------- 1 | import type { Tree } from "@lezer/common" 2 | 3 | function indent(depth: number, str = " ") { 4 | return depth > 0 ? str.repeat(depth) : "" 5 | } 6 | 7 | /** Pretty-prints a Lezer tree. Doesn't log the result - just returns it. */ 8 | export function printTree(tree: Tree, src: string) { 9 | let output = "" 10 | let depth = -1 11 | 12 | tree.iterate({ 13 | enter(node) { 14 | const from = node.from 15 | const to = node.to 16 | const len = to - from 17 | 18 | depth++ 19 | 20 | let slice: string 21 | if (len <= 40) slice = src.slice(from, to) 22 | else slice = `${src.slice(from, from + 20)} ... ${src.slice(to - 20, to)}` 23 | 24 | slice = slice.replaceAll("\n", "\\n").replaceAll('"', '\\"') 25 | 26 | output += `\n${indent(depth, "│ ")}${node.name} [${from}, ${to}]: "${slice}"` 27 | }, 28 | 29 | leave() { 30 | depth-- 31 | if (depth === 0) output += "\n│" 32 | } 33 | }) 34 | 35 | output = output.replace(/\u2502\s*$/, "").trim() 36 | 37 | return output 38 | } 39 | -------------------------------------------------------------------------------- /modules/codemirror/src/svelte/svelte-lifecycle-element.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Custom element that is used to detect when Svelte components should be 3 | * mounted or unmounted. 4 | */ 5 | export class LifecycleElement extends HTMLElement { 6 | /** Element/tag name that this element is registered with. */ 7 | static tag = "svelte-cm-lifecycle" 8 | 9 | /** 10 | * @param _mount - Function to be called whenever this element is mounted. 11 | * @param _unmount - Function to be called whenever this element is unmounted. 12 | */ 13 | constructor( 14 | public _mount?: (dom: LifecycleElement) => void, 15 | public _unmount?: (dom: LifecycleElement) => void 16 | ) { 17 | super() 18 | } 19 | 20 | connectedCallback() { 21 | if (this._mount) this._mount(this) 22 | this.dispatchEvent(new CustomEvent("connected")) 23 | } 24 | 25 | disconnectedCallback() { 26 | if (this._unmount) this._unmount(this) 27 | this.dispatchEvent(new CustomEvent("disconnected")) 28 | } 29 | } 30 | 31 | if (!customElements.get(LifecycleElement.tag)) { 32 | customElements.define(LifecycleElement.tag, LifecycleElement) 33 | } 34 | -------------------------------------------------------------------------------- /modules/codemirror/src/svelte/svelte-panel.ts: -------------------------------------------------------------------------------- 1 | import type { Extension } from "@codemirror/state" 2 | import { EditorView, showPanel, type Panel } from "@codemirror/view" 3 | import type { SvelteComponent } from "svelte" 4 | import { EditorField } from "../editor-field" 5 | import { 6 | EditorSvelteComponent, 7 | type EditorSvelteComponentOpts, 8 | type EditorSvelteComponentProps 9 | } from "./svelte-dom" 10 | 11 | /** 12 | * The props provided to a {@link EditorSveltePanel} component. 13 | * 14 | * @see {@link EditorSveltePanel} 15 | */ 16 | export interface EditorSveltePanelProps extends EditorSvelteComponentProps { 17 | /** Calls `$destroy()` on the component and then unmounts the panel. */ 18 | unmount: () => void 19 | } 20 | 21 | export interface EditorSveltePanelOpts 22 | extends EditorSvelteComponentOpts { 23 | /** If true, the panel will be mounted on the top of the editor. */ 24 | top?: boolean 25 | } 26 | 27 | /** 28 | * A panel that uses a Svelte component to render its contents. 29 | * 30 | * The component is provided with three props: 31 | * 32 | * - `view` 33 | * - `update` 34 | * - `unmount` 35 | * 36 | * You can see the types of these props in the 37 | * {@link EditorSveltePanelProps} interface. 38 | * 39 | * @see {@link EditorSveltePanelProps} 40 | */ 41 | export class EditorSveltePanel { 42 | /** 43 | * Extension that mounts the panel to the editor. You don't really need 44 | * to use this property - any object with the `extension` property is a 45 | * valid CodeMirror extension entrypoint. 46 | */ 47 | declare extension: Extension 48 | 49 | private declare field: EditorField 50 | private declare handler: EditorSvelteComponent 51 | 52 | /** 53 | * @param component - The Svelte component the panel will mount with. 54 | * @param opts - {@link EditorSveltePanelOpts} 55 | */ 56 | constructor( 57 | public component: T, 58 | private opts: EditorSveltePanelOpts> = {} 59 | ) { 60 | this.handler = new EditorSvelteComponent(component) 61 | 62 | const create = this.create.bind(this) 63 | this.field = new EditorField({ 64 | default: false, 65 | provide: field => showPanel.from(field, show => (show ? create : null)) 66 | }) 67 | 68 | this.extension = this.field 69 | } 70 | 71 | /** 72 | * Creates the Svelte component and DOM container element and returns the 73 | * CodeMirror panel instance. 74 | */ 75 | private create(view: EditorView): Panel { 76 | const instance = this.handler.create(view, { 77 | unmount: () => this.toggle(view, false) 78 | }) 79 | return { ...instance, top: this.opts.top } 80 | } 81 | 82 | /** 83 | * Toggle, or directly set, the panel's state (whether or not it is mounted). 84 | * 85 | * @param view - The {@link EditorView} that the panel is attached to. 86 | * @param state - Forces the panel to either mount or unmount. 87 | */ 88 | toggle(view: EditorView, state?: boolean) { 89 | if (state === undefined) state = !this.field.get(view) 90 | this.field.set(view, state) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /modules/codemirror/tests/adapters.ts: -------------------------------------------------------------------------------- 1 | import { assert, describe, it } from "vitest" 2 | import { LifecycleElement } from "../src/svelte/svelte-lifecycle-element" 3 | 4 | // testing the other adapters might not really be possible, at least in this module 5 | // they ofc require a svelte component to work, and even after you'd need an entire 6 | // CodeMirror editor to test it 7 | 8 | describe("SheafAdapters", () => { 9 | it("disconnect element calls callback", async () => { 10 | const element = new LifecycleElement() 11 | document.documentElement.append(element) 12 | 13 | let disconnected = false 14 | element.addEventListener("disconnected", () => (disconnected = true)) 15 | 16 | document.documentElement.removeChild(element) 17 | 18 | assert.equal(disconnected, true, "Disconnect element did not fire callback") 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /modules/codemirror/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true 4 | }, 5 | "extends": "../../tsconfig.json", 6 | "include": ["src/**/*", "tests/**/*", "../_types/**/*", "cm.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /modules/comlink/README.md: -------------------------------------------------------------------------------- 1 | # @wikijump/comlink 2 | -------------------------------------------------------------------------------- /modules/comlink/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@wikijump/comlink", 3 | "license": "agpl-3.0-or-later", 4 | "description": "Comlink wrapper for Wikijump.", 5 | "version": "0.0.0", 6 | "keywords": [ 7 | "wikijump" 8 | ], 9 | "private": true, 10 | "scripts": {}, 11 | "type": "module", 12 | "main": "src/index.ts", 13 | "dependencies": { 14 | "@wikijump/util": "workspace:*", 15 | "comlink": "^4.3.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /modules/comlink/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true 4 | }, 5 | "extends": "../../tsconfig.json", 6 | "include": ["src/**/*", "tests/**/*", "vendor/**/*", "../_types/**/*"] 7 | } 8 | -------------------------------------------------------------------------------- /modules/dom/README.md: -------------------------------------------------------------------------------- 1 | # @wikijump/dom 2 | -------------------------------------------------------------------------------- /modules/dom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@wikijump/dom", 3 | "license": "agpl-3.0-or-later", 4 | "description": "DOM related utilities for Wikijump.", 5 | "version": "0.0.0", 6 | "keywords": [ 7 | "wikijump" 8 | ], 9 | "private": true, 10 | "scripts": {}, 11 | "type": "module", 12 | "main": "src/index.ts", 13 | "dependencies": { 14 | "@popperjs/core": "^2.11.5", 15 | "@wikijump/util": "workspace:*" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /modules/dom/src/base-button.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Abstract custom element that can serve as a replacement for the 3 | * `