@anweb/nuxt-aneditor 0.1.1

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.
Files changed (59) hide show
  1. package/README.md +81 -0
  2. package/dist/module.d.mts +14 -0
  3. package/dist/module.d.ts +14 -0
  4. package/dist/module.json +9 -0
  5. package/dist/module.mjs +29 -0
  6. package/dist/runtime/assets/icons/blockquote.svg +1 -0
  7. package/dist/runtime/assets/icons/bold.svg +1 -0
  8. package/dist/runtime/assets/icons/code-block.svg +1 -0
  9. package/dist/runtime/assets/icons/code.svg +1 -0
  10. package/dist/runtime/assets/icons/edit.svg +1 -0
  11. package/dist/runtime/assets/icons/expand.svg +1 -0
  12. package/dist/runtime/assets/icons/heading.svg +1 -0
  13. package/dist/runtime/assets/icons/horizontal-rule.svg +1 -0
  14. package/dist/runtime/assets/icons/image.svg +1 -0
  15. package/dist/runtime/assets/icons/italic.svg +1 -0
  16. package/dist/runtime/assets/icons/link.svg +1 -0
  17. package/dist/runtime/assets/icons/ordered-list.svg +1 -0
  18. package/dist/runtime/assets/icons/redo.svg +1 -0
  19. package/dist/runtime/assets/icons/remove.svg +1 -0
  20. package/dist/runtime/assets/icons/strikethrough.svg +1 -0
  21. package/dist/runtime/assets/icons/table.svg +1 -0
  22. package/dist/runtime/assets/icons/task-list.svg +1 -0
  23. package/dist/runtime/assets/icons/undo.svg +1 -0
  24. package/dist/runtime/assets/icons/unordered-list.svg +1 -0
  25. package/dist/runtime/assets/icons/upload.svg +1 -0
  26. package/dist/runtime/assets/icons/youtube.svg +1 -0
  27. package/dist/runtime/components/AnEditor/Editor.vue +630 -0
  28. package/dist/runtime/components/AnEditor/Editor.vue.d.ts +2 -0
  29. package/dist/runtime/components/AnEditor/Prompt.vue +50 -0
  30. package/dist/runtime/components/AnEditor/Prompt.vue.d.ts +2 -0
  31. package/dist/runtime/components/AnEditor/Toolbar.vue +191 -0
  32. package/dist/runtime/components/AnEditor/Toolbar.vue.d.ts +2 -0
  33. package/dist/runtime/components/AnEditor/Viewer.vue +16 -0
  34. package/dist/runtime/components/AnEditor/Viewer.vue.d.ts +2 -0
  35. package/dist/runtime/composables/useBlocks.d.ts +15 -0
  36. package/dist/runtime/composables/useBlocks.js +258 -0
  37. package/dist/runtime/composables/useHistory.d.ts +12 -0
  38. package/dist/runtime/composables/useHistory.js +56 -0
  39. package/dist/runtime/composables/useImage.d.ts +27 -0
  40. package/dist/runtime/composables/useImage.js +81 -0
  41. package/dist/runtime/composables/useList.d.ts +10 -0
  42. package/dist/runtime/composables/useList.js +116 -0
  43. package/dist/runtime/composables/useSelection.d.ts +20 -0
  44. package/dist/runtime/composables/useSelection.js +92 -0
  45. package/dist/runtime/composables/useTable.d.ts +29 -0
  46. package/dist/runtime/composables/useTable.js +175 -0
  47. package/dist/runtime/types/global.d.ts +8 -0
  48. package/dist/runtime/types/index.d.ts +1 -0
  49. package/dist/runtime/types/index.js +1 -0
  50. package/dist/runtime/utils/index.d.ts +3 -0
  51. package/dist/runtime/utils/index.js +3 -0
  52. package/dist/runtime/utils/parseMarkdown.d.ts +1 -0
  53. package/dist/runtime/utils/parseMarkdown.js +184 -0
  54. package/dist/runtime/utils/toMarkdown.d.ts +1 -0
  55. package/dist/runtime/utils/toMarkdown.js +233 -0
  56. package/dist/runtime/utils/youtube.d.ts +1 -0
  57. package/dist/runtime/utils/youtube.js +6 -0
  58. package/dist/types.d.mts +3 -0
  59. package/package.json +50 -0
@@ -0,0 +1,116 @@
1
+ export const useList = (refContent, sel, syncToModel, ensureBlockWrapped, isInsideCodeBlock) => {
2
+ const findParentLi = () => {
3
+ const s = sel.getSelection();
4
+ if (!s) return null;
5
+ let node = s.range.commonAncestorContainer;
6
+ if (node.nodeType === Node.TEXT_NODE) node = node.parentNode;
7
+ while (node && node !== refContent.value) {
8
+ if (node instanceof HTMLElement && node.tagName.toLowerCase() === "li") {
9
+ return node;
10
+ }
11
+ node = node.parentNode;
12
+ }
13
+ return null;
14
+ };
15
+ const indentListItem = (li) => {
16
+ const prevLi = li.previousElementSibling;
17
+ if (!prevLi || prevLi.tagName.toLowerCase() !== "li") return;
18
+ const parentList = li.parentElement;
19
+ const listTag = parentList.tagName.toLowerCase();
20
+ const offset = sel.getCaretOffset(li);
21
+ let nestedList = prevLi.querySelector(`:scope > ${listTag}`);
22
+ if (!nestedList) {
23
+ nestedList = document.createElement(listTag);
24
+ prevLi.appendChild(nestedList);
25
+ }
26
+ nestedList.appendChild(li);
27
+ sel.restoreCaretOffset(li, offset);
28
+ syncToModel();
29
+ };
30
+ const outdentListItem = (li) => {
31
+ const parentList = li.parentElement;
32
+ if (!parentList) return;
33
+ const parentLi = parentList.parentElement;
34
+ if (!parentLi || parentLi.tagName.toLowerCase() !== "li") return;
35
+ const grandparentList = parentLi.parentElement;
36
+ if (!grandparentList) return;
37
+ const offset = sel.getCaretOffset(li);
38
+ const siblings = [];
39
+ let sibling = li.nextElementSibling;
40
+ while (sibling) {
41
+ siblings.push(sibling);
42
+ sibling = sibling.nextElementSibling;
43
+ }
44
+ grandparentList.insertBefore(li, parentLi.nextSibling);
45
+ if (siblings.length > 0) {
46
+ const newNested = document.createElement(parentList.tagName.toLowerCase());
47
+ for (const s of siblings) {
48
+ newNested.appendChild(s);
49
+ }
50
+ li.appendChild(newNested);
51
+ }
52
+ if (parentList.children.length === 0) {
53
+ parentList.remove();
54
+ }
55
+ sel.restoreCaretOffset(li, offset);
56
+ syncToModel();
57
+ };
58
+ const convertToList = (tagName) => {
59
+ if (isInsideCodeBlock()) return;
60
+ const block = ensureBlockWrapped();
61
+ if (!block) return;
62
+ const offset = sel.getCaretOffset(block);
63
+ if (block.tagName.toLowerCase() === tagName) {
64
+ const items = block.querySelectorAll(":scope > li");
65
+ const fragment = document.createDocumentFragment();
66
+ let cursorTarget = null;
67
+ items.forEach((li2) => {
68
+ const p = document.createElement("p");
69
+ let content = "";
70
+ for (const node of li2.childNodes) {
71
+ if (node instanceof HTMLElement) {
72
+ const t = node.tagName.toLowerCase();
73
+ if (t === "ul" || t === "ol") continue;
74
+ }
75
+ content += node.outerHTML ?? node.textContent ?? "";
76
+ }
77
+ p.innerHTML = content.trim() || "<br>";
78
+ fragment.appendChild(p);
79
+ if (!cursorTarget) cursorTarget = p;
80
+ });
81
+ if (!fragment.childNodes.length) {
82
+ const p = document.createElement("p");
83
+ p.innerHTML = block.innerHTML || "<br>";
84
+ fragment.appendChild(p);
85
+ cursorTarget = p;
86
+ }
87
+ block.replaceWith(fragment);
88
+ if (cursorTarget) sel.restoreCaretOffset(cursorTarget, offset);
89
+ syncToModel();
90
+ return;
91
+ }
92
+ const list = document.createElement(tagName);
93
+ const li = document.createElement("li");
94
+ if (block.tagName.toLowerCase() === "ul" || block.tagName.toLowerCase() === "ol") {
95
+ for (const existingLi of block.querySelectorAll(":scope > li")) {
96
+ const newLi = document.createElement("li");
97
+ for (const node of existingLi.childNodes) {
98
+ newLi.appendChild(node.cloneNode(true));
99
+ }
100
+ list.appendChild(newLi);
101
+ }
102
+ } else {
103
+ li.innerHTML = block.innerHTML;
104
+ list.appendChild(li);
105
+ }
106
+ block.replaceWith(list);
107
+ sel.restoreCaretOffset(list, offset);
108
+ syncToModel();
109
+ };
110
+ return {
111
+ findParentLi,
112
+ indentListItem,
113
+ outdentListItem,
114
+ convertToList
115
+ };
116
+ };
@@ -0,0 +1,20 @@
1
+ import type { Ref } from 'vue';
2
+ export declare const useSelection: (refContent: Ref<HTMLDivElement | null>) => {
3
+ getSelection: () => {
4
+ selection: Selection;
5
+ range: Range;
6
+ } | null;
7
+ getTextOffset: (root: Node, targetNode: Node, targetOffset: number) => number;
8
+ getCaretOffset: (root: Node) => number;
9
+ getCaretOffsetInContent: () => number;
10
+ getSelectionOffsets: (root: Node) => {
11
+ start: number;
12
+ end: number;
13
+ };
14
+ findTextPosition: (root: Node, offset: number) => {
15
+ node: Node;
16
+ offset: number;
17
+ } | null;
18
+ restoreCaretOffset: (root: Node, offset: number) => void;
19
+ restoreSelectionOffsets: (root: Node, start: number, end: number) => void;
20
+ };
@@ -0,0 +1,92 @@
1
+ export const useSelection = (refContent) => {
2
+ const getSelection = () => {
3
+ const selection = window.getSelection();
4
+ if (!selection || selection.rangeCount === 0) return null;
5
+ if (!refContent.value?.contains(selection.anchorNode)) return null;
6
+ return { selection, range: selection.getRangeAt(0) };
7
+ };
8
+ const getTextOffset = (root, targetNode, targetOffset) => {
9
+ const range = document.createRange();
10
+ range.selectNodeContents(root);
11
+ range.setEnd(targetNode, targetOffset);
12
+ return range.toString().length;
13
+ };
14
+ const getCaretOffset = (root) => {
15
+ const sel = window.getSelection();
16
+ if (!sel || sel.rangeCount === 0) return 0;
17
+ return getTextOffset(root, sel.anchorNode, sel.anchorOffset);
18
+ };
19
+ const getCaretOffsetInContent = () => {
20
+ if (!refContent.value) return 0;
21
+ const sel = window.getSelection();
22
+ if (!sel || sel.rangeCount === 0) return 0;
23
+ if (!refContent.value.contains(sel.anchorNode)) return 0;
24
+ return getTextOffset(refContent.value, sel.anchorNode, sel.anchorOffset);
25
+ };
26
+ const getSelectionOffsets = (root) => {
27
+ const sel = window.getSelection();
28
+ if (!sel || sel.rangeCount === 0) return { start: 0, end: 0 };
29
+ const range = sel.getRangeAt(0);
30
+ const start = getTextOffset(root, range.startContainer, range.startOffset);
31
+ const end = getTextOffset(root, range.endContainer, range.endOffset);
32
+ return { start, end };
33
+ };
34
+ const findTextPosition = (root, offset) => {
35
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
36
+ let remaining = offset;
37
+ while (walker.nextNode()) {
38
+ const textNode = walker.currentNode;
39
+ const len = textNode.textContent?.length ?? 0;
40
+ if (remaining <= len) {
41
+ return { node: textNode, offset: remaining };
42
+ }
43
+ remaining -= len;
44
+ }
45
+ return null;
46
+ };
47
+ const restoreCaretOffset = (root, offset) => {
48
+ const pos = findTextPosition(root, offset);
49
+ const selection = window.getSelection();
50
+ if (pos) {
51
+ const range = document.createRange();
52
+ range.setStart(pos.node, pos.offset);
53
+ range.collapse(true);
54
+ selection?.removeAllRanges();
55
+ selection?.addRange(range);
56
+ } else {
57
+ const range = document.createRange();
58
+ range.selectNodeContents(root);
59
+ range.collapse(false);
60
+ selection?.removeAllRanges();
61
+ selection?.addRange(range);
62
+ }
63
+ };
64
+ const restoreSelectionOffsets = (root, start, end) => {
65
+ const startPos = findTextPosition(root, start);
66
+ const endPos = findTextPosition(root, end);
67
+ const selection = window.getSelection();
68
+ if (startPos && endPos) {
69
+ const range = document.createRange();
70
+ range.setStart(startPos.node, startPos.offset);
71
+ range.setEnd(endPos.node, endPos.offset);
72
+ selection?.removeAllRanges();
73
+ selection?.addRange(range);
74
+ } else if (startPos) {
75
+ const range = document.createRange();
76
+ range.setStart(startPos.node, startPos.offset);
77
+ range.collapse(true);
78
+ selection?.removeAllRanges();
79
+ selection?.addRange(range);
80
+ }
81
+ };
82
+ return {
83
+ getSelection,
84
+ getTextOffset,
85
+ getCaretOffset,
86
+ getCaretOffsetInContent,
87
+ getSelectionOffsets,
88
+ findTextPosition,
89
+ restoreCaretOffset,
90
+ restoreSelectionOffsets
91
+ };
92
+ };
@@ -0,0 +1,29 @@
1
+ import { type Ref } from 'vue';
2
+ export declare const useTable: (refContent: Ref<HTMLDivElement | null>, syncToModel: () => void) => {
3
+ activeTable: Ref<{
4
+ table: HTMLElement;
5
+ cell: HTMLElement;
6
+ } | null, {
7
+ table: HTMLElement;
8
+ cell: HTMLElement;
9
+ } | {
10
+ table: HTMLElement;
11
+ cell: HTMLElement;
12
+ } | null>;
13
+ createTableElement: (rows: number, cols: number) => HTMLElement;
14
+ tableAddRow: (position: "above" | "below") => void;
15
+ tableAddColumn: (position: "left" | "right") => void;
16
+ tableDelete: () => void;
17
+ tableDeleteRow: () => void;
18
+ tableDeleteColumn: () => void;
19
+ isHeaderCell: import("vue").ComputedRef<boolean>;
20
+ canAddHeader: import("vue").ComputedRef<boolean>;
21
+ tableAddHeader: () => void;
22
+ tableActionsStyle: import("vue").ComputedRef<{
23
+ top?: undefined;
24
+ left?: undefined;
25
+ } | {
26
+ top: string;
27
+ left: string;
28
+ }>;
29
+ };
@@ -0,0 +1,175 @@
1
+ import { ref, computed } from "vue";
2
+ export const useTable = (refContent, syncToModel) => {
3
+ const activeTable = ref(null);
4
+ const createTableElement = (rows, cols) => {
5
+ const table = document.createElement("table");
6
+ table.className = "an-editor__table";
7
+ const thead = document.createElement("thead");
8
+ const headerRow = document.createElement("tr");
9
+ for (let c = 0; c < cols; c++) {
10
+ const th = document.createElement("th");
11
+ th.innerHTML = "<br>";
12
+ headerRow.appendChild(th);
13
+ }
14
+ thead.appendChild(headerRow);
15
+ table.appendChild(thead);
16
+ const tbody = document.createElement("tbody");
17
+ for (let r = 0; r < rows; r++) {
18
+ const tr = document.createElement("tr");
19
+ for (let c = 0; c < cols; c++) {
20
+ const td = document.createElement("td");
21
+ td.innerHTML = "<br>";
22
+ tr.appendChild(td);
23
+ }
24
+ tbody.appendChild(tr);
25
+ }
26
+ table.appendChild(tbody);
27
+ return table;
28
+ };
29
+ const tableAddRow = (position) => {
30
+ if (!activeTable.value) return;
31
+ const { cell } = activeTable.value;
32
+ const row = cell.parentElement;
33
+ const isHeader = row.parentElement?.tagName.toLowerCase() === "thead";
34
+ const colCount = row.cells.length;
35
+ const newRow = document.createElement("tr");
36
+ for (let i = 0; i < colCount; i++) {
37
+ const td = document.createElement("td");
38
+ td.innerHTML = "<br>";
39
+ newRow.appendChild(td);
40
+ }
41
+ if (isHeader) {
42
+ const tbody = row.closest("table")?.querySelector("tbody");
43
+ if (tbody) {
44
+ tbody.insertBefore(newRow, tbody.firstChild);
45
+ }
46
+ } else {
47
+ if (position === "above") {
48
+ row.parentNode.insertBefore(newRow, row);
49
+ } else {
50
+ row.parentNode.insertBefore(newRow, row.nextSibling);
51
+ }
52
+ }
53
+ activeTable.value = null;
54
+ syncToModel();
55
+ };
56
+ const tableAddColumn = (position) => {
57
+ if (!activeTable.value) return;
58
+ const { table, cell } = activeTable.value;
59
+ const colIndex = Array.from(cell.parentElement.children).indexOf(cell);
60
+ for (const row of table.querySelectorAll("tr")) {
61
+ const cells = row.children;
62
+ const isHeader = row.parentElement?.tagName.toLowerCase() === "thead";
63
+ const newCell = document.createElement(isHeader ? "th" : "td");
64
+ newCell.innerHTML = "<br>";
65
+ const refCell = cells[colIndex];
66
+ if (position === "left") {
67
+ row.insertBefore(newCell, refCell || null);
68
+ } else {
69
+ row.insertBefore(newCell, refCell?.nextSibling ?? null);
70
+ }
71
+ }
72
+ activeTable.value = null;
73
+ syncToModel();
74
+ };
75
+ const tableDelete = () => {
76
+ if (!activeTable.value) return;
77
+ const { table } = activeTable.value;
78
+ const next = table.nextElementSibling;
79
+ table.remove();
80
+ if (next && next.tagName === "P" && (!next.textContent || next.textContent.trim() === "") && next.innerHTML === "<br>") {
81
+ next.remove();
82
+ }
83
+ activeTable.value = null;
84
+ syncToModel();
85
+ };
86
+ const tableDeleteRow = () => {
87
+ if (!activeTable.value) return;
88
+ const { table, cell } = activeTable.value;
89
+ const row = cell.parentElement;
90
+ const isHeader = row.parentElement?.tagName.toLowerCase() === "thead";
91
+ if (isHeader) {
92
+ const thead = row.parentElement;
93
+ thead.remove();
94
+ activeTable.value = null;
95
+ syncToModel();
96
+ return;
97
+ }
98
+ const tbody = table.querySelector("tbody");
99
+ if (tbody && tbody.querySelectorAll("tr").length <= 1) {
100
+ tableDelete();
101
+ return;
102
+ }
103
+ row.remove();
104
+ activeTable.value = null;
105
+ syncToModel();
106
+ };
107
+ const tableDeleteColumn = () => {
108
+ if (!activeTable.value) return;
109
+ const { table, cell } = activeTable.value;
110
+ const colIndex = Array.from(cell.parentElement.children).indexOf(cell);
111
+ const allRows = table.querySelectorAll("tr");
112
+ const colCount = allRows[0]?.children.length ?? 0;
113
+ if (colCount <= 1) {
114
+ tableDelete();
115
+ return;
116
+ }
117
+ for (const row of allRows) {
118
+ const cellToRemove = row.children[colIndex];
119
+ if (cellToRemove) cellToRemove.remove();
120
+ }
121
+ activeTable.value = null;
122
+ syncToModel();
123
+ };
124
+ const isHeaderCell = computed(() => {
125
+ if (!activeTable.value) return false;
126
+ const row = activeTable.value.cell.parentElement;
127
+ return row?.parentElement?.tagName.toLowerCase() === "thead";
128
+ });
129
+ const canAddHeader = computed(() => {
130
+ if (!activeTable.value) return false;
131
+ const { table } = activeTable.value;
132
+ return !table.querySelector("thead");
133
+ });
134
+ const tableAddHeader = () => {
135
+ if (!activeTable.value) return;
136
+ const { table } = activeTable.value;
137
+ if (table.querySelector("thead")) return;
138
+ const firstRow = table.querySelector("tbody > tr");
139
+ const colCount = firstRow?.children.length ?? 1;
140
+ const thead = document.createElement("thead");
141
+ const tr = document.createElement("tr");
142
+ for (let i = 0; i < colCount; i++) {
143
+ const th = document.createElement("th");
144
+ th.innerHTML = "<br>";
145
+ tr.appendChild(th);
146
+ }
147
+ thead.appendChild(tr);
148
+ table.insertBefore(thead, table.firstChild);
149
+ activeTable.value = null;
150
+ syncToModel();
151
+ };
152
+ const tableActionsStyle = computed(() => {
153
+ if (!activeTable.value) return {};
154
+ const rect = activeTable.value.cell.getBoundingClientRect();
155
+ const editorRect = refContent.value?.getBoundingClientRect();
156
+ if (!editorRect) return {};
157
+ return {
158
+ top: `${rect.bottom - editorRect.top + 4}px`,
159
+ left: `${rect.left - editorRect.left}px`
160
+ };
161
+ });
162
+ return {
163
+ activeTable,
164
+ createTableElement,
165
+ tableAddRow,
166
+ tableAddColumn,
167
+ tableDelete,
168
+ tableDeleteRow,
169
+ tableDeleteColumn,
170
+ isHeaderCell,
171
+ canAddHeader,
172
+ tableAddHeader,
173
+ tableActionsStyle
174
+ };
175
+ };
@@ -0,0 +1,8 @@
1
+ import type { ModuleOptions } from '../../module'
2
+
3
+
4
+ declare module 'nuxt/schema' {
5
+ interface NuxtConfig {
6
+ aneditor?: ModuleOptions
7
+ }
8
+ }
@@ -0,0 +1 @@
1
+ export * from './global.js';
@@ -0,0 +1 @@
1
+ export * from "./global";
@@ -0,0 +1,3 @@
1
+ export * from './parseMarkdown.js';
2
+ export * from './toMarkdown.js';
3
+ export * from './youtube.js';
@@ -0,0 +1,3 @@
1
+ export * from "./parseMarkdown.js";
2
+ export * from "./toMarkdown.js";
3
+ export * from "./youtube.js";
@@ -0,0 +1 @@
1
+ export declare const parseMarkdown: (md: string) => string;
@@ -0,0 +1,184 @@
1
+ import { extractYouTubeId } from "./youtube.js";
2
+ export const parseMarkdown = (md) => {
3
+ if (!md || !md.trim()) return "";
4
+ const placeholders = /* @__PURE__ */ new Map();
5
+ let idx = 0;
6
+ const ph = (html) => {
7
+ const key = `\0PH${idx++}\0`;
8
+ placeholders.set(key, html);
9
+ return key;
10
+ };
11
+ let text = md.replace(/```(\w*)\n([\s\S]*?)```/g, (_match, lang, code) => {
12
+ const cls = lang ? ` class="language-${escapeHtml(lang)}"` : "";
13
+ return ph(`<pre class="an-editor__codeblock"><code${cls}>${escapeHtml(code.replace(/\n$/, ""))}</code></pre>`);
14
+ });
15
+ text = text.replace(/`([^`]+)`/g, (_match, code) => {
16
+ return ph(`<code class="an-editor__code">${escapeHtml(code)}</code>`);
17
+ });
18
+ const blocks = text.split(/\n{2,}/);
19
+ const output = [];
20
+ for (let i = 0; i < blocks.length; i++) {
21
+ const block = blocks[i].trim();
22
+ if (!block) continue;
23
+ if (/^(-{3,}|\*{3,}|_{3,})$/.test(block)) {
24
+ output.push("<hr>");
25
+ continue;
26
+ }
27
+ const headingMatch = block.match(/^(#{1,6})\s+(.+)$/);
28
+ if (headingMatch) {
29
+ const level = headingMatch[1].length;
30
+ output.push(`<h${level}>${processInline(headingMatch[2])}</h${level}>`);
31
+ continue;
32
+ }
33
+ const blockLines = block.split("\n");
34
+ if (isListLine(blockLines[0])) {
35
+ const allList = blockLines.every((l) => isListLine(l));
36
+ if (allList) {
37
+ const { items } = parseListBlock(blockLines, 0);
38
+ output.push(renderList(items));
39
+ continue;
40
+ }
41
+ }
42
+ if (blockLines.every((l) => /^>\s?/.test(l))) {
43
+ const content = blockLines.map((l) => l.replace(/^>\s?/, "")).join("\n");
44
+ output.push(`<blockquote>${processInline(content)}</blockquote>`);
45
+ continue;
46
+ }
47
+ const tableHtml = parseTable(block);
48
+ if (tableHtml) {
49
+ output.push(tableHtml);
50
+ continue;
51
+ }
52
+ const ytId = extractYouTubeId(block);
53
+ if (ytId && /^https?:\/\//.test(block.trim())) {
54
+ output.push(`<div class="an-editor__youtube"><iframe src="https://www.youtube-nocookie.com/embed/${ytId}" style="border:none" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>`);
55
+ continue;
56
+ }
57
+ const imgMatch = block.match(/^!\[([^\]]*)\]\(([^)]+)\)$/);
58
+ if (imgMatch) {
59
+ output.push(`<figure class="an-editor__image"><img src="${escapeHtml(imgMatch[2])}" alt="${escapeHtml(imgMatch[1])}" /></figure>`);
60
+ continue;
61
+ }
62
+ const imgHtmlMatch = block.match(/^<img\s+([^>]+?)\s*\/?>$/);
63
+ if (imgHtmlMatch) {
64
+ const attrs = imgHtmlMatch[1];
65
+ const srcM = attrs.match(/src="([^"]*)"/);
66
+ const altM = attrs.match(/alt="([^"]*)"/);
67
+ const widthM = attrs.match(/width="([^"]*)"/);
68
+ if (srcM) {
69
+ const src = escapeHtml(srcM[1]);
70
+ const alt = altM ? escapeHtml(altM[1]) : "";
71
+ const widthAttr = widthM ? ` style="width:${escapeHtml(widthM[1])}"` : "";
72
+ output.push(`<figure class="an-editor__image"><img src="${src}" alt="${alt}"${widthAttr} /></figure>`);
73
+ continue;
74
+ }
75
+ }
76
+ const lines = block.split("\n").map((l) => processInline(l)).join("<br>");
77
+ output.push(`<p>${lines}</p>`);
78
+ }
79
+ let result = output.join("\n");
80
+ for (const [key, value] of placeholders) {
81
+ result = result.replaceAll(key, value);
82
+ }
83
+ return result;
84
+ };
85
+ const escapeHtml = (text) => {
86
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
87
+ };
88
+ const processInline = (text) => {
89
+ text = text.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1" />');
90
+ text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
91
+ text = text.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
92
+ text = text.replace(/\*(.+?)\*/g, "<em>$1</em>");
93
+ text = text.replace(/~~(.+?)~~/g, "<s>$1</s>");
94
+ return text;
95
+ };
96
+ const parseListBlock = (lines, startIndent = 0) => {
97
+ const items = [];
98
+ let i = 0;
99
+ while (i < lines.length) {
100
+ const line = lines[i];
101
+ const indentMatch = line.match(/^(\s*)/);
102
+ const indent = indentMatch ? indentMatch[1].length : 0;
103
+ const ulMatch = line.match(/^(\s*)[-*]\s+(.*)$/);
104
+ const olMatch = line.match(/^(\s*)\d+\.\s+(.*)$/);
105
+ const match = ulMatch || olMatch;
106
+ if (!match) break;
107
+ const itemIndent = match[1].length;
108
+ if (itemIndent < startIndent && i > 0) break;
109
+ if (itemIndent > startIndent) {
110
+ const nested = parseListBlock(lines.slice(i), itemIndent);
111
+ if (items.length > 0) {
112
+ items[items.length - 1].children = nested.items;
113
+ }
114
+ i += nested.consumed;
115
+ continue;
116
+ }
117
+ const content = match[2];
118
+ const type = ulMatch ? "ul" : "ol";
119
+ items.push({ content, children: [], type });
120
+ i++;
121
+ }
122
+ return { items, consumed: i };
123
+ };
124
+ const renderList = (items) => {
125
+ if (items.length === 0) return "";
126
+ const type = items[0].type;
127
+ let html = `<${type}>`;
128
+ for (const item of items) {
129
+ html += `<li>`;
130
+ html += processInline(item.content);
131
+ if (item.children.length > 0) {
132
+ html += renderList(item.children);
133
+ }
134
+ html += "</li>";
135
+ }
136
+ html += `</${type}>`;
137
+ return html;
138
+ };
139
+ const isListLine = (line) => {
140
+ return /^\s*[-*]\s+/.test(line) || /^\s*\d+\.\s+/.test(line);
141
+ };
142
+ const parseTable = (block) => {
143
+ const lines = block.split("\n").filter((l) => l.trim());
144
+ if (lines.length < 2) return null;
145
+ const sepLine = lines[1].trim();
146
+ if (!/^\|?(\s*:?-+:?\s*\|)+\s*:?-+:?\s*\|?$/.test(sepLine)) return null;
147
+ const parseCells = (line) => {
148
+ let trimmed = line.trim();
149
+ if (trimmed.startsWith("|")) trimmed = trimmed.slice(1);
150
+ if (trimmed.endsWith("|")) trimmed = trimmed.slice(0, -1);
151
+ return trimmed.split("|").map((c) => c.trim());
152
+ };
153
+ const parseAlign = (line) => {
154
+ return parseCells(line).map((cell) => {
155
+ const t = cell.trim();
156
+ const left = t.startsWith(":");
157
+ const right = t.endsWith(":");
158
+ if (left && right) return "center";
159
+ if (right) return "right";
160
+ return "left";
161
+ });
162
+ };
163
+ const headers = parseCells(lines[0]);
164
+ const aligns = parseAlign(lines[1]);
165
+ let html = '<table class="an-editor__table"><thead><tr>';
166
+ headers.forEach((h, i) => {
167
+ const align = aligns[i] && aligns[i] !== "left" ? ` style="text-align:${aligns[i]}"` : "";
168
+ const content = processInline(h) || "<br>";
169
+ html += `<th${align}>${content}</th>`;
170
+ });
171
+ html += "</tr></thead><tbody>";
172
+ for (let r = 2; r < lines.length; r++) {
173
+ const cells = parseCells(lines[r]);
174
+ html += "<tr>";
175
+ cells.forEach((c, ci) => {
176
+ const align = aligns[ci] && aligns[ci] !== "left" ? ` style="text-align:${aligns[ci]}"` : "";
177
+ const content = processInline(c) || "<br>";
178
+ html += `<td${align}>${content}</td>`;
179
+ });
180
+ html += "</tr>";
181
+ }
182
+ html += "</tbody></table>";
183
+ return html;
184
+ };
@@ -0,0 +1 @@
1
+ export declare const toMarkdown: (html: string) => string;