mirror of
https://github.com/vadimmelnicuk/meo.git
synced 2026-09-26 09:55:39 +00:00
feat: enhance markdown export with visible link-reference definitions and improve copy functionality in editor
This commit is contained in:
+2
-2
@@ -1,9 +1,9 @@
|
||||
# Markdown Editor Optimized (MEO)
|
||||
---
|
||||
## Unreleased
|
||||
- Added link-reference definitions as visible linked lists in HTML and PDF exports, and restored copying selections across rendered blocks
|
||||
- Added configurable CodeMirror keymap via `markdownEditorOptimized.keymap`
|
||||
- Hardened external document sync (git/LLM/outside editors): prefer host content on conflict, fix stuck applyingExternal, Reload recovery
|
||||
- Avoid silent local-draft overwrite of external writes; surface appliedFailed + requestReload paths
|
||||
- Added configurable CodeMirror keymap via `markdownEditorOptimized.keymap` (whitelist commands + `passthrough`)
|
||||
|
||||
## 0.1.26
|
||||
- Improved dark Mermaid diagram line contrast
|
||||
|
||||
+1
-1
@@ -98,7 +98,7 @@
|
||||
"customEditors": [
|
||||
{
|
||||
"viewType": "markdownEditorOptimized.editor",
|
||||
"displayName": "Markdown Editor Optimized",
|
||||
"displayName": "MEO",
|
||||
"selector": [
|
||||
{
|
||||
"filenamePattern": "*.md"
|
||||
|
||||
@@ -220,7 +220,115 @@ export function renderMarkdownToHtml(options: RenderMarkdownOptions): RenderMark
|
||||
}
|
||||
|
||||
function normalizeMarkdownForExport(markdownText: string): string {
|
||||
return ensureBlankLinesAroundTableBlocks(normalizeMermaidColonFences(markdownText));
|
||||
return ensureVisibleReferenceSections(
|
||||
ensureBlankLinesAroundTableBlocks(normalizeMermaidColonFences(markdownText))
|
||||
);
|
||||
}
|
||||
|
||||
type LinkReferenceDefinition = {
|
||||
label: string;
|
||||
title: string;
|
||||
};
|
||||
|
||||
const referenceSectionHeadingPattern = /^([ \t]{0,3})(#{1,6})[ \t]+(?:references|sources|citations)[ \t]*#*[ \t]*$/i;
|
||||
const linkReferenceDefinitionPattern = /^[ \t]{0,3}\[([^\]^][^\]]*)\]:[ \t]*(?:<[^>\r\n]+>|\S+)(?:[ \t]+(?:"([^"]*)"|'([^']*)'|\(([^)]*)\)))?[ \t]*$/;
|
||||
|
||||
function ensureVisibleReferenceSections(markdownText: string): string {
|
||||
const lines = String(markdownText ?? '').split(/\r?\n/);
|
||||
const out: string[] = [];
|
||||
const fenceState = { inFence: false, char: '', length: 0 };
|
||||
|
||||
for (let index = 0; index < lines.length; index += 1) {
|
||||
const line = lines[index] ?? '';
|
||||
updateExportFenceState(fenceState, line);
|
||||
out.push(line);
|
||||
|
||||
if (fenceState.inFence) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const headingMatch = referenceSectionHeadingPattern.exec(line);
|
||||
if (!headingMatch) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const definitions = collectReferenceDefinitions(lines, index + 1, headingMatch[2].length);
|
||||
if (!definitions.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
out.push('', ...definitions.map(renderVisibleReferenceDefinition), '');
|
||||
}
|
||||
|
||||
return out.join('\n');
|
||||
}
|
||||
|
||||
function collectReferenceDefinitions(
|
||||
lines: string[],
|
||||
startIndex: number,
|
||||
sectionHeadingLevel: number
|
||||
): LinkReferenceDefinition[] {
|
||||
const definitions: LinkReferenceDefinition[] = [];
|
||||
|
||||
for (let index = startIndex; index < lines.length; index += 1) {
|
||||
const line = lines[index] ?? '';
|
||||
if (!line.trim()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const heading = /^[ \t]{0,3}(#{1,6})[ \t]+/.exec(line);
|
||||
if (heading && heading[1].length <= sectionHeadingLevel) {
|
||||
break;
|
||||
}
|
||||
|
||||
const definitionMatch = linkReferenceDefinitionPattern.exec(line);
|
||||
if (!definitionMatch) {
|
||||
return [];
|
||||
}
|
||||
|
||||
definitions.push({
|
||||
label: definitionMatch[1].trim(),
|
||||
title: (definitionMatch[2] ?? definitionMatch[3] ?? definitionMatch[4] ?? '').trim()
|
||||
});
|
||||
}
|
||||
|
||||
return definitions;
|
||||
}
|
||||
|
||||
function renderVisibleReferenceDefinition(definition: LinkReferenceDefinition): string {
|
||||
const text = escapeMarkdownLinkText(definition.title || definition.label);
|
||||
const label = definition.label.replace(/\\/g, '\\\\').replace(/\]/g, '\\]');
|
||||
return /^\d+$/.test(definition.label)
|
||||
? `${definition.label}. [${text}][${label}]`
|
||||
: `- [${text}][${label}]`;
|
||||
}
|
||||
|
||||
function escapeMarkdownLinkText(value: string): string {
|
||||
return value.replace(/\\/g, '\\\\').replace(/([\[\]])/g, '\\$1');
|
||||
}
|
||||
|
||||
function updateExportFenceState(
|
||||
state: { inFence: boolean; char: string; length: number },
|
||||
line: string
|
||||
): void {
|
||||
const fence = /^[ \t]{0,3}([`~]{3,})/.exec(line);
|
||||
if (!fence) {
|
||||
return;
|
||||
}
|
||||
|
||||
const marker = fence[1];
|
||||
if (!state.inFence) {
|
||||
state.inFence = true;
|
||||
state.char = marker[0];
|
||||
state.length = marker.length;
|
||||
return;
|
||||
}
|
||||
|
||||
if (marker[0] === state.char && marker.length >= state.length) {
|
||||
state.inFence = false;
|
||||
state.char = '';
|
||||
state.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeMermaidColonFences(markdownText: string): string {
|
||||
|
||||
@@ -1518,6 +1518,26 @@ export function createEditor({
|
||||
EditorView.lineWrapping,
|
||||
scrollPastEnd(),
|
||||
EditorView.domEventHandlers({
|
||||
copy(event, view) {
|
||||
const selectedRanges = view.state.selection.ranges.filter((range) => !range.empty);
|
||||
if (!selectedRanges.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!event.clipboardData) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const selectedMarkdown = selectedRanges
|
||||
.map((range) => view.state.doc.sliceString(
|
||||
Math.min(range.from, range.to),
|
||||
Math.max(range.from, range.to)
|
||||
))
|
||||
.join(view.state.lineBreak);
|
||||
event.clipboardData.setData('text/plain', selectedMarkdown);
|
||||
event.preventDefault();
|
||||
return true;
|
||||
},
|
||||
pointerdown(event, view) {
|
||||
if (event.button !== 0) {
|
||||
frontmatterBoundaryClick = null;
|
||||
|
||||
@@ -944,6 +944,7 @@ function addDetailsBlockDecorations(builder, state, detailsBlocks, activeLines)
|
||||
const openingActive = rangeTouchesActiveLine(state, detailsBlock.anchorFrom, detailsBlock.anchorTo, activeLines);
|
||||
const closingActive = rangeTouchesActiveLine(state, detailsBlock.closingFrom, detailsBlock.closingTo, activeLines);
|
||||
const editingBoundary = openingActive || closingActive;
|
||||
const selectingBlock = overlapsSelection(state, detailsBlock.sectionFrom, detailsBlock.sectionTo);
|
||||
|
||||
if (!editingBoundary) {
|
||||
addLineClass(builder, state, detailsBlock.lineFrom, detailsBlock.lineTo, lineStyleDecos.detailsSummary);
|
||||
@@ -976,7 +977,7 @@ function addDetailsBlockDecorations(builder, state, detailsBlocks, activeLines)
|
||||
builder.push(collapsedHeadingBodyDeco.range(detailsBlock.closingFrom, detailsBlock.closingTo));
|
||||
}
|
||||
|
||||
if (detailsBlock.collapsed && detailsBlock.bodyTo > detailsBlock.bodyFrom) {
|
||||
if (detailsBlock.collapsed && !selectingBlock && detailsBlock.bodyTo > detailsBlock.bodyFrom) {
|
||||
builder.push(collapsedHeadingBodyDeco.range(detailsBlock.bodyFrom, detailsBlock.bodyTo));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user