diff --git a/pom.xml b/pom.xml index f2ce2342..c0f9d912 100644 --- a/pom.xml +++ b/pom.xml @@ -36,7 +36,10 @@ ${project.build.directory}/media ${project.build.directory}/mir-layout-assets 2025.06.3-SNAPSHOT + v20.19.0 10.9.2 + ${basedir}/src/main/podcast + ${project.build.directory}/podcast ${project.build.directory}/rcclient ${template.targetdir}/dbt/assets ${template.targetdir}/dbt/css @@ -283,6 +286,31 @@ run build + + podcast-deps + + npm + + process-resources + + ${podcast.projectBase} + ci + + ${project.build.directory}/npm-cache + + + + + podcast-build + + npm + + process-resources + + ${podcast.projectBase} + run build -- --outDir=${podcast.targetBase}/dist --emptyOutDir + + @@ -345,6 +373,22 @@ + + podcast-web-resources + + copy-resources + + process-resources + + ${template.targetdir}/dbt/assets/podcast + + + false + ${podcast.targetBase}/dist + + + + diff --git a/src/main/java/de/urmel_dl/dbt/migration/PodcastClassificationInitializer.java b/src/main/java/de/urmel_dl/dbt/migration/PodcastClassificationInitializer.java new file mode 100644 index 00000000..1cf18449 --- /dev/null +++ b/src/main/java/de/urmel_dl/dbt/migration/PodcastClassificationInitializer.java @@ -0,0 +1,101 @@ +/* + * This file is part of the Digitale Bibliothek Thüringen repository software. + * Copyright (c) 2000 - 2016 + * See and + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this + * program. If not, see . + */ +package de.urmel_dl.dbt.migration; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.mycore.backend.jpa.MCREntityManagerProvider; +import org.mycore.common.events.MCRStartupHandler; +import org.mycore.datamodel.classifications2.MCRCategory; +import org.mycore.datamodel.classifications2.MCRCategoryDAO; +import org.mycore.datamodel.classifications2.MCRCategoryDAOFactory; +import org.mycore.datamodel.classifications2.MCRCategoryID; +import org.mycore.datamodel.classifications2.MCRLabel; +import org.mycore.datamodel.classifications2.impl.MCRCategoryImpl; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.EntityTransaction; +import jakarta.servlet.ServletContext; + +/** + * Adds the podcast publication type to installations whose MIR genre classification + * has already been persisted and is therefore not refreshed from setup resources. + */ +public final class PodcastClassificationInitializer implements MCRStartupHandler.AutoExecutable { + + private static final Logger LOGGER = LogManager.getLogger(); + + private static final MCRCategoryID GENRES_ID = new MCRCategoryID("mir_genres"); + + private static final MCRCategoryID PODCAST_ID = new MCRCategoryID("mir_genres", "podcast"); + + private static final int PRIORITY = 998; + + @Override + public String getName() { + return PodcastClassificationInitializer.class.getSimpleName(); + } + + @Override + public int getPriority() { + return PRIORITY; + } + + @Override + public void startUp(final ServletContext servletContext) { + MCRCategoryDAO categoryDAO = MCRCategoryDAOFactory.obtainInstance(); + EntityManager entityManager = MCREntityManagerProvider.getCurrentEntityManager(); + EntityTransaction transaction = entityManager.getTransaction(); + try { + transaction.begin(); + if (categoryDAO.exist(PODCAST_ID)) { + return; + } + + if (!categoryDAO.exist(GENRES_ID)) { + LOGGER.warn("Cannot add {} because classification {} does not exist", + PODCAST_ID, GENRES_ID); + return; + } + + categoryDAO.addCategory(GENRES_ID, createPodcastCategory()); + LOGGER.info("Added publication type {}", PODCAST_ID); + } finally { + if (transaction.isActive()) { + if (transaction.getRollbackOnly()) { + transaction.rollback(); + } else { + transaction.commit(); + } + } + } + } + + private static MCRCategory createPodcastCategory() { + MCRCategory category = new MCRCategoryImpl(); + category.setId(PODCAST_ID); + category.getLabels().add(new MCRLabel("de", "Podcast", null)); + category.getLabels().add(new MCRLabel("en", "Podcast", null)); + category.getLabels().add(new MCRLabel("it", "Podcast", null)); + category.getLabels().add(new MCRLabel("x-hosts", "standalone", null)); + category.getLabels().add(new MCRLabel("x-mapping", + "marcgt:sound diniPublType:Sound diniPublType2022:Sound schemaOrg:PodcastEpisode " + + "coarResourceTypes32:c_18cc openAIRE400resourceTypeGeneral:other_research_product", + null)); + return category; + } +} diff --git a/src/main/java/de/urmel_dl/dbt/podcast/servlets/PodcastServlet.java b/src/main/java/de/urmel_dl/dbt/podcast/servlets/PodcastServlet.java new file mode 100644 index 00000000..b4deb07b --- /dev/null +++ b/src/main/java/de/urmel_dl/dbt/podcast/servlets/PodcastServlet.java @@ -0,0 +1,37 @@ +/* + * This file is part of the Digitale Bibliothek Thüringen repository software. + * Copyright (c) 2000 - 2026 + * See and + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + */ +package de.urmel_dl.dbt.podcast.servlets; + +import java.io.Serial; + +import org.jdom2.Document; +import org.jdom2.Element; +import org.mycore.common.content.MCRJDOMContent; +import org.mycore.frontend.servlets.MCRServlet; +import org.mycore.frontend.servlets.MCRServletJob; + +/** + * Renders the MIR shell used as the mount point for the podcast Vue application. + * + *

The servlet deliberately does not interpret {@code pathInfo}. Every URL below + * {@code /podcast/} receives the same shell; client-side routing is exclusively + * handled by Vue Router.

+ */ +public class PodcastServlet extends MCRServlet { + + @Serial + private static final long serialVersionUID = 1L; + + @Override + public void doGetPost(final MCRServletJob job) throws Exception { + final Document document = new Document(new Element("podcast")); + getLayoutService().doLayout(job.getRequest(), job.getResponse(), new MCRJDOMContent(document)); + } +} diff --git a/src/main/podcast/.gitignore b/src/main/podcast/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/src/main/podcast/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/src/main/podcast/index.html b/src/main/podcast/index.html new file mode 100644 index 00000000..5ec419be --- /dev/null +++ b/src/main/podcast/index.html @@ -0,0 +1,25 @@ + + + + + Development mode + + + + + + + + + + + +
+
+
+
+
+
+
+ + diff --git a/src/main/podcast/package-lock.json b/src/main/podcast/package-lock.json new file mode 100644 index 00000000..0c436a0b --- /dev/null +++ b/src/main/podcast/package-lock.json @@ -0,0 +1,2034 @@ +{ + "name": "podcast", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "podcast", + "version": "0.0.0", + "dependencies": { + "dompurify": "^3.4.17", + "pinia": "^4.0.3", + "vue": "^3.5.41", + "vue-i18n": "^11.4.16", + "vue-router": "^5.3.0" + }, + "devDependencies": { + "@types/node": "^24.19.2", + "@vitejs/plugin-vue": "^6.0.8", + "@vue/tsconfig": "^0.9.1", + "sass": "^1.103.1", + "typescript": "~6.0.2", + "vite": "^8.2.2", + "vue-tsc": "^3.3.11" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@intlify/core-base": { + "version": "11.4.16", + "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.4.16.tgz", + "integrity": "sha512-xB8nMdMRLNx7b0zPvrHy0+N9uq9Vfi1aUxHqWgn2e2/TbjCKy8HfoFpnUXXxVP8I0p1ccd6LFSC3XsFjZ3vweA==", + "license": "MIT", + "dependencies": { + "@intlify/devtools-types": "11.4.16", + "@intlify/message-compiler": "11.4.16", + "@intlify/shared": "11.4.16" + }, + "engines": { + "node": ">= 22" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/devtools-types": { + "version": "11.4.16", + "resolved": "https://registry.npmjs.org/@intlify/devtools-types/-/devtools-types-11.4.16.tgz", + "integrity": "sha512-Bl7arICsKgwcSEQuZJun3swenN4jwSpg35lCpijFznYbW9GvkfH4wLxcI5+gz7d/EHqu66UT9CZDIdnQdgMAGg==", + "license": "MIT", + "dependencies": { + "@intlify/core-base": "11.4.16", + "@intlify/shared": "11.4.16" + }, + "engines": { + "node": ">= 22" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/message-compiler": { + "version": "11.4.16", + "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.4.16.tgz", + "integrity": "sha512-p4lDWmBAsi3ja+PuEfPRavYyvqkYn/Upxz7UpnyI9LgIIy8XawcGIDzN+k99Z+99etO3HmDXGeTxnJIJGgSZ+g==", + "license": "MIT", + "dependencies": { + "@intlify/shared": "11.4.16", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 22" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/shared": { + "version": "11.4.16", + "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.4.16.tgz", + "integrity": "sha512-xehmkbEgbbNh/V+d8r3hFup0ViaN29YTJSAfRr28C54rxjyZEO0RNQKqW7VVajj16gLdglotNjiq+sIVNtHGzw==", + "license": "MIT", + "engines": { + "node": ">= 22" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.147.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.147.0.tgz", + "integrity": "sha512-IJ3s6ltHLp45S0bh7phkX+gJO7A1Wuz2EaqpAhb8WjqDwbzMiWKHhyyT42tskaWjEYXtHtVCPpnBJVT9+dcRLg==", + "devOptional": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.6.0.tgz", + "integrity": "sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.6.0", + "@parcel/watcher-darwin-arm64": "2.6.0", + "@parcel/watcher-darwin-x64": "2.6.0", + "@parcel/watcher-freebsd-x64": "2.6.0", + "@parcel/watcher-linux-arm-glibc": "2.6.0", + "@parcel/watcher-linux-arm-musl": "2.6.0", + "@parcel/watcher-linux-arm64-glibc": "2.6.0", + "@parcel/watcher-linux-arm64-musl": "2.6.0", + "@parcel/watcher-linux-x64-glibc": "2.6.0", + "@parcel/watcher-linux-x64-musl": "2.6.0", + "@parcel/watcher-win32-arm64": "2.6.0", + "@parcel/watcher-win32-x64": "2.6.0" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.6.0.tgz", + "integrity": "sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.6.0.tgz", + "integrity": "sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.6.0.tgz", + "integrity": "sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.6.0.tgz", + "integrity": "sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.6.0.tgz", + "integrity": "sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.6.0.tgz", + "integrity": "sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.6.0.tgz", + "integrity": "sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.6.0.tgz", + "integrity": "sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.6.0.tgz", + "integrity": "sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.6.0.tgz", + "integrity": "sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.6.0.tgz", + "integrity": "sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.6.0.tgz", + "integrity": "sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.6.tgz", + "integrity": "sha512-b+jTcARdTiFLI6jB4a5XjTm0RWd6KcRfQj/I2356fxUZemiho9zQLxo0RtCuMDAyKcLo6cEltkgbQp6d1+sjjQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.6.tgz", + "integrity": "sha512-lkWU8ZJaRk9q3CIEY1Tc7vIFALp3Xw5NfGJo2hQg5oIqNgxWi1zI+IiDEK3r70BF5Dzol1tcXsnzsRc8NLhG+Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.6.tgz", + "integrity": "sha512-dgR56NYnvAszm7Ob1B2/Vn0e8bUQYZH2UjVaMMtMVOCKFSfjhfLmuA/9+O+F+ajUdG6B/bSssrKW6JJYASa8jA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.6.tgz", + "integrity": "sha512-vpVxFvUCFioJqug7OTvqptkc4yb8UX0AwfDmJpaR/0sWz+BUmqSVAf7c8JkUgnN8YLspb4a/N6NhTyMAmdyQ7Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.6.tgz", + "integrity": "sha512-h1wG6Y6K3JlRswxsI64qQJqBAy4vrLuHgRbc8CZMGSWTOFRY6ghMApM1NKzB2I0n5xV1fjkE18SuVl2QpLeNpA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.6.tgz", + "integrity": "sha512-tbCiqub0q2MVWJKgF5PoAlNWCtQydiOYSLIkd8sByqK/6MMYLJRcSXSYodqYtd0O+Fw7QaVmKKlS4oL94YRZ0w==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.6.tgz", + "integrity": "sha512-oxK9+baEBPhZG5HB4URY+uU04zJWeZlH6Tb9rB5DK4DF9XR1uXNLXt5Q5ZsugTKayNCNLhkcwz/ye74hRI98dg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.6.tgz", + "integrity": "sha512-muWCk27FVBEZtv0MsK8gnfSmgczA8KQ0uRVJbTABKhkRfQc38aUrcb7fhi3BNiyseFmgcRsoMfQsSNJ+DbZdSw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.6.tgz", + "integrity": "sha512-eWDoSfU7Co2qj3vgB3Dt4lj1mG6CoWbcJQkRMP3XJplyCMtuaq3LHvPFjS9QIPvMGWVadJC04Xiy0IdcVPtnwQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.6.tgz", + "integrity": "sha512-2bWNjRSIayvupRKxXUY2tWG9fYdoUlTqWywHRvE8Eq3GvuQ+f2HeIkve697fIt+IQs/PV8yFsdWuhp1aJ1PdnA==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.6.tgz", + "integrity": "sha512-KekI0gS0wLxe1UBSQSjenBVwou/JkcQPDzBPICGZjxUv9k3RteHDPBQaiOicZUFKRIH2wKEimGwVpnJsbPzu7w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.6.tgz", + "integrity": "sha512-TvtPnfVr+HtyGiDmPK4VWmlNm7QhNNAcK5Q9A7aOXsI8545yCyaoMaicXrFZ72JzeYjaUVk7yT243zT0jzjFKQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.6.tgz", + "integrity": "sha512-iOo0VEay2XFhaCcH0sps5XIimkSuOnNaZrf6+ZkoSOQBJPKNU48RkmJv0/lSpipexu5P+ouFgafe5IGr/DiQfg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.6.tgz", + "integrity": "sha512-y5NTmmasMS455JlOCO4ZM9krIchv3Mvm1crL1iUPGOPgEzSkves9n0SdC5Sjz6+qWDFhd8/JpfWMH8NSWNHe+A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.6.tgz", + "integrity": "sha512-np8iZSLfXlAD4kWhiyq/u0Yt8oZDtRQ8lGhQaCXo2rl37KNjeU0GjJuwr4P3oeZ++ROfofsKNBqR5LTO8aXyWQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.19.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.19.2.tgz", + "integrity": "sha512-93d49wCWJkCyN87MxLpNN6KXPCwFw7bVUg79mGUV1OS40JGWwVCr8n61DXCSqz7CV6/VfPkb7xec7uP2M+QHWQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.8.tgz", + "integrity": "sha512-0ZjgOg7oO6farnNGup7yvoM/YXZV84OZxHAwtflItNa/6zzQyVb5LNxyea3FEKEX2XlagIKzrlH7wwxkKgtiew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", + "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.28" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz", + "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz", + "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue-macros/common": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-3.1.4.tgz", + "integrity": "sha512-/5Fv+6DgIcM9ajY05ZmKBv+LMX1M9A0X+IUwDRVdt67ciw8OV9bvG2r34p3RiEadlsQybjhKPRKNXDC8Bp23cw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-sfc": "^3.5.22", + "ast-kit": "^2.1.2", + "local-pkg": "^1.1.2", + "magic-string-ast": "^1.0.2", + "unplugin-utils": "^0.3.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/vue-macros" + }, + "peerDependencies": { + "vue": "^2.7.0 || ^3.2.25" + }, + "peerDependenciesMeta": { + "vue": { + "optional": true + } + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.42.tgz", + "integrity": "sha512-2Ye1ilMtKXxl8qZUrQ5j0CdgenFp/HFQmta6rfRyfEsTG69L6Wk+tWuNoHYHMx9E8tF2Slvdg1FuwDvAXdy1LQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@vue/shared": "3.5.42", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.42.tgz", + "integrity": "sha512-qbhQZEFmycr+ni/qyuccS4sucNN7VAbDfbkvNxWOX2VfgFm90MNs3/UhRNKoPMEIVn0F8gdlYjLPvqxHwHeQOA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.42", + "@vue/shared": "3.5.42" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.42.tgz", + "integrity": "sha512-fkCAFB4okcAANGMThboWnScp/gzWjU0ZSkVnjTIiplmMDq2uq0tIB3j+xVu4rhv5rvOgBySCysudmbMd6xRRqw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@vue/compiler-core": "3.5.42", + "@vue/compiler-dom": "3.5.42", + "@vue/compiler-ssr": "3.5.42", + "@vue/shared": "3.5.42", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.19", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.42.tgz", + "integrity": "sha512-xmLk3wLkbizPAiLyomjgFFosf2ys9b5Ghb+oh/k2tnvipNz8OFrQOiTcWCzyK7MpBp9KkyGtfvgfLUivbmuGYA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.42", + "@vue/shared": "3.5.42" + } + }, + "node_modules/@vue/devtools-api": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-8.2.1.tgz", + "integrity": "sha512-6u4vXBlIBAC1wMplIZgpyPn7uh/s4Bf6F5bMzvLv+EdJ0aHs/+4B7Ygv864EStQSjRbsRzTko/kUG1A1IejQ3A==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^8.2.1" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.2.1.tgz", + "integrity": "sha512-FIGIuq3AWReEpbAHY/cRGeHDfI0qOb8OCQ3YjbEAX04uaxIDbGc9rhkbVcG7rnfHPXE3RsU5KrWOu9V/okd8AQ==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^8.2.1", + "birpc": "^2.6.1", + "hookable": "^5.5.3", + "perfect-debounce": "^2.0.0" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.2.1.tgz", + "integrity": "sha512-Fkac7lUdGReh6pVOi3AYPRGe82LQqRmAfThW7RRligOAP0ZA/Z1z9XLHDM9dv34pV2HRc79DK8uKPeG2fLnA/g==", + "license": "MIT" + }, + "node_modules/@vue/language-core": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.3.11.tgz", + "integrity": "sha512-QJmpliwAVpC/OxubIByPAhNzsQPRc8/gxlN2qnVzVfIMjMDz/9RnXRFoetjz5yEgXVXyp4LqhXq3V53PjmNzFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "@vue/compiler-dom": "^3.5.0", + "@vue/shared": "^3.5.0", + "alien-signals": "^3.2.1", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1", + "picomatch": "^4.0.4" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.42.tgz", + "integrity": "sha512-TzNNfKpb7hDxbQltwAut8VDQA5YP+BuRlxntHUuRjyKwlMvmAPbs3unhCvieijifY6vFfVBwsS7wG/C7uq+bEQ==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.42" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.42.tgz", + "integrity": "sha512-9uACtuHs7vJGkm5Bp3xu4xRDLFTIYy5DgxpToVjqGIAhAEKwQfsaLvKINhM6nFVp6bZPRFGdDqd1g52MqKsotA==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.42", + "@vue/shared": "3.5.42" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.42.tgz", + "integrity": "sha512-rsCmhiWLaRxGltLwhlCWyYkFn7WAbKRh0q17eZ1A6Dq6eqc2ACQ61IIryxz0LrsvCzHSilLA9JHovVwM8CNE2g==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.42", + "@vue/runtime-core": "3.5.42", + "@vue/shared": "3.5.42", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.42.tgz", + "integrity": "sha512-2++5dUyYS4gvo7xQXSECUDhB7TS0aOl5SeVfC5qSq1Jgfhjvegw1zqhwTIR3imZ+QYPJQw9gfcFvXGAjGZ7ajQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.42", + "@vue/runtime-dom": "3.5.42", + "@vue/shared": "3.5.42" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.42.tgz", + "integrity": "sha512-2rPxex1jQf4jvl9MOHl6YaXCPcrNqz/FstMOEh3QWY+/OME9nQTvl9WYeCwhW7AFjaR0SnngZGlp/wkR6rkI6g==", + "license": "MIT" + }, + "node_modules/@vue/tsconfig": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.9.1.tgz", + "integrity": "sha512-buvjm+9NzLCJL29KY1j1991YYJ5e6275OiK+G4jtmfIb+z4POywbdm0wXusT9adVWqe0xqg70TbI7+mRx4uU9w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": ">= 5.8", + "vue": "^3.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/alien-signals": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.2.1.tgz", + "integrity": "sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/ast-kit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-2.2.0.tgz", + "integrity": "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "pathe": "^2.0.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/ast-walker-scope": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/ast-walker-scope/-/ast-walker-scope-0.9.0.tgz", + "integrity": "sha512-IJdzo2vLiElBxKzwS36VsCue/62d6IdWjnPB2v3nuPKeWGynp6FF/CYoLa5i/3jXH/z97ZDdsXz6abpgM6w07A==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.2", + "@babel/types": "^7.29.0", + "ast-kit": "^2.2.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dompurify": { + "version": "3.4.17", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.17.tgz", + "integrity": "sha512-B/pSlYVHQ58kqISD/yDjuJ1yfzI34m1gF4lAod2bwSRhiN4K5Cl3H8Af7hqB2PmTL911tdEudnnXn5FnhRoz0w==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/exsolve": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.1.tgz", + "integrity": "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/immutable": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.9.tgz", + "integrity": "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "optional": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "devOptional": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/local-pkg": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.2.1.tgz", + "integrity": "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==", + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magic-string-ast": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/magic-string-ast/-/magic-string-ast-1.0.3.tgz", + "integrity": "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==", + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.19" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT", + "optional": true + }, + "node_modules/nostics": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/nostics/-/nostics-1.2.0.tgz", + "integrity": "sha512-FGqEfhQjrvo1lL8KFifdTQiNwwQHJxC1jtYE1Rc54qF/jxONUNL+kC9gS1krX8Q65PgrQ5fCqH/I4NhWBvdSqg==", + "license": "MIT" + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pinia": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-4.0.3.tgz", + "integrity": "sha512-XMQqpvjgG7LMqVhhFUzKLT4KEbsYbfZZ0CZU9PgdFm1O2VKBmIkykL8+SfNhOaT7sR0wT6BEgKT0YNDYWgmVRA==", + "license": "MIT", + "dependencies": { + "nostics": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "@vue/devtools-api": "^8.1.5", + "typescript": ">=5.6.0", + "vue": "^3.5.11" + }, + "peerDependenciesMeta": { + "@vue/devtools-api": { + "optional": false + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/pkg-types": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz", + "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==", + "license": "MIT", + "dependencies": { + "confbox": "^0.2.4", + "exsolve": "^1.0.8", + "pathe": "^2.0.3" + } + }, + "node_modules/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/rolldown": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.6.tgz", + "integrity": "sha512-vMM4q3aixf46GiF1Kok8jDPFsEpXgFWGjUHXNkNHNm+Y2adXAG2dbX91jkti3i0ZRsOlcmbuzAz1poObSHCmUA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.147.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.6", + "@rolldown/binding-android-arm64": "1.2.6", + "@rolldown/binding-darwin-arm64": "1.2.6", + "@rolldown/binding-darwin-x64": "1.2.6", + "@rolldown/binding-freebsd-x64": "1.2.6", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.6", + "@rolldown/binding-linux-arm64-gnu": "1.2.6", + "@rolldown/binding-linux-arm64-musl": "1.2.6", + "@rolldown/binding-linux-ppc64-gnu": "1.2.6", + "@rolldown/binding-linux-s390x-gnu": "1.2.6", + "@rolldown/binding-linux-x64-gnu": "1.2.6", + "@rolldown/binding-linux-x64-musl": "1.2.6", + "@rolldown/binding-openharmony-arm64": "1.2.6", + "@rolldown/binding-win32-arm64-msvc": "1.2.6", + "@rolldown/binding-win32-x64-msvc": "1.2.6" + } + }, + "node_modules/sass": { + "version": "1.103.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.103.1.tgz", + "integrity": "sha512-9icZURbP51S6S0QGoyaeqk9uB06GNWxsFYWfH5RgpFgqK5FA8tJcM3AdVxrZEVJ7dz+L87nG95gBKf4VuaMHGw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "chokidar": "^5.0.0", + "immutable": "^5.1.5", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=20.19.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/scule": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", + "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/unplugin": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.3.0.tgz", + "integrity": "sha512-qa66K+crbfyE6JK10GjvbJeRrOsuC/JpbnHctfyp/i4oBTxWOzJfRZyDiOk1PtErMFRu8JhsU/wPvOdBNWe5Rg==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "picomatch": "^4.0.4", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@farmfe/core": "*", + "@rspack/core": "*", + "bun-types-no-globals": "*", + "esbuild": "*", + "rolldown": "*", + "rollup": "*", + "unloader": "*", + "vite": "*", + "webpack": "*" + }, + "peerDependenciesMeta": { + "@farmfe/core": { + "optional": true + }, + "@rspack/core": { + "optional": true + }, + "bun-types-no-globals": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "rolldown": { + "optional": true + }, + "rollup": { + "optional": true + }, + "unloader": { + "optional": true + }, + "vite": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/unplugin-utils": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.2.tgz", + "integrity": "sha512-xVToRh2CTmLk2HnEG7ac4rl1MJTT3RFkpS8B++/SnB0kXvuaavD+n3m/vrzyWQOdJNSZQACnbz01pnppbwV5BA==", + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/vite": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz", + "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.26", + "rolldown": "~1.2.4", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.4.0 || ^0.5.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vscode-uri": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.2.0.tgz", + "integrity": "sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.42.tgz", + "integrity": "sha512-4RyHQTbQvOPs3MfvUO1Sg0YRrKNnA0mAVtvpd12Tg1fKDN7OHBUl1IqSn8zGJjK9nI3NkNp8cgTpVrSZC5TTcA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.42", + "@vue/compiler-sfc": "3.5.42", + "@vue/runtime-dom": "3.5.42", + "@vue/server-renderer": "3.5.42", + "@vue/shared": "3.5.42" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-i18n": { + "version": "11.4.16", + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.4.16.tgz", + "integrity": "sha512-Kv9tp6iTmaAeOwop7hkAo28N1IBsbSVpnxyE26b6gqc4Q2E9Ke3ZHHA3oqqwS6/cYLT1henZU4etTaLQxrK98Q==", + "license": "MIT", + "dependencies": { + "@intlify/core-base": "11.4.16", + "@intlify/devtools-types": "11.4.16", + "@intlify/shared": "11.4.16", + "@vue/devtools-api": "^6.5.0" + }, + "engines": { + "node": ">= 22" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/vue-i18n/node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/vue-router": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.3.0.tgz", + "integrity": "sha512-a2PBXX9yfkS58JzSJALUffgDa09lEzKmCcEnLaepoIr88L+9hEnsgEQkcadJGID+vtHa0gFpVo064zmylA9fcg==", + "license": "MIT", + "dependencies": { + "@vue-macros/common": "^3.1.3", + "@vue/devtools-api": "^8.1.5", + "ast-walker-scope": "^0.9.0", + "chokidar": "^5.0.0", + "confbox": "^0.2.4", + "local-pkg": "^1.2.1", + "magic-string": "^0.30.21", + "mlly": "^1.8.2", + "muggle-string": "^0.4.1", + "nostics": "^1.1.4", + "pathe": "^2.0.3", + "picomatch": "^4.0.5", + "scule": "^1.3.0", + "tinyglobby": "^0.2.17", + "unplugin": "^3.3.0", + "unplugin-utils": "^0.3.2" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "@pinia/colada": ">=0.21.2", + "@vue/compiler-sfc": "^3.5.34 || ^4.0.0", + "pinia": "^3.0.4 || ^4.0.2", + "vite": "^7.3.0 || ^8.0.0", + "vue": "^3.5.34 || ^4.0.0" + }, + "peerDependenciesMeta": { + "@pinia/colada": { + "optional": true + }, + "@vue/compiler-sfc": { + "optional": true + }, + "pinia": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/vue-tsc": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.3.11.tgz", + "integrity": "sha512-gOb0B9rtU2+f1dszwPqSH5kAieIF9ReeLhD3kSRNHv5WZZUQz/JdVXW0RTdqhNTMlQkqKzrTTviqKr/4FYZraQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.28", + "@vue/language-core": "3.3.11" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "license": "MIT" + } + } +} diff --git a/src/main/podcast/package.json b/src/main/podcast/package.json new file mode 100644 index 00000000..29e6307b --- /dev/null +++ b/src/main/podcast/package.json @@ -0,0 +1,26 @@ +{ + "name": "podcast", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc -b && vite build" + }, + "dependencies": { + "dompurify": "^3.4.17", + "pinia": "^4.0.3", + "vue": "^3.5.41", + "vue-i18n": "^11.4.16", + "vue-router": "^5.3.0" + }, + "devDependencies": { + "@types/node": "^24.19.2", + "@vitejs/plugin-vue": "^6.0.8", + "@vue/tsconfig": "^0.9.1", + "sass": "^1.103.1", + "typescript": "~6.0.2", + "vite": "^8.2.2", + "vue-tsc": "^3.3.11" + } +} diff --git a/src/main/podcast/public/favicon.png b/src/main/podcast/public/favicon.png new file mode 100644 index 00000000..3e1c847c Binary files /dev/null and b/src/main/podcast/public/favicon.png differ diff --git a/src/main/podcast/src/App.vue b/src/main/podcast/src/App.vue new file mode 100644 index 00000000..b7c3fb26 --- /dev/null +++ b/src/main/podcast/src/App.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/main/podcast/src/components/EmptyState.vue b/src/main/podcast/src/components/EmptyState.vue new file mode 100644 index 00000000..597fb061 --- /dev/null +++ b/src/main/podcast/src/components/EmptyState.vue @@ -0,0 +1,39 @@ + + + + + diff --git a/src/main/podcast/src/components/ErrorState.vue b/src/main/podcast/src/components/ErrorState.vue new file mode 100644 index 00000000..396c6023 --- /dev/null +++ b/src/main/podcast/src/components/ErrorState.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/src/main/podcast/src/components/LoadingState.vue b/src/main/podcast/src/components/LoadingState.vue new file mode 100644 index 00000000..a422b9c7 --- /dev/null +++ b/src/main/podcast/src/components/LoadingState.vue @@ -0,0 +1,35 @@ + + + + + diff --git a/src/main/podcast/src/components/Player.vue b/src/main/podcast/src/components/Player.vue new file mode 100644 index 00000000..bad2a3ba --- /dev/null +++ b/src/main/podcast/src/components/Player.vue @@ -0,0 +1,878 @@ + + + + + diff --git a/src/main/podcast/src/components/PlaylistEpisode.vue b/src/main/podcast/src/components/PlaylistEpisode.vue new file mode 100644 index 00000000..5a6fcc9e --- /dev/null +++ b/src/main/podcast/src/components/PlaylistEpisode.vue @@ -0,0 +1,331 @@ + + + + + diff --git a/src/main/podcast/src/components/PlaylistHeader.vue b/src/main/podcast/src/components/PlaylistHeader.vue new file mode 100644 index 00000000..bdb16c5a --- /dev/null +++ b/src/main/podcast/src/components/PlaylistHeader.vue @@ -0,0 +1,410 @@ + + + + + diff --git a/src/main/podcast/src/components/PodcastFilter.vue b/src/main/podcast/src/components/PodcastFilter.vue new file mode 100644 index 00000000..9ba3cda3 --- /dev/null +++ b/src/main/podcast/src/components/PodcastFilter.vue @@ -0,0 +1,193 @@ + + + + + diff --git a/src/main/podcast/src/components/PodcastList.vue b/src/main/podcast/src/components/PodcastList.vue new file mode 100644 index 00000000..e19fd1d9 --- /dev/null +++ b/src/main/podcast/src/components/PodcastList.vue @@ -0,0 +1,506 @@ + + + + + diff --git a/src/main/podcast/src/components/PodcastsHero.vue b/src/main/podcast/src/components/PodcastsHero.vue new file mode 100644 index 00000000..8145eab4 --- /dev/null +++ b/src/main/podcast/src/components/PodcastsHero.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/src/main/podcast/src/data/podcast-categories.json b/src/main/podcast/src/data/podcast-categories.json new file mode 100644 index 00000000..522d3f41 --- /dev/null +++ b/src/main/podcast/src/data/podcast-categories.json @@ -0,0 +1,476 @@ +[ + { + "value": "Arts", + "label": "Arts", + "subcategories": [ + { + "value": "Books", + "label": "Books" + }, + { + "value": "Design", + "label": "Design" + }, + { + "value": "Fashion_Beauty", + "label": "Fashion & Beauty" + }, + { + "value": "Food", + "label": "Food" + }, + { + "value": "Performing_Arts", + "label": "Performing Arts" + }, + { + "value": "Visual_Arts", + "label": "Visual Arts" + } + ] + }, + { + "value": "Business", + "label": "Business", + "subcategories": [ + { + "value": "Careers", + "label": "Careers" + }, + { + "value": "Entrepreneurship", + "label": "Entrepreneurship" + }, + { + "value": "Investing", + "label": "Investing" + }, + { + "value": "Management", + "label": "Management" + }, + { + "value": "Marketing", + "label": "Marketing" + }, + { + "value": "Non-Profit", + "label": "Non-Profit" + } + ] + }, + { + "value": "Comedy", + "label": "Comedy", + "subcategories": [ + { + "value": "Comedy_Interviews", + "label": "Comedy Interviews" + }, + { + "value": "Improv", + "label": "Improv" + }, + { + "value": "Stand-Up", + "label": "Stand-Up" + } + ] + }, + { + "value": "Education", + "label": "Education", + "subcategories": [ + { + "value": "Courses", + "label": "Courses" + }, + { + "value": "How_To", + "label": "How To" + }, + { + "value": "Language_Learning", + "label": "Language Learning" + }, + { + "value": "Self-Improvement", + "label": "Self-Improvement" + } + ] + }, + { + "value": "Fiction", + "label": "Fiction", + "subcategories": [ + { + "value": "Comedy_Fiction", + "label": "Comedy Fiction" + }, + { + "value": "Drama", + "label": "Drama" + }, + { + "value": "Science_Fiction", + "label": "Science Fiction" + } + ] + }, + { + "value": "Government", + "label": "Government", + "subcategories": [] + }, + { + "value": "History", + "label": "History", + "subcategories": [] + }, + { + "value": "Health_Fitness", + "label": "Health & Fitness", + "subcategories": [ + { + "value": "Alternative_Health", + "label": "Alternative Health" + }, + { + "value": "Fitness", + "label": "Fitness" + }, + { + "value": "Medicine", + "label": "Medicine" + }, + { + "value": "Mental_Health", + "label": "Mental Health" + }, + { + "value": "Nutrition", + "label": "Nutrition" + }, + { + "value": "Sexuality", + "label": "Sexuality" + } + ] + }, + { + "value": "Kids_Family", + "label": "Kids & Family", + "subcategories": [ + { + "value": "Education_for_Kids", + "label": "Education for Kids" + }, + { + "value": "Parenting", + "label": "Parenting" + }, + { + "value": "Pets_Animals", + "label": "Pets & Animals" + }, + { + "value": "Stories_for_Kids", + "label": "Stories for Kids" + } + ] + }, + { + "value": "Leisure", + "label": "Leisure", + "subcategories": [ + { + "value": "Animation_Manga", + "label": "Animation & Manga" + }, + { + "value": "Automotive", + "label": "Automotive" + }, + { + "value": "Aviation", + "label": "Aviation" + }, + { + "value": "Crafts", + "label": "Crafts" + }, + { + "value": "Games", + "label": "Games" + }, + { + "value": "Hobbies", + "label": "Hobbies" + }, + { + "value": "Home_Garden", + "label": "Home & Garden" + }, + { + "value": "Video_Games", + "label": "Video Games" + } + ] + }, + { + "value": "Music", + "label": "Music", + "subcategories": [ + { + "value": "Music_Commentary", + "label": "Music Commentary" + }, + { + "value": "Music_History", + "label": "Music History" + }, + { + "value": "Music_Interviews", + "label": "Music Interviews" + } + ] + }, + { + "value": "News", + "label": "News", + "subcategories": [ + { + "value": "Business_News", + "label": "Business News" + }, + { + "value": "Daily_News", + "label": "Daily News" + }, + { + "value": "Entertainment_News", + "label": "Entertainment News" + }, + { + "value": "News_Commentary", + "label": "News Commentary" + }, + { + "value": "Politics", + "label": "Politics" + }, + { + "value": "Sports_News", + "label": "Sports News" + }, + { + "value": "Tech_News", + "label": "Tech News" + } + ] + }, + { + "value": "Religion_Spirituality", + "label": "Religion & Spirituality", + "subcategories": [ + { + "value": "Buddhism", + "label": "Buddhism" + }, + { + "value": "Christianity", + "label": "Christianity" + }, + { + "value": "Hinduism", + "label": "Hinduism" + }, + { + "value": "Islam", + "label": "Islam" + }, + { + "value": "Judaism", + "label": "Judaism" + }, + { + "value": "Religion", + "label": "Religion" + }, + { + "value": "Spirituality", + "label": "Spirituality" + } + ] + }, + { + "value": "Science", + "label": "Science", + "subcategories": [ + { + "value": "Astronomy", + "label": "Astronomy" + }, + { + "value": "Chemistry", + "label": "Chemistry" + }, + { + "value": "Earth_Sciences", + "label": "Earth Sciences" + }, + { + "value": "Life_Sciences", + "label": "Life Sciences" + }, + { + "value": "Mathematics", + "label": "Mathematics" + }, + { + "value": "Natural_Sciences", + "label": "Natural Sciences" + }, + { + "value": "Nature", + "label": "Nature" + }, + { + "value": "Physics", + "label": "Physics" + }, + { + "value": "Social_Sciences", + "label": "Social Sciences" + } + ] + }, + { + "value": "Society_Culture", + "label": "Society & Culture", + "subcategories": [ + { + "value": "Documentary", + "label": "Documentary" + }, + { + "value": "Personal_Journals", + "label": "Personal Journals" + }, + { + "value": "Philosophy", + "label": "Philosophy" + }, + { + "value": "Places_Travel", + "label": "Places & Travel" + }, + { + "value": "Relationships", + "label": "Relationships" + } + ] + }, + { + "value": "Sports", + "label": "Sports", + "subcategories": [ + { + "value": "Baseball", + "label": "Baseball" + }, + { + "value": "Basketball", + "label": "Basketball" + }, + { + "value": "Cricket", + "label": "Cricket" + }, + { + "value": "Fantasy_Sports", + "label": "Fantasy Sports" + }, + { + "value": "Football", + "label": "Football" + }, + { + "value": "Golf", + "label": "Golf" + }, + { + "value": "Hockey", + "label": "Hockey" + }, + { + "value": "Rugby", + "label": "Rugby" + }, + { + "value": "Running", + "label": "Running" + }, + { + "value": "Soccer", + "label": "Soccer" + }, + { + "value": "Swimming", + "label": "Swimming" + }, + { + "value": "Tennis", + "label": "Tennis" + }, + { + "value": "Volleyball", + "label": "Volleyball" + }, + { + "value": "Wilderness", + "label": "Wilderness" + }, + { + "value": "Wrestling", + "label": "Wrestling" + } + ] + }, + { + "value": "Technology", + "label": "Technology", + "subcategories": [] + }, + { + "value": "True_Crime", + "label": "True Crime", + "subcategories": [] + }, + { + "value": "TV_Film", + "label": "TV & Film", + "subcategories": [ + { + "value": "After_Shows", + "label": "After Shows" + }, + { + "value": "Film_History", + "label": "Film History" + }, + { + "value": "Film_Interviews", + "label": "Film Interviews" + }, + { + "value": "Film_Reviews", + "label": "Film Reviews" + }, + { + "value": "TV_Reviews", + "label": "TV Reviews" + } + ] + } +] diff --git a/src/main/podcast/src/env.d.ts b/src/main/podcast/src/env.d.ts new file mode 100644 index 00000000..3e2beb40 --- /dev/null +++ b/src/main/podcast/src/env.d.ts @@ -0,0 +1,16 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue'; + + const component: DefineComponent<{}, {}, any>; + export default component; +} + +declare global { + interface Window { + currentLang?: string; + } +} + +export {}; diff --git a/src/main/podcast/src/i18n/index.ts b/src/main/podcast/src/i18n/index.ts new file mode 100644 index 00000000..5c04dba6 --- /dev/null +++ b/src/main/podcast/src/i18n/index.ts @@ -0,0 +1,49 @@ +// src/i18n/index.ts +import { createI18n } from 'vue-i18n'; + +import deSource from '../../../resources/config/dbt/messages_de.properties?raw'; +import enSource from '../../../resources/config/dbt/messages_en.properties?raw'; + +import { parseNamespacedProperties } from './properties.ts'; + +export type SupportedLocale = 'de' | 'en'; + +function supportedLocale(value: string | null): SupportedLocale | null { + const language = value?.toLowerCase(); + + if (language?.startsWith('en')) { + return 'en'; + } + + if (language?.startsWith('de')) { + return 'de'; + } + + return null; +} + +function getCurrentLocale(): SupportedLocale { + const queryLocale = supportedLocale(new URLSearchParams(window.location.search).get('lang')); + const configuredLocale = supportedLocale( + document.querySelector('meta[name="podcast-locale"]')?.content ?? null, + ); + + return ( + queryLocale ?? + supportedLocale(window.currentLang ?? null) ?? + configuredLocale ?? + supportedLocale(document.documentElement.lang) ?? + 'de' + ); +} + +export const i18n = createI18n({ + legacy: false, + globalInjection: true, + locale: getCurrentLocale(), + fallbackLocale: 'de', + messages: { + de: parseNamespacedProperties(deSource, 'podcast'), + en: parseNamespacedProperties(enSource, 'podcast'), + }, +}); diff --git a/src/main/podcast/src/i18n/properties.ts b/src/main/podcast/src/i18n/properties.ts new file mode 100644 index 00000000..6a3ebbdb --- /dev/null +++ b/src/main/podcast/src/i18n/properties.ts @@ -0,0 +1,163 @@ +export type Messages = Record; +export type MessageTree = { + [key: string]: string | MessageTree; +}; + +function unescapeProperty(value: string): string { + let result = ''; + + for (let i = 0; i < value.length; i++) { + if (value[i] !== '\\') { + result += value[i]; + continue; + } + + const next = value[++i]; + + switch (next) { + case 't': + result += '\t'; + break; + case 'r': + result += '\r'; + break; + case 'n': + result += '\n'; + break; + case 'f': + result += '\f'; + break; + case 'u': { + const code = value.slice(i + 1, i + 5); + + if (/^[0-9a-fA-F]{4}$/.test(code)) { + result += String.fromCharCode(parseInt(code, 16)); + i += 4; + } else { + result += 'u'; + } + + break; + } + default: + result += next ?? '\\'; + } + } + + return result; +} + +function getLogicalLines(source: string): string[] { + const result: string[] = []; + let current = ''; + + for (const physicalLine of source.split(/\r?\n/)) { + const line = current ? physicalLine.replace(/^\s+/, '') : physicalLine; + + current += line; + + const trailingBackslashes = current.match(/\\+$/)?.[0].length ?? 0; + + if (trailingBackslashes % 2 === 1) { + current = current.slice(0, -1); + } else { + result.push(current); + current = ''; + } + } + + if (current) { + result.push(current); + } + + return result; +} + +export function parseProperties(source: string): Messages { + const messages: Messages = {}; + + for (const line of getLogicalLines(source)) { + const trimmed = line.trimStart(); + + if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('!')) { + continue; + } + + let separator = -1; + let escaped = false; + + for (let i = 0; i < trimmed.length; i++) { + const character = trimmed[i]; + + if (character === '\\') { + escaped = !escaped; + continue; + } + + if (!escaped && (character === '=' || character === ':' || /\s/.test(character))) { + separator = i; + break; + } + + escaped = false; + } + + if (separator === -1) { + messages[unescapeProperty(trimmed)] = ''; + continue; + } + + const rawKey = trimmed.slice(0, separator); + let valueStart = separator; + + while (/\s/.test(trimmed[valueStart] ?? '')) { + valueStart++; + } + + if (trimmed[valueStart] === '=' || trimmed[valueStart] === ':') { + valueStart++; + } + + while (/\s/.test(trimmed[valueStart] ?? '')) { + valueStart++; + } + + messages[unescapeProperty(rawKey)] = unescapeProperty(trimmed.slice(valueStart)); + } + + return messages; +} + +export function parseNamespacedProperties(source: string, namespace: string): MessageTree { + const messages = parseProperties(source); + const result: MessageTree = {}; + const prefix = `${namespace}.`; + + for (const [key, value] of Object.entries(messages)) { + if (!key.startsWith(prefix)) { + continue; + } + + const path = key.split('.'); + let target = result; + + for (let index = 0; index < path.length - 1; index++) { + const segment = path[index]; + const existing = target[segment]; + + if (typeof existing === 'string') { + throw new Error(`Conflicting translation key: ${path.slice(0, index + 1).join('.')}`); + } + + if (!existing) { + target[segment] = {}; + } + + target = target[segment] as MessageTree; + } + + target[path[path.length - 1]] = value; + } + + return result; +} diff --git a/src/main/podcast/src/index.ts b/src/main/podcast/src/index.ts new file mode 100644 index 00000000..4079992d --- /dev/null +++ b/src/main/podcast/src/index.ts @@ -0,0 +1,35 @@ +import { createApp, type App as VueApp } from 'vue'; +import { createPinia } from 'pinia'; +import { i18n } from './i18n'; + +import App from './App.vue'; +import router from './router/router'; +import { syncLanguageLinks } from './utils/languageLinks'; + +import './style.css'; + +const PODCAST_MOUNT_SELECTOR = '#main_content > .container'; + +router.afterEach(() => { + syncLanguageLinks(); +}); + +export function mountPodcast(selector = PODCAST_MOUNT_SELECTOR): VueApp { + const container = document.querySelector(selector); + + if (!container) { + throw new Error(`Podcast mount element not found: ${selector}`); + } + + const app = createApp(App); + + app.use(createPinia()); + app.use(i18n); + app.use(router); + + app.mount(container); + + return app; +} + +mountPodcast(); diff --git a/src/main/podcast/src/router/router.ts b/src/main/podcast/src/router/router.ts new file mode 100644 index 00000000..467ea5fb --- /dev/null +++ b/src/main/podcast/src/router/router.ts @@ -0,0 +1,36 @@ +import { createRouter, createWebHistory } from 'vue-router'; +import Podcasts from '../views/Podcasts.vue'; +import Playlist from '../views/Playlist.vue'; +import NotFound from '../views/NotFound.vue'; + +const configuredBasePath = document.querySelector( + 'meta[name="podcast-base-path"]', +)?.content; + +const basePath = configuredBasePath + ? new URL(configuredBasePath, window.location.origin).pathname + : '/podcast/'; + +const router = createRouter({ + history: createWebHistory(basePath), + routes: [ + { + path: '/', + name: 'podcasts', + component: Podcasts, + }, + { + path: '/:podcastId', + name: 'podcast', + component: Playlist, + props: true, + }, + { + path: '/:pathMatch(.*)*', + name: 'not-found', + component: NotFound, + }, + ], +}); + +export default router; diff --git a/src/main/podcast/src/stores/player.ts b/src/main/podcast/src/stores/player.ts new file mode 100644 index 00000000..e6212728 --- /dev/null +++ b/src/main/podcast/src/stores/player.ts @@ -0,0 +1,140 @@ +import { defineStore } from 'pinia'; +import { computed, ref } from 'vue'; +import type { PodcastEpisode } from '../types/types'; + +type PlaybackCommand = 'play' | 'pause'; + +interface PlaybackRequest { + id: number; + command: PlaybackCommand; +} + +export const usePlayerStore = defineStore('player', () => { + const episode = ref(null); + const episodes = ref([]); + const isPlaying = ref(false); + const playbackRequest = ref({ + id: 0, + command: 'pause', + }); + const playlistTitle = ref(''); + + let playbackRequestId = 0; + + const currentTime = ref(0); + const duration = ref(0); + + const remainingTime = computed(() => Math.max(0, duration.value - currentTime.value)); + + const currentEpisodeIndex = computed(() => { + if (!episode.value) { + return -1; + } + + return episodes.value.findIndex((item) => item.guid === episode.value?.guid); + }); + + function setPlaylist(newEpisodes: PodcastEpisode[], title: string): void { + episodes.value = newEpisodes; + playlistTitle.value = title; + } + + function selectEpisode(newEpisode: PodcastEpisode): void { + episode.value = newEpisode; + requestPlay(); + } + + function requestPlay(): void { + playbackRequest.value = { + id: ++playbackRequestId, + command: 'play', + }; + } + + function requestPause(): void { + playbackRequest.value = { + id: ++playbackRequestId, + command: 'pause', + }; + } + + function previousEpisode(): void { + if (currentEpisodeIndex.value < 0 || currentEpisodeIndex.value >= episodes.value.length - 1) { + return; + } + + selectEpisode(episodes.value[currentEpisodeIndex.value + 1]); + } + + function nextEpisode(): void { + if (currentEpisodeIndex.value <= 0) { + return; + } + + selectEpisode(episodes.value[currentEpisodeIndex.value - 1]); + } + + function closePlayer(): void { + requestPause(); + isPlaying.value = false; + episode.value = null; + } + + function togglePlayback(): void { + if (isPlaying.value) { + requestPause(); + } else { + requestPlay(); + } + } + + function toggleEpisode(selectedEpisode: PodcastEpisode): void { + if (episode.value?.guid === selectedEpisode.guid) { + togglePlayback(); + } else { + selectEpisode(selectedEpisode); + } + } + + function formatTime(seconds: number): string { + if (!Number.isFinite(seconds)) { + return '0:00'; + } + + const totalSeconds = Math.floor(seconds); + + const hours = Math.floor(totalSeconds / 3600); + + const minutes = Math.floor((totalSeconds % 3600) / 60); + + const secs = totalSeconds % 60; + + if (hours > 0) { + return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; + } + + return `${minutes}:${secs.toString().padStart(2, '0')}`; + } + + return { + episode, + episodes, + isPlaying, + playbackRequest, + playlistTitle, + currentEpisodeIndex, + currentTime, + duration, + remainingTime, + + setPlaylist, + selectEpisode, + requestPause, + togglePlayback, + nextEpisode, + previousEpisode, + closePlayer, + toggleEpisode, + formatTime, + }; +}); diff --git a/src/main/podcast/src/style.css b/src/main/podcast/src/style.css new file mode 100644 index 00000000..e4f0b27a --- /dev/null +++ b/src/main/podcast/src/style.css @@ -0,0 +1,24 @@ +#main_content > .container[data-v-app] { + /* Typography */ + --podcast-font-display: clamp(1.5rem, 1.2rem + 1.2vw, 2.25rem); + --podcast-font-heading: clamp(1.25rem, 1.1rem + 0.6vw, 1.625rem); + --podcast-font-item-title: clamp(0.9375rem, 0.88rem + 0.2vw, 1.0625rem); + --podcast-font-body: clamp(0.875rem, 0.82rem + 0.2vw, 1rem); + --podcast-font-body-small: clamp(0.8125rem, 0.77rem + 0.15vw, 0.9375rem); + --podcast-font-meta: clamp(0.75rem, 0.72rem + 0.12vw, 0.875rem); + --podcast-font-caption: clamp(0.6875rem, 0.66rem + 0.1vw, 0.75rem); + --podcast-font-control: clamp(0.75rem, 0.71rem + 0.15vw, 0.875rem); + + /* Line Heights */ + --podcast-line-height-tight: 1.2; + --podcast-line-height-compact: 1.4; + --podcast-line-height-body: 1.5; + + /* Icons */ + --podcast-icon-placeholder: clamp(2rem, 1.7rem + 1.2vw, 3rem); + --podcast-icon-artwork: clamp(1rem, 0.95rem + 0.2vw, 1.25rem); + --podcast-icon-control: clamp(1rem, 0.95rem + 0.2vw, 1.25rem); + --podcast-icon-action: clamp(1.25rem, 1.15rem + 0.4vw, 1.75rem); + + min-width: 0; +} diff --git a/src/main/podcast/src/types/types.ts b/src/main/podcast/src/types/types.ts new file mode 100644 index 00000000..f58a8229 --- /dev/null +++ b/src/main/podcast/src/types/types.ts @@ -0,0 +1,123 @@ +export interface SolrResponse { + responseHeader: SolrResponseHeader; + response: SolrResult; + facet_counts: SolrFacetCounts; +} + +export interface SolrResponseHeader { + zkConnected: boolean; + status: number; + QTime: number; + params: Record; +} + +export interface SolrResult { + numFound: number; + start: number; + maxScore: number; + numFoundExact: boolean; + docs: SolrDocument[]; +} + +export interface SolrDocument { + objectKind?: string; + id: string; + returnId?: string; + objectProject?: string; + objectType?: string; + + link?: string[]; + + modified?: string; + created?: string; + modifiedby?: string; + createdby?: string; + + state?: string; + + derCount?: number; + derivates?: string[]; + + worldReadable?: boolean; + worldReadableComplete?: boolean; + + category?: string[]; + 'category.top'?: string[]; + + 'mods.title'?: string[]; + 'mods.title.main'?: string; + + 'mods.name'?: string[]; + 'mods.name.top'?: string[]; + + 'mods.author'?: string[]; + + 'mods.publisher'?: string[]; + + 'mods.genre'?: string[]; + + 'mods.abstract'?: string[]; + + 'mods.dateIssued'?: string; + 'mods.yearIssued'?: number; + 'mods.type'?: string; + + search_result_link_text?: string; + + 'mods.pindexname'?: string[]; + 'mods.pindexname.published'?: string[]; + + 'mods.nameByRole.personal.aut'?: string[]; + 'mods.nameByRole.personal.own'?: string[]; + + 'mods.abstract.result'?: string; + 'mods.abstract.result.de'?: string[]; + + 'mir.toc.title'?: string; + 'mir.toc.authors'?: string; + + _version_?: number; + score?: number; +} + +export interface SolrFacetCounts { + facet_queries: Record; + + facet_fields: { + 'mods.genre': (string | number)[]; + worldReadableComplete: (string | number)[]; + }; + + facet_ranges: Record; + facet_intervals: Record; + facet_heatmaps: Record; +} + +export interface PodcastEpisode { + title: string; + link: string; + author: string[]; + description: string; + image: string; + audioUrl: string; + audioLength: number; + audioType: string; + guid: string; + pubDate: string; + duration: string; +} + +export interface PodcastPlaylist { + title: string; + link: string; + language: string; + explicit: boolean; + author: string[]; + description: string; + ownerName: string; + ownerEmail: string; + image: string; + category: string; + subcategory: string; + episodes: PodcastEpisode[]; +} diff --git a/src/main/podcast/src/utils/XMLParser.ts b/src/main/podcast/src/utils/XMLParser.ts new file mode 100644 index 00000000..870b3503 --- /dev/null +++ b/src/main/podcast/src/utils/XMLParser.ts @@ -0,0 +1,118 @@ +import DOMPurify from 'dompurify'; + +import type { PodcastEpisode, PodcastPlaylist } from '../types/types.ts'; + +const ITUNES_NS = 'http://www.itunes.com/dtds/podcast-1.0.dtd'; + +function getDirectChildren( + element: Element, + tagName: string, + namespaceUri: string | null, +): Element[] { + return Array.from(element.children).filter( + (child) => child.localName === tagName && child.namespaceURI === namespaceUri, + ); +} + +function getDirectChild( + element: Element, + tagName: string, + namespaceUri: string | null, +): Element | undefined { + return getDirectChildren(element, tagName, namespaceUri)[0]; +} + +function getText(element: Element, tagName: string): string { + return getDirectChild(element, tagName, element.namespaceURI)?.textContent?.trim() ?? ''; +} + +function getItunesText(element: Element, tagName: string): string { + return getDirectChild(element, tagName, ITUNES_NS)?.textContent?.trim() ?? ''; +} + +function getItunesAuthors(element: Element): string[] { + return getItunesText(element, 'author') + .split(';') + .map((author) => author.trim()) + .filter(Boolean); +} + +function getSafeHtml(element: Element, tagName: string): string { + return DOMPurify.sanitize(getText(element, tagName)); +} + +function getItunesAttribute(element: Element, tagName: string, attributeName: string): string { + return getDirectChild(element, tagName, ITUNES_NS)?.getAttribute(attributeName) ?? ''; +} + +function parsePodcastXml(xmlString: string): PodcastPlaylist { + const parser = new DOMParser(); + + const xml = parser.parseFromString(xmlString, 'application/xml'); + + const parserError = xml.querySelector('parsererror'); + + if (parserError) { + throw new Error('Podcast XML konnte nicht verarbeitet werden'); + } + + const root = xml.documentElement; + const channel = getDirectChild(root, 'channel', root.namespaceURI); + + if (!channel) { + throw new Error('RSS channel wurde nicht gefunden'); + } + + const categoryElement = getDirectChild(channel, 'category', ITUNES_NS); + + const category = categoryElement?.getAttribute('text') ?? ''; + + const subcategoryElement = categoryElement + ? getDirectChild(categoryElement, 'category', ITUNES_NS) + : undefined; + + const subcategory = subcategoryElement?.getAttribute('text') ?? ''; + + const owner = getDirectChild(channel, 'owner', ITUNES_NS); + + const ownerName = owner ? getItunesText(owner, 'name') : ''; + + const ownerEmail = owner ? getItunesText(owner, 'email') : ''; + + const items = getDirectChildren(channel, 'item', channel.namespaceURI); + + const episodes: PodcastEpisode[] = items.map((item) => { + const enclosure = getDirectChild(item, 'enclosure', item.namespaceURI); + + return { + title: getText(item, 'title'), + link: getText(item, 'link'), + author: getItunesAuthors(item), + description: getSafeHtml(item, 'description'), + image: getItunesAttribute(item, 'image', 'href'), + audioUrl: enclosure?.getAttribute('url') ?? '', + audioLength: Number(enclosure?.getAttribute('length') ?? 0), + audioType: enclosure?.getAttribute('type') ?? '', + guid: getText(item, 'guid'), + pubDate: getText(item, 'pubDate'), + duration: getItunesText(item, 'duration'), + }; + }); + + return { + title: getText(channel, 'title'), + link: getText(channel, 'link'), + language: getText(channel, 'language'), + explicit: getItunesText(channel, 'explicit').toLowerCase() === 'true', + author: getItunesAuthors(channel), + description: getSafeHtml(channel, 'description'), + ownerName, + ownerEmail, + image: getItunesAttribute(channel, 'image', 'href'), + category, + subcategory, + episodes, + }; +} + +export default parsePodcastXml; diff --git a/src/main/podcast/src/utils/applicationUrl.ts b/src/main/podcast/src/utils/applicationUrl.ts new file mode 100644 index 00000000..6e5098d3 --- /dev/null +++ b/src/main/podcast/src/utils/applicationUrl.ts @@ -0,0 +1,13 @@ +const configuredApplicationBase = document.querySelector( + 'meta[name="podcast-application-base-url"]', +)?.content; + +const normalizedApplicationBase = configuredApplicationBase?.endsWith('/') + ? configuredApplicationBase + : `${configuredApplicationBase || ''}/`; + +const applicationBaseUrl = new URL(normalizedApplicationBase, window.location.origin); + +export default function applicationUrl(path: string): string { + return new URL(path.replace(/^\/+/, ''), applicationBaseUrl).toString(); +} diff --git a/src/main/podcast/src/utils/languageLinks.ts b/src/main/podcast/src/utils/languageLinks.ts new file mode 100644 index 00000000..8e41297d --- /dev/null +++ b/src/main/podcast/src/utils/languageLinks.ts @@ -0,0 +1,20 @@ +const LANGUAGE_LINK_SELECTOR = '.language-select a.language'; +const SUPPORTED_LANGUAGES = ['de', 'en'] as const; + +function languageFromLink(link: HTMLAnchorElement): string | null { + return SUPPORTED_LANGUAGES.find((language) => link.classList.contains(language)) ?? null; +} + +export function syncLanguageLinks(): void { + document.querySelectorAll(LANGUAGE_LINK_SELECTOR).forEach((link) => { + const language = languageFromLink(link); + + if (!language) { + return; + } + + const url = new URL(window.location.href); + url.searchParams.set('lang', language); + link.href = url.href; + }); +} diff --git a/src/main/podcast/src/utils/podcastCategory.ts b/src/main/podcast/src/utils/podcastCategory.ts new file mode 100644 index 00000000..e9fcff43 --- /dev/null +++ b/src/main/podcast/src/utils/podcastCategory.ts @@ -0,0 +1,10 @@ +import itunesPodcastCategories from '../data/podcast-categories.json'; + +const categories = itunesPodcastCategories.flatMap((category) => [ + category, + ...category.subcategories, +]); + +export default function podcastCategoryValue(category: string): string | undefined { + return categories.find((item) => item.value === category || item.label === category)?.value; +} diff --git a/src/main/podcast/src/utils/requestReceive.ts b/src/main/podcast/src/utils/requestReceive.ts new file mode 100644 index 00000000..5e843507 --- /dev/null +++ b/src/main/podcast/src/utils/requestReceive.ts @@ -0,0 +1,24 @@ +import applicationUrl from './applicationUrl'; + +export class ReceiveRequestError extends Error { + readonly status: number; + + constructor(status: number) { + super(`Request failed with status ${status}`); + this.name = 'ReceiveRequestError'; + this.status = status; + } +} + +export default async function requestReceive(id: string): Promise { + const url = new URL(applicationUrl(`receive/${encodeURIComponent(id)}`)); + url.searchParams.set('XSL.Style', 'podcast'); + + const response = await fetch(url); + + if (!response.ok) { + throw new ReceiveRequestError(response.status); + } + + return response.text(); +} diff --git a/src/main/podcast/src/utils/requestSolr.ts b/src/main/podcast/src/utils/requestSolr.ts new file mode 100644 index 00000000..fb661bac --- /dev/null +++ b/src/main/podcast/src/utils/requestSolr.ts @@ -0,0 +1,14 @@ +import applicationUrl from './applicationUrl'; + +export default async function requestSolr(params: URLSearchParams): Promise { + const url = new URL(applicationUrl('servlets/solr/find')); + url.search = params.toString(); + + const response = await fetch(url); + + if (!response.ok) { + throw new Error(`Request failed with status ${response.status}`); + } + + return response.text(); +} diff --git a/src/main/podcast/src/views/NotFound.vue b/src/main/podcast/src/views/NotFound.vue new file mode 100644 index 00000000..10fd8eb4 --- /dev/null +++ b/src/main/podcast/src/views/NotFound.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/src/main/podcast/src/views/Playlist.vue b/src/main/podcast/src/views/Playlist.vue new file mode 100644 index 00000000..55f1f29f --- /dev/null +++ b/src/main/podcast/src/views/Playlist.vue @@ -0,0 +1,122 @@ + + + diff --git a/src/main/podcast/src/views/Podcasts.vue b/src/main/podcast/src/views/Podcasts.vue new file mode 100644 index 00000000..64de86e9 --- /dev/null +++ b/src/main/podcast/src/views/Podcasts.vue @@ -0,0 +1,11 @@ + + + diff --git a/src/main/podcast/tsconfig.app.json b/src/main/podcast/tsconfig.app.json new file mode 100644 index 00000000..2419297b --- /dev/null +++ b/src/main/podcast/tsconfig.app.json @@ -0,0 +1,22 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "types": ["vite/client"], + "allowArbitraryExtensions": true, + "lib": ["ES2023", "DOM", "DOM.Iterable"], + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + + "include": [ + "env.d.ts", + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.vue" + ] +} \ No newline at end of file diff --git a/src/main/podcast/tsconfig.json b/src/main/podcast/tsconfig.json new file mode 100644 index 00000000..1ffef600 --- /dev/null +++ b/src/main/podcast/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/src/main/podcast/tsconfig.node.json b/src/main/podcast/tsconfig.node.json new file mode 100644 index 00000000..8455dcbc --- /dev/null +++ b/src/main/podcast/tsconfig.node.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023"], + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "module": "nodenext", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["vite.config.ts"] +} diff --git a/src/main/podcast/vite.config.ts b/src/main/podcast/vite.config.ts new file mode 100644 index 00000000..4d069b0d --- /dev/null +++ b/src/main/podcast/vite.config.ts @@ -0,0 +1,41 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import { resolve } from 'node:path' + +export default defineConfig(({ command }) => ({ + plugins: [vue()], + + server: { + proxy: { + '/mir/servlets/solr': { + target: 'http://localhost:8291', + changeOrigin: true, + }, + '/mir/api/iiif/image/v2/thumbnail': { + target: 'http://localhost:8291', + changeOrigin: true, + }, + '/mir/receive/': { + target: 'http://localhost:8291', + changeOrigin: true, + } + } + }, + + // This bundle is loaded directly by the browser, not by another bundler. + define: command === 'build' + ? { + 'process.env.NODE_ENV': JSON.stringify('production'), + } + : undefined, + + build: { + lib: { + entry: resolve(import.meta.dirname, 'src/index.ts'), + name: 'Podcast', + formats: ['es'], + fileName: () => 'podcast.js', + cssFileName: 'podcast', + }, + }, +})) diff --git a/src/main/resources/META-INF/resources/config/navigation.xml b/src/main/resources/META-INF/resources/config/navigation.xml index 51aca320..e9264d58 100644 --- a/src/main/resources/META-INF/resources/config/navigation.xml +++ b/src/main/resources/META-INF/resources/config/navigation.xml @@ -106,6 +106,15 @@ + + + + fas fa-headphones me-1 d-none d-sm-inline-block d-lg-none + + + + + diff --git a/src/main/resources/META-INF/resources/editor/editor-dbt.xed b/src/main/resources/META-INF/resources/editor/editor-dbt.xed index 8183e68a..c002496b 100644 --- a/src/main/resources/META-INF/resources/editor/editor-dbt.xed +++ b/src/main/resources/META-INF/resources/editor/editor-dbt.xed @@ -102,6 +102,12 @@ + + + + + + diff --git a/src/main/resources/META-INF/resources/scss/dbt/mir2fix/xeditor.scss b/src/main/resources/META-INF/resources/scss/dbt/mir2fix/xeditor.scss index 8b0c6613..50155e77 100644 --- a/src/main/resources/META-INF/resources/scss/dbt/mir2fix/xeditor.scss +++ b/src/main/resources/META-INF/resources/scss/dbt/mir2fix/xeditor.scss @@ -1,38 +1,41 @@ -#container-main { - form { - .expand-item { - position: inherit; - } +#main_content { + + div.container { - div.is-invalid { - color: map-get(map-get($form-validation-states, 'invalid'),'color'); - input, select { - border-color: map-get(map-get($form-validation-states, 'invalid'),'color'); + form { + .expand-item { + position: inherit; } - } - .mir-fieldset-legend { - border-color: $input-fieldset-border-color; - } + div.is-invalid { + input, select { + border-color: map-get(map-get($form-validation-states, 'invalid'),'color'); + } + } + + .mir-fieldset-legend { + border-color: $input-fieldset-border-color; + } - div.mir-fieldset-content { - & > div.form-group { - & > div.col-md-6 { - border-color: $input-fieldset-border-color; + div.mir-fieldset-content { + & > div.form-group { + & > div.col-md-6 { + border-color: $input-fieldset-border-color; + } } } - } - .search-completion { - box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.2), 0 2px 5px 0 rgba(0, 0, 0, 0.19); - } - .select2-selection { - border: 1px solid $input-border-color; - height: calc(1.5em + 0.75rem + 2px);; - padding: 0.175rem 0.75rem; - } - .select2-selection__arrow { - top: 8px; + .search-completion { + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.2), 0 2px 5px 0 rgba(0, 0, 0, 0.19); + } + .select2-selection { + border: 1px solid $input-border-color; + height: calc(1.5em + 0.75rem + 2px);; + padding: 0.175rem 0.75rem; + } + .select2-selection__arrow { + top: 8px; + } } } } diff --git a/src/main/resources/META-INF/web-fragment.xml b/src/main/resources/META-INF/web-fragment.xml index 2fa219fd..b82506e8 100644 --- a/src/main/resources/META-INF/web-fragment.xml +++ b/src/main/resources/META-INF/web-fragment.xml @@ -87,6 +87,16 @@ /rcclient/* + + PodcastServlet + de.urmel_dl.dbt.podcast.servlets.PodcastServlet + + + PodcastServlet + /podcast + /podcast/* + + OPCServlet de.urmel_dl.dbt.opc.servlets.OPCServlet diff --git a/src/main/resources/config/dbt/messages_de.properties b/src/main/resources/config/dbt/messages_de.properties index 787f2c46..4147614d 100644 --- a/src/main/resources/config/dbt/messages_de.properties +++ b/src/main/resources/config/dbt/messages_de.properties @@ -208,3 +208,178 @@ editor.oai.help = W\u00E4hlen Sie hier mir.date.issued.4 =Erschienen am: mir.error.finalLine = Es liegen keine weiteren Informationen vor. selfRegistration.step.create.info = Bevor Sie die Digitale Bibliothek Th\u00FCringen benutzen k\u00F6nnen, ben\u00F6tigen Sie eine Nutzerkennung. Sie k\u00F6nnen sich \u00FCber das unten stehende Formular selbst registrieren. Um die Registrierung abzuschlie\u00DFen, m\u00FCssen Sie einmal den Link aufrufen, der Ihnen per Mail zugesandt wird, nachdem Sie das Formular abgesandt haben. Vielen Dank! + +# Podcast +podcast.categories.After_Shows = Aftershows +podcast.categories.Alternative_Health = Alternative Medizin +podcast.categories.Animation_Manga = Animation & Manga +podcast.categories.Arts = Kunst +podcast.categories.Astronomy = Astronomie +podcast.categories.Automotive = Automobil +podcast.categories.Aviation = Luftfahrt +podcast.categories.Baseball = Baseball +podcast.categories.Basketball = Basketball +podcast.categories.Books = B\u00FCcher +podcast.categories.Buddhism = Buddhismus +podcast.categories.Business = Wirtschaft +podcast.categories.Business_News = Wirtschaftsnachrichten +podcast.categories.Careers = Karriere +podcast.categories.Chemistry = Chemie +podcast.categories.Christianity = Christentum +podcast.categories.Comedy = Comedy +podcast.categories.Comedy_Fiction = Comedy-Fiktion +podcast.categories.Comedy_Interviews = Comedy-Interviews +podcast.categories.Courses = Kurse +podcast.categories.Crafts = Handarbeiten +podcast.categories.Cricket = Cricket +podcast.categories.Daily_News = T\u00E4gliche Nachrichten +podcast.categories.Design = Design +podcast.categories.Documentary = Dokumentation +podcast.categories.Drama = Drama +podcast.categories.Earth_Sciences = Geowissenschaften +podcast.categories.Education = Bildung +podcast.categories.Education_for_Kids = Bildung f\u00FCr Kinder +podcast.categories.Entertainment_News = Unterhaltungsnachrichten +podcast.categories.Entrepreneurship = Unternehmertum +podcast.categories.Fantasy_Sports = Fantasy-Sport +podcast.categories.Fashion_Beauty = Mode & Sch\u00F6nheit +podcast.categories.Fiction = Fiktion +podcast.categories.Film_History = Filmgeschichte +podcast.categories.Film_Interviews = Filminterviews +podcast.categories.Film_Reviews = Filmkritiken +podcast.categories.Fitness = Fitness +podcast.categories.Food = Essen +podcast.categories.Football = American Football +podcast.categories.Games = Spiele +podcast.categories.Golf = Golf +podcast.categories.Government = Regierung +podcast.categories.Health_Fitness = Gesundheit & Fitness +podcast.categories.Hinduism = Hinduismus +podcast.categories.History = Geschichte +podcast.categories.Hobbies = Hobbys +podcast.categories.Hockey = Hockey +podcast.categories.Home_Garden = Haus & Garten +podcast.categories.How_To = Ratgeber +podcast.categories.Improv = Improvisation +podcast.categories.Investing = Geldanlage +podcast.categories.Islam = Islam +podcast.categories.Judaism = Judentum +podcast.categories.Kids_Family = Kinder & Familie +podcast.categories.Language_Learning = Sprachen lernen +podcast.categories.Leisure = Freizeit +podcast.categories.Life_Sciences = Biowissenschaften +podcast.categories.Management = Management +podcast.categories.Marketing = Marketing +podcast.categories.Mathematics = Mathematik +podcast.categories.Medicine = Medizin +podcast.categories.Mental_Health = Psychische Gesundheit +podcast.categories.Music = Musik +podcast.categories.Music_Commentary = Musikkommentare +podcast.categories.Music_History = Musikgeschichte +podcast.categories.Music_Interviews = Musikinterviews +podcast.categories.Natural_Sciences = Naturwissenschaften +podcast.categories.Nature = Natur +podcast.categories.News = Nachrichten +podcast.categories.News_Commentary = Nachrichtenkommentare +podcast.categories.Non-Profit = Gemeinn\u00FCtzig +podcast.categories.Nutrition = Ern\u00E4hrung +podcast.categories.Parenting = Erziehung +podcast.categories.Performing_Arts = Darstellende Kunst +podcast.categories.Personal_Journals = Pers\u00F6nliche Tageb\u00FCcher +podcast.categories.Pets_Animals = Haustiere & Tiere +podcast.categories.Philosophy = Philosophie +podcast.categories.Physics = Physik +podcast.categories.Places_Travel = Reisen & Orte +podcast.categories.Politics = Politik +podcast.categories.Relationships = Beziehungen +podcast.categories.Religion = Religion +podcast.categories.Religion_Spirituality = Religion & Spiritualit\u00E4t +podcast.categories.Rugby = Rugby +podcast.categories.Running = Laufen +podcast.categories.Science = Wissenschaft +podcast.categories.Science_Fiction = Science-Fiction +podcast.categories.Self-Improvement = Pers\u00F6nliche Entwicklung +podcast.categories.Sexuality = Sexualit\u00E4t +podcast.categories.Soccer = Fu\u00DFball +podcast.categories.Social_Sciences = Sozialwissenschaften +podcast.categories.Society_Culture = Gesellschaft & Kultur +podcast.categories.Spirituality = Spiritualit\u00E4t +podcast.categories.Sports = Sport +podcast.categories.Sports_News = Sportnachrichten +podcast.categories.Stand-Up = Stand-up +podcast.categories.Stories_for_Kids = Geschichten f\u00FCr Kinder +podcast.categories.Swimming = Schwimmen +podcast.categories.TV_Film = TV & Film +podcast.categories.TV_Reviews = TV-Kritiken +podcast.categories.Tech_News = Techniknachrichten +podcast.categories.Technology = Technologie +podcast.categories.Tennis = Tennis +podcast.categories.True_Crime = Wahre Verbrechen +podcast.categories.Video_Games = Videospiele +podcast.categories.Visual_Arts = Bildende Kunst +podcast.categories.Volleyball = Volleyball +podcast.categories.Wilderness = Outdoor +podcast.categories.Wrestling = Ringen +podcast.description.showLess = Weniger anzeigen +podcast.description.showMore = Mehr anzeigen +podcast.details.email = E-Mail +podcast.details.language = Sprache +podcast.details.owner = Besitzer +podcast.episode.empty = F\u00FCr diesen Podcast sind noch keine Folgen vorhanden. +podcast.episode.heading = Folgen +podcast.episode.latest = Neueste Folge +podcast.filter.allCategories = Alle Kategorien +podcast.filter.ariaLabel = Podcasts filtern +podcast.filter.category = Kategorie +podcast.filter.newestFirst = Neueste zuerst +podcast.filter.oldestFirst = \u00C4lteste zuerst +podcast.filter.searchLabel = Podcasts durchsuchen +podcast.filter.searchPlaceholder = Titel, Beschreibung, Herausgeber oder Autor +podcast.filter.select = Bitte w\u00E4hlen +podcast.filter.sort = Sortierung +podcast.filter.subcategory = Subkategorie +podcast.filter.submit = Suchen +podcast.hero.description = Wissen aus Th\u00FCringen zum Anh\u00F6ren: Entdecken Sie Podcast-Reihen aus Wissenschaft, Lehre und Gesellschaft. +podcast.hero.eyebrow = Digitale Bibliothek Th\u00FCringen +podcast.hero.title = Podcasts entdecken +podcast.image.coverAlt = Cover von {title} +podcast.list.emptyDescription = Keine passenden Podcasts gefunden. \u00C4ndern Sie den Suchbegriff oder w\u00E4hlen Sie eine andere Kategorie. +podcast.list.loading = Podcasts werden geladen ... +podcast.list.resultMultiple = {count} Podcasts gefunden +podcast.list.resultSingle = {count} Podcast gefunden +podcast.list.uncategorized = Ohne Kategorie +podcast.list.untitled = Ohne Titel +podcast.navigation.toHomepage = Zur Startseite +podcast.navigation.toOverview = Zur Podcast-\u00DCbersicht +podcast.notFound.description = Die von Ihnen aufgerufene Seite existiert nicht oder wurde m\u00F6glicherweise verschoben. +podcast.notFound.title = Seite nicht gefunden +podcast.pagination.ariaLabel = Podcast-Seitennavigation +podcast.pagination.next = Weiter +podcast.pagination.nextAriaLabel = N\u00E4chste Seite +podcast.pagination.previous = Zur\u00FCck +podcast.pagination.previousAriaLabel = Vorherige Seite +podcast.player.ariaLabel = Podcast-Player +podcast.player.back15 = 15 Sekunden zur\u00FCck +podcast.player.changeSpeed = Wiedergabegeschwindigkeit \u00E4ndern +podcast.player.close = Player schlie\u00DFen +podcast.player.controls = Wiedergabesteuerung +podcast.player.forward15 = 15 Sekunden vor +podcast.player.mute = Ton ausschalten +podcast.player.nextEpisode = N\u00E4chste Folge +podcast.player.pause = Pause +podcast.player.play = Abspielen +podcast.player.playEpisode = Folge abspielen +podcast.player.position = Wiedergabeposition +podcast.player.previousEpisode = Vorherige Folge +podcast.player.resume = Fortsetzen +podcast.player.status.buffering = WIRD GEPUFFERT ... +podcast.player.status.paused = PAUSIERT +podcast.player.status.playing = WIEDERGABE L\u00C4UFT +podcast.player.status.unavailable = AUDIODATEI NICHT VERF\u00D6GBAR +podcast.player.unmute = Ton einschalten +podcast.player.volume = Lautst\u00E4rke +podcast.state.empty.description = Passen Sie den Suchbegriff an. +podcast.state.empty.title = Keine Daten gefunden +podcast.state.error.description = Bitte versuchen Sie es sp\u00E4ter erneut. +podcast.state.error.title = Podcastdaten konnten nicht geladen werden +podcast.state.loading = Wird geladen ... diff --git a/src/main/resources/config/dbt/messages_en.properties b/src/main/resources/config/dbt/messages_en.properties index 73b737a3..dc2329f3 100644 --- a/src/main/resources/config/dbt/messages_en.properties +++ b/src/main/resources/config/dbt/messages_en.properties @@ -203,3 +203,178 @@ editor.oai = OAI enabling editor.oai.help = Chose who can access the document over the OAI interface. mir.date.issued.4 =Published on: mir.error.finalLine = That's all we know. + +# Podcast +podcast.categories.After_Shows = After Shows +podcast.categories.Alternative_Health = Alternative Health +podcast.categories.Animation_Manga = Animation & Manga +podcast.categories.Arts = Arts +podcast.categories.Astronomy = Astronomy +podcast.categories.Automotive = Automotive +podcast.categories.Aviation = Aviation +podcast.categories.Baseball = Baseball +podcast.categories.Basketball = Basketball +podcast.categories.Books = Books +podcast.categories.Buddhism = Buddhism +podcast.categories.Business = Business +podcast.categories.Business_News = Business News +podcast.categories.Careers = Careers +podcast.categories.Chemistry = Chemistry +podcast.categories.Christianity = Christianity +podcast.categories.Comedy = Comedy +podcast.categories.Comedy_Fiction = Comedy Fiction +podcast.categories.Comedy_Interviews = Comedy Interviews +podcast.categories.Courses = Courses +podcast.categories.Crafts = Crafts +podcast.categories.Cricket = Cricket +podcast.categories.Daily_News = Daily News +podcast.categories.Design = Design +podcast.categories.Documentary = Documentary +podcast.categories.Drama = Drama +podcast.categories.Earth_Sciences = Earth Sciences +podcast.categories.Education = Education +podcast.categories.Education_for_Kids = Education for Kids +podcast.categories.Entertainment_News = Entertainment News +podcast.categories.Entrepreneurship = Entrepreneurship +podcast.categories.Fantasy_Sports = Fantasy Sports +podcast.categories.Fashion_Beauty = Fashion & Beauty +podcast.categories.Fiction = Fiction +podcast.categories.Film_History = Film History +podcast.categories.Film_Interviews = Film Interviews +podcast.categories.Film_Reviews = Film Reviews +podcast.categories.Fitness = Fitness +podcast.categories.Food = Food +podcast.categories.Football = Football +podcast.categories.Games = Games +podcast.categories.Golf = Golf +podcast.categories.Government = Government +podcast.categories.Health_Fitness = Health & Fitness +podcast.categories.Hinduism = Hinduism +podcast.categories.History = History +podcast.categories.Hobbies = Hobbies +podcast.categories.Hockey = Hockey +podcast.categories.Home_Garden = Home & Garden +podcast.categories.How_To = How To +podcast.categories.Improv = Improv +podcast.categories.Investing = Investing +podcast.categories.Islam = Islam +podcast.categories.Judaism = Judaism +podcast.categories.Kids_Family = Kids & Family +podcast.categories.Language_Learning = Language Learning +podcast.categories.Leisure = Leisure +podcast.categories.Life_Sciences = Life Sciences +podcast.categories.Management = Management +podcast.categories.Marketing = Marketing +podcast.categories.Mathematics = Mathematics +podcast.categories.Medicine = Medicine +podcast.categories.Mental_Health = Mental Health +podcast.categories.Music = Music +podcast.categories.Music_Commentary = Music Commentary +podcast.categories.Music_History = Music History +podcast.categories.Music_Interviews = Music Interviews +podcast.categories.Natural_Sciences = Natural Sciences +podcast.categories.Nature = Nature +podcast.categories.News = News +podcast.categories.News_Commentary = News Commentary +podcast.categories.Non-Profit = Non-Profit +podcast.categories.Nutrition = Nutrition +podcast.categories.Parenting = Parenting +podcast.categories.Performing_Arts = Performing Arts +podcast.categories.Personal_Journals = Personal Journals +podcast.categories.Pets_Animals = Pets & Animals +podcast.categories.Philosophy = Philosophy +podcast.categories.Physics = Physics +podcast.categories.Places_Travel = Places & Travel +podcast.categories.Politics = Politics +podcast.categories.Relationships = Relationships +podcast.categories.Religion = Religion +podcast.categories.Religion_Spirituality = Religion & Spirituality +podcast.categories.Rugby = Rugby +podcast.categories.Running = Running +podcast.categories.Science = Science +podcast.categories.Science_Fiction = Science Fiction +podcast.categories.Self-Improvement = Self-Improvement +podcast.categories.Sexuality = Sexuality +podcast.categories.Soccer = Soccer +podcast.categories.Social_Sciences = Social Sciences +podcast.categories.Society_Culture = Society & Culture +podcast.categories.Spirituality = Spirituality +podcast.categories.Sports = Sports +podcast.categories.Sports_News = Sports News +podcast.categories.Stand-Up = Stand-Up +podcast.categories.Stories_for_Kids = Stories for Kids +podcast.categories.Swimming = Swimming +podcast.categories.TV_Film = TV & Film +podcast.categories.TV_Reviews = TV Reviews +podcast.categories.Tech_News = Tech News +podcast.categories.Technology = Technology +podcast.categories.Tennis = Tennis +podcast.categories.True_Crime = True Crime +podcast.categories.Video_Games = Video Games +podcast.categories.Visual_Arts = Visual Arts +podcast.categories.Volleyball = Volleyball +podcast.categories.Wilderness = Wilderness +podcast.categories.Wrestling = Wrestling +podcast.description.showLess = Show less +podcast.description.showMore = Show more +podcast.details.email = Email +podcast.details.language = Language +podcast.details.owner = Owner +podcast.episode.empty = This podcast does not have any episodes yet. +podcast.episode.heading = Episodes +podcast.episode.latest = Latest episode +podcast.filter.allCategories = All categories +podcast.filter.ariaLabel = Filter podcasts +podcast.filter.category = Category +podcast.filter.newestFirst = Newest first +podcast.filter.oldestFirst = Oldest first +podcast.filter.searchLabel = Search podcasts +podcast.filter.searchPlaceholder = Title, description, publisher, or author +podcast.filter.select = Please select +podcast.filter.sort = Sort order +podcast.filter.subcategory = Subcategory +podcast.filter.submit = Search +podcast.hero.description = Knowledge from Thuringia to listen to: discover podcast series from research, teaching, and society. +podcast.hero.eyebrow = Digital Library of Thuringia +podcast.hero.title = Discover podcasts +podcast.image.coverAlt = Cover of {title} +podcast.list.emptyDescription = No matching podcasts found. Change the search term or select a different category. +podcast.list.loading = Loading podcasts ... +podcast.list.resultMultiple = {count} podcasts found +podcast.list.resultSingle = {count} podcast found +podcast.list.uncategorized = Uncategorized +podcast.list.untitled = Untitled +podcast.navigation.toHomepage = Go to homepage +podcast.navigation.toOverview = Go to podcast overview +podcast.notFound.description = The page you requested does not exist or may have been moved. +podcast.notFound.title = Page not found +podcast.pagination.ariaLabel = Podcast pagination +podcast.pagination.next = Next +podcast.pagination.nextAriaLabel = Next page +podcast.pagination.previous = Previous +podcast.pagination.previousAriaLabel = Previous page +podcast.player.ariaLabel = Podcast player +podcast.player.back15 = Go back 15 seconds +podcast.player.changeSpeed = Change playback speed +podcast.player.close = Close player +podcast.player.controls = Playback controls +podcast.player.forward15 = Go forward 15 seconds +podcast.player.mute = Mute +podcast.player.nextEpisode = Next episode +podcast.player.pause = Pause +podcast.player.play = Play +podcast.player.playEpisode = Play episode +podcast.player.position = Playback position +podcast.player.previousEpisode = Previous episode +podcast.player.resume = Resume +podcast.player.status.buffering = BUFFERING ... +podcast.player.status.paused = PAUSED +podcast.player.status.playing = PLAYING +podcast.player.status.unavailable = AUDIO FILE UNAVAILABLE +podcast.player.unmute = Unmute +podcast.player.volume = Volume +podcast.state.empty.description = Adjust the search term. +podcast.state.empty.title = No data found +podcast.state.error.description = Please try again later. +podcast.state.error.title = Podcast data could not be loaded +podcast.state.loading = Loading ... diff --git a/src/main/resources/config/dbt/mycore.properties b/src/main/resources/config/dbt/mycore.properties index b8ca20f9..45d30a45 100644 --- a/src/main/resources/config/dbt/mycore.properties +++ b/src/main/resources/config/dbt/mycore.properties @@ -266,7 +266,7 @@ MCR.URIResolver.ModuleResolver.enum=de.urmel_dl.dbt.resolver.EnumResolver # Configuration for Mailer Queue ###################################################################### -MCR.Startup.Class=%MCR.Startup.Class%,de.urmel_dl.dbt.migration.MailJobRenamer +MCR.Startup.Class=%MCR.Startup.Class%,de.urmel_dl.dbt.migration.MailJobRenamer,de.urmel_dl.dbt.migration.PodcastClassificationInitializer MCR.QueuedJob.JobThreads=1 ###################################################################### @@ -293,6 +293,9 @@ MCR.ContentTransformer.slots.Stylesheet=xsl/rc/slots.xsl,%MCR.LayoutTransformerF MCR.ContentTransformer.entry-file.Stylesheet=xsl/rc/entry-file.xsl,%MCR.LayoutTransformerFactory.Default.Stylesheets% MCR.ContentTransformer.mycoreobject-slot.Stylesheet=xsl/rc/slot.xsl MCR.ContentTransformer.mail-queue.Stylesheet=xsl/rc/mail-queue.xsl,%MCR.LayoutTransformerFactory.Default.Stylesheets% +MCR.ContentTransformer.podcast.Stylesheet=xsl/podcast.xsl,%MCR.LayoutTransformerFactory.Default.Stylesheets% +MCR.ContentTransformer.mycoreobject-podcast.Owner.Name=DB Thüringen +MCR.ContentTransformer.mycoreobject-podcast.Owner.EMail=podcast@db-thueringen.de MCR.LayoutTransformerFactory.Default.Ignore=%MCR.LayoutTransformerFactory.Default.Ignore%,rc/classificationBrowserData-locationSubselect diff --git a/src/main/resources/setup/classifications/mir_genres.xml b/src/main/resources/setup/classifications/mir_genres.xml new file mode 100644 index 00000000..cc03ab90 --- /dev/null +++ b/src/main/resources/setup/classifications/mir_genres.xml @@ -0,0 +1,329 @@ + + + diff --git a/src/main/resources/setup/setup-commands.txt b/src/main/resources/setup/setup-commands.txt index 9219b754..636b8cdf 100644 --- a/src/main/resources/setup/setup-commands.txt +++ b/src/main/resources/setup/setup-commands.txt @@ -1,6 +1,7 @@ update classification from file classifications/mcr_roles.xml +update classification from file classifications/mir_genres.xml update classification from file classifications/mir_institutes.xml update classification from file classifications/RCLOC.xml process acl/defaultrules-commands.txt -process acl/webacl-commands.txt \ No newline at end of file +process acl/webacl-commands.txt diff --git a/src/main/resources/xsl/layout/dbt-layout.xsl b/src/main/resources/xsl/layout/dbt-layout.xsl index ea8c7c8e..255fcdd5 100644 --- a/src/main/resources/xsl/layout/dbt-layout.xsl +++ b/src/main/resources/xsl/layout/dbt-layout.xsl @@ -340,6 +340,7 @@ +