mdsquare 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/LICENSE +21 -0
- package/README.md +206 -2
- package/dist/callout.d.ts +17 -0
- package/dist/callout.js +541 -0
- package/dist/calloutHeader.d.ts +47 -0
- package/dist/calloutHeader.js +75 -0
- package/dist/codeBlock.d.ts +78 -0
- package/dist/codeBlock.js +686 -0
- package/dist/codeTheme.d.ts +47 -0
- package/dist/codeTheme.js +394 -0
- package/dist/delimiter.d.ts +20 -0
- package/dist/delimiter.js +73 -0
- package/dist/editor.d.ts +114 -0
- package/dist/editor.js +199 -0
- package/dist/emphasis.d.ts +6 -0
- package/dist/emphasis.js +101 -0
- package/dist/escape.d.ts +4 -0
- package/dist/escape.js +39 -0
- package/dist/extensions.d.ts +66 -0
- package/dist/extensions.js +79 -0
- package/dist/footnote.d.ts +24 -0
- package/dist/footnote.js +456 -0
- package/dist/footnoteSyntax.d.ts +23 -0
- package/dist/footnoteSyntax.js +107 -0
- package/dist/hashtag.d.ts +4 -0
- package/dist/hashtag.js +64 -0
- package/dist/hashtagSyntax.d.ts +36 -0
- package/dist/hashtagSyntax.js +99 -0
- package/dist/heading.d.ts +7 -0
- package/dist/heading.js +159 -0
- package/dist/highlight.d.ts +5 -0
- package/dist/highlight.js +6 -0
- package/dist/horizontalRule.d.ts +21 -0
- package/dist/horizontalRule.js +142 -0
- package/dist/image.d.ts +58 -0
- package/dist/image.js +344 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +16 -0
- package/dist/inlineCode.d.ts +4 -0
- package/dist/inlineCode.js +68 -0
- package/dist/link.d.ts +47 -0
- package/dist/link.js +217 -0
- package/dist/list.d.ts +14 -0
- package/dist/list.js +136 -0
- package/dist/listEnter.d.ts +20 -0
- package/dist/listEnter.js +72 -0
- package/dist/listIndent.d.ts +23 -0
- package/dist/listIndent.js +335 -0
- package/dist/markVision.d.ts +17 -0
- package/dist/markVision.js +56 -0
- package/dist/math.d.ts +45 -0
- package/dist/math.js +233 -0
- package/dist/mathSyntax.d.ts +24 -0
- package/dist/mathSyntax.js +179 -0
- package/dist/mention.d.ts +4 -0
- package/dist/mention.js +58 -0
- package/dist/mentionSyntax.d.ts +14 -0
- package/dist/mentionSyntax.js +71 -0
- package/dist/quote.d.ts +9 -0
- package/dist/quote.js +122 -0
- package/dist/setup.d.ts +13 -0
- package/dist/setup.js +40 -0
- package/dist/style.css +6 -0
- package/dist/style.css.d.ts +2 -0
- package/dist/table.d.ts +38 -0
- package/dist/table.js +960 -0
- package/dist/tableEdit.d.ts +30 -0
- package/dist/tableEdit.js +305 -0
- package/dist/tableMenu.d.ts +65 -0
- package/dist/tableMenu.js +1125 -0
- package/dist/tableModel.d.ts +168 -0
- package/dist/tableModel.js +433 -0
- package/dist/tableState.d.ts +36 -0
- package/dist/tableState.js +96 -0
- package/dist/task.d.ts +13 -0
- package/dist/task.js +179 -0
- package/dist/textMate.d.ts +49 -0
- package/dist/textMate.js +284 -0
- package/dist/tikz.d.ts +56 -0
- package/dist/tikz.js +343 -0
- package/dist/underline.d.ts +5 -0
- package/dist/underline.js +6 -0
- package/dist/utils/blockMargin.d.ts +6 -0
- package/dist/utils/blockMargin.js +6 -0
- package/dist/utils/codePoint.d.ts +13 -0
- package/dist/utils/codePoint.js +43 -0
- package/dist/utils/container.d.ts +10 -0
- package/dist/utils/container.js +10 -0
- package/dist/utils/reveal.d.ts +99 -0
- package/dist/utils/reveal.js +161 -0
- package/dist/utils/selection.d.ts +33 -0
- package/dist/utils/selection.js +45 -0
- package/dist/utils/syntaxTree.d.ts +18 -0
- package/dist/utils/syntaxTree.js +23 -0
- package/package.json +79 -4
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ChangeSpec, EditorState, TransactionSpec } from "@codemirror/state";
|
|
2
|
+
import type { TableCellRange } from "./tableState.js";
|
|
3
|
+
import { type TableBlock, type TableColorName } from "./tableModel.js";
|
|
4
|
+
export type TableRowAction = "insertAbove" | "insertBelow" | "duplicate" | "clear" | "delete";
|
|
5
|
+
export type TableColumnAction = "insertLeft" | "insertRight" | "duplicate" | "clear" | "delete";
|
|
6
|
+
/** 행 하나를 지울 때 함께 쓸 색 번호 변경 (표 끝내기 등 table.ts의 명령용) */
|
|
7
|
+
export declare function rowRemovedChange(state: EditorState, table: TableBlock, index: number): ChangeSpec | null;
|
|
8
|
+
/** index번째 행(0: 머리글 줄)에 대한 동작 */
|
|
9
|
+
export declare function editTableRow(state: EditorState, table: TableBlock, index: number, action: TableRowAction): TransactionSpec | null;
|
|
10
|
+
/**
|
|
11
|
+
* from번째 행을 slot(0..행 수, 옛 번호 기준 행 사이)으로 옮깁니다. 첫 줄이 바뀌면 구분 줄은 새 첫 줄 뒤에 둡니다.
|
|
12
|
+
* 표의 줄들(머리글 줄 ~ 마지막 행)을 순서만 바꿔 다시 씁니다. (각 줄의 글자와 접두어는 그대로)
|
|
13
|
+
*/
|
|
14
|
+
export declare function moveTableRow(state: EditorState, table: TableBlock, from: number, slot: number): TransactionSpec | null;
|
|
15
|
+
/** column번째 열(0부터)에 대한 동작. 머리글 줄, 구분 줄, 본문 줄을 함께 바꿉니다. */
|
|
16
|
+
export declare function editTableColumn(state: EditorState, table: TableBlock, column: number, action: TableColumnAction): TransactionSpec | null;
|
|
17
|
+
/**
|
|
18
|
+
* from번째 열을 slot(0..열 수, 옛 번호 기준 열 사이)으로 옮깁니다. 행마다 칸 조각(앞뒤 공백 포함)의 순서만 바꿔 다시 씁니다.
|
|
19
|
+
* 칸이 모자란 행은 빈 칸으로 채웁니다. (모자란 자리가 옮겨지므로 그대로 둘 수 없음)
|
|
20
|
+
*/
|
|
21
|
+
export declare function moveTableColumn(state: EditorState, table: TableBlock, from: number, slot: number): TransactionSpec | null;
|
|
22
|
+
/**
|
|
23
|
+
* 칸들의 글자 색(fg) 또는 배경 색(bg)을 바꿉니다. (null: 그 색을 없앰, 다른 쪽 색은 그대로)
|
|
24
|
+
* 색은 칸 내용 앞의 `<!--fg:red bg:blue-->` 표식에 씁니다. 표식이 비면 지웁니다.
|
|
25
|
+
*/
|
|
26
|
+
export declare function setCellColor(state: EditorState, table: TableBlock, range: TableCellRange, kind: "fg" | "bg", color: TableColorName | null): TransactionSpec | null;
|
|
27
|
+
/** 칸들의 내용을 지웁니다. (색 표식은 남김) */
|
|
28
|
+
export declare function clearCells(state: EditorState, table: TableBlock, range: TableCellRange): TransactionSpec | null;
|
|
29
|
+
/** 칸들의 색: 모두 같으면 그 색, 다르면 undefined */
|
|
30
|
+
export declare function commonCellColor(state: EditorState, table: TableBlock, range: TableCellRange, kind: "fg" | "bg"): TableColorName | null | undefined;
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { cellMarkerText, commentChange, continuationPrefix, emptyRow, readDelimiterRow, readTableRows, remapColorWords, tableNodeAt, } from "./tableModel.js";
|
|
2
|
+
function context(state, table) {
|
|
3
|
+
const node = tableNodeAt(state, table.from);
|
|
4
|
+
if (!node || node.from !== table.from)
|
|
5
|
+
return null;
|
|
6
|
+
return { state, table, rows: readTableRows(state, node), delimiter: readDelimiterRow(state.doc, table) };
|
|
7
|
+
}
|
|
8
|
+
const lineText = (state, row) => state.doc.sliceString(row.lineFrom, row.lineTo);
|
|
9
|
+
const prefixOf = (state, row) => continuationPrefix(state.doc.sliceString(row.lineFrom, row.from));
|
|
10
|
+
/** 새 칸/행이 at에 생김 */
|
|
11
|
+
const insertedAt = (at) => (i) => [i >= at ? i + 1 : i];
|
|
12
|
+
const removedAt = (at) => (i) => (i === at ? [] : [i > at ? i - 1 : i]);
|
|
13
|
+
const duplicatedAt = (at) => (i) => (i === at ? [i, i + 1] : [i > at ? i + 1 : i]);
|
|
14
|
+
/** 옛 번호 from을 칸 사이 slot(0..n, 옛 번호 기준)으로 옮겼을 때의 새 순서 (새 자리 → 옛 번호). 그대로면 null */
|
|
15
|
+
function moveOrder(count, from, slot) {
|
|
16
|
+
if (slot === from || slot === from + 1)
|
|
17
|
+
return null;
|
|
18
|
+
const order = Array.from({ length: count }, (_, i) => i).filter((i) => i !== from);
|
|
19
|
+
order.splice(slot > from ? slot - 1 : slot, 0, from);
|
|
20
|
+
return order;
|
|
21
|
+
}
|
|
22
|
+
const movedBy = (order) => (i) => [order.indexOf(i)];
|
|
23
|
+
/**
|
|
24
|
+
* 원문 변경 + 색 번호 옮기기 + 커서를 한 트랜잭션으로 묶습니다.
|
|
25
|
+
* cursor는 변경 뒤 문서의 위치를 돌려줍니다. (설정 주석의 길이가 바뀌면 그 아래가 밀리므로 ChangeSet으로 계산)
|
|
26
|
+
*/
|
|
27
|
+
function finish(state, table, changes, remap, cursor) {
|
|
28
|
+
const all = [...changes];
|
|
29
|
+
if (remap) {
|
|
30
|
+
const words = remapColorWords(table, remap.axis, remap.map);
|
|
31
|
+
const change = words && commentChange(state, table, words);
|
|
32
|
+
if (change)
|
|
33
|
+
all.push(change);
|
|
34
|
+
}
|
|
35
|
+
if (all.length === 0)
|
|
36
|
+
return null;
|
|
37
|
+
const set = state.changes(all);
|
|
38
|
+
return cursor ? { changes: set, selection: { anchor: cursor(set) } } : { changes: set };
|
|
39
|
+
}
|
|
40
|
+
/** 표 전체(설정 주석 포함)를 줄째로 지웁니다. */
|
|
41
|
+
function deleteTable(state, table) {
|
|
42
|
+
const from = table.comment ? table.comment.lineFrom : table.lineFrom;
|
|
43
|
+
const { doc } = state;
|
|
44
|
+
// 뒤 줄바꿈을 함께 지우고, 문서 끝이면 앞 줄바꿈을 지웁니다.
|
|
45
|
+
if (table.lineTo < doc.length)
|
|
46
|
+
return { changes: { from, to: table.lineTo + 1 } };
|
|
47
|
+
return { changes: { from: Math.max(0, from - 1), to: table.lineTo } };
|
|
48
|
+
}
|
|
49
|
+
/** 행 하나를 지울 때 함께 쓸 색 번호 변경 (표 끝내기 등 table.ts의 명령용) */
|
|
50
|
+
export function rowRemovedChange(state, table, index) {
|
|
51
|
+
const words = remapColorWords(table, "row", removedAt(index));
|
|
52
|
+
return words && commentChange(state, table, words);
|
|
53
|
+
}
|
|
54
|
+
// -- 행 -----------------------------------------------------------------------
|
|
55
|
+
/** index번째 행(0: 머리글 줄)에 대한 동작 */
|
|
56
|
+
export function editTableRow(state, table, index, action) {
|
|
57
|
+
const cx = context(state, table);
|
|
58
|
+
const row = cx?.rows[index];
|
|
59
|
+
if (!cx || !row)
|
|
60
|
+
return null;
|
|
61
|
+
const { rows, delimiter } = cx;
|
|
62
|
+
const columns = Math.max(1, table.columns);
|
|
63
|
+
const prefix = prefixOf(state, row);
|
|
64
|
+
const delimiterText = lineText(state, delimiter);
|
|
65
|
+
// 머리글 줄 아래는 구분 줄 뒤에 이어 붙입니다. (머리글 줄 바로 다음은 구분 줄이어야 함)
|
|
66
|
+
const after = index === 0 ? delimiter.lineTo : row.lineTo;
|
|
67
|
+
const newRow = `${prefix}${emptyRow(columns)}`;
|
|
68
|
+
switch (action) {
|
|
69
|
+
case "insertBelow":
|
|
70
|
+
return finish(state, table, [{ from: after, insert: `\n${newRow}` }], { axis: "row", map: insertedAt(index + 1) }, (set) => set.mapPos(after, -1) + 1 + prefix.length + 2);
|
|
71
|
+
case "insertAbove": {
|
|
72
|
+
const changes = index > 0
|
|
73
|
+
? [{ from: row.lineFrom, insert: `${newRow}\n` }]
|
|
74
|
+
: // 새 머리글 줄: 새 행 + 구분 줄을 머리글 줄 앞에 넣고, 원래 구분 줄은 지웁니다.
|
|
75
|
+
[
|
|
76
|
+
{ from: row.lineFrom, insert: `${newRow}\n${delimiterText}\n` },
|
|
77
|
+
{ from: row.lineTo, to: delimiter.lineTo },
|
|
78
|
+
];
|
|
79
|
+
return finish(state, table, changes, { axis: "row", map: insertedAt(index) }, (set) => set.mapPos(row.lineFrom, -1) + prefix.length + 2);
|
|
80
|
+
}
|
|
81
|
+
case "duplicate":
|
|
82
|
+
return finish(state, table, [{ from: after, insert: `\n${lineText(state, row)}` }], { axis: "row", map: duplicatedAt(index) });
|
|
83
|
+
case "clear":
|
|
84
|
+
return finish(state, table, row.cells.filter((c) => c.contentFrom < c.contentTo).map((c) => ({ from: c.contentFrom, to: c.contentTo })), null);
|
|
85
|
+
case "delete": {
|
|
86
|
+
if (rows.length === 1)
|
|
87
|
+
return deleteTable(state, table);
|
|
88
|
+
const changes = index > 0
|
|
89
|
+
? [{ from: row.lineFrom - 1, to: row.lineTo }]
|
|
90
|
+
: // 머리글 줄을 지우면 첫 본문 행이 머리글 줄이 되므로, 구분 줄을 그 뒤로 옮깁니다.
|
|
91
|
+
[
|
|
92
|
+
{ from: row.lineFrom, to: delimiter.lineTo + 1 },
|
|
93
|
+
{ from: rows[1].lineTo, insert: `\n${delimiterText}` },
|
|
94
|
+
];
|
|
95
|
+
return finish(state, table, changes, { axis: "row", map: removedAt(index) });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* from번째 행을 slot(0..행 수, 옛 번호 기준 행 사이)으로 옮깁니다. 첫 줄이 바뀌면 구분 줄은 새 첫 줄 뒤에 둡니다.
|
|
101
|
+
* 표의 줄들(머리글 줄 ~ 마지막 행)을 순서만 바꿔 다시 씁니다. (각 줄의 글자와 접두어는 그대로)
|
|
102
|
+
*/
|
|
103
|
+
export function moveTableRow(state, table, from, slot) {
|
|
104
|
+
const cx = context(state, table);
|
|
105
|
+
if (!cx || !cx.rows[from])
|
|
106
|
+
return null;
|
|
107
|
+
const { rows, delimiter } = cx;
|
|
108
|
+
const order = moveOrder(rows.length, from, slot);
|
|
109
|
+
if (!order)
|
|
110
|
+
return null;
|
|
111
|
+
const lines = order.map((i) => lineText(state, rows[i]));
|
|
112
|
+
lines.splice(1, 0, lineText(state, delimiter));
|
|
113
|
+
const start = rows[0].lineFrom;
|
|
114
|
+
const end = rows[rows.length - 1].lineTo;
|
|
115
|
+
// 옮긴 행의 첫 칸에 커서
|
|
116
|
+
const target = order.indexOf(from);
|
|
117
|
+
const offset = lines.slice(0, target === 0 ? 0 : target + 1).reduce((n, l) => n + l.length + 1, 0);
|
|
118
|
+
const moved = rows[from];
|
|
119
|
+
const inLine = (moved.cells[0]?.textFrom ?? moved.from) - moved.lineFrom;
|
|
120
|
+
return finish(state, table, [{ from: start, to: end, insert: lines.join("\n") }], { axis: "row", map: movedBy(order) }, (set) => set.mapPos(start, -1) + offset + inLine);
|
|
121
|
+
}
|
|
122
|
+
// -- 열 -----------------------------------------------------------------------
|
|
123
|
+
/** 행 하나에서 column번째 칸 왼쪽/오른쪽에 칸(filler)을 넣는 변경. 그 칸이 없는 행(모자란 칸)이면 null */
|
|
124
|
+
function insertCell(row, column, side, filler) {
|
|
125
|
+
const cell = row.cells[column];
|
|
126
|
+
if (!cell)
|
|
127
|
+
return null;
|
|
128
|
+
if (side === "right") {
|
|
129
|
+
const after = row.pipes.find((p) => p.from === cell.to);
|
|
130
|
+
return after ? { from: after.to, insert: `${filler}|` } : { from: cell.to, insert: `|${filler}|` };
|
|
131
|
+
}
|
|
132
|
+
const before = row.pipes.find((p) => p.to === cell.from);
|
|
133
|
+
// 앞 "|"가 없는 행(`a | b`)의 첫 칸 앞: "|"로 열어야 칸이 됩니다.
|
|
134
|
+
return before ? { from: cell.from, insert: `${filler}|` } : { from: cell.from, insert: `|${filler}|` };
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* 행 하나에서 column번째 칸과 "|" 하나를 지우는 변경.
|
|
138
|
+
* 앞/뒤 "|"가 없는 행의 끝 칸이면 옆 "|"를 남겨 바깥 "|"로 씁니다. (`a | b`에서 b를 지우면 `a |`: "|"가 하나도 없으면 표가 아님)
|
|
139
|
+
*/
|
|
140
|
+
function deleteCell(row, column) {
|
|
141
|
+
const cell = row.cells[column];
|
|
142
|
+
if (!cell)
|
|
143
|
+
return null;
|
|
144
|
+
const before = row.pipes.find((p) => p.to === cell.from);
|
|
145
|
+
const after = row.pipes.find((p) => p.from === cell.to);
|
|
146
|
+
if (before && after)
|
|
147
|
+
return { from: cell.from, to: after.to };
|
|
148
|
+
if (after) {
|
|
149
|
+
const last = row.cells[row.cells.length - 1];
|
|
150
|
+
const closed = row.pipes.some((p) => p.from >= last.to);
|
|
151
|
+
// 앞 "|"만 있는 한 열 표(`| b`)는 GFM 표가 아니므로, 뒤 "|"가 없는 행에서 한 칸만 남으면 뒤를 닫습니다. (`b |`)
|
|
152
|
+
if (!closed && row.cells.length === 2)
|
|
153
|
+
return [{ from: cell.from, to: after.to }, { from: row.lineTo, insert: " |" }];
|
|
154
|
+
return { from: cell.from, to: after.from };
|
|
155
|
+
}
|
|
156
|
+
if (before)
|
|
157
|
+
return { from: before.to, to: cell.to };
|
|
158
|
+
return { from: cell.from, to: cell.to };
|
|
159
|
+
}
|
|
160
|
+
/** 행 하나에서 column번째 칸을 복제해 오른쪽에 넣는 변경 */
|
|
161
|
+
function duplicateCell(state, row, column) {
|
|
162
|
+
const cell = row.cells[column];
|
|
163
|
+
if (!cell)
|
|
164
|
+
return null;
|
|
165
|
+
const text = state.doc.sliceString(cell.from, cell.to);
|
|
166
|
+
const after = row.pipes.find((p) => p.from === cell.to);
|
|
167
|
+
return after ? { from: after.to, insert: `${text}|` } : { from: cell.to, insert: `|${text}` };
|
|
168
|
+
}
|
|
169
|
+
/** 머리글 줄, 구분 줄, 본문 줄 순서로 행마다 f를 적용한 변경들 */
|
|
170
|
+
function eachRow(cx, f) {
|
|
171
|
+
const out = [];
|
|
172
|
+
const push = (change) => {
|
|
173
|
+
if (change)
|
|
174
|
+
out.push(change);
|
|
175
|
+
};
|
|
176
|
+
push(f(cx.rows[0], false));
|
|
177
|
+
push(f(cx.delimiter, true));
|
|
178
|
+
for (const row of cx.rows.slice(1))
|
|
179
|
+
push(f(row, false));
|
|
180
|
+
return out;
|
|
181
|
+
}
|
|
182
|
+
/** column번째 열(0부터)에 대한 동작. 머리글 줄, 구분 줄, 본문 줄을 함께 바꿉니다. */
|
|
183
|
+
export function editTableColumn(state, table, column, action) {
|
|
184
|
+
const cx = context(state, table);
|
|
185
|
+
if (!cx || column < 0 || column >= table.columns)
|
|
186
|
+
return null;
|
|
187
|
+
const filler = (delimiter) => (delimiter ? "---" : " ");
|
|
188
|
+
switch (action) {
|
|
189
|
+
case "insertLeft":
|
|
190
|
+
return finish(state, table, eachRow(cx, (row, d) => insertCell(row, column, "left", filler(d))), {
|
|
191
|
+
axis: "column",
|
|
192
|
+
map: insertedAt(column),
|
|
193
|
+
});
|
|
194
|
+
case "insertRight":
|
|
195
|
+
return finish(state, table, eachRow(cx, (row, d) => insertCell(row, column, "right", filler(d))), {
|
|
196
|
+
axis: "column",
|
|
197
|
+
map: insertedAt(column + 1),
|
|
198
|
+
});
|
|
199
|
+
case "duplicate":
|
|
200
|
+
return finish(state, table, eachRow(cx, (row) => duplicateCell(state, row, column)), {
|
|
201
|
+
axis: "column",
|
|
202
|
+
map: duplicatedAt(column),
|
|
203
|
+
});
|
|
204
|
+
case "clear": {
|
|
205
|
+
const changes = [];
|
|
206
|
+
for (const row of cx.rows) {
|
|
207
|
+
const cell = row.cells[column];
|
|
208
|
+
if (cell && cell.contentFrom < cell.contentTo)
|
|
209
|
+
changes.push({ from: cell.contentFrom, to: cell.contentTo });
|
|
210
|
+
}
|
|
211
|
+
return finish(state, table, changes, null);
|
|
212
|
+
}
|
|
213
|
+
case "delete":
|
|
214
|
+
if (table.columns === 1)
|
|
215
|
+
return deleteTable(state, table);
|
|
216
|
+
return finish(state, table, eachRow(cx, (row) => deleteCell(row, column)), { axis: "column", map: removedAt(column) });
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* from번째 열을 slot(0..열 수, 옛 번호 기준 열 사이)으로 옮깁니다. 행마다 칸 조각(앞뒤 공백 포함)의 순서만 바꿔 다시 씁니다.
|
|
221
|
+
* 칸이 모자란 행은 빈 칸으로 채웁니다. (모자란 자리가 옮겨지므로 그대로 둘 수 없음)
|
|
222
|
+
*/
|
|
223
|
+
export function moveTableColumn(state, table, from, slot) {
|
|
224
|
+
const cx = context(state, table);
|
|
225
|
+
const columns = table.columns;
|
|
226
|
+
if (!cx || from < 0 || from >= columns)
|
|
227
|
+
return null;
|
|
228
|
+
const order = moveOrder(columns, from, slot);
|
|
229
|
+
if (!order)
|
|
230
|
+
return null;
|
|
231
|
+
const changes = eachRow(cx, (row, delimiter) => {
|
|
232
|
+
const segments = row.cells.map((c) => state.doc.sliceString(c.from, c.to));
|
|
233
|
+
while (segments.length < columns)
|
|
234
|
+
segments.push(delimiter ? "---" : " ");
|
|
235
|
+
const moved = [...order.map((i) => segments[i]), ...segments.slice(columns)];
|
|
236
|
+
const last = row.cells[row.cells.length - 1];
|
|
237
|
+
// 앞/뒤 "|"는 원래 행을 따르되, 끝 칸이 비면 "|"로 닫아야 칸이 됩니다.
|
|
238
|
+
const lead = row.pipes.some((p) => p.from === row.from) || moved[0].trim() === "";
|
|
239
|
+
const trail = (last ? row.pipes.some((p) => p.from >= last.to) : true) || moved[moved.length - 1].trim() === "";
|
|
240
|
+
const text = `${lead ? "|" : ""}${moved.join("|")}${trail ? "|" : ""}`;
|
|
241
|
+
return { from: row.from, to: row.lineTo, insert: text };
|
|
242
|
+
});
|
|
243
|
+
return finish(state, table, changes, { axis: "column", map: movedBy(order) });
|
|
244
|
+
}
|
|
245
|
+
// -- 칸 (색 표식, 내용) --------------------------------------------------------------
|
|
246
|
+
/** 칸 범위의 칸들 (모자란 칸은 없으므로 빠짐) */
|
|
247
|
+
function cellsIn(cx, range) {
|
|
248
|
+
const out = [];
|
|
249
|
+
for (let r = range.fromRow; r <= range.toRow; r++) {
|
|
250
|
+
const row = cx.rows[r];
|
|
251
|
+
if (!row)
|
|
252
|
+
continue;
|
|
253
|
+
for (let c = range.fromColumn; c <= range.toColumn; c++) {
|
|
254
|
+
const cell = row.cells[c];
|
|
255
|
+
if (cell)
|
|
256
|
+
out.push(cell);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return out;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* 칸들의 글자 색(fg) 또는 배경 색(bg)을 바꿉니다. (null: 그 색을 없앰, 다른 쪽 색은 그대로)
|
|
263
|
+
* 색은 칸 내용 앞의 `<!--fg:red bg:blue-->` 표식에 씁니다. 표식이 비면 지웁니다.
|
|
264
|
+
*/
|
|
265
|
+
export function setCellColor(state, table, range, kind, color) {
|
|
266
|
+
const cx = context(state, table);
|
|
267
|
+
if (!cx)
|
|
268
|
+
return null;
|
|
269
|
+
const changes = [];
|
|
270
|
+
for (const cell of cellsIn(cx, range)) {
|
|
271
|
+
const fg = kind === "fg" ? color : (cell.marker?.fg ?? null);
|
|
272
|
+
const bg = kind === "bg" ? color : (cell.marker?.bg ?? null);
|
|
273
|
+
const text = cellMarkerText(fg, bg);
|
|
274
|
+
const { marker } = cell;
|
|
275
|
+
const nonEmpty = cell.contentFrom < cell.contentTo;
|
|
276
|
+
if (marker) {
|
|
277
|
+
if (marker.fg === fg && marker.bg === bg)
|
|
278
|
+
continue;
|
|
279
|
+
// 표식을 지우면 표식 뒤 공백도 함께 (내용이 있을 때)
|
|
280
|
+
changes.push(text === null ? { from: marker.from, to: nonEmpty ? cell.contentFrom : marker.to } : { from: marker.from, to: marker.to, insert: text });
|
|
281
|
+
}
|
|
282
|
+
else if (text !== null) {
|
|
283
|
+
changes.push({ from: cell.contentFrom, insert: nonEmpty ? `${text} ` : text });
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return changes.length > 0 ? { changes } : null;
|
|
287
|
+
}
|
|
288
|
+
/** 칸들의 내용을 지웁니다. (색 표식은 남김) */
|
|
289
|
+
export function clearCells(state, table, range) {
|
|
290
|
+
const cx = context(state, table);
|
|
291
|
+
if (!cx)
|
|
292
|
+
return null;
|
|
293
|
+
const changes = cellsIn(cx, range)
|
|
294
|
+
.filter((c) => c.contentFrom < c.contentTo)
|
|
295
|
+
.map((c) => ({ from: c.contentFrom, to: c.contentTo }));
|
|
296
|
+
return changes.length > 0 ? { changes } : null;
|
|
297
|
+
}
|
|
298
|
+
/** 칸들의 색: 모두 같으면 그 색, 다르면 undefined */
|
|
299
|
+
export function commonCellColor(state, table, range, kind) {
|
|
300
|
+
const cx = context(state, table);
|
|
301
|
+
if (!cx)
|
|
302
|
+
return undefined;
|
|
303
|
+
const colors = new Set(cellsIn(cx, range).map((c) => c.marker?.[kind] ?? null));
|
|
304
|
+
return colors.size === 1 ? [...colors][0] : undefined;
|
|
305
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { EditorView, ViewPlugin, type PluginValue, type ViewUpdate } from "@codemirror/view";
|
|
2
|
+
declare class TableHandles implements PluginValue {
|
|
3
|
+
readonly dom: HTMLElement;
|
|
4
|
+
private readonly rowHandle;
|
|
5
|
+
private readonly columnHandle;
|
|
6
|
+
/** 끌 때 놓일 자리를 보여주는 선 */
|
|
7
|
+
private readonly indicator;
|
|
8
|
+
private menu;
|
|
9
|
+
/** 칸 메뉴를 연 화면 좌표 */
|
|
10
|
+
private menuPoint;
|
|
11
|
+
private search;
|
|
12
|
+
private list;
|
|
13
|
+
private page;
|
|
14
|
+
private entries;
|
|
15
|
+
private active;
|
|
16
|
+
private hover;
|
|
17
|
+
private drag;
|
|
18
|
+
/** 끌기를 마친 직후의 click은 메뉴를 열지 않습니다. */
|
|
19
|
+
private suppressClick;
|
|
20
|
+
private hideTimer;
|
|
21
|
+
private readonly view;
|
|
22
|
+
private readonly onDocumentMouseDown;
|
|
23
|
+
/** 에디터에 포커스가 없어도(메뉴를 마우스로만 열었을 때) Esc로 닫습니다. */
|
|
24
|
+
private readonly onDocumentKeyDown;
|
|
25
|
+
private readonly onScroll;
|
|
26
|
+
private readonly onDragMove;
|
|
27
|
+
private readonly onDragEnd;
|
|
28
|
+
constructor(view: EditorView);
|
|
29
|
+
private handle;
|
|
30
|
+
mousemove(event: MouseEvent): void;
|
|
31
|
+
mouseleave(): void;
|
|
32
|
+
private scheduleHide;
|
|
33
|
+
private cancelHide;
|
|
34
|
+
/** 손잡이/메뉴 위치를 다시 잽니다. (읽기와 쓰기를 나눠 레이아웃을 한 번만 계산) */
|
|
35
|
+
private place;
|
|
36
|
+
/** 손잡이 자리 (마우스가 표 위에 있을 때만) */
|
|
37
|
+
private measureHandles;
|
|
38
|
+
private dragStart;
|
|
39
|
+
private dragMove;
|
|
40
|
+
private dragEnd;
|
|
41
|
+
private toggleMenu;
|
|
42
|
+
/** 칸에서 오른쪽 클릭: 고른 칸 범위 안이면 그 범위, 아니면 그 칸의 메뉴를 마우스 자리에 엽니다. */
|
|
43
|
+
contextmenu(event: MouseEvent): boolean;
|
|
44
|
+
/** 메뉴를 엽니다. point: 칸 메뉴를 열 화면 좌표 (손잡이 메뉴는 null: 손잡이 옆) */
|
|
45
|
+
private openMenu;
|
|
46
|
+
/** 메뉴 키보드: ↑/↓ 항목 이동, Enter 실행, 빈 검색에서 Backspace는 하위 메뉴에서 돌아가기 (Esc는 문서 전체에서 처리) */
|
|
47
|
+
private menuKey;
|
|
48
|
+
private setActive;
|
|
49
|
+
private context;
|
|
50
|
+
/** 원문을 바꾸는 명령 실행 */
|
|
51
|
+
private apply;
|
|
52
|
+
/** 지금 상태로 메뉴 항목들을 만듭니다. */
|
|
53
|
+
private buildEntries;
|
|
54
|
+
private switchEntry;
|
|
55
|
+
private renderMenu;
|
|
56
|
+
private renderEntry;
|
|
57
|
+
/** 정렬: 왼쪽/가운데/오른쪽 버튼 (정렬 표시가 없으면 왼쪽) */
|
|
58
|
+
private alignGroup;
|
|
59
|
+
closeMenu(refocus?: boolean): void;
|
|
60
|
+
update(update: ViewUpdate): void;
|
|
61
|
+
destroy(): void;
|
|
62
|
+
}
|
|
63
|
+
export declare const tableHandles: ViewPlugin<TableHandles, undefined>;
|
|
64
|
+
export declare const tableMenuTheme: import("@codemirror/state").Extension;
|
|
65
|
+
export {};
|