@canopy-iiif/app 1.3.6 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/build/iiif.js +78 -4
- package/lib/build/mdx.js +123 -0
- package/lib/build/pages.js +18 -0
- package/lib/build/runtimes.js +1 -0
- package/lib/build/sitemap.js +62 -0
- package/lib/components/map-runtime.js +114 -0
- package/lib/components/nav-place.js +227 -0
- package/package.json +5 -3
- package/ui/dist/index.mjs +652 -0
- package/ui/dist/index.mjs.map +4 -4
- package/ui/dist/server.mjs +251 -49
- package/ui/dist/server.mjs.map +4 -4
- package/ui/styles/base/_markdown.scss +1 -1
- package/ui/styles/components/_map.scss +146 -0
- package/ui/styles/components/index.scss +1 -0
- package/ui/styles/index.css +126 -1
package/lib/build/iiif.js
CHANGED
|
@@ -20,6 +20,7 @@ const {log, logLine, logResponse} = require("./log");
|
|
|
20
20
|
const { getPageContext } = require("../page-context");
|
|
21
21
|
const PageContext = getPageContext();
|
|
22
22
|
const referenced = require("../components/referenced");
|
|
23
|
+
const navPlace = require("../components/nav-place");
|
|
23
24
|
const {
|
|
24
25
|
getThumbnail,
|
|
25
26
|
getRepresentativeImage,
|
|
@@ -1387,6 +1388,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1387
1388
|
);
|
|
1388
1389
|
} catch (_) {}
|
|
1389
1390
|
const iiifRecords = [];
|
|
1391
|
+
const navPlaceRecords = [];
|
|
1390
1392
|
const {size: thumbSize, unsafe: unsafeThumbs} = resolveThumbnailPreferences();
|
|
1391
1393
|
|
|
1392
1394
|
// Compile the works layout component once per run
|
|
@@ -1543,9 +1545,13 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1543
1545
|
try {
|
|
1544
1546
|
let components = {};
|
|
1545
1547
|
try {
|
|
1546
|
-
components = await import("@canopy-iiif/app/ui");
|
|
1548
|
+
components = await import("@canopy-iiif/app/ui/server");
|
|
1547
1549
|
} catch (_) {
|
|
1548
|
-
|
|
1550
|
+
try {
|
|
1551
|
+
components = await import("@canopy-iiif/app/ui");
|
|
1552
|
+
} catch (_) {
|
|
1553
|
+
components = {};
|
|
1554
|
+
}
|
|
1549
1555
|
}
|
|
1550
1556
|
const {withBase} = require("../common");
|
|
1551
1557
|
const Anchor = function A(props) {
|
|
@@ -1658,6 +1664,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1658
1664
|
const needsRelated = body.includes("data-canopy-related-items");
|
|
1659
1665
|
const needsHeroSlider = body.includes("data-canopy-hero-slider");
|
|
1660
1666
|
const needsTimeline = body.includes("data-canopy-timeline");
|
|
1667
|
+
const needsMap = body.includes("data-canopy-map");
|
|
1661
1668
|
const needsSearchForm = body.includes("data-canopy-search-form");
|
|
1662
1669
|
const needsHydrate =
|
|
1663
1670
|
body.includes("data-canopy-hydrate") ||
|
|
@@ -1692,6 +1699,24 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1692
1699
|
.split(path.sep)
|
|
1693
1700
|
.join("/")
|
|
1694
1701
|
: null;
|
|
1702
|
+
const mapRel = needsMap
|
|
1703
|
+
? path
|
|
1704
|
+
.relative(
|
|
1705
|
+
path.dirname(outPath),
|
|
1706
|
+
path.join(OUT_DIR, "scripts", "canopy-map.js")
|
|
1707
|
+
)
|
|
1708
|
+
.split(path.sep)
|
|
1709
|
+
.join("/")
|
|
1710
|
+
: null;
|
|
1711
|
+
const mapCssRel = needsMap
|
|
1712
|
+
? path
|
|
1713
|
+
.relative(
|
|
1714
|
+
path.dirname(outPath),
|
|
1715
|
+
path.join(OUT_DIR, "scripts", "canopy-map.css")
|
|
1716
|
+
)
|
|
1717
|
+
.split(path.sep)
|
|
1718
|
+
.join("/")
|
|
1719
|
+
: null;
|
|
1695
1720
|
const heroRel = needsHeroSlider
|
|
1696
1721
|
? path
|
|
1697
1722
|
.relative(
|
|
@@ -1727,6 +1752,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1727
1752
|
if (heroRel) primaryClassicScripts.push(heroRel);
|
|
1728
1753
|
if (relatedRel) primaryClassicScripts.push(relatedRel);
|
|
1729
1754
|
if (timelineRel) primaryClassicScripts.push(timelineRel);
|
|
1755
|
+
if (mapRel) primaryClassicScripts.push(mapRel);
|
|
1730
1756
|
const secondaryClassicScripts = [];
|
|
1731
1757
|
if (searchFormRel) secondaryClassicScripts.push(searchFormRel);
|
|
1732
1758
|
let jsRel = null;
|
|
@@ -1741,7 +1767,8 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1741
1767
|
const needsReact = !!(
|
|
1742
1768
|
needsHydrateViewer ||
|
|
1743
1769
|
needsRelated ||
|
|
1744
|
-
needsTimeline
|
|
1770
|
+
needsTimeline ||
|
|
1771
|
+
needsMap
|
|
1745
1772
|
);
|
|
1746
1773
|
let vendorTag = "";
|
|
1747
1774
|
if (needsReact) {
|
|
@@ -1782,6 +1809,17 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1782
1809
|
)}</script>` + vendorTag;
|
|
1783
1810
|
} catch (_) {}
|
|
1784
1811
|
let pageBody = body;
|
|
1812
|
+
const extraStyles = [];
|
|
1813
|
+
if (mapCssRel) {
|
|
1814
|
+
let rel = mapCssRel;
|
|
1815
|
+
try {
|
|
1816
|
+
const mapCssAbs = path.join(OUT_DIR, "scripts", "canopy-map.css");
|
|
1817
|
+
const st = fs.statSync(mapCssAbs);
|
|
1818
|
+
rel += `?v=${Math.floor(st.mtimeMs || Date.now())}`;
|
|
1819
|
+
} catch (_) {}
|
|
1820
|
+
extraStyles.push(`<link rel="stylesheet" href="${rel}">`);
|
|
1821
|
+
}
|
|
1822
|
+
if (extraStyles.length) headSegments.push(extraStyles.join(""));
|
|
1785
1823
|
if (vendorTag) headSegments.push(vendorTag);
|
|
1786
1824
|
if (extraScripts.length) headSegments.push(extraScripts.join(""));
|
|
1787
1825
|
const headExtra = headSegments.join("");
|
|
@@ -1878,10 +1916,36 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1878
1916
|
annotationValue = "";
|
|
1879
1917
|
}
|
|
1880
1918
|
}
|
|
1919
|
+
const navThumbnail =
|
|
1920
|
+
thumbUrl || (heroMedia && heroMedia.heroThumbnail) || "";
|
|
1921
|
+
const navThumbWidth =
|
|
1922
|
+
typeof thumbWidth === "number"
|
|
1923
|
+
? thumbWidth
|
|
1924
|
+
: heroMedia && typeof heroMedia.heroThumbnailWidth === "number"
|
|
1925
|
+
? heroMedia.heroThumbnailWidth
|
|
1926
|
+
: undefined;
|
|
1927
|
+
const navThumbHeight =
|
|
1928
|
+
typeof thumbHeight === "number"
|
|
1929
|
+
? thumbHeight
|
|
1930
|
+
: heroMedia && typeof heroMedia.heroThumbnailHeight === "number"
|
|
1931
|
+
? heroMedia.heroThumbnailHeight
|
|
1932
|
+
: undefined;
|
|
1933
|
+
const navRecord = navPlace.buildManifestNavPlaceRecord({
|
|
1934
|
+
manifest,
|
|
1935
|
+
slug,
|
|
1936
|
+
href: pageHref,
|
|
1937
|
+
title,
|
|
1938
|
+
summary: summaryRaw,
|
|
1939
|
+
thumbnail: navThumbnail,
|
|
1940
|
+
thumbnailWidth: navThumbWidth,
|
|
1941
|
+
thumbnailHeight: navThumbHeight,
|
|
1942
|
+
});
|
|
1943
|
+
if (navRecord) navPlaceRecords.push(navRecord);
|
|
1944
|
+
|
|
1881
1945
|
iiifRecords.push({
|
|
1882
1946
|
id: String(manifest.id || id),
|
|
1883
1947
|
title,
|
|
1884
|
-
href:
|
|
1948
|
+
href: pageHref,
|
|
1885
1949
|
type: "work",
|
|
1886
1950
|
thumbnail: thumbUrl || undefined,
|
|
1887
1951
|
thumbnailWidth:
|
|
@@ -1915,6 +1979,16 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1915
1979
|
);
|
|
1916
1980
|
await Promise.all(workers);
|
|
1917
1981
|
}
|
|
1982
|
+
try {
|
|
1983
|
+
await navPlace.writeNavPlaceDataset(navPlaceRecords);
|
|
1984
|
+
} catch (error) {
|
|
1985
|
+
try {
|
|
1986
|
+
console.warn(
|
|
1987
|
+
'[canopy][navPlace] failed to write dataset:',
|
|
1988
|
+
error && error.message ? error.message : error
|
|
1989
|
+
);
|
|
1990
|
+
} catch (_) {}
|
|
1991
|
+
}
|
|
1918
1992
|
return {iiifRecords};
|
|
1919
1993
|
}
|
|
1920
1994
|
|
package/lib/build/mdx.js
CHANGED
|
@@ -1739,6 +1739,128 @@ async function ensureTimelineRuntime() {
|
|
|
1739
1739
|
logLevel: "silent",
|
|
1740
1740
|
minify: true,
|
|
1741
1741
|
plugins: [plugin],
|
|
1742
|
+
loader: {
|
|
1743
|
+
".png": "dataurl",
|
|
1744
|
+
".svg": "dataurl",
|
|
1745
|
+
".gif": "dataurl",
|
|
1746
|
+
},
|
|
1747
|
+
});
|
|
1748
|
+
try {
|
|
1749
|
+
const {logLine} = require("./log");
|
|
1750
|
+
let size = 0;
|
|
1751
|
+
try {
|
|
1752
|
+
const st = fs.statSync(outFile);
|
|
1753
|
+
size = (st && st.size) || 0;
|
|
1754
|
+
} catch (_) {}
|
|
1755
|
+
const kb = size ? ` (${(size / 1024).toFixed(1)} KB)` : "";
|
|
1756
|
+
const rel = path.relative(process.cwd(), outFile).split(path.sep).join("/");
|
|
1757
|
+
logLine(`✓ Wrote ${rel}${kb}`, "cyan");
|
|
1758
|
+
} catch (_) {}
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
async function ensureMapRuntime() {
|
|
1762
|
+
let esbuild = null;
|
|
1763
|
+
try {
|
|
1764
|
+
esbuild = require("../ui/node_modules/esbuild");
|
|
1765
|
+
} catch (_) {
|
|
1766
|
+
try {
|
|
1767
|
+
esbuild = require("esbuild");
|
|
1768
|
+
} catch (_) {}
|
|
1769
|
+
}
|
|
1770
|
+
if (!esbuild)
|
|
1771
|
+
throw new Error(
|
|
1772
|
+
"Map runtime bundling requires esbuild. Install dependencies before building."
|
|
1773
|
+
);
|
|
1774
|
+
ensureDirSync(OUT_DIR);
|
|
1775
|
+
const scriptsDir = path.join(OUT_DIR, "scripts");
|
|
1776
|
+
ensureDirSync(scriptsDir);
|
|
1777
|
+
const outFile = path.join(scriptsDir, "canopy-map.js");
|
|
1778
|
+
const entryFile = path.join(
|
|
1779
|
+
__dirname,
|
|
1780
|
+
"..",
|
|
1781
|
+
"components",
|
|
1782
|
+
"map-runtime.js"
|
|
1783
|
+
);
|
|
1784
|
+
const reactShim = `
|
|
1785
|
+
const React = (typeof window !== 'undefined' && window.React) || {};
|
|
1786
|
+
export default React;
|
|
1787
|
+
export const Children = React.Children;
|
|
1788
|
+
export const Component = React.Component;
|
|
1789
|
+
export const Fragment = React.Fragment;
|
|
1790
|
+
export const createElement = React.createElement;
|
|
1791
|
+
export const cloneElement = React.cloneElement;
|
|
1792
|
+
export const createContext = React.createContext;
|
|
1793
|
+
export const forwardRef = React.forwardRef;
|
|
1794
|
+
export const memo = React.memo;
|
|
1795
|
+
export const startTransition = React.startTransition;
|
|
1796
|
+
export const isValidElement = React.isValidElement;
|
|
1797
|
+
export const useEffect = React.useEffect;
|
|
1798
|
+
export const useLayoutEffect = React.useLayoutEffect;
|
|
1799
|
+
export const useMemo = React.useMemo;
|
|
1800
|
+
export const useState = React.useState;
|
|
1801
|
+
export const useRef = React.useRef;
|
|
1802
|
+
export const useCallback = React.useCallback;
|
|
1803
|
+
export const useContext = React.useContext;
|
|
1804
|
+
export const useReducer = React.useReducer;
|
|
1805
|
+
export const useId = React.useId;
|
|
1806
|
+
`;
|
|
1807
|
+
const rdomShim = `
|
|
1808
|
+
const ReactDOM = (typeof window !== 'undefined' && window.ReactDOM) || {};
|
|
1809
|
+
export default ReactDOM;
|
|
1810
|
+
export const render = ReactDOM.render;
|
|
1811
|
+
export const hydrate = ReactDOM.hydrate;
|
|
1812
|
+
export const findDOMNode = ReactDOM.findDOMNode;
|
|
1813
|
+
export const unmountComponentAtNode = ReactDOM.unmountComponentAtNode;
|
|
1814
|
+
export const createPortal = ReactDOM.createPortal;
|
|
1815
|
+
export const flushSync = ReactDOM.flushSync;
|
|
1816
|
+
export const unstable_batchedUpdates = ReactDOM.unstable_batchedUpdates;
|
|
1817
|
+
export const unstable_renderSubtreeIntoContainer = ReactDOM.unstable_renderSubtreeIntoContainer;
|
|
1818
|
+
`;
|
|
1819
|
+
const rdomClientShim = `
|
|
1820
|
+
const RDC = (typeof window !== 'undefined' && window.ReactDOMClient) || {};
|
|
1821
|
+
export const createRoot = RDC.createRoot;
|
|
1822
|
+
export const hydrateRoot = RDC.hydrateRoot;
|
|
1823
|
+
`;
|
|
1824
|
+
const plugin = {
|
|
1825
|
+
name: "canopy-react-shims-map",
|
|
1826
|
+
setup(build) {
|
|
1827
|
+
const ns = "canopy-map-shim";
|
|
1828
|
+
build.onResolve({filter: /^react$/}, () => ({path: "react", namespace: ns}));
|
|
1829
|
+
build.onResolve({filter: /^react-dom$/}, () => ({path: "react-dom", namespace: ns}));
|
|
1830
|
+
build.onResolve({filter: /^react-dom\/client$/}, () => ({
|
|
1831
|
+
path: "react-dom-client",
|
|
1832
|
+
namespace: ns,
|
|
1833
|
+
}));
|
|
1834
|
+
build.onLoad({filter: /^react$/, namespace: ns}, () => ({
|
|
1835
|
+
contents: reactShim,
|
|
1836
|
+
loader: "js",
|
|
1837
|
+
}));
|
|
1838
|
+
build.onLoad({filter: /^react-dom$/, namespace: ns}, () => ({
|
|
1839
|
+
contents: rdomShim,
|
|
1840
|
+
loader: "js",
|
|
1841
|
+
}));
|
|
1842
|
+
build.onLoad({filter: /^react-dom-client$/, namespace: ns}, () => ({
|
|
1843
|
+
contents: rdomClientShim,
|
|
1844
|
+
loader: "js",
|
|
1845
|
+
}));
|
|
1846
|
+
},
|
|
1847
|
+
};
|
|
1848
|
+
await esbuild.build({
|
|
1849
|
+
entryPoints: [entryFile],
|
|
1850
|
+
outfile: outFile,
|
|
1851
|
+
platform: "browser",
|
|
1852
|
+
format: "iife",
|
|
1853
|
+
bundle: true,
|
|
1854
|
+
sourcemap: false,
|
|
1855
|
+
target: ["es2018"],
|
|
1856
|
+
logLevel: "silent",
|
|
1857
|
+
minify: true,
|
|
1858
|
+
plugins: [plugin],
|
|
1859
|
+
loader: {
|
|
1860
|
+
".png": "dataurl",
|
|
1861
|
+
".svg": "dataurl",
|
|
1862
|
+
".gif": "dataurl",
|
|
1863
|
+
},
|
|
1742
1864
|
});
|
|
1743
1865
|
try {
|
|
1744
1866
|
const {logLine} = require("./log");
|
|
@@ -1768,6 +1890,7 @@ module.exports = {
|
|
|
1768
1890
|
ensureClientRuntime,
|
|
1769
1891
|
ensureSliderRuntime,
|
|
1770
1892
|
ensureTimelineRuntime,
|
|
1893
|
+
ensureMapRuntime,
|
|
1771
1894
|
ensureHeroRuntime,
|
|
1772
1895
|
ensureFacetsRuntime,
|
|
1773
1896
|
ensureReactGlobals,
|
package/lib/build/pages.js
CHANGED
|
@@ -176,6 +176,7 @@ async function renderContentMdxToHtml(filePath, outPath, extraProps = {}, source
|
|
|
176
176
|
const needsHydrateSlider = body.includes('data-canopy-slider');
|
|
177
177
|
const needsHeroSlider = body.includes('data-canopy-hero-slider');
|
|
178
178
|
const needsTimeline = body.includes('data-canopy-timeline');
|
|
179
|
+
const needsMap = body.includes('data-canopy-map');
|
|
179
180
|
const needsSearchForm = true; // search form runtime is global
|
|
180
181
|
const needsFacets = body.includes('data-canopy-related-items');
|
|
181
182
|
const needsCustomClients = body.includes('data-canopy-client-component');
|
|
@@ -194,6 +195,12 @@ async function renderContentMdxToHtml(filePath, outPath, extraProps = {}, source
|
|
|
194
195
|
const timelineRel = needsTimeline
|
|
195
196
|
? path.relative(path.dirname(outPath), path.join(OUT_DIR, 'scripts', 'canopy-timeline.js')).split(path.sep).join('/')
|
|
196
197
|
: null;
|
|
198
|
+
const mapRel = needsMap
|
|
199
|
+
? path.relative(path.dirname(outPath), path.join(OUT_DIR, 'scripts', 'canopy-map.js')).split(path.sep).join('/')
|
|
200
|
+
: null;
|
|
201
|
+
const mapCssRel = needsMap
|
|
202
|
+
? path.relative(path.dirname(outPath), path.join(OUT_DIR, 'scripts', 'canopy-map.css')).split(path.sep).join('/')
|
|
203
|
+
: null;
|
|
197
204
|
const facetsRel = needsFacets
|
|
198
205
|
? path.relative(path.dirname(outPath), path.join(OUT_DIR, 'scripts', 'canopy-related-items.js')).split(path.sep).join('/')
|
|
199
206
|
: null;
|
|
@@ -226,6 +233,7 @@ async function renderContentMdxToHtml(filePath, outPath, extraProps = {}, source
|
|
|
226
233
|
const primaryClassicScripts = [];
|
|
227
234
|
if (heroRel) primaryClassicScripts.push(heroRel);
|
|
228
235
|
if (timelineRel) primaryClassicScripts.push(timelineRel);
|
|
236
|
+
if (mapRel) primaryClassicScripts.push(mapRel);
|
|
229
237
|
if (facetsRel) primaryClassicScripts.push(facetsRel);
|
|
230
238
|
const secondaryClassicScripts = [];
|
|
231
239
|
if (searchFormRel) secondaryClassicScripts.push(searchFormRel);
|
|
@@ -239,6 +247,7 @@ async function renderContentMdxToHtml(filePath, outPath, extraProps = {}, source
|
|
|
239
247
|
needsHydrateSlider ||
|
|
240
248
|
needsFacets ||
|
|
241
249
|
needsTimeline ||
|
|
250
|
+
needsMap ||
|
|
242
251
|
(customClientRel && needsCustomClients)
|
|
243
252
|
);
|
|
244
253
|
let vendorTag = '';
|
|
@@ -279,6 +288,15 @@ async function renderContentMdxToHtml(filePath, outPath, extraProps = {}, source
|
|
|
279
288
|
} catch (_) {}
|
|
280
289
|
extraStyles.push(`<link rel="stylesheet" href="${rel}">`);
|
|
281
290
|
}
|
|
291
|
+
if (mapCssRel) {
|
|
292
|
+
let rel = mapCssRel;
|
|
293
|
+
try {
|
|
294
|
+
const mapCssAbs = path.join(OUT_DIR, 'scripts', 'canopy-map.css');
|
|
295
|
+
const st = fs.statSync(mapCssAbs);
|
|
296
|
+
rel += `?v=${Math.floor(st.mtimeMs || Date.now())}`;
|
|
297
|
+
} catch (_) {}
|
|
298
|
+
extraStyles.push(`<link rel="stylesheet" href="${rel}">`);
|
|
299
|
+
}
|
|
282
300
|
if (extraStyles.length) headSegments.push(extraStyles.join(''));
|
|
283
301
|
if (vendorTag) headSegments.push(vendorTag);
|
|
284
302
|
if (extraScripts.length) headSegments.push(extraScripts.join(''));
|
package/lib/build/runtimes.js
CHANGED
|
@@ -5,6 +5,7 @@ async function prepareAllRuntimes() {
|
|
|
5
5
|
const mdx = require('./mdx');
|
|
6
6
|
try { await mdx.ensureClientRuntime(); } catch (_) {}
|
|
7
7
|
try { if (typeof mdx.ensureTimelineRuntime === 'function') await mdx.ensureTimelineRuntime(); } catch (_) {}
|
|
8
|
+
try { if (typeof mdx.ensureMapRuntime === 'function') await mdx.ensureMapRuntime(); } catch (_) {}
|
|
8
9
|
try { if (typeof mdx.ensureHeroRuntime === 'function') await mdx.ensureHeroRuntime(); } catch (_) {}
|
|
9
10
|
try { if (typeof mdx.ensureFacetsRuntime === 'function') await mdx.ensureFacetsRuntime(); } catch (_) {}
|
|
10
11
|
try { if (typeof mdx.ensureReactGlobals === 'function') await mdx.ensureReactGlobals(); } catch (_) {}
|
package/lib/build/sitemap.js
CHANGED
|
@@ -106,10 +106,68 @@ async function cleanupLegacySitemaps() {
|
|
|
106
106
|
await Promise.all(deletions);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
async function ensureNoExtensionGuards(fileNames) {
|
|
110
|
+
const guards = new Map();
|
|
111
|
+
(Array.isArray(fileNames) ? fileNames : []).forEach((file) => {
|
|
112
|
+
const raw = typeof file === 'string' ? file.trim() : '';
|
|
113
|
+
if (!raw || !/\.xml$/i.test(raw)) return;
|
|
114
|
+
const base = raw.replace(/\.xml$/i, '');
|
|
115
|
+
if (!base || base === raw) return;
|
|
116
|
+
guards.set(base, raw);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
let entries;
|
|
120
|
+
try {
|
|
121
|
+
entries = await fsp.readdir(OUT_DIR, { withFileTypes: true });
|
|
122
|
+
} catch (_) {
|
|
123
|
+
entries = [];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const markerName = '.canopy-xml-guard';
|
|
127
|
+
const staleRemovals = [];
|
|
128
|
+
for (const entry of entries) {
|
|
129
|
+
if (!entry || !entry.isDirectory()) continue;
|
|
130
|
+
const dirName = entry.name;
|
|
131
|
+
const dirPath = path.join(OUT_DIR, dirName);
|
|
132
|
+
const markerPath = path.join(dirPath, markerName);
|
|
133
|
+
let hasMarker = false;
|
|
134
|
+
try {
|
|
135
|
+
const stat = await fsp.stat(markerPath);
|
|
136
|
+
hasMarker = stat.isFile();
|
|
137
|
+
} catch (_) {}
|
|
138
|
+
if (!hasMarker) continue;
|
|
139
|
+
if (guards.has(dirName)) {
|
|
140
|
+
guards.delete(dirName);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
staleRemovals.push(
|
|
144
|
+
fsp
|
|
145
|
+
.rm(dirPath, { recursive: true, force: true })
|
|
146
|
+
.catch(() => {})
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
await Promise.all(staleRemovals);
|
|
150
|
+
|
|
151
|
+
if (!guards.size) return;
|
|
152
|
+
|
|
153
|
+
const creations = [];
|
|
154
|
+
for (const base of guards.keys()) {
|
|
155
|
+
const dirPath = path.join(OUT_DIR, base);
|
|
156
|
+
const markerPath = path.join(dirPath, markerName);
|
|
157
|
+
creations.push(
|
|
158
|
+
fsp
|
|
159
|
+
.mkdir(dirPath, { recursive: true })
|
|
160
|
+
.then(() => fsp.writeFile(markerPath, '', 'utf8').catch(() => {}))
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
await Promise.all(creations);
|
|
164
|
+
}
|
|
165
|
+
|
|
109
166
|
async function writeSitemap(iiifRecords, pageRecords) {
|
|
110
167
|
const urls = collectAbsoluteUrls(iiifRecords, pageRecords);
|
|
111
168
|
if (!urls.length) {
|
|
112
169
|
await cleanupLegacySitemaps();
|
|
170
|
+
await ensureNoExtensionGuards([]);
|
|
113
171
|
logLine('• No URLs to write to sitemap', 'yellow');
|
|
114
172
|
return;
|
|
115
173
|
}
|
|
@@ -117,15 +175,19 @@ async function writeSitemap(iiifRecords, pageRecords) {
|
|
|
117
175
|
|
|
118
176
|
const chunks = chunkList(urls, MAX_URLS_PER_SITEMAP);
|
|
119
177
|
const indexEntries = [];
|
|
178
|
+
const writtenFiles = [];
|
|
120
179
|
for (let i = 0; i < chunks.length; i += 1) {
|
|
121
180
|
const fileName = `sitemap-${i + 1}.xml`;
|
|
122
181
|
const dest = path.join(OUT_DIR, fileName);
|
|
123
182
|
await fsp.writeFile(dest, buildUrlsetXml(chunks[i]), 'utf8');
|
|
124
183
|
indexEntries.push({ loc: absoluteUrl(fileName) });
|
|
184
|
+
writtenFiles.push(fileName);
|
|
125
185
|
}
|
|
126
186
|
|
|
127
187
|
const indexDest = path.join(OUT_DIR, SITEMAP_INDEX_BASENAME);
|
|
128
188
|
await fsp.writeFile(indexDest, buildSitemapIndexXml(indexEntries), 'utf8');
|
|
189
|
+
writtenFiles.push(SITEMAP_INDEX_BASENAME);
|
|
190
|
+
await ensureNoExtensionGuards(writtenFiles);
|
|
129
191
|
logLine(
|
|
130
192
|
`✓ Wrote sitemap index (${chunks.length} files, ${urls.length} urls total)`,
|
|
131
193
|
'cyan'
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { createRoot } from 'react-dom/client';
|
|
3
|
+
import { Map } from '../../ui/dist/index.mjs';
|
|
4
|
+
import Leaflet from 'leaflet';
|
|
5
|
+
import 'leaflet/dist/leaflet.css';
|
|
6
|
+
import 'leaflet.markercluster';
|
|
7
|
+
import 'leaflet.markercluster/dist/MarkerCluster.css';
|
|
8
|
+
import 'leaflet.markercluster/dist/MarkerCluster.Default.css';
|
|
9
|
+
|
|
10
|
+
function broadcastLeafletReady() {
|
|
11
|
+
let updated = false;
|
|
12
|
+
try {
|
|
13
|
+
if (typeof globalThis !== 'undefined' && !globalThis.L) {
|
|
14
|
+
globalThis.L = Leaflet;
|
|
15
|
+
updated = true;
|
|
16
|
+
}
|
|
17
|
+
} catch (_) {}
|
|
18
|
+
try {
|
|
19
|
+
if (typeof window !== 'undefined' && !window.L) {
|
|
20
|
+
window.L = Leaflet;
|
|
21
|
+
updated = true;
|
|
22
|
+
}
|
|
23
|
+
} catch (_) {}
|
|
24
|
+
if (updated && typeof document !== 'undefined') {
|
|
25
|
+
try {
|
|
26
|
+
document.dispatchEvent(new CustomEvent('canopy:leaflet-ready'));
|
|
27
|
+
} catch (_) {
|
|
28
|
+
const evt = document.createEvent('CustomEvent');
|
|
29
|
+
evt.initCustomEvent('canopy:leaflet-ready', false, false, {});
|
|
30
|
+
document.dispatchEvent(evt);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
broadcastLeafletReady();
|
|
36
|
+
|
|
37
|
+
function ready(fn) {
|
|
38
|
+
if (typeof document === 'undefined') return;
|
|
39
|
+
if (document.readyState === 'loading') {
|
|
40
|
+
document.addEventListener('DOMContentLoaded', fn, { once: true });
|
|
41
|
+
} else {
|
|
42
|
+
fn();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function parseProps(el) {
|
|
47
|
+
try {
|
|
48
|
+
const script = el.querySelector('script[type="application/json"]');
|
|
49
|
+
if (script) return JSON.parse(script.textContent || '{}');
|
|
50
|
+
const raw = el.getAttribute('data-props') || '{}';
|
|
51
|
+
return JSON.parse(raw);
|
|
52
|
+
} catch (_) {
|
|
53
|
+
return {};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function mount(el) {
|
|
58
|
+
try {
|
|
59
|
+
if (!el || el.getAttribute('data-canopy-map-mounted') === '1') return;
|
|
60
|
+
const props = parseProps(el);
|
|
61
|
+
const script = el.querySelector('script[type="application/json"]');
|
|
62
|
+
if (script && script.parentNode === el) {
|
|
63
|
+
script.parentNode.removeChild(script);
|
|
64
|
+
}
|
|
65
|
+
while (el.firstChild) {
|
|
66
|
+
el.removeChild(el.firstChild);
|
|
67
|
+
}
|
|
68
|
+
const root = createRoot(el);
|
|
69
|
+
root.render(React.createElement(Map, props));
|
|
70
|
+
el.setAttribute('data-canopy-map-mounted', '1');
|
|
71
|
+
} catch (error) {
|
|
72
|
+
try {
|
|
73
|
+
console.warn('[canopy][map] failed to mount map', error);
|
|
74
|
+
} catch (_) {}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function scan() {
|
|
79
|
+
try {
|
|
80
|
+
document
|
|
81
|
+
.querySelectorAll('[data-canopy-map]:not([data-canopy-map-mounted="1"])')
|
|
82
|
+
.forEach(mount);
|
|
83
|
+
} catch (_) {}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function observe() {
|
|
87
|
+
try {
|
|
88
|
+
const obs = new MutationObserver((mutations) => {
|
|
89
|
+
const toMount = [];
|
|
90
|
+
mutations.forEach((mutation) => {
|
|
91
|
+
mutation.addedNodes &&
|
|
92
|
+
mutation.addedNodes.forEach((node) => {
|
|
93
|
+
if (!(node instanceof Element)) return;
|
|
94
|
+
if (node.matches && node.matches('[data-canopy-map]')) toMount.push(node);
|
|
95
|
+
const inner = node.querySelectorAll
|
|
96
|
+
? node.querySelectorAll('[data-canopy-map]')
|
|
97
|
+
: [];
|
|
98
|
+
inner && inner.forEach && inner.forEach((el) => toMount.push(el));
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
if (toMount.length) Promise.resolve().then(() => toMount.forEach(mount));
|
|
102
|
+
});
|
|
103
|
+
obs.observe(document.documentElement || document.body, {
|
|
104
|
+
childList: true,
|
|
105
|
+
subtree: true,
|
|
106
|
+
});
|
|
107
|
+
} catch (_) {}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
ready(() => {
|
|
111
|
+
if (typeof document === 'undefined') return;
|
|
112
|
+
scan();
|
|
113
|
+
observe();
|
|
114
|
+
});
|