@dou-ui/gantt 0.0.0-stage → 0.1.0-alpha.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 +10 -2
- package/dist/collaboration/index.js +323 -0
- package/dist/collaboration/index.js.map +1 -0
- package/dist/collaboration/peers.js +78 -0
- package/dist/collaboration/peers.js.map +1 -0
- package/dist/components/cell-editor.js +6 -0
- package/dist/components/cell-editor.js.map +1 -0
- package/dist/components/cell-editor.vue_vue_type_script_setup_true_lang.js +114 -0
- package/dist/components/cell-editor.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/gantt-menu.js +6 -0
- package/dist/components/gantt-menu.js.map +1 -0
- package/dist/components/gantt-menu.vue_vue_type_script_setup_true_lang.js +40 -0
- package/dist/components/gantt-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/time-grid.js +6 -0
- package/dist/components/time-grid.js.map +1 -0
- package/dist/components/time-grid.vue_vue_type_script_setup_true_lang.js +32 -0
- package/dist/components/time-grid.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/time-header.js +6 -0
- package/dist/components/time-header.js.map +1 -0
- package/dist/components/time-header.vue_vue_type_script_setup_true_lang.js +60 -0
- package/dist/components/time-header.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/context.js +12 -0
- package/dist/context.js.map +1 -0
- package/dist/core/baseline.js +48 -0
- package/dist/core/baseline.js.map +1 -0
- package/dist/core/calendar.js +191 -0
- package/dist/core/calendar.js.map +1 -0
- package/dist/core/csv.js +424 -0
- package/dist/core/csv.js.map +1 -0
- package/dist/core/document.js +231 -0
- package/dist/core/document.js.map +1 -0
- package/dist/core/extension.js +42 -0
- package/dist/core/extension.js.map +1 -0
- package/dist/core/geometry.js +112 -0
- package/dist/core/geometry.js.map +1 -0
- package/dist/core/history.js +70 -0
- package/dist/core/history.js.map +1 -0
- package/dist/core/links.js +72 -0
- package/dist/core/links.js.map +1 -0
- package/dist/core/model.js +52 -0
- package/dist/core/model.js.map +1 -0
- package/dist/core/order.js +34 -0
- package/dist/core/order.js.map +1 -0
- package/dist/core/scale.js +118 -0
- package/dist/core/scale.js.map +1 -0
- package/dist/core/schedule.js +235 -0
- package/dist/core/schedule.js.map +1 -0
- package/dist/core/time.js +81 -0
- package/dist/core/time.js.map +1 -0
- package/dist/core/transaction.js +371 -0
- package/dist/core/transaction.js.map +1 -0
- package/dist/core/tree.js +114 -0
- package/dist/core/tree.js.map +1 -0
- package/dist/export/draw.js +214 -0
- package/dist/export/draw.js.map +1 -0
- package/dist/export/theme.js +131 -0
- package/dist/export/theme.js.map +1 -0
- package/dist/gantt.js +6 -0
- package/dist/gantt.js.map +1 -0
- package/dist/gantt.vue_vue_type_script_setup_true_lang.js +2774 -0
- package/dist/gantt.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/labels.js +87 -0
- package/dist/labels.js.map +1 -0
- package/dist/locale.js +355 -0
- package/dist/locale.js.map +1 -0
- package/dist/scheduler/index.js +8 -0
- package/dist/scheduler/index.js.map +1 -0
- package/dist/scheduler/layout.js +97 -0
- package/dist/scheduler/layout.js.map +1 -0
- package/dist/scheduler/scheduler.js +6 -0
- package/dist/scheduler/scheduler.js.map +1 -0
- package/dist/scheduler/scheduler.vue_vue_type_script_setup_true_lang.js +1359 -0
- package/dist/scheduler/scheduler.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/scheduler/style.d.ts +1 -0
- package/dist/scheduler/style.js +2 -0
- package/dist/style.css +1 -0
- package/dist/style.d.ts +1 -0
- package/dist/style.js +4 -0
- package/dist/time-axis.js +337 -0
- package/dist/time-axis.js.map +1 -0
- package/dist/types/collaboration/index.d.ts +39 -0
- package/dist/types/collaboration/peers.d.ts +22 -0
- package/dist/types/components/cell-editor.vue.d.ts +21 -0
- package/dist/types/components/gantt-menu.vue.d.ts +18 -0
- package/dist/types/components/time-grid.vue.d.ts +12 -0
- package/dist/types/components/time-header.vue.d.ts +23 -0
- package/dist/types/context.d.ts +64 -0
- package/dist/types/core/baseline.d.ts +14 -0
- package/dist/types/core/calendar.d.ts +75 -0
- package/dist/types/core/csv.d.ts +77 -0
- package/dist/types/core/document.d.ts +51 -0
- package/dist/types/core/extension.d.ts +88 -0
- package/dist/types/core/geometry.d.ts +33 -0
- package/dist/types/core/history.d.ts +19 -0
- package/dist/types/core/links.d.ts +20 -0
- package/dist/types/core/menu.d.ts +27 -0
- package/dist/types/core/model.d.ts +72 -0
- package/dist/types/core/order.d.ts +9 -0
- package/dist/types/core/scale.d.ts +33 -0
- package/dist/types/core/schedule.d.ts +44 -0
- package/dist/types/core/time.d.ts +39 -0
- package/dist/types/core/transaction.d.ts +134 -0
- package/dist/types/core/tree.d.ts +30 -0
- package/dist/types/export/draw.d.ts +76 -0
- package/dist/types/export/theme.d.ts +49 -0
- package/dist/types/gantt.vue.d.ts +54 -0
- package/dist/types/index.d.ts +148 -0
- package/dist/types/labels.d.ts +24 -0
- package/dist/types/locale.d.ts +161 -0
- package/dist/types/scheduler/index.d.ts +102 -0
- package/dist/types/scheduler/layout.d.ts +58 -0
- package/dist/types/scheduler/scheduler.vue.d.ts +46 -0
- package/dist/types/scheduler/types.d.ts +109 -0
- package/dist/types/time-axis.d.ts +80 -0
- package/dist/types/types.d.ts +305 -0
- package/package.json +79 -4
package/dist/core/csv.js
ADDED
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
import { MINUTES_PER_DAY, parseTime } from "./time.js";
|
|
2
|
+
import { endKeepingWork, shiftWorkMinutes, shiftWorkdays } from "./calendar.js";
|
|
3
|
+
import { createId, spanOf, writeSpan } from "./model.js";
|
|
4
|
+
import { buildTree } from "./tree.js";
|
|
5
|
+
import { format, ganttLocales } from "../locale.js";
|
|
6
|
+
import { orderKeys } from "./order.js";
|
|
7
|
+
// src/core/csv.ts
|
|
8
|
+
/** Thrown when a file can't be read as tasks: for developers in `message`, for people in `reason`. */
|
|
9
|
+
var GanttCSVError = class extends Error {
|
|
10
|
+
code;
|
|
11
|
+
reason;
|
|
12
|
+
name = "GanttCSVError";
|
|
13
|
+
constructor(message, code, reason) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.reason = reason;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
/** What couldn't be read of each task, for `importCSV` to keep the chart's values. */
|
|
20
|
+
var unreadOf = /* @__PURE__ */ new WeakMap();
|
|
21
|
+
/**
|
|
22
|
+
* Splits CSV into rows of fields (RFC 4180); a byte order mark at the start
|
|
23
|
+
* is dropped. `delimiter` defaults to the one the text most likely uses.
|
|
24
|
+
*/
|
|
25
|
+
function splitCSV(text, delimiter = delimiterOf(text)) {
|
|
26
|
+
if (text.charCodeAt(0) === 65279) text = text.slice(1);
|
|
27
|
+
const rows = [];
|
|
28
|
+
let row = [];
|
|
29
|
+
let field = "";
|
|
30
|
+
let quoted = false;
|
|
31
|
+
let wasQuoted = false;
|
|
32
|
+
for (let i = 0; i < text.length; i++) {
|
|
33
|
+
const char = text[i];
|
|
34
|
+
if (quoted) {
|
|
35
|
+
if (char === "\"" && text[i + 1] === "\"") {
|
|
36
|
+
field += "\"";
|
|
37
|
+
i++;
|
|
38
|
+
} else if (char === "\"") quoted = false;
|
|
39
|
+
else field += char;
|
|
40
|
+
} else if (char === "\"" && field === "") {
|
|
41
|
+
quoted = true;
|
|
42
|
+
wasQuoted = true;
|
|
43
|
+
} else if (char === delimiter) {
|
|
44
|
+
row.push(field);
|
|
45
|
+
field = "";
|
|
46
|
+
wasQuoted = false;
|
|
47
|
+
} else if (char === "\n" || char === "\r") {
|
|
48
|
+
if (char === "\r" && text[i + 1] === "\n") i++;
|
|
49
|
+
row.push(field);
|
|
50
|
+
rows.push(row);
|
|
51
|
+
row = [];
|
|
52
|
+
field = "";
|
|
53
|
+
wasQuoted = false;
|
|
54
|
+
} else field += char;
|
|
55
|
+
}
|
|
56
|
+
if (field !== "" || wasQuoted || row.length) {
|
|
57
|
+
row.push(field);
|
|
58
|
+
rows.push(row);
|
|
59
|
+
}
|
|
60
|
+
return rows;
|
|
61
|
+
}
|
|
62
|
+
var DELIMITERS = [
|
|
63
|
+
",",
|
|
64
|
+
";",
|
|
65
|
+
" ",
|
|
66
|
+
"|"
|
|
67
|
+
];
|
|
68
|
+
/**
|
|
69
|
+
* The delimiter a text most likely uses: of `,`, `;`, tab and `|`, the one
|
|
70
|
+
* found most often outside quotes in its first lines, and the same number
|
|
71
|
+
* of times on each; a comma when none is found. Kept in step with
|
|
72
|
+
* `detectDelimiter` of `@dou-ui/sheet`, so a file reads the same in both.
|
|
73
|
+
*/
|
|
74
|
+
function delimiterOf(text) {
|
|
75
|
+
const counts = new Map(DELIMITERS.map((d) => [d, [0]]));
|
|
76
|
+
let quoted = false;
|
|
77
|
+
let lines = 0;
|
|
78
|
+
for (let i = 0; i < text.length && lines < 10; i++) {
|
|
79
|
+
const char = text[i];
|
|
80
|
+
if (char === "\"") quoted = !quoted;
|
|
81
|
+
else if (quoted) continue;
|
|
82
|
+
else if (char === "\n") {
|
|
83
|
+
lines++;
|
|
84
|
+
for (const line of counts.values()) line.push(0);
|
|
85
|
+
} else {
|
|
86
|
+
const line = counts.get(char);
|
|
87
|
+
if (line) line[line.length - 1]++;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
let best = ",";
|
|
91
|
+
let score = 0;
|
|
92
|
+
for (const [delimiter, perLine] of counts) {
|
|
93
|
+
const full = perLine.length > 1 && perLine[perLine.length - 1] === 0 ? perLine.slice(0, -1) : perLine;
|
|
94
|
+
const first = full[0] ?? 0;
|
|
95
|
+
if (!first) continue;
|
|
96
|
+
const value = first * (full.every((n) => n === first) ? 2 : 1);
|
|
97
|
+
if (value > score) {
|
|
98
|
+
score = value;
|
|
99
|
+
best = delimiter;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return best;
|
|
103
|
+
}
|
|
104
|
+
var BUILT_IN = [
|
|
105
|
+
"title",
|
|
106
|
+
"start",
|
|
107
|
+
"end",
|
|
108
|
+
"duration",
|
|
109
|
+
"progress"
|
|
110
|
+
];
|
|
111
|
+
var BUILT_IN_KEYS = /* @__PURE__ */ new Set([
|
|
112
|
+
...BUILT_IN,
|
|
113
|
+
"id",
|
|
114
|
+
"parent"
|
|
115
|
+
]);
|
|
116
|
+
/** A column the chart has that only reads a value: known, but not read. */
|
|
117
|
+
var SKIP = "\0skip";
|
|
118
|
+
/**
|
|
119
|
+
* What a header can say for each column: the built-in fields always, in
|
|
120
|
+
* the chart's language and in the built-in ones, whatever `columns` shows;
|
|
121
|
+
* the chart's own columns by their titles.
|
|
122
|
+
*/
|
|
123
|
+
function headerNames(columns, messages) {
|
|
124
|
+
const names = /* @__PURE__ */ new Map();
|
|
125
|
+
const add = (name, key) => {
|
|
126
|
+
const text = name?.trim().toLowerCase();
|
|
127
|
+
if (text && !names.has(text)) names.set(text, key);
|
|
128
|
+
};
|
|
129
|
+
const all = [messages, ...Object.values(ganttLocales)];
|
|
130
|
+
const titles = (key, own) => {
|
|
131
|
+
const list = own != null ? [own] : [];
|
|
132
|
+
if (BUILT_IN.includes(key)) for (const m of all) list.push(m.columns[key]);
|
|
133
|
+
return list;
|
|
134
|
+
};
|
|
135
|
+
const addColumn = (key, own) => {
|
|
136
|
+
for (const title of titles(key, own)) {
|
|
137
|
+
add(title, key);
|
|
138
|
+
if (key === "duration") for (const m of all) {
|
|
139
|
+
add(format(m.daysColumn, { title }), "duration:d");
|
|
140
|
+
add(format(m.hoursColumn, { title }), "duration:h");
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
add(key, key);
|
|
144
|
+
};
|
|
145
|
+
for (const column of columns) if (column.value) add(column.title ?? column.key, SKIP);
|
|
146
|
+
for (const column of columns) if (!column.value) addColumn(column.key, column.title);
|
|
147
|
+
for (const key of BUILT_IN) addColumn(key);
|
|
148
|
+
for (const m of all) {
|
|
149
|
+
add(m.idColumn, "id");
|
|
150
|
+
add(m.parentColumn, "parent");
|
|
151
|
+
}
|
|
152
|
+
add("id", "id");
|
|
153
|
+
add("parent", "parent");
|
|
154
|
+
return names;
|
|
155
|
+
}
|
|
156
|
+
/** Text `toCSV` wrote behind a `'` so that spreadsheets don't run it as a formula: `'-复盘`. */
|
|
157
|
+
var unquoteFormula = (text) => /^'[=+\-@\t\r]/.test(text) ? text.slice(1) : text;
|
|
158
|
+
var pad = (n) => n.padStart(2, "0");
|
|
159
|
+
var DATE = /^(\d{4})([-/.])(\d{1,2})\2(\d{1,2})(?:[ T](\d{1,2}):(\d{2})(?::\d{2})?)?$/;
|
|
160
|
+
/**
|
|
161
|
+
* A date as tasks write it, from the ways spreadsheets save one:
|
|
162
|
+
* `2026/3/2`, `2026-3-2`, `2026.3.2`, with `9:30` or `09:30:00` after it.
|
|
163
|
+
* `null` when it isn't a date.
|
|
164
|
+
*/
|
|
165
|
+
function readCSVDate(text) {
|
|
166
|
+
const match = DATE.exec(text.trim());
|
|
167
|
+
if (!match) return null;
|
|
168
|
+
const [, y, , m, d, h, mi] = match;
|
|
169
|
+
const value = `${y}-${pad(m)}-${pad(d)}${h != null ? ` ${pad(h)}:${mi}` : ""}`;
|
|
170
|
+
return parseTime(value) ? value : null;
|
|
171
|
+
}
|
|
172
|
+
/** A unit as a message writes it: `{count} 天` is `天`. */
|
|
173
|
+
var unitWord = (pattern) => format(pattern, { count: "" }).trim().toLowerCase();
|
|
174
|
+
/**
|
|
175
|
+
* A number, with a decimal comma in a file split by `;` (`1,5`), as such
|
|
176
|
+
* files are written; kept in step with `readCSV` of `@dou-ui/sheet`.
|
|
177
|
+
*/
|
|
178
|
+
function readNumber(text, comma) {
|
|
179
|
+
const value = text.trim();
|
|
180
|
+
if (!value || !/^[+-]?(\d+([.,]\d+)?|[.,]\d+)$/.test(value)) return NaN;
|
|
181
|
+
if (comma ? value.includes(".") : value.includes(",")) return NaN;
|
|
182
|
+
return Number(value.replace(",", "."));
|
|
183
|
+
}
|
|
184
|
+
/** A duration with its unit, when it says one: `5`, `5 天`, `2 小时`, `2h`. */
|
|
185
|
+
function readDuration(text, messages, comma) {
|
|
186
|
+
const match = /^([+-]?[\d.,]+)\s*(.*)$/.exec(text.trim());
|
|
187
|
+
if (!match) return null;
|
|
188
|
+
const count = readNumber(match[1], comma);
|
|
189
|
+
if (Number.isNaN(count)) return null;
|
|
190
|
+
const unit = match[2].trim().toLowerCase();
|
|
191
|
+
if (!unit) return {
|
|
192
|
+
count,
|
|
193
|
+
unit: null
|
|
194
|
+
};
|
|
195
|
+
const all = [messages, ...Object.values(ganttLocales)];
|
|
196
|
+
if (unit === "d" || all.some((m) => unitWord(m.days) === unit)) return {
|
|
197
|
+
count,
|
|
198
|
+
unit: "d"
|
|
199
|
+
};
|
|
200
|
+
if (unit === "h" || all.some((m) => unitWord(m.hours) === unit)) return {
|
|
201
|
+
count,
|
|
202
|
+
unit: "h"
|
|
203
|
+
};
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Reads the tasks of a CSV file. Throws when no header is the title
|
|
208
|
+
* column, as the file then isn't a list of tasks.
|
|
209
|
+
*/
|
|
210
|
+
function readGanttCSV(text, context) {
|
|
211
|
+
const { columns, messages, calendar } = context;
|
|
212
|
+
const delimiter = delimiterOf(text.charCodeAt(0) === 65279 ? text.slice(1) : text);
|
|
213
|
+
const comma = delimiter === ";";
|
|
214
|
+
const [head, ...lines] = splitCSV(text, delimiter);
|
|
215
|
+
const names = headerNames(columns, messages);
|
|
216
|
+
const keys = (head ?? []).map((header) => names.get(unquoteFormula(header.trim()).toLowerCase()));
|
|
217
|
+
if (!keys.some((key) => key === "title")) throw new GanttCSVError(`[@dou-ui/gantt] No column of the file is the title column; its headers are ${JSON.stringify(head ?? [])}.`, "noTitleColumn", format(messages.csvNoTitle, { title: messages.columns.title }));
|
|
218
|
+
const unknownColumns = (head ?? []).filter((header, i) => !keys[i] && header.trim());
|
|
219
|
+
keys.forEach((key, i) => {
|
|
220
|
+
if (key === SKIP) keys[i] = void 0;
|
|
221
|
+
});
|
|
222
|
+
const byKey = new Map(columns.map((column) => [column.key, column]));
|
|
223
|
+
const timing = keys.some((key) => key === "end" || key?.startsWith("duration"));
|
|
224
|
+
const warnings = [];
|
|
225
|
+
const tasks = [];
|
|
226
|
+
const parents = /* @__PURE__ */ new Map();
|
|
227
|
+
const ids = /* @__PURE__ */ new Set();
|
|
228
|
+
lines.forEach((fields, index) => {
|
|
229
|
+
const row = index + 2;
|
|
230
|
+
if (fields.every((field) => !field.trim())) return;
|
|
231
|
+
const cells = /* @__PURE__ */ new Map();
|
|
232
|
+
keys.forEach((key, i) => {
|
|
233
|
+
if (key && !cells.has(key)) cells.set(key, unquoteFormula(fields[i]?.trim() ?? ""));
|
|
234
|
+
});
|
|
235
|
+
const task = {
|
|
236
|
+
id: "",
|
|
237
|
+
title: cells.get("title") ?? ""
|
|
238
|
+
};
|
|
239
|
+
const unread = /* @__PURE__ */ new Set();
|
|
240
|
+
const warn = (column, value) => {
|
|
241
|
+
warnings.push({
|
|
242
|
+
row,
|
|
243
|
+
column,
|
|
244
|
+
text: value
|
|
245
|
+
});
|
|
246
|
+
unread.add(column);
|
|
247
|
+
};
|
|
248
|
+
const id = cells.get("id");
|
|
249
|
+
if (id && ids.has(id)) warn("id", id);
|
|
250
|
+
task.id = id && !ids.has(id) ? id : createId();
|
|
251
|
+
ids.add(task.id);
|
|
252
|
+
const parent = cells.get("parent");
|
|
253
|
+
if (parent) parents.set(task, {
|
|
254
|
+
text: parent,
|
|
255
|
+
row
|
|
256
|
+
});
|
|
257
|
+
const startText = cells.get("start") ?? "";
|
|
258
|
+
const startValue = startText ? readCSVDate(startText) : null;
|
|
259
|
+
const start = startValue ? parseTime(startValue) : null;
|
|
260
|
+
if (startText && !start) warn("start", startText);
|
|
261
|
+
const endText = cells.get("end") ?? "";
|
|
262
|
+
const durationKey = keys.find((key) => key?.startsWith("duration"));
|
|
263
|
+
const durationText = durationKey ? cells.get(durationKey) : "";
|
|
264
|
+
if (!startText) {
|
|
265
|
+
if (endText) warn("end", endText);
|
|
266
|
+
else if (durationText) warn("duration", durationText);
|
|
267
|
+
}
|
|
268
|
+
if (start) {
|
|
269
|
+
task.start = startValue;
|
|
270
|
+
if (timing && !endText && !durationText) task.type = "milestone";
|
|
271
|
+
else {
|
|
272
|
+
const unit = start.dateOnly ? MINUTES_PER_DAY : 60;
|
|
273
|
+
let end = null;
|
|
274
|
+
if (endText) {
|
|
275
|
+
const endValue = readCSVDate(endText);
|
|
276
|
+
const at = endValue ? parseTime(endValue) : null;
|
|
277
|
+
const to = at ? at.at + (at.dateOnly ? MINUTES_PER_DAY : 0) : null;
|
|
278
|
+
if (to != null && to > start.at) end = to;
|
|
279
|
+
else warn("end", endText);
|
|
280
|
+
} else if (durationText) {
|
|
281
|
+
const read = readDuration(durationText, messages, comma);
|
|
282
|
+
const hours = read?.unit ? read.unit === "h" : durationKey === "duration:h" || durationKey !== "duration:d" && !start.dateOnly;
|
|
283
|
+
const count = read ? hours ? Math.round(read.count * 60) : read.count : 0;
|
|
284
|
+
if (!(count > 0) || !Number.isInteger(count)) warn("duration", durationText);
|
|
285
|
+
else end = hours ? shiftWorkMinutes(calendar, start.at, count) : shiftWorkdays(calendar, start.at, count);
|
|
286
|
+
}
|
|
287
|
+
Object.assign(task, writeSpan(task, start.at, end ?? start.at + unit));
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const progress = cells.get("progress");
|
|
291
|
+
if (progress && task.type !== "milestone") {
|
|
292
|
+
const count = readNumber(progress.replace(/\s*%$/, ""), comma);
|
|
293
|
+
if (Number.isFinite(count)) task.progress = Math.min(100, Math.max(0, count)) / 100;
|
|
294
|
+
else warn("progress", progress);
|
|
295
|
+
}
|
|
296
|
+
for (const [key, value] of cells) {
|
|
297
|
+
if (key === "id" || key === "parent" || key.startsWith("duration")) continue;
|
|
298
|
+
if (BUILT_IN.includes(key) || !value) continue;
|
|
299
|
+
const column = byKey.get(key);
|
|
300
|
+
const next = column.parse ? column.parse(value, task) : value;
|
|
301
|
+
if (next === void 0) warn(key, value);
|
|
302
|
+
else task.data = {
|
|
303
|
+
...task.data,
|
|
304
|
+
[key]: next
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
if (unread.size) unreadOf.set(task, unread);
|
|
308
|
+
tasks.push(task);
|
|
309
|
+
});
|
|
310
|
+
const byId = new Map(tasks.map((task) => [task.id, task]));
|
|
311
|
+
for (const [task, { text: parent, row }] of parents) {
|
|
312
|
+
let at = byId.get(parent);
|
|
313
|
+
while (at && at !== task) at = at.parent ? byId.get(at.parent) : void 0;
|
|
314
|
+
if (byId.has(parent) && !at || !byId.has(parent) && context.outer?.(parent)) task.parent = parent;
|
|
315
|
+
else {
|
|
316
|
+
warnings.push({
|
|
317
|
+
row,
|
|
318
|
+
column: "parent",
|
|
319
|
+
text: parent
|
|
320
|
+
});
|
|
321
|
+
const unread = unreadOf.get(task) ?? /* @__PURE__ */ new Set();
|
|
322
|
+
unread.add("parent");
|
|
323
|
+
unreadOf.set(task, unread);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
const summaries = new Set(tasks.map((task) => task.parent));
|
|
327
|
+
for (const task of tasks) if (summaries.has(task.id)) {
|
|
328
|
+
delete task.start;
|
|
329
|
+
delete task.end;
|
|
330
|
+
delete task.progress;
|
|
331
|
+
delete task.type;
|
|
332
|
+
}
|
|
333
|
+
const found = keys.filter((key) => key != null).map((key) => key.startsWith("duration") ? "duration" : key);
|
|
334
|
+
return {
|
|
335
|
+
tasks,
|
|
336
|
+
warnings,
|
|
337
|
+
columns: [...new Set(found)],
|
|
338
|
+
unknownColumns
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Puts the tasks `readGanttCSV` read into a transaction, as
|
|
343
|
+
* `GanttCSVImportOptions.mode` says.
|
|
344
|
+
*/
|
|
345
|
+
function applyGanttCSV(tr, result, mode, calendar) {
|
|
346
|
+
if (mode === "append") {
|
|
347
|
+
const renamed = /* @__PURE__ */ new Map();
|
|
348
|
+
for (const task of result.tasks) if (tr.getTask(task.id)) renamed.set(task.id, createId());
|
|
349
|
+
for (const task of result.tasks) tr.addTask({
|
|
350
|
+
...task,
|
|
351
|
+
id: renamed.get(task.id) ?? task.id,
|
|
352
|
+
parent: task.parent != null ? renamed.get(task.parent) ?? task.parent : void 0
|
|
353
|
+
});
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const has = new Set(result.columns);
|
|
357
|
+
const timed = has.has("end") || has.has("duration");
|
|
358
|
+
const keep = new Set(result.tasks.map((task) => task.id));
|
|
359
|
+
for (const task of result.tasks) {
|
|
360
|
+
const old = tr.getTask(task.id);
|
|
361
|
+
if (!old) continue;
|
|
362
|
+
const unread = unreadOf.get(task);
|
|
363
|
+
const read = (key) => has.has(key) && !unread?.has(key);
|
|
364
|
+
const patch = { title: task.title };
|
|
365
|
+
if (read("parent")) patch.parent = task.parent;
|
|
366
|
+
else if (old.parent != null && !keep.has(old.parent)) patch.parent = void 0;
|
|
367
|
+
if (read("progress")) patch.progress = task.progress;
|
|
368
|
+
const datesRead = ![
|
|
369
|
+
"start",
|
|
370
|
+
"end",
|
|
371
|
+
"duration"
|
|
372
|
+
].some((key) => unread?.has(key));
|
|
373
|
+
if (has.has("start") && datesRead) {
|
|
374
|
+
const span = spanOf(old);
|
|
375
|
+
const start = spanOf(task);
|
|
376
|
+
if (timed || task.start == null || !span || !start || task.type !== old.type) {
|
|
377
|
+
patch.start = task.start;
|
|
378
|
+
patch.end = task.end;
|
|
379
|
+
patch.type = task.type;
|
|
380
|
+
} else if (old.type === "milestone") patch.start = task.start;
|
|
381
|
+
else Object.assign(patch, writeSpan(old, start.start, endKeepingWork(calendar, span, start.start)));
|
|
382
|
+
}
|
|
383
|
+
const own = [...has].filter((key) => !BUILT_IN_KEYS.has(key) && !unread?.has(key));
|
|
384
|
+
if (own.length) {
|
|
385
|
+
const data = { ...old.data };
|
|
386
|
+
for (const key of own) if (task.data?.[key] === void 0) delete data[key];
|
|
387
|
+
else data[key] = task.data[key];
|
|
388
|
+
patch.data = Object.keys(data).length ? data : void 0;
|
|
389
|
+
}
|
|
390
|
+
tr.updateTask(task.id, patch);
|
|
391
|
+
}
|
|
392
|
+
const now = tr.doc;
|
|
393
|
+
const gone = new Set(now.tasks.filter((task) => !keep.has(task.id)).map((task) => task.id));
|
|
394
|
+
if (gone.size) {
|
|
395
|
+
for (let i = now.links.length - 1; i >= 0; i--) {
|
|
396
|
+
const link = now.links[i];
|
|
397
|
+
if (gone.has(link.from) || gone.has(link.to)) tr.removeLink(link.id);
|
|
398
|
+
}
|
|
399
|
+
for (let i = now.tasks.length - 1; i >= 0; i--) {
|
|
400
|
+
const id = now.tasks[i].id;
|
|
401
|
+
if (gone.has(id)) tr.step({
|
|
402
|
+
type: "removeTask",
|
|
403
|
+
id
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
for (const task of result.tasks) if (!tr.getTask(task.id)) tr.addTask(task);
|
|
408
|
+
const tree = buildTree(tr.doc.tasks);
|
|
409
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
410
|
+
for (const task of result.tasks) {
|
|
411
|
+
const parent = tr.getTask(task.id)?.parent;
|
|
412
|
+
const list = wanted.get(parent);
|
|
413
|
+
if (list) list.push(task.id);
|
|
414
|
+
else wanted.set(parent, [task.id]);
|
|
415
|
+
}
|
|
416
|
+
for (const [parent, ids] of wanted) {
|
|
417
|
+
if ((tree.childrenOf.get(parent) ?? []).map((task) => task.id).join("\n") === ids.join("\n")) continue;
|
|
418
|
+
const keys = orderKeys(ids.length);
|
|
419
|
+
ids.forEach((id, i) => tr.updateTask(id, { order: keys[i] }));
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
export { GanttCSVError, applyGanttCSV, readCSVDate, readGanttCSV, splitCSV };
|
|
423
|
+
|
|
424
|
+
//# sourceMappingURL=csv.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csv.js","names":[],"sources":["../../src/core/csv.ts"],"sourcesContent":["import type { GanttColumn } from '../types'\nimport { ganttLocales, format, type GanttMessages } from '../locale'\nimport { endKeepingWork, shiftWorkMinutes, shiftWorkdays, type WorkCalendar } from './calendar'\nimport { createId, spanOf, writeSpan, type GanttTask } from './model'\nimport { orderKeys } from './order'\nimport type { GanttTransaction } from './transaction'\nimport { buildTree } from './tree'\nimport { MINUTES_PER_DAY, MINUTES_PER_HOUR, parseTime } from './time'\n\n/*\n * Reading tasks back from CSV, as `toCSV` writes it: the columns are\n * found by their headers, dates are `YYYY-MM-DD` (or with `HH:mm`), the\n * duration is working days or hours and progress is 0–100.\n */\n\n/** A value `readCSV` couldn't read; the task keeps what it had, or goes without it. */\nexport interface GanttCSVWarning {\n /** The row of the file it is on, the header being row 1. */\n row: number\n /** The column's `key`; `id` and `parent` for the columns `ids` adds. */\n column: string\n /** The text in the cell. */\n text: string\n}\n\n/** What `readCSV` and `importCSV` give back. */\nexport interface GanttCSVResult {\n /** In the order of the file. */\n tasks: GanttTask[]\n /** Values that couldn't be read. */\n warnings: GanttCSVWarning[]\n /**\n * The keys of the columns the file has, in its order; `id` and `parent`\n * for the columns `ids` adds.\n */\n columns: string[]\n /** Headers that are no field and no column of the chart; their values are left out. */\n unknownColumns: string[]\n}\n\n/** Why a file can't be read as tasks, as the `code` of a `GanttCSVError`. */\nexport type GanttCSVErrorCode = 'noTitleColumn'\n\n/** Thrown when a file can't be read as tasks: for developers in `message`, for people in `reason`. */\nexport class GanttCSVError extends Error {\n override name = 'GanttCSVError'\n constructor(\n message: string,\n readonly code: GanttCSVErrorCode,\n readonly reason: string,\n ) {\n super(message)\n }\n}\n\n/** What couldn't be read of each task, for `importCSV` to keep the chart's values. */\nconst unreadOf = new WeakMap<GanttTask, Set<string>>()\n\n/** How `importCSV` puts the tasks of a file into the chart. */\nexport interface GanttCSVImportOptions {\n /**\n * `replace` (the default): the file's tasks become the chart's. A task\n * whose id is already in the chart is updated with what the file has\n * and keeps the rest, links too; the tasks the file doesn't have are\n * removed. It is the default as the usual way back is the file the\n * chart wrote, edited in a spreadsheet. `append`: the file's tasks are\n * added after the chart's, with new ids where theirs are taken.\n */\n mode?: 'replace' | 'append'\n}\n\n/**\n * Splits CSV into rows of fields (RFC 4180); a byte order mark at the start\n * is dropped. `delimiter` defaults to the one the text most likely uses.\n */\nexport function splitCSV(text: string, delimiter = delimiterOf(text)): string[][] {\n if (text.charCodeAt(0) === 0xfeff) text = text.slice(1)\n const rows: string[][] = []\n let row: string[] = []\n let field = ''\n let quoted = false\n let wasQuoted = false\n for (let i = 0; i < text.length; i++) {\n const char = text[i]!\n if (quoted) {\n if (char === '\"' && text[i + 1] === '\"') {\n field += '\"'\n i++\n } else if (char === '\"') quoted = false\n else field += char\n } else if (char === '\"' && field === '') {\n quoted = true\n wasQuoted = true\n } else if (char === delimiter) {\n row.push(field)\n field = ''\n wasQuoted = false\n } else if (char === '\\n' || char === '\\r') {\n if (char === '\\r' && text[i + 1] === '\\n') i++\n row.push(field)\n rows.push(row)\n row = []\n field = ''\n wasQuoted = false\n } else field += char\n }\n if (field !== '' || wasQuoted || row.length) {\n row.push(field)\n rows.push(row)\n }\n return rows\n}\n\nconst DELIMITERS = [',', ';', '\\t', '|'] as const\n\n/**\n * The delimiter a text most likely uses: of `,`, `;`, tab and `|`, the one\n * found most often outside quotes in its first lines, and the same number\n * of times on each; a comma when none is found. Kept in step with\n * `detectDelimiter` of `@dou-ui/sheet`, so a file reads the same in both.\n */\nfunction delimiterOf(text: string) {\n const counts = new Map<string, number[]>(DELIMITERS.map((d) => [d, [0]]))\n let quoted = false\n let lines = 0\n for (let i = 0; i < text.length && lines < 10; i++) {\n const char = text[i]!\n if (char === '\"') quoted = !quoted\n else if (quoted) continue\n else if (char === '\\n') {\n lines++\n for (const line of counts.values()) line.push(0)\n } else {\n const line = counts.get(char)\n if (line) line[line.length - 1]!++\n }\n }\n let best: string = ','\n let score = 0\n for (const [delimiter, perLine] of counts) {\n // a last line cut short or left empty doesn't count\n const full =\n perLine.length > 1 && perLine[perLine.length - 1] === 0 ? perLine.slice(0, -1) : perLine\n const first = full[0] ?? 0\n if (!first) continue\n const value = first * (full.every((n) => n === first) ? 2 : 1)\n if (value > score) {\n score = value\n best = delimiter\n }\n }\n return best\n}\n\nconst BUILT_IN = ['title', 'start', 'end', 'duration', 'progress'] as const\nconst BUILT_IN_KEYS = new Set<string>([...BUILT_IN, 'id', 'parent'])\n/** A column the chart has that only reads a value: known, but not read. */\nconst SKIP = '\\0skip'\n\n/**\n * What a header can say for each column: the built-in fields always, in\n * the chart's language and in the built-in ones, whatever `columns` shows;\n * the chart's own columns by their titles.\n */\nfunction headerNames(columns: GanttColumn[], messages: GanttMessages) {\n const names = new Map<string, string>()\n const add = (name: string | undefined, key: string) => {\n const text = name?.trim().toLowerCase()\n if (text && !names.has(text)) names.set(text, key)\n }\n const all = [messages, ...Object.values(ganttLocales)]\n const titles = (key: string, own?: string) => {\n const list = own != null ? [own] : []\n if ((BUILT_IN as readonly string[]).includes(key))\n for (const m of all) list.push(m.columns[key as (typeof BUILT_IN)[number]])\n return list\n }\n const addColumn = (key: string, own?: string) => {\n for (const title of titles(key, own)) {\n add(title, key)\n if (key === 'duration')\n for (const m of all) {\n add(format(m.daysColumn, { title }), 'duration:d')\n add(format(m.hoursColumn, { title }), 'duration:h')\n }\n }\n add(key, key)\n }\n // a column of your own that only reads a value can't be written back\n for (const column of columns) if (column.value) add(column.title ?? column.key, SKIP)\n for (const column of columns) if (!column.value) addColumn(column.key, column.title)\n for (const key of BUILT_IN) addColumn(key)\n for (const m of all) {\n add(m.idColumn, 'id')\n add(m.parentColumn, 'parent')\n }\n add('id', 'id')\n add('parent', 'parent')\n return names\n}\n\n/** Text `toCSV` wrote behind a `'` so that spreadsheets don't run it as a formula: `'-复盘`. */\nconst unquoteFormula = (text: string) => (/^'[=+\\-@\\t\\r]/.test(text) ? text.slice(1) : text)\n\nconst pad = (n: string) => n.padStart(2, '0')\n\nconst DATE = /^(\\d{4})([-/.])(\\d{1,2})\\2(\\d{1,2})(?:[ T](\\d{1,2}):(\\d{2})(?::\\d{2})?)?$/\n\n/**\n * A date as tasks write it, from the ways spreadsheets save one:\n * `2026/3/2`, `2026-3-2`, `2026.3.2`, with `9:30` or `09:30:00` after it.\n * `null` when it isn't a date.\n */\nexport function readCSVDate(text: string) {\n const match = DATE.exec(text.trim())\n if (!match) return null\n const [, y, , m, d, h, mi] = match\n const value = `${y}-${pad(m!)}-${pad(d!)}${h != null ? ` ${pad(h)}:${mi}` : ''}`\n return parseTime(value) ? value : null\n}\n\n/** A unit as a message writes it: `{count} 天` is `天`. */\nconst unitWord = (pattern: string) => format(pattern, { count: '' }).trim().toLowerCase()\n\n/**\n * A number, with a decimal comma in a file split by `;` (`1,5`), as such\n * files are written; kept in step with `readCSV` of `@dou-ui/sheet`.\n */\nfunction readNumber(text: string, comma: boolean) {\n const value = text.trim()\n if (!value || !/^[+-]?(\\d+([.,]\\d+)?|[.,]\\d+)$/.test(value)) return NaN\n if (comma ? value.includes('.') : value.includes(',')) return NaN\n return Number(value.replace(',', '.'))\n}\n\n/** A duration with its unit, when it says one: `5`, `5 天`, `2 小时`, `2h`. */\nfunction readDuration(text: string, messages: GanttMessages, comma: boolean) {\n const match = /^([+-]?[\\d.,]+)\\s*(.*)$/.exec(text.trim())\n if (!match) return null\n const count = readNumber(match[1]!, comma)\n if (Number.isNaN(count)) return null\n const unit = match[2]!.trim().toLowerCase()\n if (!unit) return { count, unit: null }\n const all = [messages, ...Object.values(ganttLocales)]\n if (unit === 'd' || all.some((m) => unitWord(m.days) === unit))\n return { count, unit: 'd' as const }\n if (unit === 'h' || all.some((m) => unitWord(m.hours) === unit))\n return { count, unit: 'h' as const }\n return null\n}\n\nexport interface ReadCSVContext {\n columns: GanttColumn[]\n messages: GanttMessages\n calendar: WorkCalendar\n /** Tasks outside the file that a task of it may be under, as when appending. */\n outer?: (id: string) => boolean\n}\n\n/**\n * Reads the tasks of a CSV file. Throws when no header is the title\n * column, as the file then isn't a list of tasks.\n */\nexport function readGanttCSV(text: string, context: ReadCSVContext): GanttCSVResult {\n const { columns, messages, calendar } = context\n const delimiter = delimiterOf(text.charCodeAt(0) === 0xfeff ? text.slice(1) : text)\n const comma = delimiter === ';'\n const [head, ...lines] = splitCSV(text, delimiter)\n const names = headerNames(columns, messages)\n const keys = (head ?? []).map((header) => names.get(unquoteFormula(header.trim()).toLowerCase()))\n if (!keys.some((key) => key === 'title'))\n throw new GanttCSVError(\n `[@dou-ui/gantt] No column of the file is the title column; its headers are ${JSON.stringify(head ?? [])}.`,\n 'noTitleColumn',\n format(messages.csvNoTitle, { title: messages.columns.title }),\n )\n const unknownColumns = (head ?? []).filter((header, i) => !keys[i] && header.trim())\n keys.forEach((key, i) => {\n if (key === SKIP) keys[i] = undefined\n })\n const byKey = new Map(columns.map((column) => [column.key, column]))\n // a task whose duration and end are both empty is a milestone, as toCSV writes one\n const timing = keys.some((key) => key === 'end' || key?.startsWith('duration'))\n const warnings: GanttCSVWarning[] = []\n const tasks: GanttTask[] = []\n const parents = new Map<GanttTask, { text: string; row: number }>()\n const ids = new Set<string>()\n\n lines.forEach((fields, index) => {\n const row = index + 2\n if (fields.every((field) => !field.trim())) return\n const cells = new Map<string, string>()\n keys.forEach((key, i) => {\n if (key && !cells.has(key)) cells.set(key, unquoteFormula(fields[i]?.trim() ?? ''))\n })\n const task: GanttTask = { id: '', title: cells.get('title') ?? '' }\n const unread = new Set<string>()\n const warn = (column: string, value: string) => {\n warnings.push({ row, column, text: value })\n unread.add(column)\n }\n\n const id = cells.get('id')\n if (id && ids.has(id)) warn('id', id)\n task.id = id && !ids.has(id) ? id : createId()\n ids.add(task.id)\n const parent = cells.get('parent')\n if (parent) parents.set(task, { text: parent, row })\n\n const startText = cells.get('start') ?? ''\n const startValue = startText ? readCSVDate(startText) : null\n const start = startValue ? parseTime(startValue) : null\n if (startText && !start) warn('start', startText)\n const endText = cells.get('end') ?? ''\n const durationKey = keys.find((key) => key?.startsWith('duration'))\n const durationText = durationKey ? cells.get(durationKey)! : ''\n // an end or a duration needs a start to count from\n if (!startText) {\n if (endText) warn('end', endText)\n else if (durationText) warn('duration', durationText)\n }\n if (start) {\n task.start = startValue!\n if (timing && !endText && !durationText) task.type = 'milestone'\n else {\n const unit = start.dateOnly ? MINUTES_PER_DAY : MINUTES_PER_HOUR\n let end: number | null = null\n if (endText) {\n const endValue = readCSVDate(endText)\n const at = endValue ? parseTime(endValue) : null\n const to = at ? at.at + (at.dateOnly ? MINUTES_PER_DAY : 0) : null\n if (to != null && to > start.at) end = to\n else warn('end', endText)\n } else if (durationText) {\n const read = readDuration(durationText, messages, comma)\n const hours = read?.unit\n ? read.unit === 'h'\n : durationKey === 'duration:h' || (durationKey !== 'duration:d' && !start.dateOnly)\n // working days are whole; hours are kept to the minute\n const count = read ? (hours ? Math.round(read.count * 60) : read.count) : 0\n if (!(count > 0) || !Number.isInteger(count)) warn('duration', durationText)\n else\n end = hours\n ? shiftWorkMinutes(calendar, start.at, count)\n : shiftWorkdays(calendar, start.at, count)\n }\n Object.assign(task, writeSpan(task, start.at, end ?? start.at + unit))\n }\n }\n\n const progress = cells.get('progress')\n if (progress && task.type !== 'milestone') {\n const count = readNumber(progress.replace(/\\s*%$/, ''), comma)\n if (Number.isFinite(count)) task.progress = Math.min(100, Math.max(0, count)) / 100\n else warn('progress', progress)\n }\n\n for (const [key, value] of cells) {\n if (key === 'id' || key === 'parent' || key.startsWith('duration')) continue\n if ((BUILT_IN as readonly string[]).includes(key) || !value) continue\n const column = byKey.get(key)!\n const next = column.parse ? column.parse(value, task) : value\n if (next === undefined) warn(key, value)\n else task.data = { ...task.data, [key]: next }\n }\n if (unread.size) unreadOf.set(task, unread)\n tasks.push(task)\n })\n\n // parents, once every id is known; a parent that is missing, or one of its own subtasks, is left out\n const byId = new Map(tasks.map((task) => [task.id, task]))\n for (const [task, { text: parent, row }] of parents) {\n let at = byId.get(parent)\n while (at && at !== task) at = at.parent ? byId.get(at.parent) : undefined\n if ((byId.has(parent) && !at) || (!byId.has(parent) && context.outer?.(parent)))\n task.parent = parent\n else {\n warnings.push({ row, column: 'parent', text: parent })\n const unread = unreadOf.get(task) ?? new Set()\n unread.add('parent')\n unreadOf.set(task, unread)\n }\n }\n // a summary task's dates and progress come from its subtasks\n const summaries = new Set(tasks.map((task) => task.parent))\n for (const task of tasks)\n if (summaries.has(task.id)) {\n delete task.start\n delete task.end\n delete task.progress\n delete task.type\n }\n const found = keys\n .filter((key) => key != null)\n .map((key) => (key.startsWith('duration') ? 'duration' : key))\n return { tasks, warnings, columns: [...new Set(found)], unknownColumns }\n}\n\n/**\n * Puts the tasks `readGanttCSV` read into a transaction, as\n * `GanttCSVImportOptions.mode` says.\n */\nexport function applyGanttCSV(\n tr: GanttTransaction,\n result: GanttCSVResult,\n mode: 'replace' | 'append',\n calendar: WorkCalendar,\n) {\n if (mode === 'append') {\n // ids already in the chart are made new, and the subtasks follow\n // a task may also go under one the chart has; the file's own ids come first\n const renamed = new Map<string, string>()\n for (const task of result.tasks) if (tr.getTask(task.id)) renamed.set(task.id, createId())\n for (const task of result.tasks)\n tr.addTask({\n ...task,\n id: renamed.get(task.id) ?? task.id,\n parent: task.parent != null ? (renamed.get(task.parent) ?? task.parent) : undefined,\n })\n return\n }\n\n const has = new Set(result.columns)\n const timed = has.has('end') || has.has('duration')\n const keep = new Set(result.tasks.map((task) => task.id))\n for (const task of result.tasks) {\n const old = tr.getTask(task.id)\n if (!old) continue\n // a value that couldn't be read leaves the chart's\n const unread = unreadOf.get(task)\n const read = (key: string) => has.has(key) && !unread?.has(key)\n const patch: Partial<GanttTask> = { title: task.title }\n if (read('parent')) patch.parent = task.parent\n // a parent that is removed would take the task with it\n else if (old.parent != null && !keep.has(old.parent)) patch.parent = undefined\n if (read('progress')) patch.progress = task.progress\n const datesRead = !['start', 'end', 'duration'].some((key) => unread?.has(key))\n if (has.has('start') && datesRead) {\n const span = spanOf(old)\n const start = spanOf(task)\n if (timed || task.start == null || !span || !start || task.type !== old.type) {\n patch.start = task.start\n patch.end = task.end\n patch.type = task.type\n } else if (old.type === 'milestone') patch.start = task.start\n // without an end or a duration in the file, the task moves and keeps its work\n else\n Object.assign(\n patch,\n writeSpan(old, start.start, endKeepingWork(calendar, span, start.start)),\n )\n }\n const own = [...has].filter((key) => !BUILT_IN_KEYS.has(key) && !unread?.has(key))\n if (own.length) {\n const data = { ...old.data }\n for (const key of own) {\n if (task.data?.[key] === undefined) delete data[key]\n else data[key] = task.data[key]\n }\n patch.data = Object.keys(data).length ? data : undefined\n }\n tr.updateTask(task.id, patch)\n }\n // the rest goes with its links, from the end, before the new tasks are added after them\n const now = tr.doc\n const gone = new Set(now.tasks.filter((task) => !keep.has(task.id)).map((task) => task.id))\n if (gone.size) {\n for (let i = now.links.length - 1; i >= 0; i--) {\n const link = now.links[i]!\n if (gone.has(link.from) || gone.has(link.to)) tr.removeLink(link.id)\n }\n for (let i = now.tasks.length - 1; i >= 0; i--) {\n const id = now.tasks[i]!.id\n if (gone.has(id)) tr.step({ type: 'removeTask', id })\n }\n }\n\n for (const task of result.tasks) if (!tr.getTask(task.id)) tr.addTask(task)\n\n // siblings in the order of the file: new order keys where the chart has them otherwise\n const tree = buildTree(tr.doc.tasks)\n const wanted = new Map<string | undefined, string[]>()\n for (const task of result.tasks) {\n const parent = tr.getTask(task.id)?.parent\n const list = wanted.get(parent)\n if (list) list.push(task.id)\n else wanted.set(parent, [task.id])\n }\n for (const [parent, ids] of wanted) {\n const shown = (tree.childrenOf.get(parent) ?? []).map((task) => task.id)\n if (shown.join('\\n') === ids.join('\\n')) continue\n const keys = orderKeys(ids.length)\n ids.forEach((id, i) => tr.updateTask(id, { order: keys[i] }))\n }\n}\n"],"mappings":"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"}
|