structural-codes-viewer 0.1.0-alpha.1 → 0.1.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +501 -0
- package/NOTICE +16 -0
- package/README.md +119 -63
- package/package-dist/CorpusContent.d.ts +44 -9
- package/package-dist/CorpusContent.js +452 -26
- package/package-dist/CorpusContent.js.map +1 -1
- package/package-dist/NormativeViewer.d.ts +23 -2
- package/package-dist/NormativeViewer.js +1699 -234
- package/package-dist/NormativeViewer.js.map +1 -1
- package/package-dist/ReferenceTools.d.ts +49 -0
- package/package-dist/ReferenceTools.js +14 -0
- package/package-dist/ReferenceTools.js.map +1 -0
- package/package-dist/annotations/AnnotationUi.d.ts +57 -0
- package/package-dist/annotations/AnnotationUi.js +114 -0
- package/package-dist/annotations/AnnotationUi.js.map +1 -0
- package/package-dist/annotations/annotationTargets.d.ts +39 -0
- package/package-dist/annotations/annotationTargets.js +94 -0
- package/package-dist/annotations/annotationTargets.js.map +1 -0
- package/package-dist/annotations/exportImport.d.ts +6 -0
- package/package-dist/annotations/exportImport.js +30 -0
- package/package-dist/annotations/exportImport.js.map +1 -0
- package/package-dist/annotations/index.d.ts +4 -0
- package/package-dist/annotations/index.js +5 -0
- package/package-dist/annotations/index.js.map +1 -0
- package/package-dist/annotations/indexedDb.d.ts +21 -0
- package/package-dist/annotations/indexedDb.js +210 -0
- package/package-dist/annotations/indexedDb.js.map +1 -0
- package/package-dist/annotations/schema.d.ts +8 -0
- package/package-dist/annotations/schema.js +102 -0
- package/package-dist/annotations/schema.js.map +1 -0
- package/package-dist/annotations/types.d.ts +52 -0
- package/package-dist/annotations/types.js +18 -0
- package/package-dist/annotations/types.js.map +1 -0
- package/package-dist/chatntc/canonicalization.d.ts +15 -0
- package/package-dist/chatntc/canonicalization.js +107 -0
- package/package-dist/chatntc/canonicalization.js.map +1 -0
- package/package-dist/chatntc/directives.d.ts +2 -0
- package/package-dist/chatntc/directives.js +38 -0
- package/package-dist/chatntc/directives.js.map +1 -0
- package/package-dist/chatntc/evidence.d.ts +17 -0
- package/package-dist/chatntc/evidence.js +116 -0
- package/package-dist/chatntc/evidence.js.map +1 -0
- package/package-dist/chatntc/index.d.ts +10 -0
- package/package-dist/chatntc/index.js +9 -0
- package/package-dist/chatntc/index.js.map +1 -0
- package/package-dist/chatntc/issueCategories.d.ts +2 -0
- package/package-dist/chatntc/issueCategories.js +11 -0
- package/package-dist/chatntc/issueCategories.js.map +1 -0
- package/package-dist/chatntc/policy.d.ts +35 -0
- package/package-dist/chatntc/policy.js +36 -0
- package/package-dist/chatntc/policy.js.map +1 -0
- package/package-dist/chatntc/provider.d.ts +42 -0
- package/package-dist/chatntc/provider.js +2 -0
- package/package-dist/chatntc/provider.js.map +1 -0
- package/package-dist/chatntc/responseContract.d.ts +52 -0
- package/package-dist/chatntc/responseContract.js +73 -0
- package/package-dist/chatntc/responseContract.js.map +1 -0
- package/package-dist/chatntc/retrieval.d.ts +5 -0
- package/package-dist/chatntc/retrieval.js +220 -0
- package/package-dist/chatntc/retrieval.js.map +1 -0
- package/package-dist/chatntc/types.d.ts +240 -0
- package/package-dist/chatntc/types.js +2 -0
- package/package-dist/chatntc/types.js.map +1 -0
- package/package-dist/chatntc/validation.d.ts +8 -0
- package/package-dist/chatntc/validation.js +299 -0
- package/package-dist/chatntc/validation.js.map +1 -0
- package/package-dist/chatntc/viewerArtifacts.d.ts +14 -0
- package/package-dist/chatntc/viewerArtifacts.js +168 -0
- package/package-dist/chatntc/viewerArtifacts.js.map +1 -0
- package/package-dist/chatntc-history/index.d.ts +2 -0
- package/package-dist/chatntc-history/index.js +3 -0
- package/package-dist/chatntc-history/index.js.map +1 -0
- package/package-dist/chatntc-history/indexedDb.d.ts +27 -0
- package/package-dist/chatntc-history/indexedDb.js +258 -0
- package/package-dist/chatntc-history/indexedDb.js.map +1 -0
- package/package-dist/chatntc-history/schema.d.ts +5 -0
- package/package-dist/chatntc-history/schema.js +102 -0
- package/package-dist/chatntc-history/schema.js.map +1 -0
- package/package-dist/chatntc-history/types.d.ts +78 -0
- package/package-dist/chatntc-history/types.js +23 -0
- package/package-dist/chatntc-history/types.js.map +1 -0
- package/package-dist/chatntc-ui/ChatNTCHistoryPanel.d.ts +9 -0
- package/package-dist/chatntc-ui/ChatNTCHistoryPanel.js +239 -0
- package/package-dist/chatntc-ui/ChatNTCHistoryPanel.js.map +1 -0
- package/package-dist/chatntc-ui/ChatNTCIcons.d.ts +10 -0
- package/package-dist/chatntc-ui/ChatNTCIcons.js +20 -0
- package/package-dist/chatntc-ui/ChatNTCIcons.js.map +1 -0
- package/package-dist/chatntc-ui/ChatNTCLoadingSkeleton.d.ts +1 -0
- package/package-dist/chatntc-ui/ChatNTCLoadingSkeleton.js +5 -0
- package/package-dist/chatntc-ui/ChatNTCLoadingSkeleton.js.map +1 -0
- package/package-dist/chatntc-ui/ChatNTCMarkdown.d.ts +14 -0
- package/package-dist/chatntc-ui/ChatNTCMarkdown.js +112 -0
- package/package-dist/chatntc-ui/ChatNTCMarkdown.js.map +1 -0
- package/package-dist/chatntc-ui/ChatNTCPanel.d.ts +29 -0
- package/package-dist/chatntc-ui/ChatNTCPanel.js +241 -0
- package/package-dist/chatntc-ui/ChatNTCPanel.js.map +1 -0
- package/package-dist/chatntc-ui/historyMessages.d.ts +5 -0
- package/package-dist/chatntc-ui/historyMessages.js +53 -0
- package/package-dist/chatntc-ui/historyMessages.js.map +1 -0
- package/package-dist/chatntc-ui/index.d.ts +3 -0
- package/package-dist/chatntc-ui/index.js +4 -0
- package/package-dist/chatntc-ui/index.js.map +1 -0
- package/package-dist/chatntc-ui/transport.d.ts +51 -0
- package/package-dist/chatntc-ui/transport.js +9 -0
- package/package-dist/chatntc-ui/transport.js.map +1 -0
- package/package-dist/chunkNavigation.d.ts +12 -0
- package/package-dist/chunkNavigation.js +44 -0
- package/package-dist/chunkNavigation.js.map +1 -0
- package/package-dist/corpusData.d.ts +176 -33
- package/package-dist/corpusData.js +52 -0
- package/package-dist/corpusData.js.map +1 -1
- package/package-dist/crossReferences.d.ts +46 -0
- package/package-dist/crossReferences.js +198 -0
- package/package-dist/crossReferences.js.map +1 -0
- package/package-dist/generate-artifacts.mjs +47 -12
- package/package-dist/globalScrubber.d.ts +10 -0
- package/package-dist/globalScrubber.js +85 -0
- package/package-dist/globalScrubber.js.map +1 -0
- package/package-dist/permalinks.d.ts +22 -0
- package/package-dist/permalinks.js +36 -0
- package/package-dist/permalinks.js.map +1 -0
- package/package-dist/searchClient.d.ts +22 -0
- package/package-dist/searchClient.js +133 -0
- package/package-dist/searchClient.js.map +1 -0
- package/package-dist/searchEngine.d.ts +68 -0
- package/package-dist/searchEngine.js +558 -0
- package/package-dist/searchEngine.js.map +1 -0
- package/package-dist/searchWorker.d.ts +1 -0
- package/package-dist/searchWorker.js +32 -0
- package/package-dist/searchWorker.js.map +1 -0
- package/package-dist/styles.css +927 -182
- package/package-dist/tableCaptions.d.mts +3 -0
- package/package-dist/tableCaptions.mjs +35 -0
- package/package-dist/tableCaptions.mjs.map +1 -0
- package/package.json +61 -15
|
@@ -1,37 +1,219 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
-
import { documentForMode, loadChunk, loadDocumentIndex, loadManifest, loadRelations,
|
|
5
|
-
import { BlockContent, isRepeatedUnitTitle } from "./CorpusContent";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { memo, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { adjacentChunkPaths, createDocumentLookup, documentForMode, initialUnitForIndex, loadChunk, loadCrossReferenceIndex, loadDocumentIndex, loadManifest, loadRelations, } from "./corpusData";
|
|
5
|
+
import { AlignedLabelList, BlockContent, groupAlignedLabelBlocks, hasAlphabeticListMarker, hasAlphaRatioListLayout, hasInferredAlphaRatioListMarker, hasLeadingEmphasisLabel, hasLeadingMath, hasNoListMarker, hasOfficialListMarker, hasSimpleDashMarker, hasTrailingMath, hasTrailingStrong, indentLevelClass, isRepeatedUnitTitle, listLevelClass, listMarkerClass, renderInlineSegments } from "./CorpusContent";
|
|
6
|
+
import { useViewerSearch } from "./searchClient";
|
|
7
|
+
import { createCrossReferenceLookup, resolveCrossReference } from "./crossReferences.js";
|
|
8
|
+
import { ReferencePreview } from "./ReferenceTools";
|
|
9
|
+
import { targetFromUrl, urlForViewerTarget } from "./permalinks";
|
|
10
|
+
import { isChunkNearRenderedWindow, navigationChunkWindow, progressiveChunkTargetsForVisibleUnit } from "./chunkNavigation.js";
|
|
11
|
+
import { buildGlobalScrubberEntries, createGlobalScrubberDragSession, globalScrubberKeyboardIndex, globalScrubberRatioForId, resolveGlobalScrubberActiveId } from "./globalScrubber.js";
|
|
12
|
+
import { AnnotationContextMenu, AnnotationEditor, AnnotationMarginMarker } from "./annotations/AnnotationUi";
|
|
13
|
+
import { annotationPositionRanks, annotationsForTarget, annotationTargetFromElement, automaticAnnotationLabel, clusterAnnotationMarkers, createLongPressSession } from "./annotations/annotationTargets";
|
|
14
|
+
import { annotationTargetKey } from "./annotations/schema";
|
|
6
15
|
const modeOptions = [
|
|
7
|
-
{ id: "combined", label: "NTC 2018 + Circolare 7/2019" },
|
|
8
16
|
{ id: "ntc", label: "Solo NTC 2018" },
|
|
9
17
|
{ id: "circ", label: "Solo Circolare 7/2019" },
|
|
18
|
+
{ id: "combined", label: "NTC 2018 + Circolare 7/2019" },
|
|
10
19
|
];
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
20
|
+
const ModeSegmentedControl = memo(function ModeSegmentedControl({ mode, onChange }) {
|
|
21
|
+
const tooltipBaseId = useId();
|
|
22
|
+
return _jsx("div", { className: "scv-mode-switch", role: "group", "aria-label": "Modalit\u00E0 documento", children: modeOptions.map((option) => {
|
|
23
|
+
const tooltipId = `${tooltipBaseId}-${option.id}`;
|
|
24
|
+
return _jsxs("button", { type: "button", className: `scv-mode-button ${mode === option.id ? "active" : ""}`, onClick: () => onChange(option.id), "aria-label": option.label, "aria-describedby": tooltipId, "aria-pressed": mode === option.id, children: [_jsx("span", { className: "scv-mode-label", children: option.id === "ntc" ? _jsxs(_Fragment, { children: ["NTC", _jsx("br", {}), "2018"] }) : option.id === "circ" ? _jsxs(_Fragment, { children: ["CIRC.", _jsx("br", {}), "2019"] }) : _jsxs(_Fragment, { children: ["NTC", _jsx("br", {}), "CIRC."] }) }), _jsx("span", { id: tooltipId, className: "scv-control-tooltip scv-mode-tooltip", role: "tooltip", children: option.label })] }, option.id);
|
|
25
|
+
}) });
|
|
26
|
+
});
|
|
27
|
+
function scvBlockClass(block, sourceUnitId) {
|
|
28
|
+
const catenaryLabel = (sourceUnitId === null || sourceUnitId === void 0 ? void 0 : sourceUnitId.endsWith(":5.2.2.9.1")) && block.kind === "list-item" && block.listMarker === "none";
|
|
29
|
+
const alphaRatioLayout = hasAlphaRatioListLayout(block, sourceUnitId);
|
|
30
|
+
const inferredAlphaRatioMarker = hasInferredAlphaRatioListMarker(block, sourceUnitId);
|
|
31
|
+
const officialMarker = hasOfficialListMarker(block) || inferredAlphaRatioMarker;
|
|
32
|
+
const levelClass = listLevelClass(block) || (alphaRatioLayout ? "list-item-level-1" : "");
|
|
33
|
+
return `scv-block scv-block-${block.kind} ${officialMarker ? "list-item-with-official-marker" : ""} ${hasAlphabeticListMarker(block) ? "list-item-with-alphabetic-marker" : ""} ${hasSimpleDashMarker(block) && !inferredAlphaRatioMarker ? "list-item-with-simple-dash" : ""} ${hasNoListMarker(block) ? "list-item-without-marker" : ""} ${listMarkerClass(block)} ${levelClass} ${indentLevelClass(block)} ${hasLeadingMath(block) ? "list-item-with-leading-symbol" : ""} ${hasLeadingEmphasisLabel(block) ? "block-with-leading-label" : ""} ${hasTrailingStrong(block) ? "list-item-with-trailing-siglum" : ""} ${hasTrailingMath(block) ? "list-item-with-trailing-symbol" : ""} ${catenaryLabel ? "list-item-with-catenary-label" : ""} ${alphaRatioLayout ? "list-item-with-alpha-ratio" : ""}`;
|
|
34
|
+
}
|
|
35
|
+
function blockAssetKind(block, assets) {
|
|
36
|
+
if (!block.assetId)
|
|
37
|
+
return undefined;
|
|
38
|
+
if (assets.formulas[block.assetId])
|
|
39
|
+
return "formula";
|
|
40
|
+
if (assets.tables[block.assetId])
|
|
41
|
+
return "table";
|
|
42
|
+
if (assets.figures[block.assetId])
|
|
43
|
+
return "figure";
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
const ScvBlockFlow = memo(function ScvBlockFlow({ blocks, assets, assetsBaseUrl, sourceUnitId, sourceDocument, notesByTarget, onOpenAnnotation }) {
|
|
47
|
+
return _jsx("div", { className: "scv-unit-blocks", children: groupAlignedLabelBlocks(blocks).map((group) => {
|
|
48
|
+
var _a;
|
|
49
|
+
return group.kind === "label-list"
|
|
50
|
+
? _jsx(AlignedLabelList, { blocks: group.blocks, assets: assets, assetsBaseUrl: assetsBaseUrl, sourceUnitId: sourceUnitId, sourceDocument: sourceDocument, renderAccessory: (block) => { var _a; return _jsx(AnnotationMarginMarker, { notes: (_a = notesByTarget.get(annotationTargetKey({ kind: "block", unitId: sourceUnitId, blockId: block.blockId }))) !== null && _a !== void 0 ? _a : [], onOpen: onOpenAnnotation }); } }, group.blocks[0].blockId)
|
|
51
|
+
: _jsxs("div", { tabIndex: 0, className: scvBlockClass(group.block, sourceUnitId), "data-scv-citation-target": group.block.assetId ? "asset" : "block", "data-scv-source-unit-id": sourceUnitId, "data-scv-block-id": group.block.blockId, "data-scv-asset-id": group.block.assetId, "data-scv-asset-kind": blockAssetKind(group.block, assets), children: [_jsx(BlockContent, { block: group.block, assets: assets, assetsBaseUrl: assetsBaseUrl, sourceUnitId: sourceUnitId, sourceDocument: sourceDocument }), _jsx(AnnotationMarginMarker, { notes: (_a = notesByTarget.get(annotationTargetKey(group.block.assetId ? { kind: "asset", unitId: sourceUnitId, assetId: group.block.assetId, assetKind: blockAssetKind(group.block, assets) } : { kind: "block", unitId: sourceUnitId, blockId: group.block.blockId }))) !== null && _a !== void 0 ? _a : [], onOpen: onOpenAnnotation })] }, group.block.blockId);
|
|
52
|
+
}) });
|
|
53
|
+
});
|
|
54
|
+
function AuxiliaryContent({ panel, context }) {
|
|
55
|
+
return typeof panel === "function" ? panel(context) : panel;
|
|
56
|
+
}
|
|
57
|
+
function viewerHistoryEntry(state) {
|
|
58
|
+
if (!state || typeof state !== "object" || !("scvNavigation" in state))
|
|
59
|
+
return null;
|
|
60
|
+
const entry = state.scvNavigation;
|
|
61
|
+
return entry && typeof entry === "object" && "session" in entry && "index" in entry && "scrollTop" in entry
|
|
62
|
+
&& typeof entry.session === "string" && typeof entry.index === "number" && typeof entry.scrollTop === "number" ? entry : null;
|
|
63
|
+
}
|
|
64
|
+
function withViewerHistoryEntry(state, entry) {
|
|
65
|
+
return Object.assign(Object.assign({}, (state && typeof state === "object" ? state : {})), { scvNavigation: entry });
|
|
66
|
+
}
|
|
67
|
+
const chunkUnitMaps = new WeakMap();
|
|
68
|
+
const emptyRelatedRecords = [];
|
|
69
|
+
const emptyNavigationEntries = [];
|
|
70
|
+
function unitsForChunk(chunk) {
|
|
71
|
+
let map = chunkUnitMaps.get(chunk);
|
|
72
|
+
if (!map) {
|
|
73
|
+
map = new Map(chunk.units.map((unit) => [unit.id, unit]));
|
|
74
|
+
chunkUnitMaps.set(chunk, map);
|
|
75
|
+
}
|
|
76
|
+
return map;
|
|
77
|
+
}
|
|
14
78
|
function depth(unit) {
|
|
15
79
|
return unit.hierarchy.ancestorIds.length;
|
|
16
80
|
}
|
|
17
|
-
function
|
|
81
|
+
function hasUnitContent(unit) {
|
|
82
|
+
return unit.blocks.some((block) => {
|
|
83
|
+
var _a, _b;
|
|
84
|
+
if (block.kind === "heading" || block.blockId === unit.titleBlockId)
|
|
85
|
+
return false;
|
|
86
|
+
return Boolean(block.assetId || ((_b = (_a = block.text) === null || _a === void 0 ? void 0 : _a.normalized) === null || _b === void 0 ? void 0 : _b.trim()));
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function baseNumbering(value) {
|
|
90
|
+
return value.replace(/^C/iu, "");
|
|
91
|
+
}
|
|
92
|
+
function compareNumbering(left, right) {
|
|
93
|
+
return left.numbering.sortKey.localeCompare(right.numbering.sortKey, "it", { numeric: true });
|
|
94
|
+
}
|
|
95
|
+
function compareBaseNumbering(left, right) {
|
|
96
|
+
return baseNumbering(left.numbering.official).localeCompare(baseNumbering(right.numbering.official), "it", { numeric: true });
|
|
97
|
+
}
|
|
98
|
+
function parentNumbering(value) {
|
|
99
|
+
const parts = value.split(".");
|
|
100
|
+
parts.pop();
|
|
101
|
+
return parts.join(".") || null;
|
|
102
|
+
}
|
|
103
|
+
function findTextUnit(root, unitId) {
|
|
18
104
|
var _a;
|
|
19
|
-
return
|
|
105
|
+
return (_a = root === null || root === void 0 ? void 0 : root.querySelector(`[data-scv-text-unit="${CSS.escape(unitId)}"]`)) !== null && _a !== void 0 ? _a : null;
|
|
20
106
|
}
|
|
21
|
-
function
|
|
22
|
-
|
|
107
|
+
function scrollElementIntoPane(root, target) {
|
|
108
|
+
var _a;
|
|
109
|
+
const top = root.scrollTop + target.getBoundingClientRect().top - root.getBoundingClientRect().top;
|
|
110
|
+
const contextBar = (_a = root.parentElement) === null || _a === void 0 ? void 0 : _a.querySelector(".scv-mobile-context");
|
|
111
|
+
const offset = contextBar && window.getComputedStyle(contextBar).display !== "none"
|
|
112
|
+
? Math.max(18, contextBar.getBoundingClientRect().bottom - root.getBoundingClientRect().top + 10) : 18;
|
|
113
|
+
root.scrollTo({ top: Math.max(0, top - offset), behavior: "auto" });
|
|
23
114
|
}
|
|
24
|
-
function
|
|
25
|
-
|
|
115
|
+
function scrollTextUnit(root, unitId) {
|
|
116
|
+
const target = findTextUnit(root, unitId);
|
|
117
|
+
if (!root || !target)
|
|
118
|
+
return false;
|
|
119
|
+
scrollElementIntoPane(root, target);
|
|
120
|
+
return true;
|
|
26
121
|
}
|
|
27
|
-
function
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
122
|
+
function findViewerTarget(root, target) {
|
|
123
|
+
if (!root)
|
|
124
|
+
return null;
|
|
125
|
+
const unitId = CSS.escape(target.unitId);
|
|
126
|
+
if (target.kind === "unit")
|
|
127
|
+
return root.querySelector(`[data-scv-text-unit="${unitId}"], [data-scv-related-unit="${unitId}"]`);
|
|
128
|
+
if (target.kind === "block")
|
|
129
|
+
return root.querySelector(`[data-scv-source-unit-id="${unitId}"][data-scv-block-id="${CSS.escape(target.blockId)}"]`);
|
|
130
|
+
return root.querySelector(`[data-scv-source-unit-id="${unitId}"][data-scv-asset-id="${CSS.escape(target.assetId)}"]`);
|
|
131
|
+
}
|
|
132
|
+
function scrollViewerTarget(root, target) {
|
|
133
|
+
const element = findViewerTarget(root, target);
|
|
134
|
+
if (!root || !element)
|
|
135
|
+
return false;
|
|
136
|
+
scrollElementIntoPane(root, element);
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
async function writeTextClipboard(value) {
|
|
140
|
+
var _a;
|
|
141
|
+
if ((_a = navigator.clipboard) === null || _a === void 0 ? void 0 : _a.writeText) {
|
|
142
|
+
await navigator.clipboard.writeText(value);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const input = document.createElement("textarea");
|
|
146
|
+
input.value = value;
|
|
147
|
+
input.style.cssText = "position:fixed;left:-10000px;top:-10000px";
|
|
148
|
+
document.body.append(input);
|
|
149
|
+
try {
|
|
150
|
+
input.select();
|
|
151
|
+
if (!document.execCommand("copy"))
|
|
152
|
+
throw new Error("Copia non consentita.");
|
|
153
|
+
}
|
|
154
|
+
finally {
|
|
155
|
+
input.remove();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function updateDeepLink(mode, target, defaultMode, action = "replace", state = window.history.state) {
|
|
159
|
+
const url = urlForViewerTarget(window.location.href, mode, defaultMode, target);
|
|
160
|
+
window.history[`${action}State`](state, "", url);
|
|
161
|
+
}
|
|
162
|
+
function documentFromUnitId(unitId) {
|
|
163
|
+
return unitId.includes(":circ2019:") ? "circ2019" : "ntc2018";
|
|
164
|
+
}
|
|
165
|
+
function referenceDescriptor(element) {
|
|
166
|
+
const kind = element.dataset.scvReferenceKind;
|
|
167
|
+
const number = element.dataset.scvReferenceNumber;
|
|
168
|
+
const sourceUnitId = element.dataset.scvSourceUnitId;
|
|
169
|
+
const sourceDocument = element.dataset.scvSourceDocument;
|
|
170
|
+
if (!kind || !number || !sourceUnitId || !sourceDocument)
|
|
171
|
+
return null;
|
|
172
|
+
const hint = element.dataset.scvReferenceDocument;
|
|
173
|
+
return { kind, number, sourceUnitId, sourceDocument, documentHint: hint === "ntc2018" || hint === "circ2019" ? hint : null };
|
|
174
|
+
}
|
|
175
|
+
function citationSelectionFromElement(element) {
|
|
176
|
+
const kind = element.dataset.scvCitationTarget;
|
|
177
|
+
const unitId = element.dataset.scvSourceUnitId;
|
|
178
|
+
if (!unitId)
|
|
179
|
+
return null;
|
|
180
|
+
if (kind === "asset" && element.dataset.scvAssetId) {
|
|
181
|
+
const assetKind = element.dataset.scvAssetKind;
|
|
182
|
+
return { kind: "asset", unitId, assetId: element.dataset.scvAssetId, assetKind: assetKind === "formula" || assetKind === "table" || assetKind === "figure" ? assetKind : undefined };
|
|
183
|
+
}
|
|
184
|
+
if (kind === "block" && element.dataset.scvBlockId)
|
|
185
|
+
return { kind: "block", unitId, blockId: element.dataset.scvBlockId };
|
|
186
|
+
if (kind === "unit")
|
|
187
|
+
return { kind: "unit", unitId };
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
function viewerTargetForReference(reference) {
|
|
191
|
+
if (!reference.asset)
|
|
192
|
+
return { kind: "unit", unitId: reference.unit.id };
|
|
193
|
+
return { kind: "asset", unitId: reference.unit.id, assetId: reference.asset.id, assetKind: reference.asset.kind };
|
|
194
|
+
}
|
|
195
|
+
function viewerTargetForSearchResult(result) {
|
|
196
|
+
if (result.assetId)
|
|
197
|
+
return { kind: "asset", unitId: result.id, assetId: result.assetId, assetKind: result.assetKind };
|
|
198
|
+
if (result.blockId)
|
|
199
|
+
return { kind: "block", unitId: result.id, blockId: result.blockId };
|
|
200
|
+
return { kind: "unit", unitId: result.id };
|
|
201
|
+
}
|
|
202
|
+
function sameViewerTarget(left, right) {
|
|
203
|
+
if (!left || left.kind !== right.kind || left.unitId !== right.unitId)
|
|
204
|
+
return false;
|
|
205
|
+
if (left.kind === "block" && right.kind === "block")
|
|
206
|
+
return left.blockId === right.blockId;
|
|
207
|
+
if (left.kind === "asset" && right.kind === "asset")
|
|
208
|
+
return left.assetId === right.assetId;
|
|
209
|
+
return left.kind === "unit" && right.kind === "unit";
|
|
210
|
+
}
|
|
211
|
+
function referenceLabel(reference) {
|
|
212
|
+
const document = reference.unit.document === "ntc2018" ? "NTC 2018" : "Circolare 7/2019";
|
|
213
|
+
if (!reference.asset)
|
|
214
|
+
return `${document} · § ${reference.unit.numbering}`;
|
|
215
|
+
const prefix = reference.asset.kind === "formula" ? "Formula" : reference.asset.kind === "table" ? "Tab." : "Fig.";
|
|
216
|
+
return `${document} · ${prefix} ${reference.asset.officialNumber}`;
|
|
35
217
|
}
|
|
36
218
|
function evidencePages(unit, chunk) {
|
|
37
219
|
const pages = unit.blocks.flatMap((block) => {
|
|
@@ -45,280 +227,1563 @@ function evidencePages(unit, chunk) {
|
|
|
45
227
|
});
|
|
46
228
|
return [...new Set(pages)].sort((left, right) => left - right);
|
|
47
229
|
}
|
|
48
|
-
function snippet(value) {
|
|
49
|
-
const compact = value.replace(/\s+/gu, " ").trim();
|
|
50
|
-
return compact.length > 145 ? `${compact.slice(0, 142)}…` : compact;
|
|
51
|
-
}
|
|
52
230
|
function relationTargets(resultId, relations) {
|
|
53
231
|
return relations
|
|
54
232
|
.filter((edge) => edge.sourceUnitId === resultId)
|
|
55
233
|
.sort((left, right) => left.targetUnitId.localeCompare(right.targetUnitId, "it", { numeric: true }));
|
|
56
234
|
}
|
|
57
|
-
|
|
58
|
-
var _a
|
|
235
|
+
function matchingPrimarySummary(summary, lookup) {
|
|
236
|
+
var _a;
|
|
237
|
+
return (_a = lookup.unitByNumbering.get(baseNumbering(summary.numbering.official))) !== null && _a !== void 0 ? _a : null;
|
|
238
|
+
}
|
|
239
|
+
function recordsForPaths(index, lookup, paths, chunks) {
|
|
240
|
+
var _a;
|
|
241
|
+
if (!index || !lookup)
|
|
242
|
+
return [];
|
|
243
|
+
const records = [];
|
|
244
|
+
for (const path of lookup.chunkPaths) {
|
|
245
|
+
if (!paths.has(path))
|
|
246
|
+
continue;
|
|
247
|
+
const chunk = chunks.get(path);
|
|
248
|
+
if (!chunk)
|
|
249
|
+
continue;
|
|
250
|
+
const units = unitsForChunk(chunk);
|
|
251
|
+
for (const summary of (_a = lookup.unitsByChunkPath.get(path)) !== null && _a !== void 0 ? _a : []) {
|
|
252
|
+
const unit = units.get(summary.id);
|
|
253
|
+
if (unit)
|
|
254
|
+
records.push({ summary, unit, chunk });
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return records;
|
|
258
|
+
}
|
|
259
|
+
function buildCombinedPlan(primaryIndex, circIndex, relations) {
|
|
260
|
+
var _a, _b;
|
|
261
|
+
const empty = { fallbackSummaries: [], circPathsByPrimaryPath: new Map(), primaryAnchorByFallbackId: new Map() };
|
|
262
|
+
if (!primaryIndex || !circIndex)
|
|
263
|
+
return empty;
|
|
264
|
+
const relatedSourceIds = new Set(relations.map((edge) => edge.sourceUnitId));
|
|
265
|
+
const fallbackSummaries = circIndex.units.filter((summary) => !relatedSourceIds.has(summary.id));
|
|
266
|
+
const primary = [...primaryIndex.units].sort(compareBaseNumbering);
|
|
267
|
+
const circPathsByPrimaryPath = new Map();
|
|
268
|
+
const primaryAnchorByFallbackId = new Map();
|
|
269
|
+
for (const fallback of fallbackSummaries) {
|
|
270
|
+
let low = 0;
|
|
271
|
+
let high = primary.length;
|
|
272
|
+
while (low < high) {
|
|
273
|
+
const middle = (low + high) >>> 1;
|
|
274
|
+
if (compareBaseNumbering(primary[middle], fallback) <= 0)
|
|
275
|
+
low = middle + 1;
|
|
276
|
+
else
|
|
277
|
+
high = middle;
|
|
278
|
+
}
|
|
279
|
+
const anchor = (_a = primary[Math.max(0, low - 1)]) !== null && _a !== void 0 ? _a : primary[0];
|
|
280
|
+
if (!anchor)
|
|
281
|
+
continue;
|
|
282
|
+
primaryAnchorByFallbackId.set(fallback.id, anchor);
|
|
283
|
+
const paths = (_b = circPathsByPrimaryPath.get(anchor.chunkPath)) !== null && _b !== void 0 ? _b : new Set();
|
|
284
|
+
paths.add(fallback.chunkPath);
|
|
285
|
+
circPathsByPrimaryPath.set(anchor.chunkPath, paths);
|
|
286
|
+
}
|
|
287
|
+
return { fallbackSummaries, circPathsByPrimaryPath, primaryAnchorByFallbackId };
|
|
288
|
+
}
|
|
289
|
+
function navigationEntry(summary, source = summary.document, parentId = summary.hierarchy.parentId) {
|
|
290
|
+
const rawNumber = summary.numbering.official;
|
|
291
|
+
const displayNumber = source === "circ2019" && !/^C/iu.test(rawNumber) ? `C${rawNumber}` : rawNumber;
|
|
292
|
+
const baseNumber = baseNumbering(displayNumber);
|
|
293
|
+
const level = Math.max(0, baseNumber.split(".").length - 1);
|
|
294
|
+
return { summary, source, baseNumber, displayNumber, level, parentId, parentBaseNumber: parentNumbering(baseNumber) };
|
|
295
|
+
}
|
|
296
|
+
function sameRelatedRecords(left, right) {
|
|
297
|
+
return left.length === right.length && left.every((record, index) => { var _a, _b; return record.edge.relationId === ((_a = right[index]) === null || _a === void 0 ? void 0 : _a.edge.relationId) && record.unit === ((_b = right[index]) === null || _b === void 0 ? void 0 : _b.unit); });
|
|
298
|
+
}
|
|
299
|
+
function escapeRegExp(value) {
|
|
300
|
+
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
301
|
+
}
|
|
302
|
+
function unitTitleContent(unit) {
|
|
303
|
+
var _a;
|
|
304
|
+
const titleBlock = unit.blocks.find((block) => block.blockId === unit.titleBlockId);
|
|
305
|
+
const inline = (_a = titleBlock === null || titleBlock === void 0 ? void 0 : titleBlock.text) === null || _a === void 0 ? void 0 : _a.inline;
|
|
306
|
+
if (!inline)
|
|
307
|
+
return unit.title;
|
|
308
|
+
const officialNumber = unit.numbering.official;
|
|
309
|
+
const prefix = new RegExp(`^${escapeRegExp(officialNumber)}\\s*`, "u");
|
|
310
|
+
const titleInline = inline.map((segment, index) => index === 0 && typeof segment.value === "string" ? Object.assign(Object.assign({}, segment), { value: segment.value.replace(prefix, "") }) : segment);
|
|
311
|
+
return renderInlineSegments(titleInline);
|
|
312
|
+
}
|
|
313
|
+
const MemoizedUnit = memo(function MemoizedUnit({ record, mode, relatedRecords, assetsBaseUrl, notesByTarget, onOpenAnnotation }) {
|
|
314
|
+
var _a;
|
|
315
|
+
const { unit, chunk } = record;
|
|
316
|
+
const isChapter = depth(unit) === 0;
|
|
317
|
+
const isCircularFallback = mode === "combined" && unit.document === "circ2019";
|
|
318
|
+
const visibleRelated = mode === "combined" ? relatedRecords.filter(({ unit: relatedUnit }) => hasUnitContent(relatedUnit)) : emptyRelatedRecords;
|
|
319
|
+
return _jsxs("section", { tabIndex: 0, className: `scv-unit scv-unit-depth-${Math.min(depth(unit), 4)}${isCircularFallback ? " scv-circular-fallback" : ""}`, "data-provenance": isCircularFallback ? "Circolare 7/2019" : undefined, "data-scv-text-unit": unit.id, "data-scv-citation-target": "unit", "data-scv-source-unit-id": unit.id, "data-scv-chunk-path": record.summary.chunkPath, children: [isChapter ? _jsxs("h2", { className: "scv-chapter-heading", children: [_jsxs("span", { className: "scv-chapter-badge", children: [_jsx("span", { className: "scv-chapter-badge-label", children: "Capitolo" }), _jsxs("strong", { children: [unit.numbering.official, "."] })] }), _jsx("span", { className: "scv-chapter-rule", "aria-hidden": "true" }), _jsx("span", { className: "scv-chapter-title", children: _jsx("button", { type: "button", className: "scv-permalink-trigger", "data-scv-copy-link": true, "aria-label": `Copia link al capitolo ${unit.numbering.official}`, children: unit.title }) })] }) : _jsxs("h2", { children: [_jsx("span", { className: "scv-unit-number", children: unit.numbering.official }), _jsx("span", { className: "scv-unit-title", children: _jsx("button", { type: "button", className: "scv-permalink-trigger", "data-scv-copy-link": true, "aria-label": `Copia link al paragrafo ${unit.numbering.official}`, children: unitTitleContent(unit) }) })] }), _jsx(AnnotationMarginMarker, { notes: (_a = notesByTarget.get(annotationTargetKey({ kind: "unit", unitId: unit.id }))) !== null && _a !== void 0 ? _a : [], onOpen: onOpenAnnotation }), _jsx(ScvBlockFlow, { blocks: unit.blocks.filter((block) => !isRepeatedUnitTitle(unit, block)), assets: chunk.assets, assetsBaseUrl: assetsBaseUrl, sourceUnitId: unit.id, sourceDocument: unit.document, notesByTarget: notesByTarget, onOpenAnnotation: onOpenAnnotation }), visibleRelated.map(({ edge, unit: relatedUnit, chunk: relatedChunk }) => { var _a; return _jsxs("section", { tabIndex: 0, className: "scv-related-unit", "data-provenance": "Circolare 7/2019", "data-scv-related-unit": relatedUnit.id, "data-scv-citation-target": "unit", "data-scv-source-unit-id": relatedUnit.id, children: [_jsx("header", { children: _jsxs("h3", { children: [_jsx("span", { className: "scv-related-number", children: relatedUnit.numbering.official }), _jsx("span", { className: "scv-related-title", children: _jsx("button", { type: "button", className: "scv-permalink-trigger", "data-scv-copy-link": true, "aria-label": `Copia link al paragrafo ${relatedUnit.numbering.official}`, children: relatedUnit.title }) })] }) }), _jsx(AnnotationMarginMarker, { notes: (_a = notesByTarget.get(annotationTargetKey({ kind: "unit", unitId: relatedUnit.id }))) !== null && _a !== void 0 ? _a : [], onOpen: onOpenAnnotation }), _jsx(ScvBlockFlow, { blocks: relatedUnit.blocks.filter((block) => !isRepeatedUnitTitle(relatedUnit, block)), assets: relatedChunk.assets, assetsBaseUrl: assetsBaseUrl, sourceUnitId: relatedUnit.id, sourceDocument: relatedUnit.document, notesByTarget: notesByTarget, onOpenAnnotation: onOpenAnnotation })] }, edge.relationId); })] });
|
|
320
|
+
}, (previous, next) => previous.record.unit === next.record.unit && previous.record.chunk === next.record.chunk && previous.mode === next.mode && previous.assetsBaseUrl === next.assetsBaseUrl && previous.notesByTarget === next.notesByTarget && previous.onOpenAnnotation === next.onOpenAnnotation && sameRelatedRecords(previous.relatedRecords, next.relatedRecords));
|
|
321
|
+
const DocumentContent = memo(function DocumentContent({ records, relatedByTarget, mode, assetsBaseUrl, documentLabel, documentUnits, documentChunks, hasPrevious, hasNext, notesByTarget, onOpenAnnotation }) {
|
|
322
|
+
if (records.length === 0)
|
|
323
|
+
return _jsx(LoadingPanel, { label: "Caricamento dei contenuti\u2026" });
|
|
324
|
+
const version = records[0].chunk.structuralCodesVersion;
|
|
325
|
+
return _jsxs("div", { className: "scv-text-flow", children: [hasPrevious && _jsx("div", { className: "scv-progressive-edge", "aria-hidden": "true" }), _jsxs("p", { className: "scv-chunk-note", children: ["Documento continuo \u00B7 ", documentLabel, " \u00B7 ", documentUnits, " unit\u00E0 \u00B7 ", documentChunks, " chunk \u00B7 structural-codes ", version] }), records.map((record) => { var _a; return _jsx(MemoizedUnit, { record: record, mode: mode, relatedRecords: (_a = relatedByTarget.get(record.unit.id)) !== null && _a !== void 0 ? _a : emptyRelatedRecords, assetsBaseUrl: assetsBaseUrl, notesByTarget: notesByTarget, onOpenAnnotation: onOpenAnnotation }, record.unit.id); }), hasNext ? _jsx("div", { className: "scv-progressive-edge", "aria-hidden": "true" }) : _jsx("div", { className: "scv-end-note", children: "Fine del documento." })] });
|
|
326
|
+
});
|
|
327
|
+
const GlobalDocumentScrubber = memo(function GlobalDocumentScrubber({ rootRef, entries, activeId, onSelect, annotationMarkers, onAnnotationSelect }) {
|
|
328
|
+
var _a, _b, _c, _d;
|
|
329
|
+
const [interaction, setInteraction] = useState("idle");
|
|
330
|
+
const [previewEntry, setPreviewEntry] = useState(null);
|
|
331
|
+
const [openCluster, setOpenCluster] = useState(null);
|
|
332
|
+
const hideTimerRef = useRef(null);
|
|
333
|
+
const pointerFrameRef = useRef(null);
|
|
334
|
+
const pendingPointerYRef = useRef(null);
|
|
335
|
+
const dragRef = useRef(null);
|
|
336
|
+
const activeEntry = (_a = entries.find((entry) => entry.id === activeId)) !== null && _a !== void 0 ? _a : null;
|
|
337
|
+
const activeIndex = (_b = activeEntry === null || activeEntry === void 0 ? void 0 : activeEntry.index) !== null && _b !== void 0 ? _b : 0;
|
|
338
|
+
const ratio = interaction === "dragging" && previewEntry ? previewEntry.ratio : globalScrubberRatioForId(entries, activeId, (_c = activeEntry === null || activeEntry === void 0 ? void 0 : activeEntry.ratio) !== null && _c !== void 0 ? _c : 0);
|
|
339
|
+
const visible = interaction !== "idle";
|
|
340
|
+
const annotationClusters = useMemo(() => clusterAnnotationMarkers(annotationMarkers), [annotationMarkers]);
|
|
341
|
+
const clearHideTimer = useCallback(() => {
|
|
342
|
+
if (hideTimerRef.current !== null)
|
|
343
|
+
window.clearTimeout(hideTimerRef.current);
|
|
344
|
+
hideTimerRef.current = null;
|
|
345
|
+
}, []);
|
|
346
|
+
const hideLater = useCallback(() => {
|
|
347
|
+
clearHideTimer();
|
|
348
|
+
hideTimerRef.current = window.setTimeout(() => setInteraction((current) => current === "scrolling" ? "idle" : current), 1000);
|
|
349
|
+
}, [clearHideTimer]);
|
|
350
|
+
const showTransient = useCallback(() => {
|
|
351
|
+
setInteraction((current) => current === "dragging" || current === "hover" || current === "focus" ? current : "scrolling");
|
|
352
|
+
hideLater();
|
|
353
|
+
}, [hideLater]);
|
|
354
|
+
useEffect(() => {
|
|
355
|
+
const root = rootRef.current;
|
|
356
|
+
if (!root)
|
|
357
|
+
return;
|
|
358
|
+
const onScroll = () => showTransient();
|
|
359
|
+
root.addEventListener("scroll", onScroll, { passive: true });
|
|
360
|
+
return () => root.removeEventListener("scroll", onScroll);
|
|
361
|
+
}, [rootRef, showTransient]);
|
|
362
|
+
useEffect(() => () => {
|
|
363
|
+
clearHideTimer();
|
|
364
|
+
if (pointerFrameRef.current !== null)
|
|
365
|
+
window.cancelAnimationFrame(pointerFrameRef.current);
|
|
366
|
+
}, [clearHideTimer]);
|
|
367
|
+
if (entries.length < 2 || !activeEntry)
|
|
368
|
+
return null;
|
|
369
|
+
const previewPointer = (clientY) => {
|
|
370
|
+
const drag = dragRef.current;
|
|
371
|
+
if (!drag)
|
|
372
|
+
return null;
|
|
373
|
+
const entry = drag.session.preview((clientY - drag.trackTop) / Math.max(1, drag.trackHeight));
|
|
374
|
+
if (entry)
|
|
375
|
+
setPreviewEntry(entry);
|
|
376
|
+
return entry;
|
|
377
|
+
};
|
|
378
|
+
const startDrag = (event) => {
|
|
379
|
+
if (!event.isPrimary || event.button !== 0)
|
|
380
|
+
return;
|
|
381
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
382
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
383
|
+
dragRef.current = { pointerId: event.pointerId, trackTop: rect.top, trackHeight: rect.height, session: createGlobalScrubberDragSession(entries, (entry) => onSelect(entry.id)) };
|
|
384
|
+
previewPointer(event.clientY);
|
|
385
|
+
setInteraction("dragging");
|
|
386
|
+
clearHideTimer();
|
|
387
|
+
event.preventDefault();
|
|
388
|
+
};
|
|
389
|
+
const moveDrag = (event) => {
|
|
390
|
+
const drag = dragRef.current;
|
|
391
|
+
if (!drag || drag.pointerId !== event.pointerId)
|
|
392
|
+
return;
|
|
393
|
+
pendingPointerYRef.current = event.clientY;
|
|
394
|
+
if (pointerFrameRef.current !== null)
|
|
395
|
+
return;
|
|
396
|
+
pointerFrameRef.current = window.requestAnimationFrame(() => {
|
|
397
|
+
pointerFrameRef.current = null;
|
|
398
|
+
if (pendingPointerYRef.current !== null)
|
|
399
|
+
previewPointer(pendingPointerYRef.current);
|
|
400
|
+
});
|
|
401
|
+
};
|
|
402
|
+
const stopDrag = (event, commit) => {
|
|
403
|
+
const drag = dragRef.current;
|
|
404
|
+
if (!drag || drag.pointerId !== event.pointerId)
|
|
405
|
+
return;
|
|
406
|
+
if (pointerFrameRef.current !== null) {
|
|
407
|
+
window.cancelAnimationFrame(pointerFrameRef.current);
|
|
408
|
+
pointerFrameRef.current = null;
|
|
409
|
+
}
|
|
410
|
+
previewPointer(event.clientY);
|
|
411
|
+
if (commit)
|
|
412
|
+
drag.session.commit();
|
|
413
|
+
else
|
|
414
|
+
drag.session.cancel();
|
|
415
|
+
dragRef.current = null;
|
|
416
|
+
pendingPointerYRef.current = null;
|
|
417
|
+
setInteraction("scrolling");
|
|
418
|
+
hideLater();
|
|
419
|
+
};
|
|
420
|
+
const onKeyDown = (event) => {
|
|
421
|
+
if (!["ArrowUp", "ArrowDown", "PageUp", "PageDown", "Home", "End"].includes(event.key))
|
|
422
|
+
return;
|
|
423
|
+
event.preventDefault();
|
|
424
|
+
const target = entries[globalScrubberKeyboardIndex(activeIndex < 0 ? 0 : activeIndex, event.key, entries.length)];
|
|
425
|
+
if (target) {
|
|
426
|
+
setPreviewEntry(target);
|
|
427
|
+
onSelect(target.id);
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
const overlayEntry = previewEntry !== null && previewEntry !== void 0 ? previewEntry : activeEntry;
|
|
431
|
+
const displayedEntry = interaction === "dragging" && previewEntry ? previewEntry : activeEntry;
|
|
432
|
+
const valueText = displayedEntry ? `§ ${displayedEntry.label} — ${displayedEntry.title}` : "Posizione nel documento";
|
|
433
|
+
return _jsxs("div", { className: `scv-scroll-rail ${visible ? "is-visible" : "is-idle"} is-${interaction}`, "aria-label": "Navigazione globale del documento", style: { "--scv-scrubber-ratio": ratio }, onMouseEnter: () => { clearHideTimer(); setInteraction((current) => current === "dragging" ? current : "hover"); }, onMouseLeave: () => { if (interaction !== "dragging" && interaction !== "focus") {
|
|
434
|
+
setInteraction("scrolling");
|
|
435
|
+
hideLater();
|
|
436
|
+
} }, onFocusCapture: () => { clearHideTimer(); setInteraction("focus"); }, onBlurCapture: () => { setInteraction("scrolling"); hideLater(); }, children: [_jsxs("div", { className: "scv-scroll-track", role: "presentation", onPointerDown: startDrag, onPointerMove: moveDrag, onPointerUp: (event) => stopDrag(event, true), onPointerCancel: (event) => stopDrag(event, false), children: [annotationClusters.map((cluster) => {
|
|
437
|
+
var _a;
|
|
438
|
+
const id = cluster.markers.map(({ annotation }) => annotation.id).join(":");
|
|
439
|
+
const single = cluster.markers.length === 1 ? cluster.markers[0].annotation : null;
|
|
440
|
+
const type = (_a = single === null || single === void 0 ? void 0 : single.type) !== null && _a !== void 0 ? _a : "cluster";
|
|
441
|
+
const label = single ? `${single.type === "bookmark" ? "Segnalibro" : "Nota"}: § ${single.numbering} — ${single.title}` : `${cluster.markers.length} annotazioni vicine`;
|
|
442
|
+
return _jsxs("span", { className: "scv-annotation-scrubber-group", style: { "--scv-annotation-ratio": cluster.ratio }, children: [_jsx("button", { type: "button", className: `scv-annotation-scrubber-marker is-${type}`, "aria-label": label, title: label, onPointerDown: (event) => event.stopPropagation(), onClick: (event) => { event.stopPropagation(); if (single)
|
|
443
|
+
onAnnotationSelect(single);
|
|
444
|
+
else
|
|
445
|
+
setOpenCluster((current) => current === id ? null : id); }, children: _jsx("span", { "aria-hidden": "true", children: type === "bookmark" ? "‹" : type === "note" ? "›" : cluster.markers.length }) }), !single && openCluster === id && _jsx("div", { className: "scv-annotation-cluster-popover", role: "menu", "aria-label": "Annotazioni vicine", children: cluster.markers.map(({ annotation }) => _jsxs("button", { type: "button", role: "menuitem", onPointerDown: (event) => event.stopPropagation(), onClick: (event) => { event.stopPropagation(); setOpenCluster(null); onAnnotationSelect(annotation); }, children: [_jsx("b", { "aria-hidden": "true", children: annotation.type === "bookmark" ? "‹" : "›" }), _jsxs("span", { children: [annotation.type === "bookmark" ? "Segnalibro" : "Nota", " \u00B7 \u00A7 ", annotation.numbering] })] }, annotation.id)) })] }, id);
|
|
446
|
+
}), _jsx("button", { type: "button", className: "scv-scroll-thumb", role: "slider", "aria-label": "Posizione nel documento", "aria-valuemin": 1, "aria-valuemax": entries.length, "aria-valuenow": ((_d = displayedEntry === null || displayedEntry === void 0 ? void 0 : displayedEntry.index) !== null && _d !== void 0 ? _d : 0) + 1, "aria-valuetext": valueText, onKeyDown: onKeyDown })] }), interaction === "dragging" && overlayEntry && _jsxs("output", { className: "scv-scroll-overlay", "aria-live": "off", children: [_jsxs("strong", { children: ["\u00A7 ", overlayEntry.label] }), _jsx("span", { children: overlayEntry.title })] })] });
|
|
447
|
+
});
|
|
448
|
+
function useVisibleUnitObserver(rootRef, records, onVisible) {
|
|
449
|
+
useEffect(() => {
|
|
450
|
+
const root = rootRef.current;
|
|
451
|
+
if (!root || records.length === 0)
|
|
452
|
+
return;
|
|
453
|
+
const elements = [...root.querySelectorAll("[data-scv-text-unit]")];
|
|
454
|
+
if (elements.length === 0)
|
|
455
|
+
return;
|
|
456
|
+
const orderedIds = elements.flatMap((element) => element.dataset.scvTextUnit ? [element.dataset.scvTextUnit] : []);
|
|
457
|
+
const nearEnd = () => root.scrollTop + root.clientHeight >= root.scrollHeight - Math.max(24, root.clientHeight * 0.5);
|
|
458
|
+
if (typeof IntersectionObserver === "undefined") {
|
|
459
|
+
const positions = elements.map((element) => { var _a; return ({ id: (_a = element.dataset.scvTextUnit) !== null && _a !== void 0 ? _a : "", top: element.offsetTop }); });
|
|
460
|
+
let frame = null;
|
|
461
|
+
const update = () => {
|
|
462
|
+
frame = null;
|
|
463
|
+
const marker = root.scrollTop + Math.min(root.clientHeight * 0.28, 220);
|
|
464
|
+
let low = 0;
|
|
465
|
+
let high = positions.length;
|
|
466
|
+
while (low < high) {
|
|
467
|
+
const middle = (low + high) >>> 1;
|
|
468
|
+
if (positions[middle].top <= marker)
|
|
469
|
+
low = middle + 1;
|
|
470
|
+
else
|
|
471
|
+
high = middle;
|
|
472
|
+
}
|
|
473
|
+
const candidate = nearEnd() ? positions[positions.length - 1] : positions[Math.max(0, low - 1)];
|
|
474
|
+
if (candidate === null || candidate === void 0 ? void 0 : candidate.id)
|
|
475
|
+
onVisible(candidate.id);
|
|
476
|
+
};
|
|
477
|
+
const onScroll = () => { if (frame === null)
|
|
478
|
+
frame = window.requestAnimationFrame(update); };
|
|
479
|
+
root.addEventListener("scroll", onScroll, { passive: true });
|
|
480
|
+
update();
|
|
481
|
+
return () => {
|
|
482
|
+
root.removeEventListener("scroll", onScroll);
|
|
483
|
+
if (frame !== null)
|
|
484
|
+
window.cancelAnimationFrame(frame);
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
const visible = new Set();
|
|
488
|
+
let frame = null;
|
|
489
|
+
const commit = () => {
|
|
490
|
+
frame = null;
|
|
491
|
+
for (let index = orderedIds.length - 1; index >= 0; index -= 1) {
|
|
492
|
+
if (visible.has(orderedIds[index])) {
|
|
493
|
+
onVisible(orderedIds[index]);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
const observer = new IntersectionObserver((entries) => {
|
|
499
|
+
for (const entry of entries) {
|
|
500
|
+
const id = entry.target.dataset.scvTextUnit;
|
|
501
|
+
if (!id)
|
|
502
|
+
continue;
|
|
503
|
+
if (entry.isIntersecting)
|
|
504
|
+
visible.add(id);
|
|
505
|
+
else
|
|
506
|
+
visible.delete(id);
|
|
507
|
+
}
|
|
508
|
+
if (frame === null)
|
|
509
|
+
frame = window.requestAnimationFrame(commit);
|
|
510
|
+
}, { root, rootMargin: "0px 0px -72% 0px", threshold: 0.01 });
|
|
511
|
+
const onScroll = () => {
|
|
512
|
+
if (!nearEnd())
|
|
513
|
+
return;
|
|
514
|
+
const lastId = orderedIds[orderedIds.length - 1];
|
|
515
|
+
if (lastId)
|
|
516
|
+
onVisible(lastId);
|
|
517
|
+
};
|
|
518
|
+
root.addEventListener("scroll", onScroll, { passive: true });
|
|
519
|
+
for (const element of elements)
|
|
520
|
+
observer.observe(element);
|
|
521
|
+
return () => {
|
|
522
|
+
root.removeEventListener("scroll", onScroll);
|
|
523
|
+
observer.disconnect();
|
|
524
|
+
if (frame !== null)
|
|
525
|
+
window.cancelAnimationFrame(frame);
|
|
526
|
+
};
|
|
527
|
+
}, [onVisible, records, rootRef]);
|
|
528
|
+
}
|
|
529
|
+
function HighlightedSnippet({ result }) {
|
|
530
|
+
const parts = [];
|
|
531
|
+
let offset = 0;
|
|
532
|
+
for (const [start, end] of result.highlights) {
|
|
533
|
+
const from = Math.max(offset, Math.min(result.snippet.length, start));
|
|
534
|
+
const to = Math.max(from, Math.min(result.snippet.length, end));
|
|
535
|
+
if (from > offset)
|
|
536
|
+
parts.push(result.snippet.slice(offset, from));
|
|
537
|
+
if (to > from)
|
|
538
|
+
parts.push(_jsx("mark", { children: result.snippet.slice(from, to) }, `${from}:${to}`));
|
|
539
|
+
offset = to;
|
|
540
|
+
}
|
|
541
|
+
if (offset < result.snippet.length)
|
|
542
|
+
parts.push(result.snippet.slice(offset));
|
|
543
|
+
return _jsx(_Fragment, { children: parts });
|
|
544
|
+
}
|
|
545
|
+
const NavigationPane = memo(function NavigationPane({ id, mode, onModeChange, hierarchy, activeLevelIds, sidebarMode, sidebarSession, indexReady, query, onQueryChange, searchReady, searchStatus, searchSource, searchDurationMs, searchResults, onSearchSubmit, onSearchResult, onSelectUnit, onRequestClose, onHistoryBack, onHistoryForward, canHistoryBack, canHistoryForward, searchRef, darkMode, onToggleTheme }) {
|
|
546
|
+
var _a, _b, _c, _d, _e, _f;
|
|
547
|
+
const indexListRef = useRef(null);
|
|
548
|
+
const searchResultsRef = useRef(null);
|
|
549
|
+
const [searchEditing, setSearchEditing] = useState(false);
|
|
550
|
+
const [settingsOpen, setSettingsOpen] = useState(false);
|
|
551
|
+
const [sidebarExpanded, setSidebarExpanded] = useState({ session: -1, chapterId: null, paragraphId: null });
|
|
552
|
+
const lastSidebarEntryRef = useRef(null);
|
|
553
|
+
const searchResultsId = useId();
|
|
554
|
+
const settingsId = useId();
|
|
555
|
+
const themeTooltipId = useId();
|
|
556
|
+
const chapters = (_a = hierarchy[0]) !== null && _a !== void 0 ? _a : [];
|
|
557
|
+
const paragraphs = (_b = hierarchy[1]) !== null && _b !== void 0 ? _b : emptyNavigationEntries;
|
|
558
|
+
const subparagraphs = (_c = hierarchy[2]) !== null && _c !== void 0 ? _c : emptyNavigationEntries;
|
|
559
|
+
const activeChapterId = (_d = activeLevelIds[0]) !== null && _d !== void 0 ? _d : null;
|
|
560
|
+
const activeParagraphId = (_e = activeLevelIds[1]) !== null && _e !== void 0 ? _e : null;
|
|
561
|
+
const activeSubparagraphId = (_f = activeLevelIds[2]) !== null && _f !== void 0 ? _f : null;
|
|
562
|
+
const paragraphsByChapter = useMemo(() => {
|
|
563
|
+
var _a, _b, _c;
|
|
564
|
+
const grouped = new Map();
|
|
565
|
+
for (const entry of paragraphs) {
|
|
566
|
+
const group = (_b = grouped.get((_a = entry.parentId) !== null && _a !== void 0 ? _a : "")) !== null && _b !== void 0 ? _b : [];
|
|
567
|
+
group.push(entry);
|
|
568
|
+
grouped.set((_c = entry.parentId) !== null && _c !== void 0 ? _c : "", group);
|
|
569
|
+
}
|
|
570
|
+
return grouped;
|
|
571
|
+
}, [paragraphs]);
|
|
572
|
+
const subparagraphsByParagraph = useMemo(() => {
|
|
573
|
+
var _a, _b, _c;
|
|
574
|
+
const grouped = new Map();
|
|
575
|
+
for (const entry of subparagraphs) {
|
|
576
|
+
const group = (_b = grouped.get((_a = entry.parentId) !== null && _a !== void 0 ? _a : "")) !== null && _b !== void 0 ? _b : [];
|
|
577
|
+
group.push(entry);
|
|
578
|
+
grouped.set((_c = entry.parentId) !== null && _c !== void 0 ? _c : "", group);
|
|
579
|
+
}
|
|
580
|
+
return grouped;
|
|
581
|
+
}, [subparagraphs]);
|
|
582
|
+
useEffect(() => {
|
|
583
|
+
var _a;
|
|
584
|
+
const targetId = (_a = activeSubparagraphId !== null && activeSubparagraphId !== void 0 ? activeSubparagraphId : activeParagraphId) !== null && _a !== void 0 ? _a : activeChapterId;
|
|
585
|
+
if (!targetId)
|
|
586
|
+
return;
|
|
587
|
+
const timeout = window.setTimeout(() => {
|
|
588
|
+
var _a, _b;
|
|
589
|
+
const target = (_a = indexListRef.current) === null || _a === void 0 ? void 0 : _a.querySelector(`[data-index-unit="${CSS.escape(targetId)}"]`);
|
|
590
|
+
if (!target)
|
|
591
|
+
return;
|
|
592
|
+
const reducedMotion = (_b = window.matchMedia) === null || _b === void 0 ? void 0 : _b.call(window, "(prefers-reduced-motion: reduce)").matches;
|
|
593
|
+
target.scrollIntoView({ block: "nearest", behavior: reducedMotion ? "auto" : "smooth" });
|
|
594
|
+
}, 360);
|
|
595
|
+
return () => window.clearTimeout(timeout);
|
|
596
|
+
}, [activeChapterId, activeParagraphId, activeSubparagraphId]);
|
|
597
|
+
const focusSearchResult = (position) => {
|
|
598
|
+
var _a, _b, _c;
|
|
599
|
+
const results = [...((_b = (_a = searchResultsRef.current) === null || _a === void 0 ? void 0 : _a.querySelectorAll(".scv-search-result")) !== null && _b !== void 0 ? _b : [])];
|
|
600
|
+
if (results.length === 0)
|
|
601
|
+
return;
|
|
602
|
+
(_c = results[(position + results.length) % results.length]) === null || _c === void 0 ? void 0 : _c.focus();
|
|
603
|
+
};
|
|
604
|
+
const searchResultKeyDown = (event, position) => {
|
|
605
|
+
var _a, _b;
|
|
606
|
+
if (event.key === "ArrowDown") {
|
|
607
|
+
event.preventDefault();
|
|
608
|
+
focusSearchResult(position + 1);
|
|
609
|
+
}
|
|
610
|
+
else if (event.key === "ArrowUp") {
|
|
611
|
+
event.preventDefault();
|
|
612
|
+
if (position === 0)
|
|
613
|
+
(_a = searchRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
614
|
+
else
|
|
615
|
+
focusSearchResult(position - 1);
|
|
616
|
+
}
|
|
617
|
+
else if (event.key === "Home") {
|
|
618
|
+
event.preventDefault();
|
|
619
|
+
focusSearchResult(0);
|
|
620
|
+
}
|
|
621
|
+
else if (event.key === "End") {
|
|
622
|
+
event.preventDefault();
|
|
623
|
+
focusSearchResult(searchResults.length - 1);
|
|
624
|
+
}
|
|
625
|
+
else if (event.key === "Escape") {
|
|
626
|
+
event.preventDefault();
|
|
627
|
+
onQueryChange("");
|
|
628
|
+
(_b = searchRef.current) === null || _b === void 0 ? void 0 : _b.focus();
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
const renderEntryButton = (entry, level, active, tabIndex, expanded) => _jsxs("button", { type: "button", "data-index-unit": entry.summary.id, className: `scv-index-entry scv-index-level-${level} ${active ? "active" : ""}`, onClick: () => {
|
|
632
|
+
var _a;
|
|
633
|
+
if (sidebarMode && expanded !== undefined) {
|
|
634
|
+
if (((_a = lastSidebarEntryRef.current) === null || _a === void 0 ? void 0 : _a.session) !== sidebarSession || lastSidebarEntryRef.current.id !== entry.summary.id) {
|
|
635
|
+
lastSidebarEntryRef.current = { session: sidebarSession, id: entry.summary.id };
|
|
636
|
+
setSidebarExpanded((current) => {
|
|
637
|
+
var _a;
|
|
638
|
+
return level === 0
|
|
639
|
+
? { session: sidebarSession, chapterId: entry.summary.id, paragraphId: null }
|
|
640
|
+
: { session: sidebarSession, chapterId: (_a = entry.parentId) !== null && _a !== void 0 ? _a : current.chapterId, paragraphId: entry.summary.id };
|
|
641
|
+
});
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
lastSidebarEntryRef.current = null;
|
|
646
|
+
onSelectUnit(entry.summary);
|
|
647
|
+
onRequestClose();
|
|
648
|
+
}, title: `${entry.displayNumber} ${entry.summary.title}`, "aria-current": active ? "page" : undefined, "aria-expanded": expanded, tabIndex: tabIndex, children: [_jsx("strong", { children: entry.displayNumber }), _jsx("span", { children: entry.summary.title })] });
|
|
649
|
+
return _jsxs("aside", { id: id, className: "scv-index-pane", "aria-label": "Indice gerarchico", onClickCapture: (event) => {
|
|
650
|
+
if (!event.target.closest("[data-index-unit]"))
|
|
651
|
+
lastSidebarEntryRef.current = null;
|
|
652
|
+
}, children: [_jsxs("div", { className: "scv-search-toolbar", children: [_jsxs("div", { className: "scv-history-controls", role: "group", "aria-label": "Cronologia di navigazione", children: [_jsx("button", { type: "button", "aria-label": "Indietro", disabled: !canHistoryBack, onClick: onHistoryBack, children: "\u2190" }), _jsx("button", { type: "button", "aria-label": "Avanti", disabled: !canHistoryForward, onClick: onHistoryForward, children: "\u2192" })] }), _jsxs("div", { className: `scv-search-box ${searchEditing ? "is-editing" : ""}`, onBlur: (event) => {
|
|
653
|
+
if (!event.currentTarget.contains(event.relatedTarget))
|
|
654
|
+
setSearchEditing(false);
|
|
655
|
+
}, children: [_jsxs("form", { className: "scv-search-form", role: "search", onMouseDown: (event) => {
|
|
656
|
+
var _a;
|
|
657
|
+
if (!event.target.closest("button"))
|
|
658
|
+
(_a = searchRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
659
|
+
}, onSubmit: (event) => {
|
|
660
|
+
onSearchSubmit(event);
|
|
661
|
+
if (searchResults.length > 0)
|
|
662
|
+
setSearchEditing(false);
|
|
663
|
+
}, children: [_jsx("span", { className: "scv-search-icon", "aria-hidden": "true", children: _jsxs("svg", { viewBox: "0 0 24 24", fill: "none", children: [_jsx("circle", { cx: "10.75", cy: "10.75", r: "6.75" }), _jsx("path", { d: "m15.75 15.75 4.25 4.25" })] }) }), _jsx("input", { ref: searchRef, type: "search", role: "combobox", "aria-autocomplete": "list", value: query, onChange: (event) => onQueryChange(event.target.value), onKeyDown: (event) => {
|
|
664
|
+
if (event.key === "Escape" && query) {
|
|
665
|
+
event.preventDefault();
|
|
666
|
+
onQueryChange("");
|
|
667
|
+
}
|
|
668
|
+
else if (event.key === "ArrowDown" && searchResults.length > 0) {
|
|
669
|
+
event.preventDefault();
|
|
670
|
+
focusSearchResult(0);
|
|
671
|
+
}
|
|
672
|
+
}, onFocus: () => setSearchEditing(true), placeholder: searchEditing ? "" : "Cerca nella normativa…", "aria-label": "Cerca nella normativa", "aria-controls": searchResultsId, "aria-expanded": searchEditing && searchReady }), searchEditing && query && _jsx("button", { type: "button", className: "scv-clear-search", onClick: () => onQueryChange(""), "aria-label": "Cancella ricerca", children: _jsx("svg", { viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: _jsx("path", { d: "m6.5 6.5 11 11M17.5 6.5l-11 11" }) }) }), !searchEditing && !query && _jsx("kbd", { children: "/" })] }), searchEditing && searchReady && _jsx("div", { id: searchResultsId, ref: searchResultsRef, className: "scv-search-results", role: "listbox", "aria-label": "Risultati ricerca", "aria-busy": searchStatus === "loading", "data-scv-search-source": searchSource, "data-scv-search-duration-ms": searchDurationMs !== null && searchDurationMs !== void 0 ? searchDurationMs : undefined, children: searchStatus === "loading" ? _jsx("p", { className: "scv-search-status", children: "Ricerca in corso\u2026" }) : searchStatus === "error" ? _jsx("p", { className: "scv-search-status", children: "Ricerca non disponibile." }) : searchResults.length === 0 ? _jsx("p", { className: "scv-search-status", children: "Nessun risultato nella modalit\u00E0 corrente." }) : searchResults.map((result, index) => { var _a, _b; return _jsxs("button", { type: "button", role: "option", "aria-selected": false, className: "scv-search-result", "data-search-match": result.matchKind, onKeyDown: (event) => searchResultKeyDown(event, index), onClick: () => { setSearchEditing(false); onSearchResult(result); onRequestClose(); }, children: [_jsxs("span", { children: [result.document === "ntc2018" ? "NTC 2018" : "Circolare 7/2019", " \u00B7 ", result.numbering, result.assetKind ? ` · ${result.assetKind === "formula" ? "Formula" : result.assetKind === "table" ? "Tabella" : "Figura"}` : ""] }), _jsx("strong", { children: result.title }), _jsx("small", { children: _jsx(HighlightedSnippet, { result: result }) })] }, `${result.id}:${(_a = result.blockId) !== null && _a !== void 0 ? _a : ""}:${(_b = result.assetId) !== null && _b !== void 0 ? _b : ""}`); }) })] }), _jsx("button", { type: "button", className: "scv-index-settings-toggle", "aria-controls": settingsId, "aria-expanded": settingsOpen, onClick: () => setSettingsOpen((current) => !current), "aria-label": "Impostazioni indice", children: _jsxs("svg", { viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [_jsx("path", { d: "M4 7h16M4 12h16M4 17h16" }), _jsx("circle", { cx: "9", cy: "7", r: "2" }), _jsx("circle", { cx: "15", cy: "12", r: "2" }), _jsx("circle", { cx: "10", cy: "17", r: "2" })] }) }), _jsx("div", { id: settingsId, className: `scv-index-settings ${settingsOpen ? "is-open" : ""}`, children: _jsxs("div", { className: "scv-index-settings-inner", children: [_jsx(ModeSegmentedControl, { mode: mode, onChange: onModeChange }), _jsxs("button", { type: "button", className: "scv-theme-button", onClick: onToggleTheme, "aria-label": darkMode ? "Attiva modalità giorno" : "Attiva modalità notte", "aria-describedby": themeTooltipId, "aria-pressed": darkMode, children: [_jsx("span", { className: "scv-theme-icon", "aria-hidden": "true", children: darkMode
|
|
673
|
+
? _jsxs("svg", { viewBox: "0 0 24 24", fill: "none", children: [_jsx("circle", { cx: "12", cy: "12", r: "3.75" }), _jsx("path", { d: "M12 2.25v2.1M12 19.65v2.1M21.75 12h-2.1M4.35 12h-2.1M18.9 5.1l-1.48 1.48M6.58 17.42 5.1 18.9M18.9 18.9l-1.48-1.48M6.58 6.58 5.1 5.1" })] })
|
|
674
|
+
: _jsx("svg", { viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M20.2 15.15A8.35 8.35 0 0 1 8.85 3.8 8.36 8.36 0 1 0 20.2 15.15Z" }) }) }), _jsx("span", { id: themeTooltipId, className: "scv-control-tooltip", role: "tooltip", children: darkMode ? "Attiva modalità giorno" : "Attiva modalità notte" })] })] }) }), _jsx("button", { type: "button", className: "scv-mobile-index-close", onClick: onRequestClose, "aria-label": "Chiudi indice", children: "\u00D7" })] }), _jsx("div", { className: "scv-index-grid", children: _jsxs("section", { className: "scv-index-cell", children: [_jsx("header", { children: _jsx("span", { children: "Indice" }) }), _jsx("div", { className: "scv-index-list", ref: indexListRef, children: !indexReady ? _jsx(LoadingRows, {}) : chapters.length === 0 ? _jsx("p", { className: "scv-index-empty", children: "L\u2019indice non \u00E8 disponibile." }) : _jsx("ul", { className: "scv-index-tree", children: chapters.map((chapter) => {
|
|
675
|
+
var _a;
|
|
676
|
+
const chapterParagraphs = (_a = paragraphsByChapter.get(chapter.summary.id)) !== null && _a !== void 0 ? _a : [];
|
|
677
|
+
const chapterActive = activeChapterId === chapter.summary.id;
|
|
678
|
+
const chapterOpen = sidebarMode ? sidebarExpanded.session === sidebarSession && sidebarExpanded.chapterId === chapter.summary.id : chapterActive;
|
|
679
|
+
return _jsxs("li", { className: `scv-index-tree-item scv-index-tree-item-level-0 ${chapterOpen ? "is-open" : ""}`, children: [renderEntryButton(chapter, 0, chapterActive, 0, chapterParagraphs.length > 0 ? chapterOpen : undefined), chapterParagraphs.length > 0 && _jsx("div", { className: `scv-index-children ${chapterOpen ? "is-open" : ""}`, "aria-hidden": !chapterOpen, children: _jsx("div", { className: "scv-index-children-inner", children: _jsx("ul", { className: "scv-index-children-list scv-index-paragraph-list", children: chapterParagraphs.map((paragraph) => {
|
|
680
|
+
var _a;
|
|
681
|
+
const paragraphSubparagraphs = (_a = subparagraphsByParagraph.get(paragraph.summary.id)) !== null && _a !== void 0 ? _a : [];
|
|
682
|
+
const paragraphActive = activeParagraphId === paragraph.summary.id;
|
|
683
|
+
const paragraphOpen = chapterOpen && (sidebarMode ? sidebarExpanded.session === sidebarSession && sidebarExpanded.paragraphId === paragraph.summary.id : paragraphActive);
|
|
684
|
+
return _jsxs("li", { className: `scv-index-tree-item scv-index-tree-item-level-1 ${paragraphOpen ? "is-open" : ""}`, children: [renderEntryButton(paragraph, 1, paragraphActive, chapterOpen ? 0 : -1, paragraphSubparagraphs.length > 0 ? paragraphOpen : undefined), paragraphSubparagraphs.length > 0 && _jsx("div", { className: `scv-index-children ${paragraphOpen ? "is-open" : ""}`, "aria-hidden": !paragraphOpen, children: _jsx("div", { className: "scv-index-children-inner", children: _jsx("ul", { className: "scv-index-children-list scv-index-subparagraph-list", children: paragraphSubparagraphs.map((subparagraph) => _jsx("li", { className: "scv-index-tree-item scv-index-tree-item-level-2", children: renderEntryButton(subparagraph, 2, activeSubparagraphId === subparagraph.summary.id, paragraphOpen ? 0 : -1) }, subparagraph.summary.id)) }) }) })] }, paragraph.summary.id);
|
|
685
|
+
}) }) }) })] }, chapter.summary.id);
|
|
686
|
+
}) }) })] }) })] });
|
|
687
|
+
});
|
|
688
|
+
function scheduleIdle(callback) {
|
|
689
|
+
const idleWindow = window;
|
|
690
|
+
if (idleWindow.requestIdleCallback) {
|
|
691
|
+
const id = idleWindow.requestIdleCallback(callback, { timeout: 1800 });
|
|
692
|
+
return () => { var _a; return (_a = idleWindow.cancelIdleCallback) === null || _a === void 0 ? void 0 : _a.call(idleWindow, id); };
|
|
693
|
+
}
|
|
694
|
+
const id = window.setTimeout(callback, 250);
|
|
695
|
+
return () => window.clearTimeout(id);
|
|
696
|
+
}
|
|
697
|
+
export function NormativeViewer({ defaultMode = "combined", dataBaseUrl = "/data/codes", assetsBaseUrl = "/assets", auxiliaryPanel, auxiliaryPanelLabel = "PDF ufficiale", auxiliaryPanelButtonText = "Apri", auxiliaryPanelDefaultVisible = false, auxiliaryPanelDesktopDefaultVisible = false, auxiliaryPanelModes, auxiliaryPanelKeepMounted = false, searchMaxResults = 12, className, annotationStore }) {
|
|
698
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
59
699
|
const [manifest, setManifest] = useState(null);
|
|
60
|
-
const [
|
|
61
|
-
const [
|
|
62
|
-
const [
|
|
63
|
-
const [
|
|
700
|
+
const [indexes, setIndexes] = useState(new Map());
|
|
701
|
+
const [loadedChunks, setLoadedChunks] = useState(new Map());
|
|
702
|
+
const [renderedChunkPaths, setRenderedChunkPaths] = useState(new Set());
|
|
703
|
+
const [requestedCircPaths, setRequestedCircPaths] = useState(new Set());
|
|
64
704
|
const [mode, setMode] = useState(defaultMode);
|
|
705
|
+
const [modeRevision, setModeRevision] = useState(0);
|
|
65
706
|
const [activeUnitId, setActiveUnitId] = useState(null);
|
|
707
|
+
const [historyPosition, setHistoryPosition] = useState({ index: 0, max: 0 });
|
|
66
708
|
const [relations, setRelations] = useState([]);
|
|
67
|
-
const [
|
|
68
|
-
const [
|
|
709
|
+
const [relationsLoaded, setRelationsLoaded] = useState(false);
|
|
710
|
+
const [crossReferenceIndex, setCrossReferenceIndex] = useState(null);
|
|
711
|
+
const [referencePreview, setReferencePreview] = useState(null);
|
|
712
|
+
const [citationSelection, setCitationSelection] = useState(null);
|
|
713
|
+
const [permalinkNotice, setPermalinkNotice] = useState(null);
|
|
69
714
|
const [query, setQuery] = useState("");
|
|
70
715
|
const [loadError, setLoadError] = useState(false);
|
|
71
|
-
const [
|
|
72
|
-
const [
|
|
716
|
+
const [contentLoadNotice, setContentLoadNotice] = useState(null);
|
|
717
|
+
const [mobileIndexOpen, setMobileIndexOpen] = useState(false);
|
|
718
|
+
const [mobileIndexSession, setMobileIndexSession] = useState(0);
|
|
719
|
+
const [darkMode, setDarkMode] = useState(null);
|
|
720
|
+
const [auxiliaryVisible, setAuxiliaryVisible] = useState(false);
|
|
721
|
+
const [annotations, setAnnotations] = useState([]);
|
|
722
|
+
const [annotationsLoading, setAnnotationsLoading] = useState(Boolean(annotationStore));
|
|
723
|
+
const [annotationError, setAnnotationError] = useState(null);
|
|
724
|
+
const [annotationMenu, setAnnotationMenu] = useState(null);
|
|
725
|
+
const [annotationEditor, setAnnotationEditor] = useState(null);
|
|
726
|
+
const [annotationBusy, setAnnotationBusy] = useState(false);
|
|
73
727
|
const searchRef = useRef(null);
|
|
74
|
-
const
|
|
75
|
-
const
|
|
728
|
+
const mobileIndexButtonRef = useRef(null);
|
|
729
|
+
const auxiliaryButtonRef = useRef(null);
|
|
730
|
+
const auxiliaryPaneRef = useRef(null);
|
|
731
|
+
const auxiliaryId = useId();
|
|
732
|
+
const navigationId = useId();
|
|
76
733
|
const requestedIdRef = useRef(null);
|
|
77
|
-
const
|
|
734
|
+
const requestedTargetRef = useRef(null);
|
|
735
|
+
const scrollRequestRef = useRef(null);
|
|
736
|
+
const activeIdRef = useRef(null);
|
|
737
|
+
const manualTargetRef = useRef(null);
|
|
738
|
+
const navigationGenerationRef = useRef(0);
|
|
739
|
+
const renderedChunkPathsRef = useRef(new Set());
|
|
740
|
+
const pendingScrollAnchorRef = useRef(null);
|
|
741
|
+
const initializedContextRef = useRef("");
|
|
742
|
+
const initializedOnceRef = useRef(false);
|
|
743
|
+
const historyNavigationRef = useRef(false);
|
|
744
|
+
const historySessionRef = useRef(crypto.randomUUID());
|
|
745
|
+
const historyPositionRef = useRef({ index: 0, max: 0 });
|
|
746
|
+
const pendingHistoryScrollRef = useRef(null);
|
|
747
|
+
const crossReferencePromiseRef = useRef(null);
|
|
748
|
+
const relationsPromiseRef = useRef(null);
|
|
749
|
+
const permalinkNoticeTimerRef = useRef(null);
|
|
750
|
+
const searchHighlightTimerRef = useRef(null);
|
|
751
|
+
const pendingSearchHighlightRef = useRef(null);
|
|
752
|
+
const referencePreviewRequestRef = useRef(0);
|
|
753
|
+
const touchReferenceRef = useRef(null);
|
|
754
|
+
const lastPointerTypeRef = useRef("");
|
|
755
|
+
const suppressAnnotationClickRef = useRef(false);
|
|
756
|
+
const longPressRef = useRef(null);
|
|
757
|
+
const textPaneRef = useRef(null);
|
|
758
|
+
const documentId = documentForMode(mode);
|
|
759
|
+
const documentIdRef = useRef(documentId);
|
|
78
760
|
const hasAuxiliary = Boolean(auxiliaryPanel);
|
|
761
|
+
const auxiliaryAvailable = hasAuxiliary && (auxiliaryPanelModes ? auxiliaryPanelModes.includes(mode) : mode !== "combined");
|
|
762
|
+
useEffect(() => {
|
|
763
|
+
var _a, _b;
|
|
764
|
+
window.history.replaceState(withViewerHistoryEntry(window.history.state, { session: historySessionRef.current, index: 0, scrollTop: (_b = (_a = textPaneRef.current) === null || _a === void 0 ? void 0 : _a.scrollTop) !== null && _b !== void 0 ? _b : 0 }), "", window.location.href);
|
|
765
|
+
}, []);
|
|
766
|
+
const saveHistoryScroll = useCallback(() => {
|
|
767
|
+
var _a, _b;
|
|
768
|
+
const entry = viewerHistoryEntry(window.history.state);
|
|
769
|
+
if ((entry === null || entry === void 0 ? void 0 : entry.session) !== historySessionRef.current)
|
|
770
|
+
return;
|
|
771
|
+
window.history.replaceState(withViewerHistoryEntry(window.history.state, Object.assign(Object.assign({}, entry), { scrollTop: (_b = (_a = textPaneRef.current) === null || _a === void 0 ? void 0 : _a.scrollTop) !== null && _b !== void 0 ? _b : 0 })), "", window.location.href);
|
|
772
|
+
}, []);
|
|
773
|
+
const pushViewerHistory = useCallback((nextMode, target) => {
|
|
774
|
+
const url = urlForViewerTarget(window.location.href, nextMode, defaultMode, target);
|
|
775
|
+
if (url.href === window.location.href)
|
|
776
|
+
return;
|
|
777
|
+
saveHistoryScroll();
|
|
778
|
+
const index = historyPositionRef.current.index + 1;
|
|
779
|
+
const position = { index, max: index };
|
|
780
|
+
historyPositionRef.current = position;
|
|
781
|
+
setHistoryPosition(position);
|
|
782
|
+
updateDeepLink(nextMode, target, defaultMode, "push", withViewerHistoryEntry(window.history.state, { session: historySessionRef.current, index, scrollTop: 0 }));
|
|
783
|
+
}, [defaultMode, saveHistoryScroll]);
|
|
79
784
|
useEffect(() => {
|
|
785
|
+
const tablet = window.matchMedia("(min-width: 992px)");
|
|
786
|
+
const desktop = window.matchMedia("(min-width: 1400px)");
|
|
787
|
+
const syncLayout = () => {
|
|
788
|
+
setAuxiliaryVisible(Boolean(hasAuxiliary && (desktop.matches ? auxiliaryPanelDesktopDefaultVisible || auxiliaryPanelDefaultVisible : false)));
|
|
789
|
+
setMobileIndexOpen(false);
|
|
790
|
+
};
|
|
791
|
+
syncLayout();
|
|
792
|
+
tablet.addEventListener("change", syncLayout);
|
|
793
|
+
desktop.addEventListener("change", syncLayout);
|
|
794
|
+
return () => {
|
|
795
|
+
tablet.removeEventListener("change", syncLayout);
|
|
796
|
+
desktop.removeEventListener("change", syncLayout);
|
|
797
|
+
};
|
|
798
|
+
}, [hasAuxiliary, auxiliaryPanelDefaultVisible, auxiliaryPanelDesktopDefaultVisible]);
|
|
799
|
+
const flashSearchTarget = useCallback((target) => {
|
|
800
|
+
var _a;
|
|
801
|
+
const root = textPaneRef.current;
|
|
802
|
+
const element = findViewerTarget(root, target);
|
|
803
|
+
if (!element)
|
|
804
|
+
return false;
|
|
805
|
+
(_a = root === null || root === void 0 ? void 0 : root.querySelector(".scv-search-target")) === null || _a === void 0 ? void 0 : _a.classList.remove("scv-search-target");
|
|
806
|
+
element.classList.add("scv-search-target");
|
|
807
|
+
if (searchHighlightTimerRef.current !== null)
|
|
808
|
+
window.clearTimeout(searchHighlightTimerRef.current);
|
|
809
|
+
searchHighlightTimerRef.current = window.setTimeout(() => {
|
|
810
|
+
element.classList.remove("scv-search-target");
|
|
811
|
+
searchHighlightTimerRef.current = null;
|
|
812
|
+
}, 2200);
|
|
813
|
+
return true;
|
|
814
|
+
}, []);
|
|
815
|
+
useEffect(() => () => {
|
|
816
|
+
if (searchHighlightTimerRef.current !== null)
|
|
817
|
+
window.clearTimeout(searchHighlightTimerRef.current);
|
|
818
|
+
if (permalinkNoticeTimerRef.current !== null)
|
|
819
|
+
window.clearTimeout(permalinkNoticeTimerRef.current);
|
|
820
|
+
}, []);
|
|
821
|
+
const reportChunkLoadFailure = useCallback((message = "Una parte del documento non è disponibile. Puoi continuare a consultare i contenuti già caricati.") => {
|
|
822
|
+
if (renderedChunkPathsRef.current.size === 0)
|
|
823
|
+
setLoadError(true);
|
|
824
|
+
else
|
|
825
|
+
setContentLoadNotice(message);
|
|
826
|
+
}, []);
|
|
827
|
+
useEffect(() => {
|
|
828
|
+
documentIdRef.current = documentId;
|
|
829
|
+
}, [documentId]);
|
|
830
|
+
useEffect(() => {
|
|
831
|
+
if (!annotationStore)
|
|
832
|
+
return;
|
|
833
|
+
let cancelled = false;
|
|
834
|
+
annotationStore.listAnnotations().then((items) => {
|
|
835
|
+
if (!cancelled) {
|
|
836
|
+
setAnnotations(items);
|
|
837
|
+
setAnnotationError(null);
|
|
838
|
+
}
|
|
839
|
+
}).catch((error) => {
|
|
840
|
+
if (!cancelled)
|
|
841
|
+
setAnnotationError(error instanceof Error ? error.message : "Annotazioni locali non disponibili.");
|
|
842
|
+
}).finally(() => { if (!cancelled)
|
|
843
|
+
setAnnotationsLoading(false); });
|
|
844
|
+
return () => { cancelled = true; };
|
|
845
|
+
}, [annotationStore]);
|
|
846
|
+
const notesByTarget = useMemo(() => {
|
|
847
|
+
var _a;
|
|
848
|
+
const grouped = new Map();
|
|
849
|
+
for (const annotation of annotations) {
|
|
850
|
+
if (annotation.type !== "note")
|
|
851
|
+
continue;
|
|
852
|
+
const key = annotationTargetKey(annotation.target);
|
|
853
|
+
const group = (_a = grouped.get(key)) !== null && _a !== void 0 ? _a : [];
|
|
854
|
+
group.push(annotation);
|
|
855
|
+
grouped.set(key, group);
|
|
856
|
+
}
|
|
857
|
+
return grouped;
|
|
858
|
+
}, [annotations]);
|
|
859
|
+
const openAnnotation = useCallback((annotation) => {
|
|
860
|
+
var _a;
|
|
861
|
+
setAnnotationMenu(null);
|
|
862
|
+
setAnnotationError(null);
|
|
863
|
+
setAnnotationEditor({ type: annotation.type, target: annotation.target, annotation, metadata: {
|
|
864
|
+
documentId: annotation.documentId, numbering: annotation.numbering, title: annotation.title,
|
|
865
|
+
automaticLabel: (_a = annotation.label) !== null && _a !== void 0 ? _a : automaticAnnotationLabel(annotation.target, annotation.numbering, annotation.title),
|
|
866
|
+
} });
|
|
867
|
+
}, []);
|
|
868
|
+
useEffect(() => {
|
|
869
|
+
const timer = window.setTimeout(() => {
|
|
870
|
+
try {
|
|
871
|
+
setDarkMode(window.localStorage.getItem("scv-theme") === "dark");
|
|
872
|
+
}
|
|
873
|
+
catch (_a) {
|
|
874
|
+
setDarkMode(false);
|
|
875
|
+
}
|
|
876
|
+
}, 0);
|
|
877
|
+
return () => window.clearTimeout(timer);
|
|
878
|
+
}, []);
|
|
879
|
+
useEffect(() => {
|
|
880
|
+
if (darkMode === null)
|
|
881
|
+
return;
|
|
882
|
+
try {
|
|
883
|
+
window.localStorage.setItem("scv-theme", darkMode ? "dark" : "light");
|
|
884
|
+
}
|
|
885
|
+
catch ( /* Storage can be unavailable in private or embedded browsing contexts. */_a) { /* Storage can be unavailable in private or embedded browsing contexts. */ }
|
|
886
|
+
}, [darkMode]);
|
|
887
|
+
useEffect(() => {
|
|
888
|
+
let cancelled = false;
|
|
80
889
|
loadManifest(dataBaseUrl).then((loaded) => {
|
|
890
|
+
var _a, _b;
|
|
891
|
+
if (cancelled)
|
|
892
|
+
return;
|
|
893
|
+
setIndexes(new Map());
|
|
894
|
+
setLoadedChunks(new Map());
|
|
895
|
+
setRenderedChunkPaths(new Set());
|
|
896
|
+
renderedChunkPathsRef.current = new Set();
|
|
897
|
+
setRequestedCircPaths(new Set());
|
|
898
|
+
setRelations([]);
|
|
899
|
+
setRelationsLoaded(false);
|
|
900
|
+
relationsPromiseRef.current = null;
|
|
901
|
+
setCrossReferenceIndex(null);
|
|
902
|
+
crossReferencePromiseRef.current = null;
|
|
903
|
+
setContentLoadNotice(null);
|
|
81
904
|
const url = new URL(window.location.href);
|
|
82
905
|
const requestedMode = url.searchParams.get("mode");
|
|
83
906
|
if (modeOptions.some((option) => option.id === requestedMode))
|
|
84
907
|
setMode(requestedMode);
|
|
85
|
-
|
|
908
|
+
requestedTargetRef.current = targetFromUrl(url);
|
|
909
|
+
pendingSearchHighlightRef.current = requestedTargetRef.current;
|
|
910
|
+
setCitationSelection(requestedTargetRef.current);
|
|
911
|
+
requestedIdRef.current = (_b = (_a = requestedTargetRef.current) === null || _a === void 0 ? void 0 : _a.unitId) !== null && _b !== void 0 ? _b : null;
|
|
86
912
|
setActiveUnitId(requestedIdRef.current);
|
|
87
913
|
setManifest(loaded);
|
|
88
|
-
}).catch(() =>
|
|
914
|
+
}).catch(() => { if (!cancelled)
|
|
915
|
+
setLoadError(true); });
|
|
916
|
+
return () => { cancelled = true; };
|
|
89
917
|
}, [dataBaseUrl]);
|
|
90
|
-
const documentId = documentForMode(mode);
|
|
91
918
|
useEffect(() => {
|
|
92
|
-
if (!manifest)
|
|
919
|
+
if (!manifest || indexes.has(documentId))
|
|
93
920
|
return;
|
|
94
921
|
let cancelled = false;
|
|
95
922
|
loadDocumentIndex(manifest, documentId, dataBaseUrl).then((loaded) => {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const requested = loaded.units.find((unit) => unit.id === requestedIdRef.current);
|
|
101
|
-
const initial = (_a = requested !== null && requested !== void 0 ? requested : loaded.units.find((unit) => unit.numbering.official === (documentId === "ntc2018" ? "4.1" : "C4.1"))) !== null && _a !== void 0 ? _a : loaded.units[0];
|
|
102
|
-
if (initial) {
|
|
103
|
-
requestedIdRef.current = initial.id;
|
|
104
|
-
setActiveUnitId(initial.id);
|
|
105
|
-
updateDeepLink(mode, initial.id, defaultMode);
|
|
106
|
-
}
|
|
107
|
-
}).catch(() => setLoadError(true));
|
|
923
|
+
if (!cancelled)
|
|
924
|
+
setIndexes((current) => new Map(current).set(documentId, loaded));
|
|
925
|
+
}).catch(() => { if (!cancelled)
|
|
926
|
+
setLoadError(true); });
|
|
108
927
|
return () => { cancelled = true; };
|
|
109
|
-
}, [dataBaseUrl,
|
|
110
|
-
const activeSummary = (_a = index === null || index === void 0 ? void 0 : index.units.find((unit) => unit.id === activeUnitId)) !== null && _a !== void 0 ? _a : null;
|
|
111
|
-
const activeChapterId = activeSummary ? ancestorIdAtDepth(activeSummary, 0) : null;
|
|
112
|
-
const activeParagraphId = activeSummary && depth(activeSummary) >= 1 ? ancestorIdAtDepth(activeSummary, 1) : null;
|
|
113
|
-
const activeSubparagraphId = activeSummary && depth(activeSummary) >= 2 ? ancestorIdAtDepth(activeSummary, 2) : null;
|
|
114
|
-
const scope = useMemo(() => {
|
|
115
|
-
var _a;
|
|
116
|
-
if (!index || !activeSummary || !activeChapterId) {
|
|
117
|
-
return { mode: "paragraph", summaries: [], chapterId: null, paragraphId: null };
|
|
118
|
-
}
|
|
119
|
-
const chapterSummaries = index.units.filter((unit) => containsUnit(unit, activeChapterId));
|
|
120
|
-
const chunkBytes = new Map(((_a = manifest === null || manifest === void 0 ? void 0 : manifest.chunks) !== null && _a !== void 0 ? _a : []).map((entry) => [entry.path, entry.bytes]));
|
|
121
|
-
const chapterBytes = [...new Set(chapterSummaries.map((unit) => unit.chunkPath))].reduce((total, path) => { var _a; return total + ((_a = chunkBytes.get(path)) !== null && _a !== void 0 ? _a : 0); }, 0);
|
|
122
|
-
const chapterCanBeContinuous = chapterSummaries.length <= maxContinuousChapterUnits && chapterBytes <= maxContinuousChapterBytes;
|
|
123
|
-
if (depth(activeSummary) === 0 && chapterCanBeContinuous) {
|
|
124
|
-
return { mode: "chapter", summaries: chapterSummaries, chapterId: activeChapterId, paragraphId: null };
|
|
125
|
-
}
|
|
126
|
-
if (depth(activeSummary) === 0) {
|
|
127
|
-
const paragraphSummaries = index.units.filter((unit) => unit.hierarchy.parentId === activeChapterId && depth(unit) === 1);
|
|
128
|
-
const chapter = index.units.find((unit) => unit.id === activeChapterId);
|
|
129
|
-
return { mode: "paragraph", summaries: [chapter, ...paragraphSummaries].filter(Boolean), chapterId: activeChapterId, paragraphId: null };
|
|
130
|
-
}
|
|
131
|
-
const paragraphId = activeParagraphId !== null && activeParagraphId !== void 0 ? activeParagraphId : activeChapterId;
|
|
132
|
-
return { mode: "paragraph", summaries: index.units.filter((unit) => containsUnit(unit, paragraphId)), chapterId: activeChapterId, paragraphId };
|
|
133
|
-
}, [activeChapterId, activeParagraphId, activeSummary, index, manifest]);
|
|
134
|
-
const scopePaths = useMemo(() => [...new Set(scope.summaries.map((unit) => unit.chunkPath))], [scope.summaries]);
|
|
135
|
-
const scopeKey = useMemo(() => scope.summaries.map((unit) => unit.id).join("|"), [scope.summaries]);
|
|
136
|
-
const scopeLoading = scope.summaries.length > 0 && loadedScopeKey !== scopeKey;
|
|
928
|
+
}, [dataBaseUrl, documentId, indexes, manifest]);
|
|
137
929
|
useEffect(() => {
|
|
138
|
-
if (!
|
|
930
|
+
if (!manifest || mode !== "combined" || indexes.has("circ2019"))
|
|
139
931
|
return;
|
|
140
932
|
let cancelled = false;
|
|
141
|
-
|
|
933
|
+
loadDocumentIndex(manifest, "circ2019", dataBaseUrl).then((loaded) => {
|
|
142
934
|
if (!cancelled)
|
|
143
|
-
|
|
144
|
-
}).catch(() =>
|
|
935
|
+
setIndexes((current) => new Map(current).set("circ2019", loaded));
|
|
936
|
+
}).catch(() => { if (!cancelled)
|
|
937
|
+
setContentLoadNotice("L’indice della Circolare non è disponibile; la lettura NTC resta utilizzabile."); });
|
|
145
938
|
return () => { cancelled = true; };
|
|
146
|
-
}, [
|
|
939
|
+
}, [dataBaseUrl, indexes, manifest, mode]);
|
|
940
|
+
const ensureRelations = useCallback(async () => {
|
|
941
|
+
if (relationsLoaded)
|
|
942
|
+
return relations;
|
|
943
|
+
if (!manifest)
|
|
944
|
+
return [];
|
|
945
|
+
let pending = relationsPromiseRef.current;
|
|
946
|
+
if (!pending) {
|
|
947
|
+
pending = loadRelations(manifest, dataBaseUrl).then(({ relations: loadedRelations }) => {
|
|
948
|
+
setRelations(loadedRelations);
|
|
949
|
+
setRelationsLoaded(true);
|
|
950
|
+
return loadedRelations;
|
|
951
|
+
});
|
|
952
|
+
relationsPromiseRef.current = pending;
|
|
953
|
+
pending.catch(() => { if (relationsPromiseRef.current === pending)
|
|
954
|
+
relationsPromiseRef.current = null; });
|
|
955
|
+
}
|
|
956
|
+
return pending;
|
|
957
|
+
}, [dataBaseUrl, manifest, relations, relationsLoaded]);
|
|
147
958
|
useEffect(() => {
|
|
148
|
-
if (
|
|
959
|
+
if (mode !== "combined" || relationsLoaded)
|
|
149
960
|
return;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
961
|
+
void ensureRelations().catch(() => setContentLoadNotice("Le relazioni NTC–Circolare non sono temporaneamente disponibili."));
|
|
962
|
+
}, [ensureRelations, mode, relationsLoaded]);
|
|
963
|
+
const ensureCrossReferenceIndex = useCallback(async () => {
|
|
964
|
+
if (crossReferenceIndex)
|
|
965
|
+
return crossReferenceIndex;
|
|
966
|
+
if (!manifest)
|
|
967
|
+
throw new Error("Manifest non ancora disponibile.");
|
|
968
|
+
let pending = crossReferencePromiseRef.current;
|
|
969
|
+
if (!pending) {
|
|
970
|
+
pending = loadCrossReferenceIndex(manifest, dataBaseUrl).then((loaded) => {
|
|
971
|
+
setCrossReferenceIndex(loaded);
|
|
972
|
+
return loaded;
|
|
159
973
|
});
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
974
|
+
crossReferencePromiseRef.current = pending;
|
|
975
|
+
pending.catch(() => { if (crossReferencePromiseRef.current === pending)
|
|
976
|
+
crossReferencePromiseRef.current = null; });
|
|
977
|
+
}
|
|
978
|
+
return pending;
|
|
979
|
+
}, [crossReferenceIndex, dataBaseUrl, manifest]);
|
|
980
|
+
const index = (_a = indexes.get(documentId)) !== null && _a !== void 0 ? _a : null;
|
|
981
|
+
const circIndex = (_b = indexes.get("circ2019")) !== null && _b !== void 0 ? _b : null;
|
|
982
|
+
const lookup = useMemo(() => index ? createDocumentLookup(index) : null, [index]);
|
|
983
|
+
const circLookup = useMemo(() => circIndex ? createDocumentLookup(circIndex) : null, [circIndex]);
|
|
984
|
+
const crossReferenceLookup = useMemo(() => crossReferenceIndex ? createCrossReferenceLookup(crossReferenceIndex) : null, [crossReferenceIndex]);
|
|
985
|
+
const search = useViewerSearch({ query, mode, manifest, dataBaseUrl, lookup, circLookup, maxResults: searchMaxResults });
|
|
986
|
+
const combinedPlan = useMemo(() => buildCombinedPlan(mode === "combined" ? index : null, mode === "combined" ? circIndex : null, relations), [circIndex, index, mode, relations]);
|
|
987
|
+
const primaryRenderedPaths = useMemo(() => { var _a; return new Set((_a = lookup === null || lookup === void 0 ? void 0 : lookup.chunkPaths.filter((path) => renderedChunkPaths.has(path))) !== null && _a !== void 0 ? _a : []); }, [lookup, renderedChunkPaths]);
|
|
988
|
+
const rememberChunk = useCallback(async (path) => {
|
|
989
|
+
const chunk = await loadChunk(path, dataBaseUrl);
|
|
990
|
+
setLoadedChunks((current) => current.get(path) === chunk ? current : new Map(current).set(path, chunk));
|
|
991
|
+
return chunk;
|
|
992
|
+
}, [dataBaseUrl]);
|
|
993
|
+
const preserveActiveScrollAnchor = useCallback((generation) => {
|
|
994
|
+
const root = textPaneRef.current;
|
|
995
|
+
const anchorId = activeIdRef.current;
|
|
996
|
+
const anchor = anchorId ? findTextUnit(root, anchorId) : null;
|
|
997
|
+
if (!root || !anchor || !anchorId)
|
|
171
998
|
return;
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
|
|
999
|
+
pendingScrollAnchorRef.current = {
|
|
1000
|
+
id: anchorId,
|
|
1001
|
+
top: anchor.getBoundingClientRect().top - root.getBoundingClientRect().top,
|
|
1002
|
+
generation,
|
|
1003
|
+
};
|
|
1004
|
+
}, []);
|
|
1005
|
+
const mountPrimaryChunk = useCallback(async (path, replace, preserveAnchor = false, generation = navigationGenerationRef.current) => {
|
|
1006
|
+
const chunk = await rememberChunk(path);
|
|
1007
|
+
if (generation !== navigationGenerationRef.current || chunk.document !== documentIdRef.current)
|
|
1008
|
+
return;
|
|
1009
|
+
const current = renderedChunkPathsRef.current;
|
|
1010
|
+
if (!replace && current.has(path))
|
|
1011
|
+
return;
|
|
1012
|
+
if (preserveAnchor)
|
|
1013
|
+
preserveActiveScrollAnchor(generation);
|
|
1014
|
+
const next = replace ? new Set([path]) : new Set(current).add(path);
|
|
1015
|
+
renderedChunkPathsRef.current = next;
|
|
1016
|
+
setRenderedChunkPaths(next);
|
|
1017
|
+
}, [preserveActiveScrollAnchor, rememberChunk]);
|
|
1018
|
+
const mountPrimaryWindow = useCallback(async (summary, replace, generation) => {
|
|
1019
|
+
await mountPrimaryChunk(summary.chunkPath, replace, false, generation);
|
|
1020
|
+
if (!replace || generation !== navigationGenerationRef.current)
|
|
1021
|
+
return;
|
|
1022
|
+
if (!lookup)
|
|
1023
|
+
return;
|
|
1024
|
+
const window = navigationChunkWindow(lookup, summary.chunkPath);
|
|
1025
|
+
if (window.previous)
|
|
1026
|
+
void mountPrimaryChunk(window.previous, false, true, generation).catch(() => reportChunkLoadFailure("Il chunk precedente non è disponibile; il target resta consultabile."));
|
|
1027
|
+
if (window.next)
|
|
1028
|
+
void mountPrimaryChunk(window.next, false, false, generation).catch(() => reportChunkLoadFailure("Il chunk successivo non è disponibile; il target resta consultabile."));
|
|
1029
|
+
}, [lookup, mountPrimaryChunk, reportChunkLoadFailure]);
|
|
1030
|
+
const requiredCombinedCircPaths = useMemo(() => {
|
|
1031
|
+
var _a;
|
|
1032
|
+
const paths = new Set(requestedCircPaths);
|
|
1033
|
+
if (mode !== "combined")
|
|
1034
|
+
return paths;
|
|
1035
|
+
for (const primaryPath of primaryRenderedPaths) {
|
|
1036
|
+
for (const path of (_a = combinedPlan.circPathsByPrimaryPath.get(primaryPath)) !== null && _a !== void 0 ? _a : [])
|
|
1037
|
+
paths.add(path);
|
|
1038
|
+
}
|
|
1039
|
+
for (const edge of relations) {
|
|
1040
|
+
const target = lookup === null || lookup === void 0 ? void 0 : lookup.unitById.get(edge.targetUnitId);
|
|
1041
|
+
if (target && primaryRenderedPaths.has(target.chunkPath))
|
|
1042
|
+
paths.add(edge.sourceChunkPath);
|
|
1043
|
+
}
|
|
1044
|
+
return paths;
|
|
1045
|
+
}, [combinedPlan.circPathsByPrimaryPath, lookup, mode, primaryRenderedPaths, relations, requestedCircPaths]);
|
|
179
1046
|
useEffect(() => {
|
|
180
|
-
if (mode !== "combined"
|
|
1047
|
+
if (mode !== "combined")
|
|
1048
|
+
return;
|
|
1049
|
+
const missing = [...requiredCombinedCircPaths].filter((path) => !loadedChunks.has(path));
|
|
1050
|
+
if (missing.length === 0)
|
|
181
1051
|
return;
|
|
182
1052
|
let cancelled = false;
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
Promise.all([...new Set(incoming.map((edge) => edge.sourceChunkPath))].map(async (path) => [path, await loadChunk(path, dataBaseUrl)])).then((loadedChunks) => {
|
|
186
|
-
var _a;
|
|
1053
|
+
const generation = navigationGenerationRef.current;
|
|
1054
|
+
Promise.all(missing.map((path) => loadChunk(path, dataBaseUrl))).then((chunks) => {
|
|
187
1055
|
if (cancelled)
|
|
188
1056
|
return;
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const group = (_a = grouped.get(edge.targetUnitId)) !== null && _a !== void 0 ? _a : [];
|
|
197
|
-
group.push({ edge, unit, chunk: sourceChunk });
|
|
198
|
-
grouped.set(edge.targetUnitId, group);
|
|
199
|
-
}
|
|
200
|
-
for (const group of grouped.values())
|
|
201
|
-
group.sort((left, right) => left.unit.numbering.sortKey.localeCompare(right.unit.numbering.sortKey, "it", { numeric: true }));
|
|
202
|
-
setRelatedByTarget(grouped);
|
|
1057
|
+
if (generation === navigationGenerationRef.current)
|
|
1058
|
+
preserveActiveScrollAnchor(generation);
|
|
1059
|
+
setLoadedChunks((current) => {
|
|
1060
|
+
const next = new Map(current);
|
|
1061
|
+
missing.forEach((path, position) => next.set(path, chunks[position]));
|
|
1062
|
+
return next;
|
|
1063
|
+
});
|
|
203
1064
|
}).catch(() => { if (!cancelled)
|
|
204
|
-
|
|
1065
|
+
setContentLoadNotice("Alcuni contenuti correlati della Circolare non sono disponibili."); });
|
|
205
1066
|
return () => { cancelled = true; };
|
|
206
|
-
}, [dataBaseUrl, mode,
|
|
1067
|
+
}, [dataBaseUrl, loadedChunks, mode, preserveActiveScrollAnchor, requiredCombinedCircPaths]);
|
|
1068
|
+
const documentRecords = useMemo(() => recordsForPaths(index, lookup, primaryRenderedPaths, loadedChunks), [index, loadedChunks, lookup, primaryRenderedPaths]);
|
|
1069
|
+
const fallbackIds = useMemo(() => new Set(combinedPlan.fallbackSummaries.map((summary) => summary.id)), [combinedPlan.fallbackSummaries]);
|
|
1070
|
+
const circRecords = useMemo(() => {
|
|
1071
|
+
if (mode !== "combined" || !index || !circIndex || !circLookup)
|
|
1072
|
+
return [];
|
|
1073
|
+
const primaryBases = new Set(index.units.map((summary) => baseNumbering(summary.numbering.official)));
|
|
1074
|
+
return recordsForPaths(circIndex, circLookup, requiredCombinedCircPaths, loadedChunks).filter(({ unit }) => fallbackIds.has(unit.id) && (hasUnitContent(unit) || !primaryBases.has(baseNumbering(unit.numbering.official))));
|
|
1075
|
+
}, [circIndex, circLookup, fallbackIds, index, loadedChunks, mode, requiredCombinedCircPaths]);
|
|
1076
|
+
const relatedByTarget = useMemo(() => {
|
|
1077
|
+
var _a;
|
|
1078
|
+
const grouped = new Map();
|
|
1079
|
+
if (mode !== "combined")
|
|
1080
|
+
return grouped;
|
|
1081
|
+
for (const edge of relations) {
|
|
1082
|
+
const target = lookup === null || lookup === void 0 ? void 0 : lookup.unitById.get(edge.targetUnitId);
|
|
1083
|
+
if (!target || !primaryRenderedPaths.has(target.chunkPath))
|
|
1084
|
+
continue;
|
|
1085
|
+
const sourceChunk = loadedChunks.get(edge.sourceChunkPath);
|
|
1086
|
+
const unit = sourceChunk ? unitsForChunk(sourceChunk).get(edge.sourceUnitId) : null;
|
|
1087
|
+
if (!sourceChunk || !unit)
|
|
1088
|
+
continue;
|
|
1089
|
+
const group = (_a = grouped.get(edge.targetUnitId)) !== null && _a !== void 0 ? _a : [];
|
|
1090
|
+
group.push({ edge, unit, chunk: sourceChunk });
|
|
1091
|
+
grouped.set(edge.targetUnitId, group);
|
|
1092
|
+
}
|
|
1093
|
+
for (const group of grouped.values())
|
|
1094
|
+
group.sort((left, right) => compareNumbering(left.unit, right.unit));
|
|
1095
|
+
return grouped;
|
|
1096
|
+
}, [loadedChunks, lookup, mode, primaryRenderedPaths, relations]);
|
|
1097
|
+
const renderRecords = useMemo(() => [...documentRecords, ...circRecords].sort((left, right) => compareBaseNumbering(left.summary, right.summary)), [circRecords, documentRecords]);
|
|
1098
|
+
const navigationEntries = useMemo(() => {
|
|
1099
|
+
if (!index)
|
|
1100
|
+
return [];
|
|
1101
|
+
const primaryBases = new Set(index.units.map((summary) => baseNumbering(summary.numbering.official)));
|
|
1102
|
+
const summaries = mode === "combined"
|
|
1103
|
+
? [...index.units, ...combinedPlan.fallbackSummaries].filter((summary) => summary.document !== "circ2019" || !primaryBases.has(baseNumbering(summary.numbering.official)))
|
|
1104
|
+
: index.units;
|
|
1105
|
+
const entries = summaries.map((summary) => navigationEntry(summary));
|
|
1106
|
+
if (mode !== "combined")
|
|
1107
|
+
return entries.sort((left, right) => compareBaseNumbering(left.summary, right.summary));
|
|
1108
|
+
const entryIdByBase = new Map(entries.map((entry) => [entry.baseNumber, entry.summary.id]));
|
|
1109
|
+
return entries.map((entry) => {
|
|
1110
|
+
var _a;
|
|
1111
|
+
const parentBase = parentNumbering(entry.baseNumber);
|
|
1112
|
+
const parentId = parentBase ? (_a = entryIdByBase.get(parentBase)) !== null && _a !== void 0 ? _a : null : null;
|
|
1113
|
+
return entry.parentId === parentId ? entry : Object.assign(Object.assign({}, entry), { parentId });
|
|
1114
|
+
}).sort((left, right) => compareBaseNumbering(left.summary, right.summary));
|
|
1115
|
+
}, [combinedPlan.fallbackSummaries, index, mode]);
|
|
1116
|
+
const entryById = useMemo(() => new Map(navigationEntries.map((entry) => [entry.summary.id, entry])), [navigationEntries]);
|
|
1117
|
+
const recordById = useMemo(() => new Map(renderRecords.map((record) => [record.unit.id, record])), [renderRecords]);
|
|
1118
|
+
const summaryById = useMemo(() => new Map([...indexes.values()].flatMap((documentIndex) => documentIndex.units).map((summary) => [summary.id, summary])), [indexes]);
|
|
1119
|
+
const annotationRanks = useMemo(() => annotationPositionRanks(indexes.values()), [indexes]);
|
|
1120
|
+
const revealSummary = useCallback(async (summary, replace = false, generation = navigationGenerationRef.current) => {
|
|
1121
|
+
var _a;
|
|
1122
|
+
if (generation !== navigationGenerationRef.current)
|
|
1123
|
+
return;
|
|
1124
|
+
scrollRequestRef.current = ((_a = requestedTargetRef.current) === null || _a === void 0 ? void 0 : _a.unitId) === summary.id ? requestedTargetRef.current : { kind: "unit", unitId: summary.id };
|
|
1125
|
+
if (summary.document === documentIdRef.current) {
|
|
1126
|
+
await mountPrimaryWindow(summary, replace, generation);
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
if (mode !== "combined" || !lookup)
|
|
1130
|
+
return;
|
|
1131
|
+
const explicitTarget = relationTargets(summary.id, relations)[0];
|
|
1132
|
+
if (explicitTarget) {
|
|
1133
|
+
const target = lookup.unitById.get(explicitTarget.targetUnitId);
|
|
1134
|
+
if (target) {
|
|
1135
|
+
setRequestedCircPaths((current) => replace ? new Set([summary.chunkPath]) : new Set(current).add(summary.chunkPath));
|
|
1136
|
+
await Promise.all([rememberChunk(summary.chunkPath), mountPrimaryWindow(target, replace, generation)]);
|
|
1137
|
+
}
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
const matchingPrimary = matchingPrimarySummary(summary, lookup);
|
|
1141
|
+
if (matchingPrimary) {
|
|
1142
|
+
scrollRequestRef.current = { kind: "unit", unitId: matchingPrimary.id };
|
|
1143
|
+
await mountPrimaryWindow(matchingPrimary, replace, generation);
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
const anchor = combinedPlan.primaryAnchorByFallbackId.get(summary.id);
|
|
1147
|
+
if (!anchor)
|
|
1148
|
+
return;
|
|
1149
|
+
setRequestedCircPaths((current) => replace ? new Set([summary.chunkPath]) : new Set(current).add(summary.chunkPath));
|
|
1150
|
+
await Promise.all([rememberChunk(summary.chunkPath), mountPrimaryWindow(anchor, replace, generation)]);
|
|
1151
|
+
}, [combinedPlan.primaryAnchorByFallbackId, lookup, mode, mountPrimaryWindow, relations, rememberChunk]);
|
|
207
1152
|
useEffect(() => {
|
|
208
|
-
|
|
1153
|
+
var _a, _b, _c, _d, _e;
|
|
1154
|
+
if (!index || !lookup)
|
|
209
1155
|
return;
|
|
210
|
-
|
|
211
|
-
|
|
1156
|
+
const contextKey = `${mode}:${modeRevision}:${index.document}`;
|
|
1157
|
+
if (initializedContextRef.current === contextKey)
|
|
1158
|
+
return;
|
|
1159
|
+
const requestedId = requestedIdRef.current;
|
|
1160
|
+
let initial = requestedId ? (_a = lookup.unitById.get(requestedId)) !== null && _a !== void 0 ? _a : null : null;
|
|
1161
|
+
let revealInitial = initial;
|
|
1162
|
+
if (!initial && mode === "combined" && (requestedId === null || requestedId === void 0 ? void 0 : requestedId.includes(":circ2019:"))) {
|
|
1163
|
+
if (!circLookup || !relationsLoaded)
|
|
1164
|
+
return;
|
|
1165
|
+
const requestedCirc = (_b = circLookup.unitById.get(requestedId)) !== null && _b !== void 0 ? _b : null;
|
|
1166
|
+
const explicitTarget = requestedCirc ? relationTargets(requestedCirc.id, relations)[0] : null;
|
|
1167
|
+
const explicitPrimary = explicitTarget ? (_c = lookup.unitById.get(explicitTarget.targetUnitId)) !== null && _c !== void 0 ? _c : null : null;
|
|
1168
|
+
const matchingPrimary = requestedCirc ? matchingPrimarySummary(requestedCirc, lookup) : null;
|
|
1169
|
+
initial = (_d = explicitPrimary !== null && explicitPrimary !== void 0 ? explicitPrimary : matchingPrimary) !== null && _d !== void 0 ? _d : requestedCirc;
|
|
1170
|
+
revealInitial = explicitPrimary || !matchingPrimary ? requestedCirc : matchingPrimary;
|
|
1171
|
+
}
|
|
1172
|
+
initial !== null && initial !== void 0 ? initial : (initial = initialUnitForIndex(index, requestedId));
|
|
1173
|
+
revealInitial !== null && revealInitial !== void 0 ? revealInitial : (revealInitial = initial);
|
|
1174
|
+
if (!initial || !revealInitial)
|
|
1175
|
+
return;
|
|
1176
|
+
initializedContextRef.current = contextKey;
|
|
1177
|
+
requestedIdRef.current = initial.id;
|
|
1178
|
+
activeIdRef.current = initial.id;
|
|
1179
|
+
setActiveUnitId(initial.id);
|
|
1180
|
+
const requestedTarget = ((_e = requestedTargetRef.current) === null || _e === void 0 ? void 0 : _e.unitId) === revealInitial.id ? requestedTargetRef.current : { kind: "unit", unitId: initial.id };
|
|
1181
|
+
if (!historyNavigationRef.current) {
|
|
1182
|
+
if (initializedOnceRef.current)
|
|
1183
|
+
pushViewerHistory(mode, requestedTarget);
|
|
1184
|
+
else
|
|
1185
|
+
updateDeepLink(mode, requestedTarget, defaultMode);
|
|
1186
|
+
}
|
|
1187
|
+
initializedOnceRef.current = true;
|
|
1188
|
+
historyNavigationRef.current = false;
|
|
1189
|
+
const generation = ++navigationGenerationRef.current;
|
|
1190
|
+
pendingScrollAnchorRef.current = null;
|
|
1191
|
+
void revealSummary(revealInitial, primaryRenderedPaths.size === 0 || revealInitial.document !== documentId, generation).catch(() => reportChunkLoadFailure());
|
|
1192
|
+
}, [circLookup, defaultMode, documentId, index, lookup, mode, modeRevision, primaryRenderedPaths.size, pushViewerHistory, relations, relationsLoaded, reportChunkLoadFailure, revealSummary]);
|
|
212
1193
|
useEffect(() => {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
1194
|
+
if (!lookup || !activeUnitId)
|
|
1195
|
+
return;
|
|
1196
|
+
const active = lookup.unitById.get(activeUnitId);
|
|
1197
|
+
if (!active)
|
|
1198
|
+
return;
|
|
1199
|
+
const candidates = new Set();
|
|
1200
|
+
const { previous, next } = adjacentChunkPaths(lookup, active.chunkPath);
|
|
1201
|
+
if (previous)
|
|
1202
|
+
candidates.add(previous);
|
|
1203
|
+
if (next)
|
|
1204
|
+
candidates.add(next);
|
|
1205
|
+
if (candidates.size === 0)
|
|
1206
|
+
return;
|
|
1207
|
+
return scheduleIdle(() => { for (const path of candidates)
|
|
1208
|
+
void loadChunk(path, dataBaseUrl).catch(() => undefined); });
|
|
1209
|
+
}, [activeUnitId, dataBaseUrl, lookup]);
|
|
1210
|
+
useLayoutEffect(() => {
|
|
1211
|
+
const root = textPaneRef.current;
|
|
1212
|
+
const pendingAnchor = pendingScrollAnchorRef.current;
|
|
1213
|
+
if (root && pendingAnchor && pendingAnchor.generation === navigationGenerationRef.current) {
|
|
1214
|
+
const anchor = findTextUnit(root, pendingAnchor.id);
|
|
1215
|
+
if (anchor) {
|
|
1216
|
+
const nextTop = anchor.getBoundingClientRect().top - root.getBoundingClientRect().top;
|
|
1217
|
+
root.scrollTop += nextTop - pendingAnchor.top;
|
|
218
1218
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
1219
|
+
pendingScrollAnchorRef.current = null;
|
|
1220
|
+
}
|
|
1221
|
+
const target = scrollRequestRef.current;
|
|
1222
|
+
if (!target || renderRecords.length === 0)
|
|
1223
|
+
return;
|
|
1224
|
+
if (!scrollViewerTarget(root, target)) {
|
|
1225
|
+
if (target.kind === "unit")
|
|
1226
|
+
return;
|
|
1227
|
+
const unitTarget = { kind: "unit", unitId: target.unitId };
|
|
1228
|
+
if (!scrollViewerTarget(root, unitTarget))
|
|
1229
|
+
return;
|
|
1230
|
+
scrollRequestRef.current = null;
|
|
1231
|
+
requestedTargetRef.current = unitTarget;
|
|
1232
|
+
updateDeepLink(mode, unitTarget, defaultMode);
|
|
1233
|
+
setContentLoadNotice("Il blocco o asset richiesto non esiste; è stata aperta l’unità normativa corrispondente.");
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
scrollRequestRef.current = null;
|
|
1237
|
+
if (pendingHistoryScrollRef.current !== null && root) {
|
|
1238
|
+
root.scrollTop = pendingHistoryScrollRef.current;
|
|
1239
|
+
pendingHistoryScrollRef.current = null;
|
|
1240
|
+
}
|
|
1241
|
+
if (sameViewerTarget(pendingSearchHighlightRef.current, target) && flashSearchTarget(target))
|
|
1242
|
+
pendingSearchHighlightRef.current = null;
|
|
1243
|
+
manualTargetRef.current = { id: target.unitId, expires: performance.now() + 500 };
|
|
1244
|
+
}, [auxiliaryAvailable, auxiliaryVisible, defaultMode, flashSearchTarget, mode, relatedByTarget, renderRecords]);
|
|
1245
|
+
const navigationRef = useRef({ entryById, lookup, mode, relations, primaryAnchorByFallbackId: combinedPlan.primaryAnchorByFallbackId });
|
|
225
1246
|
useEffect(() => {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
if (
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
1247
|
+
navigationRef.current = { entryById, lookup, mode, relations, primaryAnchorByFallbackId: combinedPlan.primaryAnchorByFallbackId };
|
|
1248
|
+
}, [combinedPlan.primaryAnchorByFallbackId, entryById, lookup, mode, relations]);
|
|
1249
|
+
const loadAdjacentAtUnit = useCallback((unitId) => {
|
|
1250
|
+
const current = navigationRef.current;
|
|
1251
|
+
const adjacent = progressiveChunkTargetsForVisibleUnit(current.lookup, unitId, current.primaryAnchorByFallbackId);
|
|
1252
|
+
if (adjacent.previous)
|
|
1253
|
+
void mountPrimaryChunk(adjacent.previous, false, true).catch(() => reportChunkLoadFailure("Il chunk precedente non è disponibile; il resto del documento rimane consultabile."));
|
|
1254
|
+
if (adjacent.next)
|
|
1255
|
+
void mountPrimaryChunk(adjacent.next, false).catch(() => reportChunkLoadFailure("Il chunk successivo non è disponibile; il resto del documento rimane consultabile."));
|
|
1256
|
+
}, [mountPrimaryChunk, reportChunkLoadFailure]);
|
|
1257
|
+
const onVisibleUnit = useCallback((nextId) => {
|
|
1258
|
+
const guard = manualTargetRef.current;
|
|
1259
|
+
if (guard && performance.now() < guard.expires && guard.id !== nextId)
|
|
1260
|
+
return;
|
|
1261
|
+
if ((guard === null || guard === void 0 ? void 0 : guard.id) === nextId || (guard && performance.now() >= guard.expires))
|
|
1262
|
+
manualTargetRef.current = null;
|
|
1263
|
+
if (nextId !== activeIdRef.current) {
|
|
1264
|
+
setCitationSelection(null);
|
|
1265
|
+
activeIdRef.current = nextId;
|
|
1266
|
+
requestedIdRef.current = nextId;
|
|
1267
|
+
requestedTargetRef.current = { kind: "unit", unitId: nextId };
|
|
1268
|
+
setActiveUnitId(nextId);
|
|
1269
|
+
updateDeepLink(navigationRef.current.mode, requestedTargetRef.current, defaultMode);
|
|
1270
|
+
}
|
|
1271
|
+
loadAdjacentAtUnit(nextId);
|
|
1272
|
+
}, [defaultMode, loadAdjacentAtUnit]);
|
|
1273
|
+
useVisibleUnitObserver(textPaneRef, renderRecords, onVisibleUnit);
|
|
1274
|
+
const selectUnit = useCallback((unit) => {
|
|
1275
|
+
const target = { kind: "unit", unitId: unit.id };
|
|
1276
|
+
setCitationSelection(null);
|
|
1277
|
+
setMobileIndexOpen(false);
|
|
1278
|
+
const generation = ++navigationGenerationRef.current;
|
|
1279
|
+
pendingScrollAnchorRef.current = null;
|
|
1280
|
+
setContentLoadNotice(null);
|
|
260
1281
|
requestedIdRef.current = unit.id;
|
|
1282
|
+
requestedTargetRef.current = target;
|
|
1283
|
+
pendingSearchHighlightRef.current = target;
|
|
1284
|
+
manualTargetRef.current = { id: unit.id, expires: performance.now() + 500 };
|
|
1285
|
+
pushViewerHistory(navigationRef.current.mode, target);
|
|
1286
|
+
const present = scrollTextUnit(textPaneRef.current, unit.id);
|
|
1287
|
+
activeIdRef.current = unit.id;
|
|
261
1288
|
setActiveUnitId(unit.id);
|
|
262
|
-
|
|
263
|
-
|
|
1289
|
+
if (present) {
|
|
1290
|
+
if (flashSearchTarget(target))
|
|
1291
|
+
pendingSearchHighlightRef.current = null;
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
const currentLookup = navigationRef.current.lookup;
|
|
1295
|
+
const nearMountedWindow = currentLookup ? isChunkNearRenderedWindow(currentLookup, unit.chunkPath, primaryRenderedPaths) : false;
|
|
1296
|
+
void revealSummary(unit, !nearMountedWindow, generation).catch(() => reportChunkLoadFailure());
|
|
1297
|
+
}, [flashSearchTarget, primaryRenderedPaths, pushViewerHistory, reportChunkLoadFailure, revealSummary]);
|
|
1298
|
+
const navigateViewerTarget = useCallback(async (target, action = "push", forcedMode) => {
|
|
1299
|
+
var _a;
|
|
1300
|
+
setCitationSelection(target);
|
|
1301
|
+
setMobileIndexOpen(false);
|
|
1302
|
+
pendingSearchHighlightRef.current = target;
|
|
1303
|
+
const generation = ++navigationGenerationRef.current;
|
|
1304
|
+
pendingScrollAnchorRef.current = null;
|
|
1305
|
+
setContentLoadNotice(null);
|
|
1306
|
+
const targetDocument = documentFromUnitId(target.unitId);
|
|
1307
|
+
const nextMode = forcedMode !== null && forcedMode !== void 0 ? forcedMode : (mode === "combined" || documentForMode(mode) === targetDocument ? mode : targetDocument === "ntc2018" ? "ntc" : "circ");
|
|
1308
|
+
requestedIdRef.current = target.unitId;
|
|
1309
|
+
requestedTargetRef.current = target;
|
|
1310
|
+
scrollRequestRef.current = target;
|
|
1311
|
+
manualTargetRef.current = { id: target.unitId, expires: performance.now() + 700 };
|
|
1312
|
+
if (action === "push")
|
|
1313
|
+
pushViewerHistory(nextMode, target);
|
|
1314
|
+
else if (action === "replace")
|
|
1315
|
+
updateDeepLink(nextMode, target, defaultMode);
|
|
1316
|
+
if (nextMode !== mode) {
|
|
1317
|
+
historyNavigationRef.current = true;
|
|
1318
|
+
initializedContextRef.current = "";
|
|
1319
|
+
setRequestedCircPaths(new Set());
|
|
1320
|
+
setModeRevision((revision) => revision + 1);
|
|
1321
|
+
setMode(nextMode);
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
if (targetDocument === documentIdRef.current) {
|
|
1325
|
+
activeIdRef.current = target.unitId;
|
|
1326
|
+
setActiveUnitId(target.unitId);
|
|
1327
|
+
}
|
|
1328
|
+
if (scrollViewerTarget(textPaneRef.current, target)) {
|
|
1329
|
+
if (pendingHistoryScrollRef.current !== null && textPaneRef.current) {
|
|
1330
|
+
textPaneRef.current.scrollTop = pendingHistoryScrollRef.current;
|
|
1331
|
+
pendingHistoryScrollRef.current = null;
|
|
1332
|
+
}
|
|
1333
|
+
if (sameViewerTarget(pendingSearchHighlightRef.current, target) && flashSearchTarget(target))
|
|
1334
|
+
pendingSearchHighlightRef.current = null;
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1337
|
+
if (!manifest)
|
|
1338
|
+
return;
|
|
1339
|
+
let targetIndex = indexes.get(targetDocument);
|
|
1340
|
+
if (!targetIndex) {
|
|
1341
|
+
targetIndex = await loadDocumentIndex(manifest, targetDocument, dataBaseUrl);
|
|
1342
|
+
if (generation !== navigationGenerationRef.current)
|
|
1343
|
+
return;
|
|
1344
|
+
setIndexes((current) => current.has(targetDocument) ? current : new Map(current).set(targetDocument, targetIndex));
|
|
1345
|
+
}
|
|
1346
|
+
const summary = targetIndex.units.find((unit) => unit.id === target.unitId);
|
|
1347
|
+
if (!summary) {
|
|
1348
|
+
const fallbackId = activeIdRef.current;
|
|
1349
|
+
if (fallbackId)
|
|
1350
|
+
updateDeepLink(mode, { kind: "unit", unitId: fallbackId }, defaultMode);
|
|
1351
|
+
setContentLoadNotice("Il target richiesto non esiste nel documento corrente.");
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
let activeId = summary.id;
|
|
1355
|
+
if (mode === "combined" && summary.document === "circ2019") {
|
|
1356
|
+
const explicit = relationTargets(summary.id, relations)[0];
|
|
1357
|
+
activeId = (_a = explicit === null || explicit === void 0 ? void 0 : explicit.targetUnitId) !== null && _a !== void 0 ? _a : summary.id;
|
|
1358
|
+
}
|
|
1359
|
+
activeIdRef.current = activeId;
|
|
1360
|
+
setActiveUnitId(activeId);
|
|
1361
|
+
const currentLookup = navigationRef.current.lookup;
|
|
1362
|
+
const nearMountedWindow = summary.document === documentIdRef.current && currentLookup ? isChunkNearRenderedWindow(currentLookup, summary.chunkPath, primaryRenderedPaths) : false;
|
|
1363
|
+
if (generation !== navigationGenerationRef.current)
|
|
1364
|
+
return;
|
|
1365
|
+
await revealSummary(summary, summary.document === documentIdRef.current && !nearMountedWindow, generation);
|
|
1366
|
+
}, [dataBaseUrl, defaultMode, flashSearchTarget, indexes, manifest, mode, primaryRenderedPaths, pushViewerHistory, relations, revealSummary]);
|
|
264
1367
|
useEffect(() => {
|
|
265
|
-
|
|
1368
|
+
const onPopState = (event) => {
|
|
1369
|
+
const url = new URL(window.location.href);
|
|
1370
|
+
const target = targetFromUrl(url);
|
|
1371
|
+
if (!target)
|
|
1372
|
+
return;
|
|
1373
|
+
const entry = viewerHistoryEntry(event.state);
|
|
1374
|
+
if ((entry === null || entry === void 0 ? void 0 : entry.session) === historySessionRef.current) {
|
|
1375
|
+
const position = { index: entry.index, max: historyPositionRef.current.max };
|
|
1376
|
+
historyPositionRef.current = position;
|
|
1377
|
+
setHistoryPosition(position);
|
|
1378
|
+
pendingHistoryScrollRef.current = entry.scrollTop;
|
|
1379
|
+
}
|
|
1380
|
+
else {
|
|
1381
|
+
pendingHistoryScrollRef.current = null;
|
|
1382
|
+
historyPositionRef.current = { index: 0, max: 0 };
|
|
1383
|
+
setHistoryPosition({ index: 0, max: 0 });
|
|
1384
|
+
}
|
|
1385
|
+
const requestedMode = url.searchParams.get("mode");
|
|
1386
|
+
const nextMode = modeOptions.some((option) => option.id === requestedMode) ? requestedMode : defaultMode;
|
|
1387
|
+
void navigateViewerTarget(target, "none", nextMode).catch(() => reportChunkLoadFailure());
|
|
1388
|
+
};
|
|
1389
|
+
window.addEventListener("popstate", onPopState);
|
|
1390
|
+
return () => window.removeEventListener("popstate", onPopState);
|
|
1391
|
+
}, [defaultMode, navigateViewerTarget, reportChunkLoadFailure]);
|
|
1392
|
+
const resolveReferenceElement = useCallback(async (element) => {
|
|
1393
|
+
const descriptor = referenceDescriptor(element);
|
|
1394
|
+
if (!descriptor)
|
|
1395
|
+
return null;
|
|
1396
|
+
const loaded = await ensureCrossReferenceIndex();
|
|
1397
|
+
const referenceLookup = crossReferenceLookup !== null && crossReferenceLookup !== void 0 ? crossReferenceLookup : createCrossReferenceLookup(loaded);
|
|
1398
|
+
return resolveCrossReference(referenceLookup, descriptor, descriptor.sourceDocument);
|
|
1399
|
+
}, [crossReferenceLookup, ensureCrossReferenceIndex]);
|
|
1400
|
+
const clearReferencePreview = useCallback(() => {
|
|
1401
|
+
referencePreviewRequestRef.current += 1;
|
|
1402
|
+
setReferencePreview(null);
|
|
1403
|
+
touchReferenceRef.current = null;
|
|
1404
|
+
}, []);
|
|
1405
|
+
const showReferencePreview = useCallback((element, interactive = false) => {
|
|
1406
|
+
var _a;
|
|
1407
|
+
const requestId = ++referencePreviewRequestRef.current;
|
|
1408
|
+
const bounds = element.getBoundingClientRect();
|
|
1409
|
+
const left = Math.max(12, Math.min(window.innerWidth - 332, bounds.left));
|
|
1410
|
+
const previewHeight = 190;
|
|
1411
|
+
const below = bounds.bottom + 7;
|
|
1412
|
+
const top = Math.max(12, below + previewHeight <= window.innerHeight - 12 ? below : bounds.top - previewHeight - 7);
|
|
1413
|
+
setReferencePreview({ label: (_a = element.textContent) !== null && _a !== void 0 ? _a : "Riferimento", title: "", snippet: "", left, top, loading: true, interactive });
|
|
1414
|
+
void resolveReferenceElement(element).then((reference) => {
|
|
1415
|
+
var _a, _b, _c;
|
|
1416
|
+
if (requestId !== referencePreviewRequestRef.current)
|
|
1417
|
+
return;
|
|
1418
|
+
if (!reference) {
|
|
1419
|
+
element.dataset.scvReferenceResolved = "false";
|
|
1420
|
+
setReferencePreview({ label: (_a = element.textContent) !== null && _a !== void 0 ? _a : "Riferimento", title: "Target non disponibile", snippet: "Il riferimento non è stato reso cliccabile perché non è risolto dall’indice derivato.", left, top, interactive, available: false });
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1423
|
+
element.dataset.scvReferenceResolved = "true";
|
|
1424
|
+
setReferencePreview({ label: referenceLabel(reference), title: ((_b = reference.asset) === null || _b === void 0 ? void 0 : _b.title) || reference.unit.title, snippet: ((_c = reference.asset) === null || _c === void 0 ? void 0 : _c.snippet) || reference.unit.snippet, left, top, interactive, available: true });
|
|
1425
|
+
}).catch(() => {
|
|
1426
|
+
if (requestId === referencePreviewRequestRef.current)
|
|
1427
|
+
setReferencePreview(null);
|
|
1428
|
+
});
|
|
1429
|
+
}, [resolveReferenceElement]);
|
|
1430
|
+
const activateReference = useCallback((element) => {
|
|
1431
|
+
clearReferencePreview();
|
|
1432
|
+
void resolveReferenceElement(element).then((reference) => {
|
|
1433
|
+
if (!reference)
|
|
1434
|
+
return;
|
|
1435
|
+
return navigateViewerTarget(viewerTargetForReference(reference), "push");
|
|
1436
|
+
}).catch(() => { });
|
|
1437
|
+
}, [clearReferencePreview, navigateViewerTarget, resolveReferenceElement]);
|
|
1438
|
+
const showPermalinkNotice = useCallback((element, message, status) => {
|
|
1439
|
+
const bounds = element.getBoundingClientRect();
|
|
1440
|
+
const placement = bounds.top >= 54 ? "above" : "below";
|
|
1441
|
+
setPermalinkNotice({
|
|
1442
|
+
message,
|
|
1443
|
+
left: Math.max(116, Math.min(window.innerWidth - 116, bounds.left + bounds.width / 2)),
|
|
1444
|
+
top: placement === "above" ? bounds.top : bounds.bottom,
|
|
1445
|
+
placement,
|
|
1446
|
+
status,
|
|
1447
|
+
});
|
|
1448
|
+
if (permalinkNoticeTimerRef.current !== null)
|
|
1449
|
+
window.clearTimeout(permalinkNoticeTimerRef.current);
|
|
1450
|
+
permalinkNoticeTimerRef.current = window.setTimeout(() => {
|
|
1451
|
+
setPermalinkNotice(null);
|
|
1452
|
+
permalinkNoticeTimerRef.current = null;
|
|
1453
|
+
}, status === "success" ? 1800 : 2400);
|
|
1454
|
+
}, []);
|
|
1455
|
+
const copyElementPermalink = useCallback((element) => {
|
|
1456
|
+
const citationElement = element.closest("[data-scv-citation-target]");
|
|
1457
|
+
const target = citationElement ? citationSelectionFromElement(citationElement) : null;
|
|
1458
|
+
if (!target)
|
|
266
1459
|
return;
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
1460
|
+
const targetDocument = documentFromUnitId(target.unitId);
|
|
1461
|
+
const linkMode = mode === "combined" || documentForMode(mode) === targetDocument ? mode : targetDocument === "ntc2018" ? "ntc" : "circ";
|
|
1462
|
+
const permalink = urlForViewerTarget(window.location.href, linkMode, defaultMode, target).href;
|
|
1463
|
+
setCitationSelection(target);
|
|
1464
|
+
void writeTextClipboard(permalink)
|
|
1465
|
+
.then(() => showPermalinkNotice(element, "Link copiato negli appunti", "success"))
|
|
1466
|
+
.catch(() => showPermalinkNotice(element, "Copia del link non disponibile", "error"));
|
|
1467
|
+
}, [defaultMode, mode, showPermalinkNotice]);
|
|
1468
|
+
const metadataForAnnotationTarget = useCallback((target) => {
|
|
1469
|
+
var _a, _b;
|
|
1470
|
+
const summary = summaryById.get(target.unitId);
|
|
1471
|
+
if (!summary)
|
|
1472
|
+
return null;
|
|
1473
|
+
let officialAssetNumber;
|
|
1474
|
+
if (target.kind === "asset") {
|
|
1475
|
+
for (const loaded of loadedChunks.values()) {
|
|
1476
|
+
const asset = (_b = (_a = loaded.assets.formulas[target.assetId]) !== null && _a !== void 0 ? _a : loaded.assets.tables[target.assetId]) !== null && _b !== void 0 ? _b : loaded.assets.figures[target.assetId];
|
|
1477
|
+
if (asset) {
|
|
1478
|
+
officialAssetNumber = asset.officialNumber;
|
|
1479
|
+
break;
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
return { documentId: summary.document, numbering: summary.numbering.official, title: summary.title,
|
|
1484
|
+
automaticLabel: automaticAnnotationLabel(target, summary.numbering.official, summary.title, officialAssetNumber) };
|
|
1485
|
+
}, [loadedChunks, summaryById]);
|
|
1486
|
+
const showAnnotationMenu = useCallback((target, point) => {
|
|
1487
|
+
const metadata = metadataForAnnotationTarget(target);
|
|
1488
|
+
if (!metadata || !annotationStore)
|
|
1489
|
+
return false;
|
|
1490
|
+
setCitationSelection(target);
|
|
1491
|
+
setAnnotationEditor(null);
|
|
1492
|
+
setAnnotationMenu({ target, metadata, x: point.x, y: point.y, bookmark: annotationsForTarget(annotations, target, "bookmark")[0] });
|
|
1493
|
+
return true;
|
|
1494
|
+
}, [annotationStore, annotations, metadataForAnnotationTarget]);
|
|
1495
|
+
const handleDocumentContextMenu = useCallback((event) => {
|
|
1496
|
+
const target = annotationTargetFromElement(event.target instanceof Element ? event.target : null);
|
|
1497
|
+
if (target && showAnnotationMenu(target, { x: event.clientX, y: event.clientY }))
|
|
1498
|
+
event.preventDefault();
|
|
1499
|
+
}, [showAnnotationMenu]);
|
|
1500
|
+
const handleDocumentKeyDown = useCallback((event) => {
|
|
270
1501
|
var _a;
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
if (summary)
|
|
285
|
-
selectUnit(summary);
|
|
1502
|
+
if (event.key !== "ContextMenu" && !(event.shiftKey && event.key === "F10"))
|
|
1503
|
+
return;
|
|
1504
|
+
const element = event.target instanceof Element ? event.target : null;
|
|
1505
|
+
const target = annotationTargetFromElement(element);
|
|
1506
|
+
const anchor = (_a = element === null || element === void 0 ? void 0 : element.closest("[data-scv-citation-target]")) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
|
|
1507
|
+
if (target && anchor && showAnnotationMenu(target, { x: anchor.left + Math.min(32, anchor.width / 2), y: anchor.top + Math.min(32, anchor.height / 2) }))
|
|
1508
|
+
event.preventDefault();
|
|
1509
|
+
}, [showAnnotationMenu]);
|
|
1510
|
+
const handleDocumentClick = useCallback((event) => {
|
|
1511
|
+
if (suppressAnnotationClickRef.current) {
|
|
1512
|
+
suppressAnnotationClickRef.current = false;
|
|
1513
|
+
event.preventDefault();
|
|
1514
|
+
event.stopPropagation();
|
|
286
1515
|
return;
|
|
287
1516
|
}
|
|
288
|
-
const
|
|
289
|
-
if (
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
selectUnit(summary);
|
|
1517
|
+
const permalinkElement = event.target instanceof Element ? event.target.closest("[data-scv-copy-link]") : null;
|
|
1518
|
+
if (permalinkElement) {
|
|
1519
|
+
event.preventDefault();
|
|
1520
|
+
copyElementPermalink(permalinkElement);
|
|
293
1521
|
return;
|
|
294
1522
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
1523
|
+
const element = event.target instanceof Element ? event.target.closest(".scv-cross-reference") : null;
|
|
1524
|
+
if (element) {
|
|
1525
|
+
if (lastPointerTypeRef.current === "touch" && touchReferenceRef.current !== element) {
|
|
1526
|
+
event.preventDefault();
|
|
1527
|
+
touchReferenceRef.current = element;
|
|
1528
|
+
showReferencePreview(element, true);
|
|
1529
|
+
return;
|
|
1530
|
+
}
|
|
1531
|
+
activateReference(element);
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
const citationElement = event.target instanceof Element ? event.target.closest("[data-scv-citation-target]") : null;
|
|
1535
|
+
if (citationElement)
|
|
1536
|
+
setCitationSelection(citationSelectionFromElement(citationElement));
|
|
1537
|
+
}, [activateReference, copyElementPermalink, showReferencePreview]);
|
|
1538
|
+
const handleDocumentPointerDown = useCallback((event) => {
|
|
1539
|
+
var _a;
|
|
1540
|
+
lastPointerTypeRef.current = event.pointerType;
|
|
1541
|
+
(_a = longPressRef.current) === null || _a === void 0 ? void 0 : _a.session.cancel();
|
|
1542
|
+
longPressRef.current = null;
|
|
1543
|
+
if (event.pointerType !== "touch" || !event.isPrimary || event.button !== 0 || !annotationStore)
|
|
1544
|
+
return;
|
|
1545
|
+
const element = event.target instanceof Element ? event.target : null;
|
|
1546
|
+
if (element === null || element === void 0 ? void 0 : element.closest(".scv-cross-reference, button, a, input, textarea, select"))
|
|
1547
|
+
return;
|
|
1548
|
+
const target = annotationTargetFromElement(element);
|
|
1549
|
+
if (!target)
|
|
1550
|
+
return;
|
|
1551
|
+
const session = createLongPressSession((point) => {
|
|
1552
|
+
if (showAnnotationMenu(target, point))
|
|
1553
|
+
suppressAnnotationClickRef.current = true;
|
|
1554
|
+
longPressRef.current = null;
|
|
1555
|
+
});
|
|
1556
|
+
longPressRef.current = { pointerId: event.pointerId, session };
|
|
1557
|
+
session.start({ x: event.clientX, y: event.clientY });
|
|
1558
|
+
}, [annotationStore, showAnnotationMenu]);
|
|
1559
|
+
const handleDocumentPointerMove = useCallback((event) => {
|
|
1560
|
+
const active = longPressRef.current;
|
|
1561
|
+
if ((active === null || active === void 0 ? void 0 : active.pointerId) === event.pointerId)
|
|
1562
|
+
active.session.move({ x: event.clientX, y: event.clientY });
|
|
1563
|
+
}, []);
|
|
1564
|
+
const cancelDocumentLongPress = useCallback((event) => {
|
|
1565
|
+
const active = longPressRef.current;
|
|
1566
|
+
if ((active === null || active === void 0 ? void 0 : active.pointerId) !== event.pointerId)
|
|
1567
|
+
return;
|
|
1568
|
+
active.session.cancel();
|
|
1569
|
+
longPressRef.current = null;
|
|
1570
|
+
}, []);
|
|
1571
|
+
const handleDocumentPointerOver = useCallback((event) => {
|
|
1572
|
+
if (event.pointerType === "touch")
|
|
1573
|
+
return;
|
|
1574
|
+
const element = event.target instanceof Element ? event.target.closest(".scv-cross-reference") : null;
|
|
1575
|
+
if (element && !element.contains(event.relatedTarget))
|
|
1576
|
+
showReferencePreview(element);
|
|
1577
|
+
}, [showReferencePreview]);
|
|
1578
|
+
const handleDocumentPointerOut = useCallback((event) => {
|
|
1579
|
+
if (event.pointerType === "touch" || (referencePreview === null || referencePreview === void 0 ? void 0 : referencePreview.interactive))
|
|
1580
|
+
return;
|
|
1581
|
+
const element = event.target instanceof Element ? event.target.closest(".scv-cross-reference") : null;
|
|
1582
|
+
if (element && !element.contains(event.relatedTarget) && document.activeElement !== element)
|
|
1583
|
+
clearReferencePreview();
|
|
1584
|
+
}, [clearReferencePreview, referencePreview === null || referencePreview === void 0 ? void 0 : referencePreview.interactive]);
|
|
1585
|
+
const handleDocumentFocus = useCallback((event) => {
|
|
1586
|
+
const element = event.target instanceof Element ? event.target.closest(".scv-cross-reference") : null;
|
|
1587
|
+
if (element)
|
|
1588
|
+
showReferencePreview(element);
|
|
1589
|
+
}, [showReferencePreview]);
|
|
1590
|
+
const handleDocumentBlur = useCallback((event) => {
|
|
1591
|
+
if (event.target instanceof Element && event.target.matches(".scv-cross-reference") && !(referencePreview === null || referencePreview === void 0 ? void 0 : referencePreview.interactive))
|
|
1592
|
+
clearReferencePreview();
|
|
1593
|
+
}, [clearReferencePreview, referencePreview === null || referencePreview === void 0 ? void 0 : referencePreview.interactive]);
|
|
1594
|
+
const openPreviewReference = useCallback(() => {
|
|
1595
|
+
const element = touchReferenceRef.current;
|
|
1596
|
+
if (element)
|
|
1597
|
+
activateReference(element);
|
|
1598
|
+
}, [activateReference]);
|
|
1599
|
+
const selectScrollMarker = useCallback((unitId) => {
|
|
1600
|
+
const entry = navigationRef.current.entryById.get(unitId);
|
|
1601
|
+
if (entry)
|
|
1602
|
+
selectUnit(entry.summary);
|
|
1603
|
+
}, [selectUnit]);
|
|
1604
|
+
const activeSummary = (_f = (_d = (_c = lookup === null || lookup === void 0 ? void 0 : lookup.unitById.get(activeUnitId !== null && activeUnitId !== void 0 ? activeUnitId : "")) !== null && _c !== void 0 ? _c : circLookup === null || circLookup === void 0 ? void 0 : circLookup.unitById.get(activeUnitId !== null && activeUnitId !== void 0 ? activeUnitId : "")) !== null && _d !== void 0 ? _d : (_e = entryById.get(activeUnitId !== null && activeUnitId !== void 0 ? activeUnitId : "")) === null || _e === void 0 ? void 0 : _e.summary) !== null && _f !== void 0 ? _f : null;
|
|
1605
|
+
const contextSummary = activeSummary && baseNumbering(activeSummary.numbering.official).split(".").length > 3
|
|
1606
|
+
? (_g = activeSummary.hierarchy.ancestorIds.map((id) => summaryById.get(id)).find((summary) => summary && baseNumbering(summary.numbering.official).split(".").length === 3)) !== null && _g !== void 0 ? _g : null
|
|
1607
|
+
: activeSummary;
|
|
1608
|
+
const chapters = useMemo(() => navigationEntries.filter(({ level }) => level === 0), [navigationEntries]);
|
|
1609
|
+
const paragraphs = useMemo(() => navigationEntries.filter(({ level }) => level === 1), [navigationEntries]);
|
|
1610
|
+
const subparagraphs = useMemo(() => navigationEntries.filter(({ level }) => level === 2), [navigationEntries]);
|
|
1611
|
+
const hierarchy = useMemo(() => [chapters, paragraphs, subparagraphs], [chapters, paragraphs, subparagraphs]);
|
|
1612
|
+
const activeLevelIds = useMemo(() => {
|
|
1613
|
+
if (!activeSummary)
|
|
1614
|
+
return [null, null, null];
|
|
1615
|
+
const activeBaseParts = baseNumbering(activeSummary.numbering.official).split(".");
|
|
1616
|
+
return [chapters, paragraphs, subparagraphs].map((levelEntries, level) => {
|
|
1617
|
+
var _a, _b;
|
|
1618
|
+
const baseNumber = activeBaseParts.slice(0, level + 1).join(".");
|
|
1619
|
+
return (_b = (_a = levelEntries.find((entry) => entry.baseNumber === baseNumber)) === null || _a === void 0 ? void 0 : _a.summary.id) !== null && _b !== void 0 ? _b : null;
|
|
1620
|
+
});
|
|
1621
|
+
}, [activeSummary, chapters, paragraphs, subparagraphs]);
|
|
1622
|
+
const scrubberEntries = useMemo(() => buildGlobalScrubberEntries(navigationEntries.map(({ summary, displayNumber, baseNumber, level }) => ({ id: summary.id, label: displayNumber, title: summary.title, baseNumber, level })), 2), [navigationEntries]);
|
|
1623
|
+
const scrubberActiveId = resolveGlobalScrubberActiveId(scrubberEntries, activeUnitId, activeSummary ? baseNumbering(activeSummary.numbering.official) : null);
|
|
1624
|
+
const annotationMarkers = useMemo(() => {
|
|
1625
|
+
const scrubberById = new Map(scrubberEntries.map((entry) => [entry.id, entry]));
|
|
1626
|
+
return annotations.flatMap((annotation) => {
|
|
1627
|
+
var _a;
|
|
1628
|
+
if (mode !== "combined" && annotation.documentId !== documentId)
|
|
1629
|
+
return [];
|
|
1630
|
+
let entry = scrubberById.get(annotation.target.unitId);
|
|
1631
|
+
const summary = summaryById.get(annotation.target.unitId);
|
|
1632
|
+
if (!entry && mode === "combined" && (summary === null || summary === void 0 ? void 0 : summary.document) === "circ2019") {
|
|
1633
|
+
const relatedId = (_a = relationTargets(summary.id, relations)[0]) === null || _a === void 0 ? void 0 : _a.targetUnitId;
|
|
1634
|
+
entry = relatedId ? scrubberById.get(relatedId) : undefined;
|
|
1635
|
+
if (!entry)
|
|
1636
|
+
entry = scrubberEntries.find((candidate) => candidate.baseNumber === baseNumbering(summary.numbering.official));
|
|
1637
|
+
}
|
|
1638
|
+
return entry ? [{ annotation, ratio: entry.ratio }] : [];
|
|
1639
|
+
});
|
|
1640
|
+
}, [annotations, documentId, mode, relations, scrubberEntries, summaryById]);
|
|
1641
|
+
const selectAnnotationMarker = useCallback((annotation) => {
|
|
1642
|
+
void navigateViewerTarget(annotation.target, "push").then(() => { if (annotation.type === "note")
|
|
1643
|
+
openAnnotation(annotation); }).catch(() => reportChunkLoadFailure());
|
|
1644
|
+
}, [navigateViewerTarget, openAnnotation, reportChunkLoadFailure]);
|
|
1645
|
+
const activeRecord = (_h = recordById.get(activeUnitId !== null && activeUnitId !== void 0 ? activeUnitId : "")) !== null && _h !== void 0 ? _h : null;
|
|
1646
|
+
const chunk = (_j = activeRecord === null || activeRecord === void 0 ? void 0 : activeRecord.chunk) !== null && _j !== void 0 ? _j : null;
|
|
1647
|
+
const activePages = activeRecord ? evidencePages(activeRecord.unit, activeRecord.chunk) : [];
|
|
1648
|
+
const pageBounds = activePages.length > 0 ? { from: Math.min(...activePages), to: Math.max(...activePages) } : { from: 1, to: 1 };
|
|
1649
|
+
const changeMode = useCallback((nextMode, preferredUnitId = null) => {
|
|
1650
|
+
var _a;
|
|
1651
|
+
if (nextMode === mode)
|
|
1652
|
+
return;
|
|
1653
|
+
navigationGenerationRef.current += 1;
|
|
1654
|
+
pendingScrollAnchorRef.current = null;
|
|
1655
|
+
setContentLoadNotice(null);
|
|
1656
|
+
const keepCurrent = activeSummary && (nextMode === "combined" || activeSummary.document === documentForMode(nextMode));
|
|
1657
|
+
requestedIdRef.current = preferredUnitId !== null && preferredUnitId !== void 0 ? preferredUnitId : (keepCurrent ? (_a = activeSummary === null || activeSummary === void 0 ? void 0 : activeSummary.id) !== null && _a !== void 0 ? _a : null : null);
|
|
1658
|
+
requestedTargetRef.current = requestedIdRef.current ? { kind: "unit", unitId: requestedIdRef.current } : null;
|
|
1659
|
+
initializedContextRef.current = "";
|
|
1660
|
+
setRequestedCircPaths(new Set());
|
|
1661
|
+
setCitationSelection(null);
|
|
1662
|
+
clearReferencePreview();
|
|
1663
|
+
setModeRevision((revision) => revision + 1);
|
|
1664
|
+
setMode(nextMode);
|
|
1665
|
+
}, [activeSummary, clearReferencePreview, mode]);
|
|
1666
|
+
const closeMobileIndex = useCallback(() => {
|
|
1667
|
+
setMobileIndexOpen(false);
|
|
1668
|
+
if (window.matchMedia("(max-width: 991.98px)").matches)
|
|
1669
|
+
window.requestAnimationFrame(() => { var _a; return (_a = mobileIndexButtonRef.current) === null || _a === void 0 ? void 0 : _a.focus(); });
|
|
1670
|
+
}, []);
|
|
1671
|
+
useEffect(() => {
|
|
1672
|
+
const onKeyDown = (event) => {
|
|
1673
|
+
var _a;
|
|
1674
|
+
const editing = (event.target instanceof HTMLElement && event.target.closest("input, textarea, select, [contenteditable='true']"))
|
|
1675
|
+
|| ((_a = window.document.activeElement) === null || _a === void 0 ? void 0 : _a.closest("input, textarea, select, [contenteditable='true']"));
|
|
1676
|
+
if (event.key === "/" && !editing) {
|
|
1677
|
+
event.preventDefault();
|
|
1678
|
+
if (window.matchMedia("(max-width: 1399.98px)").matches)
|
|
1679
|
+
setAuxiliaryVisible(false);
|
|
1680
|
+
if (window.matchMedia("(max-width: 991.98px)").matches) {
|
|
1681
|
+
setMobileIndexSession((session) => session + 1);
|
|
1682
|
+
setMobileIndexOpen(true);
|
|
1683
|
+
}
|
|
1684
|
+
window.requestAnimationFrame(() => { var _a; return (_a = searchRef.current) === null || _a === void 0 ? void 0 : _a.focus(); });
|
|
1685
|
+
}
|
|
1686
|
+
if (event.key === "Escape") {
|
|
1687
|
+
if (referencePreview === null || referencePreview === void 0 ? void 0 : referencePreview.interactive)
|
|
1688
|
+
clearReferencePreview();
|
|
1689
|
+
else if (mobileIndexOpen)
|
|
1690
|
+
closeMobileIndex();
|
|
1691
|
+
}
|
|
1692
|
+
};
|
|
1693
|
+
window.addEventListener("keydown", onKeyDown);
|
|
1694
|
+
return () => window.removeEventListener("keydown", onKeyDown);
|
|
1695
|
+
}, [clearReferencePreview, closeMobileIndex, mobileIndexOpen, referencePreview === null || referencePreview === void 0 ? void 0 : referencePreview.interactive]);
|
|
1696
|
+
const selectSearchResult = useCallback((result) => {
|
|
1697
|
+
const target = viewerTargetForSearchResult(result);
|
|
1698
|
+
void navigateViewerTarget(target, "push").catch(() => reportChunkLoadFailure());
|
|
1699
|
+
}, [navigateViewerTarget, reportChunkLoadFailure]);
|
|
1700
|
+
const submitSearch = useCallback((event) => {
|
|
299
1701
|
event.preventDefault();
|
|
300
|
-
if (
|
|
301
|
-
selectSearchResult(
|
|
302
|
-
}
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
const
|
|
309
|
-
const scopeNote = scope.mode === "chapter"
|
|
310
|
-
? `Continuità capitolo ${(_b = activeSummary === null || activeSummary === void 0 ? void 0 : activeSummary.numbering.official) !== null && _b !== void 0 ? _b : ""} · ${scope.summaries.length} unità · ${scopePaths.length} chunk`
|
|
311
|
-
: scope.paragraphId
|
|
312
|
-
? `Continuità paragrafo ${(_d = (_c = index === null || index === void 0 ? void 0 : index.units.find((unit) => unit.id === scope.paragraphId)) === null || _c === void 0 ? void 0 : _c.numbering.official) !== null && _d !== void 0 ? _d : ""} · ${scope.summaries.length} unità · ${scopePaths.length} chunk`
|
|
313
|
-
: `Capitolo ${(_e = activeSummary === null || activeSummary === void 0 ? void 0 : activeSummary.numbering.official) !== null && _e !== void 0 ? _e : ""} · continuità ridotta ai paragrafi per la dimensione del capitolo`;
|
|
314
|
-
function renderRecord({ unit, chunk: unitChunk }) {
|
|
1702
|
+
if (search.results[0])
|
|
1703
|
+
selectSearchResult(search.results[0]);
|
|
1704
|
+
}, [search.results, selectSearchResult]);
|
|
1705
|
+
const primaryPathPositions = (_k = lookup === null || lookup === void 0 ? void 0 : lookup.chunkPaths.flatMap((path, position) => primaryRenderedPaths.has(path) ? [position] : [])) !== null && _k !== void 0 ? _k : [];
|
|
1706
|
+
const hasPrevious = primaryPathPositions.length > 0 && Math.min(...primaryPathPositions) > 0;
|
|
1707
|
+
const hasNext = Boolean(lookup && primaryPathPositions.length > 0 && Math.max(...primaryPathPositions) < lookup.chunkPaths.length - 1);
|
|
1708
|
+
const documentLoading = !index || documentRecords.length === 0;
|
|
1709
|
+
const auxiliaryUnit = (_l = (citationSelection ? summaryById.get(citationSelection.unitId) : null)) !== null && _l !== void 0 ? _l : activeSummary;
|
|
1710
|
+
const closeAuxiliary = useCallback(() => {
|
|
315
1711
|
var _a;
|
|
316
|
-
|
|
317
|
-
|
|
1712
|
+
scrollRequestRef.current = requestedTargetRef.current;
|
|
1713
|
+
setAuxiliaryVisible(false);
|
|
1714
|
+
(_a = auxiliaryButtonRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
1715
|
+
}, []);
|
|
1716
|
+
const saveAnnotationEditor = useCallback(async (value) => {
|
|
1717
|
+
if (!annotationStore || !annotationEditor)
|
|
1718
|
+
return;
|
|
1719
|
+
setAnnotationBusy(true);
|
|
1720
|
+
setAnnotationError(null);
|
|
1721
|
+
try {
|
|
1722
|
+
const trimmed = value.trim();
|
|
1723
|
+
const saved = annotationEditor.annotation
|
|
1724
|
+
? await annotationStore.updateAnnotation(annotationEditor.annotation.id, annotationEditor.type === "bookmark" ? { label: trimmed || annotationEditor.metadata.automaticLabel } : { text: trimmed })
|
|
1725
|
+
: await annotationStore.createAnnotation(Object.assign({ type: annotationEditor.type, target: annotationEditor.target, documentId: annotationEditor.metadata.documentId, numbering: annotationEditor.metadata.numbering, title: annotationEditor.metadata.title }, (annotationEditor.type === "bookmark" ? { label: trimmed || annotationEditor.metadata.automaticLabel } : { text: trimmed })));
|
|
1726
|
+
setAnnotations((current) => [...current.filter((annotation) => annotation.id !== saved.id && !(saved.type === "bookmark" && annotation.type === "bookmark" && annotationTargetKey(annotation.target) === annotationTargetKey(saved.target))), saved]);
|
|
1727
|
+
setAnnotationEditor(null);
|
|
1728
|
+
}
|
|
1729
|
+
catch (error) {
|
|
1730
|
+
setAnnotationError(error instanceof Error ? error.message : "Salvataggio non riuscito.");
|
|
1731
|
+
}
|
|
1732
|
+
finally {
|
|
1733
|
+
setAnnotationBusy(false);
|
|
1734
|
+
}
|
|
1735
|
+
}, [annotationEditor, annotationStore]);
|
|
1736
|
+
const removeAnnotation = useCallback(async (annotation) => {
|
|
1737
|
+
if (!annotationStore)
|
|
1738
|
+
return;
|
|
1739
|
+
setAnnotationBusy(true);
|
|
1740
|
+
setAnnotationError(null);
|
|
1741
|
+
try {
|
|
1742
|
+
await annotationStore.deleteAnnotation(annotation.id);
|
|
1743
|
+
setAnnotations((current) => current.filter((item) => item.id !== annotation.id));
|
|
1744
|
+
setAnnotationEditor((current) => { var _a; return ((_a = current === null || current === void 0 ? void 0 : current.annotation) === null || _a === void 0 ? void 0 : _a.id) === annotation.id ? null : current; });
|
|
1745
|
+
}
|
|
1746
|
+
catch (error) {
|
|
1747
|
+
setAnnotationError(error instanceof Error ? error.message : "Eliminazione non riuscita.");
|
|
1748
|
+
}
|
|
1749
|
+
finally {
|
|
1750
|
+
setAnnotationBusy(false);
|
|
1751
|
+
}
|
|
1752
|
+
}, [annotationStore]);
|
|
1753
|
+
const annotationController = useMemo(() => annotationStore ? {
|
|
1754
|
+
items: annotations, loading: annotationsLoading, error: annotationError, currentDocumentId: documentId, positionRanks: annotationRanks,
|
|
1755
|
+
navigate: (annotation) => { void navigateViewerTarget(annotation.target, "push").catch(() => reportChunkLoadFailure()); },
|
|
1756
|
+
edit: openAnnotation,
|
|
1757
|
+
remove: removeAnnotation,
|
|
1758
|
+
exportAnnotations: () => annotationStore.exportAnnotations(),
|
|
1759
|
+
importAnnotations: async (value) => { const result = await annotationStore.importAnnotations(value); setAnnotations(await annotationStore.listAnnotations()); setAnnotationError(null); return result; },
|
|
1760
|
+
} : undefined, [annotationError, annotationRanks, annotationStore, annotations, annotationsLoading, documentId, navigateViewerTarget, openAnnotation, removeAnnotation, reportChunkLoadFailure]);
|
|
1761
|
+
const renderAuxiliary = !manifest || !auxiliaryAvailable ? null : _jsx(AuxiliaryContent, { panel: auxiliaryPanel, context: { mode, documentId, manifest, chunk, pageBounds,
|
|
1762
|
+
currentUnit: auxiliaryUnit ? { documentId: auxiliaryUnit.document, unitId: auxiliaryUnit.id, numbering: auxiliaryUnit.numbering.official } : null,
|
|
1763
|
+
selectedTarget: (citationSelection === null || citationSelection === void 0 ? void 0 : citationSelection.unitId) === (auxiliaryUnit === null || auxiliaryUnit === void 0 ? void 0 : auxiliaryUnit.id) ? citationSelection : null,
|
|
1764
|
+
navigateTo: navigateViewerTarget,
|
|
1765
|
+
hrefForTarget: (target) => urlForViewerTarget(window.location.href, mode === "combined" || documentForMode(mode) === documentFromUnitId(target.unitId) ? mode : documentFromUnitId(target.unitId) === "ntc2018" ? "ntc" : "circ", defaultMode, target).href,
|
|
1766
|
+
close: closeAuxiliary,
|
|
1767
|
+
annotations: annotationController, } });
|
|
318
1768
|
if (loadError)
|
|
319
1769
|
return _jsxs("main", { className: "scv-fatal", children: [_jsx("strong", { children: "Il corpus non \u00E8 disponibile." }), _jsx("span", { children: "Rigenera gli artefatti del viewer e ricarica la pagina." })] });
|
|
320
|
-
return _jsxs("div", { className: `scv-root ${
|
|
321
|
-
|
|
1770
|
+
return _jsxs("div", { className: `scv-root ${darkMode ? "scv-dark" : ""} ${auxiliaryAvailable ? "scv-auxiliary-available" : ""} ${auxiliaryVisible && auxiliaryAvailable ? "scv-has-auxiliary" : ""} ${mobileIndexOpen ? "scv-mobile-index-open" : ""} ${className !== null && className !== void 0 ? className : ""}`, "data-scv-mounted-chunks": primaryRenderedPaths.size, "data-scv-loaded-related-chunks": mode === "combined" ? requiredCombinedCircPaths.size : 0, "data-scv-search-index-requested": search.indexRequested, "data-scv-cross-reference-index-requested": Boolean(crossReferenceIndex), "data-scv-search-error": search.errorMessage, children: [_jsx(NavigationPane, { id: navigationId, mode: mode, onModeChange: changeMode, hierarchy: hierarchy, activeLevelIds: activeLevelIds, sidebarMode: mobileIndexOpen, sidebarSession: mobileIndexSession, indexReady: Boolean(index), query: query, onQueryChange: setQuery, searchReady: search.queryReady, searchStatus: search.status, searchSource: search.source, searchDurationMs: search.durationMs, searchResults: search.results, onSearchSubmit: submitSearch, onSearchResult: selectSearchResult, onSelectUnit: selectUnit, onRequestClose: closeMobileIndex, onHistoryBack: () => { saveHistoryScroll(); window.history.back(); }, onHistoryForward: () => { saveHistoryScroll(); window.history.forward(); }, canHistoryBack: historyPosition.index > 0, canHistoryForward: historyPosition.index < historyPosition.max, searchRef: searchRef, darkMode: Boolean(darkMode), onToggleTheme: () => setDarkMode((current) => !current) }), _jsxs("div", { className: "scv-text-pane-shell", children: [_jsxs("button", { ref: mobileIndexButtonRef, type: "button", className: "scv-mobile-index-toggle", "aria-controls": navigationId, "aria-expanded": mobileIndexOpen, onClick: () => {
|
|
1771
|
+
setMobileIndexSession((session) => session + 1);
|
|
1772
|
+
setMobileIndexOpen(true);
|
|
1773
|
+
window.requestAnimationFrame(() => { var _a; return (_a = searchRef.current) === null || _a === void 0 ? void 0 : _a.focus(); });
|
|
1774
|
+
}, children: [_jsx("svg", { viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: _jsx("path", { d: "M4 6h16M4 12h16M4 18h16" }) }), _jsx("span", { className: "scv-visually-hidden", children: "Apri indice" })] }), contextSummary && _jsxs("div", { className: "scv-mobile-context", title: `${contextSummary.numbering.official} ${contextSummary.title}`, "aria-label": `Unità attiva: ${contextSummary.numbering.official} ${contextSummary.title}`, children: [_jsx("strong", { children: contextSummary.numbering.official }), _jsx("span", { children: contextSummary.title })] }), auxiliaryAvailable && _jsx("button", { type: "button", className: "scv-tools-toggle", ref: auxiliaryButtonRef, disabled: !manifest, "aria-controls": auxiliaryId, "aria-expanded": auxiliaryVisible, onClick: () => {
|
|
1775
|
+
if (auxiliaryVisible)
|
|
1776
|
+
closeAuxiliary();
|
|
1777
|
+
else {
|
|
1778
|
+
scrollRequestRef.current = requestedTargetRef.current;
|
|
1779
|
+
setMobileIndexOpen(false);
|
|
1780
|
+
setAuxiliaryVisible(true);
|
|
1781
|
+
window.requestAnimationFrame(() => {
|
|
1782
|
+
var _a, _b;
|
|
1783
|
+
(_b = (_a = auxiliaryPaneRef.current) === null || _a === void 0 ? void 0 : _a.querySelector("button")) === null || _b === void 0 ? void 0 : _b.focus({ preventScroll: true });
|
|
1784
|
+
});
|
|
1785
|
+
}
|
|
1786
|
+
}, "aria-label": `Apri ${auxiliaryPanelLabel}`, children: auxiliaryPanelButtonText }), contentLoadNotice && _jsx("p", { className: "scv-content-notice", role: "status", children: contentLoadNotice }), _jsx("article", { ref: textPaneRef, className: "scv-text-pane", "aria-label": "Corpus JSON", onClick: handleDocumentClick, onContextMenu: handleDocumentContextMenu, onKeyDown: handleDocumentKeyDown, onPointerDown: handleDocumentPointerDown, onPointerMove: handleDocumentPointerMove, onPointerUp: cancelDocumentLongPress, onPointerCancel: cancelDocumentLongPress, onPointerOver: handleDocumentPointerOver, onPointerOut: handleDocumentPointerOut, onFocus: handleDocumentFocus, onBlur: handleDocumentBlur, children: documentLoading || !index || !lookup ? _jsx(LoadingPanel, { label: "Caricamento del documento\u2026" }) : _jsx(DocumentContent, { records: renderRecords, relatedByTarget: relatedByTarget, mode: mode, assetsBaseUrl: assetsBaseUrl, documentLabel: documentId === "ntc2018" ? "NTC 2018" : "Circolare 7/2019", documentUnits: index.units.length, documentChunks: lookup.chunkPaths.length, hasPrevious: hasPrevious, hasNext: hasNext, notesByTarget: notesByTarget, onOpenAnnotation: openAnnotation }) }), _jsx(GlobalDocumentScrubber, { rootRef: textPaneRef, entries: scrubberEntries, activeId: scrubberActiveId, onSelect: selectScrollMarker, annotationMarkers: annotationMarkers, onAnnotationSelect: selectAnnotationMarker })] }), _jsx(ReferencePreview, { preview: referencePreview, onOpen: openPreviewReference, onClose: clearReferencePreview }), permalinkNotice && _jsx("span", { className: `scv-permalink-notice is-${permalinkNotice.placement} is-${permalinkNotice.status}`, style: { left: permalinkNotice.left, top: permalinkNotice.top }, role: "status", children: permalinkNotice.message }), annotationMenu && _jsx(AnnotationContextMenu, { state: annotationMenu, onClose: () => setAnnotationMenu(null), onBookmark: () => { setAnnotationEditor({ type: "bookmark", target: annotationMenu.target, metadata: annotationMenu.metadata, annotation: annotationMenu.bookmark, x: annotationMenu.x, y: annotationMenu.y }); setAnnotationMenu(null); }, onNote: () => { setAnnotationEditor({ type: "note", target: annotationMenu.target, metadata: annotationMenu.metadata, x: annotationMenu.x, y: annotationMenu.y }); setAnnotationMenu(null); } }), annotationEditor && _jsx(AnnotationEditor, { state: annotationEditor, busy: annotationBusy, error: annotationError, onSave: (value) => void saveAnnotationEditor(value), onDelete: annotationEditor.annotation ? () => void removeAnnotation(annotationEditor.annotation) : null, onClose: () => { setAnnotationEditor(null); setAnnotationError(null); } }), (auxiliaryVisible || auxiliaryPanelKeepMounted) && auxiliaryAvailable && renderAuxiliary && _jsx("aside", { ref: auxiliaryPaneRef, id: auxiliaryId, inert: !auxiliaryVisible ? true : undefined, "aria-hidden": !auxiliaryVisible, className: "scv-auxiliary-pane", "aria-label": auxiliaryPanelLabel, children: renderAuxiliary })] });
|
|
322
1787
|
}
|
|
323
1788
|
function LoadingRows() {
|
|
324
1789
|
return _jsx("div", { className: "scv-loading-rows", "aria-label": "Caricamento", children: Array.from({ length: 7 }, (_, index) => _jsx("span", {}, index)) });
|