feat: the debug dump reports the colour actually painted
The colour section only read the marks, so a colour no mark carries — a decoration's, a stylesheet rule's, the page's own — was invisible in the dump. `painted` adds the computed colour per rendered run with the element that declares it, the page colour variables read at the editor's own element, and the document stylesheet's colour rules.
This commit is contained in:
1 parent
c771193c83
commit
4e343f2014
2 files changed
+93
-1
No files matched your search
@@ -30,6 +30,23 @@ export type ColorDebugSnapshot = {
|
||||
total: number;
|
||||
sample: Array<{ dataColor: string | null; inlineStyle: string | null; text: string }>;
|
||||
};
|
||||
/** What the browser paints — the marks above only say what it was asked to. */
|
||||
painted: {
|
||||
/** The custom properties a run of no colour of its own resolves through. */
|
||||
vars: Record<string, string>;
|
||||
/** One entry per colour actually rendered, with where that colour comes from. */
|
||||
colors: Record<string, { chars: number; sample: string; block: string; source: PaintSource | null }>;
|
||||
/** The colour-carrying rules of the document stylesheet (styles/styleSheet.ts). */
|
||||
styleRules: string[];
|
||||
};
|
||||
};
|
||||
|
||||
/** The nearest element declaring a colour, i.e. the channel a painted colour arrives on. */
|
||||
type PaintSource = {
|
||||
tag: string;
|
||||
className: string;
|
||||
dataColor: string | null;
|
||||
inlineColor: string | null;
|
||||
};
|
||||
|
||||
const TEXT_PREVIEW_LIMIT = 80;
|
||||
@@ -55,6 +72,67 @@ function getTextStyleAttr(marks: readonly Mark[], attr: string): string | null {
|
||||
return typeof v === 'string' && v ? v : null;
|
||||
}
|
||||
|
||||
const PAINT_VARS = ['--color-page-text', '--color-page-bg', '--color-text', '--font-color'];
|
||||
const INLINE_COLOR = /(?:^|;)\s*color\s*:\s*([^;]+)/;
|
||||
const STYLE_RULE_LIMIT = 40;
|
||||
|
||||
// Walking up to the editor root: the first element declaring a colour is the channel the
|
||||
// run is painted through. None of them = the page's own colour (editor.css).
|
||||
function paintSource(from: HTMLElement, root: HTMLElement): PaintSource | null {
|
||||
for (let e: HTMLElement | null = from; e; e = e.parentElement) {
|
||||
const inline = INLINE_COLOR.exec(e.getAttribute('style') ?? '');
|
||||
if (inline || e.hasAttribute('data-color')) {
|
||||
return {
|
||||
tag: e.tagName.toLowerCase(),
|
||||
className: e.getAttribute('class') ?? '',
|
||||
dataColor: e.getAttribute('data-color'),
|
||||
inlineColor: inline ? inline[1].trim() : null,
|
||||
};
|
||||
}
|
||||
if (e === root) break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// The block a run sits in, named as the CSS would address it: its tag and the named
|
||||
// paragraph style whose rule reaches it.
|
||||
function blockOf(from: HTMLElement, root: HTMLElement): string {
|
||||
const block = from.closest('p, h1, h2, h3, h4, h5, h6, h7, h8, h9, h10, li, td, th') ?? from;
|
||||
const styled = from.closest('[data-style]');
|
||||
const name = styled && root.contains(styled) ? styled.getAttribute('data-style') : null;
|
||||
return name ? `${block.tagName.toLowerCase()}[${name}]` : block.tagName.toLowerCase();
|
||||
}
|
||||
|
||||
function paintedColors(root: HTMLElement): ColorDebugSnapshot['painted']['colors'] {
|
||||
const out: ColorDebugSnapshot['painted']['colors'] = {};
|
||||
if (typeof document === 'undefined' || typeof getComputedStyle !== 'function') return out;
|
||||
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
||||
for (let n = walker.nextNode(); n; n = walker.nextNode()) {
|
||||
const text = n.textContent ?? '';
|
||||
const el = n.parentElement;
|
||||
if (!text.trim() || !el) continue;
|
||||
const color = getComputedStyle(el).color;
|
||||
if (!out[color]) {
|
||||
out[color] = { chars: 0, sample: truncate(text.trim(), 40), block: blockOf(el, root), source: paintSource(el, root) };
|
||||
}
|
||||
out[color].chars += text.length;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function documentStyleColorRules(): string[] {
|
||||
const sheet = (document.getElementById('document-styles') as HTMLStyleElement | null)?.sheet;
|
||||
if (!sheet) return [];
|
||||
try {
|
||||
return Array.from(sheet.cssRules)
|
||||
.map((r) => r.cssText)
|
||||
.filter((t) => INLINE_COLOR.test(t) || t.includes('{color:') || t.includes(' color:'))
|
||||
.slice(0, STYLE_RULE_LIMIT);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export function getColorDebug(editor: Editor): ColorDebugSnapshot {
|
||||
const { state } = editor;
|
||||
const { doc, selection, storedMarks } = state;
|
||||
@@ -115,6 +193,15 @@ export function getColorDebug(editor: Editor): ColorDebugSnapshot {
|
||||
? document.documentElement.getAttribute('data-theme')
|
||||
: null;
|
||||
|
||||
// Read on the editor's own element: a page decoration or a theme override lands
|
||||
// between :root and the text, and only this end of the cascade shows it.
|
||||
const paintVars: Record<string, string> = {};
|
||||
if (root && typeof getComputedStyle === 'function') {
|
||||
const cs = getComputedStyle(root as HTMLElement);
|
||||
for (const v of PAINT_VARS) paintVars[v] = cs.getPropertyValue(v).trim();
|
||||
paintVars.color = cs.color;
|
||||
}
|
||||
|
||||
return {
|
||||
timestamp: new Date().toISOString(),
|
||||
theme: { dataTheme },
|
||||
@@ -133,5 +220,10 @@ export function getColorDebug(editor: Editor): ColorDebugSnapshot {
|
||||
total: domSpansTotal,
|
||||
sample: domSpansSample,
|
||||
},
|
||||
painted: {
|
||||
vars: paintVars,
|
||||
colors: root ? paintedColors(root as HTMLElement) : {},
|
||||
styleRules: typeof document !== 'undefined' ? documentStyleColorRules() : [],
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user