@shivaedev/work-board 0.3.0 → 0.4.1
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/CHANGELOG.md +36 -0
- package/README.md +70 -11
- package/dist/attention/overviewHtml.d.ts +3 -0
- package/dist/attention/overviewHtml.d.ts.map +1 -0
- package/dist/attention/overviewHtml.js +41 -0
- package/dist/attention/overviewHtml.js.map +1 -0
- package/dist/attention/requests.d.ts +24 -0
- package/dist/attention/requests.d.ts.map +1 -0
- package/dist/attention/requests.js +40 -0
- package/dist/attention/requests.js.map +1 -0
- package/dist/attention/style.d.ts +2 -0
- package/dist/attention/style.d.ts.map +1 -0
- package/dist/attention/style.js +10 -0
- package/dist/attention/style.js.map +1 -0
- package/dist/board.d.ts.map +1 -1
- package/dist/board.js +6 -1
- package/dist/board.js.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/files/changes.d.ts.map +1 -1
- package/dist/files/changes.js +4 -9
- package/dist/files/changes.js.map +1 -1
- package/dist/files/home.d.ts.map +1 -1
- package/dist/files/home.js +4 -0
- package/dist/files/home.js.map +1 -1
- package/dist/files/list.d.ts +7 -0
- package/dist/files/list.d.ts.map +1 -1
- package/dist/files/list.js +23 -13
- package/dist/files/list.js.map +1 -1
- package/dist/files/markdownPath.d.ts +3 -0
- package/dist/files/markdownPath.d.ts.map +1 -0
- package/dist/files/markdownPath.js +31 -0
- package/dist/files/markdownPath.js.map +1 -0
- package/dist/files/watchDirectories.d.ts +9 -0
- package/dist/files/watchDirectories.d.ts.map +1 -0
- package/dist/files/watchDirectories.js +33 -0
- package/dist/files/watchDirectories.js.map +1 -0
- package/dist/history/baseline.d.ts +19 -0
- package/dist/history/baseline.d.ts.map +1 -0
- package/dist/history/baseline.js +40 -0
- package/dist/history/baseline.js.map +1 -0
- package/dist/history/compare.d.ts +14 -0
- package/dist/history/compare.d.ts.map +1 -0
- package/dist/history/compare.js +47 -0
- package/dist/history/compare.js.map +1 -0
- package/dist/history/documents.d.ts +10 -0
- package/dist/history/documents.d.ts.map +1 -0
- package/dist/history/documents.js +15 -0
- package/dist/history/documents.js.map +1 -0
- package/dist/history/fields.d.ts +3 -0
- package/dist/history/fields.d.ts.map +1 -0
- package/dist/history/fields.js +27 -0
- package/dist/history/fields.js.map +1 -0
- package/dist/history/html.d.ts +4 -0
- package/dist/history/html.d.ts.map +1 -0
- package/dist/history/html.js +22 -0
- package/dist/history/html.js.map +1 -0
- package/dist/history/limits.d.ts +3 -0
- package/dist/history/limits.d.ts.map +1 -0
- package/dist/history/limits.js +3 -0
- package/dist/history/limits.js.map +1 -0
- package/dist/history/response.d.ts +4 -0
- package/dist/history/response.d.ts.map +1 -0
- package/dist/history/response.js +20 -0
- package/dist/history/response.js.map +1 -0
- package/dist/history/schema.d.ts +24 -0
- package/dist/history/schema.d.ts.map +1 -0
- package/dist/history/schema.js +19 -0
- package/dist/history/schema.js.map +1 -0
- package/dist/history/script.d.ts +2 -0
- package/dist/history/script.d.ts.map +1 -0
- package/dist/history/script.js +125 -0
- package/dist/history/script.js.map +1 -0
- package/dist/http/assets.d.ts.map +1 -1
- package/dist/http/assets.js +2 -0
- package/dist/http/assets.js.map +1 -1
- package/dist/http/history.d.ts +7 -0
- package/dist/http/history.d.ts.map +1 -0
- package/dist/http/history.js +45 -0
- package/dist/http/history.js.map +1 -0
- package/dist/http/overview.d.ts +5 -0
- package/dist/http/overview.d.ts.map +1 -0
- package/dist/http/overview.js +13 -0
- package/dist/http/overview.js.map +1 -0
- package/dist/http/page.d.ts +2 -2
- package/dist/http/page.d.ts.map +1 -1
- package/dist/http/page.js +5 -6
- package/dist/http/page.js.map +1 -1
- package/dist/metadata/attentionProblems.d.ts +4 -0
- package/dist/metadata/attentionProblems.d.ts.map +1 -0
- package/dist/metadata/attentionProblems.js +16 -0
- package/dist/metadata/attentionProblems.js.map +1 -0
- package/dist/metadata/backlinks.d.ts.map +1 -1
- package/dist/metadata/backlinks.js +5 -0
- package/dist/metadata/backlinks.js.map +1 -1
- package/dist/metadata/fields.d.ts +8 -0
- package/dist/metadata/fields.d.ts.map +1 -1
- package/dist/metadata/problems.d.ts.map +1 -1
- package/dist/metadata/problems.js +3 -1
- package/dist/metadata/problems.js.map +1 -1
- package/dist/metadata/relationshipLabel.d.ts.map +1 -1
- package/dist/metadata/relationshipLabel.js +1 -0
- package/dist/metadata/relationshipLabel.js.map +1 -1
- package/dist/metadata/schema.d.ts +10 -0
- package/dist/metadata/schema.d.ts.map +1 -1
- package/dist/metadata/schema.js +9 -0
- package/dist/metadata/schema.js.map +1 -1
- package/dist/page/attentionDetails.d.ts +4 -0
- package/dist/page/attentionDetails.d.ts.map +1 -0
- package/dist/page/attentionDetails.js +13 -0
- package/dist/page/attentionDetails.js.map +1 -0
- package/dist/page/client.d.ts +1 -1
- package/dist/page/client.d.ts.map +1 -1
- package/dist/page/client.js +1 -0
- package/dist/page/client.js.map +1 -1
- package/dist/page/metadataDetails.d.ts.map +1 -1
- package/dist/page/metadataDetails.js +2 -0
- package/dist/page/metadataDetails.js.map +1 -1
- package/dist/page/navigationScript.js +1 -1
- package/dist/page/shell.d.ts +1 -1
- package/dist/page/shell.d.ts.map +1 -1
- package/dist/page/shell.js +2 -2
- package/dist/page/shell.js.map +1 -1
- package/dist/page/stateScript.d.ts.map +1 -1
- package/dist/page/stateScript.js +6 -0
- package/dist/page/stateScript.js.map +1 -1
- package/dist/page/style.d.ts.map +1 -1
- package/dist/page/style.js +2 -1
- package/dist/page/style.js.map +1 -1
- package/dist/search/attentionEntries.d.ts +4 -0
- package/dist/search/attentionEntries.d.ts.map +1 -0
- package/dist/search/attentionEntries.js +20 -0
- package/dist/search/attentionEntries.js.map +1 -0
- package/dist/search/metadataEntries.d.ts.map +1 -1
- package/dist/search/metadataEntries.js +2 -0
- package/dist/search/metadataEntries.js.map +1 -1
- package/dist/search/snapshot.d.ts +2 -2
- package/dist/search/snapshot.d.ts.map +1 -1
- package/dist/search/snapshot.js +4 -5
- package/dist/search/snapshot.js.map +1 -1
- package/package.json +5 -4
- package/src/attention/overviewHtml.ts +44 -0
- package/src/attention/requests.ts +64 -0
- package/src/attention/style.ts +9 -0
- package/src/board.ts +7 -2
- package/src/cli.ts +1 -1
- package/src/files/changes.ts +4 -16
- package/src/files/home.ts +4 -0
- package/src/files/list.ts +25 -14
- package/src/files/markdownPath.ts +31 -0
- package/src/files/watchDirectories.ts +60 -0
- package/src/history/baseline.ts +43 -0
- package/src/history/compare.ts +54 -0
- package/src/history/documents.ts +21 -0
- package/src/history/fields.ts +32 -0
- package/src/history/html.ts +23 -0
- package/src/history/limits.ts +2 -0
- package/src/history/response.ts +23 -0
- package/src/history/schema.ts +36 -0
- package/src/history/script.ts +124 -0
- package/src/http/assets.ts +2 -0
- package/src/http/history.ts +50 -0
- package/src/http/overview.ts +19 -0
- package/src/http/page.ts +6 -6
- package/src/metadata/attentionProblems.ts +18 -0
- package/src/metadata/backlinks.ts +7 -0
- package/src/metadata/problems.ts +5 -1
- package/src/metadata/relationshipLabel.ts +1 -0
- package/src/metadata/schema.ts +9 -0
- package/src/page/attentionDetails.ts +18 -0
- package/src/page/client.ts +1 -0
- package/src/page/metadataDetails.ts +2 -0
- package/src/page/navigationScript.ts +1 -1
- package/src/page/shell.ts +11 -3
- package/src/page/stateScript.ts +6 -0
- package/src/page/style.ts +2 -1
- package/src/search/attentionEntries.ts +22 -0
- package/src/search/metadataEntries.ts +2 -0
- package/src/search/snapshot.ts +4 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stateScript.d.ts","sourceRoot":"","sources":["../../src/page/stateScript.ts"],"names":[],"mappings":"AAAA,wBAAgB,eAAe,IAAI,MAAM,
|
|
1
|
+
{"version":3,"file":"stateScript.d.ts","sourceRoot":"","sources":["../../src/page/stateScript.ts"],"names":[],"mappings":"AAAA,wBAAgB,eAAe,IAAI,MAAM,CAqDxC"}
|
package/dist/page/stateScript.js
CHANGED
|
@@ -18,6 +18,10 @@ export const applyPage = (page, preserve = false) => {
|
|
|
18
18
|
const reading = preserve ? captureReading() : null;
|
|
19
19
|
const cachedScheme = incoming.dataset.scheme;
|
|
20
20
|
if (preserve && (current.dataset.file === incoming.dataset.file || (current.dataset.identity && current.dataset.identity === incoming.dataset.identity))) {
|
|
21
|
+
if (current.dataset.view === "changes" && incoming.dataset.view === "changes") {
|
|
22
|
+
const content = current.querySelector("#changes-content");
|
|
23
|
+
if (content) incoming.querySelector("#changes-content")?.replaceWith(content.cloneNode(true));
|
|
24
|
+
}
|
|
21
25
|
swap(current, incoming);
|
|
22
26
|
} else {
|
|
23
27
|
current.replaceWith(document.importNode(incoming, true));
|
|
@@ -30,6 +34,8 @@ export const applyPage = (page, preserve = false) => {
|
|
|
30
34
|
doc.dataset.url = incoming.dataset.url;
|
|
31
35
|
if (incoming.dataset.view) doc.dataset.view = incoming.dataset.view; else delete doc.dataset.view;
|
|
32
36
|
document.getElementById("work-open")?.setAttribute("aria-current", doc.dataset.view === "work" ? "page" : "false");
|
|
37
|
+
document.getElementById("overview-open")?.setAttribute("aria-current", doc.dataset.view === "overview" ? "page" : "false");
|
|
38
|
+
document.getElementById("changes-open")?.setAttribute("aria-current", doc.dataset.view === "changes" ? "page" : "false");
|
|
33
39
|
doc.dataset.scheme = document.documentElement.dataset.scheme;
|
|
34
40
|
swap(document.getElementById("files"), page.getElementById("files"));
|
|
35
41
|
const crumbs = page.getElementById("breadcrumbs");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stateScript.js","sourceRoot":"","sources":["../../src/page/stateScript.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,eAAe;IAC9B,OAAO
|
|
1
|
+
{"version":3,"file":"stateScript.js","sourceRoot":"","sources":["../../src/page/stateScript.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,eAAe;IAC9B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmDP,CAAC;AACF,CAAC"}
|
package/dist/page/style.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"style.d.ts","sourceRoot":"","sources":["../../src/page/style.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"style.d.ts","sourceRoot":"","sources":["../../src/page/style.ts"],"names":[],"mappings":"AA0DA,eAAO,MAAM,KAAK,QAA+H,CAAC"}
|
package/dist/page/style.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { attentionStyle } from "#attention/style.ts";
|
|
1
2
|
import { workStyle } from "#views/workStyle.ts";
|
|
2
3
|
import { metadataStyles } from "./metadata.js";
|
|
3
4
|
import { readingStyles } from "./readingStyles.js";
|
|
@@ -51,5 +52,5 @@ figure.diagram[data-state="failed"] .diagram-source { display: block; }
|
|
|
51
52
|
figure.diagram[data-state="failed"]::after { content: attr(data-error); color: var(--danger); font-size: 0.875em; }
|
|
52
53
|
@media (prefers-reduced-motion: reduce) { * { transition: none !important; animation: none !important; } }
|
|
53
54
|
`;
|
|
54
|
-
export const style = theme + layout + prose + board + diagrams + readingStyles() + searchStyles + metadataStyles + workStyle();
|
|
55
|
+
export const style = theme + layout + prose + board + diagrams + readingStyles() + searchStyles + metadataStyles + workStyle() + attentionStyle();
|
|
55
56
|
//# sourceMappingURL=style.js.map
|
package/dist/page/style.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"style.js","sourceRoot":"","sources":["../../src/page/style.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAErD,MAAM,KAAK,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;CA0Bb,CAAC;AAEF,MAAM,KAAK,GAAG;;;;;;;;;;;;CAYb,CAAC;AAEF,MAAM,QAAQ,GAAG;;;;;;;CAOhB,CAAC;AAEF,MAAM,CAAC,MAAM,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,QAAQ,GAAG,aAAa,EAAE,GAAG,YAAY,GAAG,cAAc,GAAG,SAAS,EAAE,CAAC"}
|
|
1
|
+
{"version":3,"file":"style.js","sourceRoot":"","sources":["../../src/page/style.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAErD,MAAM,KAAK,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;CA0Bb,CAAC;AAEF,MAAM,KAAK,GAAG;;;;;;;;;;;;CAYb,CAAC;AAEF,MAAM,QAAQ,GAAG;;;;;;;CAOhB,CAAC;AAEF,MAAM,CAAC,MAAM,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,QAAQ,GAAG,aAAa,EAAE,GAAG,YAAY,GAAG,cAAc,GAAG,SAAS,EAAE,GAAG,cAAc,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attentionEntries.d.ts","sourceRoot":"","sources":["../../src/search/attentionEntries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAE1C,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,KAAK,EAAE,CAkB3F"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function attentionEntries(document, href) {
|
|
2
|
+
const requests = document.parsed.fields.attention ?? [];
|
|
3
|
+
return requests.flatMap((request, index) => {
|
|
4
|
+
if (requests.filter((other) => other.id === request.id).length !== 1) {
|
|
5
|
+
return [];
|
|
6
|
+
}
|
|
7
|
+
const line = document.parsed.lines[`attention.${index}`] ?? document.parsed.lines.attention;
|
|
8
|
+
return [
|
|
9
|
+
{
|
|
10
|
+
file: document.file,
|
|
11
|
+
href: `${href}#attention-request-${encodeURIComponent(request.id)}`,
|
|
12
|
+
kind: "passage",
|
|
13
|
+
...(line === undefined ? {} : { line }),
|
|
14
|
+
text: [request.reason, request.id, request.kind, request.state, ...request.responseFrom, ...request.unblocks].join(" "),
|
|
15
|
+
title: request.reason,
|
|
16
|
+
},
|
|
17
|
+
];
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=attentionEntries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attentionEntries.js","sourceRoot":"","sources":["../../src/search/attentionEntries.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,gBAAgB,CAAC,QAA0B,EAAE,IAAY;IACxE,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC;IACxD,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE;QAC1C,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,OAAO,CAAC,EAAE,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtE,OAAO,EAAE,CAAC;QACX,CAAC;QACD,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,KAAK,EAAE,CAAC,IAAI,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC;QAC5F,OAAO;YACN;gBACC,IAAI,EAAE,QAAQ,CAAC,IAAI;gBACnB,IAAI,EAAE,GAAG,IAAI,sBAAsB,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE;gBACnE,IAAI,EAAE,SAAS;gBACf,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;gBACvC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;gBACvH,KAAK,EAAE,OAAO,CAAC,MAAM;aACrB;SACD,CAAC;IACH,CAAC,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metadataEntries.d.ts","sourceRoot":"","sources":["../../src/search/metadataEntries.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"metadataEntries.d.ts","sourceRoot":"","sources":["../../src/search/metadataEntries.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAE1E,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAM1C,wBAAgB,eAAe,CAAC,OAAO,EAAE,SAAS,KAAK,EAAE,EAAE,SAAS,EAAE,SAAS,gBAAgB,EAAE,EAAE,KAAK,EAAE,aAAa,GAAG,SAAS,KAAK,EAAE,CAiCzI"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { fileUrl } from "#files/url.ts";
|
|
2
2
|
import { identityUrl } from "#metadata/links.ts";
|
|
3
|
+
import { attentionEntries } from "./attentionEntries.js";
|
|
3
4
|
function itemUrl(document, model) {
|
|
4
5
|
const id = document.parsed.fields.id;
|
|
5
6
|
return model.unavailable.length === 0 && id && model.ids.get(id)?.length === 1 ? identityUrl(id) : fileUrl(document.file);
|
|
@@ -19,6 +20,7 @@ export function metadataEntries(entries, documents, model) {
|
|
|
19
20
|
};
|
|
20
21
|
});
|
|
21
22
|
for (const document of documents) {
|
|
23
|
+
enriched.push(...attentionEntries(document, itemUrl(document, model)));
|
|
22
24
|
const criteria = document.parsed.fields.criteria ?? [];
|
|
23
25
|
for (const [index, criterion] of criteria.entries()) {
|
|
24
26
|
if (criteria.filter((item) => item.id === criterion.id).length !== 1) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metadataEntries.js","sourceRoot":"","sources":["../../src/search/metadataEntries.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"metadataEntries.js","sourceRoot":"","sources":["../../src/search/metadataEntries.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect, FileSystem
|
|
1
|
+
import { Effect, FileSystem } from "effect";
|
|
2
2
|
import type { Changes } from "#files/changes.ts";
|
|
3
3
|
import { type MetadataDocument, metadataModel } from "#metadata/model.ts";
|
|
4
4
|
import { type Entry } from "./entries.ts";
|
|
@@ -9,5 +9,5 @@ export interface Snapshot {
|
|
|
9
9
|
readonly revision: number;
|
|
10
10
|
readonly unavailable: readonly string[];
|
|
11
11
|
}
|
|
12
|
-
export declare const searchSnapshot: (root: string, home: string | undefined, changes: Changes) => Effect.Effect<Effect.Effect<Snapshot, import("effect/PlatformError").PlatformError, FileSystem.FileSystem | Path.Path>, never, never>;
|
|
12
|
+
export declare const searchSnapshot: (root: string, home: string | undefined, changes: Changes) => Effect.Effect<Effect.Effect<Snapshot, import("effect/PlatformError").PlatformError, FileSystem.FileSystem | import("effect/Path").Path>, never, never>;
|
|
13
13
|
//# sourceMappingURL=snapshot.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../../src/search/snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,UAAU,
|
|
1
|
+
{"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../../src/search/snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,UAAU,EAA0B,MAAM,QAAQ,CAAC;AACpE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAAE,KAAK,gBAAgB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAE1E,OAAO,EAAE,KAAK,KAAK,EAAa,MAAM,cAAc,CAAC;AAGrD,MAAM,WAAW,QAAQ;IACxB,QAAQ,CAAC,SAAS,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAChD,QAAQ,CAAC,OAAO,EAAE,SAAS,KAAK,EAAE,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;IACjD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAED,eAAO,MAAM,cAAc,sNA0CzB,CAAC"}
|
package/dist/search/snapshot.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Effect, FileSystem, Option,
|
|
2
|
-
import {
|
|
1
|
+
import { Effect, FileSystem, Option, Ref, Semaphore } from "effect";
|
|
2
|
+
import { markdownPath } from "#files/markdownPath.ts";
|
|
3
3
|
import { metadataModel } from "#metadata/model.ts";
|
|
4
4
|
import { metadataParse } from "#metadata/parse.ts";
|
|
5
5
|
import { entriesOf } from "./entries.js";
|
|
@@ -14,15 +14,14 @@ export const searchSnapshot = Effect.fn("WorkBoard.searchSnapshot")(function* (r
|
|
|
14
14
|
return hit;
|
|
15
15
|
}
|
|
16
16
|
const fs = yield* FileSystem.FileSystem;
|
|
17
|
-
const path = yield* Path.Path;
|
|
18
17
|
const files = yield* changes.files;
|
|
19
18
|
const entries = [];
|
|
20
19
|
const documents = [];
|
|
21
20
|
const unavailable = [];
|
|
22
21
|
for (const file of files) {
|
|
23
22
|
const found = yield* Effect.option(Effect.gen(function* () {
|
|
24
|
-
const real = yield*
|
|
25
|
-
if (
|
|
23
|
+
const real = yield* markdownPath(root, changes.realRoot, file.path);
|
|
24
|
+
if (real === undefined) {
|
|
26
25
|
return undefined;
|
|
27
26
|
}
|
|
28
27
|
const source = yield* fs.readFileString(real);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../src/search/snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../src/search/snapshot.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/work-board",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Serve a folder of markdown files as a live local page that updates in place when a file changes",
|
|
5
5
|
"homepage": "https://github.com/ShivaeDev/platform/tree/main/packages/work-board#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"sideEffects": false,
|
|
16
16
|
"type": "module",
|
|
17
17
|
"imports": {
|
|
18
|
-
"#test/*": "./test/*",
|
|
18
|
+
"#test/*": "./src/test-support/*",
|
|
19
19
|
"#*.ts": {
|
|
20
20
|
"source": "./src/*.ts",
|
|
21
21
|
"types": "./dist/*.d.ts",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"src",
|
|
40
40
|
"!src/**/*.test.ts",
|
|
41
41
|
"!src/**/*.spec.ts",
|
|
42
|
+
"!src/**/test-support",
|
|
42
43
|
"CHANGELOG.md",
|
|
43
44
|
"README.md"
|
|
44
45
|
],
|
|
@@ -51,7 +52,7 @@
|
|
|
51
52
|
"devDependencies": {
|
|
52
53
|
"@effect/platform-node": "4.0.0-rc.112",
|
|
53
54
|
"@effect/platform-node-shared": "4.0.0-rc.112",
|
|
54
|
-
"@shivaedev/quality": "0.
|
|
55
|
+
"@shivaedev/quality": "0.15.0",
|
|
55
56
|
"@types/hast": "3.0.5",
|
|
56
57
|
"@types/mdast": "4.0.4",
|
|
57
58
|
"@types/node": "24.10.1",
|
|
@@ -59,7 +60,7 @@
|
|
|
59
60
|
"effect": "4.0.0-rc.112",
|
|
60
61
|
"happy-dom": "20.11.2",
|
|
61
62
|
"typescript": "npm:@typescript/typescript6@6.0.2",
|
|
62
|
-
"vitest": "4.1.
|
|
63
|
+
"vitest": "4.1.11"
|
|
63
64
|
},
|
|
64
65
|
"peerDependencies": {
|
|
65
66
|
"@effect/platform-node": "4.0.0-rc.112",
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { fileUrl } from "#files/url.ts";
|
|
2
|
+
import { escapeHtml } from "#page/escape.ts";
|
|
3
|
+
import { referenceHtml } from "#page/metadataLinks.ts";
|
|
4
|
+
import type { Snapshot } from "#search/snapshot.ts";
|
|
5
|
+
import { type AttentionEntry, attentionRequests } from "./requests.ts";
|
|
6
|
+
|
|
7
|
+
function card(entry: AttentionEntry, snapshot: Snapshot): string {
|
|
8
|
+
const { request } = entry;
|
|
9
|
+
return `<article class="attention-card" data-key="attention:${escapeHtml(entry.itemId)}:${escapeHtml(request.id)}" data-attention-item="${escapeHtml(entry.itemId)}" data-request="${escapeHtml(request.id)}">
|
|
10
|
+
<p class="work-kind">${escapeHtml(request.kind)} request</p><h3><a href="${escapeHtml(entry.href)}">${escapeHtml(entry.title)}</a></h3>
|
|
11
|
+
<p class="work-id">${escapeHtml(entry.itemId)} / ${escapeHtml(request.id)}</p><p class="attention-reason">${escapeHtml(request.reason)}</p>
|
|
12
|
+
<dl><dt>Response needed from</dt><dd>${request.responseFrom.map(escapeHtml).join(", ")}</dd><dt>Recorded to unblock</dt><dd>${request.unblocks.map((target) => referenceHtml(target, target, snapshot.model)).join(", ")}</dd></dl>
|
|
13
|
+
<a class="attention-source" href="${escapeHtml(fileUrl(entry.document.file))}#attention-request-${encodeURIComponent(request.id)}">Source: ${escapeHtml(entry.document.file)}:${entry.line}</a></article>`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function overviewHtml(snapshot: Snapshot): string {
|
|
17
|
+
const projection = attentionRequests(snapshot);
|
|
18
|
+
const header =
|
|
19
|
+
'<header class="work-head"><p class="work-eyebrow">WORKSPACE · OVERVIEW</p><h1>What needs a response?</h1><p>Choose a recorded request to read its context, reasoning and evidence.</p><a href="/_board/work?view=board">Browse all work</a></header>';
|
|
20
|
+
const issues =
|
|
21
|
+
projection.issues.length > 0
|
|
22
|
+
? `<details class="view-issues" data-key="attention-issues"><summary>Unclassified requests and source issues (${projection.issues.length})</summary><p>Ambiguous or invalid requests stay outside the queues. Read their original sources.</p><ul>${projection.issues.map((issue) => `<li><a href="${escapeHtml(fileUrl(issue.file))}">${escapeHtml(issue.file)}:${issue.line}</a> — ${escapeHtml(issue.message)}</li>`).join("")}</ul></details>`
|
|
23
|
+
: "";
|
|
24
|
+
if (!projection.complete) {
|
|
25
|
+
return `${header}<p class="attention-incomplete" role="status">Workspace incomplete. Could not read ${snapshot.unavailable.map(escapeHtml).join(", ")}. Requests are not counted or classified until the index is complete.</p>${issues}`;
|
|
26
|
+
}
|
|
27
|
+
let summary = "All quiet — no explicit open requests recorded.";
|
|
28
|
+
if (projection.entries.length > 0) {
|
|
29
|
+
summary = `${projection.entries.length} validated open request${projection.entries.length === 1 ? "" : "s"}`;
|
|
30
|
+
} else if (projection.issues.length > 0) {
|
|
31
|
+
summary = "No validated open requests. Check unresolved source issues before treating this workspace as quiet.";
|
|
32
|
+
}
|
|
33
|
+
const groups = [
|
|
34
|
+
{ empty: "No open decision requests.", kind: "decision", title: "Decisions" },
|
|
35
|
+
{ empty: "No open review requests.", kind: "review", title: "Reviews" },
|
|
36
|
+
{ empty: "No open blocker requests.", kind: "blocker", title: "Blockers" },
|
|
37
|
+
]
|
|
38
|
+
.map((group) => {
|
|
39
|
+
const entries = projection.entries.filter((entry) => entry.request.kind === group.kind);
|
|
40
|
+
return `<section class="attention-group" aria-labelledby="attention-${group.kind}"><h2 id="attention-${group.kind}" tabindex="-1">${group.title}<span>${entries.length}</span></h2>${entries.length > 0 ? entries.map((entry) => card(entry, snapshot)).join("") : `<p class="empty">${group.empty}</p>`}</section>`;
|
|
41
|
+
})
|
|
42
|
+
.join("");
|
|
43
|
+
return `${header}<p class="attention-total" role="status">${summary}</p><p class="attention-order">Decisions, reviews, then blockers. Within each group: source title, item ID, request ID. Recipients are recorded labels.</p>${issues}<div class="attention-groups">${groups}</div><p class="attention-footnote">These are authored requests. Closing one does not verify evidence or record an answer. Status, owner, file activity and finished runs do not create requests.</p>`;
|
|
44
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { identityUrl, referenceTarget } from "#metadata/links.ts";
|
|
2
|
+
import type { MetadataDocument } from "#metadata/model.ts";
|
|
3
|
+
import type { Metadata } from "#metadata/schema.ts";
|
|
4
|
+
import type { Snapshot } from "#search/snapshot.ts";
|
|
5
|
+
|
|
6
|
+
export type Request = NonNullable<Metadata["attention"]>[number];
|
|
7
|
+
export interface AttentionEntry {
|
|
8
|
+
readonly document: MetadataDocument;
|
|
9
|
+
readonly href: string;
|
|
10
|
+
readonly itemId: string;
|
|
11
|
+
readonly line: number;
|
|
12
|
+
readonly request: Request;
|
|
13
|
+
readonly title: string;
|
|
14
|
+
}
|
|
15
|
+
export interface SourceIssue {
|
|
16
|
+
readonly file: string;
|
|
17
|
+
readonly line: number;
|
|
18
|
+
readonly message: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function requestsFor(document: MetadataDocument, snapshot: Snapshot, title: string): readonly AttentionEntry[] {
|
|
22
|
+
const { fields, lines } = document.parsed;
|
|
23
|
+
const itemId = fields.id;
|
|
24
|
+
if (!itemId || snapshot.unavailable.length > 0 || snapshot.model.ids.get(itemId)?.length !== 1) {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
const requests = fields.attention ?? [];
|
|
28
|
+
return requests.flatMap((request, index) => {
|
|
29
|
+
if (
|
|
30
|
+
request.state !== "open"
|
|
31
|
+
|| requests.filter((other) => other.id === request.id).length !== 1
|
|
32
|
+
|| request.unblocks.some((target) => !referenceTarget(target, snapshot.model))
|
|
33
|
+
) {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
return [
|
|
37
|
+
{
|
|
38
|
+
document,
|
|
39
|
+
href: `${identityUrl(itemId)}#attention-request-${encodeURIComponent(request.id)}`,
|
|
40
|
+
itemId,
|
|
41
|
+
line: lines[`attention.${index}`] ?? lines.attention ?? 2,
|
|
42
|
+
request,
|
|
43
|
+
title,
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function attentionRequests(snapshot: Snapshot) {
|
|
50
|
+
const titles = new Map(snapshot.entries.filter((entry) => entry.kind === "document").map((entry) => [entry.file, entry.title]));
|
|
51
|
+
const issues: SourceIssue[] = snapshot.documents.flatMap((source) =>
|
|
52
|
+
(snapshot.model.diagnostics.get(source.file) ?? source.parsed.diagnostics).map((problem) => ({ ...problem, file: source.file })),
|
|
53
|
+
);
|
|
54
|
+
const entries = snapshot.documents.flatMap((source) => requestsFor(source, snapshot, titles.get(source.file) ?? source.file));
|
|
55
|
+
const collator = new Intl.Collator("en", { numeric: true, sensitivity: "base" });
|
|
56
|
+
entries.sort((a, b) => collator.compare(a.title, b.title) || a.itemId.localeCompare(b.itemId) || a.request.id.localeCompare(b.request.id));
|
|
57
|
+
return { complete: snapshot.unavailable.length === 0, entries, issues };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function attentionSource(document: MetadataDocument, index: number): string {
|
|
61
|
+
const requests = document.parsed.fields.attention ?? [];
|
|
62
|
+
const id = requests[index]?.id;
|
|
63
|
+
return id && requests.filter((request) => request.id === id).length === 1 ? `attention-request-${id}` : `attention-record-${index}`;
|
|
64
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function attentionStyle(): string {
|
|
2
|
+
return `
|
|
3
|
+
.attention-total{margin:24px 0 8px;font-weight:600}.attention-order,.attention-footnote{font-size:13px;color:var(--muted);margin:0 0 24px;overflow-wrap:anywhere}
|
|
4
|
+
.attention-groups{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:20px;align-items:start}.attention-group{min-width:0}.attention-group>h2{display:flex;justify-content:space-between;gap:12px;font-size:16px;margin:0 0 16px}.attention-group>h2>span{font:12px var(--mono);color:var(--muted)}
|
|
5
|
+
.attention-card{border:1px solid var(--border);border-radius:10px;padding:var(--card-space);background:var(--card);margin:0 0 16px;overflow-wrap:anywhere}.attention-card h3{font-size:1rem;margin:6px 0}.attention-card h3 a{text-decoration:none}.attention-card dl{font-size:13px;margin:16px 0}.attention-card dt{color:var(--muted);margin-top:12px}.attention-card dd{margin:4px 0}.attention-source{font-size:12px}.attention-reason{font-size:14px;margin:0}.attention-footnote{margin-top:24px}.attention-incomplete{padding:16px;border:1px solid var(--danger);border-radius:8px;margin:24px 0;overflow-wrap:anywhere}
|
|
6
|
+
[id^="attention-request-"],[id^="attention-record-"]{scroll-margin-top:20px}
|
|
7
|
+
@media(max-width:1100px){.attention-groups{grid-template-columns:minmax(0,1fr)}}
|
|
8
|
+
`;
|
|
9
|
+
}
|
package/src/board.ts
CHANGED
|
@@ -4,8 +4,10 @@ import { watchChanges } from "#files/changes.ts";
|
|
|
4
4
|
import { type HomeMissing, homeIn } from "#files/home.ts";
|
|
5
5
|
import { ASSETS, MERMAID_ROUTE, type MermaidMissing, mermaidFile, mermaidRoot } from "#http/assets.ts";
|
|
6
6
|
import { events } from "#http/events.ts";
|
|
7
|
+
import { changesPage, history } from "#http/history.ts";
|
|
7
8
|
import { identity } from "#http/identity.ts";
|
|
8
9
|
import { loopbackOnly } from "#http/loopback.ts";
|
|
10
|
+
import { overview } from "#http/overview.ts";
|
|
9
11
|
import { page } from "#http/page.ts";
|
|
10
12
|
import { respond } from "#http/respond.ts";
|
|
11
13
|
import { search } from "#http/search.ts";
|
|
@@ -29,9 +31,9 @@ const routes = (options: BoardOptions) =>
|
|
|
29
31
|
const mermaid = yield* mermaidRoot();
|
|
30
32
|
const changes = yield* watchChanges(options.root);
|
|
31
33
|
const context = yield* Effect.context<Services>();
|
|
32
|
-
const serve = <E>(route: HttpRouter.PathInput, handler: Handler<E
|
|
34
|
+
const serve = <E>(route: HttpRouter.PathInput, handler: Handler<E>, method: "GET" | "POST" = "GET") =>
|
|
33
35
|
router.add(
|
|
34
|
-
|
|
36
|
+
method,
|
|
35
37
|
route,
|
|
36
38
|
loopbackOnly((request) => Effect.provideContext(handler(request), context)),
|
|
37
39
|
);
|
|
@@ -39,6 +41,9 @@ const routes = (options: BoardOptions) =>
|
|
|
39
41
|
const index = yield* searchSnapshot(options.root, home, changes);
|
|
40
42
|
yield* serve("/_board/search", search(index));
|
|
41
43
|
yield* serve("/_board/work", work(index, changes, home));
|
|
44
|
+
yield* serve("/_board/overview", overview(index, changes, home));
|
|
45
|
+
yield* serve("/_board/changes", changesPage(changes, home));
|
|
46
|
+
yield* serve("/_board/history", history(index), "POST");
|
|
42
47
|
for (const [route, body, contentType] of ASSETS) {
|
|
43
48
|
yield* serve(route, () => Effect.succeed(respond(body, contentType)));
|
|
44
49
|
}
|
package/src/cli.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { Effect, Layer, Option, Path } from "effect";
|
|
|
4
4
|
import { Argument, Command, Flag } from "effect/unstable/cli";
|
|
5
5
|
import { HOST, serveBoard } from "./serve.ts";
|
|
6
6
|
|
|
7
|
-
const VERSION = "0.
|
|
7
|
+
const VERSION = "0.4.1";
|
|
8
8
|
|
|
9
9
|
const serve = Effect.fn("WorkBoard.serve")(function* (input: { readonly dir: string; readonly port: number; readonly home: Option.Option<string> }) {
|
|
10
10
|
const path = yield* Path.Path;
|
package/src/files/changes.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Deferred, Effect, FileSystem, Option, Path, type PlatformError, PubSub, Ref, Schedule } from "effect";
|
|
2
|
+
import { listMarkdown, type MarkdownFile } from "./list.ts";
|
|
3
|
+
import { watchDirectories } from "./watchDirectories.ts";
|
|
3
4
|
|
|
4
5
|
export type Change = { readonly _tag: "Changed"; readonly paths: readonly string[] } | { readonly _tag: "Watching"; readonly watching: boolean };
|
|
5
6
|
|
|
@@ -16,10 +17,6 @@ interface Cached {
|
|
|
16
17
|
readonly generation: number;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
class WatchEnded extends Data.TaggedError("WatchEnded") {}
|
|
20
|
-
|
|
21
|
-
const SETTLE = "100 millis";
|
|
22
|
-
const BATCH = 256;
|
|
23
20
|
const BACKLOG = 16;
|
|
24
21
|
const RETRY = Schedule.min([Schedule.exponential("100 millis"), Schedule.spaced("5 seconds")]);
|
|
25
22
|
|
|
@@ -33,7 +30,6 @@ export const watchChanges = Effect.fn("WorkBoard.watchChanges")(function* (root:
|
|
|
33
30
|
const cached = yield* Ref.make(Option.none<Cached>());
|
|
34
31
|
const settled = yield* Deferred.make<void>();
|
|
35
32
|
const stale = Ref.update(generation, (count) => count + 1);
|
|
36
|
-
const relative = (changed: string) => (path.isAbsolute(changed) ? path.relative(root, changed) : changed).split(path.sep).join("/");
|
|
37
33
|
const setWatching = (now: boolean) =>
|
|
38
34
|
Effect.andThen(
|
|
39
35
|
Effect.flatMap(Ref.getAndSet(watching, now), (was) =>
|
|
@@ -41,15 +37,7 @@ export const watchChanges = Effect.fn("WorkBoard.watchChanges")(function* (root:
|
|
|
41
37
|
),
|
|
42
38
|
Deferred.succeed(settled, undefined),
|
|
43
39
|
);
|
|
44
|
-
const
|
|
45
|
-
Stream.tap(() => stale),
|
|
46
|
-
Stream.map((event) => relative(event.path)),
|
|
47
|
-
Stream.filter(isMarkdown),
|
|
48
|
-
Stream.groupedWithin(BATCH, SETTLE),
|
|
49
|
-
Stream.runForEach((paths) => PubSub.publish(events, { _tag: "Changed", paths: [...new Set(paths)] })),
|
|
50
|
-
Effect.andThen(Effect.fail(new WatchEnded())),
|
|
51
|
-
);
|
|
52
|
-
const attempt = Effect.scoped(Effect.andThen(Effect.forkScoped(Effect.delay(setWatching(true), SETTLE)), watchOnce));
|
|
40
|
+
const attempt = watchDirectories(root, realRoot, stale, setWatching, (paths) => PubSub.publish(events, { _tag: "Changed", paths }));
|
|
53
41
|
yield* attempt.pipe(
|
|
54
42
|
Effect.sandbox,
|
|
55
43
|
Effect.tapError((cause) => Effect.andThen(setWatching(false), Effect.logWarning("Watching the folder failed; retrying", cause))),
|
package/src/files/home.ts
CHANGED
|
@@ -20,6 +20,10 @@ export const homeIn = Effect.fn("WorkBoard.homeIn")(function* (root: string, hom
|
|
|
20
20
|
return yield* missing;
|
|
21
21
|
}
|
|
22
22
|
const files = yield* listMarkdown(root, realRoot);
|
|
23
|
+
const logical = path.relative(root, path.resolve(root, home)).split(path.sep).join("/");
|
|
24
|
+
if (files.some((file) => file.path === logical)) {
|
|
25
|
+
return logical;
|
|
26
|
+
}
|
|
23
27
|
const relative = path.relative(realRoot, real.value).split(path.sep).join("/");
|
|
24
28
|
if (files.some((file) => file.path === relative)) {
|
|
25
29
|
return relative;
|
package/src/files/list.ts
CHANGED
|
@@ -17,14 +17,14 @@ const byFolder = (left: string, right: string): number => folderOf(left).localeC
|
|
|
17
17
|
|
|
18
18
|
type Listing = Effect.Effect<readonly MarkdownFile[], PlatformError.PlatformError>;
|
|
19
19
|
|
|
20
|
-
export const
|
|
20
|
+
export const scanMarkdown = Effect.fn("WorkBoard.scanMarkdown")(function* (root: string, realRoot: string) {
|
|
21
21
|
const fs = yield* FileSystem.FileSystem;
|
|
22
22
|
const path = yield* Path.Path;
|
|
23
|
-
const
|
|
24
|
-
const entry = (relative: string): Listing =>
|
|
23
|
+
const roots: Array<{ readonly real: string; readonly relative: string }> = [{ real: realRoot, relative: "" }];
|
|
24
|
+
const entry = (relative: string, boundary: string, ancestors: ReadonlySet<string>, parent: string): Listing =>
|
|
25
25
|
Effect.gen(function* () {
|
|
26
26
|
const real = yield* Effect.option(fs.realPath(path.join(root, relative)));
|
|
27
|
-
if (Option.isNone(real)
|
|
27
|
+
if (Option.isNone(real)) {
|
|
28
28
|
return [];
|
|
29
29
|
}
|
|
30
30
|
const info = yield* Effect.option(fs.stat(real.value));
|
|
@@ -32,24 +32,35 @@ export const listMarkdown = Effect.fn("WorkBoard.listMarkdown")(function* (root:
|
|
|
32
32
|
return [];
|
|
33
33
|
}
|
|
34
34
|
if (info.value.type === "Directory") {
|
|
35
|
-
return yield*
|
|
35
|
+
return yield* directory(relative, real.value, boundary, ancestors, real.value !== path.join(parent, path.basename(relative)));
|
|
36
36
|
}
|
|
37
37
|
const modified = Option.match(info.value.mtime, { onNone: () => 0, onSome: (date) => date.getTime() });
|
|
38
|
-
return info.value.type === "File" && relative.endsWith(".md") ? [{ modified, path: relative }] : [];
|
|
38
|
+
return info.value.type === "File" && relative.endsWith(".md") && within(boundary, path.sep, real.value) ? [{ modified, path: relative }] : [];
|
|
39
39
|
});
|
|
40
|
-
|
|
40
|
+
function directory(relative: string, real: string, boundary: string, ancestors: ReadonlySet<string>, alias: boolean): Listing {
|
|
41
|
+
if (ancestors.has(real) || within(real, path.sep, realRoot)) {
|
|
42
|
+
return Effect.succeed([]);
|
|
43
|
+
}
|
|
44
|
+
const linked = !within(boundary, path.sep, real);
|
|
45
|
+
if (alias) {
|
|
46
|
+
roots.push({ real, relative });
|
|
47
|
+
}
|
|
48
|
+
return Effect.orElseSucceed(folder(relative, real, linked ? real : boundary, ancestors), () => []);
|
|
49
|
+
}
|
|
50
|
+
const folder = (relative: string, real: string, boundary: string, ancestors: ReadonlySet<string>): Listing =>
|
|
41
51
|
Effect.gen(function* () {
|
|
42
|
-
|
|
43
|
-
return [];
|
|
44
|
-
}
|
|
45
|
-
visited.add(real);
|
|
52
|
+
const branch = new Set([...ancestors, real]);
|
|
46
53
|
const names = yield* fs.readDirectory(real);
|
|
47
54
|
const found = yield* Effect.forEach(
|
|
48
55
|
names.filter((name) => !skipped(name)),
|
|
49
|
-
(name) => entry(relative === "" ? name : `${relative}/${name}
|
|
56
|
+
(name) => entry(relative === "" ? name : `${relative}/${name}`, boundary, branch, real),
|
|
50
57
|
);
|
|
51
58
|
return found.flat();
|
|
52
59
|
});
|
|
53
|
-
const files = yield* folder("", realRoot);
|
|
54
|
-
return files.toSorted((left, right) => byFolder(left.path, right.path));
|
|
60
|
+
const files = yield* folder("", realRoot, realRoot, new Set());
|
|
61
|
+
return { files: files.toSorted((left, right) => byFolder(left.path, right.path)), roots };
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export const listMarkdown = Effect.fn("WorkBoard.listMarkdown")(function* (root: string, realRoot: string) {
|
|
65
|
+
return (yield* scanMarkdown(root, realRoot)).files;
|
|
55
66
|
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Effect, FileSystem, Option, Path } from "effect";
|
|
2
|
+
import { isMarkdown, within } from "./list.ts";
|
|
3
|
+
|
|
4
|
+
export const markdownPath = Effect.fn("WorkBoard.markdownPath")(function* (root: string, realRoot: string, relative: string) {
|
|
5
|
+
if (!isMarkdown(relative) || relative.split("/").some((segment) => segment === "" || segment === "..")) {
|
|
6
|
+
return undefined;
|
|
7
|
+
}
|
|
8
|
+
const fs = yield* FileSystem.FileSystem;
|
|
9
|
+
const path = yield* Path.Path;
|
|
10
|
+
const segments = relative.split("/");
|
|
11
|
+
const ancestors = new Set([realRoot]);
|
|
12
|
+
let boundary = realRoot;
|
|
13
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
14
|
+
const real = yield* Effect.option(fs.realPath(path.join(root, ...segments.slice(0, index + 1))));
|
|
15
|
+
if (Option.isNone(real)) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
if (index === segments.length - 1) {
|
|
19
|
+
return within(boundary, path.sep, real.value) ? real.value : undefined;
|
|
20
|
+
}
|
|
21
|
+
const info = yield* Effect.option(fs.stat(real.value));
|
|
22
|
+
if (Option.isNone(info) || info.value.type !== "Directory" || ancestors.has(real.value) || within(real.value, path.sep, realRoot)) {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
ancestors.add(real.value);
|
|
26
|
+
if (!within(boundary, path.sep, real.value)) {
|
|
27
|
+
boundary = real.value;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Data, Deferred, Effect, FileSystem, Option, Path, Stream } from "effect";
|
|
2
|
+
import { isMarkdown, scanMarkdown } from "./list.ts";
|
|
3
|
+
|
|
4
|
+
class WatchEnded extends Data.TaggedError("WatchEnded") {}
|
|
5
|
+
|
|
6
|
+
const SETTLE = "100 millis";
|
|
7
|
+
const BATCH = 256;
|
|
8
|
+
|
|
9
|
+
export const watchDirectories = Effect.fn("WorkBoard.watchDirectories")(function* (
|
|
10
|
+
root: string,
|
|
11
|
+
realRoot: string,
|
|
12
|
+
stale: Effect.Effect<void>,
|
|
13
|
+
setWatching: (watching: boolean) => Effect.Effect<unknown>,
|
|
14
|
+
publish: (paths: readonly string[]) => Effect.Effect<unknown>,
|
|
15
|
+
) {
|
|
16
|
+
const fs = yield* FileSystem.FileSystem;
|
|
17
|
+
const path = yield* Path.Path;
|
|
18
|
+
const cycle = Effect.scoped(
|
|
19
|
+
Effect.gen(function* () {
|
|
20
|
+
const { roots } = yield* scanMarkdown(root, realRoot);
|
|
21
|
+
const topology = yield* Deferred.make<void>();
|
|
22
|
+
// Node's recursive watcher can miss replacement of directory symlinks.
|
|
23
|
+
const parents = yield* Effect.forEach(
|
|
24
|
+
roots.filter((folder) => folder.relative !== ""),
|
|
25
|
+
(folder) =>
|
|
26
|
+
Effect.map(fs.realPath(path.dirname(path.join(root, folder.relative))), (real) => ({ real, relative: path.dirname(folder.relative) })),
|
|
27
|
+
);
|
|
28
|
+
const watched = [...roots.map((folder) => ({ ...folder, recursive: true })), ...parents.map((folder) => ({ ...folder, recursive: false }))];
|
|
29
|
+
const streams = watched.map((folder) =>
|
|
30
|
+
fs.watch(folder.real, { recursive: folder.recursive }).pipe(
|
|
31
|
+
Stream.map((event) => {
|
|
32
|
+
const local = path.isAbsolute(event.path) ? path.relative(folder.real, event.path) : event.path;
|
|
33
|
+
return path.join(folder.relative, local).split(path.sep).join("/");
|
|
34
|
+
}),
|
|
35
|
+
),
|
|
36
|
+
);
|
|
37
|
+
const watch = Stream.mergeAll(streams, { concurrency: "unbounded" }).pipe(
|
|
38
|
+
Stream.tap(() => stale),
|
|
39
|
+
Stream.tap((relative) =>
|
|
40
|
+
Effect.gen(function* () {
|
|
41
|
+
const info = yield* Effect.option(fs.stat(path.join(root, relative)));
|
|
42
|
+
const removedLink = roots.some((folder) => folder.relative === relative || folder.relative.startsWith(`${relative}/`));
|
|
43
|
+
if (Option.match(info, { onNone: () => removedLink, onSome: (value) => value.type === "Directory" })) {
|
|
44
|
+
yield* Effect.forkScoped(Effect.delay(Deferred.succeed(topology, undefined), SETTLE));
|
|
45
|
+
}
|
|
46
|
+
}),
|
|
47
|
+
),
|
|
48
|
+
Stream.filter(isMarkdown),
|
|
49
|
+
Stream.interruptWhen(Deferred.await(topology)),
|
|
50
|
+
Stream.groupedWithin(BATCH, SETTLE),
|
|
51
|
+
Stream.runForEach((paths) => publish([...new Set(paths)])),
|
|
52
|
+
Effect.andThen(Effect.flatMap(Deferred.isDone(topology), (changed) => (changed ? Effect.void : Effect.fail(new WatchEnded())))),
|
|
53
|
+
);
|
|
54
|
+
yield* Effect.forkScoped(Effect.delay(setWatching(true), SETTLE));
|
|
55
|
+
yield* watch;
|
|
56
|
+
yield* setWatching(false);
|
|
57
|
+
}),
|
|
58
|
+
);
|
|
59
|
+
yield* Effect.forever(cycle);
|
|
60
|
+
});
|