diff --git a/.vitepress/theme/EditPage.vue b/.vitepress/theme/EditPage.vue
index 8f811bf..4612422 100644
--- a/.vitepress/theme/EditPage.vue
+++ b/.vitepress/theme/EditPage.vue
@@ -887,7 +887,10 @@ function insertOutline() {
@@ -1723,6 +1726,17 @@ function insertOutline() {
background: var(--vp-c-bg-soft);
}
+.nb-edit-check-message {
+ min-width: 0;
+ overflow-wrap: anywhere;
+}
+
+.nb-edit-check-message small {
+ display: block;
+ white-space: pre-wrap;
+ color: var(--vp-c-text-3);
+}
+
.nb-edit-check-place,
.nb-edit-check-rule {
flex: none;
diff --git a/.vitepress/theme/editor/codemirror.ts b/.vitepress/theme/editor/codemirror.ts
index 3fc5b48..3a0ad3c 100644
--- a/.vitepress/theme/editor/codemirror.ts
+++ b/.vitepress/theme/editor/codemirror.ts
@@ -5,7 +5,7 @@ import { defaultKeymap, history, historyKeymap, indentLess, indentMore } from '@
import { markdown, markdownLanguage } from '@codemirror/lang-markdown'
import { syntaxTree } from '@codemirror/language'
import { Compartment, EditorSelection, EditorState, RangeSet, StateEffect, StateField } from '@codemirror/state'
-import { Decoration, EditorView, keymap, placeholder, ViewPlugin } from '@codemirror/view'
+import { Decoration, EditorView, keymap, placeholder, ViewPlugin, WidgetType } from '@codemirror/view'
const HEADING = /^ATXHeading([1-6])$/
const MARK_CLASS: Record = {
@@ -110,12 +110,46 @@ function styleDoc(state: EditorState): DecorationSet {
return RangeSet.of(ranges, true)
}
+class IssueMessage extends WidgetType {
+ constructor(readonly issues: MarkdownIssue[]) {
+ super()
+ }
+
+ eq(other: IssueMessage): boolean {
+ return JSON.stringify(this.issues) === JSON.stringify(other.issues)
+ }
+
+ toDOM(): HTMLElement {
+ const container = document.createElement('div')
+ container.className = 'cm-nb-issue-messages'
+ for (const issue of this.issues) {
+ const message = document.createElement('div')
+ message.className = 'cm-nb-issue-message'
+ const summary = document.createElement('div')
+ summary.textContent = `${issue.rule} · ${issue.message}`
+ message.append(summary)
+ if (issue.detail && issue.detail !== issue.message) {
+ const detail = document.createElement('div')
+ detail.className = 'cm-nb-issue-detail'
+ detail.textContent = issue.detail
+ message.append(detail)
+ }
+ container.append(message)
+ }
+ return container
+ }
+}
+
function issueDecorations(state: EditorState, issues: MarkdownIssue[]): DecorationSet {
const ranges: Range[] = []
+ const byLine = new Map()
for (const problem of issues) {
if (!problem.line || problem.line > state.doc.lines)
continue
const line = state.doc.line(problem.line)
+ const messages = byLine.get(problem.line) ?? []
+ messages.push(problem)
+ byLine.set(problem.line, messages)
ranges.push(Decoration.line({ class: 'cm-nb-issue-line' }).range(line.from))
const start = Math.min(line.to, line.from + Math.max(0, problem.column - 1))
if (start < line.to)
@@ -123,6 +157,13 @@ function issueDecorations(state: EditorState, issues: MarkdownIssue[]): Decorati
else if (line.length)
ranges.push(Decoration.mark({ class: 'cm-nb-issue-text' }).range(line.to - 1, line.to))
}
+ for (const [number, messages] of byLine) {
+ ranges.push(Decoration.widget({
+ widget: new IssueMessage(messages),
+ block: true,
+ side: 1,
+ }).range(state.doc.line(number).to))
+ }
return RangeSet.of(ranges, true)
}
@@ -202,6 +243,25 @@ const theme = EditorView.theme({
'.cm-changedText': { backgroundColor: 'color-mix(in srgb, var(--vp-c-brand-1) 18%, transparent)' },
'.cm-nb-issue-line': { backgroundColor: 'color-mix(in srgb, var(--vp-c-danger-1) 7%, transparent)' },
'.cm-nb-issue-text': { textDecoration: 'underline wavy var(--vp-c-danger-1)', textUnderlineOffset: '3px' },
+ '.cm-nb-issue-messages': {
+ maxWidth: 'var(--nb-measure)',
+ margin: '4px auto 10px',
+ padding: '0 21px',
+ boxSizing: 'border-box',
+ color: 'var(--vp-c-danger-1)',
+ fontFamily: 'var(--vp-font-family-base)',
+ fontSize: '13px',
+ lineHeight: '1.6',
+ whiteSpace: 'pre-wrap',
+ overflowWrap: 'anywhere',
+ },
+ '.cm-nb-issue-message': {
+ padding: '5px 10px',
+ borderLeft: '2px solid var(--vp-c-danger-1)',
+ backgroundColor: 'color-mix(in srgb, var(--vp-c-danger-1) 7%, transparent)',
+ },
+ '.cm-nb-issue-message + .cm-nb-issue-message': { marginTop: '4px' },
+ '.cm-nb-issue-detail': { color: 'var(--vp-c-text-2)', fontSize: '12px' },
'.cm-nb-code': {
fontFamily: 'var(--nb-mono)',
fontSize: '0.9em',