Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions apps/desktop/src/renderer/components/MarkdownEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import {
wrapSelectionWithUrl,
continueMarkupKeymap,
editorLineKeymap,
listIndentKeymap,
} from '@dripnex/commands';
import {
createWikilinkHighlighter,
Expand All @@ -78,6 +79,7 @@ import {
} from '../utils/isMissingWikilink';
import { notebookStyleProps } from '../utils/notebookStyle';
import { createEditorTheme, markdownHighlighting, SCROLL_PAST_END_PADDING } from './editorTheme.js';
import { listMarkHighlighter } from './editor/listMarkDecorations';
import { fenceLanguageCompletions, slashCompletions } from './editor/slashCompletions';
import { UrlPastePicker } from './editor/UrlPastePicker';
import styles from './MarkdownEditor.module.css';
Expand Down Expand Up @@ -412,6 +414,7 @@ export const MarkdownEditor = forwardRef<MarkdownEditorHandle, MarkdownEditorPro
...searchKeymap,
...foldKeymap,
...historyKeymap,
listIndentKeymap,
indentWithTab,
]),

Expand All @@ -426,6 +429,7 @@ export const MarkdownEditor = forwardRef<MarkdownEditorHandle, MarkdownEditorPro

// Syntax highlighting
syntaxHighlighting(markdownHighlighting),
listMarkHighlighter,

// Wikilink [[note]] highlighting
wikilinkHighlightCompartment.of(createWikilinkHighlighter(null)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,30 @@
margin: 0.25em 0;
}

.markdown-preview li::marker {
color: var(--mde-preview-list-marker-color);
}

.markdown-preview li li::marker {
color: var(--mde-preview-list-marker-2-color);
}

.markdown-preview li li li::marker {
color: var(--mde-preview-list-marker-3-color);
}

.markdown-preview li li li li::marker {
color: var(--mde-preview-list-marker-color);
}

.markdown-preview li li li li li::marker {
color: var(--mde-preview-list-marker-2-color);
}

.markdown-preview li li li li li li::marker {
color: var(--mde-preview-list-marker-3-color);
}

/* Task lists (GFM) */
.markdown-preview ul:global(.contains-task-list) {
list-style: none;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { describe, it, expect } from 'vitest';
import { EditorState } from '@codemirror/state';
import { markdown } from '@codemirror/lang-markdown';
import { syntaxTree } from '@codemirror/language';
import { listMarkLevelClass } from '../listMarkDecorations';

describe('listMarkLevelClass', () => {
it('cycles every three nesting levels', () => {
expect(listMarkLevelClass(1)).toBe('md-list-mark');
expect(listMarkLevelClass(2)).toBe('md-list-mark md-list-mark-2');
expect(listMarkLevelClass(3)).toBe('md-list-mark md-list-mark-3');
expect(listMarkLevelClass(4)).toBe('md-list-mark');
expect(listMarkLevelClass(5)).toBe('md-list-mark md-list-mark-2');
});
});

describe('lezer markdown list marks', () => {
it('exposes ListMark nodes at each nesting level', () => {
const state = EditorState.create({
doc: '- a\n - b\n - c',
extensions: [markdown()],
});
const marks: { from: number; to: number; depth: number }[] = [];
syntaxTree(state).iterate({
enter(node) {
if (node.name !== 'ListMark') return;
let depth = 0;
let parent = node.node.parent;
while (parent) {
if (parent.name === 'ListItem') depth += 1;
parent = parent.parent;
}
marks.push({ from: node.from, to: node.to, depth });
},
});
expect(marks.map(m => m.depth)).toEqual([1, 2, 3]);
expect(state.doc.sliceString(marks[0]!.from, marks[0]!.to)).toMatch(/-/);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* Color Markdown list markers by nesting depth.
*
* Level 1 uses `--md-list-mark-color`; levels 2 and 3 use the matching
* tokens; deeper levels cycle. Only the marker is colored.
*/

import { syntaxTree } from '@codemirror/language';
import { RangeSetBuilder } from '@codemirror/state';
import { Decoration, ViewPlugin, type EditorView, type ViewUpdate } from '@codemirror/view';

export function listMarkLevelClass(depth: number): string {
const cycle = ((Math.max(1, depth) - 1) % 3) + 1;
if (cycle === 1) return 'md-list-mark';
return `md-list-mark md-list-mark-${cycle}`;
}

function listMarkDecorations(view: EditorView) {
const builder = new RangeSetBuilder<Decoration>();
for (const { from, to } of view.visibleRanges) {
syntaxTree(view.state).iterate({
from,
to,
enter(node) {
if (node.name !== 'ListMark') return;
let depth = 0;
let parent = node.node.parent;
while (parent) {
if (parent.name === 'ListItem') depth += 1;
parent = parent.parent;
}
if (depth < 1) depth = 1;
builder.add(node.from, node.to, Decoration.mark({ class: listMarkLevelClass(depth) }));
},
});
}
return builder.finish();
}

export const listMarkHighlighter = ViewPlugin.fromClass(
class {
decorations = Decoration.none;

constructor(view: EditorView) {
this.decorations = listMarkDecorations(view);
}

update(update: ViewUpdate) {
if (update.docChanged || update.viewportChanged) {
this.decorations = listMarkDecorations(update.view);
}
}
},
{ decorations: value => value.decorations }
);
12 changes: 10 additions & 2 deletions apps/desktop/src/renderer/components/editorTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@ export function createEditorTheme(fontSize: number, fontFamily: string, lineHeig
'.cm-line': {
padding: '0 4px',
},
'.md-list-mark': {
color: 'var(--md-list-mark-color, var(--cm-list))',
},
'.md-list-mark-2': {
color: 'var(--md-list-mark-2-color)',
},
'.md-list-mark-3': {
color: 'var(--md-list-mark-3-color)',
},
'.cm-nes-ghost': {
opacity: '0.45',
pointerEvents: 'none',
Expand Down Expand Up @@ -181,8 +190,7 @@ export const markdownHighlighting = HighlightStyle.define([
{ tag: tags.link, color: 'var(--cm-link)', textDecoration: 'underline' },
{ tag: tags.url, color: 'var(--cm-link)' },

// Lists
{ tag: tags.list, color: 'var(--cm-list)' },
// Lists — marker color comes from listMarkDecorations (.md-list-mark*)

// Quotes
{
Expand Down
17 changes: 17 additions & 0 deletions apps/desktop/src/renderer/ui/tokens/tokens.css
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,15 @@
--chrome-header-padding: 8px 10px;
--chrome-traffic-inset: 72px;

/* ── Editor list marks (Inkdrop --md-list-mark-*) ─────────────────────────── */
--cm-list: var(--accent);
--md-list-mark-color: var(--accent);
--md-list-mark-2-color: #60a5fa;
--md-list-mark-3-color: #c4b5fd;
--mde-preview-list-marker-color: var(--text-primary);
--mde-preview-list-marker-2-color: var(--text-primary);
--mde-preview-list-marker-3-color: var(--text-primary);

/* ── Radii ────────────────────────────────────────────────────────────────── */
--radius-sm: 4px;
--radius-md: 6px;
Expand Down Expand Up @@ -209,6 +218,14 @@
--status-completed: #16a34a;
--status-dropped: #e11d48;

--cm-list: var(--accent);
--md-list-mark-color: var(--accent);
--md-list-mark-2-color: #2563eb;
--md-list-mark-3-color: #7c3aed;
--mde-preview-list-marker-color: var(--text-primary);
--mde-preview-list-marker-2-color: var(--text-primary);
--mde-preview-list-marker-3-color: var(--text-primary);

--glass-bg: rgba(247, 246, 243, 0.88);
--glass-bg-fallback: rgba(247, 246, 243, 0.96);
--glass-bg-menu: rgba(255, 255, 255, 0.94);
Expand Down
10 changes: 10 additions & 0 deletions packages/commands/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ export {
editorLineKeymap,
} from './markdown/lineCommands.js';

export {
parseListLine,
formatListLine,
indentList,
indentListItem,
dedentListItem,
listIndentKeymap,
} from './markdown/listIndent.js';
export type { ParsedListLine, ListKind } from './markdown/listIndent.js';

export {
SLASH_ITEMS,
FENCE_LANGUAGES,
Expand Down
Loading
Loading