code: path.join(__dirname, '../../resources/js/code/index.mjs'),
'legacy-modes': path.join(__dirname, '../../resources/js/code/legacy-modes.mjs'),
markdown: path.join(__dirname, '../../resources/js/markdown/index.mjs'),
- wysiwyg: path.join(__dirname, '../../resources/js/wysiwyg/index.mjs'),
+ wysiwyg: path.join(__dirname, '../../resources/js/wysiwyg/index.ts'),
};
// Locate our output directory
"eslint-plugin-import": "^2.29.0",
"livereload": "^0.9.3",
"npm-run-all": "^4.1.5",
- "sass": "^1.69.5"
+ "sass": "^1.69.5",
+ "typescript": "^5.4.5"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/typescript": {
+ "version": "5.4.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
+ "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
"node_modules/uc.micro": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
"eslint-plugin-import": "^2.29.0",
"livereload": "^0.9.3",
"npm-run-all": "^4.1.5",
- "sass": "^1.69.5"
+ "sass": "^1.69.5",
+ "typescript": "^5.4.5"
},
"dependencies": {
"@codemirror/commands": "^6.3.2",
},
"extends": "airbnb-base",
"ignorePatterns": [
- "resources/**/*-stub.js"
+ "resources/**/*-stub.js",
+ "resources/**/*.ts"
],
"overrides": [],
"parserOptions": {
$getSelection,
COMMAND_PRIORITY_LOW,
createCommand,
- createEditor
+ createEditor, CreateEditorArgs,
} from 'lexical';
import {createEmptyHistoryState, registerHistory} from '@lexical/history';
import {registerRichText} from '@lexical/rich-text';
import {$getNearestBlockElementAncestorOrThrow, mergeRegister} from '@lexical/utils';
import {$generateNodesFromDOM} from '@lexical/html';
-import {getNodesForPageEditor} from "./nodes/index.js";
-import {$createCalloutNode, $isCalloutNode} from "./nodes/callout.js";
-import {$setBlocksType} from "@lexical/selection";
+import {$setBlocksType} from '@lexical/selection';
+import {getNodesForPageEditor} from './nodes';
+import {$createCalloutNode, $isCalloutNode, CalloutCategory} from './nodes/callout';
-export function createPageEditorInstance(editArea) {
- const config = {
+export function createPageEditorInstance(editArea: HTMLElement) {
+ const config: CreateEditorArgs = {
namespace: 'BookStackPageEditor',
nodes: getNodesForPageEditor(),
onError: console.error,
// Example of creating, registering and using a custom command
const SET_BLOCK_CALLOUT_COMMAND = createCommand();
- editor.registerCommand(SET_BLOCK_CALLOUT_COMMAND, (category = 'info') => {
+ editor.registerCommand(SET_BLOCK_CALLOUT_COMMAND, (category: CalloutCategory = 'info') => {
const selection = $getSelection();
const blockElement = $getNearestBlockElementAncestorOrThrow(selection.getNodes()[0]);
if ($isCalloutNode(blockElement)) {
button.addEventListener('click', event => {
editor.dispatchCommand(SET_BLOCK_CALLOUT_COMMAND, 'info');
});
-}
\ No newline at end of file
+}
-import {$createParagraphNode, ElementNode} from 'lexical';
+import {
+ $createParagraphNode,
+ DOMConversion,
+ DOMConversionMap, DOMConversionOutput,
+ ElementNode,
+ LexicalEditor,
+ LexicalNode,
+ ParagraphNode, SerializedElementNode, Spread
+} from 'lexical';
+import type {EditorConfig} from "lexical/LexicalEditor";
+import type {RangeSelection} from "lexical/LexicalSelection";
+
+export type CalloutCategory = 'info' | 'danger' | 'warning' | 'success';
+
+export type SerializedCalloutNode = Spread<{
+ category: CalloutCategory;
+}, SerializedElementNode>
export class Callout extends ElementNode {
- __category = 'info';
+ __category: CalloutCategory = 'info';
static getType() {
return 'callout';
}
- static clone(node) {
+ static clone(node: Callout) {
return new Callout(node.__category, node.__key);
}
- constructor(category, key) {
+ constructor(category: CalloutCategory, key?: string) {
super(key);
this.__category = category;
}
- createDOM(_config, _editor) {
+ createDOM(_config: EditorConfig, _editor: LexicalEditor) {
const element = document.createElement('p');
element.classList.add('callout', this.__category || '');
return element;
}
- updateDOM(prevNode, dom) {
+ updateDOM(prevNode: unknown, dom: HTMLElement) {
// Returning false tells Lexical that this node does not need its
// DOM element replacing with a new copy from createDOM.
return false;
}
- insertNewAfter(selection, restoreSelection) {
+ insertNewAfter(selection: RangeSelection, restoreSelection?: boolean): Callout|ParagraphNode {
const anchorOffset = selection ? selection.anchor.offset : 0;
const newElement = anchorOffset === this.getTextContentSize() || !selection
? $createParagraphNode() : $createCalloutNode(this.__category);
return newElement;
}
- static importDOM() {
+ static importDOM(): DOMConversionMap|null {
return {
- p: node => {
+ p(node: HTMLElement): DOMConversion|null {
if (node.classList.contains('callout')) {
return {
- conversion: element => {
- let category = 'info';
- const categories = ['info', 'success', 'warning', 'danger'];
+ conversion: (element: HTMLElement): DOMConversionOutput|null => {
+ let category: CalloutCategory = 'info';
+ const categories: CalloutCategory[] = ['info', 'success', 'warning', 'danger'];
for (const c of categories) {
if (element.classList.contains(c)) {
};
}
- exportJSON() {
+ exportJSON(): SerializedCalloutNode {
return {
...super.exportJSON(),
type: 'callout',
};
}
- static importJSON(serializedNode) {
+ static importJSON(serializedNode: SerializedCalloutNode): Callout {
return $createCalloutNode(serializedNode.category);
}
}
-export function $createCalloutNode(category = 'info') {
+export function $createCalloutNode(category: CalloutCategory = 'info') {
return new Callout(category);
}
-export function $isCalloutNode(node) {
+export function $isCalloutNode(node: LexicalNode | null | undefined) {
return node instanceof Callout;
}
import {HeadingNode, QuoteNode} from '@lexical/rich-text';
import {Callout} from './callout';
+import {KlassConstructor, LexicalNode} from "lexical";
/**
* Load the nodes for lexical.
- * @returns {LexicalNode[]}
*/
-export function getNodesForPageEditor() {
+export function getNodesForPageEditor(): KlassConstructor<typeof LexicalNode>[] {
return [
Callout,
HeadingNode,