@ssobig/writer-cli 0.3.4 → 0.3.5
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/package.json +1 -1
- package/templates/mystery-v1/component-draft-operations.js +50 -3
- package/templates/mystery-v1/component-field-contracts.js +1 -1
- package/templates/mystery-v1/timeline-model.js +4 -3
- package/tools/writer-cli/package-lock.json +2 -2
- package/tools/writer-cli/package.json +1 -1
package/package.json
CHANGED
|
@@ -296,7 +296,7 @@
|
|
|
296
296
|
return true;
|
|
297
297
|
}
|
|
298
298
|
|
|
299
|
-
|
|
299
|
+
const mutations = {
|
|
300
300
|
markChange,
|
|
301
301
|
replaceData,
|
|
302
302
|
arrayValue,
|
|
@@ -317,8 +317,55 @@
|
|
|
317
317
|
characterSectionMove,
|
|
318
318
|
commonAdd,
|
|
319
319
|
toggleComponentField
|
|
320
|
-
}
|
|
320
|
+
};
|
|
321
|
+
// Session-only history belongs to each Data Component, never browser storage.
|
|
322
|
+
const histories = new Map();
|
|
323
|
+
const historyFor = instance => {
|
|
324
|
+
if (!histories.has(instance.instanceId)) histories.set(instance.instanceId, { undo:[], redo:[] });
|
|
325
|
+
return histories.get(instance.instanceId);
|
|
326
|
+
};
|
|
327
|
+
const serialized = instance => JSON.stringify(options.draft(instance));
|
|
328
|
+
const wrapped = Object.fromEntries(Object.entries(mutations).map(([name, operation]) => [name,
|
|
329
|
+
name === "arrayValue" ? operation : (instance, ...args) => {
|
|
330
|
+
if (options.saveState?.(instance)?.locked) return false;
|
|
331
|
+
const before = serialized(instance), history = historyFor(instance);
|
|
332
|
+
const last = history.undo.at(-1);
|
|
333
|
+
if ((last && last.after !== before) || (history.redo.length && history.redo.at(-1).before !== before)) { history.undo=[]; history.redo=[]; }
|
|
334
|
+
const result = operation(instance, ...args);
|
|
335
|
+
const after = serialized(instance);
|
|
336
|
+
if (before === after) return result;
|
|
337
|
+
const now = Date.now(), key = name === "markChange" ? JSON.stringify(args[0]) : null;
|
|
338
|
+
const previous = history.undo.at(-1);
|
|
339
|
+
if (key && previous?.key === key && now-previous.time<800 && !history.redo.length) {
|
|
340
|
+
previous.after=after; previous.time=now;
|
|
341
|
+
} else history.undo.push({before,after,key,time:now});
|
|
342
|
+
history.redo=[];
|
|
343
|
+
while (history.undo.length>50 || (history.undo.length>1 && history.undo.reduce((n,x)=>n+x.before.length+x.after.length,0)>8*1024*1024)) history.undo.shift();
|
|
344
|
+
return result;
|
|
345
|
+
}
|
|
346
|
+
]));
|
|
347
|
+
function restore(instance, direction) {
|
|
348
|
+
if (options.saveState?.(instance)?.locked) return false;
|
|
349
|
+
const history = historyFor(instance), source = history[direction], entry = source.at(-1);
|
|
350
|
+
if (!entry) return false;
|
|
351
|
+
const undo = direction === "undo";
|
|
352
|
+
if (serialized(instance) !== (undo ? entry.after : entry.before)) { histories.delete(instance.instanceId); return false; }
|
|
353
|
+
const nextData = JSON.parse(undo ? entry.before : entry.after);
|
|
354
|
+
try { options.validate?.(instance, nextData); } catch { histories.delete(instance.instanceId); return false; }
|
|
355
|
+
// Use the same autosave/CAS path as every other edit.
|
|
356
|
+
source.pop(); history[undo ? "redo" : "undo"].push(entry);
|
|
357
|
+
return replaceData(instance, nextData);
|
|
358
|
+
}
|
|
359
|
+
return Object.freeze({ ...wrapped, undo:instance=>restore(instance,"undo"), redo:instance=>restore(instance,"redo"), clearHistory:()=>histories.clear() });
|
|
321
360
|
}
|
|
322
361
|
|
|
323
|
-
|
|
362
|
+
function historyShortcut(event) {
|
|
363
|
+
if (event.defaultPrevented || event.isComposing || event.altKey || !(event.ctrlKey || event.metaKey)) return null;
|
|
364
|
+
if (event.target?.closest?.("input,textarea,select,[contenteditable]:not([contenteditable='false']),.cm-editor,dialog")) return null;
|
|
365
|
+
const key = String(event.key || "").toLowerCase();
|
|
366
|
+
if (key === "z") return event.shiftKey ? "redo" : "undo";
|
|
367
|
+
if (key === "y" && !event.shiftKey) return "redo";
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
return Object.freeze({ create, historyShortcut });
|
|
324
371
|
});
|
|
@@ -322,7 +322,7 @@
|
|
|
322
322
|
placeId: field("nullable-uuid"), characterId: field("nullable-stable-key")
|
|
323
323
|
}, { timeRange: field("object", { item: "range" }), included: field("boolean") }),
|
|
324
324
|
range: shape({ start: field("object", { item: "point" }), end: field("object", { item: "point" }) }),
|
|
325
|
-
point: shape({ day: field("integer", { minimum:
|
|
325
|
+
point: shape({ day: field("integer", { minimum: -999999, maximum: 999999 }), hour: field("integer", { minimum: 0, maximum: 23 }), minute: field("integer", { minimum: 0, maximum: 59 }) })
|
|
326
326
|
}
|
|
327
327
|
});
|
|
328
328
|
function eventTimelineError(data) {
|
|
@@ -262,9 +262,10 @@
|
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
function pointMinutes(point) { return point.day * 1440 + point.hour * 60 + point.minute; }
|
|
265
|
-
function minutePoint(value) { return { day: Math.floor(value / 1440), hour: Math.floor(value % 1440 / 60), minute: value % 60 }; }
|
|
265
|
+
function minutePoint(value) { return { day: Math.floor(value / 1440), hour: Math.floor(((value % 1440 + 1440) % 1440) / 60), minute: (value % 60 + 60) % 60 }; }
|
|
266
266
|
function defaultRange(start = { day: 0, hour: 9, minute: 0 }) { return { start: { ...start }, end: minutePoint(pointMinutes(start) + 30) }; }
|
|
267
|
-
function
|
|
267
|
+
function dayLabel(day) { return day === 0 ? "D-day" : `D${day > 0 ? "+" : ""}${day}`; }
|
|
268
|
+
function pointLabel(point) { return `${dayLabel(point.day)} ${String(point.hour).padStart(2, "0")}:${String(point.minute).padStart(2, "0")}`; }
|
|
268
269
|
function rangeLabel(range) { return range ? `${pointLabel(range.start)}–${pointLabel(range.end)}` : ""; }
|
|
269
270
|
function orderedEvents(data) {
|
|
270
271
|
const groups = new Map(data.timeGroups.map((group, index) => [group.id, { ...group, index }]));
|
|
@@ -292,5 +293,5 @@
|
|
|
292
293
|
throw new Error("타임라인 data가 등록된 모양이 아닙니다.");
|
|
293
294
|
}
|
|
294
295
|
|
|
295
|
-
return Object.freeze({ convertToEventTimeline, pointMinutes, minutePoint, defaultRange, pointLabel, rangeLabel, orderedEvents, eventTimelineProjection, detectShape, convertLegacyTimeline, compareLegacyToV2, normalizedTimelineData, entryPlaceNames, orderedEntries, overlapGroups, deriveClusters });
|
|
296
|
+
return Object.freeze({ convertToEventTimeline, pointMinutes, minutePoint, defaultRange, dayLabel, pointLabel, rangeLabel, orderedEvents, eventTimelineProjection, detectShape, convertLegacyTimeline, compareLegacyToV2, normalizedTimelineData, entryPlaceNames, orderedEntries, overlapGroups, deriveClusters });
|
|
296
297
|
});
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ssobig/writer-cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@ssobig/writer-cli",
|
|
9
|
-
"version": "0.3.
|
|
9
|
+
"version": "0.3.5",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@supabase/supabase-js": "2.110.9"
|
|
12
12
|
},
|