@rightkit/document-workspace 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/LICENSE-APACHE +201 -0
- package/LICENSE-MIT +21 -0
- package/README.md +51 -2
- package/dist/app/App.d.ts +1 -0
- package/dist/app/App.js +6 -0
- package/dist/app/AppDialogs.d.ts +52 -0
- package/dist/app/AppDialogs.js +23 -0
- package/dist/app/AppDialogsHost.d.ts +31 -0
- package/dist/app/AppDialogsHost.js +30 -0
- package/dist/app/AppLifecycle.d.ts +19 -0
- package/dist/app/AppLifecycle.js +87 -0
- package/dist/app/AppProvider.d.ts +317 -0
- package/dist/app/AppProvider.js +217 -0
- package/dist/app/AppRoot.d.ts +8 -0
- package/dist/app/AppRoot.js +159 -0
- package/dist/app/AppShell.d.ts +2 -0
- package/dist/app/AppShell.js +81 -0
- package/dist/app/CommandPalette.d.ts +13 -0
- package/dist/app/CommandPalette.js +47 -0
- package/dist/app/DocActionBar.d.ts +36 -0
- package/dist/app/DocActionBar.js +300 -0
- package/dist/app/DocumentContextMenu.d.ts +29 -0
- package/dist/app/DocumentContextMenu.js +122 -0
- package/dist/app/DocumentPane.d.ts +52 -0
- package/dist/app/DocumentPane.js +30 -0
- package/dist/app/DocumentRouterTable.d.ts +58 -0
- package/dist/app/DocumentRouterTable.js +143 -0
- package/dist/app/ErrorBoundary.d.ts +12 -0
- package/dist/app/ErrorBoundary.js +16 -0
- package/dist/app/FindBar.d.ts +12 -0
- package/dist/app/FindBar.js +199 -0
- package/dist/app/LiveTitle.d.ts +14 -0
- package/dist/app/LiveTitle.js +14 -0
- package/dist/app/OpeningProgress.d.ts +13 -0
- package/dist/app/OpeningProgress.js +21 -0
- package/dist/app/PrintPreviewWindow.d.ts +2 -0
- package/dist/app/PrintPreviewWindow.js +57 -0
- package/dist/app/ShortcutHelp.d.ts +3 -0
- package/dist/app/ShortcutHelp.js +99 -0
- package/dist/app/Sidebar.d.ts +36 -0
- package/dist/app/Sidebar.js +121 -0
- package/dist/app/ToastViewport.d.ts +1 -0
- package/dist/app/ToastViewport.js +31 -0
- package/dist/app/Toolbar.d.ts +26 -0
- package/dist/app/Toolbar.js +63 -0
- package/dist/app/activeFormatSavers.d.ts +7 -0
- package/dist/app/activeFormatSavers.js +4 -0
- package/dist/app/clipboardPaths.d.ts +1 -0
- package/dist/app/clipboardPaths.js +31 -0
- package/dist/app/documentDrafts.d.ts +17 -0
- package/dist/app/documentDrafts.js +104 -0
- package/dist/app/draftResolution.d.ts +5 -0
- package/dist/app/draftResolution.js +25 -0
- package/dist/app/markdownPaletteActions.d.ts +8 -0
- package/dist/app/markdownPaletteActions.js +107 -0
- package/dist/app/resourceCensusDev.d.ts +41 -0
- package/dist/app/resourceCensusDev.js +518 -0
- package/dist/app/setupRecovery.d.ts +34 -0
- package/dist/app/setupRecovery.js +70 -0
- package/dist/app/shell/DocStatsPill.d.ts +18 -0
- package/dist/app/shell/DocStatsPill.js +32 -0
- package/dist/app/shell/DocumentTools.d.ts +9 -0
- package/dist/app/shell/DocumentTools.js +73 -0
- package/dist/app/shell/InfoInspector.d.ts +9 -0
- package/dist/app/shell/InfoInspector.js +67 -0
- package/dist/app/shell/StartPage.d.ts +9 -0
- package/dist/app/shell/StartPage.js +49 -0
- package/dist/app/shell/readEdit.d.ts +6 -0
- package/dist/app/shell/readEdit.js +6 -0
- package/dist/app/sidebar/SidebarDocumentMenu.d.ts +20 -0
- package/dist/app/sidebar/SidebarDocumentMenu.js +56 -0
- package/dist/app/sidebar/SidebarExpandedPanel.d.ts +9 -0
- package/dist/app/sidebar/SidebarExpandedPanel.js +78 -0
- package/dist/app/sidebar/SidebarIcons.d.ts +3 -0
- package/dist/app/sidebar/SidebarIcons.js +18 -0
- package/dist/app/sidebar/SidebarMenus.d.ts +19 -0
- package/dist/app/sidebar/SidebarMenus.js +64 -0
- package/dist/app/sidebar/SidebarPathUtils.d.ts +10 -0
- package/dist/app/sidebar/SidebarPathUtils.js +55 -0
- package/dist/app/sidebar/SidebarPrimitives.d.ts +12 -0
- package/dist/app/sidebar/SidebarPrimitives.js +45 -0
- package/dist/app/sidebar/SidebarRail.d.ts +5 -0
- package/dist/app/sidebar/SidebarRail.js +37 -0
- package/dist/app/sidebar/explorer/ExplorerRowMenu.d.ts +21 -0
- package/dist/app/sidebar/explorer/ExplorerRowMenu.js +37 -0
- package/dist/app/sidebar/explorer/ExplorerSections.d.ts +16 -0
- package/dist/app/sidebar/explorer/ExplorerSections.js +259 -0
- package/dist/app/sidebar/explorer/ExplorerTree.d.ts +46 -0
- package/dist/app/sidebar/explorer/ExplorerTree.js +387 -0
- package/dist/app/sidebar/explorer/explorerState.d.ts +37 -0
- package/dist/app/sidebar/explorer/explorerState.js +135 -0
- package/dist/app/tabMountPolicy.d.ts +45 -0
- package/dist/app/tabMountPolicy.js +104 -0
- package/dist/app/tabWarmOrder.d.ts +7 -0
- package/dist/app/tabWarmOrder.js +48 -0
- package/dist/app/tabs/TabFuseCurves.d.ts +3 -0
- package/dist/app/tabs/TabFuseCurves.js +15 -0
- package/dist/app/tabs/TabOverview.d.ts +24 -0
- package/dist/app/tabs/TabOverview.js +121 -0
- package/dist/app/tabs/TabStrip.d.ts +42 -0
- package/dist/app/tabs/TabStrip.js +380 -0
- package/dist/app/tabs/tabIds.d.ts +5 -0
- package/dist/app/tabs/tabIds.js +7 -0
- package/dist/app/tabs/useTabDirty.d.ts +3 -0
- package/dist/app/tabs/useTabDirty.js +11 -0
- package/dist/app/useAppChromeState.d.ts +21 -0
- package/dist/app/useAppChromeState.js +48 -0
- package/dist/app/useAppCommands.d.ts +66 -0
- package/dist/app/useAppCommands.js +71 -0
- package/dist/app/useAppDocumentCore.d.ts +210 -0
- package/dist/app/useAppDocumentCore.js +40 -0
- package/dist/app/useAppEffects.d.ts +18 -0
- package/dist/app/useAppEffects.js +52 -0
- package/dist/app/useAppPaneSearch.d.ts +37 -0
- package/dist/app/useAppPaneSearch.js +74 -0
- package/dist/app/useAppShellSetup.d.ts +40 -0
- package/dist/app/useAppShellSetup.js +52 -0
- package/dist/app/useAppSlots.d.ts +28 -0
- package/dist/app/useAppSlots.js +40 -0
- package/dist/app/useAppTabChrome.d.ts +51 -0
- package/dist/app/useAppTabChrome.js +66 -0
- package/dist/app/useAutoSave.d.ts +34 -0
- package/dist/app/useAutoSave.js +116 -0
- package/dist/app/useCloseAll.d.ts +52 -0
- package/dist/app/useCloseAll.js +295 -0
- package/dist/app/useClosedTabEviction.d.ts +10 -0
- package/dist/app/useClosedTabEviction.js +38 -0
- package/dist/app/useCommandPalette.d.ts +44 -0
- package/dist/app/useCommandPalette.js +254 -0
- package/dist/app/useCrossDocSearch.d.ts +7 -0
- package/dist/app/useCrossDocSearch.js +94 -0
- package/dist/app/useDistractionFree.d.ts +21 -0
- package/dist/app/useDistractionFree.js +73 -0
- package/dist/app/useDocumentCreation.d.ts +18 -0
- package/dist/app/useDocumentCreation.js +63 -0
- package/dist/app/useDocumentDirty.d.ts +31 -0
- package/dist/app/useDocumentDirty.js +46 -0
- package/dist/app/useDocumentLoader.d.ts +55 -0
- package/dist/app/useDocumentLoader.js +267 -0
- package/dist/app/useDocumentOutputActions.d.ts +6 -0
- package/dist/app/useDocumentOutputActions.js +45 -0
- package/dist/app/useDocumentRuntimeEffects.d.ts +36 -0
- package/dist/app/useDocumentRuntimeEffects.js +458 -0
- package/dist/app/useDocumentSaver.d.ts +58 -0
- package/dist/app/useDocumentSaver.js +511 -0
- package/dist/app/useDocumentTabs.d.ts +41 -0
- package/dist/app/useDocumentTabs.js +207 -0
- package/dist/app/useDocumentWatcher.d.ts +83 -0
- package/dist/app/useDocumentWatcher.js +393 -0
- package/dist/app/useDocumentZoom.d.ts +24 -0
- package/dist/app/useDocumentZoom.js +104 -0
- package/dist/app/useDocuments.d.ts +169 -0
- package/dist/app/useDocuments.js +455 -0
- package/dist/app/useDraftConfirmGate.d.ts +27 -0
- package/dist/app/useDraftConfirmGate.js +47 -0
- package/dist/app/useFileDragOverlay.d.ts +12 -0
- package/dist/app/useFileDragOverlay.js +49 -0
- package/dist/app/useFormatApis.d.ts +13 -0
- package/dist/app/useFormatApis.js +62 -0
- package/dist/app/useFormatDirtyState.d.ts +29 -0
- package/dist/app/useFormatDirtyState.js +81 -0
- package/dist/app/useGlobalShortcuts.d.ts +47 -0
- package/dist/app/useGlobalShortcuts.js +359 -0
- package/dist/app/useHtmlSession.d.ts +68 -0
- package/dist/app/useHtmlSession.js +463 -0
- package/dist/app/useHtmlSessionProps.d.ts +33 -0
- package/dist/app/useHtmlSessionProps.js +57 -0
- package/dist/app/useLibrary.d.ts +21 -0
- package/dist/app/useLibrary.js +81 -0
- package/dist/app/useOpenFromClipboard.d.ts +7 -0
- package/dist/app/useOpenFromClipboard.js +44 -0
- package/dist/app/usePaneLiveness.d.ts +29 -0
- package/dist/app/usePaneLiveness.js +199 -0
- package/dist/app/usePendingSavePaths.d.ts +19 -0
- package/dist/app/usePendingSavePaths.js +67 -0
- package/dist/app/useReadyShow.d.ts +12 -0
- package/dist/app/useReadyShow.js +142 -0
- package/dist/app/useSearchController.d.ts +22 -0
- package/dist/app/useSearchController.js +114 -0
- package/dist/app/useSetupRecovery.d.ts +41 -0
- package/dist/app/useSetupRecovery.js +153 -0
- package/dist/app/useTabChrome.d.ts +40 -0
- package/dist/app/useTabChrome.js +115 -0
- package/dist/app/useViewState.d.ts +27 -0
- package/dist/app/useViewState.js +160 -0
- package/dist/appName.d.ts +2 -0
- package/dist/appName.js +8 -0
- package/dist/documents/DocumentRouter.d.ts +102 -0
- package/dist/documents/DocumentRouter.js +153 -0
- package/dist/documents/FileTypeIcon.d.ts +6 -0
- package/dist/documents/FileTypeIcon.js +93 -0
- package/dist/documents/ImageDocument.d.ts +14 -0
- package/dist/documents/ImageDocument.js +61 -0
- package/dist/documents/capabilities.d.ts +17 -0
- package/dist/documents/capabilities.js +18 -0
- package/dist/documents/html/HtmlDocument.d.ts +15 -0
- package/dist/documents/html/HtmlDocument.js +584 -0
- package/dist/documents/html/HtmlDocumentLayout.d.ts +87 -0
- package/dist/documents/html/HtmlDocumentLayout.js +25 -0
- package/dist/documents/html/HtmlDocumentProps.d.ts +60 -0
- package/dist/documents/html/HtmlDocumentProps.js +1 -0
- package/dist/documents/html/HtmlDocumentSource.d.ts +34 -0
- package/dist/documents/html/HtmlDocumentSource.js +28 -0
- package/dist/documents/markdown/MarkdownSplitPane.d.ts +115 -0
- package/dist/documents/markdown/MarkdownSplitPane.js +352 -0
- package/dist/documents/mdx/MdxSplitPane.d.ts +32 -0
- package/dist/documents/mdx/MdxSplitPane.js +60 -0
- package/dist/documents/txt/LinkDialog.d.ts +8 -0
- package/dist/documents/txt/LinkDialog.js +34 -0
- package/dist/documents/txt/MarkdownSourceToolbar.d.ts +8 -0
- package/dist/documents/txt/MarkdownSourceToolbar.js +25 -0
- package/dist/documents/txt/SourceEditorToolbar.d.ts +12 -0
- package/dist/documents/txt/SourceEditorToolbar.js +4 -0
- package/dist/documents/txt/TxtEditor.d.ts +39 -0
- package/dist/documents/txt/TxtEditor.js +186 -0
- package/dist/documents/txt/sourceEditorTypes.d.ts +49 -0
- package/dist/documents/txt/sourceEditorTypes.js +1 -0
- package/dist/documents/txt/sourceExtensions.d.ts +38 -0
- package/dist/documents/txt/sourceExtensions.js +444 -0
- package/dist/documents/txt/sourceFormatting.d.ts +18 -0
- package/dist/documents/txt/sourceFormatting.js +183 -0
- package/dist/documents/txt/sourceTheme.d.ts +5 -0
- package/dist/documents/txt/sourceTheme.js +220 -0
- package/dist/documents/txt/useCodeMirrorSourceEditor.d.ts +69 -0
- package/dist/documents/txt/useCodeMirrorSourceEditor.js +546 -0
- package/dist/documents/txt/useSourceSearch.d.ts +39 -0
- package/dist/documents/txt/useSourceSearch.js +122 -0
- package/dist/documents/txt/useTextEditorCommands.d.ts +26 -0
- package/dist/documents/txt/useTextEditorCommands.js +151 -0
- package/dist/documents/web/LivePreviewPane.d.ts +11 -0
- package/dist/documents/web/LivePreviewPane.js +58 -0
- package/dist/env.d.ts +66 -0
- package/dist/index.d.ts +121 -0
- package/dist/index.js +123 -0
- package/dist/settings/AppearanceMenu.d.ts +9 -0
- package/dist/settings/AppearanceMenu.js +54 -0
- package/dist/settings/SettingsPanel.d.ts +7 -0
- package/dist/settings/SettingsPanel.js +104 -0
- package/dist/settings/publishedSections.d.ts +22 -0
- package/dist/settings/publishedSections.js +79 -0
- package/dist/settings/sections/AboutSection.d.ts +1 -0
- package/dist/settings/sections/AboutSection.js +13 -0
- package/dist/settings/sections/DiagnosticsSection.d.ts +1 -0
- package/dist/settings/sections/DiagnosticsSection.js +42 -0
- package/dist/settings/sections/PrivacySection.d.ts +3 -0
- package/dist/settings/sections/PrivacySection.js +51 -0
- package/dist/settings/sections/UpdateLicenseBlock.d.ts +10 -0
- package/dist/settings/sections/UpdateLicenseBlock.js +158 -0
- package/dist/state/explorer.d.ts +32 -0
- package/dist/state/explorer.js +111 -0
- package/dist/state/legal.d.ts +51 -0
- package/dist/state/legal.js +130 -0
- package/dist/state/library.d.ts +33 -0
- package/dist/state/library.js +110 -0
- package/dist/state/updateLicense.d.ts +31 -0
- package/dist/state/updateLicense.js +340 -0
- package/dist/styles/explorer.css +219 -0
- package/dist/styles/index.css +1477 -0
- package/dist/styles/tabs.css +467 -0
- package/dist/styles.css +6 -0
- package/package.json +88 -3
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
import { defaultKeymap, historyKeymap, indentLess, indentMore, toggleComment, } from "@codemirror/commands";
|
|
2
|
+
import { autocompletion, closeBracketsKeymap, completionKeymap } from "@codemirror/autocomplete";
|
|
3
|
+
import { MARKDOWN_INSERT_TEMPLATES } from "@rightkit/markdown-editor/documents/markdown/insert/markdownInsertTemplates";
|
|
4
|
+
import { foldGutter, foldKeymap } from "@codemirror/language";
|
|
5
|
+
import { linter, lintKeymap } from "@codemirror/lint";
|
|
6
|
+
import { Decoration, EditorView, gutter, GutterMarker, highlightActiveLineGutter, keymap, lineNumbers, ViewPlugin, WidgetType, } from "@codemirror/view";
|
|
7
|
+
import { EditorSelection, EditorState, Prec, RangeSetBuilder, StateEffect, StateField, } from "@codemirror/state";
|
|
8
|
+
import { buildImageEmbed, sourceDiagnostics } from "./sourceFormatting.js";
|
|
9
|
+
export const setSearchHighlights = StateEffect.define();
|
|
10
|
+
export const searchHighlightField = StateField.define({
|
|
11
|
+
create: () => Decoration.none,
|
|
12
|
+
update(value, tr) {
|
|
13
|
+
let next = value.map(tr.changes);
|
|
14
|
+
for (const effect of tr.effects) {
|
|
15
|
+
if (effect.is(setSearchHighlights))
|
|
16
|
+
next = buildSearchDecorations(effect.value.matches, effect.value.activeIndex);
|
|
17
|
+
}
|
|
18
|
+
return next;
|
|
19
|
+
},
|
|
20
|
+
provide: (field) => EditorView.decorations.from(field),
|
|
21
|
+
});
|
|
22
|
+
export const searchHighlightClickExtension = Prec.highest(EditorView.domEventHandlers({
|
|
23
|
+
mousedown(event, view) {
|
|
24
|
+
if (event.button !== 0)
|
|
25
|
+
return false;
|
|
26
|
+
const eventTarget = event.target;
|
|
27
|
+
const target = eventTarget instanceof Element
|
|
28
|
+
? eventTarget
|
|
29
|
+
: eventTarget instanceof Node
|
|
30
|
+
? eventTarget.parentElement
|
|
31
|
+
: null;
|
|
32
|
+
if (!target?.closest(".cm-vr-searchMatchActive"))
|
|
33
|
+
return false;
|
|
34
|
+
// Find already owns the exact match selection. Run before CodeMirror's pointer handler so one
|
|
35
|
+
// click transfers focus without first collapsing or relocating that exact range.
|
|
36
|
+
event.preventDefault();
|
|
37
|
+
view.focus();
|
|
38
|
+
return true;
|
|
39
|
+
},
|
|
40
|
+
}));
|
|
41
|
+
class InvisibleCharWidget extends WidgetType {
|
|
42
|
+
label;
|
|
43
|
+
constructor(label) {
|
|
44
|
+
super();
|
|
45
|
+
this.label = label;
|
|
46
|
+
}
|
|
47
|
+
toDOM() {
|
|
48
|
+
const span = document.createElement("span");
|
|
49
|
+
span.className = "cm-vr-invisibleChar";
|
|
50
|
+
span.textContent = this.label;
|
|
51
|
+
return span;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// T2-4: decorate only the visible ranges (view.visibleRanges), not the whole document. The prior
|
|
55
|
+
// StateField called doc.toString() + looped every character on every docChanged with no windowing \u2014
|
|
56
|
+
// O(document) per keystroke regardless of file size. A ViewPlugin recomputes only on doc/viewport
|
|
57
|
+
// change and only walks what's actually on screen (plus CodeMirror's small overdraw margin).
|
|
58
|
+
class InvisibleCharsPlugin {
|
|
59
|
+
decorations;
|
|
60
|
+
constructor(view) {
|
|
61
|
+
this.decorations = buildInvisibleDecorations(view);
|
|
62
|
+
}
|
|
63
|
+
update(update) {
|
|
64
|
+
if (update.docChanged || update.viewportChanged) {
|
|
65
|
+
this.decorations = buildInvisibleDecorations(update.view);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export function invisibleCharactersExtension(enabled) {
|
|
70
|
+
if (!enabled)
|
|
71
|
+
return [];
|
|
72
|
+
return [
|
|
73
|
+
ViewPlugin.fromClass(InvisibleCharsPlugin, {
|
|
74
|
+
decorations: (plugin) => plugin.decorations,
|
|
75
|
+
}),
|
|
76
|
+
];
|
|
77
|
+
}
|
|
78
|
+
function buildInvisibleDecorations(view) {
|
|
79
|
+
const builder = new RangeSetBuilder();
|
|
80
|
+
for (const { from, to } of view.visibleRanges) {
|
|
81
|
+
const text = view.state.sliceDoc(from, to);
|
|
82
|
+
for (let i = 0; i < text.length; i++) {
|
|
83
|
+
const char = text[i];
|
|
84
|
+
const pos = from + i;
|
|
85
|
+
if (char === " ")
|
|
86
|
+
builder.add(pos, pos + 1, Decoration.replace({ widget: new InvisibleCharWidget("\u00b7") }));
|
|
87
|
+
else if (char === "\t")
|
|
88
|
+
builder.add(pos, pos + 1, Decoration.replace({ widget: new InvisibleCharWidget("\u2192") }));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return builder.finish();
|
|
92
|
+
}
|
|
93
|
+
function buildSearchDecorations(matches, activeIndex) {
|
|
94
|
+
const builder = new RangeSetBuilder();
|
|
95
|
+
matches.forEach((match, index) => {
|
|
96
|
+
if (match.end <= match.start)
|
|
97
|
+
return;
|
|
98
|
+
builder.add(match.start, match.end, Decoration.mark({
|
|
99
|
+
class: index === activeIndex ? "cm-vr-searchMatch cm-vr-searchMatchActive" : "cm-vr-searchMatch",
|
|
100
|
+
}));
|
|
101
|
+
});
|
|
102
|
+
return builder.finish();
|
|
103
|
+
}
|
|
104
|
+
export function languageExtension(_language) {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
export async function loadLanguageExtension(language) {
|
|
108
|
+
if (language === "markdown") {
|
|
109
|
+
const [{ markdown }, { javascript: jsLang }, { LanguageDescription }] = await Promise.all([
|
|
110
|
+
import("@codemirror/lang-markdown"),
|
|
111
|
+
import("@codemirror/lang-javascript"),
|
|
112
|
+
import("@codemirror/language"),
|
|
113
|
+
]);
|
|
114
|
+
return [
|
|
115
|
+
markdown({
|
|
116
|
+
codeLanguages: [
|
|
117
|
+
LanguageDescription.of({
|
|
118
|
+
name: "javascript",
|
|
119
|
+
alias: ["js", "jsx", "mjs", "cjs"],
|
|
120
|
+
support: jsLang(),
|
|
121
|
+
}),
|
|
122
|
+
LanguageDescription.of({
|
|
123
|
+
name: "typescript",
|
|
124
|
+
alias: ["ts", "tsx"],
|
|
125
|
+
support: jsLang({ typescript: true, jsx: true }),
|
|
126
|
+
}),
|
|
127
|
+
],
|
|
128
|
+
}),
|
|
129
|
+
];
|
|
130
|
+
}
|
|
131
|
+
if (language === "html") {
|
|
132
|
+
const { html } = await import("@codemirror/lang-html");
|
|
133
|
+
return [html({ autoCloseTags: true, selfClosingTags: true })];
|
|
134
|
+
}
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
export function visualSourceLineSelection(view, pos) {
|
|
138
|
+
const cursor = EditorSelection.cursor(pos);
|
|
139
|
+
return {
|
|
140
|
+
anchor: view.moveToLineBoundary(cursor, false, true).head,
|
|
141
|
+
head: view.moveToLineBoundary(cursor, true, true).head,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function selectSourceLine(view, line, event) {
|
|
145
|
+
const contentRect = view.contentDOM.getBoundingClientRect();
|
|
146
|
+
const hit = view.posAtCoords({ x: contentRect.left + 1, y: event.clientY }, false);
|
|
147
|
+
const pos = Math.max(line.from, Math.min(line.to, hit ?? line.from));
|
|
148
|
+
view.dispatch({
|
|
149
|
+
selection: visualSourceLineSelection(view, pos),
|
|
150
|
+
scrollIntoView: true,
|
|
151
|
+
userEvent: "select.pointer",
|
|
152
|
+
});
|
|
153
|
+
view.focus();
|
|
154
|
+
}
|
|
155
|
+
function selectLineFromGutter(view, line, event) {
|
|
156
|
+
if (!(event instanceof MouseEvent) || event.button !== 0)
|
|
157
|
+
return false;
|
|
158
|
+
const target = event.target instanceof Element ? event.target : null;
|
|
159
|
+
if (target?.closest(".cm-foldGutter"))
|
|
160
|
+
return false;
|
|
161
|
+
selectSourceLine(view, line, event);
|
|
162
|
+
event.preventDefault();
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
class LineSelectMarker extends GutterMarker {
|
|
166
|
+
toDOM() {
|
|
167
|
+
const span = document.createElement("span");
|
|
168
|
+
span.setAttribute("aria-hidden", "true");
|
|
169
|
+
return span;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const lineSelectMarker = new LineSelectMarker();
|
|
173
|
+
const plainLineSelectGutter = gutter({
|
|
174
|
+
class: "cm-lineSelectGutter",
|
|
175
|
+
lineMarker: () => lineSelectMarker,
|
|
176
|
+
domEventHandlers: {
|
|
177
|
+
mousedown: selectLineFromGutter,
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
export function gutterExtension(language) {
|
|
181
|
+
if (language === "plain")
|
|
182
|
+
return [plainLineSelectGutter];
|
|
183
|
+
return [
|
|
184
|
+
lineNumbers({ domEventHandlers: { mousedown: selectLineFromGutter } }),
|
|
185
|
+
foldGutter(),
|
|
186
|
+
highlightActiveLineGutter(),
|
|
187
|
+
];
|
|
188
|
+
}
|
|
189
|
+
export function readOnlyExtension(readOnly) {
|
|
190
|
+
return [EditorState.readOnly.of(readOnly), EditorView.editable.of(!readOnly)];
|
|
191
|
+
}
|
|
192
|
+
export function contentAttributesExtension(spellcheck, readOnly) {
|
|
193
|
+
return EditorView.contentAttributes.of({
|
|
194
|
+
spellcheck: String(spellcheck && !readOnly),
|
|
195
|
+
"aria-label": "Source editor",
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
function wordWrapExtension(wordWrap) {
|
|
199
|
+
return wordWrap
|
|
200
|
+
? [
|
|
201
|
+
EditorView.lineWrapping,
|
|
202
|
+
EditorView.theme({
|
|
203
|
+
".cm-scroller": { overflowX: "hidden !important" },
|
|
204
|
+
".cm-content": { minWidth: "0 !important" },
|
|
205
|
+
}),
|
|
206
|
+
]
|
|
207
|
+
: [];
|
|
208
|
+
}
|
|
209
|
+
export function sourceWrapExtension(wordWrap, language, sourceWrap) {
|
|
210
|
+
return language === "plain" ? wordWrapExtension(wordWrap) : wordWrapExtension(sourceWrap);
|
|
211
|
+
}
|
|
212
|
+
export function imagePasteExtension(language, onMarkdownHtmlPaste) {
|
|
213
|
+
if (language === "plain")
|
|
214
|
+
return [];
|
|
215
|
+
return [
|
|
216
|
+
EditorView.domEventHandlers({
|
|
217
|
+
paste(event, view) {
|
|
218
|
+
if (language === "markdown" &&
|
|
219
|
+
onMarkdownHtmlPaste &&
|
|
220
|
+
event.clipboardData?.getData("text/html")) {
|
|
221
|
+
const handled = onMarkdownHtmlPaste(event, view);
|
|
222
|
+
if (handled instanceof Promise) {
|
|
223
|
+
void handled;
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
if (handled)
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
const items = event.clipboardData?.items;
|
|
230
|
+
if (!items)
|
|
231
|
+
return false;
|
|
232
|
+
const image = Array.from(items).find((it) => it.kind === "file" && it.type.startsWith("image/"));
|
|
233
|
+
const file = image?.getAsFile();
|
|
234
|
+
if (!file)
|
|
235
|
+
return false;
|
|
236
|
+
event.preventDefault();
|
|
237
|
+
const reader = new FileReader();
|
|
238
|
+
reader.onload = () => {
|
|
239
|
+
const dataUrl = typeof reader.result === "string" ? reader.result : "";
|
|
240
|
+
if (!dataUrl)
|
|
241
|
+
return;
|
|
242
|
+
const snippet = buildImageEmbed(language, dataUrl);
|
|
243
|
+
const { from, to } = view.state.selection.main;
|
|
244
|
+
view.dispatch({
|
|
245
|
+
changes: { from, to, insert: snippet },
|
|
246
|
+
selection: { anchor: from + snippet.length },
|
|
247
|
+
scrollIntoView: true,
|
|
248
|
+
});
|
|
249
|
+
};
|
|
250
|
+
reader.readAsDataURL(file);
|
|
251
|
+
return true;
|
|
252
|
+
},
|
|
253
|
+
}),
|
|
254
|
+
];
|
|
255
|
+
}
|
|
256
|
+
export function diagnosticsExtension(language, _core = null) {
|
|
257
|
+
if (language === "plain")
|
|
258
|
+
return [];
|
|
259
|
+
return [linter((view) => sourceDiagnostics(language, view.state.doc.toString()), { delay: 350 })];
|
|
260
|
+
}
|
|
261
|
+
export async function loadDiagnosticsExtension(language, core = null) {
|
|
262
|
+
if (language === "plain")
|
|
263
|
+
return [];
|
|
264
|
+
if (language !== "markdown" || !core) {
|
|
265
|
+
return diagnosticsExtension(language, core);
|
|
266
|
+
}
|
|
267
|
+
const [{ analyzeMarkdown }, { buildCodeMirrorDiagnostics }] = await Promise.all([
|
|
268
|
+
import("@rightkit/markdown-editor/documents/markdown/analysis/analyzeMarkdown"),
|
|
269
|
+
import("@rightkit/markdown-editor/documents/markdown/analysis/markdownIssueCodeMirror"),
|
|
270
|
+
]);
|
|
271
|
+
return [
|
|
272
|
+
linter((view) => {
|
|
273
|
+
const diagnostics = sourceDiagnostics(language, view.state.doc.toString());
|
|
274
|
+
const snapshot = core.snapshot();
|
|
275
|
+
if (snapshot.logicalSource !== view.state.doc.toString())
|
|
276
|
+
return diagnostics;
|
|
277
|
+
return [...diagnostics, ...buildCodeMirrorDiagnostics(analyzeMarkdown(snapshot))];
|
|
278
|
+
}, { delay: 350 }),
|
|
279
|
+
];
|
|
280
|
+
}
|
|
281
|
+
/** Caret-anchored "/" insert menu for Markdown/MDX source: CodeMirror's own completion popup
|
|
282
|
+
* (filter-as-you-type, Esc closes, Enter inserts) over the shared insert templates. Triggers on
|
|
283
|
+
* "/" at line start or after whitespace; the "/query" text is replaced in one transaction. */
|
|
284
|
+
export function slashInsertCompletionSource(context) {
|
|
285
|
+
const match = context.matchBefore(/(?:^|\s)\/[\w-]*/);
|
|
286
|
+
if (!match)
|
|
287
|
+
return null;
|
|
288
|
+
const slashAt = match.from + match.text.indexOf("/");
|
|
289
|
+
const options = MARKDOWN_INSERT_TEMPLATES.map((template) => ({
|
|
290
|
+
label: template.label,
|
|
291
|
+
detail: "/ insert",
|
|
292
|
+
type: "text",
|
|
293
|
+
apply: (view, _completion, _from, to) => {
|
|
294
|
+
view.dispatch({
|
|
295
|
+
changes: { from: slashAt, to, insert: template.snippet },
|
|
296
|
+
selection: { anchor: slashAt + template.cursorOffset },
|
|
297
|
+
userEvent: "input.complete",
|
|
298
|
+
});
|
|
299
|
+
},
|
|
300
|
+
}));
|
|
301
|
+
return { from: slashAt + 1, options, validFor: /^[\w-]*$/ };
|
|
302
|
+
}
|
|
303
|
+
export function completionExtension(language) {
|
|
304
|
+
return autocompletion({
|
|
305
|
+
...(language === "markdown" ? { override: [slashInsertCompletionSource] } : {}),
|
|
306
|
+
activateOnTyping: language !== "plain",
|
|
307
|
+
defaultKeymap: false,
|
|
308
|
+
icons: false,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
export async function loadMarkdownCoreCompletionExtension(core) {
|
|
312
|
+
if (!core)
|
|
313
|
+
return [];
|
|
314
|
+
const { completionStartAt, completionsAt } = await import("@rightkit/markdown-editor/documents/markdown/references/markdownCompletion");
|
|
315
|
+
return [
|
|
316
|
+
autocompletion({
|
|
317
|
+
override: [
|
|
318
|
+
slashInsertCompletionSource,
|
|
319
|
+
(context) => {
|
|
320
|
+
const before = context.state.sliceDoc(0, context.pos);
|
|
321
|
+
const from = completionStartAt(before);
|
|
322
|
+
if (from === null)
|
|
323
|
+
return null;
|
|
324
|
+
const snapshot = core.snapshot();
|
|
325
|
+
const options = completionsAt({
|
|
326
|
+
textBefore: before,
|
|
327
|
+
folderFiles: [],
|
|
328
|
+
sessionFiles: [],
|
|
329
|
+
headings: snapshot.sourceMap.nodes,
|
|
330
|
+
});
|
|
331
|
+
if (!options.length)
|
|
332
|
+
return null;
|
|
333
|
+
return {
|
|
334
|
+
from,
|
|
335
|
+
options: options.map((option) => ({
|
|
336
|
+
label: option.label,
|
|
337
|
+
detail: option.detail,
|
|
338
|
+
type: option.kind === "image" ? "text" : "link",
|
|
339
|
+
apply: option.insertText,
|
|
340
|
+
})),
|
|
341
|
+
};
|
|
342
|
+
},
|
|
343
|
+
],
|
|
344
|
+
activateOnTyping: true,
|
|
345
|
+
defaultKeymap: false,
|
|
346
|
+
icons: false,
|
|
347
|
+
}),
|
|
348
|
+
];
|
|
349
|
+
}
|
|
350
|
+
export function sourceKeymap(markdownToolbar, onSave, onFormat, allowNextTabFocusRef, wrapInline, openLink, externalHistory = false, externalUndo, externalRedo) {
|
|
351
|
+
return keymap.of([
|
|
352
|
+
...(externalHistory
|
|
353
|
+
? [
|
|
354
|
+
{
|
|
355
|
+
key: "Mod-z",
|
|
356
|
+
run: () => {
|
|
357
|
+
externalUndo?.();
|
|
358
|
+
return true;
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
key: "Mod-y",
|
|
363
|
+
run: () => {
|
|
364
|
+
externalRedo?.();
|
|
365
|
+
return true;
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
key: "Mod-Shift-z",
|
|
370
|
+
run: () => {
|
|
371
|
+
externalRedo?.();
|
|
372
|
+
return true;
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
]
|
|
376
|
+
: []),
|
|
377
|
+
{
|
|
378
|
+
key: "Mod-s",
|
|
379
|
+
run: () => {
|
|
380
|
+
onSave();
|
|
381
|
+
return true;
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
key: "Shift-Alt-f",
|
|
386
|
+
run: () => {
|
|
387
|
+
onFormat();
|
|
388
|
+
return true;
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
{ key: "Mod-/", run: toggleComment },
|
|
392
|
+
{
|
|
393
|
+
key: "Escape",
|
|
394
|
+
run: () => {
|
|
395
|
+
allowNextTabFocusRef.current = true;
|
|
396
|
+
return false;
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
key: "Tab",
|
|
401
|
+
run: (view) => {
|
|
402
|
+
if (allowNextTabFocusRef.current) {
|
|
403
|
+
allowNextTabFocusRef.current = false;
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
return indentMore(view);
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
{ key: "Shift-Tab", run: indentLess },
|
|
410
|
+
...(markdownToolbar
|
|
411
|
+
? [
|
|
412
|
+
{
|
|
413
|
+
key: "Mod-b",
|
|
414
|
+
run: () => {
|
|
415
|
+
wrapInline("**", "**");
|
|
416
|
+
return true;
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
key: "Mod-i",
|
|
421
|
+
run: () => {
|
|
422
|
+
wrapInline("*", "*");
|
|
423
|
+
return true;
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
key: "Mod-k",
|
|
428
|
+
run: () => {
|
|
429
|
+
openLink("[");
|
|
430
|
+
return true;
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
]
|
|
434
|
+
: []),
|
|
435
|
+
// Completion keys first: with the slash/completion popup open, Enter accepts the option instead
|
|
436
|
+
// of defaultKeymap's insertNewlineAndIndent (completionKeymap commands return false when closed).
|
|
437
|
+
...completionKeymap,
|
|
438
|
+
...defaultKeymap,
|
|
439
|
+
...(externalHistory ? [] : historyKeymap),
|
|
440
|
+
...closeBracketsKeymap,
|
|
441
|
+
...lintKeymap,
|
|
442
|
+
...foldKeymap,
|
|
443
|
+
]);
|
|
444
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Diagnostic } from "@codemirror/lint";
|
|
2
|
+
export type SourceLanguage = "markdown" | "html" | "plain";
|
|
3
|
+
/** Pure core of the inline-wrap toolbar/shortcut action: wraps the selection [s,e) in
|
|
4
|
+
* `before`/`after`, keeping leading/trailing whitespace outside the markers and returning the new
|
|
5
|
+
* caret/selection. Exported so link/image/bold/italic wrapping is unit-testable without a DOM.
|
|
6
|
+
*/
|
|
7
|
+
export declare function applyInlineWrap(v: string, s: number, e: number, before: string, after: string): {
|
|
8
|
+
value: string;
|
|
9
|
+
start: number;
|
|
10
|
+
end: number;
|
|
11
|
+
};
|
|
12
|
+
/** Build a Markdown link/image token from the dialog's Display text + Address. Exported for tests. */
|
|
13
|
+
export declare function buildMarkdownLink(prefix: "[" | "![", text: string, url: string): string;
|
|
14
|
+
export declare function formatHtmlSource(source: string): string;
|
|
15
|
+
export declare function formatSource(language: SourceLanguage, source: string): string;
|
|
16
|
+
export declare function sourceDiagnostics(language: SourceLanguage, source: string): Diagnostic[];
|
|
17
|
+
/** The self-contained image token to insert for a pasted image. */
|
|
18
|
+
export declare function buildImageEmbed(language: SourceLanguage, dataUrl: string, alt?: string): string;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { hostAppName } from "../../appName.js";
|
|
2
|
+
/** Pure core of the inline-wrap toolbar/shortcut action: wraps the selection [s,e) in
|
|
3
|
+
* `before`/`after`, keeping leading/trailing whitespace outside the markers and returning the new
|
|
4
|
+
* caret/selection. Exported so link/image/bold/italic wrapping is unit-testable without a DOM.
|
|
5
|
+
*/
|
|
6
|
+
export function applyInlineWrap(v, s, e, before, after) {
|
|
7
|
+
const sel = v.slice(s, e);
|
|
8
|
+
const lead = (sel.match(/^\s*/) ?? [""])[0];
|
|
9
|
+
const trail = (sel.match(/\s*$/) ?? [""])[0];
|
|
10
|
+
const core = sel.slice(lead.length, sel.length - trail.length);
|
|
11
|
+
if (!core) {
|
|
12
|
+
return {
|
|
13
|
+
value: v.slice(0, s) + before + after + v.slice(e),
|
|
14
|
+
start: s + before.length,
|
|
15
|
+
end: s + before.length,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
const cs = s + lead.length + before.length;
|
|
19
|
+
return {
|
|
20
|
+
value: v.slice(0, s) + lead + before + core + after + trail + v.slice(e),
|
|
21
|
+
start: cs,
|
|
22
|
+
end: cs + core.length,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** Build a Markdown link/image token from the dialog's Display text + Address. Exported for tests. */
|
|
26
|
+
export function buildMarkdownLink(prefix, text, url) {
|
|
27
|
+
return `${prefix}${text || (prefix === "`;
|
|
28
|
+
}
|
|
29
|
+
const VOID_HTML_TAGS = new Set([
|
|
30
|
+
"area",
|
|
31
|
+
"base",
|
|
32
|
+
"br",
|
|
33
|
+
"col",
|
|
34
|
+
"embed",
|
|
35
|
+
"hr",
|
|
36
|
+
"img",
|
|
37
|
+
"input",
|
|
38
|
+
"link",
|
|
39
|
+
"meta",
|
|
40
|
+
"param",
|
|
41
|
+
"source",
|
|
42
|
+
"track",
|
|
43
|
+
"wbr",
|
|
44
|
+
]);
|
|
45
|
+
function htmlTagName(token) {
|
|
46
|
+
return token.match(/^<\/?\s*([a-zA-Z][\w:-]*)/)?.[1].toLowerCase() ?? null;
|
|
47
|
+
}
|
|
48
|
+
function isHtmlMetaToken(token) {
|
|
49
|
+
return /^<!|^<\?/.test(token) && !/^<!\[CDATA\[/i.test(token);
|
|
50
|
+
}
|
|
51
|
+
function isHtmlClosingTag(token) {
|
|
52
|
+
return /^<\//.test(token);
|
|
53
|
+
}
|
|
54
|
+
function isHtmlSelfClosing(token) {
|
|
55
|
+
const name = htmlTagName(token);
|
|
56
|
+
return !name || /\/>\s*$/.test(token) || VOID_HTML_TAGS.has(name) || isHtmlMetaToken(token);
|
|
57
|
+
}
|
|
58
|
+
function indentLine(level, text) {
|
|
59
|
+
return `${" ".repeat(Math.max(0, level))}${text}`;
|
|
60
|
+
}
|
|
61
|
+
export function formatHtmlSource(source) {
|
|
62
|
+
const tokens = source
|
|
63
|
+
.replace(/\r\n?/g, "\n")
|
|
64
|
+
.split(/(<!--[\s\S]*?-->|<!\[CDATA\[[\s\S]*?\]\]>|<![^>]*>|<\/?[^>]+>)/g)
|
|
65
|
+
.map((token) => token.trim())
|
|
66
|
+
.filter(Boolean);
|
|
67
|
+
const lines = [];
|
|
68
|
+
let level = 0;
|
|
69
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
70
|
+
const token = tokens[i];
|
|
71
|
+
const isTag = token.startsWith("<") && token.endsWith(">");
|
|
72
|
+
if (!isTag) {
|
|
73
|
+
lines.push(indentLine(level, token));
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const name = htmlTagName(token);
|
|
77
|
+
const next = tokens[i + 1];
|
|
78
|
+
const afterNext = tokens[i + 2];
|
|
79
|
+
if (!isHtmlClosingTag(token) &&
|
|
80
|
+
name &&
|
|
81
|
+
next &&
|
|
82
|
+
afterNext &&
|
|
83
|
+
!next.startsWith("<") &&
|
|
84
|
+
isHtmlClosingTag(afterNext) &&
|
|
85
|
+
htmlTagName(afterNext) === name) {
|
|
86
|
+
lines.push(indentLine(level, `${token}${next}${afterNext}`));
|
|
87
|
+
i += 2;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (isHtmlClosingTag(token)) {
|
|
91
|
+
level = Math.max(0, level - 1);
|
|
92
|
+
lines.push(indentLine(level, token));
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
lines.push(indentLine(level, token));
|
|
96
|
+
if (!isHtmlSelfClosing(token))
|
|
97
|
+
level += 1;
|
|
98
|
+
}
|
|
99
|
+
return `${lines.join("\n")}\n`;
|
|
100
|
+
}
|
|
101
|
+
export function formatSource(language, source) {
|
|
102
|
+
if (language === "html")
|
|
103
|
+
return formatHtmlSource(source);
|
|
104
|
+
return `${source
|
|
105
|
+
.replace(/\r\n?/g, "\n")
|
|
106
|
+
.split("\n")
|
|
107
|
+
.map((line) => line.replace(/[ \t]+$/g, ""))
|
|
108
|
+
.join("\n")
|
|
109
|
+
.replace(/\n*$/g, "")}\n`;
|
|
110
|
+
}
|
|
111
|
+
export function sourceDiagnostics(language, source) {
|
|
112
|
+
if (language === "plain")
|
|
113
|
+
return [];
|
|
114
|
+
const diagnostics = [];
|
|
115
|
+
// T4-10: track each line's start as a running offset instead of re-joining `lines.slice(0, index)`
|
|
116
|
+
// per flagged line (was O(n^2) on whitespace-heavy files). `offset` after processing a line equals
|
|
117
|
+
// exactly what the old `prior.length + (index === 0 ? 0 : 1)` computed for the next line — each line
|
|
118
|
+
// contributes its own length plus one for the "\n" that joined it to the next.
|
|
119
|
+
let offset = 0;
|
|
120
|
+
for (const line of source.replace(/\r\n?/g, "\n").split("\n")) {
|
|
121
|
+
if (/[ \t]+$/.test(line)) {
|
|
122
|
+
diagnostics.push({
|
|
123
|
+
from: offset + line.search(/[ \t]+$/),
|
|
124
|
+
to: offset + line.length,
|
|
125
|
+
severity: "info",
|
|
126
|
+
source: hostAppName(),
|
|
127
|
+
message: "Trailing whitespace.",
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
offset += line.length + 1;
|
|
131
|
+
}
|
|
132
|
+
if (language !== "html")
|
|
133
|
+
return diagnostics.sort((a, b) => a.from - b.from || a.to - b.to);
|
|
134
|
+
const stack = [];
|
|
135
|
+
const tagPattern = /<!--[\s\S]*?-->|<![^>]*>|<\/?([a-zA-Z][\w:-]*)\b[^>]*>/g;
|
|
136
|
+
while (true) {
|
|
137
|
+
const match = tagPattern.exec(source);
|
|
138
|
+
if (!match)
|
|
139
|
+
break;
|
|
140
|
+
const token = match[0];
|
|
141
|
+
const name = htmlTagName(token);
|
|
142
|
+
if (!name || isHtmlMetaToken(token) || token.startsWith("<!--") || isHtmlSelfClosing(token))
|
|
143
|
+
continue;
|
|
144
|
+
if (!isHtmlClosingTag(token)) {
|
|
145
|
+
stack.push({ name, from: match.index, to: match.index + token.length });
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const open = stack.pop();
|
|
149
|
+
if (!open) {
|
|
150
|
+
diagnostics.push({
|
|
151
|
+
from: match.index,
|
|
152
|
+
to: match.index + token.length,
|
|
153
|
+
severity: "warning",
|
|
154
|
+
source: hostAppName(),
|
|
155
|
+
message: `Closing </${name}> has no opening tag.`,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
else if (open.name !== name) {
|
|
159
|
+
diagnostics.push({
|
|
160
|
+
from: match.index,
|
|
161
|
+
to: match.index + token.length,
|
|
162
|
+
severity: "warning",
|
|
163
|
+
source: hostAppName(),
|
|
164
|
+
message: `Closing </${name}> does not match <${open.name}>.`,
|
|
165
|
+
});
|
|
166
|
+
stack.push(open);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
for (const open of stack.reverse()) {
|
|
170
|
+
diagnostics.push({
|
|
171
|
+
from: open.from,
|
|
172
|
+
to: open.to,
|
|
173
|
+
severity: "warning",
|
|
174
|
+
source: hostAppName(),
|
|
175
|
+
message: `<${open.name}> is not closed.`,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return diagnostics.sort((a, b) => a.from - b.from || a.to - b.to);
|
|
179
|
+
}
|
|
180
|
+
/** The self-contained image token to insert for a pasted image. */
|
|
181
|
+
export function buildImageEmbed(language, dataUrl, alt = "") {
|
|
182
|
+
return language === "html" ? `<img src="${dataUrl}" alt="${alt}" />` : ``;
|
|
183
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { HighlightStyle } from "@codemirror/language";
|
|
2
|
+
import type { Extension } from "@codemirror/state";
|
|
3
|
+
import type { SourceLanguage } from "./sourceFormatting.js";
|
|
4
|
+
export declare const sourceHighlightStyle: HighlightStyle;
|
|
5
|
+
export declare function viewRightTheme(language: SourceLanguage, useReadingFont: boolean): Extension;
|