@inkandswitch/patchwork-pkg-base 0.0.7 → 0.0.8
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/modules.json +4 -0
- package/package.json +1 -1
- package/packages/account-history/dist/assets/HistoryRecorder-HO_sxeuE.js +248 -0
- package/packages/account-history/dist/assets/HistoryRecorder-HO_sxeuE.js.map +1 -0
- package/packages/account-history/dist/assets/HistoryViewer-Ct5g7zek.js +308 -0
- package/packages/account-history/dist/assets/HistoryViewer-Ct5g7zek.js.map +1 -0
- package/packages/account-history/dist/assets/NotebookViewer-BkYJL6Jc.js +504 -0
- package/packages/account-history/dist/assets/NotebookViewer-BkYJL6Jc.js.map +1 -0
- package/packages/account-history/dist/assets/index-CFzuSuJg.js +148 -0
- package/packages/account-history/dist/assets/index-CFzuSuJg.js.map +1 -0
- package/packages/account-history/dist/assets/makeDocumentProjection-DXgEZYyv.js +706 -0
- package/packages/account-history/dist/assets/makeDocumentProjection-DXgEZYyv.js.map +1 -0
- package/packages/account-history/dist/assets/store-CRmRL4Cv.js +1079 -0
- package/packages/account-history/dist/assets/store-CRmRL4Cv.js.map +1 -0
- package/packages/account-history/dist/index.js +5 -0
- package/packages/account-history/dist/index.js.map +1 -0
- package/packages/account-history/package.json +33 -0
- package/packages/chat/dist/ChatRoot-BXsklgz7.js +6113 -0
- package/packages/chat/dist/context-tool-CenQrztX.js +107 -0
- package/packages/chat/dist/datatype-1niKKK-c.js +15 -0
- package/packages/chat/dist/index-DYFHDHZ9.js +368 -0
- package/packages/chat/dist/index.js +5 -0
- package/packages/chat/dist/tool-D9BTS5wc.js +38 -0
- package/packages/chat/package.json +42 -0
- package/packages/comments-view/dist/assets/comments-D9lwpHOh.js +50 -0
- package/packages/comments-view/dist/assets/comments-D9lwpHOh.js.map +1 -0
- package/packages/comments-view/dist/assets/datatype-C4SMwWiO.js +22 -0
- package/packages/comments-view/dist/assets/datatype-C4SMwWiO.js.map +1 -0
- package/packages/comments-view/dist/assets/main-C8GVksuY.js +2973 -0
- package/packages/comments-view/dist/assets/main-C8GVksuY.js.map +1 -0
- package/packages/comments-view/dist/index.js +115 -0
- package/packages/comments-view/dist/index.js.map +1 -0
- package/packages/comments-view/package.json +36 -0
- package/packages/drafts/dist/assets/DraftDatatype-Dn8VF6oC.js +14 -0
- package/packages/drafts/dist/assets/DraftDatatype-Dn8VF6oC.js.map +1 -0
- package/packages/drafts/dist/assets/DraftListProvider-DVhUGU1x.js +362 -0
- package/packages/drafts/dist/assets/DraftListProvider-DVhUGU1x.js.map +1 -0
- package/packages/drafts/dist/assets/DraftOverlayProvider-NMDqnpBV.js +123 -0
- package/packages/drafts/dist/assets/DraftOverlayProvider-NMDqnpBV.js.map +1 -0
- package/packages/drafts/dist/assets/clone-policy-D4XOfZxs.js +15 -0
- package/packages/drafts/dist/assets/clone-policy-D4XOfZxs.js.map +1 -0
- package/packages/drafts/dist/assets/index-DAqs20jZ.js +65 -0
- package/packages/drafts/dist/assets/index-DAqs20jZ.js.map +1 -0
- package/packages/drafts/dist/assets/main-Cdev7z-t.js +2366 -0
- package/packages/drafts/dist/assets/main-Cdev7z-t.js.map +1 -0
- package/packages/drafts/dist/index.js +140 -0
- package/packages/drafts/dist/index.js.map +1 -0
- package/packages/drafts/package.json +34 -0
package/modules.json
CHANGED
|
@@ -3,14 +3,18 @@
|
|
|
3
3
|
"type": "patchwork:module-settings"
|
|
4
4
|
},
|
|
5
5
|
"modules": [
|
|
6
|
+
"./packages/account-history/",
|
|
6
7
|
"./packages/account-picker/",
|
|
7
8
|
"./packages/add-doc-to-sidebar-button/",
|
|
9
|
+
"./packages/chat/",
|
|
8
10
|
"./packages/codemirror-base/",
|
|
9
11
|
"./packages/codemirror-markdown/",
|
|
10
12
|
"./packages/commands/",
|
|
13
|
+
"./packages/comments-view/",
|
|
11
14
|
"./packages/contact/",
|
|
12
15
|
"./packages/doc-openwith/",
|
|
13
16
|
"./packages/doc-presence/",
|
|
17
|
+
"./packages/drafts/",
|
|
14
18
|
"./packages/folder/",
|
|
15
19
|
"./packages/patternwitch/",
|
|
16
20
|
"./packages/raw/",
|
package/package.json
CHANGED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { c as createMemo, o as onCleanup, a as createStore, r as reconcile, p as produce, b as createSignal, d as onMount, e as render, f as createComponent, g as delegateEvents, h as createEffect, t as template } from "./store-CRmRL4Cv.js";
|
|
2
|
+
import { OpenDocumentEvent } from "@inkandswitch/patchwork-elements";
|
|
3
|
+
import { getType } from "@inkandswitch/patchwork-filesystem";
|
|
4
|
+
import { getFallbackTool, getRegistry } from "@inkandswitch/patchwork-plugins";
|
|
5
|
+
import { applyPatches } from "@automerge/automerge/slim";
|
|
6
|
+
import { A as ACCOUNT_HISTORY_DATATYPE, V as VIEWER_TOOL_ID, D as DEDUPLICATION_TIME_THRESHOLD } from "./index-CFzuSuJg.js";
|
|
7
|
+
function autoproduce(payload) {
|
|
8
|
+
return (doc) => applyPatches(doc, payload.patches);
|
|
9
|
+
}
|
|
10
|
+
var cache = /* @__PURE__ */ new WeakMap();
|
|
11
|
+
function initial(handle) {
|
|
12
|
+
const template2 = {};
|
|
13
|
+
applyPatches(template2, handle.diff([], handle.heads()));
|
|
14
|
+
return template2;
|
|
15
|
+
}
|
|
16
|
+
function makeDocumentProjection(handle) {
|
|
17
|
+
onCleanup(() => {
|
|
18
|
+
const item = cache.get(handle);
|
|
19
|
+
if (!item) return;
|
|
20
|
+
if (!item.refs--) item.cleanup();
|
|
21
|
+
});
|
|
22
|
+
if (cache.has(handle)) {
|
|
23
|
+
const item = cache.get(handle);
|
|
24
|
+
item.refs++;
|
|
25
|
+
return item.store;
|
|
26
|
+
}
|
|
27
|
+
const [doc, set] = createStore(initial(handle));
|
|
28
|
+
cache.set(handle, {
|
|
29
|
+
refs: 0,
|
|
30
|
+
store: doc,
|
|
31
|
+
cleanup() {
|
|
32
|
+
handle.off("change", patch);
|
|
33
|
+
handle.off("delete", ondelete);
|
|
34
|
+
cache.delete(handle);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
function patch(payload) {
|
|
38
|
+
if (payload.scopeReplaced) {
|
|
39
|
+
set(reconcile(payload.doc ?? {}));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
set(produce(autoproduce(payload)));
|
|
43
|
+
}
|
|
44
|
+
function ondelete() {
|
|
45
|
+
set(reconcile({}));
|
|
46
|
+
}
|
|
47
|
+
handle.on("change", patch);
|
|
48
|
+
handle.on("delete", ondelete);
|
|
49
|
+
return doc;
|
|
50
|
+
}
|
|
51
|
+
function createDocumentProjection(handle) {
|
|
52
|
+
return createMemo(() => {
|
|
53
|
+
const unwrappedHandle = typeof handle == "function" ? handle() : handle;
|
|
54
|
+
return unwrappedHandle && makeDocumentProjection(unwrappedHandle);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function subscribe(element, selector, listener) {
|
|
58
|
+
const view = element.closest("patchwork-view");
|
|
59
|
+
const dispatchEl = view ?? element;
|
|
60
|
+
const channel = new MessageChannel();
|
|
61
|
+
const port = channel.port2;
|
|
62
|
+
const controller = new AbortController();
|
|
63
|
+
const { signal } = controller;
|
|
64
|
+
port.addEventListener("message", (event) => {
|
|
65
|
+
if (event.data?.type === "change")
|
|
66
|
+
listener(event.data.value);
|
|
67
|
+
}, { signal });
|
|
68
|
+
port.start();
|
|
69
|
+
const detail = {
|
|
70
|
+
selector,
|
|
71
|
+
port: channel.port1
|
|
72
|
+
};
|
|
73
|
+
dispatchEl.dispatchEvent(new CustomEvent("patchwork:subscribe", {
|
|
74
|
+
detail,
|
|
75
|
+
bubbles: true,
|
|
76
|
+
composed: true
|
|
77
|
+
}));
|
|
78
|
+
return () => {
|
|
79
|
+
if (signal.aborted)
|
|
80
|
+
return;
|
|
81
|
+
controller.abort();
|
|
82
|
+
port.postMessage({ type: "unsubscribe" });
|
|
83
|
+
port.close();
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function resolveElement(source) {
|
|
87
|
+
return typeof source === "function" ? source() : source;
|
|
88
|
+
}
|
|
89
|
+
function subscribeDoc(element, selector) {
|
|
90
|
+
const [handle, setHandle] = createSignal(void 0);
|
|
91
|
+
onMount(() => {
|
|
92
|
+
const target = resolveElement(element);
|
|
93
|
+
if (!target)
|
|
94
|
+
return;
|
|
95
|
+
let canceled = false;
|
|
96
|
+
const unsubscribe = subscribe(target, selector, (url) => {
|
|
97
|
+
if (!url)
|
|
98
|
+
return;
|
|
99
|
+
const repo = "repo" in window ? window.repo : void 0;
|
|
100
|
+
if (!repo)
|
|
101
|
+
return;
|
|
102
|
+
void Promise.resolve(repo.find(url)).then((h) => {
|
|
103
|
+
if (canceled)
|
|
104
|
+
return;
|
|
105
|
+
setHandle(() => h);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
onCleanup(() => {
|
|
109
|
+
canceled = true;
|
|
110
|
+
unsubscribe();
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
const doc = createDocumentProjection(handle);
|
|
114
|
+
return [doc, handle];
|
|
115
|
+
}
|
|
116
|
+
var _tmpl$ = /* @__PURE__ */ template(`<button class=account-history-toolbar-button title="Open account history"><svg xmlns=http://www.w3.org/2000/svg width=20 height=20 viewBox="0 0 24 24"fill=none stroke=currentColor stroke-width=2 stroke-linecap=round stroke-linejoin=round><circle cx=12 cy=12 r=10></circle><polyline points="12 6 12 12 16 14">`);
|
|
117
|
+
const TOOL_STORAGE_ID = "account-history";
|
|
118
|
+
function HistoryRecorder(props) {
|
|
119
|
+
const [historyDoc, historyHandle] = subscribeDoc(props.element, {
|
|
120
|
+
type: "patchwork:tool-storage",
|
|
121
|
+
toolId: TOOL_STORAGE_ID
|
|
122
|
+
});
|
|
123
|
+
const historyUrl = () => historyHandle()?.url;
|
|
124
|
+
createEffect(() => {
|
|
125
|
+
const handle = historyHandle();
|
|
126
|
+
const doc = historyDoc();
|
|
127
|
+
if (!handle || !doc || Array.isArray(doc.entries)) return;
|
|
128
|
+
handle.change((d) => {
|
|
129
|
+
if (Array.isArray(d.entries)) return;
|
|
130
|
+
d["@patchwork"] = {
|
|
131
|
+
type: ACCOUNT_HISTORY_DATATYPE
|
|
132
|
+
};
|
|
133
|
+
d.title = "Account History";
|
|
134
|
+
d.entries = [];
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
onMount(() => {
|
|
138
|
+
const onDocumentOpened = async (event) => {
|
|
139
|
+
const openEvent = event;
|
|
140
|
+
const {
|
|
141
|
+
url: rawUrl,
|
|
142
|
+
toolId: rawToolId
|
|
143
|
+
} = openEvent.detail;
|
|
144
|
+
const [docUrl, ...heads] = rawUrl.split("#");
|
|
145
|
+
const url = docUrl;
|
|
146
|
+
let toolId = rawToolId;
|
|
147
|
+
const docHandle = await props.repo.find(url);
|
|
148
|
+
await docHandle.whenReady();
|
|
149
|
+
const doc = docHandle.doc();
|
|
150
|
+
if (!doc) {
|
|
151
|
+
console.warn("HistoryRecorder: failed to load document");
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const datatype = getType(doc);
|
|
155
|
+
if (!toolId) {
|
|
156
|
+
if (!datatype) {
|
|
157
|
+
console.warn("HistoryRecorder: no tool found for document type; skipping history recording");
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const fallbackTool = getFallbackTool(doc);
|
|
161
|
+
if (!fallbackTool) {
|
|
162
|
+
console.warn("HistoryRecorder: no tool found for document type; skipping history recording");
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
toolId = fallbackTool.id;
|
|
166
|
+
}
|
|
167
|
+
if (datatype === ACCOUNT_HISTORY_DATATYPE) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const currentHistoryHandle = historyHandle();
|
|
171
|
+
if (!currentHistoryHandle) {
|
|
172
|
+
console.warn("HistoryRecorder: history handle not ready yet");
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const historyDocSnapshot = currentHistoryHandle.doc();
|
|
176
|
+
if (!historyDocSnapshot) {
|
|
177
|
+
console.warn("HistoryRecorder: history document not ready yet");
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const lastEntry = historyDocSnapshot.entries[historyDocSnapshot.entries.length - 1];
|
|
181
|
+
if (lastEntry && lastEntry.docUrl === url && lastEntry.toolId === toolId && Date.now() - lastEntry.timestamp < DEDUPLICATION_TIME_THRESHOLD) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
let docTitle = "Untitled";
|
|
185
|
+
if (datatype) {
|
|
186
|
+
const registry = getRegistry("patchwork:datatype");
|
|
187
|
+
const loaded = registry.get(datatype);
|
|
188
|
+
if (loaded && "module" in loaded) {
|
|
189
|
+
const impl = loaded.module;
|
|
190
|
+
try {
|
|
191
|
+
docTitle = impl.getTitle(doc) || "Untitled";
|
|
192
|
+
} catch {
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
let entryHeads = heads;
|
|
197
|
+
if (entryHeads.length === 0) entryHeads = docHandle.heads();
|
|
198
|
+
const newEntry = {
|
|
199
|
+
timestamp: Date.now(),
|
|
200
|
+
docUrl: url,
|
|
201
|
+
docTitle,
|
|
202
|
+
docType: datatype || "unknown",
|
|
203
|
+
toolId,
|
|
204
|
+
heads: entryHeads
|
|
205
|
+
};
|
|
206
|
+
currentHistoryHandle.change((doc2) => {
|
|
207
|
+
doc2.entries.push(newEntry);
|
|
208
|
+
});
|
|
209
|
+
};
|
|
210
|
+
document.addEventListener("patchwork:open-document", onDocumentOpened, {
|
|
211
|
+
capture: true
|
|
212
|
+
});
|
|
213
|
+
onCleanup(() => {
|
|
214
|
+
document.removeEventListener("patchwork:open-document", onDocumentOpened, {
|
|
215
|
+
capture: true
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
const openHistory = () => {
|
|
220
|
+
const currentHistoryUrl = historyUrl();
|
|
221
|
+
if (!currentHistoryUrl) {
|
|
222
|
+
console.warn("HistoryRecorder: no history URL yet");
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
props.element.dispatchEvent(new OpenDocumentEvent({
|
|
226
|
+
url: currentHistoryUrl,
|
|
227
|
+
toolId: VIEWER_TOOL_ID
|
|
228
|
+
}));
|
|
229
|
+
};
|
|
230
|
+
return (() => {
|
|
231
|
+
var _el$ = _tmpl$();
|
|
232
|
+
_el$.$$click = openHistory;
|
|
233
|
+
return _el$;
|
|
234
|
+
})();
|
|
235
|
+
}
|
|
236
|
+
const renderHistoryRecorder = (handle, element) => render(() => createComponent(HistoryRecorder, {
|
|
237
|
+
handle,
|
|
238
|
+
get repo() {
|
|
239
|
+
return element.repo;
|
|
240
|
+
},
|
|
241
|
+
element
|
|
242
|
+
}), element);
|
|
243
|
+
delegateEvents(["click"]);
|
|
244
|
+
export {
|
|
245
|
+
HistoryRecorder,
|
|
246
|
+
renderHistoryRecorder
|
|
247
|
+
};
|
|
248
|
+
//# sourceMappingURL=HistoryRecorder-HO_sxeuE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HistoryRecorder-HO_sxeuE.js","sources":["../../../node_modules/.pnpm/@automerge+automerge-repo-solid-primitives@2.6.0-subduction.39_@automerge+automerge@3.2.6_solid-js@1.9.13/node_modules/@automerge/automerge-repo-solid-primitives/dist/index.js","../../../node_modules/.pnpm/@inkandswitch+patchwork-providers@0.2.2_@automerge+automerge-repo@2.6.0-subduction.34_supports-color@5.5.0_/node_modules/@inkandswitch/patchwork-providers/dist/index.js","../../../node_modules/.pnpm/@inkandswitch+patchwork-providers-solid@0.2.3_@automerge+automerge-repo-solid-primitive_516ecf6cccc1fa6355969df399fbbe65/node_modules/@inkandswitch/patchwork-providers-solid/dist/index.js","../../src/HistoryRecorder.tsx"],"sourcesContent":["import { applyPatches } from \"@automerge/automerge/slim\";\nimport { createContext, createEffect, createMemo, createResource, createSignal, onCleanup, useContext } from \"solid-js\";\nimport { createStore, produce, reconcile } from \"solid-js/store\";\n//#region src/autoproduce.ts\n/**\n* convert automerge patches to solid producer operations\n* @param payload the\n* [DocHandleChangePayload](https://automerge.org/automerge-repo/interfaces/_automerge_automerge_repo.DocHandleChangePayload.html)\n* from the handle.on(\"change\n* @returns a callback for an immer-like function. e.g.\n* [produce](https://docs.solidjs.com/reference/store-utilities/produce) for\n* [Solid\n* Stores](https://docs.solidjs.com/reference/store-utilities/create-store)\n*/\nfunction autoproduce(payload) {\n\treturn (doc) => applyPatches(doc, payload.patches);\n}\n//#endregion\n//#region src/makeDocumentProjection.ts\nvar cache = /* @__PURE__ */ new WeakMap();\nfunction initial(handle) {\n\tconst template = {};\n\tapplyPatches(template, handle.diff([], handle.heads()));\n\treturn template;\n}\n/**\n* make a fine-grained live view of a document from its handle.\n* @param handle an Automerge\n* [DocHandle](https://automerge.org/automerge-repo/classes/_automerge_automerge_repo.DocHandle.html)\n*/\nfunction makeDocumentProjection(handle) {\n\tonCleanup(() => {\n\t\tconst item = cache.get(handle);\n\t\tif (!item) return;\n\t\tif (!item.refs--) item.cleanup();\n\t});\n\tif (cache.has(handle)) {\n\t\tconst item = cache.get(handle);\n\t\titem.refs++;\n\t\treturn item.store;\n\t}\n\tconst [doc, set] = createStore(initial(handle));\n\tcache.set(handle, {\n\t\trefs: 0,\n\t\tstore: doc,\n\t\tcleanup() {\n\t\t\thandle.off(\"change\", patch);\n\t\t\thandle.off(\"delete\", ondelete);\n\t\t\tcache.delete(handle);\n\t\t}\n\t});\n\tfunction patch(payload) {\n\t\tif (payload.scopeReplaced) {\n\t\t\tset(reconcile(payload.doc ?? {}));\n\t\t\treturn;\n\t\t}\n\t\tset(produce(autoproduce(payload)));\n\t}\n\tfunction ondelete() {\n\t\tset(reconcile({}));\n\t}\n\thandle.on(\"change\", patch);\n\thandle.on(\"delete\", ondelete);\n\treturn doc;\n}\n//#endregion\n//#region src/createDocumentProjection.ts\n/**\n* get a fine-grained live view of a document from a handle. works with\n* {@link useDocHandle}.\n* @param handle an accessor (signal/resource) of a\n* [DocHandle](https://automerge.org/automerge-repo/classes/_automerge_automerge_repo.DocHandle.html)\n*/\nfunction createDocumentProjection(handle) {\n\treturn createMemo(() => {\n\t\tconst unwrappedHandle = typeof handle == \"function\" ? handle() : handle;\n\t\treturn unwrappedHandle && makeDocumentProjection(unwrappedHandle);\n\t});\n}\n//#endregion\n//#region src/context.ts\n/**\n* a [context](https://docs.solidjs.com/concepts/context) that provides access\n* to an Automerge Repo. you don't need this, you can pass the repo in the\n* second arg to the functions that need it.\n*/\nvar RepoContext = createContext(null);\n//#endregion\n//#region src/useDocHandle.ts\n/**\n* get a\n* [DocHandle](https://automerge.org/automerge-repo/classes/_automerge_automerge_repo.DocHandle.html)\n* from an\n* [AutomergeUrl](https://automerge.org/automerge-repo/types/_automerge_automerge_repo.AutomergeUrl.html)\n* as a\n* [Resource](https://docs.solidjs.com/reference/basic-reactivity/create-resource).\n* Waits for the handle to be ready.\n*/\nfunction useDocHandle(url, options) {\n\tconst contextRepo = useContext(RepoContext);\n\tif (!options?.repo && !contextRepo) throw new Error(\"use outside <RepoContext> requires options.repo\");\n\tconst repo = options?.repo || contextRepo;\n\tfunction getExistingHandle() {\n\t\tif (options?.[\"~skipInitialValue\"]) return void 0;\n\t\tconst unwrappedURL = typeof url == \"function\" ? url() : url;\n\t\tif (!unwrappedURL) return void 0;\n\t\tconst state = repo.findWithProgress(unwrappedURL).peek();\n\t\treturn state.state === \"ready\" ? state.handle : void 0;\n\t}\n\tconst [handle, { mutate }] = createResource(url, (url) => repo.findWithProgress(url).whenReady(), { initialValue: getExistingHandle() });\n\tcreateEffect(() => {\n\t\tif (!(typeof url == \"function\" ? url() : url)) mutate();\n\t});\n\treturn handle;\n}\n//#endregion\n//#region src/useDocument.ts\n/**\n* get a fine-grained live view of a document, and its handle, from a URL.\n* @param url a function that returns a url\n*/\nfunction useDocument(url, options) {\n\tconst handle = useDocHandle(url, options);\n\treturn [createDocumentProjection(handle), handle];\n}\n//#endregion\n//#region src/makeDocSignal.ts\n/**\n* a light coarse-grained primitive when you care only _that_ a doc has changed,\n* and not _how_.\n* @param handle an Automerge\n* [DocHandle](https://automerge.org/automerge-repo/classes/_automerge_automerge_repo.DocHandle.html)\n*/\nfunction makeDocSignal(handle) {\n\tconst [signal, setSignal] = createSignal(handle.doc());\n\tfunction update() {\n\t\tsetSignal(() => handle.doc());\n\t}\n\thandle.on(\"change\", update);\n\tonCleanup(() => handle.off(\"change\", update));\n\treturn signal;\n}\n//#endregion\n//#region src/createDocSignal.ts\n/**\n* a light coarse-grained primitive when you care only _that_ a doc has changed,\n* and not _how_. works with {@link useDocHandle}.\n* @param handle an accessor (signal/resource) of a\n* [DocHandle](https://automerge.org/automerge-repo/classes/_automerge_automerge_repo.DocHandle.html)\n*/\nfunction createDocSignal(handle) {\n\tconst [signal, setSignal] = createSignal(handle()?.doc());\n\tcreateEffect(() => {\n\t\tconst h = handle();\n\t\tfunction update() {\n\t\t\tsetSignal(() => h?.doc());\n\t\t}\n\t\tupdate();\n\t\tif (h) {\n\t\t\th.on(\"change\", update);\n\t\t\tonCleanup(() => h.off(\"change\", update));\n\t\t}\n\t});\n\treturn signal;\n}\n//#endregion\n//#region src/useDocSignal.ts\n/**\n* a light coarse-grained primitive when you care only _that_ a doc has changed,\n* and not _how_. returns [doc, handle] from a URL.\n* @param url a function that returns a url\n*/\nfunction useDocSignal(url, options) {\n\tconst handle = useDocHandle(url, options);\n\treturn [createDocSignal(handle), handle];\n}\n//#endregion\n//#region src/useRepo.ts\n/** grab the repo from the {@link RepoContext} */\nfunction useRepo() {\n\tconst repo = useContext(RepoContext);\n\tif (!repo) throw new Error(\"Please wrap me in a <RepoContext value={repo}>\");\n\treturn repo;\n}\n//#endregion\nexport { RepoContext, autoproduce, createDocSignal, createDocumentProjection, makeDocSignal, makeDocumentProjection, useDocHandle, useDocSignal, useDocument, useRepo };\n\n//# sourceMappingURL=index.js.map","/**\n * Open a streaming subscription. Dispatches a `patchwork:subscribe` for the\n * given `selector` carrying a fresh `MessageChannel` port; the answering\n * provider pushes values back over that channel for as long as the\n * subscription is live. `listener` is invoked once per emission (the first\n * delivery is always async because `MessagePort.postMessage` queues a task).\n *\n * The event is dispatched from the nearest enclosing `<patchwork-view>` so\n * callers can pass any node inside a provider subtree. An unclaimed\n * subscription is never settled: if no provider answers, `listener` simply\n * never fires.\n *\n * Returns an unsubscribe function. Calling it tells the provider to tear down\n * (via an `unsubscribe` message) and closes the consumer's port; any values\n * the provider emits after that are dropped.\n */\nexport function subscribe(element, selector, listener) {\n const view = element.closest(\"patchwork-view\");\n const dispatchEl = view ?? element;\n const channel = new MessageChannel();\n const port = channel.port2;\n const controller = new AbortController();\n const { signal } = controller;\n port.addEventListener(\"message\", (event) => {\n if (event.data?.type === \"change\")\n listener(event.data.value);\n }, { signal });\n // addEventListener (unlike assigning onmessage) does not implicitly start\n // the port, so message delivery has to be kicked off explicitly.\n port.start();\n const detail = {\n selector,\n port: channel.port1,\n };\n dispatchEl.dispatchEvent(new CustomEvent(\"patchwork:subscribe\", {\n detail,\n bubbles: true,\n composed: true,\n }));\n return () => {\n if (signal.aborted)\n return;\n controller.abort();\n port.postMessage({ type: \"unsubscribe\" });\n port.close();\n };\n}\n/**\n * Answer a `patchwork:subscribe`. The `producer` receives a `respond`\n * callback it can call any number of times to push values to the consumer,\n * and may return a teardown that runs when the consumer unsubscribes. Stops\n * propagation so ancestor providers don't double-answer. Values emitted after\n * the consumer unsubscribes are dropped.\n */\nexport function accept(event, producer) {\n event.stopPropagation();\n const port = event.detail.port;\n let alive = true;\n const respond = (value) => {\n if (!alive)\n return;\n port.postMessage({ type: \"change\", value });\n };\n let teardown;\n try {\n teardown = producer(respond);\n }\n catch (err) {\n console.error(\"[patchwork-providers] subscribe producer threw:\", err);\n }\n const stop = () => {\n if (!alive)\n return;\n alive = false;\n try {\n teardown?.();\n }\n catch (err) {\n console.error(\"[patchwork-providers] subscribe teardown threw:\", err);\n }\n port.close();\n };\n port.onmessage = (event) => {\n if (event.data?.type === \"unsubscribe\")\n stop();\n };\n}\n/**\n * One-shot convenience wrapper over {@link subscribe}: opens a subscription\n * for `selector`, resolves with the first value a provider emits, then\n * immediately unsubscribes. Because there is no fallback provider, an\n * unclaimed selector never resolves — use `subscribe` directly if you need to\n * handle the \"no provider\" case.\n */\nexport function request(element, selector) {\n return new Promise((resolve) => {\n const unsubscribe = subscribe(element, selector, (value) => {\n // The first emission is always async (postMessage queues a task), so\n // `unsubscribe` is assigned by the time this fires.\n unsubscribe();\n resolve(value);\n });\n });\n}\nexport { registerRepoProviderElement, } from \"./repo-provider.js\";\n","import { createSignal, onCleanup, onMount } from \"solid-js\";\nimport { createStore, reconcile } from \"solid-js/store\";\nimport { createDocumentProjection } from \"@automerge/automerge-repo-solid-primitives\";\nimport * as Providers from \"@inkandswitch/patchwork-providers\";\nfunction resolveElement(source) {\n return typeof source === \"function\" ? source() : source;\n}\n/**\n * Generic reactive request. Resolves the first value a provider emits for\n * `selector` (via the one-shot `request` helper) and returns an accessor that\n * reads `undefined` until then. `T` is the response type.\n */\nexport function request(element, selector) {\n const [value, setValue] = createSignal(undefined);\n onMount(() => {\n const target = resolveElement(element);\n if (!target)\n return;\n Providers.request(target, selector).then((v) => {\n if (v == null)\n return;\n setValue(() => v);\n });\n });\n return value;\n}\nexport function subscribe(element, selector, initialValue) {\n const [value, setValue] = createSignal(initialValue);\n onMount(() => {\n const target = resolveElement(element);\n if (!target)\n return;\n const unsubscribe = Providers.subscribe(target, selector, setValue);\n onCleanup(unsubscribe);\n });\n return value;\n}\n/**\n * Store-backed subscription. Use this when a consumer wants Solid's\n * fine-grained nested reactivity for incoming JSON object or array snapshots.\n * Requires an initial object/array so the store has a stable root.\n *\n * `reconcile` preserves stable object/array identity where possible, so this\n * helper is not a good fit when the top-level value is used as an identity key\n * for resources or memos.\n */\nexport function subscribeReconciled(element, selector, initialValue) {\n const [store, setStore] = createStore(initialValue);\n onMount(() => {\n const target = resolveElement(element);\n if (!target)\n return;\n const unsubscribe = Providers.subscribe(target, selector, (v) => {\n setStore(reconcile(v));\n });\n onCleanup(unsubscribe);\n });\n return store;\n}\n/**\n * Handle-specialized subscription. Use when the answering provider emits an\n * `AutomergeUrl`. The handle is recovered locally from the global repo\n * (`window.repo`), so it stays fully live — reads project reactively and writes go\n * straight back to the same repo. Returns `[doc, handle]` matching the shape\n * of solid-primitives' `useDocument`; both read `undefined` until the first\n * url arrives. `T` is the doc shape inside the handle.\n */\nexport function subscribeDoc(element, selector) {\n const [handle, setHandle] = createSignal(undefined);\n onMount(() => {\n const target = resolveElement(element);\n if (!target)\n return;\n let canceled = false;\n const unsubscribe = Providers.subscribe(target, selector, (url) => {\n if (!url)\n return;\n const repo = \"repo\" in window ? window.repo : undefined;\n if (!repo)\n return;\n void Promise.resolve(repo.find(url)).then((h) => {\n if (canceled)\n return;\n setHandle(() => h);\n });\n });\n onCleanup(() => {\n canceled = true;\n unsubscribe();\n });\n });\n const doc = createDocumentProjection(handle);\n return [doc, handle];\n}\n","import { onMount, onCleanup, createEffect } from \"solid-js\";\nimport { render } from \"solid-js/web\";\nimport { type AutomergeUrl } from \"@automerge/automerge-repo\";\nimport type { ToolImplementation } from \"@inkandswitch/patchwork-plugins\";\nimport \"./index.css\";\nimport { OpenDocumentEvent } from \"@inkandswitch/patchwork-elements\";\nimport {\n getType,\n type HasPatchworkMetadata,\n} from \"@inkandswitch/patchwork-filesystem\";\nimport {\n getFallbackTool,\n getRegistry,\n type DatatypeDescription,\n type DatatypeImplementation,\n} from \"@inkandswitch/patchwork-plugins\";\nimport { subscribeDoc } from \"@inkandswitch/patchwork-providers-solid\";\nimport {\n ACCOUNT_HISTORY_DATATYPE,\n DEDUPLICATION_TIME_THRESHOLD,\n VIEWER_TOOL_ID,\n} from \"./constants.ts\";\nimport {\n type PatchworkToolProps,\n type HistoryDoc,\n type HistoryEntry,\n} from \"./types.ts\";\n\n// Key under which this tool's private storage doc is registered — see the\n// `patchwork:tool-storage` provider (patchwork-base/providers). Mounted as a\n// titlebar-tool, this component is bound to whatever *document* is focused,\n// not the account, so it can't reach its own storage via `props.handle`; the\n// provider gives it a private, account-scoped doc instead.\nconst TOOL_STORAGE_ID = \"account-history\";\n\nexport function HistoryRecorder(props: PatchworkToolProps<any>) {\n // Resolves (and, on first use, creates) this tool's private storage doc.\n // The provider only guarantees an empty doc exists — the shape below\n // (`entries`, `title`, `@patchwork`) is seeded by us the first time it's\n // used, the same as the old `getOrCreateAccountHistoryDoc` did.\n const [historyDoc, historyHandle] = subscribeDoc<HistoryDoc>(\n props.element,\n { type: \"patchwork:tool-storage\", toolId: TOOL_STORAGE_ID }\n );\n\n const historyUrl = () => historyHandle()?.url;\n\n createEffect(() => {\n const handle = historyHandle();\n const doc = historyDoc();\n if (!handle || !doc || Array.isArray(doc.entries)) return;\n handle.change((d) => {\n if (Array.isArray(d.entries)) return; // lost an initialization race\n d[\"@patchwork\"] = { type: ACCOUNT_HISTORY_DATATYPE };\n d.title = \"Account History\";\n d.entries = [];\n });\n });\n\n onMount(() => {\n const onDocumentOpened = async (event: Event) => {\n const openEvent = event as OpenDocumentEvent;\n const { url: rawUrl, toolId: rawToolId } = openEvent.detail;\n const [docUrl, ...heads] = rawUrl.split(\"#\");\n const url = docUrl as AutomergeUrl;\n let toolId = rawToolId;\n\n // Get document to extract title and type\n const docHandle = await props.repo.find(url);\n await docHandle.whenReady();\n const doc = docHandle.doc();\n\n if (!doc) {\n console.warn(\"HistoryRecorder: failed to load document\");\n return;\n }\n\n const datatype = getType(doc as Partial<HasPatchworkMetadata>);\n\n if (!toolId) {\n // if the tool id isn't defined, use the current fallback tool\n if (!datatype) {\n console.warn(\n \"HistoryRecorder: no tool found for document type; skipping history recording\"\n );\n return;\n }\n const fallbackTool = getFallbackTool(doc as HasPatchworkMetadata);\n if (!fallbackTool) {\n console.warn(\n \"HistoryRecorder: no tool found for document type; skipping history recording\"\n );\n return;\n }\n toolId = fallbackTool.id;\n }\n\n if (datatype === ACCOUNT_HISTORY_DATATYPE) {\n return; // Don't record when opening account history documents\n }\n\n const currentHistoryHandle = historyHandle();\n if (!currentHistoryHandle) {\n console.warn(\"HistoryRecorder: history handle not ready yet\");\n return;\n }\n const historyDocSnapshot = currentHistoryHandle.doc();\n if (!historyDocSnapshot) {\n console.warn(\"HistoryRecorder: history document not ready yet\");\n return;\n }\n\n // Deduplication: check if this entry is a duplicate from rapid clicks\n const lastEntry =\n historyDocSnapshot.entries[historyDocSnapshot.entries.length - 1];\n if (\n lastEntry &&\n lastEntry.docUrl === url &&\n lastEntry.toolId === toolId &&\n Date.now() - lastEntry.timestamp < DEDUPLICATION_TIME_THRESHOLD\n ) {\n return; // Skip recording - duplicate from rapid clicking\n }\n\n // Extract title\n let docTitle = \"Untitled\";\n if (datatype) {\n const registry = getRegistry<DatatypeDescription>(\"patchwork:datatype\");\n const loaded = registry.get(datatype);\n if (loaded && \"module\" in loaded) {\n const impl = loaded.module as DatatypeImplementation;\n try {\n docTitle = impl.getTitle(doc) || \"Untitled\";\n } catch {\n // getTitle may fail if the document shape doesn't match the datatype\n }\n }\n }\n\n // Get current heads, unless the URL specified a head\n let entryHeads = heads;\n if (entryHeads.length === 0) entryHeads = docHandle.heads();\n\n // Create new entry\n const newEntry: HistoryEntry = {\n timestamp: Date.now(),\n docUrl: url,\n docTitle,\n docType: datatype || \"unknown\",\n toolId,\n heads: entryHeads,\n };\n\n // Add entry to history\n currentHistoryHandle.change((doc) => {\n doc.entries.push(newEntry);\n });\n };\n\n // Listen to patchwork:open-document events (should fire once per user action)\n document.addEventListener(\n \"patchwork:open-document\",\n onDocumentOpened as EventListener,\n { capture: true }\n );\n\n onCleanup(() => {\n document.removeEventListener(\n \"patchwork:open-document\",\n onDocumentOpened as EventListener,\n { capture: true }\n );\n });\n });\n\n const openHistory = () => {\n const currentHistoryUrl = historyUrl();\n if (!currentHistoryUrl) {\n console.warn(\"HistoryRecorder: no history URL yet\");\n return;\n }\n\n // Dispatch open document event\n props.element.dispatchEvent(\n new OpenDocumentEvent({\n url: currentHistoryUrl,\n toolId: VIEWER_TOOL_ID,\n })\n );\n };\n\n return (\n <button\n onClick={openHistory}\n class=\"account-history-toolbar-button\"\n title=\"Open account history\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <polyline points=\"12 6 12 12 16 14\" />\n </svg>\n </button>\n );\n}\n\nexport const renderHistoryRecorder: ToolImplementation<any> = (\n handle,\n element\n) => render(() => <HistoryRecorder handle={handle} repo={element.repo} element={element} />, element);\n"],"names":["template","Providers.subscribe","TOOL_STORAGE_ID","HistoryRecorder","props","historyDoc","historyHandle","subscribeDoc","element","type","toolId","historyUrl","url","createEffect","handle","doc","Array","isArray","entries","change","d","ACCOUNT_HISTORY_DATATYPE","title","onMount","onDocumentOpened","event","openEvent","rawUrl","rawToolId","detail","docUrl","heads","split","docHandle","repo","find","whenReady","console","warn","datatype","getType","fallbackTool","getFallbackTool","id","currentHistoryHandle","historyDocSnapshot","lastEntry","length","Date","now","timestamp","DEDUPLICATION_TIME_THRESHOLD","docTitle","registry","getRegistry","loaded","get","impl","module","getTitle","entryHeads","newEntry","docType","push","document","addEventListener","capture","onCleanup","removeEventListener","openHistory","currentHistoryUrl","dispatchEvent","OpenDocumentEvent","VIEWER_TOOL_ID","_el$","_tmpl$","$$click","renderHistoryRecorder","render","_$createComponent","_$delegateEvents"],"mappings":"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","x_google_ignoreList":[0,1,2]}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { e as render, f as createComponent, g as delegateEvents, b as createSignal, h as createEffect, c as createMemo, i as insert, j as createRenderEffect, k as className, S as Show, F as For, s as setAttribute, t as template } from "./store-CRmRL4Cv.js";
|
|
2
|
+
import { m as makeDocumentProjection } from "./makeDocumentProjection-DXgEZYyv.js";
|
|
3
|
+
import { OpenDocumentEvent } from "@inkandswitch/patchwork-elements";
|
|
4
|
+
import { getRegistry } from "@inkandswitch/patchwork-plugins";
|
|
5
|
+
var _tmpl$ = /* @__PURE__ */ template(`<div class=account-history-entry><div class=account-history-entry-time></div><div class=account-history-entry-content><span class=account-history-entry-title></span><span class=account-history-entry-tool>— </span><span class=account-history-entry-url></span></div><div class=account-history-entry-actions><button class=account-history-entry-delete aria-label="Delete from history"title="Delete from history"><svg xmlns=http://www.w3.org/2000/svg viewBox="0 0 24 24"fill=none stroke=currentColor stroke-width=2 stroke-linecap=round stroke-linejoin=round><line x1=18 y1=6 x2=6 y2=18></line><line x1=6 y1=6 x2=18 y2=18>`), _tmpl$2 = /* @__PURE__ */ template(`<span class=account-history-sort-label>Sort by:`), _tmpl$3 = /* @__PURE__ */ template(`<button>Most Recent`), _tmpl$4 = /* @__PURE__ */ template(`<button>Most Visits`), _tmpl$5 = /* @__PURE__ */ template(`<div class=account-history-empty>No history yet. Open some documents to start tracking your history.`), _tmpl$6 = /* @__PURE__ */ template(`<div class=account-history-viewer><div class=account-history-header><h1 class=account-history-title>Account History</h1><div class=account-history-header-controls><span class=account-history-group-label>Group by:</span><button>Date</button><button>Document</button></div><div class=account-history-search><svg class=account-history-search-icon xmlns=http://www.w3.org/2000/svg viewBox="0 0 24 24"fill=none stroke=currentColor stroke-width=2 stroke-linecap=round stroke-linejoin=round><circle cx=11 cy=11 r=8></circle><path d="m21 21-4.35-4.35"></path></svg><input type=text class=account-history-search-input placeholder="search history"></div></div><div class=account-history-content>`), _tmpl$7 = /* @__PURE__ */ template(`<div class=account-history-group><div class=account-history-group-header><h2 class=account-history-group-title></h2></div><div class=account-history-entries>`), _tmpl$8 = /* @__PURE__ */ template(`<div class=account-history-entries>`), _tmpl$9 = /* @__PURE__ */ template(`<div class=account-history-group><div class="account-history-group-header account-history-group-header-clickable"><div class=account-history-group-header-content><h2 class=account-history-group-title></h2><p class=account-history-group-meta> • <!> </p></div><button class=account-history-group-toggle>`);
|
|
6
|
+
const dateGroupFormatter = new Intl.DateTimeFormat("en-US", {
|
|
7
|
+
year: "numeric",
|
|
8
|
+
month: "long",
|
|
9
|
+
day: "numeric"
|
|
10
|
+
});
|
|
11
|
+
const timeFormatter = new Intl.DateTimeFormat("en-US", {
|
|
12
|
+
hour: "numeric",
|
|
13
|
+
minute: "2-digit",
|
|
14
|
+
hour12: true
|
|
15
|
+
});
|
|
16
|
+
const shortDateFormatter = new Intl.DateTimeFormat("en-US", {
|
|
17
|
+
month: "short",
|
|
18
|
+
day: "numeric",
|
|
19
|
+
hour: "numeric",
|
|
20
|
+
minute: "2-digit"
|
|
21
|
+
});
|
|
22
|
+
function getToolMetadata(toolId) {
|
|
23
|
+
const toolRegistry = getRegistry("patchwork:tool");
|
|
24
|
+
const tool = toolRegistry.get(toolId);
|
|
25
|
+
if (tool) {
|
|
26
|
+
return {
|
|
27
|
+
name: tool.name || toolId,
|
|
28
|
+
icon: tool.icon
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
name: toolId,
|
|
33
|
+
icon: "File"
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function HistoryEntryRow(props) {
|
|
37
|
+
const toolMetadata = getToolMetadata(props.entry.toolId);
|
|
38
|
+
return (() => {
|
|
39
|
+
var _el$ = _tmpl$(), _el$2 = _el$.firstChild, _el$3 = _el$2.nextSibling, _el$4 = _el$3.firstChild, _el$5 = _el$4.nextSibling;
|
|
40
|
+
_el$5.firstChild;
|
|
41
|
+
var _el$7 = _el$5.nextSibling, _el$8 = _el$3.nextSibling, _el$9 = _el$8.firstChild;
|
|
42
|
+
insert(_el$2, () => props.timeDisplay);
|
|
43
|
+
_el$3.$$click = () => props.onOpen(props.entry);
|
|
44
|
+
insert(_el$4, () => props.entry.docTitle);
|
|
45
|
+
insert(_el$5, () => toolMetadata.name, null);
|
|
46
|
+
insert(_el$7, () => props.urlDisplay);
|
|
47
|
+
_el$9.$$click = (e) => props.onDelete(e, props.entry);
|
|
48
|
+
return _el$;
|
|
49
|
+
})();
|
|
50
|
+
}
|
|
51
|
+
function HistoryViewer(props) {
|
|
52
|
+
const historyDoc = makeDocumentProjection(props.handle);
|
|
53
|
+
const savedGroupingMode = localStorage.getItem("history-grouping-mode") || "date";
|
|
54
|
+
const savedSortMode = localStorage.getItem("history-document-sort-mode") || "recent";
|
|
55
|
+
const savedSearchQuery = localStorage.getItem("history-search-query") || "";
|
|
56
|
+
const [groupingMode, setGroupingMode] = createSignal(savedGroupingMode);
|
|
57
|
+
const [documentSortMode, setDocumentSortMode] = createSignal(savedSortMode);
|
|
58
|
+
const [searchQuery, setSearchQuery] = createSignal(savedSearchQuery);
|
|
59
|
+
const [expandedGroups, setExpandedGroups] = createSignal(/* @__PURE__ */ new Set());
|
|
60
|
+
createEffect(() => {
|
|
61
|
+
localStorage.setItem("history-grouping-mode", groupingMode());
|
|
62
|
+
});
|
|
63
|
+
createEffect(() => {
|
|
64
|
+
localStorage.setItem("history-document-sort-mode", documentSortMode());
|
|
65
|
+
});
|
|
66
|
+
createEffect(() => {
|
|
67
|
+
localStorage.setItem("history-search-query", searchQuery());
|
|
68
|
+
});
|
|
69
|
+
const filteredEntries = createMemo(() => {
|
|
70
|
+
const query = searchQuery().toLowerCase().trim();
|
|
71
|
+
if (!query) {
|
|
72
|
+
return historyDoc.entries || [];
|
|
73
|
+
}
|
|
74
|
+
return (historyDoc.entries || []).filter((entry) => {
|
|
75
|
+
const titleMatch = entry.docTitle?.toLowerCase().includes(query);
|
|
76
|
+
const urlMatch = entry.docUrl?.toLowerCase().includes(query);
|
|
77
|
+
return titleMatch || urlMatch;
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
const groupedByDate = createMemo(() => {
|
|
81
|
+
const groups = /* @__PURE__ */ new Map();
|
|
82
|
+
const entries = [...filteredEntries()].reverse();
|
|
83
|
+
for (const entry of entries) {
|
|
84
|
+
const date = dateGroupFormatter.format(new Date(entry.timestamp));
|
|
85
|
+
if (!groups.has(date)) {
|
|
86
|
+
groups.set(date, []);
|
|
87
|
+
}
|
|
88
|
+
groups.get(date).push(entry);
|
|
89
|
+
}
|
|
90
|
+
return Array.from(groups);
|
|
91
|
+
});
|
|
92
|
+
const groupedByDocument = createMemo(() => {
|
|
93
|
+
const groups = /* @__PURE__ */ new Map();
|
|
94
|
+
const entries = [...filteredEntries()].reverse();
|
|
95
|
+
for (const entry of entries) {
|
|
96
|
+
if (!groups.has(entry.docUrl)) {
|
|
97
|
+
groups.set(entry.docUrl, []);
|
|
98
|
+
}
|
|
99
|
+
groups.get(entry.docUrl).push(entry);
|
|
100
|
+
}
|
|
101
|
+
return groups;
|
|
102
|
+
});
|
|
103
|
+
const sortedDocumentGroups = createMemo(() => {
|
|
104
|
+
const groups = Array.from(groupedByDocument());
|
|
105
|
+
const sortMode = documentSortMode();
|
|
106
|
+
if (sortMode === "visits") {
|
|
107
|
+
return groups.sort((a, b) => b[1].length - a[1].length);
|
|
108
|
+
} else {
|
|
109
|
+
const maxTimestamps = /* @__PURE__ */ new Map();
|
|
110
|
+
for (const [url, entries] of groups) {
|
|
111
|
+
let max = 0;
|
|
112
|
+
for (const e of entries) {
|
|
113
|
+
if (e.timestamp > max) max = e.timestamp;
|
|
114
|
+
}
|
|
115
|
+
maxTimestamps.set(url, max);
|
|
116
|
+
}
|
|
117
|
+
return groups.sort((a, b) => maxTimestamps.get(b[0]) - maxTimestamps.get(a[0]));
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
const openDocument = (entry) => {
|
|
121
|
+
const url = entry.docUrl;
|
|
122
|
+
props.element.dispatchEvent(new OpenDocumentEvent({
|
|
123
|
+
url,
|
|
124
|
+
toolId: entry.toolId
|
|
125
|
+
}));
|
|
126
|
+
};
|
|
127
|
+
const formatTime = (timestamp) => {
|
|
128
|
+
return timeFormatter.format(new Date(timestamp));
|
|
129
|
+
};
|
|
130
|
+
const formatShortDate = (timestamp) => {
|
|
131
|
+
return shortDateFormatter.format(new Date(timestamp));
|
|
132
|
+
};
|
|
133
|
+
const deleteEntry = (e, entryToDelete) => {
|
|
134
|
+
e.stopPropagation();
|
|
135
|
+
props.handle.change((doc) => {
|
|
136
|
+
const index = doc.entries.findIndex((e2) => e2.timestamp === entryToDelete.timestamp && e2.docUrl === entryToDelete.docUrl);
|
|
137
|
+
if (index !== -1) {
|
|
138
|
+
doc.entries.splice(index, 1);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
const toggleGroup = (docUrl) => {
|
|
143
|
+
setExpandedGroups((prev) => {
|
|
144
|
+
const newSet = new Set(prev);
|
|
145
|
+
if (newSet.has(docUrl)) {
|
|
146
|
+
newSet.delete(docUrl);
|
|
147
|
+
} else {
|
|
148
|
+
newSet.add(docUrl);
|
|
149
|
+
}
|
|
150
|
+
return newSet;
|
|
151
|
+
});
|
|
152
|
+
};
|
|
153
|
+
const openDocumentGroup = (docUrl) => {
|
|
154
|
+
props.element.dispatchEvent(new OpenDocumentEvent({
|
|
155
|
+
url: docUrl
|
|
156
|
+
}));
|
|
157
|
+
};
|
|
158
|
+
return (() => {
|
|
159
|
+
var _el$0 = _tmpl$6(), _el$1 = _el$0.firstChild, _el$10 = _el$1.firstChild, _el$11 = _el$10.nextSibling, _el$12 = _el$11.firstChild, _el$13 = _el$12.nextSibling, _el$14 = _el$13.nextSibling, _el$18 = _el$11.nextSibling, _el$19 = _el$18.firstChild, _el$20 = _el$19.nextSibling, _el$21 = _el$1.nextSibling;
|
|
160
|
+
_el$13.$$click = () => setGroupingMode("date");
|
|
161
|
+
_el$14.$$click = () => setGroupingMode("document");
|
|
162
|
+
insert(_el$11, createComponent(Show, {
|
|
163
|
+
get when() {
|
|
164
|
+
return groupingMode() === "document";
|
|
165
|
+
},
|
|
166
|
+
get children() {
|
|
167
|
+
return [_tmpl$2(), (() => {
|
|
168
|
+
var _el$16 = _tmpl$3();
|
|
169
|
+
_el$16.$$click = () => setDocumentSortMode("recent");
|
|
170
|
+
createRenderEffect(() => className(_el$16, `account-history-sort-button ${documentSortMode() === "recent" ? "active" : ""}`));
|
|
171
|
+
return _el$16;
|
|
172
|
+
})(), (() => {
|
|
173
|
+
var _el$17 = _tmpl$4();
|
|
174
|
+
_el$17.$$click = () => setDocumentSortMode("visits");
|
|
175
|
+
createRenderEffect(() => className(_el$17, `account-history-sort-button ${documentSortMode() === "visits" ? "active" : ""}`));
|
|
176
|
+
return _el$17;
|
|
177
|
+
})()];
|
|
178
|
+
}
|
|
179
|
+
}), null);
|
|
180
|
+
_el$20.$$input = (e) => setSearchQuery(e.currentTarget.value);
|
|
181
|
+
insert(_el$21, createComponent(Show, {
|
|
182
|
+
get when() {
|
|
183
|
+
return groupingMode() === "date";
|
|
184
|
+
},
|
|
185
|
+
get children() {
|
|
186
|
+
return createComponent(For, {
|
|
187
|
+
get each() {
|
|
188
|
+
return groupedByDate();
|
|
189
|
+
},
|
|
190
|
+
children: ([date, entries]) => (() => {
|
|
191
|
+
var _el$23 = _tmpl$7(), _el$24 = _el$23.firstChild, _el$25 = _el$24.firstChild, _el$26 = _el$24.nextSibling;
|
|
192
|
+
insert(_el$25, date);
|
|
193
|
+
insert(_el$26, createComponent(For, {
|
|
194
|
+
each: entries,
|
|
195
|
+
children: (entry) => createComponent(HistoryEntryRow, {
|
|
196
|
+
entry,
|
|
197
|
+
get timeDisplay() {
|
|
198
|
+
return formatTime(entry.timestamp);
|
|
199
|
+
},
|
|
200
|
+
get urlDisplay() {
|
|
201
|
+
return entry.docUrl;
|
|
202
|
+
},
|
|
203
|
+
onOpen: openDocument,
|
|
204
|
+
onDelete: deleteEntry
|
|
205
|
+
})
|
|
206
|
+
}));
|
|
207
|
+
return _el$23;
|
|
208
|
+
})()
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}), null);
|
|
212
|
+
insert(_el$21, createComponent(Show, {
|
|
213
|
+
get when() {
|
|
214
|
+
return groupingMode() === "document";
|
|
215
|
+
},
|
|
216
|
+
get children() {
|
|
217
|
+
return createComponent(For, {
|
|
218
|
+
get each() {
|
|
219
|
+
return sortedDocumentGroups();
|
|
220
|
+
},
|
|
221
|
+
children: ([docUrl, entries]) => {
|
|
222
|
+
const isExpanded = () => expandedGroups().has(docUrl);
|
|
223
|
+
return (() => {
|
|
224
|
+
var _el$27 = _tmpl$9(), _el$28 = _el$27.firstChild, _el$29 = _el$28.firstChild, _el$30 = _el$29.firstChild, _el$31 = _el$30.nextSibling, _el$32 = _el$31.firstChild, _el$34 = _el$32.nextSibling;
|
|
225
|
+
_el$34.nextSibling;
|
|
226
|
+
var _el$35 = _el$29.nextSibling;
|
|
227
|
+
_el$28.$$click = () => openDocumentGroup(docUrl);
|
|
228
|
+
insert(_el$30, () => entries[0]?.docTitle ?? "Untitled");
|
|
229
|
+
insert(_el$31, () => entries[0]?.docType ?? "unknown", _el$32);
|
|
230
|
+
insert(_el$31, () => entries.length, _el$34);
|
|
231
|
+
insert(_el$31, () => entries.length === 1 ? "visit" : "visits", null);
|
|
232
|
+
_el$35.$$click = (e) => {
|
|
233
|
+
e.stopPropagation();
|
|
234
|
+
toggleGroup(docUrl);
|
|
235
|
+
};
|
|
236
|
+
insert(_el$35, () => isExpanded() ? "−" : "+");
|
|
237
|
+
insert(_el$27, createComponent(Show, {
|
|
238
|
+
get when() {
|
|
239
|
+
return isExpanded();
|
|
240
|
+
},
|
|
241
|
+
get children() {
|
|
242
|
+
var _el$36 = _tmpl$8();
|
|
243
|
+
insert(_el$36, createComponent(For, {
|
|
244
|
+
each: entries,
|
|
245
|
+
children: (entry) => createComponent(HistoryEntryRow, {
|
|
246
|
+
entry,
|
|
247
|
+
get timeDisplay() {
|
|
248
|
+
return formatShortDate(entry.timestamp);
|
|
249
|
+
},
|
|
250
|
+
get urlDisplay() {
|
|
251
|
+
return `(#${entry.heads})`;
|
|
252
|
+
},
|
|
253
|
+
onOpen: openDocument,
|
|
254
|
+
onDelete: deleteEntry
|
|
255
|
+
})
|
|
256
|
+
}));
|
|
257
|
+
return _el$36;
|
|
258
|
+
}
|
|
259
|
+
}), null);
|
|
260
|
+
createRenderEffect((_p$) => {
|
|
261
|
+
var _v$3 = isExpanded() ? "Collapse" : "Expand", _v$4 = isExpanded() ? "Collapse" : "Expand";
|
|
262
|
+
_v$3 !== _p$.e && setAttribute(_el$35, "aria-label", _p$.e = _v$3);
|
|
263
|
+
_v$4 !== _p$.t && setAttribute(_el$35, "title", _p$.t = _v$4);
|
|
264
|
+
return _p$;
|
|
265
|
+
}, {
|
|
266
|
+
e: void 0,
|
|
267
|
+
t: void 0
|
|
268
|
+
});
|
|
269
|
+
return _el$27;
|
|
270
|
+
})();
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}), null);
|
|
275
|
+
insert(_el$21, createComponent(Show, {
|
|
276
|
+
get when() {
|
|
277
|
+
return !historyDoc.entries || historyDoc.entries.length === 0;
|
|
278
|
+
},
|
|
279
|
+
get children() {
|
|
280
|
+
return _tmpl$5();
|
|
281
|
+
}
|
|
282
|
+
}), null);
|
|
283
|
+
createRenderEffect((_p$) => {
|
|
284
|
+
var _v$ = `account-history-toggle-button ${groupingMode() === "date" ? "active" : ""}`, _v$2 = `account-history-toggle-button ${groupingMode() === "document" ? "active" : ""}`;
|
|
285
|
+
_v$ !== _p$.e && className(_el$13, _p$.e = _v$);
|
|
286
|
+
_v$2 !== _p$.t && className(_el$14, _p$.t = _v$2);
|
|
287
|
+
return _p$;
|
|
288
|
+
}, {
|
|
289
|
+
e: void 0,
|
|
290
|
+
t: void 0
|
|
291
|
+
});
|
|
292
|
+
createRenderEffect(() => _el$20.value = searchQuery());
|
|
293
|
+
return _el$0;
|
|
294
|
+
})();
|
|
295
|
+
}
|
|
296
|
+
const renderHistoryViewer = (handle, element) => render(() => createComponent(HistoryViewer, {
|
|
297
|
+
handle,
|
|
298
|
+
get repo() {
|
|
299
|
+
return element.repo;
|
|
300
|
+
},
|
|
301
|
+
element
|
|
302
|
+
}), element);
|
|
303
|
+
delegateEvents(["click", "input"]);
|
|
304
|
+
export {
|
|
305
|
+
HistoryViewer,
|
|
306
|
+
renderHistoryViewer
|
|
307
|
+
};
|
|
308
|
+
//# sourceMappingURL=HistoryViewer-Ct5g7zek.js.map
|