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 {
setAttribute(name: string, value: string, el: TextEditorElement);
createPositionAt(el: TextEditorElement, opt?: "end");
setAttribute(name: string, value: string, el: CKNode);
createPositionAt(el: CKNode, opt?: "end" | number);
setSelection(pos: number, pos?: number);
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 {
previousSibling?: TextNode;
@ -308,6 +316,37 @@ declare global {
offset: number;
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 {
create(el: HTMLElement, config: {
removePlugins?: string[];
@ -321,10 +360,22 @@ declare global {
model: {
document: {
on(event: string, cb: () => void);
getRoot(): TextEditorElement;
getRoot(): CKNode;
registerPostFixer(callback: (writer: Writer) => boolean);
selection: {
getFirstPosition(): undefined | TextPosition;
getLastPosition(): undefined | TextPosition;
};
differ: {
getChanges(): {
type: string;
name: string;
position: {
nodeAfter: CKNode;
parent: CKNode;
toJSON(): Object;
}
}[];
}
},
insertContent(modelFragment: any, selection: any);
@ -340,7 +391,7 @@ declare global {
}) => void, opts?: {
priority: "high"
});
getRoot(): TextEditorElement
getRoot(): CKNode
},
domRoots: {
values: () => {
@ -363,6 +414,16 @@ declare global {
};
toModel(viewFeragment: any);
},
conversion: {
for(filter: string): {
markerToHighlight(data: {
model: string;
view: (data: {
markerName: string;
}) => void;
})
}
}
getData(): string;
setData(data: string): void;
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 { isSyntaxHighlightEnabled } from "../../../services/syntax_highlight.js";
export async function initSyntaxHighlighting(editor) {
export async function initSyntaxHighlighting(editor: TextEditor) {
if (!isSyntaxHighlightEnabled) {
return;
}
@ -25,39 +25,38 @@ const HIGHLIGHT_MAX_BLOCK_COUNT = 500;
const tag = "SyntaxHighlightWidget";
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")) {
warn = console.warn.bind(console, tag + ": ");
}
let info = function () {};
let info = function (...args: unknown[]) {};
if (debugLevel >= debugLevels.indexOf("info")) {
info = console.info.bind(console, tag + ": ");
}
let log = function () {};
let log = function (...args: unknown[]) {};
if (debugLevel >= debugLevels.indexOf("log")) {
log = console.log.bind(console, tag + ": ");
}
let dbg = function () {};
let dbg = function (...args: unknown[]) {};
if (debugLevel >= debugLevels.indexOf("debug")) {
dbg = console.debug.bind(console, tag + ": ");
}
function assert(e, msg) {
function assert(e: boolean, msg?: string) {
console.assert(e, tag + ": " + msg);
}
// TODO: Should this be scoped to note?
let markerCounter = 0;
function initTextEditor(textEditor) {
function initTextEditor(textEditor: TextEditor) {
log("initTextEditor");
let widget = this;
const document = textEditor.model.document;
// Create a conversion from model to view that converts
@ -100,7 +99,7 @@ function initTextEditor(textEditor) {
// See
// https://github.com/ckeditor/ckeditor5/blob/b53d2a4b49679b072f4ae781ac094e7e831cfb14/packages/ckeditor5-block-quote/src/blockquoteediting.js#L54
const changes = document.differ.getChanges();
let dirtyCodeBlocks = new Set();
let dirtyCodeBlocks = new Set<CKNode>();
for (const change of changes) {
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
* 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()));
const model = codeBlock.root.document.model;
@ -291,16 +290,16 @@ function highlightCodeBlock(codeBlock, writer) {
iHtml = html.indexOf(">", iHtml) + 1;
// 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 });
} else if (html[iHtml] == "<" && html[iHtml + 1] == "/") {
// Done with this span, pop the span and mark the range
iHtml = html.indexOf(">", iHtml + 1) + 1;
let stackTop = spanStack.pop();
let posStart = stackTop.posStart;
let className = stackTop.className;
let posEnd = writer.createPositionAt(codeBlock, child.startOffset + iChildText);
let posStart = stackTop?.posStart;
let className = stackTop?.className;
let posEnd = writer.createPositionAt(codeBlock, (child?.startOffset ?? 0) + iChildText);
let range = writer.createRange(posStart, posEnd);
let markerName = "hljs:" + className + ":" + markerCounter;
// Use an incrementing number for the uniqueId, random of