chore(client/ts): port syntax_highlight

This commit is contained in:
Elian Doran 2025-03-21 15:50:53 +02:00
parent 5b82b750dc
commit ebbf29b1a5
No known key found for this signature in database
2 changed files with 80 additions and 20 deletions

View File

@ -285,12 +285,20 @@ declare global {
}); });
} }
type TextEditorElement = {}; interface Range {
toJSON(): object;
}
interface Writer { interface Writer {
setAttribute(name: string, value: string, el: TextEditorElement); setAttribute(name: string, value: string, el: CKNode);
createPositionAt(el: TextEditorElement, opt?: "end"); createPositionAt(el: CKNode, opt?: "end" | number);
setSelection(pos: number, pos?: number); setSelection(pos: number, pos?: number);
insertText(text: string, opts: Record<string, unknown> | undefined, position?: TextPosition); insertText(text: string, opts: Record<string, unknown> | undefined, position?: TextPosition);
addMarker(name: string, opts: {
range: Range;
usingOperation: boolean;
});
removeMarker(name: string);
createRange(start: number, end: number): Range;
} }
interface TextNode { interface TextNode {
previousSibling?: TextNode; previousSibling?: TextNode;
@ -308,6 +316,37 @@ declare global {
offset: number; offset: number;
compareWith(pos: TextPosition): string; compareWith(pos: TextPosition): string;
} }
interface TextRange {
}
interface Marker {
name: string;
}
interface CKNode {
name: string;
childCount: number;
isEmpty: boolean;
toJSON(): object;
is(type: string, name?: string);
getAttribute(name: string): string;
getChild(index: number): CKNode;
data: string;
startOffset: number;
root: {
document: {
model: {
createRangeIn(el: CKNode): TextRange;
markers: {
getMarkersIntersectingRange(range: TextRange): Marker[];
}
}
}
};
}
interface TextEditor { interface TextEditor {
create(el: HTMLElement, config: { create(el: HTMLElement, config: {
removePlugins?: string[]; removePlugins?: string[];
@ -321,10 +360,22 @@ declare global {
model: { model: {
document: { document: {
on(event: string, cb: () => void); on(event: string, cb: () => void);
getRoot(): TextEditorElement; getRoot(): CKNode;
registerPostFixer(callback: (writer: Writer) => boolean);
selection: { selection: {
getFirstPosition(): undefined | TextPosition; getFirstPosition(): undefined | TextPosition;
getLastPosition(): undefined | TextPosition; getLastPosition(): undefined | TextPosition;
};
differ: {
getChanges(): {
type: string;
name: string;
position: {
nodeAfter: CKNode;
parent: CKNode;
toJSON(): Object;
}
}[];
} }
}, },
insertContent(modelFragment: any, selection: any); insertContent(modelFragment: any, selection: any);
@ -340,7 +391,7 @@ declare global {
}) => void, opts?: { }) => void, opts?: {
priority: "high" priority: "high"
}); });
getRoot(): TextEditorElement getRoot(): CKNode
}, },
domRoots: { domRoots: {
values: () => { values: () => {
@ -363,6 +414,16 @@ declare global {
}; };
toModel(viewFeragment: any); toModel(viewFeragment: any);
}, },
conversion: {
for(filter: string): {
markerToHighlight(data: {
model: string;
view: (data: {
markerName: string;
}) => void;
})
}
}
getData(): string; getData(): string;
setData(data: string): void; setData(data: string): void;
getSelectedHtml(): string; getSelectedHtml(): string;

View File

@ -12,7 +12,7 @@ import library_loader from "../../../services/library_loader.js";
import mime_types from "../../../services/mime_types.js"; import mime_types from "../../../services/mime_types.js";
import { isSyntaxHighlightEnabled } from "../../../services/syntax_highlight.js"; import { isSyntaxHighlightEnabled } from "../../../services/syntax_highlight.js";
export async function initSyntaxHighlighting(editor) { export async function initSyntaxHighlighting(editor: TextEditor) {
if (!isSyntaxHighlightEnabled) { if (!isSyntaxHighlightEnabled) {
return; return;
} }
@ -25,39 +25,38 @@ const HIGHLIGHT_MAX_BLOCK_COUNT = 500;
const tag = "SyntaxHighlightWidget"; const tag = "SyntaxHighlightWidget";
const debugLevels = ["error", "warn", "info", "log", "debug"]; const debugLevels = ["error", "warn", "info", "log", "debug"];
const debugLevel = "debug"; const debugLevel = debugLevels.indexOf("debug");
let warn = function () {}; let warn = function (...args: unknown[]) {};
if (debugLevel >= debugLevels.indexOf("warn")) { if (debugLevel >= debugLevels.indexOf("warn")) {
warn = console.warn.bind(console, tag + ": "); warn = console.warn.bind(console, tag + ": ");
} }
let info = function () {}; let info = function (...args: unknown[]) {};
if (debugLevel >= debugLevels.indexOf("info")) { if (debugLevel >= debugLevels.indexOf("info")) {
info = console.info.bind(console, tag + ": "); info = console.info.bind(console, tag + ": ");
} }
let log = function () {}; let log = function (...args: unknown[]) {};
if (debugLevel >= debugLevels.indexOf("log")) { if (debugLevel >= debugLevels.indexOf("log")) {
log = console.log.bind(console, tag + ": "); log = console.log.bind(console, tag + ": ");
} }
let dbg = function () {}; let dbg = function (...args: unknown[]) {};
if (debugLevel >= debugLevels.indexOf("debug")) { if (debugLevel >= debugLevels.indexOf("debug")) {
dbg = console.debug.bind(console, tag + ": "); dbg = console.debug.bind(console, tag + ": ");
} }
function assert(e, msg) { function assert(e: boolean, msg?: string) {
console.assert(e, tag + ": " + msg); console.assert(e, tag + ": " + msg);
} }
// TODO: Should this be scoped to note? // TODO: Should this be scoped to note?
let markerCounter = 0; let markerCounter = 0;
function initTextEditor(textEditor) { function initTextEditor(textEditor: TextEditor) {
log("initTextEditor"); log("initTextEditor");
let widget = this;
const document = textEditor.model.document; const document = textEditor.model.document;
// Create a conversion from model to view that converts // Create a conversion from model to view that converts
@ -100,7 +99,7 @@ function initTextEditor(textEditor) {
// See // See
// https://github.com/ckeditor/ckeditor5/blob/b53d2a4b49679b072f4ae781ac094e7e831cfb14/packages/ckeditor5-block-quote/src/blockquoteediting.js#L54 // https://github.com/ckeditor/ckeditor5/blob/b53d2a4b49679b072f4ae781ac094e7e831cfb14/packages/ckeditor5-block-quote/src/blockquoteediting.js#L54
const changes = document.differ.getChanges(); const changes = document.differ.getChanges();
let dirtyCodeBlocks = new Set(); let dirtyCodeBlocks = new Set<CKNode>();
for (const change of changes) { for (const change of changes) {
dbg("change " + JSON.stringify(change)); dbg("change " + JSON.stringify(change));
@ -151,7 +150,7 @@ function initTextEditor(textEditor) {
* the formatting would be stored with the note and it would need a * the formatting would be stored with the note and it would need a
* way to remove that formatting when editing back the note. * way to remove that formatting when editing back the note.
*/ */
function highlightCodeBlock(codeBlock, writer) { function highlightCodeBlock(codeBlock: CKNode, writer: Writer) {
log("highlighting codeblock " + JSON.stringify(codeBlock.toJSON())); log("highlighting codeblock " + JSON.stringify(codeBlock.toJSON()));
const model = codeBlock.root.document.model; const model = codeBlock.root.document.model;
@ -291,16 +290,16 @@ function highlightCodeBlock(codeBlock, writer) {
iHtml = html.indexOf(">", iHtml) + 1; iHtml = html.indexOf(">", iHtml) + 1;
// push the span // push the span
let posStart = writer.createPositionAt(codeBlock, child.startOffset + iChildText); let posStart = writer.createPositionAt(codeBlock, (child?.startOffset ?? 0) + iChildText);
spanStack.push({ className: className, posStart: posStart }); spanStack.push({ className: className, posStart: posStart });
} else if (html[iHtml] == "<" && html[iHtml + 1] == "/") { } else if (html[iHtml] == "<" && html[iHtml + 1] == "/") {
// Done with this span, pop the span and mark the range // Done with this span, pop the span and mark the range
iHtml = html.indexOf(">", iHtml + 1) + 1; iHtml = html.indexOf(">", iHtml + 1) + 1;
let stackTop = spanStack.pop(); let stackTop = spanStack.pop();
let posStart = stackTop.posStart; let posStart = stackTop?.posStart;
let className = stackTop.className; let className = stackTop?.className;
let posEnd = writer.createPositionAt(codeBlock, child.startOffset + iChildText); let posEnd = writer.createPositionAt(codeBlock, (child?.startOffset ?? 0) + iChildText);
let range = writer.createRange(posStart, posEnd); let range = writer.createRange(posStart, posEnd);
let markerName = "hljs:" + className + ":" + markerCounter; let markerName = "hljs:" + className + ":" + markerCounter;
// Use an incrementing number for the uniqueId, random of // Use an incrementing number for the uniqueId, random of