mirror of
https://github.com/zadam/trilium.git
synced 2025-11-02 03:16:11 +01:00
Add '_regroup/ckeditor5-footnotes/' from commit 'f5a6ff5684e612606d9e158e8f143bceb1cdbf8f'
git-subtree-dir: _regroup/ckeditor5-footnotes git-subtree-mainline:80c390c72bgit-subtree-split:f5a6ff5684
This commit is contained in:
19
_regroup/ckeditor5-footnotes/.editorconfig
Normal file
19
_regroup/ckeditor5-footnotes/.editorconfig
Normal file
@@ -0,0 +1,19 @@
|
||||
# Configurations to normalize the IDE behavior.
|
||||
# http://editorconfig.org/
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = tab
|
||||
tab_width = 4
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.{js,jsx,ts}]
|
||||
quote_type = single
|
||||
|
||||
[package.json]
|
||||
indent_style = space
|
||||
tab_width = 2
|
||||
46
_regroup/ckeditor5-footnotes/.eslintrc.cjs
Normal file
46
_regroup/ckeditor5-footnotes/.eslintrc.cjs
Normal file
@@ -0,0 +1,46 @@
|
||||
/* eslint-env node */
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
extends: 'ckeditor5',
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: [
|
||||
'@typescript-eslint'
|
||||
],
|
||||
root: true,
|
||||
ignorePatterns: [
|
||||
// Ignore the entire `dist/` (the NIM build).
|
||||
'dist/**',
|
||||
// Ignore compiled JavaScript files, as they are generated automatically.
|
||||
'src/**/*.js',
|
||||
// Also, do not check typing declarations, too.
|
||||
'src/**/*.d.ts'
|
||||
],
|
||||
rules: {
|
||||
// This rule disallows importing from any path other than the package main entrypoint.
|
||||
'ckeditor5-rules/allow-imports-only-from-main-package-entry-point': 'error',
|
||||
// This rule ensures that all imports from `@ckeditor/*` packages are done through the main package entry points.
|
||||
// This is required for the editor types to work properly and to ease migration to the installation methods
|
||||
// introduced in CKEditor 5 version 42.0.0.
|
||||
'ckeditor5-rules/no-legacy-imports': 'error',
|
||||
// As required by the ECMAScript (ESM) standard, all imports must include a file extension.
|
||||
// If the import does not include it, this rule will try to automatically detect the correct file extension.
|
||||
'ckeditor5-rules/require-file-extensions-in-imports': [
|
||||
'error',
|
||||
{
|
||||
extensions: [ '.ts', '.js', '.json' ]
|
||||
}
|
||||
]
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: [ 'tests/**/*.[jt]s', 'sample/**/*.[jt]s' ],
|
||||
rules: {
|
||||
// To write complex tests, you may need to import files that are not exported in DLL files by default.
|
||||
// Hence, imports CKEditor 5 packages in test files are not checked.
|
||||
'ckeditor5-rules/ckeditor-imports': 'off'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
19
_regroup/ckeditor5-footnotes/.gitattributes
vendored
Normal file
19
_regroup/ckeditor5-footnotes/.gitattributes
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
* text=auto
|
||||
|
||||
*.htaccess eol=lf
|
||||
*.cgi eol=lf
|
||||
*.sh eol=lf
|
||||
|
||||
*.css text
|
||||
*.htm text
|
||||
*.html text
|
||||
*.js text
|
||||
*.ts text
|
||||
*.json text
|
||||
*.php text
|
||||
*.txt text
|
||||
*.md text
|
||||
|
||||
*.png -text
|
||||
*.gif -text
|
||||
*.jpg -text
|
||||
26
_regroup/ckeditor5-footnotes/.github/workflows/release.yml
vendored
Normal file
26
_regroup/ckeditor5-footnotes/.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: Release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
jobs:
|
||||
publish-package:
|
||||
name: Publish package
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
always-auth: true
|
||||
- run: |
|
||||
corepack enable &&
|
||||
corepack install
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Publish package
|
||||
run: yarn publish --access public
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
||||
8
_regroup/ckeditor5-footnotes/.gitignore
vendored
Normal file
8
_regroup/ckeditor5-footnotes/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
build/
|
||||
coverage/
|
||||
dist/
|
||||
node_modules/
|
||||
tmp/
|
||||
sample/ckeditor.dist.js
|
||||
|
||||
# Ignore compiled TypeScript files.
|
||||
3
_regroup/ckeditor5-footnotes/.stylelintrc
Normal file
3
_regroup/ckeditor5-footnotes/.stylelintrc
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "stylelint-config-ckeditor5"
|
||||
}
|
||||
24
_regroup/ckeditor5-footnotes/LICENSE.md
Normal file
24
_regroup/ckeditor5-footnotes/LICENSE.md
Normal file
@@ -0,0 +1,24 @@
|
||||
Software License Agreement
|
||||
==========================
|
||||
|
||||
Copyright (c) 2024. All rights reserved.
|
||||
|
||||
Licensed under the terms of [ISC license](https://opensource.org/licenses/ISC).
|
||||
|
||||
This code is highly derivative of [Forum Magnum Footnote Plugin](https://github.com/ForumMagnum/ForumMagnum/tree/master/public/lesswrong-editor/src/ckeditor5-footnote/src) with original license reproduced below:
|
||||
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2020 Bohan Niu
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
155
_regroup/ckeditor5-footnotes/README.md
Normal file
155
_regroup/ckeditor5-footnotes/README.md
Normal file
@@ -0,0 +1,155 @@
|
||||
@tomaitken/ckeditor5-footnotes
|
||||
==============================
|
||||
|
||||
This package was created by the [ckeditor5-package-generator](https://www.npmjs.com/package/ckeditor5-package-generator) package. It is essentially lifted from the [ForumMagnum Footnote Plugin](https://github.com/ForumMagnum/ForumMagnum/tree/master/public/lesswrong-editor/src/ckeditor5-footnote/src) with only minor modifications. All intellectual credit should go to the developers of this plugin.
|
||||
|
||||
## Table of contents
|
||||
|
||||
* [Usage](#usage)
|
||||
* [Developing the package](#developing-the-package)
|
||||
* [Available scripts](#available-scripts)
|
||||
* [`start`](#start)
|
||||
* [`test`](#test)
|
||||
* [`lint`](#lint)
|
||||
* [`stylelint`](#stylelint)
|
||||
* [`build:dist`](#builddist)
|
||||
* [`translations:collect`](#translationscollect)
|
||||
* [`translations:download`](#translationsdownload)
|
||||
* [`translations:upload`](#translationsupload)
|
||||
* [`ts:build` and `ts:clear`](#tsbuild-and-tsclear)
|
||||
* [License](#license)
|
||||
|
||||
## Usage
|
||||
Install via NPM / yarn.
|
||||
|
||||
Then import code and CSS:
|
||||
```
|
||||
import '@tomaitken/ckeditor5-footnotes/index.css';
|
||||
|
||||
import { Footnotes } from '@tomaitken/ckeditor5-footnotes';
|
||||
```
|
||||
|
||||
Then add this `Footnotes` import to the plugins list and the string `'footnote'` to the toolbar buttons.
|
||||
|
||||
## Developing the package
|
||||
|
||||
To read about the CKEditor 5 Framework, visit the [CKEditor 5 Framework documentation](https://ckeditor.com/docs/ckeditor5/latest/framework/index.html).
|
||||
|
||||
## Available scripts
|
||||
|
||||
NPM scripts are a convenient way to provide commands in a project. They are defined in the `package.json` file and shared with people contributing to the project. It ensures developers use the same command with the same options (flags).
|
||||
|
||||
All the scripts can be executed by running `yarn run <script>`. Pre and post commands with matching names will be run for those as well.
|
||||
|
||||
The following scripts are available in the package.
|
||||
|
||||
### `start`
|
||||
|
||||
Starts an HTTP server with the live-reload mechanism that allows previewing and testing of plugins available in the package.
|
||||
|
||||
When the server starts, the default browser will open the developer sample. This can be disabled by passing the `--no-open` option to that command.
|
||||
|
||||
You can also define the language that will translate the created editor by specifying the `--language [LANG]` option. It defaults to `'en'`.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
# Starts the server and open the browser.
|
||||
yarn run start
|
||||
|
||||
# Disable auto-opening the browser.
|
||||
yarn run start --no-open
|
||||
|
||||
# Create the editor with the interface in German.
|
||||
yarn run start --language=de
|
||||
```
|
||||
|
||||
### ~`test`~
|
||||
|
||||
There are no tests for this plugin! Too lazy!
|
||||
|
||||
|
||||
### `lint`
|
||||
|
||||
Runs ESLint, which analyzes the code (all `*.ts` files) to quickly find problems.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
# Execute eslint.
|
||||
yarn run lint
|
||||
```
|
||||
|
||||
### `stylelint`
|
||||
|
||||
Similar to the `lint` task, stylelint analyzes the CSS code (`*.css` files in the `theme/` directory) in the package.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
# Execute stylelint.
|
||||
yarn run stylelint
|
||||
```
|
||||
|
||||
### `build:dist`
|
||||
|
||||
Creates npm and browser builds of your plugin. These builds can be added to the editor by following the [Configuring CKEditor 5 features](https://ckeditor.com/docs/ckeditor5/latest/getting-started/setup/configuration.html) guide.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
# Builds the `npm` and browser files thats are ready to publish.
|
||||
npm run build:dist
|
||||
```
|
||||
|
||||
### `translations:collect`
|
||||
|
||||
Collects translation messages (arguments of the `t()` function) and context files, then validates whether the provided values do not interfere with the values specified in the `@ckeditor/ckeditor5-core` package.
|
||||
|
||||
The task may end with an error if one of the following conditions is met:
|
||||
|
||||
* Found the `Unused context` error – entries specified in the `lang/contexts.json` file are not used in source files. They should be removed.
|
||||
* Found the `Context is duplicated for the id` error – some of the entries are duplicated. Consider removing them from the `lang/contexts.json` file, or rewrite them.
|
||||
* Found the `Context for the message id is missing` error – entries specified in source files are not described in the `lang/contexts.json` file. They should be added.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
yarn run translations:collect
|
||||
```
|
||||
|
||||
### `translations:download`
|
||||
|
||||
Download translations from the Transifex server. Depending on users' activity in the project, it creates translation files used for building the editor.
|
||||
|
||||
The task requires passing the URL to Transifex API. Usually, it matches the following format: `https://www.transifex.com/api/2/project/[PROJECT_SLUG]`.
|
||||
|
||||
To avoid passing the `--transifex` option whenever you call the command, you can store it in `package.json`, next to the `ckeditor5-package-tools translations:download` command.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
yarn run translations:download --transifex [API URL]
|
||||
```
|
||||
|
||||
### `translations:upload`
|
||||
|
||||
Uploads translation messages onto the Transifex server. It allows users to create translations into other languages using the Transifex platform.
|
||||
|
||||
The task requires passing the URL to the Transifex API. Usually, it matches the following format: `https://www.transifex.com/api/2/project/[PROJECT_SLUG]`.
|
||||
|
||||
To avoid passing the `--transifex` option whenever you call the command, you can store it in `package.json`, next to the `ckeditor5-package-tools translations:upload` command.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
yarn run translations:upload --transifex [API URL]
|
||||
```
|
||||
|
||||
### `ts:build` and `ts:clear`
|
||||
|
||||
These scripts compile TypeScript and remove the compiled files. They are used in the aforementioned life cycle scripts, and there is no need to call them manually.
|
||||
|
||||
## License
|
||||
|
||||
The `@tomaitken/ckeditor5-footnotes` package is available under [IST license](https://opensource.org/licenses/IST).
|
||||
17
_regroup/ckeditor5-footnotes/ckeditor5-metadata.json
Normal file
17
_regroup/ckeditor5-footnotes/ckeditor5-metadata.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"plugins": [
|
||||
{
|
||||
"name": "Footnotes",
|
||||
"className": "Footnotes",
|
||||
"description": "Adds text to the editor.",
|
||||
"path": "src/footnotes.ts",
|
||||
"uiComponents": [
|
||||
{
|
||||
"name": "insertFootnote",
|
||||
"type": "Button",
|
||||
"iconPath": "theme/icons/insert-footnote.svg"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
3
_regroup/ckeditor5-footnotes/lang/contexts.json
Normal file
3
_regroup/ckeditor5-footnotes/lang/contexts.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"Footnotes": "Content for a tooltip is displayed when a user hovers the CKEditor 5 icon."
|
||||
}
|
||||
91
_regroup/ckeditor5-footnotes/package.json
Normal file
91
_regroup/ckeditor5-footnotes/package.json
Normal file
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"name": "@triliumnext/ckeditor5-footnotes",
|
||||
"version": "0.0.4-hotfix11",
|
||||
"description": "A plugin for CKEditor 5 to allow footnotes.",
|
||||
"keywords": [
|
||||
"ckeditor",
|
||||
"ckeditor5",
|
||||
"ckeditor 5",
|
||||
"ckeditor5-feature",
|
||||
"ckeditor5-plugin",
|
||||
"ckeditor5-package-generator"
|
||||
],
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"module": "src/index.js",
|
||||
"types": "src/index.d.ts",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=18.0.0",
|
||||
"npm": ">=5.7.1"
|
||||
},
|
||||
"files": [
|
||||
"lang",
|
||||
"src/**/*.js",
|
||||
"src/**/*.css",
|
||||
"src/**/*.d.ts",
|
||||
"build",
|
||||
"theme",
|
||||
"ckeditor5-metadata.json",
|
||||
"CHANGELOG.md"
|
||||
],
|
||||
"dependencies": {
|
||||
"ckeditor5": "43.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ckeditor/ckeditor5-autoformat": "43.2.0",
|
||||
"@ckeditor/ckeditor5-core": "43.2.0",
|
||||
"@ckeditor/ckeditor5-utils": "43.2.0",
|
||||
"@ckeditor/ckeditor5-dev-build-tools": "42.0.1",
|
||||
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
|
||||
"@ckeditor/ckeditor5-package-tools": "^2.1.0",
|
||||
"@types/chai": "^4.3.1",
|
||||
"@types/mocha": "^9.1.1",
|
||||
"@typescript-eslint/eslint-plugin": "~5.43.0",
|
||||
"@typescript-eslint/parser": "^5.18.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-ckeditor5": ">=6.0.0",
|
||||
"http-server": "^14.1.0",
|
||||
"husky": "^4.2.5",
|
||||
"lint-staged": "^10.2.6",
|
||||
"stylelint": "^13.13.1",
|
||||
"stylelint-config-ckeditor5": ">=6.0.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "5.0.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ckeditor5": ">=42.0.0 || ^0.0.0-nightly"
|
||||
},
|
||||
"scripts": {
|
||||
"ts:build": "tsc -p ./tsconfig.release.json",
|
||||
"ts:clear": "npx rimraf \"src/**/*.@(js|d.ts)\"",
|
||||
"dll:build": "ckeditor5-package-tools dll:build",
|
||||
"dll:serve": "http-server ./ -o sample/dll.html",
|
||||
"lint": "eslint --quiet --ext .ts src/",
|
||||
"lint:fix": "eslint --quiet --fix --ext .ts src/",
|
||||
"stylelint": "stylelint --quiet --allow-empty-input 'theme/**/*.css'",
|
||||
"test": "ckeditor5-package-tools test",
|
||||
"prepare": "yarn run dll:build",
|
||||
"prepublishOnly": "yarn run ts:build && ckeditor5-package-tools export-package-as-javascript",
|
||||
"postpublish": "yarn run ts:clear && ckeditor5-package-tools export-package-as-typescript",
|
||||
"start": "ckeditor5-package-tools start"
|
||||
},
|
||||
"lint-staged": {
|
||||
"**/*.{js,ts}": [
|
||||
"eslint --quiet"
|
||||
],
|
||||
"**/*.css": [
|
||||
"stylelint --quiet --allow-empty-input"
|
||||
]
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/ThomasAitken/ckeditor5-footnotes.git"
|
||||
},
|
||||
"author": "Thomas Aitken",
|
||||
"bugs": {
|
||||
"url": "https://github.com/ThomasAitken/ckeditor5-footnotes/issues"
|
||||
},
|
||||
"homepage": "https://github.com/ThomasAitken/ckeditor5-footnotes#readme",
|
||||
"packageManager": "yarn@1.22.22"
|
||||
}
|
||||
7
_regroup/ckeditor5-footnotes/sample/ckeditor.d.ts
vendored
Normal file
7
_regroup/ckeditor5-footnotes/sample/ckeditor.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
declare global {
|
||||
interface Window {
|
||||
editor: ClassicEditor;
|
||||
}
|
||||
}
|
||||
import { ClassicEditor } from 'ckeditor5';
|
||||
import 'ckeditor5/ckeditor5.css';
|
||||
80
_regroup/ckeditor5-footnotes/sample/ckeditor.js
vendored
Normal file
80
_regroup/ckeditor5-footnotes/sample/ckeditor.js
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
import { ClassicEditor, Autoformat, Base64UploadAdapter, BlockQuote, Bold, Code, CodeBlock, Essentials, Heading, Image, ImageCaption, ImageStyle, ImageToolbar, ImageUpload, Indent, Italic, Link, List, MediaEmbed, Paragraph, Table, TableToolbar } from 'ckeditor5';
|
||||
import CKEditorInspector from '@ckeditor/ckeditor5-inspector';
|
||||
import Footnotes from '../src/footnotes.js';
|
||||
import 'ckeditor5/ckeditor5.css';
|
||||
ClassicEditor
|
||||
.create(document.getElementById('editor'), {
|
||||
plugins: [
|
||||
Footnotes,
|
||||
Essentials,
|
||||
Autoformat,
|
||||
BlockQuote,
|
||||
Bold,
|
||||
Heading,
|
||||
Image,
|
||||
ImageCaption,
|
||||
ImageStyle,
|
||||
ImageToolbar,
|
||||
ImageUpload,
|
||||
Indent,
|
||||
Italic,
|
||||
Link,
|
||||
List,
|
||||
MediaEmbed,
|
||||
Paragraph,
|
||||
Table,
|
||||
TableToolbar,
|
||||
CodeBlock,
|
||||
Code,
|
||||
Base64UploadAdapter
|
||||
],
|
||||
toolbar: [
|
||||
'undo',
|
||||
'redo',
|
||||
'|',
|
||||
'footnote',
|
||||
'|',
|
||||
'heading',
|
||||
'|',
|
||||
'bold',
|
||||
'italic',
|
||||
'link',
|
||||
'code',
|
||||
'bulletedList',
|
||||
'numberedList',
|
||||
'|',
|
||||
'outdent',
|
||||
'indent',
|
||||
'|',
|
||||
'uploadImage',
|
||||
'blockQuote',
|
||||
'insertTable',
|
||||
'mediaEmbed',
|
||||
'codeBlock'
|
||||
],
|
||||
image: {
|
||||
toolbar: [
|
||||
'imageStyle:inline',
|
||||
'imageStyle:block',
|
||||
'imageStyle:side',
|
||||
'|',
|
||||
'imageTextAlternative'
|
||||
]
|
||||
},
|
||||
table: {
|
||||
contentToolbar: [
|
||||
'tableColumn',
|
||||
'tableRow',
|
||||
'mergeTableCells'
|
||||
]
|
||||
}
|
||||
})
|
||||
.then(editor => {
|
||||
window.editor = editor;
|
||||
CKEditorInspector.attach(editor);
|
||||
window.console.log('CKEditor 5 is ready.', editor);
|
||||
})
|
||||
.catch(err => {
|
||||
window.console.error(err.stack);
|
||||
});
|
||||
//# sourceMappingURL=ckeditor.js.map
|
||||
1
_regroup/ckeditor5-footnotes/sample/ckeditor.js.map
Normal file
1
_regroup/ckeditor5-footnotes/sample/ckeditor.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ckeditor.js","sourceRoot":"","sources":["ckeditor.ts"],"names":[],"mappings":"AAMA,OAAO,EACN,aAAa,EACb,UAAU,EACV,mBAAmB,EACnB,UAAU,EACV,IAAI,EACJ,IAAI,EACJ,SAAS,EACT,UAAU,EACV,OAAO,EACP,KAAK,EACL,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,WAAW,EACX,MAAM,EACN,MAAM,EACN,IAAI,EACJ,IAAI,EACJ,UAAU,EACV,SAAS,EACT,KAAK,EACL,YAAY,EACZ,MAAM,WAAW,CAAC;AAEnB,OAAO,iBAAiB,MAAM,+BAA+B,CAAC;AAE9D,OAAO,SAAS,MAAM,qBAAqB,CAAC;AAE5C,OAAO,yBAAyB,CAAC;AAEjC,aAAa;KACX,MAAM,CAAE,QAAQ,CAAC,cAAc,CAAE,QAAQ,CAAG,EAAE;IAC9C,OAAO,EAAE;QACR,SAAS;QACT,UAAU;QACV,UAAU;QACV,UAAU;QACV,IAAI;QACJ,OAAO;QACP,KAAK;QACL,YAAY;QACZ,UAAU;QACV,YAAY;QACZ,WAAW;QACX,MAAM;QACN,MAAM;QACN,IAAI;QACJ,IAAI;QACJ,UAAU;QACV,SAAS;QACT,KAAK;QACL,YAAY;QACZ,SAAS;QACT,IAAI;QACJ,mBAAmB;KACnB;IACD,OAAO,EAAE;QACR,MAAM;QACN,MAAM;QACN,GAAG;QACH,UAAU;QACV,GAAG;QACH,SAAS;QACT,GAAG;QACH,MAAM;QACN,QAAQ;QACR,MAAM;QACN,MAAM;QACN,cAAc;QACd,cAAc;QACd,GAAG;QACH,SAAS;QACT,QAAQ;QACR,GAAG;QACH,aAAa;QACb,YAAY;QACZ,aAAa;QACb,YAAY;QACZ,WAAW;KACX;IACD,KAAK,EAAE;QACN,OAAO,EAAE;YACR,mBAAmB;YACnB,kBAAkB;YAClB,iBAAiB;YACjB,GAAG;YACH,sBAAsB;SACtB;KACD;IACD,KAAK,EAAE;QACN,cAAc,EAAE;YACf,aAAa;YACb,UAAU;YACV,iBAAiB;SACjB;KACD;CACD,CAAE;KACF,IAAI,CAAE,MAAM,CAAC,EAAE;IACf,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,iBAAiB,CAAC,MAAM,CAAE,MAAM,CAAE,CAAC;IACnC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAE,sBAAsB,EAAE,MAAM,CAAE,CAAC;AACtD,CAAC,CAAE;KACF,KAAK,CAAE,GAAG,CAAC,EAAE;IACb,MAAM,CAAC,OAAO,CAAC,KAAK,CAAE,GAAG,CAAC,KAAK,CAAE,CAAC;AACnC,CAAC,CAAE,CAAC"}
|
||||
112
_regroup/ckeditor5-footnotes/sample/ckeditor.ts
Normal file
112
_regroup/ckeditor5-footnotes/sample/ckeditor.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
declare global {
|
||||
interface Window {
|
||||
editor: ClassicEditor;
|
||||
}
|
||||
}
|
||||
|
||||
import {
|
||||
ClassicEditor,
|
||||
Autoformat,
|
||||
Base64UploadAdapter,
|
||||
BlockQuote,
|
||||
Bold,
|
||||
Code,
|
||||
CodeBlock,
|
||||
Essentials,
|
||||
Heading,
|
||||
Image,
|
||||
ImageCaption,
|
||||
ImageStyle,
|
||||
ImageToolbar,
|
||||
ImageUpload,
|
||||
Indent,
|
||||
Italic,
|
||||
Link,
|
||||
List,
|
||||
MediaEmbed,
|
||||
Paragraph,
|
||||
Table,
|
||||
TableToolbar
|
||||
} from 'ckeditor5';
|
||||
|
||||
import CKEditorInspector from '@ckeditor/ckeditor5-inspector';
|
||||
|
||||
import Footnotes from '../src/footnotes.js';
|
||||
|
||||
import 'ckeditor5/ckeditor5.css';
|
||||
|
||||
ClassicEditor
|
||||
.create( document.getElementById( 'editor' )!, {
|
||||
plugins: [
|
||||
Footnotes,
|
||||
Essentials,
|
||||
Autoformat,
|
||||
BlockQuote,
|
||||
Bold,
|
||||
Heading,
|
||||
Image,
|
||||
ImageCaption,
|
||||
ImageStyle,
|
||||
ImageToolbar,
|
||||
ImageUpload,
|
||||
Indent,
|
||||
Italic,
|
||||
Link,
|
||||
List,
|
||||
MediaEmbed,
|
||||
Paragraph,
|
||||
Table,
|
||||
TableToolbar,
|
||||
CodeBlock,
|
||||
Code,
|
||||
Base64UploadAdapter
|
||||
],
|
||||
toolbar: [
|
||||
'undo',
|
||||
'redo',
|
||||
'|',
|
||||
'footnote',
|
||||
'|',
|
||||
'heading',
|
||||
'|',
|
||||
'bold',
|
||||
'italic',
|
||||
'link',
|
||||
'code',
|
||||
'bulletedList',
|
||||
'numberedList',
|
||||
'|',
|
||||
'outdent',
|
||||
'indent',
|
||||
'|',
|
||||
'uploadImage',
|
||||
'blockQuote',
|
||||
'insertTable',
|
||||
'mediaEmbed',
|
||||
'codeBlock'
|
||||
],
|
||||
image: {
|
||||
toolbar: [
|
||||
'imageStyle:inline',
|
||||
'imageStyle:block',
|
||||
'imageStyle:side',
|
||||
'|',
|
||||
'imageTextAlternative'
|
||||
]
|
||||
},
|
||||
table: {
|
||||
contentToolbar: [
|
||||
'tableColumn',
|
||||
'tableRow',
|
||||
'mergeTableCells'
|
||||
]
|
||||
}
|
||||
} )
|
||||
.then( editor => {
|
||||
window.editor = editor;
|
||||
CKEditorInspector.attach( editor );
|
||||
window.console.log( 'CKEditor 5 is ready.', editor );
|
||||
} )
|
||||
.catch( err => {
|
||||
window.console.error( err.stack );
|
||||
} );
|
||||
172
_regroup/ckeditor5-footnotes/sample/dll.html
Normal file
172
_regroup/ckeditor5-footnotes/sample/dll.html
Normal file
@@ -0,0 +1,172 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<link rel="icon" type="image/png" href="https://ckeditor.com/docs/ckeditor5/latest/assets/img/favicons/32x32.png" sizes="32x32">
|
||||
<meta charset="utf-8">
|
||||
<title>CKEditor 5 – DLL Sample</title>
|
||||
<style>
|
||||
body {
|
||||
max-width: 800px;
|
||||
margin: 20px auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>CKEditor 5 – DLL Sample</h1>
|
||||
|
||||
<div id="editor">
|
||||
<h2>Production sample</h2>
|
||||
<p>
|
||||
This is a demo of the <a href="https://ckeditor.com/docs/ckeditor5/latest/builds/guides/overview.html#classic-editor">classic editor
|
||||
build</a>, initialized using the <a href="https://ckeditor.com/docs/ckeditor5/latest/builds/guides/development/dll-builds.html">DLL builds</a>.
|
||||
</p>
|
||||
<p>
|
||||
Your plugin (Footnotes) generated by the tool is already loaded into the editor. By default, it has an example button that adds some text to the editor. Whenever you change the plugin's name or toolbar items, make sure to update the editor configuration in the <code>sample/dll.html</code> file.
|
||||
</p>
|
||||
|
||||
<h3>Uncaught TypeError</h3>
|
||||
<p>If the editor is not initialized correctly and the browser console displays an error such as the following:</p>
|
||||
<pre><code>Uncaught TypeError: Cannot read properties of undefined (reading 'Footnotes') at dll.html:85</code></pre>
|
||||
<p>it means that the DLL build of the <code>@tomaitken/ckeditor5-footnotes</code> package has not been created.</p>
|
||||
<p>Please call the <code>yarn run dll:build</code> command in the CLI, and after it finishes, refresh the page.</p>
|
||||
|
||||
<h3>Anatomy of the DLL build</h3>
|
||||
<p>The source of the DLL build is located in the <code>src/index.ts</code> file. It may export as many things as the package offers.</p>
|
||||
|
||||
<h4>Maintaining the sample</h4>
|
||||
<p>Whenever you change objects exported by the DLL build, please review the content of the sample. Things to keep in mind:</p>
|
||||
<ul>
|
||||
<li>Review the list of loaded plugins in the configuration.</li>
|
||||
<li>Review names of items registered in toolbars.</li>
|
||||
</ul>
|
||||
|
||||
<h3>The goal</h3>
|
||||
<p>The primary purpose of the sample is to verify whether the plugins in the package will work together with CKEditor 5 created with the DLL approach.</p>
|
||||
|
||||
<h3>Publishing the package</h3>
|
||||
<p>
|
||||
While releasing TypeScript package on npm, few things have to be taken care of:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Building DLL</li>
|
||||
<li>Building TypeScript</li>
|
||||
<li>Changing the <code>main</code> filed in <code>package.json</code> to <code>.js</code> extension</li>
|
||||
</ul>
|
||||
<p>
|
||||
Likewise, after the release, there are few steps:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Deleting compiled TypeScript files (they are generated in the <code>src</code> directory, and create needless clutter)</li>
|
||||
<li>Changing the <code>main</code> filed in <code>package.json</code> back to <code>.ts</code> extension</li>
|
||||
</ul>
|
||||
<p>
|
||||
When calling <code>npm publish</code>, those steps are handled automatically by predefined <code>prepublishOnly</code> and <code>postpublish</code> scripts. To learn more, see <a href="https://docs.npmjs.com/cli/v7/using-npm/scripts#pre--post-scripts">NPM docs</a>.
|
||||
</p>
|
||||
|
||||
<h3>Reporting issues</h3>
|
||||
<p>If you found a problem with CKEditor 5 or the package generator, please, report an issue:</p>
|
||||
<ul>
|
||||
<li><a href="https://github.com/ckeditor/ckeditor5/issues/new/choose">CKEditor 5</a></li>
|
||||
<li><a href="https://github.com/ckeditor/create-ckeditor5-plugin/issues/new">The package generator</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- DLL builds are served from the `node_modules/` directory -->
|
||||
<script src="../node_modules/ckeditor5/build/ckeditor5-dll.js"></script>
|
||||
<script src="../node_modules/@ckeditor/ckeditor5-editor-classic/build/editor-classic.js"></script>
|
||||
<script src="../node_modules/@ckeditor/ckeditor5-code-block/build/code-block.js"></script>
|
||||
<script src="../node_modules/@ckeditor/ckeditor5-essentials/build/essentials.js"></script>
|
||||
<script src="../node_modules/@ckeditor/ckeditor5-basic-styles/build/basic-styles.js"></script>
|
||||
<script src="../node_modules/@ckeditor/ckeditor5-heading/build/heading.js"></script>
|
||||
<script src="../node_modules/@ckeditor/ckeditor5-autoformat/build/autoformat.js"></script>
|
||||
<script src="../node_modules/@ckeditor/ckeditor5-block-quote/build/block-quote.js"></script>
|
||||
<script src="../node_modules/@ckeditor/ckeditor5-image/build/image.js"></script>
|
||||
<script src="../node_modules/@ckeditor/ckeditor5-link/build/link.js"></script>
|
||||
<script src="../node_modules/@ckeditor/ckeditor5-indent/build/indent.js"></script>
|
||||
<script src="../node_modules/@ckeditor/ckeditor5-media-embed/build/media-embed.js"></script>
|
||||
<script src="../node_modules/@ckeditor/ckeditor5-list/build/list.js"></script>
|
||||
<script src="../node_modules/@ckeditor/ckeditor5-table/build/table.js"></script>
|
||||
|
||||
<!-- The "@tomaitken/ckeditor5-footnotes" package DLL build is served from the `build/` directory -->
|
||||
<script src="../build/footnotes.js"></script>
|
||||
|
||||
<script>
|
||||
console.log( 'Objects exported by the DLL build:', CKEditor5[ 'footnotes' ] );
|
||||
|
||||
CKEditor5.editorClassic.ClassicEditor
|
||||
.create( document.querySelector( '#editor' ), {
|
||||
plugins: [
|
||||
CKEditor5[ 'footnotes' ].Footnotes,
|
||||
CKEditor5.essentials.Essentials,
|
||||
CKEditor5.autoformat.Autoformat,
|
||||
CKEditor5.blockQuote.BlockQuote,
|
||||
CKEditor5.basicStyles.Bold,
|
||||
CKEditor5.heading.Heading,
|
||||
CKEditor5.image.Image,
|
||||
CKEditor5.image.ImageCaption,
|
||||
CKEditor5.image.ImageStyle,
|
||||
CKEditor5.image.ImageToolbar,
|
||||
CKEditor5.image.ImageUpload,
|
||||
CKEditor5.indent.Indent,
|
||||
CKEditor5.basicStyles.Italic,
|
||||
CKEditor5.link.Link,
|
||||
CKEditor5.list.List,
|
||||
CKEditor5.mediaEmbed.MediaEmbed,
|
||||
CKEditor5.paragraph.Paragraph,
|
||||
CKEditor5.table.Table,
|
||||
CKEditor5.table.TableToolbar,
|
||||
CKEditor5.codeBlock.CodeBlock,
|
||||
CKEditor5.basicStyles.Code,
|
||||
CKEditor5.upload.Base64UploadAdapter
|
||||
],
|
||||
toolbar: [
|
||||
'footnotesButton',
|
||||
'|',
|
||||
'heading',
|
||||
'|',
|
||||
'bold',
|
||||
'italic',
|
||||
'link',
|
||||
'code',
|
||||
'bulletedList',
|
||||
'numberedList',
|
||||
'|',
|
||||
'outdent',
|
||||
'indent',
|
||||
'|',
|
||||
'uploadImage',
|
||||
'blockQuote',
|
||||
'insertTable',
|
||||
'mediaEmbed',
|
||||
'codeBlock',
|
||||
'|',
|
||||
'undo',
|
||||
'redo'
|
||||
],
|
||||
image: {
|
||||
toolbar: [
|
||||
'imageStyle:inline',
|
||||
'imageStyle:block',
|
||||
'imageStyle:side',
|
||||
'|',
|
||||
'imageTextAlternative'
|
||||
]
|
||||
},
|
||||
table: {
|
||||
contentToolbar: [
|
||||
'tableColumn',
|
||||
'tableRow',
|
||||
'mergeTableCells'
|
||||
]
|
||||
}
|
||||
} )
|
||||
.then( editor => {
|
||||
window.editor = editor;
|
||||
} )
|
||||
.catch( error => {
|
||||
console.error( 'There was a problem initializing the editor.', error );
|
||||
} );
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
123
_regroup/ckeditor5-footnotes/sample/index.html
Normal file
123
_regroup/ckeditor5-footnotes/sample/index.html
Normal file
@@ -0,0 +1,123 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<link rel="icon" type="image/png" href="https://ckeditor.com/docs/ckeditor5/latest/assets/img/favicons/32x32.png"
|
||||
sizes="32x32">
|
||||
<meta charset="utf-8">
|
||||
<title>CKEditor 5 – Development Sample</title>
|
||||
<style>
|
||||
body {
|
||||
max-width: 800px;
|
||||
margin: 20px auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<h1>CKEditor 5 – Development Sample</h1>
|
||||
|
||||
<div id="editor">
|
||||
<h2>Development environment</h2>
|
||||
<p>
|
||||
This is a demo of the <a
|
||||
href="https://ckeditor.com/docs/ckeditor5/latest/builds/guides/overview.html#classic-editor">classic
|
||||
editor
|
||||
build</a> that loads your plugin (<code>Footnotes</code>) generated by the
|
||||
tool. You can modify this
|
||||
sample and use it to validate whether a plugin or a set of plugins work fine.
|
||||
</p>
|
||||
<p>
|
||||
<code>Footnotes</code> inserts text into the editor. You can click the
|
||||
CKEditor 5 icon in the toolbar and see the results.
|
||||
</p>
|
||||
|
||||
<h3>Helpful resources</h3>
|
||||
<ul>
|
||||
<li>Architecture
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
href="https://ckeditor.com/docs/ckeditor5/latest/framework/guides/architecture/core-editor-architecture.html">Core
|
||||
editor architecture</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
href="https://ckeditor.com/docs/ckeditor5/latest/framework/guides/architecture/editing-engine.html">The
|
||||
editing engine</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
href="https://ckeditor.com/docs/ckeditor5/latest/framework/guides/architecture/ui-library.html">The
|
||||
UI library</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a
|
||||
href="https://ckeditor.com/docs/ckeditor5/latest/framework/guides/support/browser-compatibility.html">Browser
|
||||
compatibility</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="https://ckeditor.com/docs/ckeditor5/latest/framework/guides/support/error-codes.html">The error
|
||||
codes</a>
|
||||
</li>
|
||||
|
||||
<li><a href="https://ckeditor.com/docs/ckeditor5/latest/builds/guides/development/dll-builds.html">The DLL
|
||||
builds</a></li>
|
||||
</ul>
|
||||
|
||||
<h3>The directory structure</h3>
|
||||
|
||||
<p>
|
||||
The code snippet below presents the directory structure.
|
||||
</p>
|
||||
|
||||
<pre><code class="language-plaintext">.
|
||||
├─ lang
|
||||
│ └─ contexts.json # Entries used for creating translations.
|
||||
├─ sample
|
||||
│ ├─ dll.html # The editor initialized using the DLL builds. Check README for details.
|
||||
│ ├─ index.html # The currently displayed file.
|
||||
│ └─ ckeditor.ts # The editor initialization script.
|
||||
├─ src
|
||||
│ ├─ footnotes.ts
|
||||
│ ├─ augmentation.ts # Type augmentations for the `@ckeditor/ckeditor5-core` module. Read more in <a href="https://ckeditor.com/docs/ckeditor5/latest/api/module_core_plugincollection-PluginsMap.html">PluginsMap</a> and <a href="https://ckeditor.com/docs/ckeditor5/latest/api/module_core_commandcollection-CommandsMap.html">CommandsMap</a>.
|
||||
│ ├─ index.ts # The modules exported by the package when using the DLL builds.
|
||||
│ └─ **/*.ts # All TypeScript source files should be saved here.
|
||||
├─ tests
|
||||
│ ├─ footnotes.ts
|
||||
│ ├─ index.ts # Tests for the plugin.
|
||||
│ └─ **/*.ts # All tests should be saved here.
|
||||
├─ theme
|
||||
│ ├─ icons
|
||||
│ │ ├─ ckeditor.svg # The CKEditor 5 icon displayed in the toolbar.
|
||||
│ │ └─ **/*.svg # All icon files should be saved here.
|
||||
│ └─ **/*.css # All CSS files should be saved here.
|
||||
├─ typings
|
||||
│ └─ **/*.d.ts # Files containing type definitions.
|
||||
├─ .editorconfig
|
||||
├─ ...
|
||||
├─ README.md
|
||||
└─ tsconfig.json # TypeScript configuration file.</code></pre>
|
||||
|
||||
<h3>Reporting issues</h3>
|
||||
<p>If you found a problem with CKEditor 5 or the package generator, please, report an issue:</p>
|
||||
<ul>
|
||||
<li><a href="https://github.com/ckeditor/ckeditor5/issues/new/choose">CKEditor 5</a></li>
|
||||
<li><a href="https://github.com/ckeditor/ckeditor5-package-generator/issues/new">The package generator</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<script src="./ckeditor.dist.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
65
_regroup/ckeditor5-footnotes/scripts/build-dist.mjs
Normal file
65
_regroup/ckeditor5-footnotes/scripts/build-dist.mjs
Normal file
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* @license Copyright (c) 2020-2024, CKSource Holding sp. z o.o. All rights reserved.
|
||||
* For licensing, see LICENSE.md.
|
||||
*/
|
||||
|
||||
/* eslint-env node */
|
||||
|
||||
import { createRequire } from 'module';
|
||||
import upath from 'upath';
|
||||
import chalk from 'chalk';
|
||||
import { build } from '@ckeditor/ckeditor5-dev-build-tools';
|
||||
|
||||
function dist( path ) {
|
||||
return upath.join( 'dist', path );
|
||||
}
|
||||
|
||||
( async () => {
|
||||
const tsconfig = 'tsconfig.dist.ckeditor5.json';
|
||||
|
||||
/**
|
||||
* Step 1
|
||||
*/
|
||||
console.log( chalk.cyan( '1/2: Generating NPM build...' ) );
|
||||
|
||||
const require = createRequire( import.meta.url );
|
||||
const pkg = require( upath.resolve( process.cwd(), './package.json' ) );
|
||||
|
||||
await build( {
|
||||
input: 'src/index.ts',
|
||||
output: dist( './index.js' ),
|
||||
tsconfig: 'tsconfig.dist.json',
|
||||
external: [
|
||||
'ckeditor5',
|
||||
'ckeditor5-premium-features',
|
||||
...Object.keys( {
|
||||
...pkg.dependencies,
|
||||
...pkg.peerDependencies
|
||||
} )
|
||||
],
|
||||
clean: true,
|
||||
sourceMap: true,
|
||||
declarations: true,
|
||||
translations: '**/*.po'
|
||||
} );
|
||||
|
||||
/**
|
||||
* Step 2
|
||||
*/
|
||||
console.log( chalk.cyan( '2/2: Generating browser build...' ) );
|
||||
|
||||
await build( {
|
||||
output: dist( 'browser/index.js' ),
|
||||
tsconfig,
|
||||
sourceMap: true,
|
||||
minify: true,
|
||||
browser: true,
|
||||
name: 'footnotes',
|
||||
external: [
|
||||
'ckeditor5',
|
||||
'ckeditor5-premium-features'
|
||||
]
|
||||
} );
|
||||
} )();
|
||||
6
_regroup/ckeditor5-footnotes/src/augmentation.d.ts
vendored
Normal file
6
_regroup/ckeditor5-footnotes/src/augmentation.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
import type { Footnotes } from './index.js';
|
||||
declare module '@ckeditor/ckeditor5-core' {
|
||||
interface PluginsMap {
|
||||
[Footnotes.pluginName]: Footnotes;
|
||||
}
|
||||
}
|
||||
2
_regroup/ckeditor5-footnotes/src/augmentation.js
Normal file
2
_regroup/ckeditor5-footnotes/src/augmentation.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=augmentation.js.map
|
||||
1
_regroup/ckeditor5-footnotes/src/augmentation.js.map
Normal file
1
_regroup/ckeditor5-footnotes/src/augmentation.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"augmentation.js","sourceRoot":"","sources":["augmentation.ts"],"names":[],"mappings":""}
|
||||
7
_regroup/ckeditor5-footnotes/src/augmentation.ts
Normal file
7
_regroup/ckeditor5-footnotes/src/augmentation.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { Footnotes } from './index.js';
|
||||
|
||||
declare module '@ckeditor/ckeditor5-core' {
|
||||
interface PluginsMap {
|
||||
[ Footnotes.pluginName ]: Footnotes;
|
||||
}
|
||||
}
|
||||
31
_regroup/ckeditor5-footnotes/src/constants.d.ts
vendored
Normal file
31
_regroup/ckeditor5-footnotes/src/constants.d.ts
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
export declare const TOOLBAR_COMPONENT_NAME = "footnote";
|
||||
export declare const DATA_FOOTNOTE_ID = "data-footnote-id";
|
||||
export declare const ELEMENTS: {
|
||||
footnoteItem: string;
|
||||
footnoteReference: string;
|
||||
footnoteSection: string;
|
||||
footnoteContent: string;
|
||||
footnoteBackLink: string;
|
||||
};
|
||||
export declare const CLASSES: {
|
||||
footnoteContent: string;
|
||||
footnoteItem: string;
|
||||
footnoteReference: string;
|
||||
footnoteSection: string;
|
||||
footnoteBackLink: string;
|
||||
footnotes: string;
|
||||
hidden: string;
|
||||
};
|
||||
export declare const COMMANDS: {
|
||||
insertFootnote: string;
|
||||
};
|
||||
export declare const ATTRIBUTES: {
|
||||
footnoteContent: string;
|
||||
footnoteId: string;
|
||||
footnoteIndex: string;
|
||||
footnoteItem: string;
|
||||
footnoteReference: string;
|
||||
footnoteSection: string;
|
||||
footnoteBackLink: string;
|
||||
footnoteBackLinkHref: string;
|
||||
};
|
||||
32
_regroup/ckeditor5-footnotes/src/constants.js
Normal file
32
_regroup/ckeditor5-footnotes/src/constants.js
Normal file
@@ -0,0 +1,32 @@
|
||||
export const TOOLBAR_COMPONENT_NAME = 'footnote';
|
||||
export const DATA_FOOTNOTE_ID = 'data-footnote-id';
|
||||
export const ELEMENTS = {
|
||||
footnoteItem: 'footnoteItem',
|
||||
footnoteReference: 'footnoteReference',
|
||||
footnoteSection: 'footnoteSection',
|
||||
footnoteContent: 'footnoteContent',
|
||||
footnoteBackLink: 'footnoteBackLink'
|
||||
};
|
||||
export const CLASSES = {
|
||||
footnoteContent: 'footnote-content',
|
||||
footnoteItem: 'footnote-item',
|
||||
footnoteReference: 'footnote-reference',
|
||||
footnoteSection: 'footnote-section',
|
||||
footnoteBackLink: 'footnote-back-link',
|
||||
footnotes: 'footnotes',
|
||||
hidden: 'hidden'
|
||||
};
|
||||
export const COMMANDS = {
|
||||
insertFootnote: 'InsertFootnote'
|
||||
};
|
||||
export const ATTRIBUTES = {
|
||||
footnoteContent: 'data-footnote-content',
|
||||
footnoteId: 'data-footnote-id',
|
||||
footnoteIndex: 'data-footnote-index',
|
||||
footnoteItem: 'data-footnote-item',
|
||||
footnoteReference: 'data-footnote-reference',
|
||||
footnoteSection: 'data-footnote-section',
|
||||
footnoteBackLink: 'data-footnote-back-link',
|
||||
footnoteBackLinkHref: 'data-footnote-back-link-href'
|
||||
};
|
||||
//# sourceMappingURL=constants.js.map
|
||||
1
_regroup/ckeditor5-footnotes/src/constants.js.map
Normal file
1
_regroup/ckeditor5-footnotes/src/constants.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"constants.js","sourceRoot":"","sources":["constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,sBAAsB,GAAG,UAAU,CAAC;AACjD,MAAM,CAAC,MAAM,gBAAgB,GAAG,kBAAkB,CAAC;AAEnD,MAAM,CAAC,MAAM,QAAQ,GAAG;IACvB,YAAY,EAAE,cAAc;IAC5B,iBAAiB,EAAE,mBAAmB;IACtC,eAAe,EAAE,iBAAiB;IAClC,eAAe,EAAE,iBAAiB;IAClC,gBAAgB,EAAE,kBAAkB;CACpC,CAAC;AAEF,MAAM,CAAC,MAAM,OAAO,GAAG;IACtB,eAAe,EAAE,kBAAkB;IACnC,YAAY,EAAE,eAAe;IAC7B,iBAAiB,EAAE,oBAAoB;IACvC,eAAe,EAAE,kBAAkB;IACnC,gBAAgB,EAAE,oBAAoB;IACtC,SAAS,EAAE,WAAW;IACtB,MAAM,EAAE,QAAQ;CAChB,CAAC;AAEF,MAAM,CAAC,MAAM,QAAQ,GAAG;IACvB,cAAc,EAAE,gBAAgB;CAChC,CAAC;AAEF,MAAM,CAAC,MAAM,UAAU,GAAG;IACzB,eAAe,EAAE,uBAAuB;IACxC,UAAU,EAAE,kBAAkB;IAC9B,aAAa,EAAE,qBAAqB;IACpC,YAAY,EAAE,oBAAoB;IAClC,iBAAiB,EAAE,yBAAyB;IAC5C,eAAe,EAAE,uBAAuB;IACxC,gBAAgB,EAAE,yBAAyB;IAC3C,oBAAoB,EAAE,8BAA8B;CACpD,CAAC"}
|
||||
35
_regroup/ckeditor5-footnotes/src/constants.ts
Normal file
35
_regroup/ckeditor5-footnotes/src/constants.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
export const TOOLBAR_COMPONENT_NAME = 'footnote';
|
||||
export const DATA_FOOTNOTE_ID = 'data-footnote-id';
|
||||
|
||||
export const ELEMENTS = {
|
||||
footnoteItem: 'footnoteItem',
|
||||
footnoteReference: 'footnoteReference',
|
||||
footnoteSection: 'footnoteSection',
|
||||
footnoteContent: 'footnoteContent',
|
||||
footnoteBackLink: 'footnoteBackLink'
|
||||
};
|
||||
|
||||
export const CLASSES = {
|
||||
footnoteContent: 'footnote-content',
|
||||
footnoteItem: 'footnote-item',
|
||||
footnoteReference: 'footnote-reference',
|
||||
footnoteSection: 'footnote-section',
|
||||
footnoteBackLink: 'footnote-back-link',
|
||||
footnotes: 'footnotes', // a class already used on our sites for the footnote section
|
||||
hidden: 'hidden'
|
||||
};
|
||||
|
||||
export const COMMANDS = {
|
||||
insertFootnote: 'InsertFootnote'
|
||||
};
|
||||
|
||||
export const ATTRIBUTES = {
|
||||
footnoteContent: 'data-footnote-content',
|
||||
footnoteId: 'data-footnote-id',
|
||||
footnoteIndex: 'data-footnote-index',
|
||||
footnoteItem: 'data-footnote-item',
|
||||
footnoteReference: 'data-footnote-reference',
|
||||
footnoteSection: 'data-footnote-section',
|
||||
footnoteBackLink: 'data-footnote-back-link',
|
||||
footnoteBackLinkHref: 'data-footnote-back-link-href'
|
||||
};
|
||||
6
_regroup/ckeditor5-footnotes/src/footnote-editing/auto-formatting.d.ts
vendored
Normal file
6
_regroup/ckeditor5-footnotes/src/footnote-editing/auto-formatting.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
import { type Editor } from 'ckeditor5/src/core.js';
|
||||
import { type Element } from 'ckeditor5/src/engine.js';
|
||||
/**
|
||||
* Adds functionality to support creating footnotes using markdown syntax, e.g. `[^1]`.
|
||||
*/
|
||||
export declare const addFootnoteAutoformatting: (editor: Editor, rootElement: Element) => void;
|
||||
@@ -0,0 +1,101 @@
|
||||
import { Text, TextProxy } from 'ckeditor5/src/engine.js';
|
||||
import { inlineAutoformatEditing } from "@ckeditor/ckeditor5-autoformat";
|
||||
import { COMMANDS, ELEMENTS } from '../constants.js';
|
||||
import { modelQueryElement, modelQueryElementsAll } from '../utils.js';
|
||||
/**
|
||||
* CKEditor's autoformatting feature (basically find and replace) has two opinionated default modes:
|
||||
* block autoformatting, which replaces the entire line, and inline autoformatting,
|
||||
* which expects a section to be formatted (but, importantly, not removed) surrounded by
|
||||
* a pair of delimters which get removed.
|
||||
*
|
||||
* Neither of those are ideal for this case. We want to replace the matched text with a new element,
|
||||
* without deleting the entire line.
|
||||
*
|
||||
* However, inlineAutoformatEditing allows for passing in a custom callback to handle
|
||||
* regex matching, which also allows us to specify which sections to remove and
|
||||
* which sections pass on to the formatting callback. This method removes the entire
|
||||
* matched text, while passing the range of the numeric text on to the formatting callback.
|
||||
*
|
||||
* If 0 or more than 1 match is found, it returns empty ranges for both format and remove, which is a no-op.
|
||||
*/
|
||||
const regexMatchCallback = (editor, text) => {
|
||||
const selectionStart = editor.model.document.selection.anchor;
|
||||
// get the text node containing the cursor's position, or the one ending at `the cursor's position
|
||||
const surroundingText = selectionStart && (selectionStart.textNode || selectionStart.getShiftedBy(-1).textNode);
|
||||
if (!selectionStart || !surroundingText) {
|
||||
return {
|
||||
remove: [],
|
||||
format: []
|
||||
};
|
||||
}
|
||||
const results = text.matchAll(/\[\^([0-9]+)\]/g);
|
||||
for (const result of results || []) {
|
||||
const removeStartIndex = text.indexOf(result[0]);
|
||||
const removeEndIndex = removeStartIndex + result[0].length;
|
||||
const textNodeOffset = selectionStart.parent.getChildStartOffset(surroundingText);
|
||||
// if the cursor isn't at the end of the range to be replaced, do nothing
|
||||
if (textNodeOffset === null || selectionStart.offset !== textNodeOffset + removeEndIndex) {
|
||||
continue;
|
||||
}
|
||||
const formatStartIndex = removeStartIndex + 2;
|
||||
const formatEndIndex = formatStartIndex + result[1].length;
|
||||
return {
|
||||
remove: [[removeStartIndex, removeEndIndex]],
|
||||
format: [[formatStartIndex, formatEndIndex]]
|
||||
};
|
||||
}
|
||||
return {
|
||||
remove: [],
|
||||
format: []
|
||||
};
|
||||
};
|
||||
/**
|
||||
* This callback takes in a range of text passed on by regexMatchCallback,
|
||||
* and attempts to insert a corresponding footnote reference at the current location.
|
||||
*
|
||||
* Footnotes only get inserted if the matching range is an integer between 1
|
||||
* and the number of existing footnotes + 1.
|
||||
*/
|
||||
const formatCallback = (ranges, editor, rootElement) => {
|
||||
const command = editor.commands.get(COMMANDS.insertFootnote);
|
||||
if (!command || !command.isEnabled) {
|
||||
return;
|
||||
}
|
||||
const text = [...ranges[0].getItems()][0];
|
||||
if (!(text instanceof TextProxy || text instanceof Text)) {
|
||||
return false;
|
||||
}
|
||||
const match = text.data.match(/[0-9]+/);
|
||||
if (!match) {
|
||||
return false;
|
||||
}
|
||||
const footnoteIndex = parseInt(match[0]);
|
||||
const footnoteSection = modelQueryElement(editor, rootElement, element => element.is('element', ELEMENTS.footnoteSection));
|
||||
if (!footnoteSection) {
|
||||
if (footnoteIndex !== 1) {
|
||||
return false;
|
||||
}
|
||||
editor.execute(COMMANDS.insertFootnote);
|
||||
return;
|
||||
}
|
||||
const footnoteCount = modelQueryElementsAll(editor, footnoteSection, element => element.is('element', ELEMENTS.footnoteItem)).length;
|
||||
if (footnoteIndex === footnoteCount + 1) {
|
||||
editor.execute(COMMANDS.insertFootnote);
|
||||
return;
|
||||
}
|
||||
else if (footnoteIndex >= 1 && footnoteIndex <= footnoteCount) {
|
||||
editor.execute(COMMANDS.insertFootnote, { footnoteIndex });
|
||||
return;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
/**
|
||||
* Adds functionality to support creating footnotes using markdown syntax, e.g. `[^1]`.
|
||||
*/
|
||||
export const addFootnoteAutoformatting = (editor, rootElement) => {
|
||||
if (editor.plugins.has('Autoformat')) {
|
||||
const autoformatPluginInstance = editor.plugins.get('Autoformat');
|
||||
inlineAutoformatEditing(editor, autoformatPluginInstance, text => regexMatchCallback(editor, text), (_, ranges) => formatCallback(ranges, editor, rootElement));
|
||||
}
|
||||
};
|
||||
//# sourceMappingURL=auto-formatting.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"auto-formatting.js","sourceRoot":"","sources":["auto-formatting.ts"],"names":[],"mappings":"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"}
|
||||
@@ -0,0 +1,123 @@
|
||||
import { type Editor } from 'ckeditor5/src/core.js';
|
||||
import { Text, TextProxy, type Element, type Range } from 'ckeditor5/src/engine.js';
|
||||
import { type Autoformat, inlineAutoformatEditing } from "@ckeditor/ckeditor5-autoformat";
|
||||
|
||||
import { COMMANDS, ELEMENTS } from '../constants.js';
|
||||
import { modelQueryElement, modelQueryElementsAll } from '../utils.js';
|
||||
|
||||
/**
|
||||
* CKEditor's autoformatting feature (basically find and replace) has two opinionated default modes:
|
||||
* block autoformatting, which replaces the entire line, and inline autoformatting,
|
||||
* which expects a section to be formatted (but, importantly, not removed) surrounded by
|
||||
* a pair of delimters which get removed.
|
||||
*
|
||||
* Neither of those are ideal for this case. We want to replace the matched text with a new element,
|
||||
* without deleting the entire line.
|
||||
*
|
||||
* However, inlineAutoformatEditing allows for passing in a custom callback to handle
|
||||
* regex matching, which also allows us to specify which sections to remove and
|
||||
* which sections pass on to the formatting callback. This method removes the entire
|
||||
* matched text, while passing the range of the numeric text on to the formatting callback.
|
||||
*
|
||||
* If 0 or more than 1 match is found, it returns empty ranges for both format and remove, which is a no-op.
|
||||
*/
|
||||
const regexMatchCallback = (
|
||||
editor: Editor,
|
||||
text: string
|
||||
): {
|
||||
remove: Array<[number, number]>;
|
||||
format: Array<[number, number]>;
|
||||
} => {
|
||||
const selectionStart = editor.model.document.selection.anchor;
|
||||
// get the text node containing the cursor's position, or the one ending at `the cursor's position
|
||||
const surroundingText = selectionStart && ( selectionStart.textNode || selectionStart.getShiftedBy( -1 ).textNode );
|
||||
|
||||
if ( !selectionStart || !surroundingText ) {
|
||||
return {
|
||||
remove: [],
|
||||
format: []
|
||||
};
|
||||
}
|
||||
|
||||
const results = text.matchAll( /\[\^([0-9]+)\]/g );
|
||||
|
||||
for ( const result of results || [] ) {
|
||||
const removeStartIndex = text.indexOf( result[ 0 ] );
|
||||
const removeEndIndex = removeStartIndex + result[ 0 ].length;
|
||||
const textNodeOffset = selectionStart.parent.getChildStartOffset( surroundingText );
|
||||
|
||||
// if the cursor isn't at the end of the range to be replaced, do nothing
|
||||
if ( textNodeOffset === null || selectionStart.offset !== textNodeOffset + removeEndIndex ) {
|
||||
continue;
|
||||
}
|
||||
const formatStartIndex = removeStartIndex + 2;
|
||||
const formatEndIndex = formatStartIndex + result[ 1 ].length;
|
||||
return {
|
||||
remove: [ [ removeStartIndex, removeEndIndex ] ],
|
||||
format: [ [ formatStartIndex, formatEndIndex ] ]
|
||||
};
|
||||
}
|
||||
return {
|
||||
remove: [],
|
||||
format: []
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* This callback takes in a range of text passed on by regexMatchCallback,
|
||||
* and attempts to insert a corresponding footnote reference at the current location.
|
||||
*
|
||||
* Footnotes only get inserted if the matching range is an integer between 1
|
||||
* and the number of existing footnotes + 1.
|
||||
*/
|
||||
const formatCallback = ( ranges: Array<Range>, editor: Editor, rootElement: Element ): boolean | undefined => {
|
||||
const command = editor.commands.get( COMMANDS.insertFootnote );
|
||||
if ( !command || !command.isEnabled ) {
|
||||
return;
|
||||
}
|
||||
const text = [ ...ranges[ 0 ].getItems() ][ 0 ];
|
||||
if ( !( text instanceof TextProxy || text instanceof Text ) ) {
|
||||
return false;
|
||||
}
|
||||
const match = text.data.match( /[0-9]+/ );
|
||||
if ( !match ) {
|
||||
return false;
|
||||
}
|
||||
const footnoteIndex = parseInt( match[ 0 ] );
|
||||
const footnoteSection = modelQueryElement( editor, rootElement, element =>
|
||||
element.is( 'element', ELEMENTS.footnoteSection )
|
||||
);
|
||||
if ( !footnoteSection ) {
|
||||
if ( footnoteIndex !== 1 ) {
|
||||
return false;
|
||||
}
|
||||
editor.execute( COMMANDS.insertFootnote );
|
||||
return;
|
||||
}
|
||||
const footnoteCount = modelQueryElementsAll( editor, footnoteSection, element =>
|
||||
element.is( 'element', ELEMENTS.footnoteItem )
|
||||
).length;
|
||||
if ( footnoteIndex === footnoteCount + 1 ) {
|
||||
editor.execute( COMMANDS.insertFootnote );
|
||||
return;
|
||||
} else if ( footnoteIndex >= 1 && footnoteIndex <= footnoteCount ) {
|
||||
editor.execute( COMMANDS.insertFootnote, { footnoteIndex } );
|
||||
return;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds functionality to support creating footnotes using markdown syntax, e.g. `[^1]`.
|
||||
*/
|
||||
export const addFootnoteAutoformatting = ( editor: Editor, rootElement: Element ): void => {
|
||||
if ( editor.plugins.has( 'Autoformat' ) ) {
|
||||
const autoformatPluginInstance = editor.plugins.get( 'Autoformat' ) as Autoformat;
|
||||
inlineAutoformatEditing(
|
||||
editor,
|
||||
autoformatPluginInstance,
|
||||
text => regexMatchCallback( editor, text ),
|
||||
( _, ranges: Array<Range> ) => formatCallback( ranges, editor, rootElement )
|
||||
);
|
||||
}
|
||||
};
|
||||
5
_regroup/ckeditor5-footnotes/src/footnote-editing/converters.d.ts
vendored
Normal file
5
_regroup/ckeditor5-footnotes/src/footnote-editing/converters.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import { type Editor } from 'ckeditor5/src/core.js';
|
||||
/**
|
||||
* Defines methods for converting between model, data view, and editing view representations of each element type.
|
||||
*/
|
||||
export declare const defineConverters: (editor: Editor) => void;
|
||||
296
_regroup/ckeditor5-footnotes/src/footnote-editing/converters.js
Normal file
296
_regroup/ckeditor5-footnotes/src/footnote-editing/converters.js
Normal file
@@ -0,0 +1,296 @@
|
||||
import { Element } from "ckeditor5/src/engine.js";
|
||||
import { toWidget, toWidgetEditable } from 'ckeditor5/src/widget.js';
|
||||
import { ATTRIBUTES, CLASSES, ELEMENTS } from '../constants.js';
|
||||
import { viewQueryElement } from '../utils.js';
|
||||
/**
|
||||
* Defines methods for converting between model, data view, and editing view representations of each element type.
|
||||
*/
|
||||
export const defineConverters = (editor) => {
|
||||
const conversion = editor.conversion;
|
||||
/** *********************************Attribute Conversion************************************/
|
||||
conversion.for('downcast').attributeToAttribute({
|
||||
model: ATTRIBUTES.footnoteId,
|
||||
view: ATTRIBUTES.footnoteId
|
||||
});
|
||||
conversion.for('downcast').attributeToAttribute({
|
||||
model: ATTRIBUTES.footnoteIndex,
|
||||
view: ATTRIBUTES.footnoteIndex
|
||||
});
|
||||
/** *********************************Footnote Section Conversion************************************/
|
||||
// ((data) view → model)
|
||||
conversion.for('upcast').elementToElement({
|
||||
view: {
|
||||
attributes: {
|
||||
[ATTRIBUTES.footnoteSection]: true
|
||||
}
|
||||
},
|
||||
model: ELEMENTS.footnoteSection,
|
||||
converterPriority: 'high'
|
||||
});
|
||||
// (model → data view)
|
||||
conversion.for('dataDowncast').elementToElement({
|
||||
model: ELEMENTS.footnoteSection,
|
||||
view: {
|
||||
name: 'ol',
|
||||
attributes: {
|
||||
[ATTRIBUTES.footnoteSection]: '',
|
||||
role: 'doc-endnotes'
|
||||
},
|
||||
classes: [CLASSES.footnoteSection, CLASSES.footnotes]
|
||||
}
|
||||
});
|
||||
// (model → editing view)
|
||||
conversion.for('editingDowncast').elementToElement({
|
||||
model: ELEMENTS.footnoteSection,
|
||||
view: (_, conversionApi) => {
|
||||
const viewWriter = conversionApi.writer;
|
||||
// eslint-disable-next-line max-len
|
||||
/** The below is a div rather than an ol because using an ol here caused weird behavior, including randomly duplicating the footnotes section.
|
||||
* This is techincally invalid HTML, but it's valid in the data view (that is, the version shown in the post). I've added role='list'
|
||||
* as a next-best option, in accordance with ARIA recommendations.
|
||||
*/
|
||||
const section = viewWriter.createContainerElement('div', {
|
||||
[ATTRIBUTES.footnoteSection]: '',
|
||||
role: 'doc-endnotes list',
|
||||
class: CLASSES.footnoteSection
|
||||
});
|
||||
return toWidget(section, viewWriter, { label: 'footnote widget' });
|
||||
}
|
||||
});
|
||||
/** *********************************Footnote Content Conversion************************************/
|
||||
conversion.for('upcast').elementToElement({
|
||||
view: {
|
||||
attributes: {
|
||||
[ATTRIBUTES.footnoteContent]: true
|
||||
}
|
||||
},
|
||||
model: (viewElement, conversionApi) => {
|
||||
const modelWriter = conversionApi.writer;
|
||||
return modelWriter.createElement(ELEMENTS.footnoteContent);
|
||||
}
|
||||
});
|
||||
conversion.for('dataDowncast').elementToElement({
|
||||
model: ELEMENTS.footnoteContent,
|
||||
view: {
|
||||
name: 'div',
|
||||
attributes: { [ATTRIBUTES.footnoteContent]: '' },
|
||||
classes: [CLASSES.footnoteContent]
|
||||
}
|
||||
});
|
||||
conversion.for('editingDowncast').elementToElement({
|
||||
model: ELEMENTS.footnoteContent,
|
||||
view: (_, conversionApi) => {
|
||||
const viewWriter = conversionApi.writer;
|
||||
// Note: You use a more specialized createEditableElement() method here.
|
||||
const section = viewWriter.createEditableElement('div', {
|
||||
[ATTRIBUTES.footnoteContent]: '',
|
||||
class: CLASSES.footnoteContent
|
||||
});
|
||||
return toWidgetEditable(section, viewWriter);
|
||||
}
|
||||
});
|
||||
/** *********************************Footnote Item Conversion************************************/
|
||||
conversion.for('upcast').elementToElement({
|
||||
view: {
|
||||
attributes: {
|
||||
[ATTRIBUTES.footnoteItem]: true
|
||||
}
|
||||
},
|
||||
model: (viewElement, conversionApi) => {
|
||||
const modelWriter = conversionApi.writer;
|
||||
const id = viewElement.getAttribute(ATTRIBUTES.footnoteId);
|
||||
const index = viewElement.getAttribute(ATTRIBUTES.footnoteIndex);
|
||||
if (id === undefined || index === undefined) {
|
||||
return null;
|
||||
}
|
||||
return modelWriter.createElement(ELEMENTS.footnoteItem, {
|
||||
[ATTRIBUTES.footnoteIndex]: index,
|
||||
[ATTRIBUTES.footnoteId]: id
|
||||
});
|
||||
},
|
||||
/** converterPriority is needed to supersede the builtin upcastListItemStyle
|
||||
* which for unknown reasons causes a null reference error.
|
||||
*/
|
||||
converterPriority: 'high'
|
||||
});
|
||||
conversion.for('dataDowncast').elementToElement({
|
||||
model: ELEMENTS.footnoteItem,
|
||||
view: createFootnoteItemViewElement
|
||||
});
|
||||
conversion.for('editingDowncast').elementToElement({
|
||||
model: ELEMENTS.footnoteItem,
|
||||
view: createFootnoteItemViewElement
|
||||
});
|
||||
/** *********************************Footnote Reference Conversion************************************/
|
||||
conversion.for('upcast').elementToElement({
|
||||
view: {
|
||||
attributes: {
|
||||
[ATTRIBUTES.footnoteReference]: true
|
||||
}
|
||||
},
|
||||
model: (viewElement, conversionApi) => {
|
||||
const modelWriter = conversionApi.writer;
|
||||
const index = viewElement.getAttribute(ATTRIBUTES.footnoteIndex);
|
||||
const id = viewElement.getAttribute(ATTRIBUTES.footnoteId);
|
||||
if (index === undefined || id === undefined) {
|
||||
return null;
|
||||
}
|
||||
return modelWriter.createElement(ELEMENTS.footnoteReference, {
|
||||
[ATTRIBUTES.footnoteIndex]: index,
|
||||
[ATTRIBUTES.footnoteId]: id
|
||||
});
|
||||
}
|
||||
});
|
||||
conversion.for('editingDowncast').elementToElement({
|
||||
model: ELEMENTS.footnoteReference,
|
||||
view: (modelElement, conversionApi) => {
|
||||
const viewWriter = conversionApi.writer;
|
||||
const footnoteReferenceViewElement = createFootnoteReferenceViewElement(modelElement, conversionApi);
|
||||
return toWidget(footnoteReferenceViewElement, viewWriter);
|
||||
}
|
||||
});
|
||||
conversion.for('dataDowncast').elementToElement({
|
||||
model: ELEMENTS.footnoteReference,
|
||||
view: createFootnoteReferenceViewElement
|
||||
});
|
||||
/** This is an event listener for changes to the `data-footnote-index` attribute on `footnoteReference` elements.
|
||||
* When that event fires, the callback function below updates the displayed view of the footnote reference in the
|
||||
* editor to match the new index.
|
||||
*/
|
||||
conversion.for('editingDowncast').add(dispatcher => {
|
||||
dispatcher.on(`attribute:${ATTRIBUTES.footnoteIndex}:${ELEMENTS.footnoteReference}`, (_, data, conversionApi) => updateFootnoteReferenceView(data, conversionApi, editor), { priority: 'high' });
|
||||
});
|
||||
/** *********************************Footnote Back Link Conversion************************************/
|
||||
conversion.for('upcast').elementToElement({
|
||||
view: {
|
||||
attributes: {
|
||||
[ATTRIBUTES.footnoteBackLink]: true
|
||||
}
|
||||
},
|
||||
model: (viewElement, conversionApi) => {
|
||||
const modelWriter = conversionApi.writer;
|
||||
const id = viewElement.getAttribute(ATTRIBUTES.footnoteId);
|
||||
if (id === undefined) {
|
||||
return null;
|
||||
}
|
||||
return modelWriter.createElement(ELEMENTS.footnoteBackLink, {
|
||||
[ATTRIBUTES.footnoteId]: id
|
||||
});
|
||||
}
|
||||
});
|
||||
conversion.for('dataDowncast').elementToElement({
|
||||
model: ELEMENTS.footnoteBackLink,
|
||||
view: createFootnoteBackLinkViewElement
|
||||
});
|
||||
conversion.for('editingDowncast').elementToElement({
|
||||
model: ELEMENTS.footnoteBackLink,
|
||||
view: createFootnoteBackLinkViewElement
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Creates and returns a view element for a footnote backlink,
|
||||
* which navigates back to the inline reference in the text. Used
|
||||
* for both data and editing downcasts.
|
||||
*/
|
||||
function createFootnoteBackLinkViewElement(modelElement, conversionApi) {
|
||||
const viewWriter = conversionApi.writer;
|
||||
const id = `${modelElement.getAttribute(ATTRIBUTES.footnoteId)}`;
|
||||
if (id === undefined) {
|
||||
throw new Error('Footnote return link has no provided Id.');
|
||||
}
|
||||
const footnoteBackLinkView = viewWriter.createContainerElement('span', {
|
||||
class: CLASSES.footnoteBackLink,
|
||||
[ATTRIBUTES.footnoteBackLink]: '',
|
||||
[ATTRIBUTES.footnoteId]: id
|
||||
});
|
||||
const sup = viewWriter.createContainerElement('sup');
|
||||
const strong = viewWriter.createContainerElement('strong');
|
||||
const anchor = viewWriter.createContainerElement('a', { href: `#fnref${id}` });
|
||||
const innerText = viewWriter.createText('^');
|
||||
viewWriter.insert(viewWriter.createPositionAt(anchor, 0), innerText);
|
||||
viewWriter.insert(viewWriter.createPositionAt(strong, 0), anchor);
|
||||
viewWriter.insert(viewWriter.createPositionAt(sup, 0), strong);
|
||||
viewWriter.insert(viewWriter.createPositionAt(footnoteBackLinkView, 0), sup);
|
||||
return footnoteBackLinkView;
|
||||
}
|
||||
/**
|
||||
* Creates and returns a view element for an inline footnote reference. Used for both
|
||||
* data downcast and editing downcast conversions.
|
||||
*/
|
||||
function createFootnoteReferenceViewElement(modelElement, conversionApi) {
|
||||
const viewWriter = conversionApi.writer;
|
||||
const index = `${modelElement.getAttribute(ATTRIBUTES.footnoteIndex)}`;
|
||||
const id = `${modelElement.getAttribute(ATTRIBUTES.footnoteId)}`;
|
||||
if (index === 'undefined') {
|
||||
throw new Error('Footnote reference has no provided index.');
|
||||
}
|
||||
if (id === 'undefined') {
|
||||
throw new Error('Footnote reference has no provided id.');
|
||||
}
|
||||
const footnoteReferenceView = viewWriter.createContainerElement('span', {
|
||||
class: CLASSES.footnoteReference,
|
||||
[ATTRIBUTES.footnoteReference]: '',
|
||||
[ATTRIBUTES.footnoteIndex]: index,
|
||||
[ATTRIBUTES.footnoteId]: id,
|
||||
role: 'doc-noteref',
|
||||
id: `fnref${id}`
|
||||
});
|
||||
const innerText = viewWriter.createText(`[${index}]`);
|
||||
const link = viewWriter.createContainerElement('a', { href: `#fn${id}` });
|
||||
const superscript = viewWriter.createContainerElement('sup');
|
||||
viewWriter.insert(viewWriter.createPositionAt(link, 0), innerText);
|
||||
viewWriter.insert(viewWriter.createPositionAt(superscript, 0), link);
|
||||
viewWriter.insert(viewWriter.createPositionAt(footnoteReferenceView, 0), superscript);
|
||||
return footnoteReferenceView;
|
||||
}
|
||||
/**
|
||||
* Creates and returns a view element for an inline footnote reference. Used for both
|
||||
* data downcast and editing downcast conversions.
|
||||
*/
|
||||
function createFootnoteItemViewElement(modelElement, conversionApi) {
|
||||
const viewWriter = conversionApi.writer;
|
||||
const index = modelElement.getAttribute(ATTRIBUTES.footnoteIndex);
|
||||
const id = modelElement.getAttribute(ATTRIBUTES.footnoteId);
|
||||
if (!index) {
|
||||
throw new Error('Footnote item has no provided index.');
|
||||
}
|
||||
if (!id) {
|
||||
throw new Error('Footnote item has no provided id.');
|
||||
}
|
||||
return viewWriter.createContainerElement('li', {
|
||||
class: CLASSES.footnoteItem,
|
||||
[ATTRIBUTES.footnoteItem]: '',
|
||||
[ATTRIBUTES.footnoteIndex]: `${index}`,
|
||||
[ATTRIBUTES.footnoteId]: `${id}`,
|
||||
role: 'doc-endnote',
|
||||
id: `fn${id}`
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Triggers when the index attribute of a footnote changes, and
|
||||
* updates the editor display of footnote references accordingly.
|
||||
*/
|
||||
function updateFootnoteReferenceView(data, conversionApi, editor) {
|
||||
const { item, attributeNewValue: newIndex } = data;
|
||||
if (!(item instanceof Element) ||
|
||||
!conversionApi.consumable.consume(item, `attribute:${ATTRIBUTES.footnoteIndex}:${ELEMENTS.footnoteReference}`)) {
|
||||
return;
|
||||
}
|
||||
const footnoteReferenceView = conversionApi.mapper.toViewElement(item);
|
||||
if (!footnoteReferenceView) {
|
||||
return;
|
||||
}
|
||||
const viewWriter = conversionApi.writer;
|
||||
const anchor = viewQueryElement(editor, footnoteReferenceView, element => element.name === 'a');
|
||||
const textNode = anchor === null || anchor === void 0 ? void 0 : anchor.getChild(0);
|
||||
if (!textNode || !anchor) {
|
||||
viewWriter.remove(footnoteReferenceView);
|
||||
return;
|
||||
}
|
||||
viewWriter.remove(textNode);
|
||||
const innerText = viewWriter.createText(`[${newIndex}]`);
|
||||
viewWriter.insert(viewWriter.createPositionAt(anchor, 0), innerText);
|
||||
viewWriter.setAttribute('href', `#fn${item.getAttribute(ATTRIBUTES.footnoteId)}`, anchor);
|
||||
viewWriter.setAttribute(ATTRIBUTES.footnoteIndex, newIndex, footnoteReferenceView);
|
||||
}
|
||||
//# sourceMappingURL=converters.js.map
|
||||
File diff suppressed because one or more lines are too long
372
_regroup/ckeditor5-footnotes/src/footnote-editing/converters.ts
Normal file
372
_regroup/ckeditor5-footnotes/src/footnote-editing/converters.ts
Normal file
@@ -0,0 +1,372 @@
|
||||
import { type Editor, } from 'ckeditor5/src/core.js';
|
||||
import { type DowncastConversionApi, type ViewContainerElement, Element } from "ckeditor5/src/engine.js";
|
||||
import { toWidget, toWidgetEditable } from 'ckeditor5/src/widget.js';
|
||||
|
||||
import { ATTRIBUTES, CLASSES, ELEMENTS } from '../constants.js';
|
||||
import { viewQueryElement } from '../utils.js';
|
||||
|
||||
/**
|
||||
* Defines methods for converting between model, data view, and editing view representations of each element type.
|
||||
*/
|
||||
export const defineConverters = ( editor: Editor ): void => {
|
||||
const conversion = editor.conversion;
|
||||
|
||||
/** *********************************Attribute Conversion************************************/
|
||||
|
||||
conversion.for( 'downcast' ).attributeToAttribute( {
|
||||
model: ATTRIBUTES.footnoteId,
|
||||
view: ATTRIBUTES.footnoteId
|
||||
} );
|
||||
|
||||
conversion.for( 'downcast' ).attributeToAttribute( {
|
||||
model: ATTRIBUTES.footnoteIndex,
|
||||
view: ATTRIBUTES.footnoteIndex
|
||||
} );
|
||||
|
||||
/** *********************************Footnote Section Conversion************************************/
|
||||
|
||||
// ((data) view → model)
|
||||
conversion.for( 'upcast' ).elementToElement( {
|
||||
view: {
|
||||
attributes: {
|
||||
[ ATTRIBUTES.footnoteSection ]: true
|
||||
}
|
||||
},
|
||||
model: ELEMENTS.footnoteSection,
|
||||
converterPriority: 'high'
|
||||
} );
|
||||
|
||||
// (model → data view)
|
||||
conversion.for( 'dataDowncast' ).elementToElement( {
|
||||
model: ELEMENTS.footnoteSection,
|
||||
view: {
|
||||
name: 'ol',
|
||||
attributes: {
|
||||
[ ATTRIBUTES.footnoteSection ]: '',
|
||||
role: 'doc-endnotes'
|
||||
},
|
||||
classes: [ CLASSES.footnoteSection, CLASSES.footnotes ]
|
||||
}
|
||||
} );
|
||||
|
||||
// (model → editing view)
|
||||
conversion.for( 'editingDowncast' ).elementToElement( {
|
||||
model: ELEMENTS.footnoteSection,
|
||||
view: ( _, conversionApi ) => {
|
||||
const viewWriter = conversionApi.writer;
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
/** The below is a div rather than an ol because using an ol here caused weird behavior, including randomly duplicating the footnotes section.
|
||||
* This is techincally invalid HTML, but it's valid in the data view (that is, the version shown in the post). I've added role='list'
|
||||
* as a next-best option, in accordance with ARIA recommendations.
|
||||
*/
|
||||
const section = viewWriter.createContainerElement( 'div', {
|
||||
[ ATTRIBUTES.footnoteSection ]: '',
|
||||
role: 'doc-endnotes list',
|
||||
class: CLASSES.footnoteSection
|
||||
} );
|
||||
|
||||
return toWidget( section, viewWriter, { label: 'footnote widget' } );
|
||||
}
|
||||
} );
|
||||
|
||||
/** *********************************Footnote Content Conversion************************************/
|
||||
|
||||
conversion.for( 'upcast' ).elementToElement( {
|
||||
view: {
|
||||
attributes: {
|
||||
[ ATTRIBUTES.footnoteContent ]: true
|
||||
}
|
||||
},
|
||||
model: ( viewElement, conversionApi ) => {
|
||||
const modelWriter = conversionApi.writer;
|
||||
|
||||
return modelWriter.createElement( ELEMENTS.footnoteContent );
|
||||
}
|
||||
} );
|
||||
|
||||
conversion.for( 'dataDowncast' ).elementToElement( {
|
||||
model: ELEMENTS.footnoteContent,
|
||||
view: {
|
||||
name: 'div',
|
||||
attributes: { [ ATTRIBUTES.footnoteContent ]: '' },
|
||||
classes: [ CLASSES.footnoteContent ]
|
||||
}
|
||||
} );
|
||||
|
||||
conversion.for( 'editingDowncast' ).elementToElement( {
|
||||
model: ELEMENTS.footnoteContent,
|
||||
view: ( _, conversionApi ) => {
|
||||
const viewWriter = conversionApi.writer;
|
||||
// Note: You use a more specialized createEditableElement() method here.
|
||||
const section = viewWriter.createEditableElement( 'div', {
|
||||
[ ATTRIBUTES.footnoteContent ]: '',
|
||||
class: CLASSES.footnoteContent
|
||||
} );
|
||||
|
||||
return toWidgetEditable( section, viewWriter );
|
||||
}
|
||||
} );
|
||||
|
||||
/** *********************************Footnote Item Conversion************************************/
|
||||
|
||||
conversion.for( 'upcast' ).elementToElement( {
|
||||
view: {
|
||||
attributes: {
|
||||
[ ATTRIBUTES.footnoteItem ]: true
|
||||
}
|
||||
},
|
||||
model: ( viewElement, conversionApi ) => {
|
||||
const modelWriter = conversionApi.writer;
|
||||
const id = viewElement.getAttribute( ATTRIBUTES.footnoteId );
|
||||
const index = viewElement.getAttribute( ATTRIBUTES.footnoteIndex );
|
||||
if ( id === undefined || index === undefined ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return modelWriter.createElement( ELEMENTS.footnoteItem, {
|
||||
[ ATTRIBUTES.footnoteIndex ]: index,
|
||||
[ ATTRIBUTES.footnoteId ]: id
|
||||
} );
|
||||
},
|
||||
|
||||
/** converterPriority is needed to supersede the builtin upcastListItemStyle
|
||||
* which for unknown reasons causes a null reference error.
|
||||
*/
|
||||
converterPriority: 'high'
|
||||
} );
|
||||
|
||||
conversion.for( 'dataDowncast' ).elementToElement( {
|
||||
model: ELEMENTS.footnoteItem,
|
||||
view: createFootnoteItemViewElement
|
||||
} );
|
||||
|
||||
conversion.for( 'editingDowncast' ).elementToElement( {
|
||||
model: ELEMENTS.footnoteItem,
|
||||
view: createFootnoteItemViewElement
|
||||
} );
|
||||
|
||||
/** *********************************Footnote Reference Conversion************************************/
|
||||
|
||||
conversion.for( 'upcast' ).elementToElement( {
|
||||
view: {
|
||||
attributes: {
|
||||
[ ATTRIBUTES.footnoteReference ]: true
|
||||
}
|
||||
},
|
||||
model: ( viewElement, conversionApi ) => {
|
||||
const modelWriter = conversionApi.writer;
|
||||
const index = viewElement.getAttribute( ATTRIBUTES.footnoteIndex );
|
||||
const id = viewElement.getAttribute( ATTRIBUTES.footnoteId );
|
||||
|
||||
if ( index === undefined || id === undefined ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return modelWriter.createElement( ELEMENTS.footnoteReference, {
|
||||
[ ATTRIBUTES.footnoteIndex ]: index,
|
||||
[ ATTRIBUTES.footnoteId ]: id
|
||||
} );
|
||||
}
|
||||
} );
|
||||
|
||||
conversion.for( 'editingDowncast' ).elementToElement( {
|
||||
model: ELEMENTS.footnoteReference,
|
||||
view: ( modelElement, conversionApi ) => {
|
||||
const viewWriter = conversionApi.writer;
|
||||
const footnoteReferenceViewElement = createFootnoteReferenceViewElement( modelElement, conversionApi );
|
||||
return toWidget( footnoteReferenceViewElement, viewWriter );
|
||||
}
|
||||
} );
|
||||
|
||||
conversion.for( 'dataDowncast' ).elementToElement( {
|
||||
model: ELEMENTS.footnoteReference,
|
||||
view: createFootnoteReferenceViewElement
|
||||
} );
|
||||
|
||||
/** This is an event listener for changes to the `data-footnote-index` attribute on `footnoteReference` elements.
|
||||
* When that event fires, the callback function below updates the displayed view of the footnote reference in the
|
||||
* editor to match the new index.
|
||||
*/
|
||||
conversion.for( 'editingDowncast' ).add( dispatcher => {
|
||||
dispatcher.on(
|
||||
`attribute:${ ATTRIBUTES.footnoteIndex }:${ ELEMENTS.footnoteReference }`,
|
||||
( _, data, conversionApi ) => updateFootnoteReferenceView( data, conversionApi, editor ),
|
||||
{ priority: 'high' }
|
||||
);
|
||||
} );
|
||||
|
||||
/** *********************************Footnote Back Link Conversion************************************/
|
||||
|
||||
conversion.for( 'upcast' ).elementToElement( {
|
||||
view: {
|
||||
attributes: {
|
||||
[ ATTRIBUTES.footnoteBackLink ]: true
|
||||
}
|
||||
},
|
||||
model: ( viewElement, conversionApi ) => {
|
||||
const modelWriter = conversionApi.writer;
|
||||
const id = viewElement.getAttribute( ATTRIBUTES.footnoteId );
|
||||
if ( id === undefined ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return modelWriter.createElement( ELEMENTS.footnoteBackLink, {
|
||||
[ ATTRIBUTES.footnoteId ]: id
|
||||
} );
|
||||
}
|
||||
} );
|
||||
|
||||
conversion.for( 'dataDowncast' ).elementToElement( {
|
||||
model: ELEMENTS.footnoteBackLink,
|
||||
view: createFootnoteBackLinkViewElement
|
||||
} );
|
||||
|
||||
conversion.for( 'editingDowncast' ).elementToElement( {
|
||||
model: ELEMENTS.footnoteBackLink,
|
||||
view: createFootnoteBackLinkViewElement
|
||||
} );
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates and returns a view element for a footnote backlink,
|
||||
* which navigates back to the inline reference in the text. Used
|
||||
* for both data and editing downcasts.
|
||||
*/
|
||||
function createFootnoteBackLinkViewElement(
|
||||
modelElement: Element,
|
||||
conversionApi: DowncastConversionApi
|
||||
): ViewContainerElement {
|
||||
const viewWriter = conversionApi.writer;
|
||||
const id = `${ modelElement.getAttribute( ATTRIBUTES.footnoteId ) }`;
|
||||
if ( id === undefined ) {
|
||||
throw new Error( 'Footnote return link has no provided Id.' );
|
||||
}
|
||||
|
||||
const footnoteBackLinkView = viewWriter.createContainerElement( 'span', {
|
||||
class: CLASSES.footnoteBackLink,
|
||||
[ ATTRIBUTES.footnoteBackLink ]: '',
|
||||
[ ATTRIBUTES.footnoteId ]: id
|
||||
} );
|
||||
const sup = viewWriter.createContainerElement( 'sup' );
|
||||
const strong = viewWriter.createContainerElement( 'strong' );
|
||||
const anchor = viewWriter.createContainerElement( 'a', { href: `#fnref${ id }` } );
|
||||
const innerText = viewWriter.createText( '^' );
|
||||
|
||||
viewWriter.insert( viewWriter.createPositionAt( anchor, 0 ), innerText );
|
||||
viewWriter.insert( viewWriter.createPositionAt( strong, 0 ), anchor );
|
||||
viewWriter.insert( viewWriter.createPositionAt( sup, 0 ), strong );
|
||||
viewWriter.insert( viewWriter.createPositionAt( footnoteBackLinkView, 0 ), sup );
|
||||
|
||||
return footnoteBackLinkView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a view element for an inline footnote reference. Used for both
|
||||
* data downcast and editing downcast conversions.
|
||||
*/
|
||||
function createFootnoteReferenceViewElement(
|
||||
modelElement: Element,
|
||||
conversionApi: DowncastConversionApi
|
||||
): ViewContainerElement {
|
||||
const viewWriter = conversionApi.writer;
|
||||
const index = `${ modelElement.getAttribute( ATTRIBUTES.footnoteIndex ) }`;
|
||||
const id = `${ modelElement.getAttribute( ATTRIBUTES.footnoteId ) }`;
|
||||
if ( index === 'undefined' ) {
|
||||
throw new Error( 'Footnote reference has no provided index.' );
|
||||
}
|
||||
if ( id === 'undefined' ) {
|
||||
throw new Error( 'Footnote reference has no provided id.' );
|
||||
}
|
||||
|
||||
const footnoteReferenceView = viewWriter.createContainerElement( 'span', {
|
||||
class: CLASSES.footnoteReference,
|
||||
[ ATTRIBUTES.footnoteReference ]: '',
|
||||
[ ATTRIBUTES.footnoteIndex ]: index,
|
||||
[ ATTRIBUTES.footnoteId ]: id,
|
||||
role: 'doc-noteref',
|
||||
id: `fnref${ id }`
|
||||
} );
|
||||
|
||||
const innerText = viewWriter.createText( `[${ index }]` );
|
||||
const link = viewWriter.createContainerElement( 'a', { href: `#fn${ id }` } );
|
||||
const superscript = viewWriter.createContainerElement( 'sup' );
|
||||
viewWriter.insert( viewWriter.createPositionAt( link, 0 ), innerText );
|
||||
viewWriter.insert( viewWriter.createPositionAt( superscript, 0 ), link );
|
||||
viewWriter.insert( viewWriter.createPositionAt( footnoteReferenceView, 0 ), superscript );
|
||||
|
||||
return footnoteReferenceView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a view element for an inline footnote reference. Used for both
|
||||
* data downcast and editing downcast conversions.
|
||||
*/
|
||||
function createFootnoteItemViewElement(
|
||||
modelElement: Element,
|
||||
conversionApi: DowncastConversionApi
|
||||
): ViewContainerElement {
|
||||
const viewWriter = conversionApi.writer;
|
||||
const index = modelElement.getAttribute( ATTRIBUTES.footnoteIndex );
|
||||
const id = modelElement.getAttribute( ATTRIBUTES.footnoteId );
|
||||
if ( !index ) {
|
||||
throw new Error( 'Footnote item has no provided index.' );
|
||||
}
|
||||
if ( !id ) {
|
||||
throw new Error( 'Footnote item has no provided id.' );
|
||||
}
|
||||
|
||||
return viewWriter.createContainerElement( 'li', {
|
||||
class: CLASSES.footnoteItem,
|
||||
[ ATTRIBUTES.footnoteItem ]: '',
|
||||
[ ATTRIBUTES.footnoteIndex ]: `${ index }`,
|
||||
[ ATTRIBUTES.footnoteId ]: `${ id }`,
|
||||
role: 'doc-endnote',
|
||||
id: `fn${ id }`
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers when the index attribute of a footnote changes, and
|
||||
* updates the editor display of footnote references accordingly.
|
||||
*/
|
||||
function updateFootnoteReferenceView(
|
||||
data: {
|
||||
item: Element;
|
||||
attributeOldValue: string;
|
||||
attributeNewValue: string;
|
||||
},
|
||||
conversionApi: DowncastConversionApi,
|
||||
editor: Editor
|
||||
) {
|
||||
const { item, attributeNewValue: newIndex } = data;
|
||||
if (
|
||||
!( item instanceof Element ) ||
|
||||
!conversionApi.consumable.consume( item, `attribute:${ ATTRIBUTES.footnoteIndex }:${ ELEMENTS.footnoteReference }` )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const footnoteReferenceView = conversionApi.mapper.toViewElement( item );
|
||||
|
||||
if ( !footnoteReferenceView ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewWriter = conversionApi.writer;
|
||||
|
||||
const anchor = viewQueryElement( editor, footnoteReferenceView, element => element.name === 'a' );
|
||||
const textNode = anchor?.getChild( 0 );
|
||||
|
||||
if ( !textNode || !anchor ) {
|
||||
viewWriter.remove( footnoteReferenceView );
|
||||
return;
|
||||
}
|
||||
|
||||
viewWriter.remove( textNode );
|
||||
const innerText = viewWriter.createText( `[${ newIndex }]` );
|
||||
viewWriter.insert( viewWriter.createPositionAt( anchor, 0 ), innerText );
|
||||
|
||||
viewWriter.setAttribute( 'href', `#fn${ item.getAttribute( ATTRIBUTES.footnoteId ) }`, anchor );
|
||||
viewWriter.setAttribute( ATTRIBUTES.footnoteIndex, newIndex, footnoteReferenceView );
|
||||
}
|
||||
59
_regroup/ckeditor5-footnotes/src/footnote-editing/footnote-editing.d.ts
vendored
Normal file
59
_regroup/ckeditor5-footnotes/src/footnote-editing/footnote-editing.d.ts
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* CKEditor dataview nodes can be converted to a output view or an editor view via downcasting
|
||||
* * Upcasting is converting to the platonic ckeditor version.
|
||||
* * Downcasting is converting to the output version.
|
||||
*/
|
||||
import { type RootElement } from 'ckeditor5/src/engine.js';
|
||||
import { Autoformat } from "@ckeditor/ckeditor5-autoformat";
|
||||
import { Plugin } from "ckeditor5/src/core.js";
|
||||
import { Widget } from 'ckeditor5/src/widget.js';
|
||||
import '../footnote.css';
|
||||
export default class FootnoteEditing extends Plugin {
|
||||
static get requires(): readonly [typeof Widget, typeof Autoformat];
|
||||
/**
|
||||
* The root element of the document.
|
||||
*/
|
||||
get rootElement(): RootElement;
|
||||
init(): void;
|
||||
/**
|
||||
* This method broadly deals with deletion of text and elements, and updating the model
|
||||
* accordingly. In particular, the following cases are handled:
|
||||
* 1. If the footnote section gets deleted, all footnote references are removed.
|
||||
* 2. If a delete operation happens in an empty footnote, the footnote is deleted.
|
||||
*/
|
||||
private _handleDelete;
|
||||
/**
|
||||
* Clear the children of the provided footnoteContent element,
|
||||
* leaving an empty paragraph behind. This allows users to empty
|
||||
* a footnote without deleting it. modelWriter is passed in to
|
||||
* batch these changes with the ones that instantiated them,
|
||||
* such that the set can be undone with a single action.
|
||||
*/
|
||||
private _clearContents;
|
||||
/**
|
||||
* Removes a footnote and its references, and renumbers subsequent footnotes. When a footnote's
|
||||
* id attribute changes, it's references automatically update from a dispatcher event in converters.js,
|
||||
* which triggers the `updateReferenceIds` method. modelWriter is passed in to batch these changes with
|
||||
* the ones that instantiated them, such that the set can be undone with a single action.
|
||||
*/
|
||||
private _removeFootnote;
|
||||
/**
|
||||
* Deletes all references to the footnote with the given id. If no id is provided,
|
||||
* all references are deleted. modelWriter is passed in to batch these changes with
|
||||
* the ones that instantiated them, such that the set can be undone with a single action.
|
||||
*/
|
||||
private _removeReferences;
|
||||
/**
|
||||
* Updates all references for a single footnote. This function is called when
|
||||
* the index attribute of an existing footnote changes, which happens when a footnote
|
||||
* with a lower index is deleted. batch is passed in to group these changes with
|
||||
* the ones that instantiated them.
|
||||
*/
|
||||
private _updateReferenceIndices;
|
||||
/**
|
||||
* Reindexes footnotes such that footnote references occur in order, and reorders
|
||||
* footnote items in the footer section accordingly. batch is passed in to group changes with
|
||||
* the ones that instantiated them.
|
||||
*/
|
||||
private _orderFootnotes;
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
/**
|
||||
* CKEditor dataview nodes can be converted to a output view or an editor view via downcasting
|
||||
* * Upcasting is converting to the platonic ckeditor version.
|
||||
* * Downcasting is converting to the output version.
|
||||
*/
|
||||
import { Element } from 'ckeditor5/src/engine.js';
|
||||
import { Autoformat } from "@ckeditor/ckeditor5-autoformat";
|
||||
import { Plugin } from "ckeditor5/src/core.js";
|
||||
import { Widget } from 'ckeditor5/src/widget.js';
|
||||
import { viewToModelPositionOutsideModelElement } from '@ckeditor/ckeditor5-widget';
|
||||
import '../footnote.css';
|
||||
import { addFootnoteAutoformatting } from './auto-formatting.js';
|
||||
import { defineConverters } from './converters.js';
|
||||
import { defineSchema } from './schema.js';
|
||||
import { ATTRIBUTES, COMMANDS, ELEMENTS } from '../constants.js';
|
||||
import InsertFootnoteCommand from '../insert-footnote-command.js';
|
||||
import { modelQueryElement, modelQueryElementsAll } from '../utils.js';
|
||||
export default class FootnoteEditing extends Plugin {
|
||||
static get requires() {
|
||||
return [Widget, Autoformat];
|
||||
}
|
||||
/**
|
||||
* The root element of the document.
|
||||
*/
|
||||
get rootElement() {
|
||||
const rootElement = this.editor.model.document.getRoot();
|
||||
if (!rootElement) {
|
||||
throw new Error('Document has no rootElement element.');
|
||||
}
|
||||
return rootElement;
|
||||
}
|
||||
init() {
|
||||
defineSchema(this.editor.model.schema);
|
||||
defineConverters(this.editor);
|
||||
this.editor.commands.add(COMMANDS.insertFootnote, new InsertFootnoteCommand(this.editor));
|
||||
addFootnoteAutoformatting(this.editor, this.rootElement);
|
||||
this.editor.model.document.on('change:data', (eventInfo, batch) => {
|
||||
const eventSource = eventInfo.source;
|
||||
const diffItems = [...eventSource.differ.getChanges()];
|
||||
// If a footnote reference is inserted, ensure that footnote references remain ordered.
|
||||
if (diffItems.some(diffItem => diffItem.type === 'insert' && diffItem.name === ELEMENTS.footnoteReference)) {
|
||||
this._orderFootnotes(batch);
|
||||
}
|
||||
// for each change to a footnote item's index attribute, update the corresponding references accordingly
|
||||
diffItems.forEach(diffItem => {
|
||||
if (diffItem.type === 'attribute' && diffItem.attributeKey === ATTRIBUTES.footnoteIndex) {
|
||||
const { attributeNewValue: newFootnoteIndex } = diffItem;
|
||||
const footnote = [...diffItem.range.getItems()].find(item => item.is('element', ELEMENTS.footnoteItem));
|
||||
const footnoteId = footnote instanceof Element && footnote.getAttribute(ATTRIBUTES.footnoteId);
|
||||
if (!footnoteId) {
|
||||
return;
|
||||
}
|
||||
this._updateReferenceIndices(batch, `${footnoteId}`, newFootnoteIndex);
|
||||
}
|
||||
});
|
||||
}, { priority: 'high' });
|
||||
this._handleDelete();
|
||||
// The following callbacks are needed to map nonempty view elements
|
||||
// to empty model elements.
|
||||
// See https://ckeditor.com/docs/ckeditor5/latest/api/module_widget_utils.html#function-viewToModelPositionOutsideModelElement
|
||||
this.editor.editing.mapper.on('viewToModelPosition', viewToModelPositionOutsideModelElement(this.editor.model, viewElement => viewElement.hasAttribute(ATTRIBUTES.footnoteReference)));
|
||||
}
|
||||
/**
|
||||
* This method broadly deals with deletion of text and elements, and updating the model
|
||||
* accordingly. In particular, the following cases are handled:
|
||||
* 1. If the footnote section gets deleted, all footnote references are removed.
|
||||
* 2. If a delete operation happens in an empty footnote, the footnote is deleted.
|
||||
*/
|
||||
_handleDelete() {
|
||||
const viewDocument = this.editor.editing.view.document;
|
||||
const editor = this.editor;
|
||||
this.listenTo(viewDocument, 'delete', (evt, data) => {
|
||||
const doc = editor.model.document;
|
||||
const deletedElement = doc.selection.getSelectedElement();
|
||||
const selectionEndPos = doc.selection.getLastPosition();
|
||||
const selectionStartPos = doc.selection.getFirstPosition();
|
||||
if (!selectionEndPos || !selectionStartPos) {
|
||||
throw new Error('Selection must have at least one range to perform delete operation.');
|
||||
}
|
||||
this.editor.model.change(modelWriter => {
|
||||
// delete all footnote references if footnote section gets deleted
|
||||
if (deletedElement && deletedElement.is('element', ELEMENTS.footnoteSection)) {
|
||||
this._removeReferences(modelWriter);
|
||||
}
|
||||
const deletingFootnote = deletedElement && deletedElement.is('element', ELEMENTS.footnoteItem);
|
||||
const currentFootnote = deletingFootnote ?
|
||||
deletedElement :
|
||||
selectionEndPos.findAncestor(ELEMENTS.footnoteItem);
|
||||
if (!currentFootnote) {
|
||||
return;
|
||||
}
|
||||
const endParagraph = selectionEndPos.findAncestor('paragraph');
|
||||
const startParagraph = selectionStartPos.findAncestor('paragraph');
|
||||
const currentFootnoteContent = selectionEndPos.findAncestor(ELEMENTS.footnoteContent);
|
||||
if (!currentFootnoteContent || !startParagraph || !endParagraph) {
|
||||
return;
|
||||
}
|
||||
const footnoteIsEmpty = startParagraph.maxOffset === 0 && currentFootnoteContent.childCount === 1;
|
||||
if (deletingFootnote || footnoteIsEmpty) {
|
||||
this._removeFootnote(modelWriter, currentFootnote);
|
||||
data.preventDefault();
|
||||
evt.stop();
|
||||
}
|
||||
});
|
||||
}, { priority: 'high' });
|
||||
}
|
||||
/**
|
||||
* Clear the children of the provided footnoteContent element,
|
||||
* leaving an empty paragraph behind. This allows users to empty
|
||||
* a footnote without deleting it. modelWriter is passed in to
|
||||
* batch these changes with the ones that instantiated them,
|
||||
* such that the set can be undone with a single action.
|
||||
*/
|
||||
_clearContents(modelWriter, footnoteContent) {
|
||||
const contents = modelWriter.createRangeIn(footnoteContent);
|
||||
modelWriter.appendElement('paragraph', footnoteContent);
|
||||
modelWriter.remove(contents);
|
||||
}
|
||||
/**
|
||||
* Removes a footnote and its references, and renumbers subsequent footnotes. When a footnote's
|
||||
* id attribute changes, it's references automatically update from a dispatcher event in converters.js,
|
||||
* which triggers the `updateReferenceIds` method. modelWriter is passed in to batch these changes with
|
||||
* the ones that instantiated them, such that the set can be undone with a single action.
|
||||
*/
|
||||
_removeFootnote(modelWriter, footnote) {
|
||||
// delete the current footnote and its references,
|
||||
// and renumber subsequent footnotes.
|
||||
if (!this.editor) {
|
||||
return;
|
||||
}
|
||||
const footnoteSection = footnote.findAncestor(ELEMENTS.footnoteSection);
|
||||
if (!footnoteSection) {
|
||||
modelWriter.remove(footnote);
|
||||
return;
|
||||
}
|
||||
const index = footnoteSection.getChildIndex(footnote);
|
||||
const id = footnote.getAttribute(ATTRIBUTES.footnoteId);
|
||||
this._removeReferences(modelWriter, `${id}`);
|
||||
modelWriter.remove(footnote);
|
||||
// if no footnotes remain, remove the footnote section
|
||||
if (footnoteSection.childCount === 0) {
|
||||
modelWriter.remove(footnoteSection);
|
||||
this._removeReferences(modelWriter);
|
||||
}
|
||||
else {
|
||||
if (index == null) {
|
||||
throw new Error('Index is nullish');
|
||||
}
|
||||
// after footnote deletion the selection winds up surrounding the previous footnote
|
||||
// (or the following footnote if no previous footnote exists). Typing in that state
|
||||
// immediately deletes the footnote. This deliberately sets the new selection position
|
||||
// to avoid that.
|
||||
const neighborFootnote = index === 0 ? footnoteSection.getChild(index) : footnoteSection.getChild((index !== null && index !== void 0 ? index : 0) - 1);
|
||||
if (!(neighborFootnote instanceof Element)) {
|
||||
return;
|
||||
}
|
||||
const neighborEndParagraph = modelQueryElementsAll(this.editor, neighborFootnote, element => element.is('element', 'paragraph')).pop();
|
||||
if (neighborEndParagraph) {
|
||||
modelWriter.setSelection(neighborEndParagraph, 'end');
|
||||
}
|
||||
}
|
||||
if (index == null) {
|
||||
throw new Error('Index is nullish');
|
||||
}
|
||||
// renumber subsequent footnotes
|
||||
const subsequentFootnotes = [...footnoteSection.getChildren()].slice(index !== null && index !== void 0 ? index : 0);
|
||||
for (const [i, child] of subsequentFootnotes.entries()) {
|
||||
modelWriter.setAttribute(ATTRIBUTES.footnoteIndex, `${index !== null && index !== void 0 ? index : 0 + i + 1}`, child);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Deletes all references to the footnote with the given id. If no id is provided,
|
||||
* all references are deleted. modelWriter is passed in to batch these changes with
|
||||
* the ones that instantiated them, such that the set can be undone with a single action.
|
||||
*/
|
||||
_removeReferences(modelWriter, footnoteId = undefined) {
|
||||
const removeList = [];
|
||||
if (!this.rootElement) {
|
||||
throw new Error('Document has no root element.');
|
||||
}
|
||||
const footnoteReferences = modelQueryElementsAll(this.editor, this.rootElement, e => e.is('element', ELEMENTS.footnoteReference));
|
||||
footnoteReferences.forEach(footnoteReference => {
|
||||
const id = footnoteReference.getAttribute(ATTRIBUTES.footnoteId);
|
||||
if (!footnoteId || id === footnoteId) {
|
||||
removeList.push(footnoteReference);
|
||||
}
|
||||
});
|
||||
for (const item of removeList) {
|
||||
modelWriter.remove(item);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Updates all references for a single footnote. This function is called when
|
||||
* the index attribute of an existing footnote changes, which happens when a footnote
|
||||
* with a lower index is deleted. batch is passed in to group these changes with
|
||||
* the ones that instantiated them.
|
||||
*/
|
||||
_updateReferenceIndices(batch, footnoteId, newFootnoteIndex) {
|
||||
const footnoteReferences = modelQueryElementsAll(this.editor, this.rootElement, e => e.is('element', ELEMENTS.footnoteReference) && e.getAttribute(ATTRIBUTES.footnoteId) === footnoteId);
|
||||
this.editor.model.enqueueChange(batch, writer => {
|
||||
footnoteReferences.forEach(footnoteReference => {
|
||||
writer.setAttribute(ATTRIBUTES.footnoteIndex, newFootnoteIndex, footnoteReference);
|
||||
});
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Reindexes footnotes such that footnote references occur in order, and reorders
|
||||
* footnote items in the footer section accordingly. batch is passed in to group changes with
|
||||
* the ones that instantiated them.
|
||||
*/
|
||||
_orderFootnotes(batch) {
|
||||
const footnoteReferences = modelQueryElementsAll(this.editor, this.rootElement, e => e.is('element', ELEMENTS.footnoteReference));
|
||||
const uniqueIds = new Set(footnoteReferences.map(e => e.getAttribute(ATTRIBUTES.footnoteId)));
|
||||
const orderedFootnotes = [...uniqueIds].map(id => modelQueryElement(this.editor, this.rootElement, e => e.is('element', ELEMENTS.footnoteItem) && e.getAttribute(ATTRIBUTES.footnoteId) === id));
|
||||
this.editor.model.enqueueChange(batch, writer => {
|
||||
var _a;
|
||||
const footnoteSection = modelQueryElement(this.editor, this.rootElement, e => e.is('element', ELEMENTS.footnoteSection));
|
||||
if (!footnoteSection) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* In order to keep footnotes with no existing references at the end of the list,
|
||||
* the loop below reverses the list of footnotes with references and inserts them
|
||||
* each at the beginning.
|
||||
*/
|
||||
for (const footnote of orderedFootnotes.reverse()) {
|
||||
if (footnote) {
|
||||
writer.move(writer.createRangeOn(footnote), footnoteSection, 0);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* once the list is sorted, make one final pass to update footnote indices.
|
||||
*/
|
||||
for (const footnote of modelQueryElementsAll(this.editor, footnoteSection, e => e.is('element', ELEMENTS.footnoteItem))) {
|
||||
const index = `${((_a = footnoteSection === null || footnoteSection === void 0 ? void 0 : footnoteSection.getChildIndex(footnote)) !== null && _a !== void 0 ? _a : -1) + 1}`;
|
||||
if (footnote) {
|
||||
writer.setAttribute(ATTRIBUTES.footnoteIndex, index, footnote);
|
||||
}
|
||||
const id = footnote.getAttribute(ATTRIBUTES.footnoteId);
|
||||
// /**
|
||||
// * unfortunately the following line seems to be necessary, even though updateReferenceIndices
|
||||
// * should fire from the attribute change immediately above. It seems that events initiated by
|
||||
// * a `change:data` event do not themselves fire another `change:data` event.
|
||||
// */
|
||||
if (id) {
|
||||
this._updateReferenceIndices(batch, `${id}`, `${index}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=footnote-editing.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,323 @@
|
||||
/**
|
||||
* CKEditor dataview nodes can be converted to a output view or an editor view via downcasting
|
||||
* * Upcasting is converting to the platonic ckeditor version.
|
||||
* * Downcasting is converting to the output version.
|
||||
*/
|
||||
|
||||
import {
|
||||
Element,
|
||||
type Batch,
|
||||
type RootElement,
|
||||
type Writer
|
||||
} from 'ckeditor5/src/engine.js';
|
||||
|
||||
import { Autoformat } from "@ckeditor/ckeditor5-autoformat";
|
||||
import { Plugin } from "ckeditor5/src/core.js";
|
||||
import { Widget } from 'ckeditor5/src/widget.js';
|
||||
import { viewToModelPositionOutsideModelElement } from '@ckeditor/ckeditor5-widget';
|
||||
|
||||
import { type ViewElement } from "ckeditor5/src/engine.js";
|
||||
|
||||
import '../footnote.css';
|
||||
|
||||
import { addFootnoteAutoformatting } from './auto-formatting.js';
|
||||
import { defineConverters } from './converters.js';
|
||||
import { defineSchema } from './schema.js';
|
||||
|
||||
import { ATTRIBUTES, COMMANDS, ELEMENTS } from '../constants.js';
|
||||
import InsertFootnoteCommand from '../insert-footnote-command.js';
|
||||
import { modelQueryElement, modelQueryElementsAll } from '../utils.js';
|
||||
|
||||
export default class FootnoteEditing extends Plugin {
|
||||
public static get requires() {
|
||||
return [ Widget, Autoformat ] as const;
|
||||
}
|
||||
|
||||
/**
|
||||
* The root element of the document.
|
||||
*/
|
||||
public get rootElement(): RootElement {
|
||||
const rootElement = this.editor.model.document.getRoot();
|
||||
if ( !rootElement ) {
|
||||
throw new Error( 'Document has no rootElement element.' );
|
||||
}
|
||||
return rootElement;
|
||||
}
|
||||
|
||||
public init(): void {
|
||||
defineSchema( this.editor.model.schema );
|
||||
defineConverters( this.editor );
|
||||
|
||||
this.editor.commands.add( COMMANDS.insertFootnote, new InsertFootnoteCommand( this.editor ) );
|
||||
|
||||
addFootnoteAutoformatting( this.editor, this.rootElement );
|
||||
|
||||
this.editor.model.document.on(
|
||||
'change:data',
|
||||
( eventInfo, batch ) => {
|
||||
const eventSource: any = eventInfo.source;
|
||||
const diffItems = [ ...eventSource.differ.getChanges() ];
|
||||
// If a footnote reference is inserted, ensure that footnote references remain ordered.
|
||||
if ( diffItems.some( diffItem => diffItem.type === 'insert' && diffItem.name === ELEMENTS.footnoteReference ) ) {
|
||||
this._orderFootnotes( batch );
|
||||
}
|
||||
// for each change to a footnote item's index attribute, update the corresponding references accordingly
|
||||
diffItems.forEach( diffItem => {
|
||||
if ( diffItem.type === 'attribute' && diffItem.attributeKey === ATTRIBUTES.footnoteIndex ) {
|
||||
const { attributeNewValue: newFootnoteIndex } = diffItem;
|
||||
const footnote = [ ...diffItem.range.getItems() ].find( item => item.is( 'element', ELEMENTS.footnoteItem ) );
|
||||
const footnoteId = footnote instanceof Element && footnote.getAttribute( ATTRIBUTES.footnoteId );
|
||||
if ( !footnoteId ) {
|
||||
return;
|
||||
}
|
||||
this._updateReferenceIndices( batch, `${ footnoteId }`, newFootnoteIndex );
|
||||
}
|
||||
} );
|
||||
},
|
||||
{ priority: 'high' }
|
||||
);
|
||||
|
||||
this._handleDelete();
|
||||
|
||||
// The following callbacks are needed to map nonempty view elements
|
||||
// to empty model elements.
|
||||
// See https://ckeditor.com/docs/ckeditor5/latest/api/module_widget_utils.html#function-viewToModelPositionOutsideModelElement
|
||||
this.editor.editing.mapper.on(
|
||||
'viewToModelPosition',
|
||||
viewToModelPositionOutsideModelElement( this.editor.model, viewElement =>
|
||||
viewElement.hasAttribute( ATTRIBUTES.footnoteReference )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method broadly deals with deletion of text and elements, and updating the model
|
||||
* accordingly. In particular, the following cases are handled:
|
||||
* 1. If the footnote section gets deleted, all footnote references are removed.
|
||||
* 2. If a delete operation happens in an empty footnote, the footnote is deleted.
|
||||
*/
|
||||
private _handleDelete() {
|
||||
const viewDocument = this.editor.editing.view.document;
|
||||
const editor = this.editor;
|
||||
this.listenTo(
|
||||
viewDocument,
|
||||
'delete',
|
||||
( evt, data ) => {
|
||||
const doc = editor.model.document;
|
||||
const deletedElement = doc.selection.getSelectedElement();
|
||||
const selectionEndPos = doc.selection.getLastPosition();
|
||||
const selectionStartPos = doc.selection.getFirstPosition();
|
||||
if ( !selectionEndPos || !selectionStartPos ) {
|
||||
throw new Error( 'Selection must have at least one range to perform delete operation.' );
|
||||
}
|
||||
|
||||
this.editor.model.change( modelWriter => {
|
||||
// delete all footnote references if footnote section gets deleted
|
||||
if ( deletedElement && deletedElement.is( 'element', ELEMENTS.footnoteSection ) ) {
|
||||
this._removeReferences( modelWriter );
|
||||
}
|
||||
|
||||
const deletingFootnote = deletedElement && deletedElement.is( 'element', ELEMENTS.footnoteItem );
|
||||
|
||||
const currentFootnote = deletingFootnote ?
|
||||
deletedElement :
|
||||
selectionEndPos.findAncestor( ELEMENTS.footnoteItem );
|
||||
if ( !currentFootnote ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const endParagraph = selectionEndPos.findAncestor( 'paragraph' );
|
||||
const startParagraph = selectionStartPos.findAncestor( 'paragraph' );
|
||||
const currentFootnoteContent = selectionEndPos.findAncestor( ELEMENTS.footnoteContent );
|
||||
if ( !currentFootnoteContent || !startParagraph || !endParagraph ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const footnoteIsEmpty = startParagraph.maxOffset === 0 && currentFootnoteContent.childCount === 1;
|
||||
|
||||
if ( deletingFootnote || footnoteIsEmpty ) {
|
||||
this._removeFootnote( modelWriter, currentFootnote );
|
||||
data.preventDefault();
|
||||
evt.stop();
|
||||
}
|
||||
} );
|
||||
},
|
||||
{ priority: 'high' }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the children of the provided footnoteContent element,
|
||||
* leaving an empty paragraph behind. This allows users to empty
|
||||
* a footnote without deleting it. modelWriter is passed in to
|
||||
* batch these changes with the ones that instantiated them,
|
||||
* such that the set can be undone with a single action.
|
||||
*/
|
||||
private _clearContents( modelWriter: Writer, footnoteContent: Element ) {
|
||||
const contents = modelWriter.createRangeIn( footnoteContent );
|
||||
modelWriter.appendElement( 'paragraph', footnoteContent );
|
||||
modelWriter.remove( contents );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a footnote and its references, and renumbers subsequent footnotes. When a footnote's
|
||||
* id attribute changes, it's references automatically update from a dispatcher event in converters.js,
|
||||
* which triggers the `updateReferenceIds` method. modelWriter is passed in to batch these changes with
|
||||
* the ones that instantiated them, such that the set can be undone with a single action.
|
||||
*/
|
||||
private _removeFootnote( modelWriter: Writer, footnote: Element ) {
|
||||
// delete the current footnote and its references,
|
||||
// and renumber subsequent footnotes.
|
||||
if ( !this.editor ) {
|
||||
return;
|
||||
}
|
||||
const footnoteSection = footnote.findAncestor( ELEMENTS.footnoteSection );
|
||||
|
||||
if ( !footnoteSection ) {
|
||||
modelWriter.remove( footnote );
|
||||
return;
|
||||
}
|
||||
const index = footnoteSection.getChildIndex( footnote );
|
||||
const id = footnote.getAttribute( ATTRIBUTES.footnoteId );
|
||||
this._removeReferences( modelWriter, `${ id }` );
|
||||
|
||||
modelWriter.remove( footnote );
|
||||
// if no footnotes remain, remove the footnote section
|
||||
if ( footnoteSection.childCount === 0 ) {
|
||||
modelWriter.remove( footnoteSection );
|
||||
this._removeReferences( modelWriter );
|
||||
} else {
|
||||
if ( index == null ) {
|
||||
throw new Error( 'Index is nullish' );
|
||||
}
|
||||
// after footnote deletion the selection winds up surrounding the previous footnote
|
||||
// (or the following footnote if no previous footnote exists). Typing in that state
|
||||
// immediately deletes the footnote. This deliberately sets the new selection position
|
||||
// to avoid that.
|
||||
const neighborFootnote = index === 0 ? footnoteSection.getChild( index ) : footnoteSection.getChild( ( index ?? 0 ) - 1 );
|
||||
if ( !( neighborFootnote instanceof Element ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const neighborEndParagraph = modelQueryElementsAll( this.editor, neighborFootnote, element =>
|
||||
element.is( 'element', 'paragraph' )
|
||||
).pop();
|
||||
|
||||
if ( neighborEndParagraph ) {
|
||||
modelWriter.setSelection( neighborEndParagraph, 'end' );
|
||||
}
|
||||
}
|
||||
if ( index == null ) {
|
||||
throw new Error( 'Index is nullish' );
|
||||
}
|
||||
// renumber subsequent footnotes
|
||||
const subsequentFootnotes = [ ...footnoteSection.getChildren() ].slice( index ?? 0 );
|
||||
for ( const [ i, child ] of subsequentFootnotes.entries() ) {
|
||||
modelWriter.setAttribute( ATTRIBUTES.footnoteIndex, `${ index ?? 0 + i + 1 }`, child );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all references to the footnote with the given id. If no id is provided,
|
||||
* all references are deleted. modelWriter is passed in to batch these changes with
|
||||
* the ones that instantiated them, such that the set can be undone with a single action.
|
||||
*/
|
||||
private _removeReferences( modelWriter: Writer, footnoteId: string | undefined = undefined ) {
|
||||
const removeList: Array<any> = [];
|
||||
if ( !this.rootElement ) {
|
||||
throw new Error( 'Document has no root element.' );
|
||||
}
|
||||
const footnoteReferences = modelQueryElementsAll( this.editor, this.rootElement, e =>
|
||||
e.is( 'element', ELEMENTS.footnoteReference )
|
||||
);
|
||||
footnoteReferences.forEach( footnoteReference => {
|
||||
const id = footnoteReference.getAttribute( ATTRIBUTES.footnoteId );
|
||||
if ( !footnoteId || id === footnoteId ) {
|
||||
removeList.push( footnoteReference );
|
||||
}
|
||||
} );
|
||||
for ( const item of removeList ) {
|
||||
modelWriter.remove( item );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all references for a single footnote. This function is called when
|
||||
* the index attribute of an existing footnote changes, which happens when a footnote
|
||||
* with a lower index is deleted. batch is passed in to group these changes with
|
||||
* the ones that instantiated them.
|
||||
*/
|
||||
private _updateReferenceIndices( batch: Batch, footnoteId: string, newFootnoteIndex: string ) {
|
||||
const footnoteReferences = modelQueryElementsAll(
|
||||
this.editor,
|
||||
this.rootElement,
|
||||
e => e.is( 'element', ELEMENTS.footnoteReference ) && e.getAttribute( ATTRIBUTES.footnoteId ) === footnoteId
|
||||
);
|
||||
this.editor.model.enqueueChange( batch, writer => {
|
||||
footnoteReferences.forEach( footnoteReference => {
|
||||
writer.setAttribute( ATTRIBUTES.footnoteIndex, newFootnoteIndex, footnoteReference );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Reindexes footnotes such that footnote references occur in order, and reorders
|
||||
* footnote items in the footer section accordingly. batch is passed in to group changes with
|
||||
* the ones that instantiated them.
|
||||
*/
|
||||
private _orderFootnotes( batch: Batch ) {
|
||||
const footnoteReferences = modelQueryElementsAll( this.editor, this.rootElement, e =>
|
||||
e.is( 'element', ELEMENTS.footnoteReference )
|
||||
);
|
||||
const uniqueIds = new Set( footnoteReferences.map( e => e.getAttribute( ATTRIBUTES.footnoteId ) ) );
|
||||
const orderedFootnotes = [ ...uniqueIds ].map( id =>
|
||||
modelQueryElement(
|
||||
this.editor,
|
||||
this.rootElement,
|
||||
e => e.is( 'element', ELEMENTS.footnoteItem ) && e.getAttribute( ATTRIBUTES.footnoteId ) === id
|
||||
)
|
||||
);
|
||||
|
||||
this.editor.model.enqueueChange( batch, writer => {
|
||||
const footnoteSection = modelQueryElement( this.editor, this.rootElement, e =>
|
||||
e.is( 'element', ELEMENTS.footnoteSection )
|
||||
);
|
||||
if ( !footnoteSection ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* In order to keep footnotes with no existing references at the end of the list,
|
||||
* the loop below reverses the list of footnotes with references and inserts them
|
||||
* each at the beginning.
|
||||
*/
|
||||
for ( const footnote of orderedFootnotes.reverse() ) {
|
||||
if ( footnote ) {
|
||||
writer.move( writer.createRangeOn( footnote ), footnoteSection, 0 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* once the list is sorted, make one final pass to update footnote indices.
|
||||
*/
|
||||
for ( const footnote of modelQueryElementsAll( this.editor, footnoteSection, e =>
|
||||
e.is( 'element', ELEMENTS.footnoteItem )
|
||||
) ) {
|
||||
const index = `${ ( footnoteSection?.getChildIndex( footnote ) ?? -1 ) + 1 }`;
|
||||
if ( footnote ) {
|
||||
writer.setAttribute( ATTRIBUTES.footnoteIndex, index, footnote );
|
||||
}
|
||||
const id = footnote.getAttribute( ATTRIBUTES.footnoteId );
|
||||
|
||||
// /**
|
||||
// * unfortunately the following line seems to be necessary, even though updateReferenceIndices
|
||||
// * should fire from the attribute change immediately above. It seems that events initiated by
|
||||
// * a `change:data` event do not themselves fire another `change:data` event.
|
||||
// */
|
||||
if ( id ) {
|
||||
this._updateReferenceIndices( batch, `${ id }`, `${ index }` );
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
7
_regroup/ckeditor5-footnotes/src/footnote-editing/schema.d.ts
vendored
Normal file
7
_regroup/ckeditor5-footnotes/src/footnote-editing/schema.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { Schema } from 'ckeditor5/src/engine.js';
|
||||
/**
|
||||
* Declares the custom element types used by the footnotes plugin.
|
||||
* See here for the meanings of each rule:
|
||||
* https://ckeditor.com/docs/ckeditor5/latest/api/module_engine_model_schema-SchemaItemDefinition.html#member-isObject
|
||||
*/
|
||||
export declare const defineSchema: (schema: Schema) => void;
|
||||
63
_regroup/ckeditor5-footnotes/src/footnote-editing/schema.js
Normal file
63
_regroup/ckeditor5-footnotes/src/footnote-editing/schema.js
Normal file
@@ -0,0 +1,63 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { ATTRIBUTES, ELEMENTS } from '../constants.js';
|
||||
/**
|
||||
* Declares the custom element types used by the footnotes plugin.
|
||||
* See here for the meanings of each rule:
|
||||
* https://ckeditor.com/docs/ckeditor5/latest/api/module_engine_model_schema-SchemaItemDefinition.html#member-isObject
|
||||
*/
|
||||
export const defineSchema = (schema) => {
|
||||
/**
|
||||
* Footnote section at the footer of the document.
|
||||
*/
|
||||
schema.register(ELEMENTS.footnoteSection, {
|
||||
isObject: true,
|
||||
allowWhere: '$block',
|
||||
allowIn: '$root',
|
||||
allowChildren: ELEMENTS.footnoteItem,
|
||||
allowAttributes: [ATTRIBUTES.footnoteSection]
|
||||
});
|
||||
/**
|
||||
* Individual footnote item within the footnote section.
|
||||
*/
|
||||
schema.register(ELEMENTS.footnoteItem, {
|
||||
isBlock: true,
|
||||
isObject: true,
|
||||
allowContentOf: '$root',
|
||||
allowAttributes: [ATTRIBUTES.footnoteSection, ATTRIBUTES.footnoteId, ATTRIBUTES.footnoteIndex]
|
||||
});
|
||||
/**
|
||||
* Editable footnote item content container.
|
||||
*/
|
||||
schema.register(ELEMENTS.footnoteContent, {
|
||||
allowIn: ELEMENTS.footnoteItem,
|
||||
allowContentOf: '$root',
|
||||
allowAttributes: [ATTRIBUTES.footnoteSection]
|
||||
});
|
||||
/**
|
||||
* Inline footnote citation, placed within the main text.
|
||||
*/
|
||||
schema.register(ELEMENTS.footnoteReference, {
|
||||
allowWhere: '$text',
|
||||
isInline: true,
|
||||
isObject: true,
|
||||
allowAttributes: [ATTRIBUTES.footnoteReference, ATTRIBUTES.footnoteId, ATTRIBUTES.footnoteIndex]
|
||||
});
|
||||
/**
|
||||
* return link which takes you from the footnote to the inline reference.
|
||||
*/
|
||||
schema.register(ELEMENTS.footnoteBackLink, {
|
||||
allowIn: ELEMENTS.footnoteItem,
|
||||
isInline: true,
|
||||
isSelectable: false,
|
||||
allowAttributes: [ATTRIBUTES.footnoteBackLink, ATTRIBUTES.footnoteId]
|
||||
});
|
||||
schema.addChildCheck((context, childDefinition) => {
|
||||
if (context.endsWith(ELEMENTS.footnoteContent) && childDefinition.name === ELEMENTS.footnoteSection) {
|
||||
return false;
|
||||
}
|
||||
if (context.endsWith(ELEMENTS.footnoteContent) && childDefinition.name === 'listItem') {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
//# sourceMappingURL=schema.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"schema.js","sourceRoot":"","sources":["schema.ts"],"names":[],"mappings":"AAEA,iDAAiD;AACjD,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAEvD;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAE,MAAc,EAAS,EAAE;IACtD;;KAEI;IACJ,MAAM,CAAC,QAAQ,CAAE,QAAQ,CAAC,eAAe,EAAE;QAC1C,QAAQ,EAAE,IAAI;QACd,UAAU,EAAE,QAAQ;QACpB,OAAO,EAAE,OAAO;QAChB,aAAa,EAAE,QAAQ,CAAC,YAAY;QACpC,eAAe,EAAE,CAAE,UAAU,CAAC,eAAe,CAAE;KAC/C,CAAE,CAAC;IAEJ;;KAEI;IACJ,MAAM,CAAC,QAAQ,CAAE,QAAQ,CAAC,YAAY,EAAE;QACvC,OAAO,EAAE,IAAI;QACb,QAAQ,EAAE,IAAI;QACd,cAAc,EAAE,OAAO;QACvB,eAAe,EAAE,CAAE,UAAU,CAAC,eAAe,EAAE,UAAU,CAAC,UAAU,EAAE,UAAU,CAAC,aAAa,CAAE;KAChG,CAAE,CAAC;IAEJ;;KAEI;IACJ,MAAM,CAAC,QAAQ,CAAE,QAAQ,CAAC,eAAe,EAAE;QAC1C,OAAO,EAAE,QAAQ,CAAC,YAAY;QAC9B,cAAc,EAAE,OAAO;QACvB,eAAe,EAAE,CAAE,UAAU,CAAC,eAAe,CAAE;KAC/C,CAAE,CAAC;IAEJ;;KAEI;IACJ,MAAM,CAAC,QAAQ,CAAE,QAAQ,CAAC,iBAAiB,EAAE;QAC5C,UAAU,EAAE,OAAO;QACnB,QAAQ,EAAE,IAAI;QACd,QAAQ,EAAE,IAAI;QACd,eAAe,EAAE,CAAE,UAAU,CAAC,iBAAiB,EAAE,UAAU,CAAC,UAAU,EAAE,UAAU,CAAC,aAAa,CAAE;KAClG,CAAE,CAAC;IAEJ;;KAEI;IACJ,MAAM,CAAC,QAAQ,CAAE,QAAQ,CAAC,gBAAgB,EAAE;QAC3C,OAAO,EAAE,QAAQ,CAAC,YAAY;QAC9B,QAAQ,EAAE,IAAI;QACd,YAAY,EAAE,KAAK;QACnB,eAAe,EAAE,CAAE,UAAU,CAAC,gBAAgB,EAAE,UAAU,CAAC,UAAU,CAAE;KACvE,CAAE,CAAC;IAEJ,MAAM,CAAC,aAAa,CAAE,CAAE,OAAO,EAAE,eAAe,EAAG,EAAE;QACpD,IAAK,OAAO,CAAC,QAAQ,CAAE,QAAQ,CAAC,eAAe,CAAE,IAAI,eAAe,CAAC,IAAI,KAAK,QAAQ,CAAC,eAAe,EAAG;YACxG,OAAO,KAAK,CAAC;SACb;QACD,IAAK,OAAO,CAAC,QAAQ,CAAE,QAAQ,CAAC,eAAe,CAAE,IAAI,eAAe,CAAC,IAAI,KAAK,UAAU,EAAG;YAC1F,OAAO,KAAK,CAAC;SACb;IACF,CAAC,CAAE,CAAC;AACL,CAAC,CAAC"}
|
||||
70
_regroup/ckeditor5-footnotes/src/footnote-editing/schema.ts
Normal file
70
_regroup/ckeditor5-footnotes/src/footnote-editing/schema.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import type { Schema } from 'ckeditor5/src/engine.js';
|
||||
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { ATTRIBUTES, ELEMENTS } from '../constants.js';
|
||||
|
||||
/**
|
||||
* Declares the custom element types used by the footnotes plugin.
|
||||
* See here for the meanings of each rule:
|
||||
* https://ckeditor.com/docs/ckeditor5/latest/api/module_engine_model_schema-SchemaItemDefinition.html#member-isObject
|
||||
*/
|
||||
export const defineSchema = ( schema: Schema ): void => {
|
||||
/**
|
||||
* Footnote section at the footer of the document.
|
||||
*/
|
||||
schema.register( ELEMENTS.footnoteSection, {
|
||||
isObject: true,
|
||||
allowWhere: '$block',
|
||||
allowIn: '$root',
|
||||
allowChildren: ELEMENTS.footnoteItem,
|
||||
allowAttributes: [ ATTRIBUTES.footnoteSection ]
|
||||
} );
|
||||
|
||||
/**
|
||||
* Individual footnote item within the footnote section.
|
||||
*/
|
||||
schema.register( ELEMENTS.footnoteItem, {
|
||||
isBlock: true,
|
||||
isObject: true,
|
||||
allowContentOf: '$root',
|
||||
allowAttributes: [ ATTRIBUTES.footnoteSection, ATTRIBUTES.footnoteId, ATTRIBUTES.footnoteIndex ]
|
||||
} );
|
||||
|
||||
/**
|
||||
* Editable footnote item content container.
|
||||
*/
|
||||
schema.register( ELEMENTS.footnoteContent, {
|
||||
allowIn: ELEMENTS.footnoteItem,
|
||||
allowContentOf: '$root',
|
||||
allowAttributes: [ ATTRIBUTES.footnoteSection ]
|
||||
} );
|
||||
|
||||
/**
|
||||
* Inline footnote citation, placed within the main text.
|
||||
*/
|
||||
schema.register( ELEMENTS.footnoteReference, {
|
||||
allowWhere: '$text',
|
||||
isInline: true,
|
||||
isObject: true,
|
||||
allowAttributes: [ ATTRIBUTES.footnoteReference, ATTRIBUTES.footnoteId, ATTRIBUTES.footnoteIndex ]
|
||||
} );
|
||||
|
||||
/**
|
||||
* return link which takes you from the footnote to the inline reference.
|
||||
*/
|
||||
schema.register( ELEMENTS.footnoteBackLink, {
|
||||
allowIn: ELEMENTS.footnoteItem,
|
||||
isInline: true,
|
||||
isSelectable: false,
|
||||
allowAttributes: [ ATTRIBUTES.footnoteBackLink, ATTRIBUTES.footnoteId ]
|
||||
} );
|
||||
|
||||
schema.addChildCheck( ( context, childDefinition ) => {
|
||||
if ( context.endsWith( ELEMENTS.footnoteContent ) && childDefinition.name === ELEMENTS.footnoteSection ) {
|
||||
return false;
|
||||
}
|
||||
if ( context.endsWith( ELEMENTS.footnoteContent ) && childDefinition.name === 'listItem' ) {
|
||||
return false;
|
||||
}
|
||||
} );
|
||||
};
|
||||
7
_regroup/ckeditor5-footnotes/src/footnote-ui.d.ts
vendored
Normal file
7
_regroup/ckeditor5-footnotes/src/footnote-ui.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Plugin } from 'ckeditor5/src/core.js';
|
||||
import { type ListDropdownItemDefinition } from '@ckeditor/ckeditor5-ui';
|
||||
import { Collection } from '@ckeditor/ckeditor5-utils';
|
||||
export default class FootnoteUI extends Plugin {
|
||||
init(): void;
|
||||
getDropdownItemsDefinitions(): Collection<ListDropdownItemDefinition>;
|
||||
}
|
||||
93
_regroup/ckeditor5-footnotes/src/footnote-ui.js
Normal file
93
_regroup/ckeditor5-footnotes/src/footnote-ui.js
Normal file
@@ -0,0 +1,93 @@
|
||||
import { Plugin } from 'ckeditor5/src/core.js';
|
||||
import { addListToDropdown, createDropdown, SplitButtonView, ViewModel } from '@ckeditor/ckeditor5-ui';
|
||||
import { Collection } from '@ckeditor/ckeditor5-utils';
|
||||
import { ATTRIBUTES, COMMANDS, ELEMENTS, TOOLBAR_COMPONENT_NAME } from './constants.js';
|
||||
import insertFootnoteIcon from '../theme/icons/insert-footnote.svg';
|
||||
import { modelQueryElement, modelQueryElementsAll } from './utils.js';
|
||||
export default class FootnoteUI extends Plugin {
|
||||
init() {
|
||||
const editor = this.editor;
|
||||
const translate = editor.t;
|
||||
editor.ui.componentFactory.add(TOOLBAR_COMPONENT_NAME, locale => {
|
||||
const dropdownView = createDropdown(locale, SplitButtonView);
|
||||
const splitButtonView = dropdownView.buttonView;
|
||||
// Populate the list in the dropdown with items.
|
||||
// addListToDropdown( dropdownView, getDropdownItemsDefinitions( placeholderNames ) );
|
||||
const command = editor.commands.get(COMMANDS.insertFootnote);
|
||||
if (!command) {
|
||||
throw new Error('Command not found.');
|
||||
}
|
||||
splitButtonView.set({
|
||||
label: translate('Footnote'),
|
||||
icon: insertFootnoteIcon,
|
||||
tooltip: true,
|
||||
isToggleable: true
|
||||
});
|
||||
splitButtonView.bind('isOn').to(command, 'value', value => !!value);
|
||||
splitButtonView.on('execute', () => {
|
||||
editor.execute(COMMANDS.insertFootnote, {
|
||||
footnoteIndex: 0
|
||||
});
|
||||
editor.editing.view.focus();
|
||||
});
|
||||
dropdownView.class = 'ck-code-block-dropdown';
|
||||
dropdownView.bind('isEnabled').to(command);
|
||||
dropdownView.on('change:isOpen', (evt, propertyName, newValue) => {
|
||||
var _a, _b, _c;
|
||||
(_a = dropdownView === null || dropdownView === void 0 ? void 0 : dropdownView.listView) === null || _a === void 0 ? void 0 : _a.items.clear();
|
||||
if (newValue) {
|
||||
addListToDropdown(dropdownView, this.getDropdownItemsDefinitions());
|
||||
}
|
||||
else {
|
||||
(_b = dropdownView === null || dropdownView === void 0 ? void 0 : dropdownView.listView) === null || _b === void 0 ? void 0 : _b.items.clear();
|
||||
const listElement = (_c = dropdownView === null || dropdownView === void 0 ? void 0 : dropdownView.listView) === null || _c === void 0 ? void 0 : _c.element;
|
||||
if (listElement && listElement.parentNode) {
|
||||
listElement.parentNode.removeChild(listElement);
|
||||
}
|
||||
}
|
||||
});
|
||||
// Execute the command when the dropdown item is clicked (executed).
|
||||
this.listenTo(dropdownView, 'execute', evt => {
|
||||
editor.execute(COMMANDS.insertFootnote, {
|
||||
footnoteIndex: evt.source.commandParam
|
||||
});
|
||||
editor.editing.view.focus();
|
||||
});
|
||||
return dropdownView;
|
||||
});
|
||||
}
|
||||
getDropdownItemsDefinitions() {
|
||||
const itemDefinitions = new Collection();
|
||||
const defaultDef = {
|
||||
type: 'button',
|
||||
model: new ViewModel({
|
||||
commandParam: 0,
|
||||
label: 'New footnote',
|
||||
withText: true
|
||||
})
|
||||
};
|
||||
itemDefinitions.add(defaultDef);
|
||||
const rootElement = this.editor.model.document.getRoot();
|
||||
if (!rootElement) {
|
||||
throw new Error('Document has no root element.');
|
||||
}
|
||||
const footnoteSection = modelQueryElement(this.editor, rootElement, element => element.is('element', ELEMENTS.footnoteSection));
|
||||
if (footnoteSection) {
|
||||
const footnoteItems = modelQueryElementsAll(this.editor, rootElement, element => element.is('element', ELEMENTS.footnoteItem));
|
||||
footnoteItems.forEach(footnote => {
|
||||
const index = footnote.getAttribute(ATTRIBUTES.footnoteIndex);
|
||||
const definition = {
|
||||
type: 'button',
|
||||
model: new ViewModel({
|
||||
commandParam: index,
|
||||
label: `Insert footnote ${index}`,
|
||||
withText: true
|
||||
})
|
||||
};
|
||||
itemDefinitions.add(definition);
|
||||
});
|
||||
}
|
||||
return itemDefinitions;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=footnote-ui.js.map
|
||||
1
_regroup/ckeditor5-footnotes/src/footnote-ui.js.map
Normal file
1
_regroup/ckeditor5-footnotes/src/footnote-ui.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"footnote-ui.js","sourceRoot":"","sources":["footnote-ui.ts"],"names":[],"mappings":"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"}
|
||||
122
_regroup/ckeditor5-footnotes/src/footnote-ui.ts
Normal file
122
_regroup/ckeditor5-footnotes/src/footnote-ui.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import { Plugin } from 'ckeditor5/src/core.js';
|
||||
import { addListToDropdown, createDropdown, SplitButtonView, ViewModel, type ListDropdownItemDefinition } from '@ckeditor/ckeditor5-ui';
|
||||
import { Collection } from '@ckeditor/ckeditor5-utils';
|
||||
|
||||
import {
|
||||
ATTRIBUTES,
|
||||
COMMANDS,
|
||||
ELEMENTS,
|
||||
TOOLBAR_COMPONENT_NAME
|
||||
} from './constants.js';
|
||||
import insertFootnoteIcon from '../theme/icons/insert-footnote.svg';
|
||||
import { modelQueryElement, modelQueryElementsAll } from './utils.js';
|
||||
|
||||
export default class FootnoteUI extends Plugin {
|
||||
public init(): void {
|
||||
const editor = this.editor;
|
||||
const translate = editor.t;
|
||||
|
||||
editor.ui.componentFactory.add( TOOLBAR_COMPONENT_NAME, locale => {
|
||||
const dropdownView = createDropdown( locale, SplitButtonView );
|
||||
const splitButtonView = dropdownView.buttonView;
|
||||
|
||||
// Populate the list in the dropdown with items.
|
||||
// addListToDropdown( dropdownView, getDropdownItemsDefinitions( placeholderNames ) );
|
||||
const command = editor.commands.get( COMMANDS.insertFootnote );
|
||||
if ( !command ) {
|
||||
throw new Error( 'Command not found.' );
|
||||
}
|
||||
|
||||
splitButtonView.set( {
|
||||
label: translate( 'Footnote' ),
|
||||
icon: insertFootnoteIcon,
|
||||
tooltip: true,
|
||||
isToggleable: true
|
||||
} );
|
||||
splitButtonView.bind( 'isOn' ).to( command, 'value', value => !!value );
|
||||
splitButtonView.on( 'execute', () => {
|
||||
editor.execute( COMMANDS.insertFootnote, {
|
||||
footnoteIndex: 0
|
||||
} );
|
||||
editor.editing.view.focus();
|
||||
} );
|
||||
|
||||
dropdownView.class = 'ck-code-block-dropdown';
|
||||
dropdownView.bind( 'isEnabled' ).to( command );
|
||||
dropdownView.on(
|
||||
'change:isOpen',
|
||||
( evt, propertyName, newValue ) => {
|
||||
dropdownView?.listView?.items.clear();
|
||||
if ( newValue ) {
|
||||
addListToDropdown(
|
||||
dropdownView,
|
||||
this.getDropdownItemsDefinitions() as any
|
||||
);
|
||||
} else {
|
||||
dropdownView?.listView?.items.clear();
|
||||
const listElement = dropdownView?.listView?.element;
|
||||
if ( listElement && listElement.parentNode ) {
|
||||
listElement.parentNode.removeChild( listElement );
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
// Execute the command when the dropdown item is clicked (executed).
|
||||
this.listenTo( dropdownView, 'execute', evt => {
|
||||
editor.execute( COMMANDS.insertFootnote, {
|
||||
footnoteIndex: ( evt.source as any ).commandParam
|
||||
} );
|
||||
editor.editing.view.focus();
|
||||
} );
|
||||
|
||||
return dropdownView;
|
||||
} );
|
||||
}
|
||||
|
||||
public getDropdownItemsDefinitions(): Collection<ListDropdownItemDefinition> {
|
||||
const itemDefinitions = new Collection<ListDropdownItemDefinition>();
|
||||
const defaultDef: ListDropdownItemDefinition = {
|
||||
type: 'button',
|
||||
model: new ViewModel( {
|
||||
commandParam: 0,
|
||||
label: 'New footnote',
|
||||
withText: true
|
||||
} )
|
||||
};
|
||||
itemDefinitions.add( defaultDef );
|
||||
|
||||
const rootElement = this.editor.model.document.getRoot();
|
||||
if ( !rootElement ) {
|
||||
throw new Error( 'Document has no root element.' );
|
||||
}
|
||||
|
||||
const footnoteSection = modelQueryElement(
|
||||
this.editor,
|
||||
rootElement,
|
||||
element => element.is( 'element', ELEMENTS.footnoteSection )
|
||||
);
|
||||
|
||||
if ( footnoteSection ) {
|
||||
const footnoteItems = modelQueryElementsAll(
|
||||
this.editor,
|
||||
rootElement,
|
||||
element => element.is( 'element', ELEMENTS.footnoteItem )
|
||||
);
|
||||
footnoteItems.forEach( footnote => {
|
||||
const index = footnote.getAttribute( ATTRIBUTES.footnoteIndex );
|
||||
const definition: ListDropdownItemDefinition = {
|
||||
type: 'button',
|
||||
model: new ViewModel( {
|
||||
commandParam: index,
|
||||
label: `Insert footnote ${ index }`,
|
||||
withText: true
|
||||
} )
|
||||
};
|
||||
|
||||
itemDefinitions.add( definition );
|
||||
} );
|
||||
}
|
||||
|
||||
return itemDefinitions;
|
||||
}
|
||||
}
|
||||
60
_regroup/ckeditor5-footnotes/src/footnote.css
Normal file
60
_regroup/ckeditor5-footnotes/src/footnote.css
Normal file
@@ -0,0 +1,60 @@
|
||||
.ck .footnote-section {
|
||||
padding: 10px;
|
||||
margin: 1em 0;
|
||||
border: solid 1px hsl(0, 0%, 77%);
|
||||
border-radius: 2px;
|
||||
counter-reset: footnote-counter;
|
||||
}
|
||||
|
||||
.ck .footnote-item {
|
||||
list-style: none;
|
||||
counter-increment: footnote-counter;
|
||||
margin-left: 0.5em;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ck .footnote-item > * {
|
||||
vertical-align: text-top;
|
||||
}
|
||||
|
||||
.ck .footnote-back-link {
|
||||
position: relative;
|
||||
margin-right: 0.1em;
|
||||
top: -0.2em;
|
||||
}
|
||||
|
||||
.ck .footnotes .footnote-back-link > sup {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.ck .footnote-item::before {
|
||||
content: counter(footnote-counter) ". ";
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
right: 0.2em;
|
||||
min-width: fit-content;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.ck .footnote-content {
|
||||
display: inline-block;
|
||||
padding: 0 0.3em;
|
||||
width: 95%;
|
||||
border-radius: 2px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.ck .ck-widget.footnote-section .ck-widget__type-around__button_after {
|
||||
display:none; /* hides the 'insert after' button from the ckeditor widget */
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
padding: 2px 2px;
|
||||
outline-offset: -2px;
|
||||
line-height: 1em;
|
||||
margin: 0 1px;
|
||||
}
|
||||
|
||||
.placeholder::selection {
|
||||
display: none;
|
||||
}
|
||||
7
_regroup/ckeditor5-footnotes/src/footnotes.d.ts
vendored
Normal file
7
_regroup/ckeditor5-footnotes/src/footnotes.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Plugin } from 'ckeditor5/src/core.js';
|
||||
import FootnoteEditing from './footnote-editing/footnote-editing.js';
|
||||
import FootnoteUI from './footnote-ui.js';
|
||||
export default class Footnotes extends Plugin {
|
||||
static get pluginName(): "Footnotes";
|
||||
static get requires(): readonly [typeof FootnoteEditing, typeof FootnoteUI];
|
||||
}
|
||||
12
_regroup/ckeditor5-footnotes/src/footnotes.js
Normal file
12
_regroup/ckeditor5-footnotes/src/footnotes.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Plugin } from 'ckeditor5/src/core.js';
|
||||
import FootnoteEditing from './footnote-editing/footnote-editing.js';
|
||||
import FootnoteUI from './footnote-ui.js';
|
||||
export default class Footnotes extends Plugin {
|
||||
static get pluginName() {
|
||||
return 'Footnotes';
|
||||
}
|
||||
static get requires() {
|
||||
return [FootnoteEditing, FootnoteUI];
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=footnotes.js.map
|
||||
1
_regroup/ckeditor5-footnotes/src/footnotes.js.map
Normal file
1
_regroup/ckeditor5-footnotes/src/footnotes.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"footnotes.js","sourceRoot":"","sources":["footnotes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAE/C,OAAO,eAAe,MAAM,wCAAwC,CAAC;AACrE,OAAO,UAAU,MAAM,kBAAkB,CAAC;AAE1C,MAAM,CAAC,OAAO,OAAO,SAAU,SAAQ,MAAM;IACrC,MAAM,KAAK,UAAU;QAC3B,OAAO,WAAoB,CAAC;IAC7B,CAAC;IAEM,MAAM,KAAK,QAAQ;QACzB,OAAO,CAAE,eAAe,EAAE,UAAU,CAAW,CAAC;IACjD,CAAC;CACD"}
|
||||
14
_regroup/ckeditor5-footnotes/src/footnotes.ts
Normal file
14
_regroup/ckeditor5-footnotes/src/footnotes.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Plugin } from 'ckeditor5/src/core.js';
|
||||
|
||||
import FootnoteEditing from './footnote-editing/footnote-editing.js';
|
||||
import FootnoteUI from './footnote-ui.js';
|
||||
|
||||
export default class Footnotes extends Plugin {
|
||||
public static get pluginName() {
|
||||
return 'Footnotes' as const;
|
||||
}
|
||||
|
||||
public static get requires() {
|
||||
return [ FootnoteEditing, FootnoteUI ] as const;
|
||||
}
|
||||
}
|
||||
5
_regroup/ckeditor5-footnotes/src/index.d.ts
vendored
Normal file
5
_regroup/ckeditor5-footnotes/src/index.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import './augmentation.js';
|
||||
export { default as Footnotes } from './footnotes.js';
|
||||
export declare const icons: {
|
||||
insertFootnoteIcon: string;
|
||||
};
|
||||
7
_regroup/ckeditor5-footnotes/src/index.js
Normal file
7
_regroup/ckeditor5-footnotes/src/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import insertFootnoteIcon from './../theme/icons/insert-footnote.svg';
|
||||
import './augmentation.js';
|
||||
export { default as Footnotes } from './footnotes.js';
|
||||
export const icons = {
|
||||
insertFootnoteIcon
|
||||
};
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
_regroup/ckeditor5-footnotes/src/index.js.map
Normal file
1
_regroup/ckeditor5-footnotes/src/index.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,MAAM,sCAAsC,CAAC;AACtE,OAAO,mBAAmB,CAAC;AAE3B,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEtD,MAAM,CAAC,MAAM,KAAK,GAAG;IACpB,kBAAkB;CAClB,CAAC"}
|
||||
8
_regroup/ckeditor5-footnotes/src/index.ts
Normal file
8
_regroup/ckeditor5-footnotes/src/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import insertFootnoteIcon from './../theme/icons/insert-footnote.svg';
|
||||
import './augmentation.js';
|
||||
|
||||
export { default as Footnotes } from './footnotes.js';
|
||||
|
||||
export const icons = {
|
||||
insertFootnoteIcon
|
||||
};
|
||||
21
_regroup/ckeditor5-footnotes/src/insert-footnote-command.d.ts
vendored
Normal file
21
_regroup/ckeditor5-footnotes/src/insert-footnote-command.d.ts
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Command } from 'ckeditor5/src/core.js';
|
||||
export default class InsertFootnoteCommand extends Command {
|
||||
/**
|
||||
* Creates a footnote reference with the given index, and creates a matching
|
||||
* footnote if one doesn't already exist. Also creates the footnote section
|
||||
* if it doesn't exist. If `footnoteIndex` is 0 (or not provided), the added
|
||||
* footnote is given the next unused index--e.g. 7, if 6 footnotes exist so far.
|
||||
*/
|
||||
execute({ footnoteIndex }?: {
|
||||
footnoteIndex?: number;
|
||||
}): void;
|
||||
/**
|
||||
* Called automatically when changes are applied to the document. Sets `isEnabled`
|
||||
* to determine whether footnote creation is allowed at the current location.
|
||||
*/
|
||||
refresh(): void;
|
||||
/**
|
||||
* Returns the footnote section if it exists, or creates on if it doesn't.
|
||||
*/
|
||||
private _getFootnoteSection;
|
||||
}
|
||||
82
_regroup/ckeditor5-footnotes/src/insert-footnote-command.js
Normal file
82
_regroup/ckeditor5-footnotes/src/insert-footnote-command.js
Normal file
@@ -0,0 +1,82 @@
|
||||
import { Command } from 'ckeditor5/src/core.js';
|
||||
import { ATTRIBUTES, ELEMENTS } from './constants.js';
|
||||
import { modelQueryElement } from './utils.js';
|
||||
export default class InsertFootnoteCommand extends Command {
|
||||
/**
|
||||
* Creates a footnote reference with the given index, and creates a matching
|
||||
* footnote if one doesn't already exist. Also creates the footnote section
|
||||
* if it doesn't exist. If `footnoteIndex` is 0 (or not provided), the added
|
||||
* footnote is given the next unused index--e.g. 7, if 6 footnotes exist so far.
|
||||
*/
|
||||
execute({ footnoteIndex } = { footnoteIndex: 0 }) {
|
||||
this.editor.model.enqueueChange(modelWriter => {
|
||||
const doc = this.editor.model.document;
|
||||
const rootElement = doc.getRoot();
|
||||
if (!rootElement) {
|
||||
return;
|
||||
}
|
||||
const footnoteSection = this._getFootnoteSection(modelWriter, rootElement);
|
||||
let index = undefined;
|
||||
let id = undefined;
|
||||
if (footnoteIndex === 0) {
|
||||
index = `${footnoteSection.maxOffset + 1}`;
|
||||
id = Math.random().toString(36).slice(2);
|
||||
}
|
||||
else {
|
||||
index = `${footnoteIndex}`;
|
||||
const matchingFootnote = modelQueryElement(this.editor, footnoteSection, element => element.is('element', ELEMENTS.footnoteItem) && element.getAttribute(ATTRIBUTES.footnoteIndex) === index);
|
||||
if (matchingFootnote) {
|
||||
id = matchingFootnote.getAttribute(ATTRIBUTES.footnoteId);
|
||||
}
|
||||
}
|
||||
if (!id || !index) {
|
||||
return;
|
||||
}
|
||||
modelWriter.setSelection(doc.selection.getLastPosition());
|
||||
const footnoteReference = modelWriter.createElement(ELEMENTS.footnoteReference, {
|
||||
[ATTRIBUTES.footnoteId]: id,
|
||||
[ATTRIBUTES.footnoteIndex]: index
|
||||
});
|
||||
this.editor.model.insertContent(footnoteReference);
|
||||
modelWriter.setSelection(footnoteReference, 'after');
|
||||
// if referencing an existing footnote
|
||||
if (footnoteIndex !== 0) {
|
||||
return;
|
||||
}
|
||||
const footnoteContent = modelWriter.createElement(ELEMENTS.footnoteContent);
|
||||
const footnoteItem = modelWriter.createElement(ELEMENTS.footnoteItem, {
|
||||
[ATTRIBUTES.footnoteId]: id,
|
||||
[ATTRIBUTES.footnoteIndex]: index
|
||||
});
|
||||
const footnoteBackLink = modelWriter.createElement(ELEMENTS.footnoteBackLink, { [ATTRIBUTES.footnoteId]: id });
|
||||
const p = modelWriter.createElement('paragraph');
|
||||
modelWriter.append(p, footnoteContent);
|
||||
modelWriter.append(footnoteContent, footnoteItem);
|
||||
modelWriter.insert(footnoteBackLink, footnoteItem, 0);
|
||||
this.editor.model.insertContent(footnoteItem, modelWriter.createPositionAt(footnoteSection, footnoteSection.maxOffset));
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Called automatically when changes are applied to the document. Sets `isEnabled`
|
||||
* to determine whether footnote creation is allowed at the current location.
|
||||
*/
|
||||
refresh() {
|
||||
const model = this.editor.model;
|
||||
const lastPosition = model.document.selection.getLastPosition();
|
||||
const allowedIn = lastPosition && model.schema.findAllowedParent(lastPosition, ELEMENTS.footnoteReference);
|
||||
this.isEnabled = allowedIn !== null;
|
||||
}
|
||||
/**
|
||||
* Returns the footnote section if it exists, or creates on if it doesn't.
|
||||
*/
|
||||
_getFootnoteSection(writer, rootElement) {
|
||||
const footnoteSection = modelQueryElement(this.editor, rootElement, element => element.is('element', ELEMENTS.footnoteSection));
|
||||
if (footnoteSection) {
|
||||
return footnoteSection;
|
||||
}
|
||||
const newFootnoteSection = writer.createElement(ELEMENTS.footnoteSection);
|
||||
this.editor.model.insertContent(newFootnoteSection, writer.createPositionAt(rootElement, rootElement.maxOffset));
|
||||
return newFootnoteSection;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=insert-footnote-command.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"insert-footnote-command.js","sourceRoot":"","sources":["insert-footnote-command.ts"],"names":[],"mappings":"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"}
|
||||
97
_regroup/ckeditor5-footnotes/src/insert-footnote-command.ts
Normal file
97
_regroup/ckeditor5-footnotes/src/insert-footnote-command.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { Command } from 'ckeditor5/src/core.js';
|
||||
import { type Element, type RootElement, type Writer } from "ckeditor5/src/engine.js";
|
||||
|
||||
import { ATTRIBUTES, ELEMENTS } from './constants.js';
|
||||
import { modelQueryElement } from './utils.js';
|
||||
|
||||
export default class InsertFootnoteCommand extends Command {
|
||||
/**
|
||||
* Creates a footnote reference with the given index, and creates a matching
|
||||
* footnote if one doesn't already exist. Also creates the footnote section
|
||||
* if it doesn't exist. If `footnoteIndex` is 0 (or not provided), the added
|
||||
* footnote is given the next unused index--e.g. 7, if 6 footnotes exist so far.
|
||||
*/
|
||||
public override execute( { footnoteIndex }: { footnoteIndex?: number } = { footnoteIndex: 0 } ): void {
|
||||
this.editor.model.enqueueChange( modelWriter => {
|
||||
const doc = this.editor.model.document;
|
||||
const rootElement = doc.getRoot();
|
||||
if ( !rootElement ) {
|
||||
return;
|
||||
}
|
||||
const footnoteSection = this._getFootnoteSection( modelWriter, rootElement );
|
||||
let index: string | undefined = undefined;
|
||||
let id: string | undefined = undefined;
|
||||
if ( footnoteIndex === 0 ) {
|
||||
index = `${ footnoteSection.maxOffset + 1 }`;
|
||||
id = Math.random().toString( 36 ).slice( 2 );
|
||||
} else {
|
||||
index = `${ footnoteIndex }`;
|
||||
const matchingFootnote = modelQueryElement(
|
||||
this.editor,
|
||||
footnoteSection,
|
||||
element =>
|
||||
element.is( 'element', ELEMENTS.footnoteItem ) && element.getAttribute( ATTRIBUTES.footnoteIndex ) === index
|
||||
);
|
||||
if ( matchingFootnote ) {
|
||||
id = matchingFootnote.getAttribute( ATTRIBUTES.footnoteId ) as string;
|
||||
}
|
||||
}
|
||||
if ( !id || !index ) {
|
||||
return;
|
||||
}
|
||||
modelWriter.setSelection( doc.selection.getLastPosition() );
|
||||
const footnoteReference = modelWriter.createElement( ELEMENTS.footnoteReference, {
|
||||
[ ATTRIBUTES.footnoteId ]: id,
|
||||
[ ATTRIBUTES.footnoteIndex ]: index
|
||||
} );
|
||||
this.editor.model.insertContent( footnoteReference );
|
||||
modelWriter.setSelection( footnoteReference, 'after' );
|
||||
// if referencing an existing footnote
|
||||
if ( footnoteIndex !== 0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const footnoteContent = modelWriter.createElement( ELEMENTS.footnoteContent );
|
||||
const footnoteItem = modelWriter.createElement( ELEMENTS.footnoteItem, {
|
||||
[ ATTRIBUTES.footnoteId ]: id,
|
||||
[ ATTRIBUTES.footnoteIndex ]: index
|
||||
} );
|
||||
const footnoteBackLink = modelWriter.createElement( ELEMENTS.footnoteBackLink, { [ ATTRIBUTES.footnoteId ]: id } );
|
||||
const p = modelWriter.createElement( 'paragraph' );
|
||||
modelWriter.append( p, footnoteContent );
|
||||
modelWriter.append( footnoteContent, footnoteItem );
|
||||
modelWriter.insert( footnoteBackLink, footnoteItem, 0 );
|
||||
|
||||
this.editor.model.insertContent(
|
||||
footnoteItem,
|
||||
modelWriter.createPositionAt( footnoteSection, footnoteSection.maxOffset )
|
||||
);
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Called automatically when changes are applied to the document. Sets `isEnabled`
|
||||
* to determine whether footnote creation is allowed at the current location.
|
||||
*/
|
||||
public override refresh(): void {
|
||||
const model = this.editor.model;
|
||||
const lastPosition = model.document.selection.getLastPosition();
|
||||
const allowedIn = lastPosition && model.schema.findAllowedParent( lastPosition, ELEMENTS.footnoteReference );
|
||||
this.isEnabled = allowedIn !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the footnote section if it exists, or creates on if it doesn't.
|
||||
*/
|
||||
private _getFootnoteSection( writer: Writer, rootElement: RootElement ): Element {
|
||||
const footnoteSection = modelQueryElement( this.editor, rootElement, element =>
|
||||
element.is( 'element', ELEMENTS.footnoteSection )
|
||||
);
|
||||
if ( footnoteSection ) {
|
||||
return footnoteSection;
|
||||
}
|
||||
const newFootnoteSection = writer.createElement( ELEMENTS.footnoteSection );
|
||||
this.editor.model.insertContent( newFootnoteSection, writer.createPositionAt( rootElement, rootElement.maxOffset ) );
|
||||
return newFootnoteSection;
|
||||
}
|
||||
}
|
||||
27
_regroup/ckeditor5-footnotes/src/utils.d.ts
vendored
Normal file
27
_regroup/ckeditor5-footnotes/src/utils.d.ts
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
import { type Editor } from 'ckeditor5/src/core.js';
|
||||
import { Element, Text, TextProxy, ViewElement } from 'ckeditor5/src/engine.js';
|
||||
/**
|
||||
* Returns an array of all descendant elements of
|
||||
* the root for which the provided predicate returns true.
|
||||
*/
|
||||
export declare const modelQueryElementsAll: (editor: Editor, rootElement: Element, predicate?: (item: Element) => boolean) => Array<Element>;
|
||||
/**
|
||||
* Returns an array of all descendant text nodes and text proxies of
|
||||
* the root for which the provided predicate returns true.
|
||||
*/
|
||||
export declare const modelQueryTextAll: (editor: Editor, rootElement: Element, predicate?: (item: Text | TextProxy) => boolean) => Array<Text | TextProxy>;
|
||||
/**
|
||||
* Returns the first descendant element of the root for which the provided
|
||||
* predicate returns true, or null if no such element is found.
|
||||
*/
|
||||
export declare const modelQueryElement: (editor: Editor, rootElement: Element, predicate?: (item: Element) => boolean) => Element | null;
|
||||
/**
|
||||
* Returns the first descendant text node or text proxy of the root for which the provided
|
||||
* predicate returns true, or null if no such element is found.
|
||||
*/
|
||||
export declare const modelQueryText: (editor: Editor, rootElement: Element, predicate?: (item: Text | TextProxy) => boolean) => Text | TextProxy | null;
|
||||
/**
|
||||
* Returns the first descendant element of the root for which the provided
|
||||
* predicate returns true, or null if no such element is found.
|
||||
*/
|
||||
export declare const viewQueryElement: (editor: Editor, rootElement: ViewElement, predicate?: (item: ViewElement) => boolean) => ViewElement | null;
|
||||
88
_regroup/ckeditor5-footnotes/src/utils.js
Normal file
88
_regroup/ckeditor5-footnotes/src/utils.js
Normal file
@@ -0,0 +1,88 @@
|
||||
import { Element, Text, TextProxy, ViewElement } from 'ckeditor5/src/engine.js';
|
||||
// There's ample DRY violation in this file; type checking
|
||||
// polymorphism without full typescript is just incredibly finicky.
|
||||
// I (Jonathan) suspect there's a more elegant solution for this,
|
||||
// but I tried a lot of things and none of them worked.
|
||||
/**
|
||||
* Returns an array of all descendant elements of
|
||||
* the root for which the provided predicate returns true.
|
||||
*/
|
||||
export const modelQueryElementsAll = (editor, rootElement, predicate = _ => true) => {
|
||||
const range = editor.model.createRangeIn(rootElement);
|
||||
const output = [];
|
||||
for (const item of range.getItems()) {
|
||||
if (!(item instanceof Element)) {
|
||||
continue;
|
||||
}
|
||||
if (predicate(item)) {
|
||||
output.push(item);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
};
|
||||
/**
|
||||
* Returns an array of all descendant text nodes and text proxies of
|
||||
* the root for which the provided predicate returns true.
|
||||
*/
|
||||
export const modelQueryTextAll = (editor, rootElement, predicate = _ => true) => {
|
||||
const range = editor.model.createRangeIn(rootElement);
|
||||
const output = [];
|
||||
for (const item of range.getItems()) {
|
||||
if (!(item instanceof Text || item instanceof TextProxy)) {
|
||||
continue;
|
||||
}
|
||||
if (predicate(item)) {
|
||||
output.push(item);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
};
|
||||
/**
|
||||
* Returns the first descendant element of the root for which the provided
|
||||
* predicate returns true, or null if no such element is found.
|
||||
*/
|
||||
export const modelQueryElement = (editor, rootElement, predicate = _ => true) => {
|
||||
const range = editor.model.createRangeIn(rootElement);
|
||||
for (const item of range.getItems()) {
|
||||
if (!(item instanceof Element)) {
|
||||
continue;
|
||||
}
|
||||
if (predicate(item)) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
/**
|
||||
* Returns the first descendant text node or text proxy of the root for which the provided
|
||||
* predicate returns true, or null if no such element is found.
|
||||
*/
|
||||
export const modelQueryText = (editor, rootElement, predicate = _ => true) => {
|
||||
const range = editor.model.createRangeIn(rootElement);
|
||||
for (const item of range.getItems()) {
|
||||
if (!(item instanceof Text || item instanceof TextProxy)) {
|
||||
continue;
|
||||
}
|
||||
if (predicate(item)) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
/**
|
||||
* Returns the first descendant element of the root for which the provided
|
||||
* predicate returns true, or null if no such element is found.
|
||||
*/
|
||||
export const viewQueryElement = (editor, rootElement, predicate = _ => true) => {
|
||||
const range = editor.editing.view.createRangeIn(rootElement);
|
||||
for (const item of range.getItems()) {
|
||||
if (!(item instanceof ViewElement)) {
|
||||
continue;
|
||||
}
|
||||
if (predicate(item)) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
//# sourceMappingURL=utils.js.map
|
||||
1
_regroup/ckeditor5-footnotes/src/utils.js.map
Normal file
1
_regroup/ckeditor5-footnotes/src/utils.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"utils.js","sourceRoot":"","sources":["utils.ts"],"names":[],"mappings":"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"}
|
||||
125
_regroup/ckeditor5-footnotes/src/utils.ts
Normal file
125
_regroup/ckeditor5-footnotes/src/utils.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import { type Editor } from 'ckeditor5/src/core.js';
|
||||
import { Element, Text, TextProxy, ViewElement } from 'ckeditor5/src/engine.js';
|
||||
|
||||
// There's ample DRY violation in this file; type checking
|
||||
// polymorphism without full typescript is just incredibly finicky.
|
||||
// I (Jonathan) suspect there's a more elegant solution for this,
|
||||
// but I tried a lot of things and none of them worked.
|
||||
|
||||
/**
|
||||
* Returns an array of all descendant elements of
|
||||
* the root for which the provided predicate returns true.
|
||||
*/
|
||||
export const modelQueryElementsAll = (
|
||||
editor: Editor,
|
||||
rootElement: Element,
|
||||
predicate: ( item: Element ) => boolean = _ => true
|
||||
): Array<Element> => {
|
||||
const range = editor.model.createRangeIn( rootElement );
|
||||
const output: Array<Element> = [];
|
||||
|
||||
for ( const item of range.getItems() ) {
|
||||
if ( !( item instanceof Element ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( predicate( item ) ) {
|
||||
output.push( item );
|
||||
}
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an array of all descendant text nodes and text proxies of
|
||||
* the root for which the provided predicate returns true.
|
||||
*/
|
||||
export const modelQueryTextAll = (
|
||||
editor: Editor,
|
||||
rootElement: Element,
|
||||
predicate: ( item: Text | TextProxy ) => boolean = _ => true
|
||||
): Array<Text | TextProxy> => {
|
||||
const range = editor.model.createRangeIn( rootElement );
|
||||
const output: Array<Text | TextProxy> = [];
|
||||
|
||||
for ( const item of range.getItems() ) {
|
||||
if ( !( item instanceof Text || item instanceof TextProxy ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( predicate( item ) ) {
|
||||
output.push( item );
|
||||
}
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the first descendant element of the root for which the provided
|
||||
* predicate returns true, or null if no such element is found.
|
||||
*/
|
||||
export const modelQueryElement = (
|
||||
editor: Editor,
|
||||
rootElement: Element,
|
||||
predicate: ( item: Element ) => boolean = _ => true
|
||||
): Element | null => {
|
||||
const range = editor.model.createRangeIn( rootElement );
|
||||
|
||||
for ( const item of range.getItems() ) {
|
||||
if ( !( item instanceof Element ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( predicate( item ) ) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the first descendant text node or text proxy of the root for which the provided
|
||||
* predicate returns true, or null if no such element is found.
|
||||
*/
|
||||
export const modelQueryText = (
|
||||
editor: Editor,
|
||||
rootElement: Element,
|
||||
predicate: ( item: Text | TextProxy ) => boolean = _ => true
|
||||
): Text | TextProxy | null => {
|
||||
const range = editor.model.createRangeIn( rootElement );
|
||||
|
||||
for ( const item of range.getItems() ) {
|
||||
if ( !( item instanceof Text || item instanceof TextProxy ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( predicate( item ) ) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the first descendant element of the root for which the provided
|
||||
* predicate returns true, or null if no such element is found.
|
||||
*/
|
||||
export const viewQueryElement = (
|
||||
editor: Editor,
|
||||
rootElement: ViewElement,
|
||||
predicate: ( item: ViewElement ) => boolean = _ => true
|
||||
): ViewElement | null => {
|
||||
const range = editor.editing.view.createRangeIn( rootElement );
|
||||
|
||||
for ( const item of range.getItems() ) {
|
||||
if ( !( item instanceof ViewElement ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( predicate( item ) ) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
|
||||
<text x="2" y="15" font-family="Arial, sans-serif" font-size="14" fill="currentColor">ab</text>
|
||||
<text x="17" y="10" font-family="Arial, sans-serif" font-size="8" fill="currentColor">1</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 384 B |
11
_regroup/ckeditor5-footnotes/tsconfig.dist.json
Normal file
11
_regroup/ckeditor5-footnotes/tsconfig.dist.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"rootDir": "./src",
|
||||
"types": [
|
||||
"./typings/types"
|
||||
]
|
||||
}
|
||||
}
|
||||
38
_regroup/ckeditor5-footnotes/tsconfig.json
Normal file
38
_regroup/ckeditor5-footnotes/tsconfig.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/**
|
||||
* TypeScript automagically loads typings from all "@types/*" packages if the "compilerOptions.types" array is not defined in
|
||||
* this file. However, if some dependencies have "@types/*" packages as their dependencies, they'll also be loaded as well.
|
||||
* As a result, TypeScript loaded "@types/node" which we don't want to use, because it allows using Node.js specific APIs that
|
||||
* are not available in the browsers.
|
||||
*
|
||||
* To avoid such issues, we defined this empty "types" to disable automatic inclusion of the "@types/*" packages.
|
||||
*/
|
||||
"types": [],
|
||||
"lib": [
|
||||
"ES2019", // Must match the "target".
|
||||
"ES2020.String",
|
||||
"DOM",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"noImplicitAny": true,
|
||||
"noImplicitOverride": true,
|
||||
"strict": true,
|
||||
"target": "es2019",
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"moduleDetection": "force",
|
||||
"moduleResolution": "NodeNext",
|
||||
"module": "NodeNext",
|
||||
"skipLibCheck": true,
|
||||
"typeRoots": [
|
||||
"typings",
|
||||
"node_modules/@types"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./sample",
|
||||
"./src",
|
||||
"./typings"
|
||||
]
|
||||
}
|
||||
12
_regroup/ckeditor5-footnotes/tsconfig.release.json
Normal file
12
_regroup/ckeditor5-footnotes/tsconfig.release.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"sourceMap": false,
|
||||
"declaration": true
|
||||
},
|
||||
"exclude": [
|
||||
"./tests/",
|
||||
"./src",
|
||||
"./sample/"
|
||||
]
|
||||
}
|
||||
15
_regroup/ckeditor5-footnotes/tsconfig.test.json
Normal file
15
_regroup/ckeditor5-footnotes/tsconfig.test.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": [
|
||||
"@types/mocha"
|
||||
],
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": [
|
||||
"./sample",
|
||||
"./src",
|
||||
"./tests",
|
||||
"./typings"
|
||||
]
|
||||
}
|
||||
7
_regroup/ckeditor5-footnotes/typings/ckeditor5-inspector.d.ts
vendored
Normal file
7
_regroup/ckeditor5-footnotes/typings/ckeditor5-inspector.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
declare module '@ckeditor/ckeditor5-inspector' {
|
||||
const inspector: {
|
||||
attach( editor: any ): void;
|
||||
};
|
||||
|
||||
export default inspector;
|
||||
}
|
||||
4
_regroup/ckeditor5-footnotes/typings/types.d.ts
vendored
Normal file
4
_regroup/ckeditor5-footnotes/typings/types.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
declare module '*.svg' {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
10107
_regroup/ckeditor5-footnotes/yarn.lock
Normal file
10107
_regroup/ckeditor5-footnotes/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user