@shivaedev/work-board 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +93 -0
- package/README.md +208 -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 +15 -2
- package/dist/board.js.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/files/changes.d.ts +1 -0
- package/dist/files/changes.d.ts.map +1 -1
- package/dist/files/changes.js +5 -10
- 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/url.d.ts +2 -0
- package/dist/files/url.d.ts.map +1 -0
- package/dist/files/url.js +4 -0
- package/dist/files/url.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 +113 -0
- package/dist/history/script.js.map +1 -0
- package/dist/http/assets.d.ts.map +1 -1
- package/dist/http/assets.js +18 -1
- 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/identity.d.ts +7 -0
- package/dist/http/identity.d.ts.map +1 -0
- package/dist/http/identity.js +38 -0
- package/dist/http/identity.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 +3 -2
- package/dist/http/page.d.ts.map +1 -1
- package/dist/http/page.js +34 -13
- package/dist/http/page.js.map +1 -1
- package/dist/http/search.d.ts +5 -0
- package/dist/http/search.d.ts.map +1 -0
- package/dist/http/search.js +11 -0
- package/dist/http/search.js.map +1 -0
- package/dist/http/work.d.ts +6 -0
- package/dist/http/work.d.ts.map +1 -0
- package/dist/http/work.js +22 -0
- package/dist/http/work.js.map +1 -0
- 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 +8 -0
- package/dist/metadata/backlinks.d.ts.map +1 -0
- package/dist/metadata/backlinks.js +34 -0
- package/dist/metadata/backlinks.js.map +1 -0
- package/dist/metadata/fields.d.ts +37 -0
- package/dist/metadata/fields.d.ts.map +1 -0
- package/dist/metadata/fields.js +25 -0
- package/dist/metadata/fields.js.map +1 -0
- package/dist/metadata/links.d.ts +4 -0
- package/dist/metadata/links.d.ts.map +1 -0
- package/dist/metadata/links.js +18 -0
- package/dist/metadata/links.js.map +1 -0
- package/dist/metadata/locations.d.ts +3 -0
- package/dist/metadata/locations.d.ts.map +1 -0
- package/dist/metadata/locations.js +35 -0
- package/dist/metadata/locations.js.map +1 -0
- package/dist/metadata/model.d.ts +15 -0
- package/dist/metadata/model.d.ts.map +1 -0
- package/dist/metadata/model.js +13 -0
- package/dist/metadata/model.js.map +1 -0
- package/dist/metadata/parse.d.ts +16 -0
- package/dist/metadata/parse.d.ts.map +1 -0
- package/dist/metadata/parse.js +48 -0
- package/dist/metadata/parse.js.map +1 -0
- package/dist/metadata/problems.d.ts +4 -0
- package/dist/metadata/problems.d.ts.map +1 -0
- package/dist/metadata/problems.js +59 -0
- package/dist/metadata/problems.js.map +1 -0
- package/dist/metadata/relationshipLabel.d.ts +2 -0
- package/dist/metadata/relationshipLabel.d.ts.map +1 -0
- package/dist/metadata/relationshipLabel.js +14 -0
- package/dist/metadata/relationshipLabel.js.map +1 -0
- package/dist/metadata/schema.d.ts +44 -0
- package/dist/metadata/schema.d.ts.map +1 -0
- package/dist/metadata/schema.js +36 -0
- package/dist/metadata/schema.js.map +1 -0
- package/dist/metadata/sourceLinks.d.ts +5 -0
- package/dist/metadata/sourceLinks.d.ts.map +1 -0
- package/dist/metadata/sourceLinks.js +44 -0
- package/dist/metadata/sourceLinks.js.map +1 -0
- 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/backlinksHtml.d.ts +3 -0
- package/dist/page/backlinksHtml.d.ts.map +1 -0
- package/dist/page/backlinksHtml.js +16 -0
- package/dist/page/backlinksHtml.js.map +1 -0
- package/dist/page/board.d.ts +1 -1
- package/dist/page/board.d.ts.map +1 -1
- package/dist/page/board.js +14 -4
- package/dist/page/board.js.map +1 -1
- package/dist/page/client.d.ts +2 -1
- package/dist/page/client.d.ts.map +1 -1
- package/dist/page/client.js +66 -7
- package/dist/page/client.js.map +1 -1
- package/dist/page/criteriaEvidence.d.ts +4 -0
- package/dist/page/criteriaEvidence.d.ts.map +1 -0
- package/dist/page/criteriaEvidence.js +27 -0
- package/dist/page/criteriaEvidence.js.map +1 -0
- package/dist/page/diagrams.d.ts +1 -1
- package/dist/page/diagrams.d.ts.map +1 -1
- package/dist/page/diagrams.js +12 -4
- package/dist/page/diagrams.js.map +1 -1
- package/dist/page/document.d.ts +1 -1
- package/dist/page/document.d.ts.map +1 -1
- package/dist/page/document.js +6 -3
- package/dist/page/document.js.map +1 -1
- package/dist/page/libraryScript.d.ts +2 -0
- package/dist/page/libraryScript.d.ts.map +1 -0
- package/dist/page/libraryScript.js +73 -0
- package/dist/page/libraryScript.js.map +1 -0
- package/dist/page/metadata.d.ts +5 -0
- package/dist/page/metadata.d.ts.map +1 -0
- package/dist/page/metadata.js +34 -0
- package/dist/page/metadata.js.map +1 -0
- package/dist/page/metadataDetails.d.ts +4 -0
- package/dist/page/metadataDetails.d.ts.map +1 -0
- package/dist/page/metadataDetails.js +23 -0
- package/dist/page/metadataDetails.js.map +1 -0
- package/dist/page/metadataLinks.d.ts +6 -0
- package/dist/page/metadataLinks.d.ts.map +1 -0
- package/dist/page/metadataLinks.js +31 -0
- package/dist/page/metadataLinks.js.map +1 -0
- package/dist/page/nav.d.ts.map +1 -1
- package/dist/page/nav.js +3 -3
- package/dist/page/nav.js.map +1 -1
- package/dist/page/navigationScript.d.ts +2 -0
- package/dist/page/navigationScript.d.ts.map +1 -0
- package/dist/page/navigationScript.js +104 -0
- package/dist/page/navigationScript.js.map +1 -0
- package/dist/page/readingStateScript.d.ts +2 -0
- package/dist/page/readingStateScript.d.ts.map +1 -0
- package/dist/page/readingStateScript.js +62 -0
- package/dist/page/readingStateScript.js.map +1 -0
- package/dist/page/readingStyles.d.ts +2 -0
- package/dist/page/readingStyles.d.ts.map +1 -0
- package/dist/page/readingStyles.js +29 -0
- package/dist/page/readingStyles.js.map +1 -0
- package/dist/page/readingTools.d.ts +3 -0
- package/dist/page/readingTools.d.ts.map +1 -0
- package/dist/page/readingTools.js +10 -0
- package/dist/page/readingTools.js.map +1 -0
- package/dist/page/recordedEvidence.d.ts +4 -0
- package/dist/page/recordedEvidence.d.ts.map +1 -0
- package/dist/page/recordedEvidence.js +10 -0
- package/dist/page/recordedEvidence.js.map +1 -0
- package/dist/page/searchDialog.d.ts +3 -0
- package/dist/page/searchDialog.d.ts.map +1 -0
- package/dist/page/searchDialog.js +16 -0
- package/dist/page/searchDialog.js.map +1 -0
- package/dist/page/searchResultsScript.d.ts +2 -0
- package/dist/page/searchResultsScript.d.ts.map +1 -0
- package/dist/page/searchResultsScript.js +64 -0
- package/dist/page/searchResultsScript.js.map +1 -0
- package/dist/page/searchScript.d.ts +2 -0
- package/dist/page/searchScript.d.ts.map +1 -0
- package/dist/page/searchScript.js +73 -0
- package/dist/page/searchScript.js.map +1 -0
- package/dist/page/shell.d.ts +1 -1
- package/dist/page/shell.d.ts.map +1 -1
- package/dist/page/shell.js +23 -5
- package/dist/page/shell.js.map +1 -1
- package/dist/page/stateScript.d.ts +2 -0
- package/dist/page/stateScript.d.ts.map +1 -0
- package/dist/page/stateScript.js +55 -0
- package/dist/page/stateScript.js.map +1 -0
- package/dist/page/style.d.ts.map +1 -1
- package/dist/page/style.js +15 -34
- package/dist/page/style.js.map +1 -1
- package/dist/page/swap.d.ts +1 -1
- package/dist/page/swap.d.ts.map +1 -1
- package/dist/page/swap.js +4 -1
- package/dist/page/swap.js.map +1 -1
- package/dist/page/workspaceStyles.d.ts +3 -0
- package/dist/page/workspaceStyles.d.ts.map +1 -0
- package/dist/page/workspaceStyles.js +79 -0
- package/dist/page/workspaceStyles.js.map +1 -0
- package/dist/render/documentHeadings.d.ts +667 -0
- package/dist/render/documentHeadings.d.ts.map +1 -0
- package/dist/render/documentHeadings.js +45 -0
- package/dist/render/documentHeadings.js.map +1 -0
- package/dist/render/documentLinks.d.ts +659 -0
- package/dist/render/documentLinks.d.ts.map +1 -0
- package/dist/render/documentLinks.js +19 -0
- package/dist/render/documentLinks.js.map +1 -0
- package/dist/render/markdown.d.ts +7 -1
- package/dist/render/markdown.d.ts.map +1 -1
- package/dist/render/markdown.js +13 -2
- package/dist/render/markdown.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/collector.d.ts +13 -0
- package/dist/search/collector.d.ts.map +1 -0
- package/dist/search/collector.js +40 -0
- package/dist/search/collector.js.map +1 -0
- package/dist/search/entries.d.ts +10 -0
- package/dist/search/entries.d.ts.map +1 -0
- package/dist/search/entries.js +37 -0
- package/dist/search/entries.js.map +1 -0
- package/dist/search/linkCollector.d.ts +8 -0
- package/dist/search/linkCollector.d.ts.map +1 -0
- package/dist/search/linkCollector.js +16 -0
- package/dist/search/linkCollector.js.map +1 -0
- package/dist/search/match.d.ts +14 -0
- package/dist/search/match.d.ts.map +1 -0
- package/dist/search/match.js +26 -0
- package/dist/search/match.js.map +1 -0
- package/dist/search/metadataEntries.d.ts +4 -0
- package/dist/search/metadataEntries.d.ts.map +1 -0
- package/dist/search/metadataEntries.js +42 -0
- package/dist/search/metadataEntries.js.map +1 -0
- package/dist/search/snapshot.d.ts +13 -0
- package/dist/search/snapshot.d.ts.map +1 -0
- package/dist/search/snapshot.js +49 -0
- package/dist/search/snapshot.js.map +1 -0
- package/dist/views/items.d.ts +11 -0
- package/dist/views/items.d.ts.map +1 -0
- package/dist/views/items.js +48 -0
- package/dist/views/items.js.map +1 -0
- package/dist/views/projection.d.ts +15 -0
- package/dist/views/projection.d.ts.map +1 -0
- package/dist/views/projection.js +47 -0
- package/dist/views/projection.js.map +1 -0
- package/dist/views/savedScript.d.ts +2 -0
- package/dist/views/savedScript.d.ts.map +1 -0
- package/dist/views/savedScript.js +89 -0
- package/dist/views/savedScript.js.map +1 -0
- package/dist/views/state.d.ts +15 -0
- package/dist/views/state.d.ts.map +1 -0
- package/dist/views/state.js +45 -0
- package/dist/views/state.js.map +1 -0
- package/dist/views/tools.d.ts +3 -0
- package/dist/views/tools.d.ts.map +1 -0
- package/dist/views/tools.js +11 -0
- package/dist/views/tools.js.map +1 -0
- package/dist/views/workCards.d.ts +4 -0
- package/dist/views/workCards.d.ts.map +1 -0
- package/dist/views/workCards.js +23 -0
- package/dist/views/workCards.js.map +1 -0
- package/dist/views/workControls.d.ts +4 -0
- package/dist/views/workControls.d.ts.map +1 -0
- package/dist/views/workControls.js +32 -0
- package/dist/views/workControls.js.map +1 -0
- package/dist/views/workDetail.d.ts +13 -0
- package/dist/views/workDetail.d.ts.map +1 -0
- package/dist/views/workDetail.js +26 -0
- package/dist/views/workDetail.js.map +1 -0
- package/dist/views/workHtml.d.ts +15 -0
- package/dist/views/workHtml.d.ts.map +1 -0
- package/dist/views/workHtml.js +35 -0
- package/dist/views/workHtml.js.map +1 -0
- package/dist/views/workStyle.d.ts +2 -0
- package/dist/views/workStyle.d.ts.map +1 -0
- package/dist/views/workStyle.js +18 -0
- package/dist/views/workStyle.js.map +1 -0
- package/dist/views/workTable.d.ts +4 -0
- package/dist/views/workTable.d.ts.map +1 -0
- package/dist/views/workTable.js +19 -0
- package/dist/views/workTable.js.map +1 -0
- package/package.json +14 -10
- 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 +16 -3
- package/src/cli.ts +1 -1
- package/src/files/changes.ts +6 -17
- package/src/files/home.ts +4 -0
- package/src/files/list.ts +25 -14
- package/src/files/markdownPath.ts +31 -0
- package/src/files/url.ts +3 -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 +112 -0
- package/src/http/assets.ts +18 -1
- package/src/http/history.ts +50 -0
- package/src/http/identity.ts +62 -0
- package/src/http/overview.ts +19 -0
- package/src/http/page.ts +52 -15
- package/src/http/search.ts +13 -0
- package/src/http/work.ts +41 -0
- package/src/metadata/attentionProblems.ts +18 -0
- package/src/metadata/backlinks.ts +46 -0
- package/src/metadata/fields.ts +26 -0
- package/src/metadata/links.ts +19 -0
- package/src/metadata/locations.ts +34 -0
- package/src/metadata/model.ts +27 -0
- package/src/metadata/parse.ts +62 -0
- package/src/metadata/problems.ts +66 -0
- package/src/metadata/relationshipLabel.ts +14 -0
- package/src/metadata/schema.ts +45 -0
- package/src/metadata/sourceLinks.ts +46 -0
- package/src/page/attentionDetails.ts +18 -0
- package/src/page/backlinksHtml.ts +18 -0
- package/src/page/board.ts +14 -4
- package/src/page/client.ts +66 -7
- package/src/page/criteriaEvidence.ts +36 -0
- package/src/page/diagrams.ts +12 -4
- package/src/page/document.ts +6 -3
- package/src/page/libraryScript.ts +72 -0
- package/src/page/metadata.ts +40 -0
- package/src/page/metadataDetails.ts +26 -0
- package/src/page/metadataLinks.ts +35 -0
- package/src/page/nav.ts +5 -4
- package/src/page/navigationScript.ts +103 -0
- package/src/page/readingStateScript.ts +61 -0
- package/src/page/readingStyles.ts +28 -0
- package/src/page/readingTools.ts +12 -0
- package/src/page/recordedEvidence.ts +12 -0
- package/src/page/searchDialog.ts +16 -0
- package/src/page/searchResultsScript.ts +63 -0
- package/src/page/searchScript.ts +72 -0
- package/src/page/shell.ts +32 -5
- package/src/page/stateScript.ts +54 -0
- package/src/page/style.ts +15 -35
- package/src/page/swap.ts +4 -1
- package/src/page/workspaceStyles.ts +79 -0
- package/src/render/documentHeadings.ts +52 -0
- package/src/render/documentLinks.ts +19 -0
- package/src/render/markdown.ts +21 -4
- package/src/search/attentionEntries.ts +22 -0
- package/src/search/collector.ts +44 -0
- package/src/search/entries.ts +51 -0
- package/src/search/linkCollector.ts +16 -0
- package/src/search/match.ts +28 -0
- package/src/search/metadataEntries.ts +44 -0
- package/src/search/snapshot.ts +59 -0
- package/src/views/items.ts +55 -0
- package/src/views/projection.ts +59 -0
- package/src/views/savedScript.ts +88 -0
- package/src/views/state.ts +47 -0
- package/src/views/tools.ts +11 -0
- package/src/views/workCards.ts +26 -0
- package/src/views/workControls.ts +38 -0
- package/src/views/workDetail.ts +28 -0
- package/src/views/workHtml.ts +39 -0
- package/src/views/workStyle.ts +17 -0
- package/src/views/workTable.ts +21 -0
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +0 -1
- package/src/index.ts +0 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { LineCounter, parseDocument } from "yaml";
|
|
2
|
+
import { metadataFields } from "./fields.ts";
|
|
3
|
+
import { metadataLocations } from "./locations.ts";
|
|
4
|
+
import type { Metadata } from "./schema.ts";
|
|
5
|
+
|
|
6
|
+
export interface Diagnostic {
|
|
7
|
+
readonly field?: string;
|
|
8
|
+
readonly line: number;
|
|
9
|
+
readonly message: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ParsedMetadata {
|
|
12
|
+
readonly body: string;
|
|
13
|
+
readonly bodyLine: number;
|
|
14
|
+
readonly diagnostics: readonly Diagnostic[];
|
|
15
|
+
readonly fields: Metadata;
|
|
16
|
+
readonly lines: Readonly<Record<string, number>>;
|
|
17
|
+
readonly raw?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function metadataParse(source: string): ParsedMetadata {
|
|
21
|
+
const plain: ParsedMetadata = { body: source, bodyLine: 1, diagnostics: [], fields: {}, lines: {} };
|
|
22
|
+
const opening = /^\uFEFF?---[ \t]*\r?\n/u.exec(source);
|
|
23
|
+
if (!opening) {
|
|
24
|
+
return plain;
|
|
25
|
+
}
|
|
26
|
+
const remainder = source.slice(opening[0].length);
|
|
27
|
+
const closing = /^---[ \t]*\r?$/mu.exec(remainder);
|
|
28
|
+
if (!closing) {
|
|
29
|
+
return { ...plain, diagnostics: [{ line: 1, message: "Unterminated frontmatter; the original Markdown is shown." }] };
|
|
30
|
+
}
|
|
31
|
+
let end = opening[0].length + closing.index + closing[0].length;
|
|
32
|
+
if (source[end] === "\n") {
|
|
33
|
+
end += 1;
|
|
34
|
+
}
|
|
35
|
+
const raw = source.slice(0, end);
|
|
36
|
+
const body = source.slice(end);
|
|
37
|
+
const bodyLine = raw.split("\n").length;
|
|
38
|
+
const counter = new LineCounter();
|
|
39
|
+
const doc = parseDocument(remainder.slice(0, closing.index), { lineCounter: counter });
|
|
40
|
+
const lines = metadataLocations(doc.contents, counter);
|
|
41
|
+
const diagnostics: Diagnostic[] = [...doc.errors, ...doc.warnings].map((error) => ({
|
|
42
|
+
line: (error.linePos?.[0].line ?? 1) + 1,
|
|
43
|
+
message: error.message,
|
|
44
|
+
}));
|
|
45
|
+
const result = { body, bodyLine, diagnostics, fields: {}, lines, raw };
|
|
46
|
+
if (doc.errors.length > 0 || doc.warnings.length > 0) {
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
let data: unknown;
|
|
50
|
+
try {
|
|
51
|
+
data = doc.toJS({ maxAliasCount: 50 });
|
|
52
|
+
} catch {
|
|
53
|
+
return { ...result, diagnostics: [...diagnostics, { line: 2, message: "Frontmatter aliases could not be expanded safely." }] };
|
|
54
|
+
}
|
|
55
|
+
if (data === null) {
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
if (typeof data !== "object" || Array.isArray(data)) {
|
|
59
|
+
return { ...plain, diagnostics: [{ line: 2, message: "Frontmatter must be a mapping; the original Markdown is shown." }] };
|
|
60
|
+
}
|
|
61
|
+
return { ...result, ...metadataFields(data, lines) };
|
|
62
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { attentionProblems } from "./attentionProblems.ts";
|
|
2
|
+
import type { MetadataDocument } from "./model.ts";
|
|
3
|
+
import type { Diagnostic } from "./parse.ts";
|
|
4
|
+
|
|
5
|
+
function referenceProblem(target: string, ids: ReadonlyMap<string, readonly MetadataDocument[]>): string | undefined {
|
|
6
|
+
const [id = "", criterion] = target.split("#");
|
|
7
|
+
const matches = ids.get(id);
|
|
8
|
+
if (!matches?.length) {
|
|
9
|
+
return `Unresolved reference ${target}.`;
|
|
10
|
+
}
|
|
11
|
+
if (matches.length > 1) {
|
|
12
|
+
return `Ambiguous reference ${target}; the ID is duplicated.`;
|
|
13
|
+
}
|
|
14
|
+
if (criterion && matches[0]?.parsed.fields.criteria?.filter((item) => item.id === criterion).length !== 1) {
|
|
15
|
+
return `Unresolved or duplicated criterion ${target}.`;
|
|
16
|
+
}
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
function criterionProblems(document: MetadataDocument): readonly Diagnostic[] {
|
|
20
|
+
const criteria = document.parsed.fields.criteria ?? [];
|
|
21
|
+
return criteria.flatMap((criterion, index) =>
|
|
22
|
+
criteria.filter((item) => item.id === criterion.id).length > 1
|
|
23
|
+
? [{ field: "criteria", line: document.parsed.lines[`criteria.${index}`] ?? 2, message: `Duplicate criterion ID ${criterion.id}.` }]
|
|
24
|
+
: [],
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
function referenceProblems(document: MetadataDocument, ids: ReadonlyMap<string, readonly MetadataDocument[]>): readonly Diagnostic[] {
|
|
28
|
+
const { fields, lines } = document.parsed;
|
|
29
|
+
const references = [
|
|
30
|
+
...(fields.attention?.flatMap((request, index) =>
|
|
31
|
+
request.unblocks.map((target, targetIndex) => ({ field: "attention", key: `attention.${index}.unblocks.${targetIndex}`, target })),
|
|
32
|
+
) ?? []),
|
|
33
|
+
...(fields.relationships?.map((link, index) => ({ field: "relationships", key: `relationships.${index}.target`, target: link.target })) ?? []),
|
|
34
|
+
...(fields.items?.map((target, index) => ({ field: "items", key: `items.${index}`, target })) ?? []),
|
|
35
|
+
...(fields.evidence?.flatMap((item, index) =>
|
|
36
|
+
item.criterion ? [{ field: "evidence", key: `evidence.${index}.criterion`, target: item.criterion }] : [],
|
|
37
|
+
) ?? []),
|
|
38
|
+
];
|
|
39
|
+
return references.flatMap((reference) => {
|
|
40
|
+
const message = referenceProblem(reference.target, ids);
|
|
41
|
+
return message ? [{ field: reference.field, line: lines[reference.key] ?? lines[reference.field] ?? 2, message }] : [];
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
export function metadataProblems(document: MetadataDocument, ids: ReadonlyMap<string, readonly MetadataDocument[]>, unavailable: readonly string[]) {
|
|
45
|
+
const { parsed } = document;
|
|
46
|
+
const problems = [...parsed.diagnostics, ...criterionProblems(document), ...attentionProblems(document)];
|
|
47
|
+
const matches = parsed.fields.id ? ids.get(parsed.fields.id) : undefined;
|
|
48
|
+
if (matches && matches.length > 1) {
|
|
49
|
+
problems.push({
|
|
50
|
+
field: "id",
|
|
51
|
+
line: parsed.lines.id ?? 2,
|
|
52
|
+
message: `Duplicate ID ${parsed.fields.id}: ${matches.map((item) => item.file).join(", ")}. No source is selected.`,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
if (unavailable.length > 0) {
|
|
56
|
+
if (parsed.raw !== undefined) {
|
|
57
|
+
problems.push({
|
|
58
|
+
line: 1,
|
|
59
|
+
message: `Workspace index incomplete; could not read ${unavailable.join(", ")}. Identity and references cannot be validated completely.`,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
problems.push(...referenceProblems(document, ids));
|
|
64
|
+
}
|
|
65
|
+
return problems;
|
|
66
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const labels = new Map([
|
|
2
|
+
["attention target", "Attention request"],
|
|
3
|
+
["board member", "Board membership"],
|
|
4
|
+
["criterion evidence", "Criterion evidence"],
|
|
5
|
+
["depends_on", "Depends on"],
|
|
6
|
+
["evidence source", "Evidence source"],
|
|
7
|
+
["implements", "Implements"],
|
|
8
|
+
["informs", "Informs"],
|
|
9
|
+
["relates_to", "Related to"],
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
export function relationshipLabel(kind: string): string {
|
|
13
|
+
return labels.get(kind) ?? kind;
|
|
14
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
|
|
3
|
+
export const Identity = Schema.String.check(Schema.isPattern(/^[a-zA-Z\d][a-zA-Z\d._-]*$/u));
|
|
4
|
+
export const Reference = Schema.String.check(Schema.isPattern(/^[a-zA-Z\d][a-zA-Z\d._-]*(?:#[a-zA-Z\d][a-zA-Z\d._-]*)?$/u));
|
|
5
|
+
const Text = Schema.String.check(Schema.isPattern(/\S/u));
|
|
6
|
+
const Attention = Schema.Struct({
|
|
7
|
+
id: Identity,
|
|
8
|
+
kind: Schema.Literals(["decision", "review", "blocker"]),
|
|
9
|
+
reason: Text,
|
|
10
|
+
responseFrom: Schema.Array(Text).check(Schema.isMinLength(1)),
|
|
11
|
+
state: Schema.Literals(["open", "closed"]),
|
|
12
|
+
unblocks: Schema.Array(Reference).check(Schema.isMinLength(1)),
|
|
13
|
+
}).pipe(Schema.encodeKeys({ responseFrom: "response_from" }));
|
|
14
|
+
const Criterion = Schema.Struct({ id: Identity, text: Text });
|
|
15
|
+
const Relationship = Schema.Struct({ kind: Schema.Literals(["implements", "informs", "depends_on", "relates_to"]), target: Reference });
|
|
16
|
+
const Evidence = Schema.Struct({
|
|
17
|
+
checkedRevision: Schema.optional(Schema.String.check(Schema.isPattern(/^(?:[a-f\d]{40}|[a-f\d]{64})$/iu))),
|
|
18
|
+
criterion: Schema.optional(Reference),
|
|
19
|
+
method: Schema.optional(Text),
|
|
20
|
+
observedAt: Schema.optional(
|
|
21
|
+
Text.check(
|
|
22
|
+
Schema.makeFilter(
|
|
23
|
+
(value) =>
|
|
24
|
+
(Number.isFinite(Date.parse(value)) && /^\d{4}-\d{2}-\d{2}T.*(?:Z|[+-]\d{2}:\d{2})$/u.exec(value) !== null)
|
|
25
|
+
|| "Expected an ISO timestamp with timezone",
|
|
26
|
+
),
|
|
27
|
+
),
|
|
28
|
+
),
|
|
29
|
+
outcome: Schema.optional(Text),
|
|
30
|
+
source: Text,
|
|
31
|
+
}).pipe(Schema.encodeKeys({ checkedRevision: "checked_revision", observedAt: "observed_at" }));
|
|
32
|
+
|
|
33
|
+
export const Metadata = Schema.Struct({
|
|
34
|
+
attention: Schema.optional(Schema.Array(Attention)),
|
|
35
|
+
criteria: Schema.optional(Schema.Array(Criterion)),
|
|
36
|
+
evidence: Schema.optional(Schema.Array(Evidence)),
|
|
37
|
+
id: Schema.optional(Identity),
|
|
38
|
+
items: Schema.optional(Schema.Array(Identity)),
|
|
39
|
+
kind: Schema.optional(Schema.Literals(["task", "investigation", "decision", "result", "project", "board"])),
|
|
40
|
+
nextAction: Schema.optional(Text),
|
|
41
|
+
owner: Schema.optional(Text),
|
|
42
|
+
relationships: Schema.optional(Schema.Array(Relationship)),
|
|
43
|
+
status: Schema.optional(Text),
|
|
44
|
+
}).pipe(Schema.encodeKeys({ nextAction: "next_action" }));
|
|
45
|
+
export type Metadata = typeof Metadata.Type;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { fileUrl } from "#files/url.ts";
|
|
2
|
+
import { referenceTarget } from "./links.ts";
|
|
3
|
+
import type { MetadataModel } from "./model.ts";
|
|
4
|
+
|
|
5
|
+
const BASE = "http://work-board.local";
|
|
6
|
+
const absolute = /^(?:[a-z][a-z\d+.-]*:|\/\/)/iu;
|
|
7
|
+
|
|
8
|
+
export function sourceHref(source: string, file: string): string | undefined {
|
|
9
|
+
try {
|
|
10
|
+
const url = new URL(source, `${BASE}${fileUrl(file)}`);
|
|
11
|
+
if (!["http:", "https:"].includes(url.protocol)) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
return absolute.exec(source) === null ? url.pathname + url.search + url.hash : url.href;
|
|
15
|
+
} catch {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function referenceFile(reference: string, model: MetadataModel): string | undefined {
|
|
21
|
+
if (!referenceTarget(reference, model)) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
return model.ids.get(reference.split("#")[0] ?? "")?.[0]?.file;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function sourceFile(source: string, file: string, model: MetadataModel): string | undefined {
|
|
28
|
+
const href = sourceHref(source, file);
|
|
29
|
+
if (!href?.startsWith("/")) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
const url = new URL(href, BASE);
|
|
33
|
+
try {
|
|
34
|
+
const path = decodeURIComponent(url.pathname);
|
|
35
|
+
if (path === "/") {
|
|
36
|
+
return model.rootFile;
|
|
37
|
+
}
|
|
38
|
+
const identity = /^\/_board\/item\/(?<id>[^/]+)\/$/u.exec(path)?.groups?.id;
|
|
39
|
+
if (identity) {
|
|
40
|
+
return referenceFile(identity, model);
|
|
41
|
+
}
|
|
42
|
+
return /\.md$/iu.exec(path) === null ? undefined : path.slice(1);
|
|
43
|
+
} catch {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { attentionSource } from "#attention/requests.ts";
|
|
2
|
+
import type { MetadataModel } from "#metadata/model.ts";
|
|
3
|
+
import type { ParsedMetadata } from "#metadata/parse.ts";
|
|
4
|
+
import { escapeHtml } from "./escape.ts";
|
|
5
|
+
import { referenceHtml } from "./metadataLinks.ts";
|
|
6
|
+
|
|
7
|
+
export function attentionDetails(parsed: ParsedMetadata, file: string, model: MetadataModel): string {
|
|
8
|
+
const requests = parsed.fields.attention ?? [];
|
|
9
|
+
if (requests.length === 0) {
|
|
10
|
+
return "";
|
|
11
|
+
}
|
|
12
|
+
return `<h3>Recorded attention requests</h3><p>Open/closed is source state. Closing a request does not establish acceptance or record an answer.</p><ul>${requests
|
|
13
|
+
.map(
|
|
14
|
+
(request, index) =>
|
|
15
|
+
`<li id="${attentionSource({ file, parsed }, index)}" tabindex="-1"><b>${escapeHtml(request.kind)} · ${escapeHtml(request.id)} · ${escapeHtml(request.state)}</b><p>${escapeHtml(request.reason)}</p><p>Response needed from: ${request.responseFrom.map(escapeHtml).join(", ")}</p><p>Recorded to unblock: ${request.unblocks.map((target) => referenceHtml(target, target, model)).join(", ")}</p><p>Source: ${escapeHtml(file)}:${parsed.lines[`attention.${index}`] ?? parsed.lines.attention ?? 2}</p></li>`,
|
|
16
|
+
)
|
|
17
|
+
.join("")}</ul>`;
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { backlinksOf } from "#metadata/backlinks.ts";
|
|
2
|
+
import type { MetadataModel } from "#metadata/model.ts";
|
|
3
|
+
import { relationshipLabel } from "#metadata/relationshipLabel.ts";
|
|
4
|
+
import { escapeHtml } from "./escape.ts";
|
|
5
|
+
import { documentReference } from "./metadataLinks.ts";
|
|
6
|
+
|
|
7
|
+
export function backlinksHtml(file: string, model: MetadataModel): string {
|
|
8
|
+
const links = backlinksOf(file, model);
|
|
9
|
+
if (links.length === 0) {
|
|
10
|
+
return "";
|
|
11
|
+
}
|
|
12
|
+
const entries = [...new Map(links.map((link) => [`${link.file}:${link.kind}:${link.line ?? ""}`, link])).values()];
|
|
13
|
+
const incomplete =
|
|
14
|
+
model.unavailable.length > 0
|
|
15
|
+
? "<p>Available source references only; unreadable files prevent complete backlink and identity validation.</p>"
|
|
16
|
+
: "";
|
|
17
|
+
return `<section class="work-context" aria-label="Source backlinks"><details data-key="work-backlinks"><summary>Referenced by (${entries.length})</summary>${incomplete}<ul>${entries.map((link) => `<li>${escapeHtml(relationshipLabel(link.kind))}: <a href="${escapeHtml(documentReference(link.file, model))}">${escapeHtml(link.file)}${link.line === undefined ? "" : `:${link.line}`}</a></li>`).join("")}</ul><p>These are explicit source links, not inferred blockers or workflow rules.</p></details></section>`;
|
|
18
|
+
}
|
package/src/page/board.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import { type Board, countsOf, type Section } from "#render/board.ts";
|
|
3
|
+
import { documentHeadings } from "#render/documentHeadings.ts";
|
|
3
4
|
import { renderMarkdown } from "#render/markdown.ts";
|
|
4
5
|
import { escapeHtml } from "./escape.ts";
|
|
6
|
+
import { readingTools } from "./readingTools.ts";
|
|
5
7
|
import { updated } from "./updated.ts";
|
|
6
8
|
|
|
7
9
|
const counts = (board: Board): string =>
|
|
@@ -12,22 +14,30 @@ const counts = (board: Board): string =>
|
|
|
12
14
|
type Render = (fragment: string) => ReturnType<typeof renderMarkdown>;
|
|
13
15
|
|
|
14
16
|
const section = Effect.fn("WorkBoard.section")(function* ({ heading, items, notes }: Section, render: Render) {
|
|
15
|
-
const
|
|
17
|
+
const title = yield* render(heading);
|
|
16
18
|
const note = notes === "" ? "" : `<div class="notes">${yield* render(notes)}</div>`;
|
|
17
|
-
|
|
19
|
+
const rendered = yield* Effect.forEach(items, (item) => Effect.map(render(item), (html) => `<article class="item">${html}</article>`));
|
|
20
|
+
return `<section>${title}${note}${rendered.join("")}</section>`;
|
|
18
21
|
});
|
|
19
22
|
|
|
20
|
-
export const boardHtml = Effect.fn("WorkBoard.boardHtml")(function* (board: Board, fallbackTitle: string, modified: number) {
|
|
23
|
+
export const boardHtml = Effect.fn("WorkBoard.boardHtml")(function* (board: Board, fallbackTitle: string, modified: number, metadata = "") {
|
|
21
24
|
let fragments = 0;
|
|
25
|
+
const headings = documentHeadings();
|
|
22
26
|
const render: Render = (fragment) => {
|
|
23
27
|
fragments += 1;
|
|
24
|
-
return renderMarkdown(board.definitions === "" ? fragment : `${fragment}\n\n${board.definitions}`,
|
|
28
|
+
return renderMarkdown(board.definitions === "" ? fragment : `${fragment}\n\n${board.definitions}`, {
|
|
29
|
+
file: fallbackTitle,
|
|
30
|
+
headings,
|
|
31
|
+
idPrefix: `part-${fragments}-`,
|
|
32
|
+
});
|
|
25
33
|
};
|
|
26
34
|
const title = board.title === "" ? `<h1>${escapeHtml(fallbackTitle)}</h1>` : yield* render(board.title);
|
|
27
35
|
const intro = board.intro === "" ? "" : `<div class="intro">${yield* render(board.intro)}</div>`;
|
|
28
36
|
const sections = yield* Effect.forEach(board.sections, (each) => section(each, render));
|
|
29
37
|
const footer = board.footer === "" ? "" : `<footer>${yield* render(board.footer)}</footer>`;
|
|
30
38
|
return [
|
|
39
|
+
readingTools(headings.entries),
|
|
40
|
+
metadata,
|
|
31
41
|
`<header class="board-head">${title}${intro}`,
|
|
32
42
|
`<p class="counts">${counts(board)}</p>`,
|
|
33
43
|
`<p class="meta">${updated(modified)}</p></header>`,
|
package/src/page/client.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
export const client = `
|
|
2
|
+
import "/_board/saved-views.js";
|
|
3
|
+
import "/_board/history.js";
|
|
4
|
+
import "/_board/preferences.js";
|
|
5
|
+
import "/_board/search.js";
|
|
2
6
|
import { renderDiagrams } from "/_board/diagrams.js";
|
|
3
|
-
import {
|
|
7
|
+
import { applyPage } from "/_board/page-state.js";
|
|
8
|
+
import { pageVersion } from "/_board/navigation.js";
|
|
9
|
+
import { remember } from "/_board/swap.js";
|
|
4
10
|
|
|
5
11
|
const since = (modified) => {
|
|
6
12
|
const seconds = Math.max(0, Math.round((Date.now() - modified) / 1000));
|
|
@@ -32,22 +38,23 @@ const show = () => {
|
|
|
32
38
|
};
|
|
33
39
|
|
|
34
40
|
const load = async () => {
|
|
41
|
+
const version = pageVersion();
|
|
42
|
+
const path = location.pathname + location.search;
|
|
35
43
|
try {
|
|
36
|
-
const response = await fetch(
|
|
44
|
+
const response = await fetch(path, { cache: "no-store" });
|
|
37
45
|
const next = new DOMParser().parseFromString(await response.text(), "text/html");
|
|
38
46
|
const files = next.getElementById("files");
|
|
39
47
|
const doc = next.getElementById("doc");
|
|
48
|
+
if (version !== pageVersion()) return;
|
|
40
49
|
if (files === null || doc === null) {
|
|
41
50
|
failure = "refresh failed (" + response.status + ")";
|
|
42
51
|
return;
|
|
43
52
|
}
|
|
44
53
|
failure = "";
|
|
45
|
-
|
|
46
|
-
swap(document.getElementById("doc"), doc);
|
|
47
|
-
document.title = next.title;
|
|
54
|
+
applyPage(next, true);
|
|
48
55
|
tick();
|
|
49
|
-
renderDiagrams(document);
|
|
50
56
|
} catch {
|
|
57
|
+
if (version !== pageVersion()) return;
|
|
51
58
|
failure = "refresh failed";
|
|
52
59
|
}
|
|
53
60
|
};
|
|
@@ -71,6 +78,7 @@ const refresh = async () => {
|
|
|
71
78
|
const events = new EventSource("/events");
|
|
72
79
|
events.addEventListener("ready", () => {
|
|
73
80
|
connected = true;
|
|
81
|
+
document.dispatchEvent(new Event("board-index-change"));
|
|
74
82
|
show();
|
|
75
83
|
if (dropped) refresh();
|
|
76
84
|
dropped = false;
|
|
@@ -80,11 +88,62 @@ const down = () => {
|
|
|
80
88
|
dropped = true;
|
|
81
89
|
show();
|
|
82
90
|
};
|
|
83
|
-
events.addEventListener("change", refresh);
|
|
91
|
+
events.addEventListener("change", () => { refresh(); document.dispatchEvent(new Event("board-index-change")); });
|
|
84
92
|
events.addEventListener("down", down);
|
|
85
93
|
events.addEventListener("error", down);
|
|
86
94
|
|
|
95
|
+
document.addEventListener("board-page", tick);
|
|
87
96
|
tick();
|
|
88
97
|
setInterval(tick, 5000);
|
|
89
98
|
renderDiagrams(document);
|
|
90
99
|
`;
|
|
100
|
+
export const preferences = `
|
|
101
|
+
const root = document.documentElement;
|
|
102
|
+
const key = "work-board:appearance:" + root.dataset.workspace;
|
|
103
|
+
const theme = document.getElementById("theme");
|
|
104
|
+
const density = document.getElementById("density");
|
|
105
|
+
const sidebar = document.getElementById("sidebar");
|
|
106
|
+
const toggle = document.getElementById("sidebar-toggle");
|
|
107
|
+
const status = document.getElementById("preference-status");
|
|
108
|
+
const system = matchMedia("(prefers-color-scheme: dark)");
|
|
109
|
+
let saved = {};
|
|
110
|
+
try {
|
|
111
|
+
const value = JSON.parse(localStorage.getItem(key) ?? "{}");
|
|
112
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) saved = value;
|
|
113
|
+
} catch {
|
|
114
|
+
status.textContent = "Preferences apply to this page only.";
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
theme.value = ["light", "dark"].includes(saved.theme) ? saved.theme : "system";
|
|
118
|
+
density.value = saved.density === "compact" ? "compact" : "comfortable";
|
|
119
|
+
let closed = typeof saved.closed === "boolean" ? saved.closed : matchMedia("(max-width: 760px)").matches;
|
|
120
|
+
|
|
121
|
+
const appearance = () => {
|
|
122
|
+
root.dataset.theme = theme.value;
|
|
123
|
+
root.dataset.scheme = theme.value === "system" ? (system.matches ? "dark" : "light") : theme.value;
|
|
124
|
+
root.dataset.density = density.value;
|
|
125
|
+
root.dataset.sidebar = closed ? "closed" : "open";
|
|
126
|
+
sidebar.hidden = closed;
|
|
127
|
+
toggle.setAttribute("aria-expanded", String(!closed));
|
|
128
|
+
document.dispatchEvent(new Event("board-theme"));
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const save = () => {
|
|
132
|
+
appearance();
|
|
133
|
+
try {
|
|
134
|
+
localStorage.setItem(key, JSON.stringify({ theme: theme.value, density: density.value, closed }));
|
|
135
|
+
status.textContent = "";
|
|
136
|
+
} catch {
|
|
137
|
+
status.textContent = "Preferences apply to this page only.";
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
theme.addEventListener("change", save);
|
|
142
|
+
density.addEventListener("change", save);
|
|
143
|
+
toggle.addEventListener("click", () => {
|
|
144
|
+
closed = !closed;
|
|
145
|
+
save();
|
|
146
|
+
});
|
|
147
|
+
system.addEventListener("change", appearance);
|
|
148
|
+
appearance();
|
|
149
|
+
`;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { referenceTarget } from "#metadata/links.ts";
|
|
2
|
+
import type { MetadataModel } from "#metadata/model.ts";
|
|
3
|
+
import type { ParsedMetadata } from "#metadata/parse.ts";
|
|
4
|
+
import { escapeHtml } from "./escape.ts";
|
|
5
|
+
import { documentReference } from "./metadataLinks.ts";
|
|
6
|
+
|
|
7
|
+
function evidenceFor(reference: string, model: MetadataModel): string {
|
|
8
|
+
const records = model.documents.flatMap(
|
|
9
|
+
(document) =>
|
|
10
|
+
document.parsed.fields.evidence?.flatMap((evidence, index) =>
|
|
11
|
+
evidence.criterion === reference ? [{ file: document.file, index, line: document.parsed.lines[`evidence.${index}.source`] }] : [],
|
|
12
|
+
) ?? [],
|
|
13
|
+
);
|
|
14
|
+
if (records.length === 0) {
|
|
15
|
+
return "<p>No recorded evidence naming this criterion. Acceptance is not established.</p>";
|
|
16
|
+
}
|
|
17
|
+
return `<p>${records.length} recorded ${records.length === 1 ? "claim" : "claims"}, not verified acceptance:</p><ul>${records.map((record) => `<li><a href="${escapeHtml(documentReference(record.file, model, `#recorded-evidence-${record.index}`))}">${escapeHtml(record.file)}${record.line === undefined ? "" : `:${record.line}`} — open recorded evidence</a></li>`).join("")}</ul>`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function criteriaEvidence(parsed: ParsedMetadata, model: MetadataModel): string {
|
|
21
|
+
const criteria = parsed.fields.criteria ?? [];
|
|
22
|
+
if (criteria.length === 0) {
|
|
23
|
+
return "";
|
|
24
|
+
}
|
|
25
|
+
return `<h3>Criteria and recorded evidence</h3><ul>${criteria
|
|
26
|
+
.map((criterion) => {
|
|
27
|
+
const unique = criteria.filter((candidate) => candidate.id === criterion.id).length === 1;
|
|
28
|
+
const reference = parsed.fields.id ? `${parsed.fields.id}#${criterion.id}` : "";
|
|
29
|
+
const evidence =
|
|
30
|
+
reference && referenceTarget(reference, model)
|
|
31
|
+
? evidenceFor(reference, model)
|
|
32
|
+
: "<p>Evidence association requires unique item/criterion IDs and a readable workspace. Acceptance is not established.</p>";
|
|
33
|
+
return `<li${unique ? ` id="criterion-${escapeHtml(criterion.id)}" tabindex="-1"` : ""}><b>${escapeHtml(criterion.id)}</b>: ${escapeHtml(criterion.text)}${evidence}</li>`;
|
|
34
|
+
})
|
|
35
|
+
.join("")}</ul>`;
|
|
36
|
+
}
|
package/src/page/diagrams.ts
CHANGED
|
@@ -13,7 +13,7 @@ const load = async () => {
|
|
|
13
13
|
securityLevel: "strict",
|
|
14
14
|
theme: "base",
|
|
15
15
|
themeVariables: {
|
|
16
|
-
darkMode: dark
|
|
16
|
+
darkMode: schemeNow() === "dark",
|
|
17
17
|
background: token("--bg"),
|
|
18
18
|
primaryColor: token("--surface"),
|
|
19
19
|
primaryTextColor: token("--text"),
|
|
@@ -26,7 +26,8 @@ const load = async () => {
|
|
|
26
26
|
return mermaid;
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
-
const schemeNow = () => (dark.matches ? "dark" : "light");
|
|
29
|
+
const schemeNow = () => document.documentElement.dataset.scheme ?? (dark.matches ? "dark" : "light");
|
|
30
|
+
let lastScheme = schemeNow();
|
|
30
31
|
|
|
31
32
|
const draw = async (figure) => {
|
|
32
33
|
const source = figure.querySelector(".diagram-source").textContent;
|
|
@@ -65,8 +66,15 @@ export const renderDiagrams = (root, selector = "figure.diagram:not([data-state=
|
|
|
65
66
|
return Promise.all([...root.querySelectorAll(selector)].map(draw));
|
|
66
67
|
};
|
|
67
68
|
|
|
68
|
-
|
|
69
|
+
const themeChanged = () => {
|
|
70
|
+
const scheme = schemeNow();
|
|
71
|
+
if (lastScheme === scheme) return;
|
|
72
|
+
lastScheme = scheme;
|
|
73
|
+
const doc = document.getElementById("doc");
|
|
74
|
+
if (doc) doc.dataset.scheme = scheme;
|
|
69
75
|
mermaid = undefined;
|
|
70
76
|
renderDiagrams(document, "figure.diagram");
|
|
71
|
-
}
|
|
77
|
+
};
|
|
78
|
+
document.addEventListener("board-theme", themeChanged);
|
|
79
|
+
dark.addEventListener("change", themeChanged);
|
|
72
80
|
`;
|
package/src/page/document.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import type { MarkdownFile } from "#files/list.ts";
|
|
3
|
+
import { documentHeadings } from "#render/documentHeadings.ts";
|
|
3
4
|
import { renderMarkdown } from "#render/markdown.ts";
|
|
4
5
|
import { escapeHtml } from "./escape.ts";
|
|
6
|
+
import { readingTools } from "./readingTools.ts";
|
|
5
7
|
import { updated } from "./updated.ts";
|
|
6
8
|
|
|
7
|
-
export const documentHtml = Effect.fn("WorkBoard.documentHtml")(function* (source: string, file: MarkdownFile) {
|
|
8
|
-
const
|
|
9
|
-
|
|
9
|
+
export const documentHtml = Effect.fn("WorkBoard.documentHtml")(function* (source: string, file: MarkdownFile, metadata = "") {
|
|
10
|
+
const headings = documentHeadings();
|
|
11
|
+
const html = yield* renderMarkdown(source, { file: file.path, headings });
|
|
12
|
+
return `${readingTools(headings.entries)}<p class="meta">${escapeHtml(file.path)} · ${updated(file.modified)}</p>${metadata}<article class="doc">${html}</article>`;
|
|
10
13
|
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export function libraryScript(): string {
|
|
2
|
+
return `
|
|
3
|
+
const root = document.documentElement;
|
|
4
|
+
const key = "work-board:library:" + root.dataset.workspace;
|
|
5
|
+
const library = document.getElementById("reading-library");
|
|
6
|
+
let favorites = [];
|
|
7
|
+
let recents = [];
|
|
8
|
+
let notice = "";
|
|
9
|
+
const valid = (items) => Array.isArray(items) ? items.filter((item) => item && typeof item.file === "string" && item.file !== "" && typeof item.title === "string") : [];
|
|
10
|
+
try {
|
|
11
|
+
const saved = JSON.parse(localStorage.getItem(key) ?? "{}");
|
|
12
|
+
favorites = valid(saved?.favorites);
|
|
13
|
+
recents = valid(saved?.recents);
|
|
14
|
+
} catch { notice = "Reading history applies to this page only."; }
|
|
15
|
+
const save = () => {
|
|
16
|
+
try { localStorage.setItem(key, JSON.stringify({ favorites, recents })); notice = ""; }
|
|
17
|
+
catch { notice = "Reading history applies to this page only."; }
|
|
18
|
+
};
|
|
19
|
+
const hrefOf = (file) => "/" + file.split("/").map(encodeURIComponent).join("/");
|
|
20
|
+
const list = (title, items) => {
|
|
21
|
+
const heading = document.createElement("h2");
|
|
22
|
+
heading.className = "nav-heading";
|
|
23
|
+
heading.textContent = title;
|
|
24
|
+
const ul = document.createElement("ul");
|
|
25
|
+
const available = new Set([...document.querySelectorAll("#files a")].map((a) => new URL(a.href).pathname));
|
|
26
|
+
for (const item of items) {
|
|
27
|
+
const li = document.createElement("li");
|
|
28
|
+
const a = document.createElement("a");
|
|
29
|
+
a.href = hrefOf(item.file);
|
|
30
|
+
a.textContent = item.title;
|
|
31
|
+
if (item.file === document.getElementById("doc").dataset.file) a.setAttribute("aria-current", "page");
|
|
32
|
+
if (!available.has(a.pathname)) {
|
|
33
|
+
a.textContent += " (missing)";
|
|
34
|
+
a.className = "missing";
|
|
35
|
+
}
|
|
36
|
+
li.append(a); ul.append(li);
|
|
37
|
+
}
|
|
38
|
+
return [heading, ul];
|
|
39
|
+
};
|
|
40
|
+
export const refreshLibrary = (visit = false) => {
|
|
41
|
+
const doc = document.getElementById("doc");
|
|
42
|
+
const title = doc.querySelector("h1")?.textContent.trim() || doc.dataset.file;
|
|
43
|
+
document.title = title ? title + " · Work Board" : "Work Board";
|
|
44
|
+
const active = document.querySelector('#files a[aria-current="page"]');
|
|
45
|
+
if (active) active.title = title;
|
|
46
|
+
const toggle = document.getElementById("favorite-toggle");
|
|
47
|
+
if (toggle) {
|
|
48
|
+
const saved = favorites.some((item) => item.file === doc.dataset.file);
|
|
49
|
+
toggle.disabled = false;
|
|
50
|
+
toggle.setAttribute("aria-pressed", String(saved));
|
|
51
|
+
toggle.textContent = saved ? "Remove favorite" : "Save favorite";
|
|
52
|
+
if (visit) {
|
|
53
|
+
recents = [{ file: doc.dataset.file, title }, ...recents.filter((item) => item.file !== doc.dataset.file)].slice(0, 10);
|
|
54
|
+
save();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const status = document.createElement("p");
|
|
58
|
+
status.className = "library-status";
|
|
59
|
+
status.setAttribute("role", "status");
|
|
60
|
+
status.textContent = notice;
|
|
61
|
+
library.replaceChildren(...(favorites.length ? list("Favorites", favorites) : []), ...(recents.length ? list("Recent documents", recents) : []), ...(notice ? [status] : []));
|
|
62
|
+
library.hidden = favorites.length === 0 && recents.length === 0 && notice === "";
|
|
63
|
+
};
|
|
64
|
+
document.addEventListener("click", (event) => {
|
|
65
|
+
if (!event.target.closest?.("#favorite-toggle")) return;
|
|
66
|
+
const doc = document.getElementById("doc");
|
|
67
|
+
const file = doc.dataset.file;
|
|
68
|
+
favorites = favorites.some((item) => item.file === file) ? favorites.filter((item) => item.file !== file) : [...favorites, { file, title: doc.querySelector("h1")?.textContent.trim() || file }];
|
|
69
|
+
save(); refreshLibrary();
|
|
70
|
+
});
|
|
71
|
+
`;
|
|
72
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { identityUrl } from "#metadata/links.ts";
|
|
2
|
+
import type { MetadataModel } from "#metadata/model.ts";
|
|
3
|
+
import type { ParsedMetadata } from "#metadata/parse.ts";
|
|
4
|
+
import { stateOf, viewUrl } from "#views/state.ts";
|
|
5
|
+
import { backlinksHtml } from "./backlinksHtml.ts";
|
|
6
|
+
import { escapeHtml } from "./escape.ts";
|
|
7
|
+
import { metadataDetails } from "./metadataDetails.ts";
|
|
8
|
+
|
|
9
|
+
export function metadataHtml(parsed: ParsedMetadata, file: string, model: MetadataModel): string {
|
|
10
|
+
const backlinks = backlinksHtml(file, model);
|
|
11
|
+
const diagnostics = model.diagnostics.get(file) ?? parsed.diagnostics;
|
|
12
|
+
if (parsed.raw === undefined && diagnostics.length === 0) {
|
|
13
|
+
return backlinks;
|
|
14
|
+
}
|
|
15
|
+
const fields = parsed.fields;
|
|
16
|
+
const unique = model.unavailable.length === 0 && fields.id !== undefined && model.ids.get(fields.id)?.length === 1;
|
|
17
|
+
const problems =
|
|
18
|
+
diagnostics.length > 0
|
|
19
|
+
? `<div class="metadata-problems" role="status"><b>Source diagnostics</b><ul>${diagnostics.map((problem) => `<li>${escapeHtml(file)}:${problem.line} — ${escapeHtml(problem.message)}</li>`).join("")}</ul></div>`
|
|
20
|
+
: "";
|
|
21
|
+
const identity =
|
|
22
|
+
unique && fields.id
|
|
23
|
+
? `<a href="${identityUrl(fields.id)}">Item link: ${escapeHtml(fields.id)}</a>`
|
|
24
|
+
: `ID: ${escapeHtml(fields.id ?? "Not recorded")}`;
|
|
25
|
+
const details = metadataDetails(parsed, file, model);
|
|
26
|
+
const boardLink =
|
|
27
|
+
unique && fields.kind === "board" && fields.id
|
|
28
|
+
? `<p><a href="${escapeHtml(viewUrl(stateOf("/"), { board: fields.id }))}">View this board</a></p>`
|
|
29
|
+
: "";
|
|
30
|
+
return `<section class="work-meta" aria-label="Recorded work"><p class="metadata-summary">Recorded work · ${identity}${fields.status ? ` · ${escapeHtml(fields.status)}` : ""}${fields.owner ? ` · ${escapeHtml(fields.owner)}` : ""}</p>${fields.nextAction ? `<p>Next action: ${escapeHtml(fields.nextAction)}</p>` : ""}${boardLink}${problems}<details data-key="work-metadata"><summary>Work details and source</summary>${details}<p>Source: ${escapeHtml(file)}:${parsed.lines.id ?? 1}; prose starts at line ${parsed.bodyLine}.</p>${parsed.raw ? `<details data-key="frontmatter-source"><summary>Original frontmatter</summary><pre>${escapeHtml(parsed.raw)}</pre></details>` : ""}</details></section>${backlinks}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const metadataStyles = `
|
|
34
|
+
.work-meta{font-size:.9rem;margin:16px 0 24px;padding:12px 16px;border:1px solid var(--border);border-radius:8px;background:var(--surface);overflow-wrap:anywhere}
|
|
35
|
+
.metadata-summary{font-weight:600;margin:0}.work-meta>p{margin-bottom:8px}.work-meta details{margin:0}.work-meta summary{color:var(--link)}
|
|
36
|
+
.work-meta dl{display:grid;grid-template-columns:minmax(80px,auto) minmax(0,1fr);gap:4px 12px;margin:12px 0}.work-meta dt{color:var(--muted)}.work-meta dd{margin:0}
|
|
37
|
+
.work-meta pre{max-height:300px;overflow:auto;margin-top:12px}.metadata-problems{color:var(--danger);margin:12px 0}.metadata-problems ul{margin:4px 0 12px;padding-left:20px}
|
|
38
|
+
.work-context{font-size:.9rem;margin:12px 0 24px;padding:12px 16px;border:1px solid var(--border);border-radius:8px;overflow-wrap:anywhere}.work-context details{margin:0}.work-context ul{margin:12px 0}.work-context p{font-size:13px;color:var(--muted);margin:8px 0 0}
|
|
39
|
+
[id^="recorded-evidence-"],[id^="criterion-"]{scroll-margin-top:20px}
|
|
40
|
+
`;
|