mdsquare 0.0.0-stage → 0.2.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.
Files changed (95) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +218 -2
  3. package/dist/callout.d.ts +17 -0
  4. package/dist/callout.js +541 -0
  5. package/dist/calloutHeader.d.ts +47 -0
  6. package/dist/calloutHeader.js +75 -0
  7. package/dist/codeBlock.d.ts +78 -0
  8. package/dist/codeBlock.js +686 -0
  9. package/dist/codeTheme.d.ts +47 -0
  10. package/dist/codeTheme.js +394 -0
  11. package/dist/delimiter.d.ts +20 -0
  12. package/dist/delimiter.js +73 -0
  13. package/dist/editor.d.ts +132 -0
  14. package/dist/editor.js +225 -0
  15. package/dist/emphasis.d.ts +6 -0
  16. package/dist/emphasis.js +101 -0
  17. package/dist/escape.d.ts +4 -0
  18. package/dist/escape.js +39 -0
  19. package/dist/extensions.d.ts +74 -0
  20. package/dist/extensions.js +81 -0
  21. package/dist/footnote.d.ts +24 -0
  22. package/dist/footnote.js +456 -0
  23. package/dist/footnoteSyntax.d.ts +23 -0
  24. package/dist/footnoteSyntax.js +107 -0
  25. package/dist/hashtag.d.ts +4 -0
  26. package/dist/hashtag.js +64 -0
  27. package/dist/hashtagSyntax.d.ts +36 -0
  28. package/dist/hashtagSyntax.js +99 -0
  29. package/dist/heading.d.ts +7 -0
  30. package/dist/heading.js +159 -0
  31. package/dist/highlight.d.ts +5 -0
  32. package/dist/highlight.js +6 -0
  33. package/dist/horizontalRule.d.ts +21 -0
  34. package/dist/horizontalRule.js +142 -0
  35. package/dist/image.d.ts +58 -0
  36. package/dist/image.js +344 -0
  37. package/dist/index.d.ts +16 -0
  38. package/dist/index.js +16 -0
  39. package/dist/inlineCode.d.ts +4 -0
  40. package/dist/inlineCode.js +68 -0
  41. package/dist/link.d.ts +47 -0
  42. package/dist/link.js +217 -0
  43. package/dist/list.d.ts +14 -0
  44. package/dist/list.js +136 -0
  45. package/dist/listEnter.d.ts +20 -0
  46. package/dist/listEnter.js +72 -0
  47. package/dist/listIndent.d.ts +23 -0
  48. package/dist/listIndent.js +335 -0
  49. package/dist/markVision.d.ts +17 -0
  50. package/dist/markVision.js +56 -0
  51. package/dist/math.d.ts +45 -0
  52. package/dist/math.js +233 -0
  53. package/dist/mathSyntax.d.ts +24 -0
  54. package/dist/mathSyntax.js +179 -0
  55. package/dist/mention.d.ts +4 -0
  56. package/dist/mention.js +58 -0
  57. package/dist/mentionSyntax.d.ts +14 -0
  58. package/dist/mentionSyntax.js +71 -0
  59. package/dist/quote.d.ts +9 -0
  60. package/dist/quote.js +122 -0
  61. package/dist/setup.d.ts +13 -0
  62. package/dist/setup.js +40 -0
  63. package/dist/style.css +6 -0
  64. package/dist/style.css.d.ts +2 -0
  65. package/dist/table.d.ts +38 -0
  66. package/dist/table.js +960 -0
  67. package/dist/tableEdit.d.ts +30 -0
  68. package/dist/tableEdit.js +305 -0
  69. package/dist/tableMenu.d.ts +65 -0
  70. package/dist/tableMenu.js +1125 -0
  71. package/dist/tableModel.d.ts +168 -0
  72. package/dist/tableModel.js +433 -0
  73. package/dist/tableState.d.ts +36 -0
  74. package/dist/tableState.js +96 -0
  75. package/dist/task.d.ts +13 -0
  76. package/dist/task.js +179 -0
  77. package/dist/textMate.d.ts +49 -0
  78. package/dist/textMate.js +284 -0
  79. package/dist/tikz.d.ts +56 -0
  80. package/dist/tikz.js +343 -0
  81. package/dist/underline.d.ts +5 -0
  82. package/dist/underline.js +6 -0
  83. package/dist/utils/blockMargin.d.ts +6 -0
  84. package/dist/utils/blockMargin.js +6 -0
  85. package/dist/utils/codePoint.d.ts +13 -0
  86. package/dist/utils/codePoint.js +43 -0
  87. package/dist/utils/container.d.ts +10 -0
  88. package/dist/utils/container.js +10 -0
  89. package/dist/utils/reveal.d.ts +99 -0
  90. package/dist/utils/reveal.js +161 -0
  91. package/dist/utils/selection.d.ts +33 -0
  92. package/dist/utils/selection.js +45 -0
  93. package/dist/utils/syntaxTree.d.ts +18 -0
  94. package/dist/utils/syntaxTree.js +23 -0
  95. package/package.json +79 -4
@@ -0,0 +1,24 @@
1
+ import type { MarkdownConfig } from "@lezer/markdown";
2
+ /**
3
+ * LaTeX 수식 문법입니다.
4
+ *
5
+ * 인라인 `$x^2$` (문단 안의 `$$x$$`도 인라인 노드, 디스플레이 모양으로 그림):
6
+ * ```
7
+ * InlineMath
8
+ * InlineMathMark "$" 또는 "$$"
9
+ * MathText "x^2"
10
+ * InlineMathMark
11
+ * ```
12
+ * 블록 (줄 맨 앞의 `$$`, 한 줄 `$$ x $$`도 가능):
13
+ * ```
14
+ * BlockMath
15
+ * BlockMathMark "$$"
16
+ * MathText 줄마다 (인용 기호 등 컨테이너 기호는 제외)
17
+ * BlockMathMark "$$" (닫히지 않았으면 없음)
18
+ * ```
19
+ * - 인라인 `$`: 여는 `$` 바로 뒤와 닫는 `$` 바로 앞은 공백이 아니어야 하고, 닫는 `$` 바로 뒤는 숫자가 아니어야
20
+ * 합니다. (Pandoc 규칙, `$5와 $6`은 수식 아님)
21
+ * - `\$`는 수식 기호가 아닙니다. 수식 안의 `\`는 다음 글자와 함께 건너뜁니다.
22
+ * - 블록은 문단을 빈 줄 없이 끊고 시작할 수 있습니다.
23
+ */
24
+ export declare const MathSyntax: MarkdownConfig;
@@ -0,0 +1,179 @@
1
+ import { styleTags, tags as t } from "@lezer/highlight";
2
+ const Dollar = 36; // "$"
3
+ const Backslash = 92; // "\"
4
+ const isSpace = (code) => code === 32 || code === 9 || code === 10 || code === 13;
5
+ const isDigit = (code) => code >= 48 && code <= 57;
6
+ /** text[from..]에서 이스케이프되지 않은 `$$`의 위치 (없으면 -1) */
7
+ function findDoubleDollar(text, from) {
8
+ for (let i = from; i < text.length - 1; i++) {
9
+ const code = text.charCodeAt(i);
10
+ if (code === Backslash)
11
+ i++;
12
+ else if (code === Dollar && text.charCodeAt(i + 1) === Dollar)
13
+ return i;
14
+ }
15
+ return -1;
16
+ }
17
+ /** 줄의 line.pos가 블록 수식 시작(`$$`)인지. 같은 줄에서 닫히면 닫는 `$$` 뒤에 다른 글자가 없어야 합니다. */
18
+ function isBlockMathStart(line) {
19
+ if (line.next !== Dollar || line.text.charCodeAt(line.pos + 1) !== Dollar)
20
+ return false;
21
+ if (line.indent - line.baseIndent >= 4)
22
+ return false; // 들여쓴 코드 블록
23
+ const close = findDoubleDollar(line.text, line.pos + 2);
24
+ return close < 0 || line.skipSpace(close + 2) === line.text.length;
25
+ }
26
+ // ---------------------------------------------------------------------------
27
+ // 인라인: $...$, $$...$$
28
+ // ---------------------------------------------------------------------------
29
+ /**
30
+ * 인라인 구간별로 "이 위치부터는 닫는 기호가 없다"를 기억합니다. ([한 겹, 두 겹])
31
+ * 닫는 기호의 조건은 여는 기호와 무관하므로, 앞에서 찾지 못했다면 뒤의 여는 기호도 찾지 못합니다.
32
+ * (`$5, $6, $7 …`처럼 짝 없는 `$`가 많은 문단에서 매번 끝까지 훑는 O(n²)을 막습니다)
33
+ */
34
+ const noCloser = new WeakMap();
35
+ function parseInlineMath(cx, next, pos) {
36
+ if (next !== Dollar)
37
+ return -1;
38
+ const double = cx.char(pos + 1) === Dollar;
39
+ const open = double ? 2 : 1;
40
+ const contentFrom = pos + open;
41
+ const first = cx.char(contentFrom);
42
+ // 여는 기호 바로 뒤가 공백이거나 끝이면 수식이 아닙니다. (`$ 5` 등)
43
+ if (contentFrom >= cx.end || isSpace(first) || (double && first === Dollar))
44
+ return -1;
45
+ const memo = noCloser.get(cx);
46
+ if (memo && pos >= memo[double ? 1 : 0])
47
+ return -1;
48
+ let close = -1;
49
+ for (let i = contentFrom; i < cx.end; i++) {
50
+ const code = cx.char(i);
51
+ if (code === Backslash) {
52
+ i++;
53
+ continue;
54
+ }
55
+ if (code !== Dollar)
56
+ continue;
57
+ if (double) {
58
+ if (cx.char(i + 1) === Dollar) {
59
+ close = i;
60
+ break;
61
+ }
62
+ }
63
+ else if (!isSpace(cx.char(i - 1)) && !isDigit(cx.char(i + 1))) {
64
+ // 닫는 `$`: 바로 앞이 공백이 아니고 바로 뒤가 숫자가 아님 (`$5와 $6` 같은 금액 오인식 방지)
65
+ close = i;
66
+ break;
67
+ }
68
+ }
69
+ if (close < 0) {
70
+ const value = memo ?? [Infinity, Infinity];
71
+ value[double ? 1 : 0] = Math.min(value[double ? 1 : 0], pos);
72
+ noCloser.set(cx, value);
73
+ return -1;
74
+ }
75
+ return cx.addElement(cx.elt("InlineMath", pos, close + open, [
76
+ cx.elt("InlineMathMark", pos, contentFrom),
77
+ cx.elt("MathText", contentFrom, close),
78
+ cx.elt("InlineMathMark", close, close + open),
79
+ ]));
80
+ }
81
+ // ---------------------------------------------------------------------------
82
+ // 블록: $$ ... $$
83
+ // ---------------------------------------------------------------------------
84
+ /**
85
+ * 현재 줄이 이 블록을 담은 컨테이너(인용, 목록) 안에 계속 있는지.
86
+ * 공개 API에는 이 정보가 없어서, @lezer/markdown의 FencedCode와 같은 내부 필드(line.depth, cx.stack)를
87
+ * 읽습니다. 내부 필드가 없어지면(라이브러리 변경) 컨테이너 끝을 확인하지 않습니다.
88
+ * (테스트 "ends a block inside a quote where the quote ends"가 이 변화를 잡아냅니다)
89
+ */
90
+ function inSameContainer(cx, line) {
91
+ const depth = line.depth;
92
+ const stack = cx.stack;
93
+ return depth === undefined || stack === undefined || depth >= stack.length;
94
+ }
95
+ function parseBlockMath(cx, line) {
96
+ if (!isBlockMathStart(line))
97
+ return false;
98
+ const start = cx.lineStart + line.pos;
99
+ const children = [cx.elt("BlockMathMark", start, start + 2)];
100
+ const text = (from, to) => {
101
+ if (line.skipSpace(from) < to)
102
+ children.push(cx.elt("MathText", cx.lineStart + from, cx.lineStart + to));
103
+ };
104
+ // 한 줄: $$ x $$
105
+ const sameLine = findDoubleDollar(line.text, line.pos + 2);
106
+ if (sameLine >= 0) {
107
+ text(line.pos + 2, sameLine);
108
+ const end = cx.lineStart + sameLine + 2;
109
+ children.push(cx.elt("BlockMathMark", end - 2, end));
110
+ cx.nextLine();
111
+ cx.addElement(cx.elt("BlockMath", start, end, children));
112
+ return true;
113
+ }
114
+ text(line.pos + 2, line.text.length);
115
+ let end = cx.lineStart + line.text.length;
116
+ // 닫는 `$$`까지. 닫히지 않으면 담고 있는 블록(인용, 목록)이나 문서 끝까지입니다. (펜스 코드와 같음)
117
+ while (cx.nextLine() && inSameContainer(cx, line)) {
118
+ for (const marker of line.markers)
119
+ children.push(marker);
120
+ const close = findDoubleDollar(line.text, line.basePos);
121
+ if (close >= 0 && line.skipSpace(close + 2) === line.text.length) {
122
+ text(line.basePos, close);
123
+ end = cx.lineStart + close + 2;
124
+ children.push(cx.elt("BlockMathMark", end - 2, end));
125
+ cx.nextLine();
126
+ break;
127
+ }
128
+ text(line.basePos, line.text.length);
129
+ end = cx.lineStart + line.text.length;
130
+ }
131
+ cx.addElement(cx.elt("BlockMath", start, end, children));
132
+ return true;
133
+ }
134
+ /**
135
+ * LaTeX 수식 문법입니다.
136
+ *
137
+ * 인라인 `$x^2$` (문단 안의 `$$x$$`도 인라인 노드, 디스플레이 모양으로 그림):
138
+ * ```
139
+ * InlineMath
140
+ * InlineMathMark "$" 또는 "$$"
141
+ * MathText "x^2"
142
+ * InlineMathMark
143
+ * ```
144
+ * 블록 (줄 맨 앞의 `$$`, 한 줄 `$$ x $$`도 가능):
145
+ * ```
146
+ * BlockMath
147
+ * BlockMathMark "$$"
148
+ * MathText 줄마다 (인용 기호 등 컨테이너 기호는 제외)
149
+ * BlockMathMark "$$" (닫히지 않았으면 없음)
150
+ * ```
151
+ * - 인라인 `$`: 여는 `$` 바로 뒤와 닫는 `$` 바로 앞은 공백이 아니어야 하고, 닫는 `$` 바로 뒤는 숫자가 아니어야
152
+ * 합니다. (Pandoc 규칙, `$5와 $6`은 수식 아님)
153
+ * - `\$`는 수식 기호가 아닙니다. 수식 안의 `\`는 다음 글자와 함께 건너뜁니다.
154
+ * - 블록은 문단을 빈 줄 없이 끊고 시작할 수 있습니다.
155
+ */
156
+ export const MathSyntax = {
157
+ defineNodes: [
158
+ { name: "InlineMath" },
159
+ { name: "InlineMathMark" },
160
+ { name: "BlockMath", block: true },
161
+ { name: "BlockMathMark" },
162
+ { name: "MathText" },
163
+ ],
164
+ props: [
165
+ styleTags({
166
+ "InlineMathMark BlockMathMark": t.processingInstruction,
167
+ MathText: t.monospace,
168
+ }),
169
+ ],
170
+ parseInline: [{ name: "InlineMath", parse: parseInlineMath, before: "Escape" }],
171
+ parseBlock: [
172
+ {
173
+ name: "BlockMath",
174
+ parse: parseBlockMath,
175
+ endLeaf: (_cx, line) => isBlockMathStart(line),
176
+ before: "FencedCode",
177
+ },
178
+ ],
179
+ };
@@ -0,0 +1,4 @@
1
+ import type { Extension } from "@codemirror/state";
2
+ export declare const mentionPlugin: import("@codemirror/view").ViewPlugin<import("./extensions.js").RevealPluginValue, undefined>;
3
+ /** `@user` 멘션에 스타일을 적용하는 확장입니다. */
4
+ export declare function mention(): Extension;
@@ -0,0 +1,58 @@
1
+ import { Decoration, EditorView } from "@codemirror/view";
2
+ import { createRevealPlugin } from "./utils/reveal.js";
3
+ // 데코레이션 객체는 모듈 로드 시 한 번만 만들고 재사용합니다.
4
+ const begin = Decoration.mark({ class: "cm-mention cm-mention-begin" });
5
+ const end = Decoration.mark({ class: "cm-mention cm-mention-end" });
6
+ const beginRevealed = Decoration.mark({
7
+ class: "cm-formatting cm-formatting-mention cm-mention cm-mention-begin",
8
+ });
9
+ const endRevealed = Decoration.mark({
10
+ class: "cm-formatting cm-formatting-mention cm-mention cm-mention-end",
11
+ });
12
+ /** 멘션이 올 수 없으므로 자식 순회를 생략하는 노드 */
13
+ const SKIP_CHILDREN = new Set([
14
+ "FencedCode",
15
+ "CodeBlock",
16
+ "InlineCode",
17
+ "HTMLBlock",
18
+ "CommentBlock",
19
+ "URL",
20
+ "Autolink",
21
+ ]);
22
+ export const mentionPlugin = createRevealPlugin({
23
+ // 멘션은 서로 겹치지 않고 문서 순서대로 추가되므로 정렬이 필요 없습니다.
24
+ visit({ decorations, isRevealed }) {
25
+ return {
26
+ enter(node) {
27
+ if (node.name === "Mention") {
28
+ const revealed = isRevealed(node.from, node.to);
29
+ // 파서가 MentionMark를 항상 '@' 한 글자로 만듭니다.
30
+ decorations.push((revealed ? beginRevealed : begin).range(node.from, node.from + 1), (revealed ? endRevealed : end).range(node.from + 1, node.to));
31
+ return false;
32
+ }
33
+ if (SKIP_CHILDREN.has(node.name))
34
+ return false;
35
+ },
36
+ };
37
+ },
38
+ });
39
+ const mentionTheme = EditorView.baseTheme({
40
+ ".cm-mention": {
41
+ backgroundColor: "rgba(135, 131, 120, 0.15)",
42
+ // `.cm-mention *`로 지정하면 안쪽 span이 있을 때만 적용되어
43
+ // (구문 강조 span이 없으면 적용되지 않음) 글자 크기가 바뀌지 않으므로 직접 지정합니다.
44
+ fontSize: "0.75rem",
45
+ },
46
+ ".cm-mention.cm-mention-begin": {
47
+ borderRadius: "2px 0 0 2px",
48
+ padding: "1px 0 1px 4px",
49
+ },
50
+ ".cm-mention.cm-mention-end": {
51
+ borderRadius: "0 2px 2px 0",
52
+ padding: "1px 4px 1px 0",
53
+ },
54
+ });
55
+ /** `@user` 멘션에 스타일을 적용하는 확장입니다. */
56
+ export function mention() {
57
+ return [mentionPlugin, mentionTheme];
58
+ }
@@ -0,0 +1,14 @@
1
+ import type { MarkdownConfig } from "@lezer/markdown";
2
+ import { Tag } from "@lezer/highlight";
3
+ /** `@user` 멘션에 사용할 하이라이트 태그 */
4
+ export declare const mentionTag: Tag;
5
+ /**
6
+ * `@user` 인라인 멘션 문법입니다. (노드: Mention > MentionMark)
7
+ *
8
+ * - `@` 앞: 인라인 구간의 시작(줄 시작 등) 또는 공백만 허용
9
+ * (`a@b`, `user@example.com`, `(@user)`는 멘션이 아님)
10
+ * - 이름: Unicode 문자/숫자/결합 문자와 `_`, `-`
11
+ * `.`은 이름 문자 사이에만 허용 (`@john.doe`는 허용, `@john.`은 `@john`까지만)
12
+ * - 이름에 문자(letter)가 최소 1개 있어야 합니다. (`@123`은 멘션이 아님)
13
+ */
14
+ export declare const Mention: MarkdownConfig;
@@ -0,0 +1,71 @@
1
+ import { Tag, tags } from "@lezer/highlight";
2
+ import { codePointAt, codePointBefore, createClassifier, sizeOf } from "./utils/codePoint.js";
3
+ /** `@user` 멘션에 사용할 하이라이트 태그 */
4
+ export const mentionTag = Tag.define(tags.content);
5
+ const At = 64; // "@"
6
+ const Dot = 46; // "."
7
+ // ---------------------------------------------------------------------------
8
+ // 문자 분류 (코드 포인트 단위, ASCII는 테이블 조회)
9
+ // ---------------------------------------------------------------------------
10
+ const NAME = 1; // 이름에 올 수 있는 문자 ('.' 제외)
11
+ const LETTER = 2; // 문자(letter). 이름에 최소 1개 필요 (@123 같은 숫자만은 제외)
12
+ const SPACE = 4; // '@' 앞에 올 수 있는 공백
13
+ const NameRe = /[\p{L}\p{N}\p{M}_-]/u;
14
+ const LetterRe = /\p{L}/u;
15
+ const SpaceRe = /\s/u;
16
+ const flagsOf = createClassifier((ch) => (NameRe.test(ch) ? NAME : 0) | (LetterRe.test(ch) ? LETTER : 0) | (SpaceRe.test(ch) ? SPACE : 0));
17
+ /**
18
+ * `@user` 인라인 멘션 문법입니다. (노드: Mention > MentionMark)
19
+ *
20
+ * - `@` 앞: 인라인 구간의 시작(줄 시작 등) 또는 공백만 허용
21
+ * (`a@b`, `user@example.com`, `(@user)`는 멘션이 아님)
22
+ * - 이름: Unicode 문자/숫자/결합 문자와 `_`, `-`
23
+ * `.`은 이름 문자 사이에만 허용 (`@john.doe`는 허용, `@john.`은 `@john`까지만)
24
+ * - 이름에 문자(letter)가 최소 1개 있어야 합니다. (`@123`은 멘션이 아님)
25
+ */
26
+ export const Mention = {
27
+ defineNodes: [
28
+ { name: "Mention", style: { "Mention/...": mentionTag } },
29
+ // '@'에 processingInstruction 등의 색을 따로 주지 않습니다.
30
+ // (구문 강조가 안쪽 span에 색을 직접 지정해 테마 색을 덮어쓰는 문제 방지)
31
+ { name: "MentionMark" },
32
+ ],
33
+ parseInline: [
34
+ {
35
+ name: "Mention",
36
+ parse(cx, next, pos) {
37
+ // 핫 패스: 모든 문자 위치마다 호출되므로 첫 비교로 즉시 빠져나갑니다.
38
+ if (next !== At)
39
+ return -1;
40
+ const before = codePointBefore(cx, pos);
41
+ if (before >= 0 && !(flagsOf(before) & SPACE))
42
+ return -1;
43
+ let end = pos + 1;
44
+ let hasLetter = false;
45
+ for (;;) {
46
+ // '.'으로 구분된 이름 조각 하나를 읽습니다.
47
+ const partStart = end;
48
+ let cp = codePointAt(cx, end);
49
+ let flags = flagsOf(cp);
50
+ while (flags & NAME) {
51
+ if (flags & LETTER)
52
+ hasLetter = true;
53
+ end += sizeOf(cp);
54
+ cp = codePointAt(cx, end);
55
+ flags = flagsOf(cp);
56
+ }
57
+ if (end === partStart)
58
+ break; // 빈 조각 (첫 조각이면 멘션 아님)
59
+ // 다음 조각이 이어지는 '.'만 이름에 포함합니다. ('.' 끝, '..'는 제외)
60
+ if (cp !== Dot || !(flagsOf(codePointAt(cx, end + 1)) & NAME))
61
+ break;
62
+ end += 1;
63
+ }
64
+ if (!hasLetter)
65
+ return -1;
66
+ return cx.addElement(cx.elt("Mention", pos, end, [cx.elt("MentionMark", pos, pos + 1)]));
67
+ },
68
+ after: "Emphasis",
69
+ },
70
+ ],
71
+ };
@@ -0,0 +1,9 @@
1
+ import type { Extension } from "@codemirror/state";
2
+ export declare const quotePlugin: import("@codemirror/view").ViewPlugin<import("./extensions.js").RevealPluginValue, undefined>;
3
+ /**
4
+ * 인용(`> 내용`)을 하이브리드 방식으로 보여주는 확장입니다.
5
+ * - 인용 줄에는 왼쪽 세로줄만 그립니다. (중첩 깊이만큼 세로줄 반복, 깊이 제한 없음)
6
+ * - `> ` 기호는 숨기고, 커서가 기호에 닿으면 원문을 보여줍니다.
7
+ * - 게으른 연속 줄(`>` 없이 이어지는 줄)과 인용 안의 목록/제목/코드 블록도 함께 처리합니다.
8
+ */
9
+ export declare function quote(): Extension;
package/dist/quote.js ADDED
@@ -0,0 +1,122 @@
1
+ import { Decoration, EditorView } from "@codemirror/view";
2
+ import { calloutsHandled, readCalloutHeader } from "./calloutHeader.js";
3
+ import { createRevealPlugin } from "./utils/reveal.js";
4
+ // ---------------------------------------------------------------------------
5
+ // 데코레이션
6
+ // ---------------------------------------------------------------------------
7
+ const hiddenMark = Decoration.replace({});
8
+ const revealedMark = Decoration.mark({ class: "cm-formatting cm-formatting-quote" });
9
+ /** 깊이별 줄 데코레이션 (깊이 제한이 없으므로 처음 쓸 때 만들어 재사용) */
10
+ const lineCache = new Map();
11
+ function quoteLine(depth) {
12
+ let deco = lineCache.get(depth);
13
+ if (!deco) {
14
+ deco = Decoration.line({
15
+ class: `cm-quote-line cm-quote-line-${depth}`,
16
+ // 테마에서 깊이만큼 왼쪽 세로줄을 그리는 데 씁니다.
17
+ attributes: { style: `--cm-quote-depth: ${depth}` },
18
+ });
19
+ lineCache.set(depth, deco);
20
+ }
21
+ return deco;
22
+ }
23
+ /** 인용 밖에서는 인용을 담을 수 있는 컨테이너만 내려갑니다. */
24
+ const CONTAINERS = new Set(["Document", "BulletList", "OrderedList", "ListItem"]);
25
+ const Space = 32;
26
+ // ---------------------------------------------------------------------------
27
+ // 플러그인
28
+ // ---------------------------------------------------------------------------
29
+ export const quotePlugin = createRevealPlugin({
30
+ // 줄 데코레이션을 인용 블록이 끝날 때 한꺼번에 추가하므로 정렬합니다.
31
+ sort: true,
32
+ visit({ state, decorations, isRevealed }) {
33
+ const { doc } = state;
34
+ const skipCallouts = state.facet(calloutsHandled);
35
+ /**
36
+ * 방문 중인 인용 스택.
37
+ * - bar: 세로줄을 그리는 인용인지 (callout()이 그리는 콜아웃은 false)
38
+ * - source: 커서가 들어와 원문을 보여주는 콜아웃 안인지 (모든 ">"를 보여주고 세로줄도 그리지 않음)
39
+ */
40
+ const stack = [];
41
+ let depth = 0; // 세로줄을 그리는 인용의 깊이
42
+ const inSource = () => stack.length > 0 && stack[stack.length - 1].source;
43
+ /** 줄 번호 → 그 줄을 덮는 인용의 최대 깊이 (가장 바깥 인용이 끝날 때 줄 데코레이션으로 변환) */
44
+ const lineDepth = new Map();
45
+ return {
46
+ enter(node) {
47
+ if (node.name === "Blockquote") {
48
+ // 콜아웃은 callout()이 박스로 그리므로 세로줄을 그리지 않습니다. (">" 숨김은 계속 처리)
49
+ const isCallout = skipCallouts && readCalloutHeader(state, node.node) !== null;
50
+ // 커서가 가장 바깥 콜아웃 안에 있으면 그 콜아웃 전체를 원문으로 보여줍니다. (Obsidian과 같은 편집 방식)
51
+ const source = inSource() || (isCallout && isRevealed(node.from, node.to));
52
+ const bar = !isCallout && !source;
53
+ stack.push({ bar, source });
54
+ if (!bar)
55
+ return;
56
+ depth++;
57
+ // 안쪽 인용이 나중에 방문되므로 더 깊은 값으로 덮어씁니다. (게으른 연속 줄 포함)
58
+ const first = doc.lineAt(node.from).number;
59
+ const last = doc.lineAt(node.to).number;
60
+ for (let n = first; n <= last; n++)
61
+ lineDepth.set(n, depth);
62
+ return;
63
+ }
64
+ if (node.name === "QuoteMark") {
65
+ // ">" 뒤의 공백 한 칸까지 숨겨 내용이 세로줄 옆에 붙도록 합니다.
66
+ const end = doc.sliceString(node.to, node.to + 1).charCodeAt(0) === Space ? node.to + 1 : node.to;
67
+ if (inSource() || isRevealed(node.from, node.to)) {
68
+ decorations.push(revealedMark.range(node.from, node.to));
69
+ }
70
+ else {
71
+ decorations.push(hiddenMark.range(node.from, end));
72
+ }
73
+ return false;
74
+ }
75
+ // 인용 안: 이어지는 줄의 ">"는 문단/코드 블록 등의 자식(또는 그 안의 인라인 노드)이므로
76
+ // 끝까지 내려가며 찾습니다. 인용 밖: 컨테이너만 내려갑니다.
77
+ if (stack.length === 0 && !CONTAINERS.has(node.name))
78
+ return false;
79
+ },
80
+ leave(node) {
81
+ if (node.name !== "Blockquote")
82
+ return;
83
+ if (stack.pop()?.bar)
84
+ depth--;
85
+ if (stack.length > 0)
86
+ return;
87
+ // 가장 바깥 인용이 끝나면 줄마다 하나의 줄 데코레이션을 추가합니다.
88
+ // (같은 줄에 style이 다른 줄 데코레이션이 여러 개면 하나만 남고 어느 쪽이 남을지는
89
+ // 데코레이션 순서에 달려 있어 깊이가 틀릴 수 있으므로, 최대 깊이 하나만 넣습니다.)
90
+ for (const [n, d] of lineDepth)
91
+ decorations.push(quoteLine(d).range(doc.line(n).from));
92
+ lineDepth.clear();
93
+ },
94
+ };
95
+ },
96
+ });
97
+ // ---------------------------------------------------------------------------
98
+ // 테마: 왼쪽 세로줄만 (깊이만큼 반복)
99
+ // ---------------------------------------------------------------------------
100
+ const BAR = "3px"; // 세로줄 두께
101
+ const STEP = "14px"; // 깊이 한 단계의 폭
102
+ const quoteTheme = EditorView.baseTheme({
103
+ ".cm-quote-line": {
104
+ backgroundImage: `repeating-linear-gradient(to right, var(--cm-quote-color) 0 ${BAR}, transparent ${BAR} ${STEP})`,
105
+ backgroundSize: `calc(var(--cm-quote-depth) * ${STEP}) 100%`,
106
+ backgroundRepeat: "no-repeat",
107
+ // 내용 시작 위치를 다른 블록(코드 블록 등)에도 알려줍니다. (utils/container.ts)
108
+ "--cm-container-indent": `calc(var(--cm-quote-depth) * ${STEP} + 2px)`,
109
+ paddingLeft: "var(--cm-container-indent)",
110
+ },
111
+ "&light .cm-quote-line": { "--cm-quote-color": "#d1d5db" },
112
+ "&dark .cm-quote-line": { "--cm-quote-color": "#4b5563" },
113
+ });
114
+ /**
115
+ * 인용(`> 내용`)을 하이브리드 방식으로 보여주는 확장입니다.
116
+ * - 인용 줄에는 왼쪽 세로줄만 그립니다. (중첩 깊이만큼 세로줄 반복, 깊이 제한 없음)
117
+ * - `> ` 기호는 숨기고, 커서가 기호에 닿으면 원문을 보여줍니다.
118
+ * - 게으른 연속 줄(`>` 없이 이어지는 줄)과 인용 안의 목록/제목/코드 블록도 함께 처리합니다.
119
+ */
120
+ export function quote() {
121
+ return [quotePlugin, quoteTheme];
122
+ }
@@ -0,0 +1,13 @@
1
+ import { type Extension } from "@codemirror/state";
2
+ export interface BasicSetupOptions {
3
+ /** 줄 번호와 접기 표시 (기본 false: 글쓰기 화면) */
4
+ readonly lineNumbers?: boolean;
5
+ }
6
+ /**
7
+ * 에디터 기본 구성: CodeMirror의 basicSetup에서 활성 줄 강조(markVision이 담당)를 빼고
8
+ * Tab 들여쓰기를 더했습니다. 줄 번호는 선택.
9
+ * - 실행 취소, 여러 커서, 괄호 자동 닫기, 검색(Mod-f), 같은 단어 강조, 사각형 선택(Alt-드래그)
10
+ * - Tab: 들여쓰기 (목록·표 안에서는 각 확장이 먼저 처리). 키보드로 에디터 밖으로 포커스를 옮기려면
11
+ * Ctrl-m (macOS: Alt-Shift-m)으로 Tab 이동 모드를 켠 뒤 Tab
12
+ */
13
+ export declare function basicSetup(options?: BasicSetupOptions): Extension;
package/dist/setup.js ADDED
@@ -0,0 +1,40 @@
1
+ import { autocompletion, closeBrackets, closeBracketsKeymap, completionKeymap } from "@codemirror/autocomplete";
2
+ import { defaultKeymap, history, historyKeymap, indentWithTab } from "@codemirror/commands";
3
+ import { bracketMatching, defaultHighlightStyle, foldGutter, foldKeymap, indentOnInput, syntaxHighlighting, } from "@codemirror/language";
4
+ import { highlightSelectionMatches, searchKeymap } from "@codemirror/search";
5
+ import { EditorState } from "@codemirror/state";
6
+ import { crosshairCursor, drawSelection, dropCursor, highlightActiveLineGutter, highlightSpecialChars, keymap, lineNumbers, rectangularSelection, } from "@codemirror/view";
7
+ /**
8
+ * 에디터 기본 구성: CodeMirror의 basicSetup에서 활성 줄 강조(markVision이 담당)를 빼고
9
+ * Tab 들여쓰기를 더했습니다. 줄 번호는 선택.
10
+ * - 실행 취소, 여러 커서, 괄호 자동 닫기, 검색(Mod-f), 같은 단어 강조, 사각형 선택(Alt-드래그)
11
+ * - Tab: 들여쓰기 (목록·표 안에서는 각 확장이 먼저 처리). 키보드로 에디터 밖으로 포커스를 옮기려면
12
+ * Ctrl-m (macOS: Alt-Shift-m)으로 Tab 이동 모드를 켠 뒤 Tab
13
+ */
14
+ export function basicSetup(options = {}) {
15
+ return [
16
+ options.lineNumbers ? [lineNumbers(), highlightActiveLineGutter(), foldGutter()] : [],
17
+ highlightSpecialChars(),
18
+ history(),
19
+ drawSelection(),
20
+ dropCursor(),
21
+ EditorState.allowMultipleSelections.of(true),
22
+ indentOnInput(),
23
+ syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
24
+ bracketMatching(),
25
+ closeBrackets(),
26
+ autocompletion(),
27
+ rectangularSelection(),
28
+ crosshairCursor(),
29
+ highlightSelectionMatches(),
30
+ keymap.of([
31
+ ...closeBracketsKeymap,
32
+ ...defaultKeymap,
33
+ ...searchKeymap,
34
+ ...historyKeymap,
35
+ ...foldKeymap,
36
+ ...completionKeymap,
37
+ indentWithTab,
38
+ ]),
39
+ ];
40
+ }
package/dist/style.css ADDED
@@ -0,0 +1,6 @@
1
+ /*
2
+ * mdsquare 스타일: 에디터 자체의 스타일은 JS(CodeMirror 테마)에 들어 있고,
3
+ * 여기서는 수식(KaTeX)의 글꼴과 스타일을 불러옵니다.
4
+ * 번들러(Vite, webpack 등)가 패키지 경로를 풀어 줍니다.
5
+ */
6
+ @import "katex/dist/katex.min.css";
@@ -0,0 +1,2 @@
1
+ // `import "mdsquare/style.css"`를 위한 빈 선언
2
+ export {};
@@ -0,0 +1,38 @@
1
+ import { Facet, type EditorState, type Extension, type StateCommand } from "@codemirror/state";
2
+ import type { SyntaxNode } from "@lezer/common";
3
+ import { type TableBlock, type TableRowInfo } from "./tableModel.js";
4
+ interface CellPosition {
5
+ readonly table: SyntaxNode;
6
+ readonly block: TableBlock;
7
+ readonly rows: readonly TableRowInfo[];
8
+ /** rows 안의 위치. 구분 줄이면 -1 */
9
+ readonly row: number;
10
+ readonly cell: number;
11
+ }
12
+ /** 커서가 있는 칸 (단일 빈 커서일 때만) */
13
+ export declare function cellAt(state: EditorState, pos: number): CellPosition | null;
14
+ /** Enter: 아래에 새 행. 비어 있는 마지막 행에서는 표를 끝냅니다(행을 빈 줄로). */
15
+ export declare const insertTableRow: StateCommand;
16
+ export declare const nextTableCell: StateCommand;
17
+ export declare const previousTableCell: StateCommand;
18
+ /**
19
+ * 빈 커서가 표 행의 앞 "|" 앞(접두어 포함)이나 뒤 "|" 뒤에 놓이면 첫 칸/마지막 칸 내용으로 옮깁니다.
20
+ * 키보드(←/→/Home/End/↑/↓), 클릭, 명령 등 어떤 경로든 같은 규칙을 적용합니다.
21
+ * (구분 줄과 선택 영역은 건드리지 않습니다)
22
+ */
23
+ export declare function clampTableCursor(state: EditorState, pos: number): number;
24
+ /** table()이 켜져 있는지 (다른 확장이 역할을 나눌 때) */
25
+ export declare const tablesHandled: Facet<boolean, boolean>;
26
+ /**
27
+ * GFM 표를 표 모양으로 보여주고 편집하는 확장입니다.
28
+ * - 원문은 그대로 두고 화면에서만 표로 그립니다. (`|` 숨김, 칸 테두리, 열 너비는 내용에 맞춤, 칸 안 줄바꿈)
29
+ * - 칸 안의 굵게/링크/수식 등은 다른 확장이 그대로 그립니다.
30
+ * - 구분 줄(`|---|`)은 숨기고, 열 정렬(`:-:`)은 칸 정렬로 보여줍니다.
31
+ * - 제목 행/열: 표 바로 윗줄의 `<!-- table: header-column no-header-row -->` 주석 (다른 렌더러에서는 보이지 않음).
32
+ * 첫 행/첫 열에 마우스를 올리면 나타나는 손잡이 메뉴로 켜고 끕니다.
33
+ * - Enter: 아래에 새 행 (비어 있는 마지막 행에서는 표 끝내기), Tab/Shift+Tab: 칸 이동 (마지막 칸에서 Tab은 새 행)
34
+ * - 칸 처음/끝에서 Backspace/Delete는 `|`를 지우지 않고 옆 칸으로 이동합니다.
35
+ * - 인용, 목록, 콜아웃 안의 표도 지원합니다.
36
+ */
37
+ export declare function table(): Extension;
38
+ export {};