From ecb2a9e91a52f5dfd05ea1dcc3e37b67e570e453 Mon Sep 17 00:00:00 2001 From: Tomasz Pluskiewicz Date: Fri, 29 Aug 2025 12:37:59 +0200 Subject: [PATCH 1/9] feat(showcases): JSON-LD export --- opendata.swiss/ui/content.config.ts | 1 + ...aner-und-entscheidungstr\303\244ger.de.md" | 4 +-- ...aner-und-entscheidungstr\303\244ger.en.md" | 4 +-- ...aner-und-entscheidungstr\303\244ger.fr.md" | 4 +-- ...aner-und-entscheidungstr\303\244ger.it.md" | 4 +-- opendata.swiss/ui/package-lock.json | 31 ++++++++++++++++++ opendata.swiss/ui/package.json | 2 ++ opendata.swiss/ui/server/routes/showcases.ts | 32 +++++++++++++++++++ opendata.swiss/ui/server/tsconfig.json | 3 ++ .../src/admin/VocabularySelectComponent.jsx | 2 +- 10 files changed, 78 insertions(+), 9 deletions(-) create mode 100644 opendata.swiss/ui/server/routes/showcases.ts create mode 100644 opendata.swiss/ui/server/tsconfig.json diff --git a/opendata.swiss/ui/content.config.ts b/opendata.swiss/ui/content.config.ts index ceafc25..d4cce49 100644 --- a/opendata.swiss/ui/content.config.ts +++ b/opendata.swiss/ui/content.config.ts @@ -55,6 +55,7 @@ export default defineContentConfig({ label: z.string(), })).optional(), tags: z.array(z.string()).optional(), + rawbody: z.string(), }) }) } diff --git "a/opendata.swiss/ui/content/showcases/mietpreisentwicklung-in-bern-\342\200\223-ein-instrument-f\303\274r-mieter-stadtplaner-und-entscheidungstr\303\244ger.de.md" "b/opendata.swiss/ui/content/showcases/mietpreisentwicklung-in-bern-\342\200\223-ein-instrument-f\303\274r-mieter-stadtplaner-und-entscheidungstr\303\244ger.de.md" index af87afc..6e8b8bc 100644 --- "a/opendata.swiss/ui/content/showcases/mietpreisentwicklung-in-bern-\342\200\223-ein-instrument-f\303\274r-mieter-stadtplaner-und-entscheidungstr\303\244ger.de.md" +++ "b/opendata.swiss/ui/content/showcases/mietpreisentwicklung-in-bern-\342\200\223-ein-instrument-f\303\274r-mieter-stadtplaner-und-entscheidungstr\303\244ger.de.md" @@ -5,8 +5,8 @@ title: Mietpreisentwicklung in Bern – Ein Instrument für Mieter, Stadtplaner image: https://repository-images.githubusercontent.com/788945570/a1998415-fef9-4518-8a5d-d2937f17edec url: https://giodi.github.io/dashboard-wohnungsmietpreise-stadt-bern/ categories: - - SOCI - - REGI + - http://publications.europa.eu/resource/authority/data-theme/SOCI + - http://publications.europa.eu/resource/authority/data-theme/REGI type: application datasets: - id: 85787-bundesamt-fur-statistik-bfs diff --git "a/opendata.swiss/ui/content/showcases/mietpreisentwicklung-in-bern-\342\200\223-ein-instrument-f\303\274r-mieter-stadtplaner-und-entscheidungstr\303\244ger.en.md" "b/opendata.swiss/ui/content/showcases/mietpreisentwicklung-in-bern-\342\200\223-ein-instrument-f\303\274r-mieter-stadtplaner-und-entscheidungstr\303\244ger.en.md" index 003f306..0e39558 100644 --- "a/opendata.swiss/ui/content/showcases/mietpreisentwicklung-in-bern-\342\200\223-ein-instrument-f\303\274r-mieter-stadtplaner-und-entscheidungstr\303\244ger.en.md" +++ "b/opendata.swiss/ui/content/showcases/mietpreisentwicklung-in-bern-\342\200\223-ein-instrument-f\303\274r-mieter-stadtplaner-und-entscheidungstr\303\244ger.en.md" @@ -5,8 +5,8 @@ title: Rent Price Development in Bern – A Tool for Tenants, Urban Planners, an image: https://repository-images.githubusercontent.com/788945570/a1998415-fef9-4518-8a5d-d2937f17edec url: https://giodi.github.io/dashboard-wohnungsmietpreise-stadt-bern/ categories: - - SOCI - - REGI + - http://publications.europa.eu/resource/authority/data-theme/SOCI + - http://publications.europa.eu/resource/authority/data-theme/REGI type: application datasets: - id: 85787-bundesamt-fur-statistik-bfs diff --git "a/opendata.swiss/ui/content/showcases/mietpreisentwicklung-in-bern-\342\200\223-ein-instrument-f\303\274r-mieter-stadtplaner-und-entscheidungstr\303\244ger.fr.md" "b/opendata.swiss/ui/content/showcases/mietpreisentwicklung-in-bern-\342\200\223-ein-instrument-f\303\274r-mieter-stadtplaner-und-entscheidungstr\303\244ger.fr.md" index e1bf763..dabfbc7 100644 --- "a/opendata.swiss/ui/content/showcases/mietpreisentwicklung-in-bern-\342\200\223-ein-instrument-f\303\274r-mieter-stadtplaner-und-entscheidungstr\303\244ger.fr.md" +++ "b/opendata.swiss/ui/content/showcases/mietpreisentwicklung-in-bern-\342\200\223-ein-instrument-f\303\274r-mieter-stadtplaner-und-entscheidungstr\303\244ger.fr.md" @@ -5,8 +5,8 @@ title: Évolution des loyers à Berne – Un outil pour les locataires, urbanist image: https://repository-images.githubusercontent.com/788945570/a1998415-fef9-4518-8a5d-d2937f17edec url: https://giodi.github.io/dashboard-wohnungsmietpreise-stadt-bern/ categories: - - SOCI - - REGI + - http://publications.europa.eu/resource/authority/data-theme/SOCI + - http://publications.europa.eu/resource/authority/data-theme/REGI type: application datasets: - id: 85787-bundesamt-fur-statistik-bfs diff --git "a/opendata.swiss/ui/content/showcases/mietpreisentwicklung-in-bern-\342\200\223-ein-instrument-f\303\274r-mieter-stadtplaner-und-entscheidungstr\303\244ger.it.md" "b/opendata.swiss/ui/content/showcases/mietpreisentwicklung-in-bern-\342\200\223-ein-instrument-f\303\274r-mieter-stadtplaner-und-entscheidungstr\303\244ger.it.md" index 7e39ba6..fe0c4ba 100644 --- "a/opendata.swiss/ui/content/showcases/mietpreisentwicklung-in-bern-\342\200\223-ein-instrument-f\303\274r-mieter-stadtplaner-und-entscheidungstr\303\244ger.it.md" +++ "b/opendata.swiss/ui/content/showcases/mietpreisentwicklung-in-bern-\342\200\223-ein-instrument-f\303\274r-mieter-stadtplaner-und-entscheidungstr\303\244ger.it.md" @@ -5,8 +5,8 @@ title: Andamento degli affitti a Berna – Uno strumento per inquilini, urbanist image: https://repository-images.githubusercontent.com/788945570/a1998415-fef9-4518-8a5d-d2937f17edec url: https://giodi.github.io/dashboard-wohnungsmietpreise-stadt-bern/ categories: - - SOCI - - REGI + - http://publications.europa.eu/resource/authority/data-theme/SOCI + - http://publications.europa.eu/resource/authority/data-theme/REGI type: application datasets: - id: 85787-bundesamt-fur-statistik-bfs diff --git a/opendata.swiss/ui/package-lock.json b/opendata.swiss/ui/package-lock.json index a8daa07..0f6655f 100644 --- a/opendata.swiss/ui/package-lock.json +++ b/opendata.swiss/ui/package-lock.json @@ -23,9 +23,11 @@ "nuxt": "^4.0.0", "pinia": "^3.0.3", "react-bootstrap-typeahead": "^6.4.1", + "remark": "^15.0.1", "rimraf": "^6.0.1", "sass-embedded": "^1.89.2", "sharp": "^0.34.3", + "strip-markdown": "^6.0.0", "swiper": "^11.2.10", "vue": "^3.5.17", "vue-i18n": "^11.1.10", @@ -22624,6 +22626,22 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remark": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/remark/-/remark-15.0.1.tgz", + "integrity": "sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/remark-emoji": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/remark-emoji/-/remark-emoji-5.0.1.tgz", @@ -24655,6 +24673,19 @@ "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", "license": "MIT" }, + "node_modules/strip-markdown": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-markdown/-/strip-markdown-6.0.0.tgz", + "integrity": "sha512-mSa8FtUoX3ExJYDkjPUTC14xaBAn4Ik5GPQD45G5E2egAmeV3kHgVSTfIoSDggbF6Pk9stahVgqsLCNExv6jHw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/structured-clone-es": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/structured-clone-es/-/structured-clone-es-1.0.0.tgz", diff --git a/opendata.swiss/ui/package.json b/opendata.swiss/ui/package.json index a364242..a2e1928 100644 --- a/opendata.swiss/ui/package.json +++ b/opendata.swiss/ui/package.json @@ -27,9 +27,11 @@ "nuxt": "^4.0.0", "pinia": "^3.0.3", "react-bootstrap-typeahead": "^6.4.1", + "remark": "^15.0.1", "rimraf": "^6.0.1", "sass-embedded": "^1.89.2", "sharp": "^0.34.3", + "strip-markdown": "^6.0.0", "swiper": "^11.2.10", "vue": "^3.5.17", "vue-i18n": "^11.1.10", diff --git a/opendata.swiss/ui/server/routes/showcases.ts b/opendata.swiss/ui/server/routes/showcases.ts new file mode 100644 index 0000000..3e70650 --- /dev/null +++ b/opendata.swiss/ui/server/routes/showcases.ts @@ -0,0 +1,32 @@ +import {remark} from 'remark' +import strip from 'strip-markdown' + +const frontMatterPattern = /^---[\s\S]*---/ + +export default defineEventHandler(async (event) => { + const showcases = await queryCollection(event, 'showcases') + .select('categories', 'datasets', 'description', 'rawbody') + .all() + + const showcasesLD = showcases.map(async (showcase) => ({ + ...showcase, + '@type': 'CreativeWork', + rawbody: await stripMarkdown(showcase.rawbody), + })) + + return { + '@context': ['https://schema.org', { + id: 'identifier', + rawbody: 'text', + categories: { '@type': '@id' }, + datasets: { + '@id': 'hasPart', + } + }], + '@graph': await Promise.all(showcasesLD) + } +}) + +function stripMarkdown(md: string) { + return remark().use(strip).process(md.replace(frontMatterPattern, '').trim()) +} diff --git a/opendata.swiss/ui/server/tsconfig.json b/opendata.swiss/ui/server/tsconfig.json new file mode 100644 index 0000000..b9ed69c --- /dev/null +++ b/opendata.swiss/ui/server/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../.nuxt/tsconfig.server.json" +} diff --git a/opendata.swiss/ui/src/admin/VocabularySelectComponent.jsx b/opendata.swiss/ui/src/admin/VocabularySelectComponent.jsx index 520ec03..efed33f 100644 --- a/opendata.swiss/ui/src/admin/VocabularySelectComponent.jsx +++ b/opendata.swiss/ui/src/admin/VocabularySelectComponent.jsx @@ -41,7 +41,7 @@ export default class VocabularySelectComponent extends PiveauSearchComponent { const res = await fetch(url) const { result: { results }} = await res.json() - return results.map(({pref_label, id}) => ({label: pref_label.de, value: id})) + return results.map(({pref_label, resource}) => ({label: pref_label.de, value: resource})) }) state = { From a37af442548f26c3f1e55168d292dde9aa125b4d Mon Sep 17 00:00:00 2001 From: Tomasz Pluskiewicz Date: Fri, 29 Aug 2025 13:06:58 +0200 Subject: [PATCH 2/9] fix(showcases): only actual stripped text --- opendata.swiss/ui/server/routes/showcases.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/opendata.swiss/ui/server/routes/showcases.ts b/opendata.swiss/ui/server/routes/showcases.ts index 3e70650..4a4d5b2 100644 --- a/opendata.swiss/ui/server/routes/showcases.ts +++ b/opendata.swiss/ui/server/routes/showcases.ts @@ -27,6 +27,7 @@ export default defineEventHandler(async (event) => { } }) -function stripMarkdown(md: string) { - return remark().use(strip).process(md.replace(frontMatterPattern, '').trim()) +async function stripMarkdown(md: string) { + const stripped = await remark().use(strip).process(md.replace(frontMatterPattern, '').trim()) + return stripped.value } From b5c7f48a26536928f59bf07c1f54b75909477326 Mon Sep 17 00:00:00 2001 From: Tomasz Pluskiewicz Date: Fri, 29 Aug 2025 15:38:48 +0200 Subject: [PATCH 3/9] feat(showcases): aggregating showcase translations --- opendata.swiss/ui/server/api/showcases.ts | 73 ++++++++++++++++++++ opendata.swiss/ui/server/routes/showcases.ts | 33 --------- 2 files changed, 73 insertions(+), 33 deletions(-) create mode 100644 opendata.swiss/ui/server/api/showcases.ts delete mode 100644 opendata.swiss/ui/server/routes/showcases.ts diff --git a/opendata.swiss/ui/server/api/showcases.ts b/opendata.swiss/ui/server/api/showcases.ts new file mode 100644 index 0000000..9dae39c --- /dev/null +++ b/opendata.swiss/ui/server/api/showcases.ts @@ -0,0 +1,73 @@ +import {remark} from 'remark' +import strip from 'strip-markdown' + +const frontMatterPattern = /^---[\s\S]*---/ +const stemPattern = /^showcases\/(?.*)\.(?\w\w)$/ + +interface AggregateShowcase { + id: string + '@type': 'CreativeWork' + title: Record + image: string | undefined + description: Record + categories: string[] + datasets: Array<{ id: string; label: string }> + text: Record +} + +export default defineEventHandler(async (event) => { + const showcases = await queryCollection(event, 'showcases') + .select('title', 'categories', 'datasets', 'description', 'rawbody', 'stem', 'image') + .all() + + const aggregatedShowcases = showcases.reduce(async (promise, showcase) => { + const arr = await promise + + const { stem, lang } = showcase.stem.match(stemPattern)?.groups! + let aggregate = arr.find(({ id }) => id === stem) + if (!aggregate) { + aggregate = { + id: stem, + '@type': 'CreativeWork', + title: {}, + image: showcase.image, + description: {}, + categories: showcase.categories || [], + datasets: showcase.datasets || [], + text: {} + } + arr.push(aggregate) + } + + aggregate.title[lang] = showcase.title || undefined + aggregate.description[lang] = showcase.description || undefined + aggregate.text[lang] = await stripMarkdown(showcase.rawbody) || undefined + + return arr + }, Promise.resolve([])) + + return { + '@context': ['https://schema.org', { + id: 'identifier', + categories: { '@type': '@id' }, + datasets: { + '@id': 'hasPart', + }, + title: { + '@container': '@language', + }, + description: { + '@container': '@language', + }, + text: { + '@container': '@language', + } + }], + '@graph': await aggregatedShowcases + } +}) + +async function stripMarkdown(md: string) { + const stripped = await remark().use(strip).process(md.replace(frontMatterPattern, '').trim()) + return stripped.value.toString() +} diff --git a/opendata.swiss/ui/server/routes/showcases.ts b/opendata.swiss/ui/server/routes/showcases.ts deleted file mode 100644 index 4a4d5b2..0000000 --- a/opendata.swiss/ui/server/routes/showcases.ts +++ /dev/null @@ -1,33 +0,0 @@ -import {remark} from 'remark' -import strip from 'strip-markdown' - -const frontMatterPattern = /^---[\s\S]*---/ - -export default defineEventHandler(async (event) => { - const showcases = await queryCollection(event, 'showcases') - .select('categories', 'datasets', 'description', 'rawbody') - .all() - - const showcasesLD = showcases.map(async (showcase) => ({ - ...showcase, - '@type': 'CreativeWork', - rawbody: await stripMarkdown(showcase.rawbody), - })) - - return { - '@context': ['https://schema.org', { - id: 'identifier', - rawbody: 'text', - categories: { '@type': '@id' }, - datasets: { - '@id': 'hasPart', - } - }], - '@graph': await Promise.all(showcasesLD) - } -}) - -async function stripMarkdown(md: string) { - const stripped = await remark().use(strip).process(md.replace(frontMatterPattern, '').trim()) - return stripped.value -} From 66b765edb88b4b438be6221893ed4feadab940f5 Mon Sep 17 00:00:00 2001 From: Tomasz Pluskiewicz Date: Fri, 29 Aug 2025 15:45:20 +0200 Subject: [PATCH 4/9] refactor(showcases): extract JSON-LD `@context` --- opendata.swiss/ui/server/api/showcases.ts | 33 ++++++++++++----------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/opendata.swiss/ui/server/api/showcases.ts b/opendata.swiss/ui/server/api/showcases.ts index 9dae39c..6c0ffec 100644 --- a/opendata.swiss/ui/server/api/showcases.ts +++ b/opendata.swiss/ui/server/api/showcases.ts @@ -15,6 +15,22 @@ interface AggregateShowcase { text: Record } +const ldContext = ['https://schema.org', { + id: 'identifier', + categories: { '@type': '@id' }, + datasets: { + '@id': 'exampleOfWork', + }, + title: { + '@container': '@language', + }, + description: { + '@container': '@language', + }, + text: { + '@container': '@language', + } +}]; export default defineEventHandler(async (event) => { const showcases = await queryCollection(event, 'showcases') .select('title', 'categories', 'datasets', 'description', 'rawbody', 'stem', 'image') @@ -47,22 +63,7 @@ export default defineEventHandler(async (event) => { }, Promise.resolve([])) return { - '@context': ['https://schema.org', { - id: 'identifier', - categories: { '@type': '@id' }, - datasets: { - '@id': 'hasPart', - }, - title: { - '@container': '@language', - }, - description: { - '@container': '@language', - }, - text: { - '@container': '@language', - } - }], + '@context': ldContext, '@graph': await aggregatedShowcases } }) From 0cf1955abdbed32431759c714b5ca166e0d8c6e1 Mon Sep 17 00:00:00 2001 From: Tomasz Pluskiewicz Date: Tue, 16 Sep 2025 13:06:40 +0200 Subject: [PATCH 5/9] refactor: use only dc and dcat namespaces --- opendata.swiss/ui/package-lock.json | 56 +++++++++++++++++++++++ opendata.swiss/ui/package.json | 1 + opendata.swiss/ui/server/api/showcases.ts | 30 ++++++++---- 3 files changed, 79 insertions(+), 8 deletions(-) diff --git a/opendata.swiss/ui/package-lock.json b/opendata.swiss/ui/package-lock.json index dba1d75..84e4288 100644 --- a/opendata.swiss/ui/package-lock.json +++ b/opendata.swiss/ui/package-lock.json @@ -16,6 +16,7 @@ "@pinia/nuxt": "^0.11.1", "@piveau/sdk-core": "^0.0.0-beta.2", "@piveau/sdk-vue": "^1.0.0-beta.9", + "@tpluscode/rdf-ns-builders": "^5.0.0", "better-sqlite3": "^12.2.0", "bootstrap": "^5.3.7", "decap-cms-app": "^3.8.3", @@ -4552,6 +4553,33 @@ "integrity": "sha512-m7bpKCD4QMlFCjA/nKTs23fuvoVFoA83brRKmObCUNmi/9tVu8Ve3w4YQAnJu4q3Tjf5fr685HYIC/IA2zHRSg==", "license": "MIT" }, + "node_modules/@rdfjs/data-model": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@rdfjs/data-model/-/data-model-2.1.1.tgz", + "integrity": "sha512-6mcOI4DjIPS6MOZw23H8oAdujHCk5gippVNQ7mKwliYTvTNh+uqRM91B9OLqhoAoNcQ3t49Dx2ooIMRG9/6ooA==", + "license": "MIT", + "bin": { + "rdfjs-data-model-test": "bin/test.js" + } + }, + "node_modules/@rdfjs/namespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@rdfjs/namespace/-/namespace-2.0.1.tgz", + "integrity": "sha512-U85NWVGnL3gWvOZ4eXwUcv3/bom7PAcutSBQqmVWvOaslPy+kDzAJCH1WYBLpdQd4yMmJ+bpJcDl9rcHtXeixg==", + "license": "MIT", + "dependencies": { + "@rdfjs/data-model": "^2.0.1" + } + }, + "node_modules/@rdfjs/types": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@rdfjs/types/-/types-2.0.1.tgz", + "integrity": "sha512-uyAzpugX7KekAXAHq26m3JlUIZJOC0uSBhpnefGV5i15bevDyyejoB7I+9MKeUrzXD8OOUI3+4FeV1wwQr5ihA==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@react-aria/ssr": { "version": "3.9.10", "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz", @@ -5375,6 +5403,19 @@ } } }, + "node_modules/@tpluscode/rdf-ns-builders": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@tpluscode/rdf-ns-builders/-/rdf-ns-builders-5.0.0.tgz", + "integrity": "sha512-rtMFbArdief+s0z2A3TOb/gNe5O5xn9LDiEpilCf6lGYCUIfyqoOvZY80fS/eILwcF2Mj6cUQN1WBQ+1neJmaw==", + "license": "MIT", + "dependencies": { + "@rdfjs/data-model": "^2.1.0", + "@rdfjs/namespace": "^2.0.1", + "@rdfjs/types": "^2", + "@types/rdfjs__namespace": "^2.0.10", + "@zazuko/prefixes": "^2.3.0" + } + }, "node_modules/@trysound/sax": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", @@ -5491,6 +5532,15 @@ "integrity": "sha512-LriObC2+KYZD3FzCrgWGv/qufdUy4eXrxcLgQMfYXgPbLIecKIsVBaQgUPmxSSLcjmYbDTQbMgr6qr6l/eb7Bg==", "license": "MIT" }, + "node_modules/@types/rdfjs__namespace": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/rdfjs__namespace/-/rdfjs__namespace-2.0.10.tgz", + "integrity": "sha512-xoVzEIOxcpyteEmzaj94MSBbrBFs+vqv05joMhzLEiPRwsBBDnhkdBCaaDxR1Tf7wOW0kB2R1IYe4C3vEBFPgA==", + "license": "MIT", + "dependencies": { + "@rdfjs/types": "*" + } + }, "node_modules/@types/react": { "version": "19.1.12", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz", @@ -6720,6 +6770,12 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "license": "0BSD" }, + "node_modules/@zazuko/prefixes": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@zazuko/prefixes/-/prefixes-2.4.0.tgz", + "integrity": "sha512-bd53k5XgFKWR56sofHeAcIbv8o0m2HsJlbHaHbrMufUCdgiZsCLvZn84Vh1dhcsyBHOD0EIo9AD4pNWDQLVRaw==", + "license": "MIT" + }, "node_modules/abbrev": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", diff --git a/opendata.swiss/ui/package.json b/opendata.swiss/ui/package.json index c75c9df..4b00605 100644 --- a/opendata.swiss/ui/package.json +++ b/opendata.swiss/ui/package.json @@ -23,6 +23,7 @@ "@pinia/nuxt": "^0.11.1", "@piveau/sdk-core": "^0.0.0-beta.2", "@piveau/sdk-vue": "^1.0.0-beta.9", + "@tpluscode/rdf-ns-builders": "^5.0.0", "better-sqlite3": "^12.2.0", "bootstrap": "^5.3.7", "decap-cms-app": "^3.8.3", diff --git a/opendata.swiss/ui/server/api/showcases.ts b/opendata.swiss/ui/server/api/showcases.ts index 6c0ffec..f8dc3fb 100644 --- a/opendata.swiss/ui/server/api/showcases.ts +++ b/opendata.swiss/ui/server/api/showcases.ts @@ -1,5 +1,6 @@ import {remark} from 'remark' import strip from 'strip-markdown' +import {dcat, dcterms, rdfs, schema} from "@tpluscode/rdf-ns-builders"; const frontMatterPattern = /^---[\s\S]*---/ const stemPattern = /^showcases\/(?.*)\.(?\w\w)$/ @@ -13,24 +14,36 @@ interface AggregateShowcase { categories: string[] datasets: Array<{ id: string; label: string }> text: Record + tag: string[] } -const ldContext = ['https://schema.org', { - id: 'identifier', - categories: { '@type': '@id' }, +const ldContext = { + '@base': 'http://example.org/', + id: '@id', + label: rdfs.label.value, + categories: { + '@id': dcat.theme.value, + '@type': '@id' + }, datasets: { - '@id': 'exampleOfWork', + '@id': dcterms.references.value, + '@type': '@id' }, title: { + '@id': dcterms.title.value, '@container': '@language', }, description: { + '@id': dcterms.description.value, '@container': '@language', }, text: { + '@id': dcterms.description.value, '@container': '@language', - } -}]; + }, + image: schema.image.value, + tag: dcat.keyword.value, +}; export default defineEventHandler(async (event) => { const showcases = await queryCollection(event, 'showcases') .select('title', 'categories', 'datasets', 'description', 'rawbody', 'stem', 'image') @@ -39,7 +52,7 @@ export default defineEventHandler(async (event) => { const aggregatedShowcases = showcases.reduce(async (promise, showcase) => { const arr = await promise - const { stem, lang } = showcase.stem.match(stemPattern)?.groups! + const { stem, lang } = showcase.stem.match(stemPattern)?.groups || {} let aggregate = arr.find(({ id }) => id === stem) if (!aggregate) { aggregate = { @@ -50,7 +63,8 @@ export default defineEventHandler(async (event) => { description: {}, categories: showcase.categories || [], datasets: showcase.datasets || [], - text: {} + text: {}, + tag: [], } arr.push(aggregate) } From 68015e64c65424f7652cc472cab61f5e7490120b Mon Sep 17 00:00:00 2001 From: Tomasz Pluskiewicz Date: Tue, 16 Sep 2025 13:12:14 +0200 Subject: [PATCH 6/9] refactor: use the current temporary type --- opendata.swiss/ui/server/api/showcases.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/opendata.swiss/ui/server/api/showcases.ts b/opendata.swiss/ui/server/api/showcases.ts index f8dc3fb..22f2164 100644 --- a/opendata.swiss/ui/server/api/showcases.ts +++ b/opendata.swiss/ui/server/api/showcases.ts @@ -7,7 +7,7 @@ const stemPattern = /^showcases\/(?.*)\.(?\w\w)$/ interface AggregateShowcase { id: string - '@type': 'CreativeWork' + '@type': 'Showcase' title: Record image: string | undefined description: Record @@ -18,7 +18,7 @@ interface AggregateShowcase { } const ldContext = { - '@base': 'http://example.org/', + '@base': 'https://example.org/', id: '@id', label: rdfs.label.value, categories: { @@ -57,7 +57,7 @@ export default defineEventHandler(async (event) => { if (!aggregate) { aggregate = { id: stem, - '@type': 'CreativeWork', + '@type': 'Showcase', title: {}, image: showcase.image, description: {}, From de97f966cadc70f90806b9d54bc75774554fe29b Mon Sep 17 00:00:00 2001 From: Tomasz Pluskiewicz Date: Tue, 16 Sep 2025 14:00:51 +0200 Subject: [PATCH 7/9] refactor: use frontmatter plugin --- opendata.swiss/ui/package-lock.json | 84 +++++++++++++++++++++++ opendata.swiss/ui/package.json | 1 + opendata.swiss/ui/server/api/showcases.ts | 7 +- 3 files changed, 90 insertions(+), 2 deletions(-) diff --git a/opendata.swiss/ui/package-lock.json b/opendata.swiss/ui/package-lock.json index 84e4288..60f21b8 100644 --- a/opendata.swiss/ui/package-lock.json +++ b/opendata.swiss/ui/package-lock.json @@ -26,6 +26,7 @@ "pinia": "^3.0.3", "react-bootstrap-typeahead": "^6.4.1", "remark": "^15.0.1", + "remark-frontmatter": "^5.0.0", "rimraf": "^6.0.1", "sass-embedded": "^1.89.2", "sharp": "^0.34.3", @@ -12607,6 +12608,19 @@ "reusify": "^1.0.4" } }, + "node_modules/fault": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz", + "integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==", + "license": "MIT", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -12844,6 +12858,14 @@ "node": ">= 6" } }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -15627,6 +15649,36 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-frontmatter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz", + "integrity": "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "escape-string-regexp": "^5.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-extension-frontmatter": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-frontmatter/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mdast-util-gfm": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", @@ -16163,6 +16215,22 @@ "micromark-util-types": "^2.0.0" } }, + "node_modules/micromark-extension-frontmatter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz", + "integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==", + "license": "MIT", + "dependencies": { + "fault": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/micromark-extension-gfm": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", @@ -21709,6 +21777,22 @@ "node": ">=18" } }, + "node_modules/remark-frontmatter": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-5.0.0.tgz", + "integrity": "sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-frontmatter": "^2.0.0", + "micromark-extension-frontmatter": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/remark-gfm": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", diff --git a/opendata.swiss/ui/package.json b/opendata.swiss/ui/package.json index 4b00605..e81286c 100644 --- a/opendata.swiss/ui/package.json +++ b/opendata.swiss/ui/package.json @@ -33,6 +33,7 @@ "pinia": "^3.0.3", "react-bootstrap-typeahead": "^6.4.1", "remark": "^15.0.1", + "remark-frontmatter": "^5.0.0", "rimraf": "^6.0.1", "sass-embedded": "^1.89.2", "sharp": "^0.34.3", diff --git a/opendata.swiss/ui/server/api/showcases.ts b/opendata.swiss/ui/server/api/showcases.ts index 22f2164..93534aa 100644 --- a/opendata.swiss/ui/server/api/showcases.ts +++ b/opendata.swiss/ui/server/api/showcases.ts @@ -1,8 +1,8 @@ import {remark} from 'remark' import strip from 'strip-markdown' +import remarkFrontmatter from "remark-frontmatter"; import {dcat, dcterms, rdfs, schema} from "@tpluscode/rdf-ns-builders"; -const frontMatterPattern = /^---[\s\S]*---/ const stemPattern = /^showcases\/(?.*)\.(?\w\w)$/ interface AggregateShowcase { @@ -83,6 +83,9 @@ export default defineEventHandler(async (event) => { }) async function stripMarkdown(md: string) { - const stripped = await remark().use(strip).process(md.replace(frontMatterPattern, '').trim()) + const stripped = await remark() + .use(strip) + .use(remarkFrontmatter) + .process(md) return stripped.value.toString() } From cdf6100270431ecc5b4e7c868d6fec89681a1d48 Mon Sep 17 00:00:00 2001 From: Tomasz Pluskiewicz Date: Tue, 16 Sep 2025 15:23:35 +0200 Subject: [PATCH 8/9] fix: duplicate mapping in context --- opendata.swiss/ui/server/api/showcases.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/opendata.swiss/ui/server/api/showcases.ts b/opendata.swiss/ui/server/api/showcases.ts index 93534aa..9a6ea8e 100644 --- a/opendata.swiss/ui/server/api/showcases.ts +++ b/opendata.swiss/ui/server/api/showcases.ts @@ -10,7 +10,7 @@ interface AggregateShowcase { '@type': 'Showcase' title: Record image: string | undefined - description: Record + abstract: Record categories: string[] datasets: Array<{ id: string; label: string }> text: Record @@ -33,8 +33,8 @@ const ldContext = { '@id': dcterms.title.value, '@container': '@language', }, - description: { - '@id': dcterms.description.value, + abstract: { + '@id': dcterms.abstract.value, '@container': '@language', }, text: { @@ -60,7 +60,7 @@ export default defineEventHandler(async (event) => { '@type': 'Showcase', title: {}, image: showcase.image, - description: {}, + abstract: {}, categories: showcase.categories || [], datasets: showcase.datasets || [], text: {}, @@ -70,7 +70,7 @@ export default defineEventHandler(async (event) => { } aggregate.title[lang] = showcase.title || undefined - aggregate.description[lang] = showcase.description || undefined + aggregate.abstract[lang] = showcase.description || undefined aggregate.text[lang] = await stripMarkdown(showcase.rawbody) || undefined return arr From 58aef3e6bb5725d1829fccb4aa663b2382bf6d7d Mon Sep 17 00:00:00 2001 From: Tomasz Pluskiewicz Date: Tue, 16 Sep 2025 15:39:14 +0200 Subject: [PATCH 9/9] refactor: schema:text --- opendata.swiss/ui/server/api/showcases.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendata.swiss/ui/server/api/showcases.ts b/opendata.swiss/ui/server/api/showcases.ts index 9a6ea8e..b115dc2 100644 --- a/opendata.swiss/ui/server/api/showcases.ts +++ b/opendata.swiss/ui/server/api/showcases.ts @@ -38,7 +38,7 @@ const ldContext = { '@container': '@language', }, text: { - '@id': dcterms.description.value, + '@id': schema.text.value, '@container': '@language', }, image: schema.image.value,