dx: add color information to debug json

This commit is contained in:
Steffen Becker committed 2026-05-30 00:31:07 +02:00
1 parent 1f09aa5c17
commit 25758b1c32
2 files changed
+139

No files matched your search

+2
View File
@@ -5,6 +5,7 @@
import ToolbarExpanded from './lib/editor/ToolbarExpanded.svelte';
import { exportToOdt } from './lib/export/odt';
import { getPageBreakDebug } from './lib/editor/pageBreaks';
import { getColorDebug } from './lib/editor/colorDebug';
import { loadTheme, saveTheme, applyTheme, loadToolbarExpanded, saveToolbarExpanded, loadFormattingMarks, saveFormattingMarks, type ThemeMode } from './lib/storage/theme';
let editor: Editor | null = $state(null);
@@ -63,6 +64,7 @@
zoom,
doc: editor.getJSON(),
pageBreaks: snapshot,
colors: getColorDebug(editor),
};
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
+137
View File
@@ -0,0 +1,137 @@
import type { Editor } from '@tiptap/core';
import type { Mark } from '@tiptap/pm/model';
type MarkInfo = { type: string; attrs: Record<string, unknown> };
export type ColorDebugSnapshot = {
timestamp: string;
theme: {
dataTheme: string | null;
};
selection: {
from: number;
to: number;
empty: boolean;
storedMarks: MarkInfo[] | null;
headMarks: MarkInfo[];
marksBefore: MarkInfo[];
marksAfter: MarkInfo[];
};
textRuns: Array<{
from: number;
to: number;
text: string;
color: string | null;
fontFamily: string | null;
fontSize: string | null;
}>;
documentColors: Record<string, { count: number; totalChars: number; sample: string }>;
domSpans: {
total: number;
sample: Array<{ dataColor: string | null; inlineStyle: string | null; text: string }>;
};
};
const TEXT_PREVIEW_LIMIT = 80;
const DOM_SAMPLE_LIMIT = 20;
function truncate(text: string, limit: number = TEXT_PREVIEW_LIMIT): string {
if (text.length <= limit) return text;
return text.slice(0, limit) + '…';
}
function serializeMark(mark: Mark): MarkInfo {
return { type: mark.type.name, attrs: { ...mark.attrs } };
}
function serializeMarks(marks: readonly Mark[] | null | undefined): MarkInfo[] {
if (!marks) return [];
return marks.map(serializeMark);
}
function getTextStyleAttr(marks: readonly Mark[], attr: string): string | null {
const m = marks.find((mk) => mk.type.name === 'textStyle');
const v = m?.attrs?.[attr];
return typeof v === 'string' && v ? v : null;
}
export function getColorDebug(editor: Editor): ColorDebugSnapshot {
const { state } = editor;
const { doc, selection, storedMarks } = state;
const { from, to, empty } = selection;
const resolvedFrom = doc.resolve(from);
const resolvedTo = doc.resolve(to);
const marksBefore = serializeMarks(resolvedFrom.nodeBefore?.marks);
const marksAfter = serializeMarks(resolvedTo.nodeAfter?.marks);
const textRuns: ColorDebugSnapshot['textRuns'] = [];
const documentColors: ColorDebugSnapshot['documentColors'] = {};
doc.descendants((node, pos) => {
if (!node.isText) return;
const text = node.text ?? '';
const color = getTextStyleAttr(node.marks, 'color');
const fontFamily = getTextStyleAttr(node.marks, 'fontFamily');
const fontSize = getTextStyleAttr(node.marks, 'fontSize');
const runFrom = pos;
const runTo = pos + node.nodeSize;
textRuns.push({
from: runFrom,
to: runTo,
text: truncate(text),
color,
fontFamily,
fontSize,
});
const key = color ?? '(none)';
if (!documentColors[key]) {
documentColors[key] = { count: 0, totalChars: 0, sample: truncate(text, 40) };
}
documentColors[key].count += 1;
documentColors[key].totalChars += text.length;
});
const domSpansSample: ColorDebugSnapshot['domSpans']['sample'] = [];
let domSpansTotal = 0;
const root = editor.view.dom;
if (root && typeof root.querySelectorAll === 'function') {
const spans = root.querySelectorAll('[data-color]');
domSpansTotal = spans.length;
for (let i = 0; i < Math.min(spans.length, DOM_SAMPLE_LIMIT); i++) {
const el = spans[i] as HTMLElement;
domSpansSample.push({
dataColor: el.getAttribute('data-color'),
inlineStyle: el.getAttribute('style'),
text: truncate(el.textContent ?? ''),
});
}
}
const dataTheme =
typeof document !== 'undefined'
? document.documentElement.getAttribute('data-theme')
: null;
return {
timestamp: new Date().toISOString(),
theme: { dataTheme },
selection: {
from,
to,
empty,
storedMarks: storedMarks ? serializeMarks(storedMarks) : null,
headMarks: serializeMarks(selection.$head.marks()),
marksBefore,
marksAfter,
},
textRuns,
documentColors,
domSpans: {
total: domSpansTotal,
sample: domSpansSample,
},
};
}