From 59380f20a659f38b2ed8b42e5517b4d333ce912e Mon Sep 17 00:00:00 2001 From: trendschau Date: Sun, 16 Mar 2025 23:03:30 +0100 Subject: [PATCH] Cleaned up kixote branch, removed secrets --- .dockerignore | 21 + .gitignore | 25 + .htaccess | 74 + Dockerfile | 41 + cache/cyanine-custom.css | 205 + composer.json | 44 + composer.lock | 2362 ++ content/.yaml | 8 + .../00-create-your-first-page.md | 15 + .../00-create-your-first-page.yaml | 12 + .../00-getting-started/01-edit-your-page.md | 14 + .../00-getting-started/01-edit-your-page.yaml | 9 + .../02-edit-the-page-meta.md | 13 + .../02-edit-the-page-meta.yaml | 9 + .../03-publish-your-page.md | 17 + .../03-publish-your-page.yaml | 9 + content/00-getting-started/index.md | 8 + content/00-getting-started/index.yaml | 13 + content/01-publish-status/00-unpublished.txt | 1 + content/01-publish-status/00-unpublished.yaml | 6 + content/01-publish-status/01-modified.md | 4 + content/01-publish-status/01-modified.txt | 1 + content/01-publish-status/01-modified.yaml | 9 + content/01-publish-status/02-published.md | 4 + content/01-publish-status/02-published.yaml | 9 + content/01-publish-status/03-hidden.md | 6 + content/01-publish-status/03-hidden.yaml | 11 + content/01-publish-status/04-noindex.md | 4 + content/01-publish-status/04-noindex.yaml | 11 + content/01-publish-status/05-restricted.md | 11 + content/01-publish-status/05-restricted.yaml | 12 + content/01-publish-status/06-redirect-301.md | 4 + .../01-publish-status/06-redirect-301.yaml | 13 + content/01-publish-status/07-redirect-302.md | 4 + .../01-publish-status/07-redirect-302.yaml | 13 + content/01-publish-status/08-copy.md | 4 + content/01-publish-status/08-copy.yaml | 13 + content/01-publish-status/09-link.md | 4 + content/01-publish-status/09-link.yaml | 13 + content/01-publish-status/index.md | 14 + content/01-publish-status/index.yaml | 12 + content/02-news/202405171834-fast-websites.md | 22 + .../02-news/202405171834-fast-websites.yaml | 12 + .../202405171843-reports-and-handbooks.md | 33 + .../202405171843-reports-and-handbooks.yaml | 12 + ...202405171855-documentations-and-manuals.md | 18 + ...2405171855-documentations-and-manuals.yaml | 12 + content/02-news/index.md | 4 + content/02-news/index.yaml | 12 + content/index.md | 6 + cypress.json | 3 + .../fixtures/01_setup/default-settings.yaml | 3 + .../settings/settings.yaml | 51 + .../settings/users/trendschau.yaml | 9 + .../01_setup/01-system-setup-signup.spec.js | 165 + .../01_setup/02-initial-frontend.spec.js | 155 + .../integration/03-system-settings.spec.js | 97 + cypress/integration/04-theme-settings.spec.js | 141 + .../05_visual-editor/05-blox-editor.spec.js | 259 + cypress/integration/99-login.spec.js | 67 + cypress/plugins/index.js | 48 + cypress/support/commands.js | 41 + cypress/support/index.js | 20 + data/security/securitylog.txt | 10 + docker-utils/init-server | 5 + docker-utils/install-composer | 17 + index.php | 5 + licence.md | 7 + media/files/filerestrictions.yaml | 4 + media/files/markdown.png | Bin 0 -> 1076 bytes media/live/youtube-6i2-uv88gke.jpeg | Bin 0 -> 9976 bytes media/live/youtube-7yvlwxjl9dc-1.jpg | Bin 0 -> 11230 bytes media/live/youtube-7yvlwxjl9dc-2.jpg | Bin 0 -> 11230 bytes media/live/youtube-7yvlwxjl9dc.jpeg | Bin 0 -> 10368 bytes media/live/youtube-7yvlwxjl9dc.jpg | Bin 0 -> 11230 bytes media/live/youtube-uw-m-4g1kaa.jpeg | Bin 0 -> 10482 bytes media/original/youtube-6i2-uv88gke.jpeg | Bin 0 -> 29956 bytes media/original/youtube-7yvlwxjl9dc-1.jpg | Bin 0 -> 25738 bytes media/original/youtube-7yvlwxjl9dc-2.jpg | Bin 0 -> 25738 bytes media/original/youtube-7yvlwxjl9dc.jpeg | Bin 0 -> 25738 bytes media/original/youtube-7yvlwxjl9dc.jpg | Bin 0 -> 25738 bytes media/original/youtube-uw-m-4g1kaa.jpeg | Bin 0 -> 33264 bytes media/thumbs/youtube-6i2-uv88gke.jpeg | Bin 0 -> 3541 bytes media/thumbs/youtube-7yvlwxjl9dc-1.jpg | Bin 0 -> 3780 bytes media/thumbs/youtube-7yvlwxjl9dc-2.jpg | Bin 0 -> 3780 bytes media/thumbs/youtube-7yvlwxjl9dc.jpeg | Bin 0 -> 3532 bytes media/thumbs/youtube-7yvlwxjl9dc.jpg | Bin 0 -> 3780 bytes media/thumbs/youtube-uw-m-4g1kaa.jpeg | Bin 0 -> 3637 bytes package-lock.json | 1542 ++ package.json | 24 + plugins/demo/DemoController.php | 17 + plugins/demo/Text.php | 12 + plugins/demo/css/demo.css | 3 + plugins/demo/demo.php | 400 + plugins/demo/demo.twig | 1 + plugins/demo/demo.yaml | 186 + plugins/demo/js/editordemo.js | 48 + plugins/demo/js/systemdemo.js | 112 + plugins/demo/templates/demo.twig | 1 + plugins/search | 1 + readme.md | 89 + settings/public_key.pem | 9 + system/autoload.php | 7 + system/typemill/Assets.php | 293 + system/typemill/Controllers/Controller.php | 253 + .../ControllerApiAuthorArticle.php | 1165 + .../Controllers/ControllerApiAuthorBlock.php | 485 + .../Controllers/ControllerApiAuthorMeta.php | 369 + .../ControllerApiAuthorShortcode.php | 31 + .../Controllers/ControllerApiFile.php | 468 + .../Controllers/ControllerApiGlobals.php | 411 + .../Controllers/ControllerApiImage.php | 410 + .../Controllers/ControllerApiKixote.php | 377 + .../ControllerApiSystemExtensions.php | 104 + .../ControllerApiSystemLicense.php | 84 + .../ControllerApiSystemPlugins.php | 67 + .../ControllerApiSystemSettings.php | 88 + .../Controllers/ControllerApiSystemThemes.php | 205 + .../Controllers/ControllerApiSystemUsers.php | 420 + .../ControllerApiSystemVersions.php | 163 + .../Controllers/ControllerApiTestmail.php | 61 + .../Controllers/ControllerWebAuth.php | 542 + .../Controllers/ControllerWebAuthor.php | 149 + .../Controllers/ControllerWebDownload.php | 163 + .../Controllers/ControllerWebFrontend.php | 443 + .../Controllers/ControllerWebRecover.php | 334 + .../Controllers/ControllerWebSetup.php | 116 + .../Controllers/ControllerWebSystem.php | 460 + system/typemill/Events/BaseEvent.php | 27 + system/typemill/Events/OnBreadcrumbLoaded.php | 14 + system/typemill/Events/OnCacheUpdated.php | 14 + .../typemill/Events/OnContentArrayLoaded.php | 14 + system/typemill/Events/OnCspLoaded.php | 14 + system/typemill/Events/OnHtmlLoaded.php | 14 + system/typemill/Events/OnItemLoaded.php | 14 + system/typemill/Events/OnMarkdownLoaded.php | 14 + .../Events/OnMetaDefinitionsLoaded.php | 14 + system/typemill/Events/OnMetaLoaded.php | 14 + system/typemill/Events/OnOriginalLoaded.php | 35 + system/typemill/Events/OnPageCreated.php | 14 + system/typemill/Events/OnPageDeleted.php | 14 + system/typemill/Events/OnPageDiscard.php | 14 + system/typemill/Events/OnPagePublished.php | 14 + system/typemill/Events/OnPageReady.php | 14 + system/typemill/Events/OnPageRenamed.php | 14 + system/typemill/Events/OnPageSorted.php | 14 + system/typemill/Events/OnPageUnpublished.php | 14 + system/typemill/Events/OnPageUpdated.php | 14 + system/typemill/Events/OnPagetreeLoaded.php | 14 + system/typemill/Events/OnPluginsLoaded.php | 14 + system/typemill/Events/OnResourcesLoaded.php | 14 + .../typemill/Events/OnRestrictionsLoaded.php | 14 + .../Events/OnRolesPermissionsLoaded.php | 14 + .../Events/OnSessionSegmentsLoaded.php | 14 + system/typemill/Events/OnSettingsLoaded.php | 14 + system/typemill/Events/OnShortcodeFound.php | 25 + system/typemill/Events/OnSystemnaviLoaded.php | 14 + .../typemill/Events/OnTwigGlobalsLoaded.php | 14 + system/typemill/Events/OnTwigLoaded.php | 14 + system/typemill/Events/OnUserConfirmed.php | 14 + system/typemill/Events/OnUserDeleted.php | 14 + system/typemill/Events/OnUserfieldsLoaded.php | 14 + system/typemill/Extensions/MediaExtension.php | 177 + .../Extensions/ParsedownExtension.php | 1358 ++ .../Extensions/TwigCaptchaExtension.php | 56 + .../typemill/Extensions/TwigCsrfExtension.php | 31 + .../Extensions/TwigLanguageExtension.php | 62 + .../Extensions/TwigMarkdownExtension.php | 41 + .../typemill/Extensions/TwigMetaExtension.php | 38 + .../Extensions/TwigPagelistExtension.php | 35 + .../typemill/Extensions/TwigUrlExtension.php | 39 + .../typemill/Extensions/TwigUserExtension.php | 110 + .../typemill/Middleware/ApiAuthentication.php | 162 + .../typemill/Middleware/ApiAuthorization.php | 50 + .../typemill/Middleware/AssetMiddleware.php | 42 + .../Middleware/CorsHeadersMiddleware.php | 63 + .../Middleware/CspHeadersMiddleware.php | 84 + .../Middleware/CustomHeadersMiddleware.php | 47 + system/typemill/Middleware/FlashMessages.php | 34 + system/typemill/Middleware/JsonBodyParser.php | 27 + .../Middleware/OldInputMiddleware.php | 37 + .../RemoveCredentialsMiddleware.php | 28 + .../Middleware/SecurityMiddleware.php | 144 + .../typemill/Middleware/SessionMiddleware.php | 62 + .../Middleware/ValidationErrorsMiddleware.php | 39 + .../typemill/Middleware/WebAuthorization.php | 43 + .../Middleware/WebRedirectIfAuthenticated.php | 45 + .../WebRedirectIfUnauthenticated.php | 56 + system/typemill/Models/ApiCalls.php | 106 + system/typemill/Models/Content.php | 384 + system/typemill/Models/Extension.php | 207 + system/typemill/Models/Field.php | 297 + system/typemill/Models/Fields.php | 138 + system/typemill/Models/Folder.php | 294 + system/typemill/Models/License.php | 587 + system/typemill/Models/Media.php | 678 + system/typemill/Models/Meta.php | 273 + system/typemill/Models/Navigation.php | 924 + system/typemill/Models/Settings.php | 386 + system/typemill/Models/SimpleMail.php | 66 + system/typemill/Models/Sitemap.php | 64 + system/typemill/Models/Storage.php | 1014 + system/typemill/Models/StorageWrapper.php | 28 + system/typemill/Models/SvgSanitizer.php | 116 + system/typemill/Models/User.php | 452 + system/typemill/Models/Validation.php | 1011 + system/typemill/Plugin.php | 385 + system/typemill/Static/Helpers.php | 114 + system/typemill/Static/Permissions.php | 68 + system/typemill/Static/Plugins.php | 129 + system/typemill/Static/Session.php | 72 + system/typemill/Static/Slug.php | 45 + system/typemill/Static/Translations.php | 108 + system/typemill/Static/Urlinfo.php | 159 + system/typemill/author/404.twig | 1 + system/typemill/author/auth/authcode.twig | 109 + system/typemill/author/auth/login.twig | 89 + system/typemill/author/auth/recover.twig | 68 + system/typemill/author/auth/recoverconf.twig | 32 + system/typemill/author/auth/reset.twig | 75 + system/typemill/author/auth/setup.twig | 93 + .../typemill/author/content/blox-editor.twig | 66 + .../typemill/author/content/raw-editor.twig | 41 + system/typemill/author/css/a11y-dark.min.css | 7 + system/typemill/author/css/custom.css | 703 + system/typemill/author/css/input.css | 3 + system/typemill/author/css/output.css | 2524 +++ system/typemill/author/js/autosize.min.js | 6 + system/typemill/author/js/axios.min.js | 2 + system/typemill/author/js/highlight.min.js | 709 + system/typemill/author/js/sortable.min.js | 2 + system/typemill/author/js/typemillutils.js | 118 + .../typemill/author/js/vue-blox-components.js | 3249 +++ system/typemill/author/js/vue-blox-config.js | 143 + system/typemill/author/js/vue-blox.js | 561 + system/typemill/author/js/vue-contentnavi.js | 489 + system/typemill/author/js/vue-eventbus.js | 31 + system/typemill/author/js/vue-forms.js | 1177 + system/typemill/author/js/vue-kixote.js | 1531 ++ system/typemill/author/js/vue-license.js | 165 + system/typemill/author/js/vue-medialib.js | 618 + system/typemill/author/js/vue-meta.js | 312 + system/typemill/author/js/vue-plugins.js | 250 + system/typemill/author/js/vue-posts.js | 145 + system/typemill/author/js/vue-publisher.js | 453 + system/typemill/author/js/vue-raw.js | 187 + system/typemill/author/js/vue-shared.js | 141 + system/typemill/author/js/vue-system.js | 153 + system/typemill/author/js/vue-systemnavi.js | 37 + system/typemill/author/js/vue-themes.js | 456 + system/typemill/author/js/vue-user.js | 149 + system/typemill/author/js/vue-usernew.js | 128 + system/typemill/author/js/vue-users.js | 295 + system/typemill/author/js/vue.global.prod.js | 9 + system/typemill/author/js/vue.js | 18096 ++++++++++++++++ .../author/js/vuedraggable.umd.min.js | 2 + .../typemill/author/layouts/layoutAuth.twig | 43 + .../author/layouts/layoutContent.twig | 86 + .../typemill/author/layouts/layoutSystem.twig | 80 + .../author/layouts/layoutSystemBlank.twig | 88 + .../typemill/author/partials/contentNavi.twig | 13 + system/typemill/author/partials/fields.twig | 103 + system/typemill/author/partials/flash.twig | 15 + system/typemill/author/partials/form.twig | 62 + system/typemill/author/partials/mainNavi.twig | 29 + system/typemill/author/partials/symbols.twig | 171 + .../typemill/author/partials/systemNavi.twig | 3 + system/typemill/author/system/account.twig | 21 + system/typemill/author/system/license.twig | 20 + system/typemill/author/system/plugins.twig | 28 + system/typemill/author/system/system.twig | 22 + system/typemill/author/system/themes.twig | 27 + system/typemill/author/system/user.twig | 21 + system/typemill/author/system/usernew.twig | 21 + system/typemill/author/system/users.twig | 21 + system/typemill/author/translations/de.yaml | 481 + system/typemill/author/translations/en.yaml | 481 + system/typemill/author/translations/fr.yaml | 481 + system/typemill/author/translations/it.yaml | 479 + system/typemill/author/translations/nl.yaml | 481 + system/typemill/author/translations/ru.yaml | 621 + system/typemill/routes/api.php | 139 + system/typemill/routes/setup.php | 10 + system/typemill/routes/web.php | 101 + system/typemill/settings/defaults.yaml | 31 + system/typemill/settings/kixote.yaml | 41 + system/typemill/settings/license.yaml | 22 + system/typemill/settings/mainnavi.yaml | 30 + system/typemill/settings/metatabs.yaml | 124 + system/typemill/settings/permissions.yaml | 50 + system/typemill/settings/resources.yaml | 5 + system/typemill/settings/system.yaml | 343 + system/typemill/settings/systemnavi.yaml | 36 + system/typemill/settings/user.yaml | 45 + system/typemill/system.php | 459 + tailwind.config.js | 25 + themes/cyanine/404.twig | 35 + themes/cyanine/blog.twig | 112 + themes/cyanine/css/style.css | 636 + themes/cyanine/css/tachyons.min.css | 2 + themes/cyanine/cyanine-thumb.png | Bin 0 -> 74931 bytes themes/cyanine/cyanine.png | Bin 0 -> 74931 bytes themes/cyanine/cyanine.yaml | 919 + themes/cyanine/home.twig | 19 + themes/cyanine/home/landingpageContrast.twig | 11 + themes/cyanine/home/landingpageInfo.twig | 40 + themes/cyanine/home/landingpageIntro.twig | 58 + themes/cyanine/home/landingpageNavi.twig | 12 + themes/cyanine/home/landingpageNews.twig | 66 + themes/cyanine/home/landingpageTeaser.twig | 27 + themes/cyanine/index.twig | 25 + themes/cyanine/js/script.js | 7 + themes/cyanine/landingpage.twig | 78 + themes/cyanine/languages/admin/en.yaml | 78 + themes/cyanine/languages/admin/fr.yaml | 84 + themes/cyanine/languages/admin/it.yaml | 78 + themes/cyanine/languages/user/en.yaml | 7 + themes/cyanine/languages/user/it.yaml | 7 + themes/cyanine/layout.twig | 251 + themes/cyanine/page.twig | 149 + themes/cyanine/partials/breadcrumb.twig | 23 + themes/cyanine/partials/footer.twig | 43 + themes/cyanine/partials/navigation.twig | 39 + themes/cyanine/partials/navigationFlat.twig | 27 + .../cyanine/partials/navigationGlossary.twig | 21 + themes/cyanine/partials/posts.twig | 51 + typemill.png | Bin 0 -> 99417 bytes 327 files changed, 67658 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 .htaccess create mode 100644 Dockerfile create mode 100644 cache/cyanine-custom.css create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 content/.yaml create mode 100644 content/00-getting-started/00-create-your-first-page.md create mode 100644 content/00-getting-started/00-create-your-first-page.yaml create mode 100644 content/00-getting-started/01-edit-your-page.md create mode 100644 content/00-getting-started/01-edit-your-page.yaml create mode 100644 content/00-getting-started/02-edit-the-page-meta.md create mode 100644 content/00-getting-started/02-edit-the-page-meta.yaml create mode 100644 content/00-getting-started/03-publish-your-page.md create mode 100644 content/00-getting-started/03-publish-your-page.yaml create mode 100644 content/00-getting-started/index.md create mode 100644 content/00-getting-started/index.yaml create mode 100644 content/01-publish-status/00-unpublished.txt create mode 100644 content/01-publish-status/00-unpublished.yaml create mode 100644 content/01-publish-status/01-modified.md create mode 100644 content/01-publish-status/01-modified.txt create mode 100644 content/01-publish-status/01-modified.yaml create mode 100644 content/01-publish-status/02-published.md create mode 100644 content/01-publish-status/02-published.yaml create mode 100644 content/01-publish-status/03-hidden.md create mode 100644 content/01-publish-status/03-hidden.yaml create mode 100644 content/01-publish-status/04-noindex.md create mode 100644 content/01-publish-status/04-noindex.yaml create mode 100644 content/01-publish-status/05-restricted.md create mode 100644 content/01-publish-status/05-restricted.yaml create mode 100644 content/01-publish-status/06-redirect-301.md create mode 100644 content/01-publish-status/06-redirect-301.yaml create mode 100644 content/01-publish-status/07-redirect-302.md create mode 100644 content/01-publish-status/07-redirect-302.yaml create mode 100644 content/01-publish-status/08-copy.md create mode 100644 content/01-publish-status/08-copy.yaml create mode 100644 content/01-publish-status/09-link.md create mode 100644 content/01-publish-status/09-link.yaml create mode 100644 content/01-publish-status/index.md create mode 100644 content/01-publish-status/index.yaml create mode 100644 content/02-news/202405171834-fast-websites.md create mode 100644 content/02-news/202405171834-fast-websites.yaml create mode 100644 content/02-news/202405171843-reports-and-handbooks.md create mode 100644 content/02-news/202405171843-reports-and-handbooks.yaml create mode 100644 content/02-news/202405171855-documentations-and-manuals.md create mode 100644 content/02-news/202405171855-documentations-and-manuals.yaml create mode 100644 content/02-news/index.md create mode 100644 content/02-news/index.yaml create mode 100644 content/index.md create mode 100644 cypress.json create mode 100644 cypress/fixtures/01_setup/default-settings.yaml create mode 100644 cypress/fixtures/01_setup/prepulate_settings_seed/settings/settings.yaml create mode 100644 cypress/fixtures/01_setup/prepulate_settings_seed/settings/users/trendschau.yaml create mode 100644 cypress/integration/01_setup/01-system-setup-signup.spec.js create mode 100644 cypress/integration/01_setup/02-initial-frontend.spec.js create mode 100644 cypress/integration/03-system-settings.spec.js create mode 100644 cypress/integration/04-theme-settings.spec.js create mode 100644 cypress/integration/05_visual-editor/05-blox-editor.spec.js create mode 100644 cypress/integration/99-login.spec.js create mode 100644 cypress/plugins/index.js create mode 100644 cypress/support/commands.js create mode 100644 cypress/support/index.js create mode 100644 data/security/securitylog.txt create mode 100644 docker-utils/init-server create mode 100644 docker-utils/install-composer create mode 100644 index.php create mode 100644 licence.md create mode 100644 media/files/filerestrictions.yaml create mode 100644 media/files/markdown.png create mode 100644 media/live/youtube-6i2-uv88gke.jpeg create mode 100644 media/live/youtube-7yvlwxjl9dc-1.jpg create mode 100644 media/live/youtube-7yvlwxjl9dc-2.jpg create mode 100644 media/live/youtube-7yvlwxjl9dc.jpeg create mode 100644 media/live/youtube-7yvlwxjl9dc.jpg create mode 100644 media/live/youtube-uw-m-4g1kaa.jpeg create mode 100644 media/original/youtube-6i2-uv88gke.jpeg create mode 100644 media/original/youtube-7yvlwxjl9dc-1.jpg create mode 100644 media/original/youtube-7yvlwxjl9dc-2.jpg create mode 100644 media/original/youtube-7yvlwxjl9dc.jpeg create mode 100644 media/original/youtube-7yvlwxjl9dc.jpg create mode 100644 media/original/youtube-uw-m-4g1kaa.jpeg create mode 100644 media/thumbs/youtube-6i2-uv88gke.jpeg create mode 100644 media/thumbs/youtube-7yvlwxjl9dc-1.jpg create mode 100644 media/thumbs/youtube-7yvlwxjl9dc-2.jpg create mode 100644 media/thumbs/youtube-7yvlwxjl9dc.jpeg create mode 100644 media/thumbs/youtube-7yvlwxjl9dc.jpg create mode 100644 media/thumbs/youtube-uw-m-4g1kaa.jpeg create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 plugins/demo/DemoController.php create mode 100644 plugins/demo/Text.php create mode 100644 plugins/demo/css/demo.css create mode 100644 plugins/demo/demo.php create mode 100644 plugins/demo/demo.twig create mode 100644 plugins/demo/demo.yaml create mode 100644 plugins/demo/js/editordemo.js create mode 100644 plugins/demo/js/systemdemo.js create mode 100644 plugins/demo/templates/demo.twig create mode 160000 plugins/search create mode 100644 readme.md create mode 100644 settings/public_key.pem create mode 100644 system/autoload.php create mode 100644 system/typemill/Assets.php create mode 100644 system/typemill/Controllers/Controller.php create mode 100644 system/typemill/Controllers/ControllerApiAuthorArticle.php create mode 100644 system/typemill/Controllers/ControllerApiAuthorBlock.php create mode 100644 system/typemill/Controllers/ControllerApiAuthorMeta.php create mode 100644 system/typemill/Controllers/ControllerApiAuthorShortcode.php create mode 100644 system/typemill/Controllers/ControllerApiFile.php create mode 100644 system/typemill/Controllers/ControllerApiGlobals.php create mode 100644 system/typemill/Controllers/ControllerApiImage.php create mode 100644 system/typemill/Controllers/ControllerApiKixote.php create mode 100644 system/typemill/Controllers/ControllerApiSystemExtensions.php create mode 100644 system/typemill/Controllers/ControllerApiSystemLicense.php create mode 100644 system/typemill/Controllers/ControllerApiSystemPlugins.php create mode 100644 system/typemill/Controllers/ControllerApiSystemSettings.php create mode 100644 system/typemill/Controllers/ControllerApiSystemThemes.php create mode 100644 system/typemill/Controllers/ControllerApiSystemUsers.php create mode 100644 system/typemill/Controllers/ControllerApiSystemVersions.php create mode 100644 system/typemill/Controllers/ControllerApiTestmail.php create mode 100644 system/typemill/Controllers/ControllerWebAuth.php create mode 100644 system/typemill/Controllers/ControllerWebAuthor.php create mode 100644 system/typemill/Controllers/ControllerWebDownload.php create mode 100644 system/typemill/Controllers/ControllerWebFrontend.php create mode 100644 system/typemill/Controllers/ControllerWebRecover.php create mode 100644 system/typemill/Controllers/ControllerWebSetup.php create mode 100644 system/typemill/Controllers/ControllerWebSystem.php create mode 100644 system/typemill/Events/BaseEvent.php create mode 100644 system/typemill/Events/OnBreadcrumbLoaded.php create mode 100644 system/typemill/Events/OnCacheUpdated.php create mode 100644 system/typemill/Events/OnContentArrayLoaded.php create mode 100644 system/typemill/Events/OnCspLoaded.php create mode 100644 system/typemill/Events/OnHtmlLoaded.php create mode 100644 system/typemill/Events/OnItemLoaded.php create mode 100644 system/typemill/Events/OnMarkdownLoaded.php create mode 100644 system/typemill/Events/OnMetaDefinitionsLoaded.php create mode 100644 system/typemill/Events/OnMetaLoaded.php create mode 100644 system/typemill/Events/OnOriginalLoaded.php create mode 100644 system/typemill/Events/OnPageCreated.php create mode 100644 system/typemill/Events/OnPageDeleted.php create mode 100644 system/typemill/Events/OnPageDiscard.php create mode 100644 system/typemill/Events/OnPagePublished.php create mode 100644 system/typemill/Events/OnPageReady.php create mode 100644 system/typemill/Events/OnPageRenamed.php create mode 100644 system/typemill/Events/OnPageSorted.php create mode 100644 system/typemill/Events/OnPageUnpublished.php create mode 100644 system/typemill/Events/OnPageUpdated.php create mode 100644 system/typemill/Events/OnPagetreeLoaded.php create mode 100644 system/typemill/Events/OnPluginsLoaded.php create mode 100644 system/typemill/Events/OnResourcesLoaded.php create mode 100644 system/typemill/Events/OnRestrictionsLoaded.php create mode 100644 system/typemill/Events/OnRolesPermissionsLoaded.php create mode 100644 system/typemill/Events/OnSessionSegmentsLoaded.php create mode 100644 system/typemill/Events/OnSettingsLoaded.php create mode 100644 system/typemill/Events/OnShortcodeFound.php create mode 100644 system/typemill/Events/OnSystemnaviLoaded.php create mode 100644 system/typemill/Events/OnTwigGlobalsLoaded.php create mode 100644 system/typemill/Events/OnTwigLoaded.php create mode 100644 system/typemill/Events/OnUserConfirmed.php create mode 100644 system/typemill/Events/OnUserDeleted.php create mode 100644 system/typemill/Events/OnUserfieldsLoaded.php create mode 100644 system/typemill/Extensions/MediaExtension.php create mode 100644 system/typemill/Extensions/ParsedownExtension.php create mode 100644 system/typemill/Extensions/TwigCaptchaExtension.php create mode 100644 system/typemill/Extensions/TwigCsrfExtension.php create mode 100644 system/typemill/Extensions/TwigLanguageExtension.php create mode 100644 system/typemill/Extensions/TwigMarkdownExtension.php create mode 100644 system/typemill/Extensions/TwigMetaExtension.php create mode 100644 system/typemill/Extensions/TwigPagelistExtension.php create mode 100644 system/typemill/Extensions/TwigUrlExtension.php create mode 100644 system/typemill/Extensions/TwigUserExtension.php create mode 100644 system/typemill/Middleware/ApiAuthentication.php create mode 100644 system/typemill/Middleware/ApiAuthorization.php create mode 100644 system/typemill/Middleware/AssetMiddleware.php create mode 100644 system/typemill/Middleware/CorsHeadersMiddleware.php create mode 100644 system/typemill/Middleware/CspHeadersMiddleware.php create mode 100644 system/typemill/Middleware/CustomHeadersMiddleware.php create mode 100644 system/typemill/Middleware/FlashMessages.php create mode 100644 system/typemill/Middleware/JsonBodyParser.php create mode 100644 system/typemill/Middleware/OldInputMiddleware.php create mode 100644 system/typemill/Middleware/RemoveCredentialsMiddleware.php create mode 100644 system/typemill/Middleware/SecurityMiddleware.php create mode 100644 system/typemill/Middleware/SessionMiddleware.php create mode 100644 system/typemill/Middleware/ValidationErrorsMiddleware.php create mode 100644 system/typemill/Middleware/WebAuthorization.php create mode 100644 system/typemill/Middleware/WebRedirectIfAuthenticated.php create mode 100644 system/typemill/Middleware/WebRedirectIfUnauthenticated.php create mode 100644 system/typemill/Models/ApiCalls.php create mode 100644 system/typemill/Models/Content.php create mode 100644 system/typemill/Models/Extension.php create mode 100644 system/typemill/Models/Field.php create mode 100644 system/typemill/Models/Fields.php create mode 100644 system/typemill/Models/Folder.php create mode 100644 system/typemill/Models/License.php create mode 100644 system/typemill/Models/Media.php create mode 100644 system/typemill/Models/Meta.php create mode 100644 system/typemill/Models/Navigation.php create mode 100644 system/typemill/Models/Settings.php create mode 100644 system/typemill/Models/SimpleMail.php create mode 100644 system/typemill/Models/Sitemap.php create mode 100644 system/typemill/Models/Storage.php create mode 100644 system/typemill/Models/StorageWrapper.php create mode 100644 system/typemill/Models/SvgSanitizer.php create mode 100644 system/typemill/Models/User.php create mode 100644 system/typemill/Models/Validation.php create mode 100644 system/typemill/Plugin.php create mode 100644 system/typemill/Static/Helpers.php create mode 100644 system/typemill/Static/Permissions.php create mode 100644 system/typemill/Static/Plugins.php create mode 100644 system/typemill/Static/Session.php create mode 100644 system/typemill/Static/Slug.php create mode 100644 system/typemill/Static/Translations.php create mode 100644 system/typemill/Static/Urlinfo.php create mode 100644 system/typemill/author/404.twig create mode 100644 system/typemill/author/auth/authcode.twig create mode 100644 system/typemill/author/auth/login.twig create mode 100644 system/typemill/author/auth/recover.twig create mode 100644 system/typemill/author/auth/recoverconf.twig create mode 100644 system/typemill/author/auth/reset.twig create mode 100644 system/typemill/author/auth/setup.twig create mode 100644 system/typemill/author/content/blox-editor.twig create mode 100644 system/typemill/author/content/raw-editor.twig create mode 100644 system/typemill/author/css/a11y-dark.min.css create mode 100644 system/typemill/author/css/custom.css create mode 100644 system/typemill/author/css/input.css create mode 100644 system/typemill/author/css/output.css create mode 100644 system/typemill/author/js/autosize.min.js create mode 100644 system/typemill/author/js/axios.min.js create mode 100644 system/typemill/author/js/highlight.min.js create mode 100644 system/typemill/author/js/sortable.min.js create mode 100644 system/typemill/author/js/typemillutils.js create mode 100644 system/typemill/author/js/vue-blox-components.js create mode 100644 system/typemill/author/js/vue-blox-config.js create mode 100644 system/typemill/author/js/vue-blox.js create mode 100644 system/typemill/author/js/vue-contentnavi.js create mode 100644 system/typemill/author/js/vue-eventbus.js create mode 100644 system/typemill/author/js/vue-forms.js create mode 100644 system/typemill/author/js/vue-kixote.js create mode 100644 system/typemill/author/js/vue-license.js create mode 100644 system/typemill/author/js/vue-medialib.js create mode 100644 system/typemill/author/js/vue-meta.js create mode 100644 system/typemill/author/js/vue-plugins.js create mode 100644 system/typemill/author/js/vue-posts.js create mode 100644 system/typemill/author/js/vue-publisher.js create mode 100644 system/typemill/author/js/vue-raw.js create mode 100644 system/typemill/author/js/vue-shared.js create mode 100644 system/typemill/author/js/vue-system.js create mode 100644 system/typemill/author/js/vue-systemnavi.js create mode 100644 system/typemill/author/js/vue-themes.js create mode 100644 system/typemill/author/js/vue-user.js create mode 100644 system/typemill/author/js/vue-usernew.js create mode 100644 system/typemill/author/js/vue-users.js create mode 100644 system/typemill/author/js/vue.global.prod.js create mode 100644 system/typemill/author/js/vue.js create mode 100644 system/typemill/author/js/vuedraggable.umd.min.js create mode 100644 system/typemill/author/layouts/layoutAuth.twig create mode 100644 system/typemill/author/layouts/layoutContent.twig create mode 100644 system/typemill/author/layouts/layoutSystem.twig create mode 100644 system/typemill/author/layouts/layoutSystemBlank.twig create mode 100644 system/typemill/author/partials/contentNavi.twig create mode 100644 system/typemill/author/partials/fields.twig create mode 100644 system/typemill/author/partials/flash.twig create mode 100644 system/typemill/author/partials/form.twig create mode 100644 system/typemill/author/partials/mainNavi.twig create mode 100644 system/typemill/author/partials/symbols.twig create mode 100644 system/typemill/author/partials/systemNavi.twig create mode 100644 system/typemill/author/system/account.twig create mode 100644 system/typemill/author/system/license.twig create mode 100644 system/typemill/author/system/plugins.twig create mode 100644 system/typemill/author/system/system.twig create mode 100644 system/typemill/author/system/themes.twig create mode 100644 system/typemill/author/system/user.twig create mode 100644 system/typemill/author/system/usernew.twig create mode 100644 system/typemill/author/system/users.twig create mode 100644 system/typemill/author/translations/de.yaml create mode 100644 system/typemill/author/translations/en.yaml create mode 100644 system/typemill/author/translations/fr.yaml create mode 100644 system/typemill/author/translations/it.yaml create mode 100644 system/typemill/author/translations/nl.yaml create mode 100644 system/typemill/author/translations/ru.yaml create mode 100644 system/typemill/routes/api.php create mode 100644 system/typemill/routes/setup.php create mode 100644 system/typemill/routes/web.php create mode 100644 system/typemill/settings/defaults.yaml create mode 100644 system/typemill/settings/kixote.yaml create mode 100644 system/typemill/settings/license.yaml create mode 100644 system/typemill/settings/mainnavi.yaml create mode 100644 system/typemill/settings/metatabs.yaml create mode 100644 system/typemill/settings/permissions.yaml create mode 100644 system/typemill/settings/resources.yaml create mode 100644 system/typemill/settings/system.yaml create mode 100644 system/typemill/settings/systemnavi.yaml create mode 100644 system/typemill/settings/user.yaml create mode 100644 system/typemill/system.php create mode 100644 tailwind.config.js create mode 100644 themes/cyanine/404.twig create mode 100644 themes/cyanine/blog.twig create mode 100644 themes/cyanine/css/style.css create mode 100644 themes/cyanine/css/tachyons.min.css create mode 100644 themes/cyanine/cyanine-thumb.png create mode 100644 themes/cyanine/cyanine.png create mode 100644 themes/cyanine/cyanine.yaml create mode 100644 themes/cyanine/home.twig create mode 100644 themes/cyanine/home/landingpageContrast.twig create mode 100644 themes/cyanine/home/landingpageInfo.twig create mode 100644 themes/cyanine/home/landingpageIntro.twig create mode 100644 themes/cyanine/home/landingpageNavi.twig create mode 100644 themes/cyanine/home/landingpageNews.twig create mode 100644 themes/cyanine/home/landingpageTeaser.twig create mode 100644 themes/cyanine/index.twig create mode 100644 themes/cyanine/js/script.js create mode 100644 themes/cyanine/landingpage.twig create mode 100644 themes/cyanine/languages/admin/en.yaml create mode 100644 themes/cyanine/languages/admin/fr.yaml create mode 100644 themes/cyanine/languages/admin/it.yaml create mode 100644 themes/cyanine/languages/user/en.yaml create mode 100644 themes/cyanine/languages/user/it.yaml create mode 100644 themes/cyanine/layout.twig create mode 100644 themes/cyanine/page.twig create mode 100644 themes/cyanine/partials/breadcrumb.twig create mode 100644 themes/cyanine/partials/footer.twig create mode 100644 themes/cyanine/partials/navigation.twig create mode 100644 themes/cyanine/partials/navigationFlat.twig create mode 100644 themes/cyanine/partials/navigationGlossary.twig create mode 100644 themes/cyanine/partials/posts.twig create mode 100644 typemill.png diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..610923b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,21 @@ +# Ignore everything +** + +# Allow system files and directories +!docker-utils/ +!system/ +!.htaccess +!composer* +!index.php + +# Allow content files and directories +!cache/ +!content/ +!data/ +!media/ +!settings/ +!themes/ + +# Ignore unnecessary files inside allowed directories below +# This should go after the allowed directories +# e.g. docker-utils/test.php \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7fad67c --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +cache/sitemap.xml +content/index.yaml +content/00-welcome/index.yaml +content/00-welcome/00-setup-your-website.yaml +content/00-welcome/01-write-content.yaml +content/00-welcome/02-manage-access.yaml +content/00-welcome/03-get-help.yaml +content/00-welcome/04-markdown-test.yaml +content/01-cyanine-theme/index.yaml +content/01-cyanine-theme/00-landingpage.yaml +content/01-cyanine-theme/01-colors-and-fonts.yaml +content/01-cyanine-theme/02-footer.yaml +content/01-cyanine-theme/03-content-elements.yaml +system/vendor +cypress +data/navigation +data/css +node_modules +plugins/search +settings/settings.yaml +settings/secrets.yaml +settings/license.yaml +settings/users +zips +cypress.json \ No newline at end of file diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..cf625b4 --- /dev/null +++ b/.htaccess @@ -0,0 +1,74 @@ + + +RewriteEngine On + +# If your homepage is http://yourdomain.com/yoursite +# Set the RewriteBase to: +# RewriteBase /yoursite + +# In some environements, an empty RewriteBase is required: +# RewriteBase / + +# Use this to redirect HTTP to HTTPS on apache servers +# RewriteCond %{HTTPS} off +# RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L] + +# Use this to redirect www to non-wwww on apache servers +# RewriteCond %{HTTP_HOST} ^www\.(.*)$ [NC] +# RewriteRule ^(.*)$ http://%1/$1 [R=301,L] + +# Use this to redirect slash/ to url without slash on apache servers +# RewriteCond %{REQUEST_FILENAME} !-d +# RewriteRule ^(.*)/$ /$1 [R=301,L] + +# Removes index.php +RewriteCond %{THE_REQUEST} ^GET.*index\.php [NC] +RewriteRule (.*?)index\.php/*(.*) /$1$2 [R=301,NE,L] + +# REWRITE TO INDEX + +# If the requested path and file not /index.php +RewriteCond %{REQUEST_URI} !^/index\.php + +# if requested doesn't match a physical file +RewriteCond %{REQUEST_FILENAME} !-f + +# if requested doesn't match a physical folder +RewriteCond %{REQUEST_FILENAME} !-d + +# then rewrite the request to the index.php script +RewriteRule ^ index.php [QSA,L] + + +# FILE/FOLDER PROTECTION + +# Deny access to these file types generally +RewriteRule ^(.*)?\.yml$ - [F,L] +Rewriterule ^(.*)?\.yaml$ - [F,L] +RewriteRule ^(.*)?\.txt$ - [F,L] +RewriteRule ^(.*)?\.example$ - [F,L] +RewriteRule ^(.*)?\.git+ - [F,L] +RewriteRule ^(.*)?\.md - [F,L] +RewriteCond %{REQUEST_URI} !/index\.php +RewriteRule ^(.*)?\.ph - [F,L] +RewriteRule ^(.*)?\.twig - [F,L] +RewriteRule ^(media\/tmp\/) - [F,L] + +# Block access to specific files in the root folder +RewriteRule ^(composer\.lock|composer\.json|\.htaccess)$ error [F,L] + +# block files and folders starting with a dot except for the .well-known folder (Let's Encrypt) +RewriteRule (^|/)\.(?!well-known\/) index.php [L] + +# Allow access to frontend files in author folder +RewriteRule ^(system\/typemill\/author\/css\/) - [L] +RewriteRule ^(system\/typemill\/author\/img\/) - [L] +RewriteRule ^(system\/typemill\/author\/js\/) - [L] + +# redirect all other direct requests to the following physical folders to the index.php so pages with same name work +RewriteRule ^(system|content|data|settings|(media\/files\/)) index.php [QSA,L] + +# disallow browsing other folders generally +Options -Indexes + + \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..36c1748 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,41 @@ +FROM php:8.0-apache + +# Install OS dependencies required +RUN apt-get update && apt-get upgrade -y && apt-get install git unzip zlib1g-dev libpng-dev -y + +# Adapt apache config +RUN a2enmod rewrite \ + && echo "ServerName 127.0.0.1" >> /etc/apache2/apache2.conf + +# Install PHP ext-gd +RUN docker-php-ext-install gd + +# Copy app content +# Use the .dockerignore file to control what ends up inside the image! +WORKDIR /var/www/html +COPY . . + +# Install server dependencies +RUN chmod +x /var/www/html/docker-utils/install-composer && \ + /var/www/html/docker-utils/install-composer && \ + ./composer.phar update && \ + chmod +x /var/www/html/docker-utils/init-server + +# Expose useful volumes (see documentation) +VOLUME /var/www/html/settings +VOLUME /var/www/html/media +VOLUME /var/www/html/cache +VOLUME /var/www/html/plugins +VOLUME /var/www/html/data + +# Create a default copy of content and theme in case of empty directories binding +RUN mkdir -p /var/www/html/content.default/ && \ + cp -R /var/www/html/content/* /var/www/html/content.default/ && \ + mkdir -p /var/www/html/themes.default/ && \ + cp -R /var/www/html/themes/* /var/www/html/themes.default/ + +VOLUME /var/www/html/content +VOLUME /var/www/html/themes + +# Inject default values if content and themes are mounted with empty directories, adjust rights and start the server +CMD ["/var/www/html/docker-utils/init-server"] \ No newline at end of file diff --git a/cache/cyanine-custom.css b/cache/cyanine-custom.css new file mode 100644 index 0000000..e3dd6f5 --- /dev/null +++ b/cache/cyanine-custom.css @@ -0,0 +1,205 @@ +.demolink{ + display:block; + font-size: .8em; + text-decoration:none; + margin-top: 20px; +} +/* +.landingpageintro h1{ +display: inline-block; +background: white; +padding: 4px 10px 10px; +margin: 15px; +} +*/ +.landingpageintro .dim:focus, .landingpageintro .dim:hover { + opacity: .8; +} +.landingpageintro figure { + display: block; + margin: 0em auto; + padding: 0; + font-weight: 500; +} +.landingpageintro figure img { + display: inline; + vertical-align: baseline; + width: 18px; +} + + +.landingpageintro h1{ + font-size: 2.5em; +} + +.landingpageintro p{ +/* display: inline; */ +/* background: white; */ +/* line-height: 2.1rem; + padding: 6px 4px 4px; */ + + font-weight: 500; +} +.landingpageintro p a{ + color: white; + font-size: .8em; +} + +.introbg.pa4{ + padding: 0; +} +@media screen and (min-width:60em) { +/* + .landingpageintro h1{ + font-size: 4.5rem; + } +*/ + .introbg.pa4{ + padding: 2rem; + } +} + +.landingpageinfo ol{ + text-align: left; + list-style: none; + counter-reset: item; +} +.landingpageinfo ol li{ + padding:.5rem 0; + counter-increment: item; +} +.landingpageinfo ol li:before { + content: counter(item); + margin-right: 10px; + background: lightseagreen; + border-radius: 100%; + color: white; + text-align: center; + display: inline-block; + margin-left: -40px; + font-size: .8em; + line-height: 30px; + width: 30px; + font-weight: 400; +} +.landingpageinfo ol a{ + color:lightseagreen; +} + + #mlb2-10705438.ml-form-embedContainer .ml-form-embedWrapper .ml-form-embedBody .ml-block-form .ml-form-embedPermissions, +#mlb2-10705438.ml-form-embedContainer .ml-form-embedWrapper .ml-form-embedBody .ml-block-form .ml-form-embedSubmit{ + float:none; + } + .ml-form-embedBody{ + width:100%; + background-color:white; + } + #mlb2-10705438.ml-form-embedContainer .ml-form-embedWrapper .ml-form-embedBody, #mlb2-10705438.ml-form-embedContainer .ml-form-embedWrapper .ml-form-successBody{ + padding: 40px 40px 40px 40px!important; + } + +@media screen and (min-width: 50em) { + .ml-form-embedBody{ + width:60%; + } + +} + +/*paddle*/ +.mt-auto{ + margin-top:auto; +} +.paddlebox h2{ + margin-top:1em; +} +.paddlebox a, +.paddlebox a:link, +.paddlebox a:hover, +.paddlebox a:focus, +.paddlebox a:active, +.paddlebox a:visited{ + text-decoration: none; +} + +.paddlebox strong{ + position: relative; + background-color: RGBA(32, 178, 170, .2); + font-size: 1.2em; + padding: 1px; +} +.paddlebox strong:before{ + content:""; + left:0em; + top:0em; + border-width:3px; + border-style:solid; + border-color:lightseagreen; + position:absolute; + border-right-color:transparent; + width:100%; + height:1.1em; + transform:rotate(2deg); + opacity:0.5; + border-radius:0.25em; +} + +.paddlebox strong:after{ + content:""; + left:0em; + top:0em; + border-width:4px; + border-style:solid; + border-color:lightseagreen; + border-left-color:transparent; + border-top-color:transparent; + position:absolute; + width:100%; + height:1em; + transform:rotate(-1deg); + opacity:0.5; + border-radius:0.25em; +} +.paddlebox ul{ + list-style-type: "✓ "; + padding-left:1em; +} +.paddle_button{ + font-family: monospace; +} + +.myclassbox p{ + background:lightseagreen; + color: white; +} + +@keyframes slideUp { +0% { + transform: translateY(0); +} +20% { + transform: translateY(-100%); +} +40% { + transform: translateY(-200%); +} +60% { + transform: translateY(-300%); +} +80% { + transform: translateY(-400%); +} +100% { + transform: translateY(0); +} +} + +.slide-up { +/* animation: slideUp 1s ease-in-out; */ + animation: slideUp 8s infinite; + will-change: transform; + animation-delay: 3s; +} + +.figureborder figure { + border: 1px solid lightgray; +} \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..9685570 --- /dev/null +++ b/composer.json @@ -0,0 +1,44 @@ +{ + "name": "typemill/typemill", + "type": "project", + "description": "A lightweight flat-file-cms for advanced publishing. Create websites, handbooks, documentations, and transform them into ebooks.", + "keywords": ["cms", "vue", "markdown", "slim", "php", "flat-file", "publishing", "documentations", "manuals", "handbooks", "pdf", "epub", "paged media", "paged.js"], + "homepage": "https://typemill.net", + "license": "MIT", + "config": { + "vendor-dir": "system/vendor", + "allow-plugins": { + "composer/installers": true + }, + "platform": { + "php": "8.0.0" + } + }, + "require": { + "php": "^8.0", + "slim/slim": "4.*", + "slim/psr7": "^1.5", + "php-di/php-di": "^6.3", + "php-di/slim-bridge": "^3.2", + "slim/twig-view": "^3.3", + "slim/flash": "^0.4.0", + "slim/csrf": "^1.2", + "vlucas/valitron": "^1.4", + "symfony/yaml": "^5.4", + "symfony/event-dispatcher": "^5.4", + "erusev/parsedown": "dev-master", + "erusev/parsedown-extra": "dev-master", + "jbroadway/urlify": "1.1.3", + "laminas/laminas-permissions-acl": "^2.10", + "akrabat/proxy-detection-middleware": "^1.0.0", + "gregwar/captcha": "master" + }, + "autoload": { + "psr-4": { + "Typemill\\": "system/typemill/", + "Plugins\\": "plugins/" + } + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..83d5699 --- /dev/null +++ b/composer.lock @@ -0,0 +1,2362 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "eb7c3aa9209071a36f2500b26a1c577c", + "packages": [ + { + "name": "akrabat/proxy-detection-middleware", + "version": "1.1", + "source": { + "type": "git", + "url": "https://github.com/akrabat/proxy-detection-middleware.git", + "reference": "ea30be71ffb863ea1bdfdab66459e820e7044122" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/akrabat/proxy-detection-middleware/zipball/ea30be71ffb863ea1bdfdab66459e820e7044122", + "reference": "ea30be71ffb863ea1bdfdab66459e820e7044122", + "shasum": "" + }, + "require": { + "psr/http-message": "^1.0 || ^2.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "laminas/laminas-diactoros": "^2.24", + "php": "^8.0", + "phpunit/phpunit": "^9.6", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "RKA\\Middleware\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Rob Allen", + "email": "rob@akrabat.com", + "homepage": "http://akrabat.com" + } + ], + "description": "PSR-7/PSR-15 Middleware that determines the scheme, host and port from the 'X-Forwarded-Proto', 'X-Forwarded-Host' and 'X-Forwarded-Port' headers and updates the Request's Uri object.", + "homepage": "http://github.com/akrabat/proxy-detection-middleware", + "keywords": [ + "IP", + "middleware", + "psr7" + ], + "support": { + "issues": "https://github.com/akrabat/proxy-detection-middleware/issues", + "source": "https://github.com/akrabat/proxy-detection-middleware/tree/1.1" + }, + "time": "2024-11-21T19:16:46+00:00" + }, + { + "name": "erusev/parsedown", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/erusev/parsedown.git", + "reference": "c9dc49f68f7bd14e5aa107d86a08ce59203f02ee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/c9dc49f68f7bd14e5aa107d86a08ce59203f02ee", + "reference": "c9dc49f68f7bd14e5aa107d86a08ce59203f02ee", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5|^8.5|^9.6" + }, + "default-branch": true, + "type": "library", + "autoload": { + "psr-0": { + "Parsedown": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Emanuil Rusev", + "email": "hello@erusev.com", + "homepage": "http://erusev.com" + } + ], + "description": "Parser for Markdown.", + "homepage": "http://parsedown.org", + "keywords": [ + "markdown", + "parser" + ], + "support": { + "issues": "https://github.com/erusev/parsedown/issues", + "source": "https://github.com/erusev/parsedown/tree/master" + }, + "time": "2024-12-01T16:36:17+00:00" + }, + { + "name": "erusev/parsedown-extra", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/erusev/parsedown-extra.git", + "reference": "acebd175967ac1224dc710ab6082e5a28c202c85" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/erusev/parsedown-extra/zipball/acebd175967ac1224dc710ab6082e5a28c202c85", + "reference": "acebd175967ac1224dc710ab6082e5a28c202c85", + "shasum": "" + }, + "require": { + "erusev/parsedown": "dev-master", + "ext-dom": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5|^8.5|^9.6" + }, + "default-branch": true, + "type": "library", + "autoload": { + "psr-0": { + "ParsedownExtra": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Emanuil Rusev", + "email": "hello@erusev.com", + "homepage": "https://erusev.com" + } + ], + "description": "An extension of Parsedown that adds support for Markdown Extra.", + "homepage": "https://github.com/erusev/parsedown-extra", + "keywords": [ + "markdown", + "markdown extra", + "parsedown", + "parser" + ], + "support": { + "issues": "https://github.com/erusev/parsedown-extra/issues", + "source": "https://github.com/erusev/parsedown-extra/tree/master" + }, + "time": "2024-11-03T08:25:00+00:00" + }, + { + "name": "fig/http-message-util", + "version": "1.1.5", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message-util.git", + "reference": "9d94dc0154230ac39e5bf89398b324a86f63f765" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message-util/zipball/9d94dc0154230ac39e5bf89398b324a86f63f765", + "reference": "9d94dc0154230ac39e5bf89398b324a86f63f765", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0 || ^8.0" + }, + "suggest": { + "psr/http-message": "The package containing the PSR-7 interfaces" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Fig\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Utility classes and constants for use with PSR-7 (psr/http-message)", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-message-util/issues", + "source": "https://github.com/php-fig/http-message-util/tree/1.1.5" + }, + "time": "2020-11-24T22:02:12+00:00" + }, + { + "name": "gregwar/captcha", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/Gregwar/Captcha.git", + "reference": "229d3cdfe33d6f1349e0aec94a26e9205a6db08e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Gregwar/Captcha/zipball/229d3cdfe33d6f1349e0aec94a26e9205a6db08e", + "reference": "229d3cdfe33d6f1349e0aec94a26e9205a6db08e", + "shasum": "" + }, + "require": { + "ext-gd": "*", + "ext-mbstring": "*", + "php": ">=5.3.0", + "symfony/finder": "*" + }, + "require-dev": { + "phpunit/phpunit": "^6.4" + }, + "default-branch": true, + "type": "library", + "autoload": { + "psr-4": { + "Gregwar\\": "src/Gregwar" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Passault", + "email": "g.passault@gmail.com", + "homepage": "http://www.gregwar.com/" + }, + { + "name": "Jeremy Livingston", + "email": "jeremy.j.livingston@gmail.com" + } + ], + "description": "Captcha generator", + "homepage": "https://github.com/Gregwar/Captcha", + "keywords": [ + "bot", + "captcha", + "spam" + ], + "support": { + "issues": "https://github.com/Gregwar/Captcha/issues", + "source": "https://github.com/Gregwar/Captcha/tree/v1.2.1" + }, + "time": "2023-09-26T13:45:37+00:00" + }, + { + "name": "jbroadway/urlify", + "version": "1.1.3-stable", + "source": { + "type": "git", + "url": "https://github.com/jbroadway/urlify.git", + "reference": "37fe4e7680a1c8cd68ac43a27dac7ef4be476300" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jbroadway/urlify/zipball/37fe4e7680a1c8cd68ac43a27dac7ef4be476300", + "reference": "37fe4e7680a1c8cd68ac43a27dac7ef4be476300", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "URLify": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause-Clear" + ], + "authors": [ + { + "name": "Johnny Broadway", + "email": "johnny@johnnybroadway.com", + "homepage": "http://www.johnnybroadway.com/" + } + ], + "description": "PHP port of URLify.js from the Django project. Transliterates non-ascii characters for use in URLs.", + "homepage": "https://github.com/jbroadway/urlify", + "keywords": [ + "encode", + "iconv", + "link", + "slug", + "translit", + "transliterate", + "transliteration", + "url", + "urlify" + ], + "support": { + "issues": "https://github.com/jbroadway/urlify/issues", + "source": "https://github.com/jbroadway/urlify/tree/1.1.3-stable" + }, + "time": "2019-06-13T18:30:56+00:00" + }, + { + "name": "laminas/laminas-permissions-acl", + "version": "2.14.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-permissions-acl.git", + "reference": "86cecb540cf8f2e088d70d8acef1fc9203ed5023" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-permissions-acl/zipball/86cecb540cf8f2e088d70d8acef1fc9203ed5023", + "reference": "86cecb540cf8f2e088d70d8acef1fc9203ed5023", + "shasum": "" + }, + "require": { + "php": "~8.0.0 || ~8.1.0 || ~8.2.0" + }, + "conflict": { + "laminas/laminas-servicemanager": "<3.0", + "zendframework/zend-permissions-acl": "*" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~2.5.0", + "laminas/laminas-servicemanager": "^3.19", + "phpbench/phpbench": "^1.2", + "phpunit/phpunit": "^9.5.26", + "psalm/plugin-phpunit": "^0.18.0", + "vimeo/psalm": "^5.0" + }, + "suggest": { + "laminas/laminas-servicemanager": "To support Laminas\\Permissions\\Acl\\Assertion\\AssertionManager plugin manager usage" + }, + "type": "library", + "autoload": { + "psr-4": { + "Laminas\\Permissions\\Acl\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Provides a lightweight and flexible access control list (ACL) implementation for privileges management", + "homepage": "https://laminas.dev", + "keywords": [ + "acl", + "laminas" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-permissions-acl/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-permissions-acl/issues", + "rss": "https://github.com/laminas/laminas-permissions-acl/releases.atom", + "source": "https://github.com/laminas/laminas-permissions-acl" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2023-02-01T16:19:54+00:00" + }, + { + "name": "laravel/serializable-closure", + "version": "v1.3.7", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "4f48ade902b94323ca3be7646db16209ec76be3d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/4f48ade902b94323ca3be7646db16209ec76be3d", + "reference": "4f48ade902b94323ca3be7646db16209ec76be3d", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "require-dev": { + "illuminate/support": "^8.0|^9.0|^10.0|^11.0", + "nesbot/carbon": "^2.61|^3.0", + "pestphp/pest": "^1.21.3", + "phpstan/phpstan": "^1.8.2", + "symfony/var-dumper": "^5.4.11|^6.2.0|^7.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2024-11-14T18:34:49+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.6", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "59f15608528d8a8838d69b422a919fd6b16aa576" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/59f15608528d8a8838d69b422a919fd6b16aa576", + "reference": "59f15608528d8a8838d69b422a919fd6b16aa576", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.6" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2025-01-17T12:49:27+00:00" + }, + { + "name": "php-di/php-di", + "version": "6.4.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/ae0f1b3b03d8b29dff81747063cbfd6276246cc4", + "reference": "ae0f1b3b03d8b29dff81747063cbfd6276246cc4", + "shasum": "" + }, + "require": { + "laravel/serializable-closure": "^1.0", + "php": ">=7.4.0", + "php-di/invoker": "^2.0", + "php-di/phpdoc-reader": "^2.0.1", + "psr/container": "^1.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "doctrine/annotations": "~1.10", + "friendsofphp/php-cs-fixer": "^2.4", + "mnapoli/phpunit-easymock": "^1.2", + "ocramius/proxy-manager": "^2.11.2", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^9.5" + }, + "suggest": { + "doctrine/annotations": "Install it if you want to use annotations (version ~1.2)", + "ocramius/proxy-manager": "Install it if you want to use lazy injection (version ~2.0)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/6.4.0" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2022-04-09T16:46:38+00:00" + }, + { + "name": "php-di/phpdoc-reader", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PhpDocReader.git", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PhpDocReader/zipball/66daff34cbd2627740ffec9469ffbac9f8c8185c", + "reference": "66daff34cbd2627740ffec9469ffbac9f8c8185c", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpDocReader\\": "src/PhpDocReader" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PhpDocReader parses @var and @param values in PHP docblocks (supports namespaced class names with the same resolution rules as PHP)", + "keywords": [ + "phpdoc", + "reflection" + ], + "support": { + "issues": "https://github.com/PHP-DI/PhpDocReader/issues", + "source": "https://github.com/PHP-DI/PhpDocReader/tree/2.2.1" + }, + "time": "2020-10-12T12:39:22+00:00" + }, + { + "name": "php-di/slim-bridge", + "version": "3.4.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Slim-Bridge.git", + "reference": "02ab0274a19d104d74561164f8915b62d93f3cf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Slim-Bridge/zipball/02ab0274a19d104d74561164f8915b62d93f3cf0", + "reference": "02ab0274a19d104d74561164f8915b62d93f3cf0", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "php-di/invoker": "^2.0.0", + "php-di/php-di": "^6.0|^7.0", + "slim/slim": "^4.2.0" + }, + "require-dev": { + "laminas/laminas-diactoros": "^2.1", + "mnapoli/hard-mode": "^0.3.0", + "phpunit/phpunit": ">= 7.0 < 10" + }, + "type": "library", + "autoload": { + "psr-4": { + "DI\\Bridge\\Slim\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHP-DI integration in Slim", + "support": { + "issues": "https://github.com/PHP-DI/Slim-Bridge/issues", + "source": "https://github.com/PHP-DI/Slim-Bridge/tree/3.4.1" + }, + "time": "2024-06-19T15:47:45+00:00" + }, + { + "name": "psr/container", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/84c4fb66179be4caaf8e97bd239203245302e7d4", + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "source": "https://github.com/php-fig/http-server-handler/tree/1.0.2" + }, + "time": "2023-04-10T20:06:20+00:00" + }, + { + "name": "psr/http-server-middleware", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/c1481f747daaa6a0782775cd6a8c26a1bf4a3829", + "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0 || ^2.0", + "psr/http-server-handler": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side middleware", + "keywords": [ + "http", + "http-interop", + "middleware", + "psr", + "psr-15", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-middleware/issues", + "source": "https://github.com/php-fig/http-server-middleware/tree/1.0.2" + }, + "time": "2023-04-11T06:14:47+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "slim/csrf", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/slimphp/Slim-Csrf.git", + "reference": "179cbcf40ee1d246d4906aefed42d3e62066974b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/Slim-Csrf/zipball/179cbcf40ee1d246d4906aefed42d3e62066974b", + "reference": "179cbcf40ee1d246d4906aefed42d3e62066974b", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "psr/http-factory": "^1.1", + "psr/http-message": "^1.0 || ^2.0", + "psr/http-server-handler": "^1.0", + "psr/http-server-middleware": "^1.0" + }, + "require-dev": { + "phpspec/prophecy": "^1.19", + "phpspec/prophecy-phpunit": "^2.2", + "phpunit/phpunit": "^9.6", + "squizlabs/php_codesniffer": "^3.10" + }, + "type": "library", + "autoload": { + "psr-4": { + "Slim\\Csrf\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "http://joshlockhart.com" + } + ], + "description": "Slim Framework 4 CSRF protection PSR-15 middleware", + "homepage": "https://www.slimframework.com", + "keywords": [ + "csrf", + "framework", + "middleware", + "slim" + ], + "support": { + "issues": "https://github.com/slimphp/Slim-Csrf/issues", + "source": "https://github.com/slimphp/Slim-Csrf/tree/1.5.0" + }, + "time": "2024-06-08T16:37:18+00:00" + }, + { + "name": "slim/flash", + "version": "0.4.0", + "source": { + "type": "git", + "url": "https://github.com/slimphp/Slim-Flash.git", + "reference": "9aaff5fded3b54f4e519ec3d4ac74d3d1f2cbbbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/Slim-Flash/zipball/9aaff5fded3b54f4e519ec3d4ac74d3d1f2cbbbc", + "reference": "9aaff5fded3b54f4e519ec3d4ac74d3d1f2cbbbc", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Slim\\Flash\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "http://joshlockhart.com" + } + ], + "description": "Slim Framework Flash message service provider", + "homepage": "http://slimframework.com", + "keywords": [ + "flash", + "framework", + "message", + "provider", + "slim" + ], + "support": { + "issues": "https://github.com/slimphp/Slim-Flash/issues", + "source": "https://github.com/slimphp/Slim-Flash/tree/master" + }, + "time": "2017-10-22T10:35:05+00:00" + }, + { + "name": "slim/psr7", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/slimphp/Slim-Psr7.git", + "reference": "753e9646def5ff4db1a06e5cf4ef539bfd30f467" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/Slim-Psr7/zipball/753e9646def5ff4db1a06e5cf4ef539bfd30f467", + "reference": "753e9646def5ff4db1a06e5cf4ef539bfd30f467", + "shasum": "" + }, + "require": { + "fig/http-message-util": "^1.1.5", + "php": "^8.0", + "psr/http-factory": "^1.1", + "psr/http-message": "^1.0 || ^2.0", + "ralouphie/getallheaders": "^3.0", + "symfony/polyfill-php80": "^1.29" + }, + "provide": { + "psr/http-factory-implementation": "^1.0", + "psr/http-message-implementation": "^1.0 || ^2.0" + }, + "require-dev": { + "adriansuter/php-autoload-override": "^1.4", + "ext-json": "*", + "http-interop/http-factory-tests": "^1.1.0", + "php-http/psr7-integration-tests": "1.3.0", + "phpspec/prophecy": "^1.19", + "phpspec/prophecy-phpunit": "^2.2", + "phpstan/phpstan": "^1.11", + "phpunit/phpunit": "^9.6", + "squizlabs/php_codesniffer": "^3.10" + }, + "type": "library", + "autoload": { + "psr-4": { + "Slim\\Psr7\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "http://joshlockhart.com" + }, + { + "name": "Andrew Smith", + "email": "a.smith@silentworks.co.uk", + "homepage": "http://silentworks.co.uk" + }, + { + "name": "Rob Allen", + "email": "rob@akrabat.com", + "homepage": "http://akrabat.com" + }, + { + "name": "Pierre Berube", + "email": "pierre@lgse.com", + "homepage": "http://www.lgse.com" + } + ], + "description": "Strict PSR-7 implementation", + "homepage": "https://www.slimframework.com", + "keywords": [ + "http", + "psr-7", + "psr7" + ], + "support": { + "issues": "https://github.com/slimphp/Slim-Psr7/issues", + "source": "https://github.com/slimphp/Slim-Psr7/tree/1.7.0" + }, + "time": "2024-06-08T14:48:17+00:00" + }, + { + "name": "slim/slim", + "version": "4.14.0", + "source": { + "type": "git", + "url": "https://github.com/slimphp/Slim.git", + "reference": "5943393b88716eb9e82c4161caa956af63423913" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/Slim/zipball/5943393b88716eb9e82c4161caa956af63423913", + "reference": "5943393b88716eb9e82c4161caa956af63423913", + "shasum": "" + }, + "require": { + "ext-json": "*", + "nikic/fast-route": "^1.3", + "php": "^7.4 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-factory": "^1.1", + "psr/http-message": "^1.1 || ^2.0", + "psr/http-server-handler": "^1.0", + "psr/http-server-middleware": "^1.0", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "adriansuter/php-autoload-override": "^1.4", + "ext-simplexml": "*", + "guzzlehttp/psr7": "^2.6", + "httpsoft/http-message": "^1.1", + "httpsoft/http-server-request": "^1.1", + "laminas/laminas-diactoros": "^2.17 || ^3", + "nyholm/psr7": "^1.8", + "nyholm/psr7-server": "^1.1", + "phpspec/prophecy": "^1.19", + "phpspec/prophecy-phpunit": "^2.1", + "phpstan/phpstan": "^1.11", + "phpunit/phpunit": "^9.6", + "slim/http": "^1.3", + "slim/psr7": "^1.6", + "squizlabs/php_codesniffer": "^3.10", + "vimeo/psalm": "^5.24" + }, + "suggest": { + "ext-simplexml": "Needed to support XML format in BodyParsingMiddleware", + "ext-xml": "Needed to support XML format in BodyParsingMiddleware", + "php-di/php-di": "PHP-DI is the recommended container library to be used with Slim", + "slim/psr7": "Slim PSR-7 implementation. See https://www.slimframework.com/docs/v4/start/installation.html for more information." + }, + "type": "library", + "autoload": { + "psr-4": { + "Slim\\": "Slim" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "https://joshlockhart.com" + }, + { + "name": "Andrew Smith", + "email": "a.smith@silentworks.co.uk", + "homepage": "http://silentworks.co.uk" + }, + { + "name": "Rob Allen", + "email": "rob@akrabat.com", + "homepage": "http://akrabat.com" + }, + { + "name": "Pierre Berube", + "email": "pierre@lgse.com", + "homepage": "http://www.lgse.com" + }, + { + "name": "Gabriel Manricks", + "email": "gmanricks@me.com", + "homepage": "http://gabrielmanricks.com" + } + ], + "description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs", + "homepage": "https://www.slimframework.com", + "keywords": [ + "api", + "framework", + "micro", + "router" + ], + "support": { + "docs": "https://www.slimframework.com/docs/v4/", + "forum": "https://discourse.slimframework.com/", + "irc": "irc://irc.freenode.net:6667/slimphp", + "issues": "https://github.com/slimphp/Slim/issues", + "rss": "https://www.slimframework.com/blog/feed.rss", + "slack": "https://slimphp.slack.com/", + "source": "https://github.com/slimphp/Slim", + "wiki": "https://github.com/slimphp/Slim/wiki" + }, + "funding": [ + { + "url": "https://opencollective.com/slimphp", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slim/slim", + "type": "tidelift" + } + ], + "time": "2024-06-13T08:54:48+00:00" + }, + { + "name": "slim/twig-view", + "version": "3.4.1", + "source": { + "type": "git", + "url": "https://github.com/slimphp/Twig-View.git", + "reference": "b4268d87d0e327feba5f88d32031e9123655b909" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/Twig-View/zipball/b4268d87d0e327feba5f88d32031e9123655b909", + "reference": "b4268d87d0e327feba5f88d32031e9123655b909", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "psr/http-message": "^1.1 || ^2.0", + "slim/slim": "^4.12", + "symfony/polyfill-php81": "^1.29", + "twig/twig": "^3.11" + }, + "require-dev": { + "phpspec/prophecy-phpunit": "^2.0", + "phpstan/phpstan": "^1.10.59", + "phpunit/phpunit": "^9.6 || ^10", + "psr/http-factory": "^1.0", + "squizlabs/php_codesniffer": "^3.9" + }, + "type": "library", + "autoload": { + "psr-4": { + "Slim\\Views\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "http://joshlockhart.com" + }, + { + "name": "Pierre Berube", + "email": "pierre@lgse.com", + "homepage": "http://www.lgse.com" + } + ], + "description": "Slim Framework 4 view helper built on top of the Twig 3 templating component", + "homepage": "https://www.slimframework.com", + "keywords": [ + "framework", + "slim", + "template", + "twig", + "view" + ], + "support": { + "issues": "https://github.com/slimphp/Twig-View/issues", + "source": "https://github.com/slimphp/Twig-View/tree/3.4.1" + }, + "time": "2024-09-26T05:42:02+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.5.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/605389f2a7e5625f273b53960dc46aeaf9c62918", + "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "2.5-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:11:13+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v5.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "72982eb416f61003e9bb6e91f8b3213600dcf9e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/72982eb416f61003e9bb6e91f8b3213600dcf9e9", + "reference": "72982eb416f61003e9bb6e91f8b3213600dcf9e9", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/event-dispatcher-contracts": "^2|^3", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "symfony/dependency-injection": "<4.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/error-handler": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/http-foundation": "^4.4|^5.0|^6.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/stopwatch": "^4.4|^5.0|^6.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.45" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:11:13+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v2.5.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "e0fe3d79b516eb75126ac6fa4cbf19b79b08c99f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/e0fe3d79b516eb75126ac6fa4cbf19b79b08c99f", + "reference": "e0fe3d79b516eb75126ac6fa4cbf19b79b08c99f", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/event-dispatcher": "^1" + }, + "suggest": { + "symfony/event-dispatcher-implementation": "" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "2.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.5.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:11:13+00:00" + }, + { + "name": "symfony/finder", + "version": "v5.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "63741784cd7b9967975eec610b256eed3ede022b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/63741784cd7b9967975eec610b256eed3ede022b", + "reference": "63741784cd7b9967975eec610b256eed3ede022b", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v5.4.45" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-28T13:32:08+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/yaml", + "version": "v5.4.45", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "a454d47278cc16a5db371fe73ae66a78a633371e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/a454d47278cc16a5db371fe73ae66a78a633371e", + "reference": "a454d47278cc16a5db371fe73ae66a78a633371e", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<5.3" + }, + "require-dev": { + "symfony/console": "^5.3|^6.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v5.4.45" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:11:13+00:00" + }, + { + "name": "twig/twig", + "version": "v3.11.3", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "3b06600ff3abefaf8ff55d5c336cd1c4253f8c7e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/3b06600ff3abefaf8ff55d5c336cd1c4253f8c7e", + "reference": "3b06600ff3abefaf8ff55d5c336cd1c4253f8c7e", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php80": "^1.22", + "symfony/polyfill-php81": "^1.29" + }, + "require-dev": { + "psr/container": "^1.0|^2.0", + "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/Resources/core.php", + "src/Resources/debug.php", + "src/Resources/escaper.php", + "src/Resources/string_loader.php" + ], + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "support": { + "issues": "https://github.com/twigphp/Twig/issues", + "source": "https://github.com/twigphp/Twig/tree/v3.11.3" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2024-11-07T12:34:41+00:00" + }, + { + "name": "vlucas/valitron", + "version": "v1.4.11", + "source": { + "type": "git", + "url": "https://github.com/vlucas/valitron.git", + "reference": "fadce39f5f235755bb9794b2573af2d5bfcba85f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/valitron/zipball/fadce39f5f235755bb9794b2573af2d5bfcba85f", + "reference": "fadce39f5f235755bb9794b2573af2d5bfcba85f", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": ">=4.8.35" + }, + "suggest": { + "ext-mbstring": "It can support the multiple bytes string length." + }, + "type": "library", + "autoload": { + "psr-4": { + "Valitron\\": "src/Valitron" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://www.vancelucas.com" + } + ], + "description": "Simple, elegant, stand-alone validation library with NO dependencies", + "homepage": "https://github.com/vlucas/valitron", + "keywords": [ + "valid", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/vlucas/valitron/issues", + "source": "https://github.com/vlucas/valitron/tree/v1.4.11" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/valitron", + "type": "tidelift" + } + ], + "time": "2022-10-14T11:54:24+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": { + "erusev/parsedown": 20, + "erusev/parsedown-extra": 20 + }, + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": "^8.0" + }, + "platform-dev": [], + "platform-overrides": { + "php": "8.0.0" + }, + "plugin-api-version": "2.3.0" +} diff --git a/content/.yaml b/content/.yaml new file mode 100644 index 0000000..a7e65f7 --- /dev/null +++ b/content/.yaml @@ -0,0 +1,8 @@ +meta: + author: Sebastian + created: '2024-09-10' + time: 12-43-18 + navtitle: null + modified: '2024-08-01' + title: '' + description: '' diff --git a/content/00-getting-started/00-create-your-first-page.md b/content/00-getting-started/00-create-your-first-page.md new file mode 100644 index 0000000..85646bb --- /dev/null +++ b/content/00-getting-started/00-create-your-first-page.md @@ -0,0 +1,15 @@ +# Create Your First Page + +To create a new page in Typemill, follow these simple steps: + +* Use the **interactive navigation** located on the left side of the screen. +* **Enter a page title** for your new page into one of the grey input fields. +* **Click the page icon** to create a new page. You can also create a folder if you want to add sub-pages later. + +Here's a quick breakdown of the options. + +* **Folders**: Use folders to organize your website hierarchically. Folders can contain subfolders or pages. Unlike a traditional file system, folders in Typemill can also have content, similar to pages. +* **Pages**: Utilize pages for simple content without needing a deeper structure. + +Additionally, you can rearrange your pages by dragging and dropping them within the navigation panel. + diff --git a/content/00-getting-started/00-create-your-first-page.yaml b/content/00-getting-started/00-create-your-first-page.yaml new file mode 100644 index 0000000..9446790 --- /dev/null +++ b/content/00-getting-started/00-create-your-first-page.yaml @@ -0,0 +1,12 @@ +meta: + navtitle: 'create your first page' + title: 'Create Your First Page' + description: "To create a new page in Typemill, follow these simple steps: \nUse the interactive navigation located on the left side of the screen. \nEnter a page title for" + owner: trendschau + author: 'Sebastian Schürmanns' + modified: '2024-04-27' + created: '2024-04-25' + time: 17-24-48 + hide: false + noindex: false + template: '' diff --git a/content/00-getting-started/01-edit-your-page.md b/content/00-getting-started/01-edit-your-page.md new file mode 100644 index 0000000..413c5b6 --- /dev/null +++ b/content/00-getting-started/01-edit-your-page.md @@ -0,0 +1,14 @@ +# Edit Your Page + +Typemill uses [Markdown](https://typemill.net/writers/markdown) to create content. But don't worry: The visual editor of Typemill will help you write content in a WYSIWYG-style (what you see is what you get). You can also switch to the [raw-markdown-mode](/tm/content/raw/getting-started/edit-your-page) with the sticky 'raw' button located at the bottom right corner of the editor interface. + +The content is organized in blocks, and you can move each content block up and down with drag & drop. + +- **To edit** a content block, simply click on it. +- **To add** a new block at the end of the page, use the edit buttons below. +- **To add** a new block above this block, use the "add" button that appears when you hover over this block. + +You can add all kinds of content like tables, quotes, images, files, an automatic table of contents (TOC), or YouTube videos. There are also plugins to embed media from other platforms or to use selected HTML tags in content. + +If you are a developer, you can write plugins and integrate nearly everything into the editor with `{::]` shortcodes. + diff --git a/content/00-getting-started/01-edit-your-page.yaml b/content/00-getting-started/01-edit-your-page.yaml new file mode 100644 index 0000000..c4ed1da --- /dev/null +++ b/content/00-getting-started/01-edit-your-page.yaml @@ -0,0 +1,9 @@ +meta: + owner: trendschau + author: 'Sebastian Schürmanns' + created: '2024-04-25' + time: 17-37-50 + navtitle: 'edit your page' + modified: '2024-04-27' + title: 'Edit Your Page' + description: 'Typemill uses Markdown to create content. But don''t worry: The visual editor of Typemill will help you write content in a WYSIWYG-style (what you see is what' diff --git a/content/00-getting-started/02-edit-the-page-meta.md b/content/00-getting-started/02-edit-the-page-meta.md new file mode 100644 index 0000000..ab256a6 --- /dev/null +++ b/content/00-getting-started/02-edit-the-page-meta.md @@ -0,0 +1,13 @@ +# Edit the Page Meta + +You can edit detailed meta-information by switching to the meta-tab at the top of the page. While most of the meta-information should be self-explanatory, let's delve into some details: + +- **Slug**: This is the URL. Changing the slug will alter the URL, potentially affecting indexing in search engines like Google. +- **Navigation Title**: Change the title for the navigation. +- **Meta Content**: Modify standard meta-tags like title and description. The image will be used when pages are shared on social media platforms and may also appear in the frontend, depending on your theme. +- **Author**: The author will appear in the frontend (depending on the theme); the owner has the rights to edit the page. +- **Access Rights**: You can restrict access to the page in the frontend. See the example for an [restricted page here](/tm/content/visual/publish-status/restricted). +- **Date**: The date of creation, last publication, and a manually set date. +- **Reference**: You can create different references for a page, such as a redirect 301 or 302 to another page, copying another page, or linking the page to an external page. These features are demonstrated in the [pages status examples](tm/content/visual/publish-status). +- **Visibility**: Hide a page from the navigation and exclude it from the index with a noindex-tag. These features are demonstrated in the [pages status examples](tm/content/visual/publish-status). + diff --git a/content/00-getting-started/02-edit-the-page-meta.yaml b/content/00-getting-started/02-edit-the-page-meta.yaml new file mode 100644 index 0000000..2b2724e --- /dev/null +++ b/content/00-getting-started/02-edit-the-page-meta.yaml @@ -0,0 +1,9 @@ +meta: + owner: trendschau + author: 'Sebastian Schürmanns' + created: '2024-04-25' + time: 17-48-32 + navtitle: 'edit the page meta' + modified: '2024-04-27' + title: 'Edit the Page Meta' + description: 'You can edit detailed meta-information by switching to the meta-tab at the top of the page. While most of the meta-information should be self-explanatory,' diff --git a/content/00-getting-started/03-publish-your-page.md b/content/00-getting-started/03-publish-your-page.md new file mode 100644 index 0000000..7f79f01 --- /dev/null +++ b/content/00-getting-started/03-publish-your-page.md @@ -0,0 +1,17 @@ +# Publish Your Page + +Is your page ready to go live? Let's publish it! In Typemill, a page can have three different statuses: + +- **Unpublished** (red): The page is not published and not visible in the frontend yet. +- **Published** (green): The page is published and visible in the frontend. +- **Modified** (orange): The page is published and has changes that have not been published yet. + +You can view and manage the status of the page in the sticky publish bar at the bottom. There, you can: + +- **Publish** a page or publish modifications. +- **Discard** modifications and revert content to the published version. +- **Unpublish** a published page. +- **Delete** a page. + +You can also see the status of all pages in the content navigation on the left side, indicated by the colors red, orange, and green. + diff --git a/content/00-getting-started/03-publish-your-page.yaml b/content/00-getting-started/03-publish-your-page.yaml new file mode 100644 index 0000000..88e36dc --- /dev/null +++ b/content/00-getting-started/03-publish-your-page.yaml @@ -0,0 +1,9 @@ +meta: + owner: trendschau + author: 'Sebastian Schürmanns' + created: '2024-04-25' + time: 18-56-38 + navtitle: 'publish your page' + modified: '2024-04-27' + title: 'Publish Your Page' + description: 'Is your page ready to go live? Let''s publish it! In Typemill, a page can have three different statuses:' diff --git a/content/00-getting-started/index.md b/content/00-getting-started/index.md new file mode 100644 index 0000000..429f7df --- /dev/null +++ b/content/00-getting-started/index.md @@ -0,0 +1,8 @@ +# Getting Started with Typemill + +Use this demo-content to familiarize yourself with Typemill. + +Not sure where to start? + +Simply select a topic in the navigation; it will briefly explain what you can do with Typemill. + diff --git a/content/00-getting-started/index.yaml b/content/00-getting-started/index.yaml new file mode 100644 index 0000000..d8898ce --- /dev/null +++ b/content/00-getting-started/index.yaml @@ -0,0 +1,13 @@ +meta: + navtitle: 'getting started' + title: 'Getting Started with Typemill' + description: 'Use this demo-content to familiarize yourself with Typemill. Not sure where to start?' + owner: trendschau + author: 'Sebastian Schürmanns' + modified: '2024-05-17' + created: '2024-04-25' + time: 13-16-58 + hide: false + noindex: false + contains: pages + template: '' diff --git a/content/01-publish-status/00-unpublished.txt b/content/01-publish-status/00-unpublished.txt new file mode 100644 index 0000000..ff08682 --- /dev/null +++ b/content/01-publish-status/00-unpublished.txt @@ -0,0 +1 @@ +["# Unpublished Page","This is an unpublished page. Unpublished pages are marked red in the navigation and in the publish bar."] \ No newline at end of file diff --git a/content/01-publish-status/00-unpublished.yaml b/content/01-publish-status/00-unpublished.yaml new file mode 100644 index 0000000..46a3c80 --- /dev/null +++ b/content/01-publish-status/00-unpublished.yaml @@ -0,0 +1,6 @@ +meta: + owner: typemill + author: '' + created: '2024-03-19' + time: 18-40-21 + navtitle: unpublished diff --git a/content/01-publish-status/01-modified.md b/content/01-publish-status/01-modified.md new file mode 100644 index 0000000..8739c54 --- /dev/null +++ b/content/01-publish-status/01-modified.md @@ -0,0 +1,4 @@ +# Modified Page + +This is a modified page. Modified pages are marked orange in the navigation and in the publish bar. Modified pages are published pages that have been modified in the editor. The published page is still online in the original version. You can publish the mofidifications to make them visible in the frontend, or you can discard the changes to restore the published version. If you discard your changes, then your changes are lost and cannot be restored. + diff --git a/content/01-publish-status/01-modified.txt b/content/01-publish-status/01-modified.txt new file mode 100644 index 0000000..ab719ae --- /dev/null +++ b/content/01-publish-status/01-modified.txt @@ -0,0 +1 @@ +["# Modified Page","This is a modified page. Modified pages are marked orange in the navigation and in the publish bar. Modified pages are published pages that have been modified in the editor. The published page is still online in the original version. You can publish the mofidifications to make them visible in the frontend, or you can discard the changes to restore the published version. If you discard your changes, then your changes are lost and cannot be restored.","A page becomes modified if you either:","* Save a block in the visual editor or\n* Save a draft in the raw editor."] \ No newline at end of file diff --git a/content/01-publish-status/01-modified.yaml b/content/01-publish-status/01-modified.yaml new file mode 100644 index 0000000..8f48d35 --- /dev/null +++ b/content/01-publish-status/01-modified.yaml @@ -0,0 +1,9 @@ +meta: + owner: typemill + author: '' + created: '2024-03-19' + time: 18-40-27 + navtitle: modified + modified: '2024-03-19' + title: 'Modified Page' + description: 'This is a modified page. Modified pages are marked orange in the navigation and in the publish bar. Modified pages are published pages that have been modified' diff --git a/content/01-publish-status/02-published.md b/content/01-publish-status/02-published.md new file mode 100644 index 0000000..a677d6f --- /dev/null +++ b/content/01-publish-status/02-published.md @@ -0,0 +1,4 @@ +# Published Page + +This is a published page. Published pages are marked green in the navigation and in the publish bar. Published pages are visible in the frontend unless they are not restricted. Published pages are visible in the navigation unless they are not hidden. Published pages are added to the xml-sitemap and alowed to crawl unless they are not marked as noindex. + diff --git a/content/01-publish-status/02-published.yaml b/content/01-publish-status/02-published.yaml new file mode 100644 index 0000000..a7bd551 --- /dev/null +++ b/content/01-publish-status/02-published.yaml @@ -0,0 +1,9 @@ +meta: + owner: typemill + author: '' + created: '2024-03-19' + time: 18-40-40 + navtitle: published + modified: '2024-03-19' + title: 'Published Page' + description: 'This is a published page. Published pages are marked green in the navigation and in the publish bar. Published pages are visible in the frontend unless they' diff --git a/content/01-publish-status/03-hidden.md b/content/01-publish-status/03-hidden.md new file mode 100644 index 0000000..63123e5 --- /dev/null +++ b/content/01-publish-status/03-hidden.md @@ -0,0 +1,6 @@ +# Hidden Page + +This is a hidden page. Hidden pages are published pages that are excluded from all navigation elements. You need to know the url to visit a hidden page. Hidden pages are great if you want to check your page in the frontend without beeing visible for visitors directly. Hidden pages are not excluded from the xml-sitemap and they do not have a noindex attribute. + +You can hide pages with a checkbox in the meta-tab (sroll to the bottom). + diff --git a/content/01-publish-status/03-hidden.yaml b/content/01-publish-status/03-hidden.yaml new file mode 100644 index 0000000..a903d48 --- /dev/null +++ b/content/01-publish-status/03-hidden.yaml @@ -0,0 +1,11 @@ +meta: + navtitle: hidden + title: 'Hidden Page' + description: 'This is a hidden page. Hidden pages are published pages that are excluded from all navigation elements. You need to know the url to visit a hidden page. Hidden' + owner: typemill + author: '' + modified: '2024-03-19' + created: '2024-03-19' + time: 18-49-34 + hide: true + noindex: false diff --git a/content/01-publish-status/04-noindex.md b/content/01-publish-status/04-noindex.md new file mode 100644 index 0000000..6ef52c0 --- /dev/null +++ b/content/01-publish-status/04-noindex.md @@ -0,0 +1,4 @@ +# Noindex Page + +This page is excluded from the xml-sitemap (see url of the xml-sitemap in the system settings) and the page has a noindex-attribute to stop robots from crawling and indexing. You can activate the noindex in the meta-tab (scroll to the bottom). + diff --git a/content/01-publish-status/04-noindex.yaml b/content/01-publish-status/04-noindex.yaml new file mode 100644 index 0000000..dd21e42 --- /dev/null +++ b/content/01-publish-status/04-noindex.yaml @@ -0,0 +1,11 @@ +meta: + navtitle: noindex + title: 'Noindex Page' + description: 'This page is excluded from the xml-sitemap (see url of the xml-sitemap in the system settings) and the page has a noindex-attribute to stop robots from' + owner: typemill + author: '' + modified: '2024-03-19' + created: '2024-03-19' + time: 18-54-12 + noindex: true + hide: false diff --git a/content/01-publish-status/05-restricted.md b/content/01-publish-status/05-restricted.md new file mode 100644 index 0000000..9dd4721 --- /dev/null +++ b/content/01-publish-status/05-restricted.md @@ -0,0 +1,11 @@ +# Restricted Page + +This is a restricted page (to activate the feature please read the paragraph below). Restricted pages are published pages that require an authentication to see the content. You can cut the content with a hr-line, everything below this line is not visible in frontend for unauthenticated users. + +--- + +You can configure page restrictions in the system settings. Open the tab "restrictions" and choose the options. You can restrict the whole website or you can restrict certain pages. To restrict certain pages, go to the meta-tab of a page and scroll down to the section "Access & Rights". There you have two options: + +* You can choose a userrole that should have access to the content. +* Or you can add one or several usernames who should have access to the content. + diff --git a/content/01-publish-status/05-restricted.yaml b/content/01-publish-status/05-restricted.yaml new file mode 100644 index 0000000..645a236 --- /dev/null +++ b/content/01-publish-status/05-restricted.yaml @@ -0,0 +1,12 @@ +meta: + navtitle: restricted + owner: typemill + author: '' + allowedrole: member + created: '2024-03-19' + time: 18-56-07 + hide: false + noindex: false + modified: '2024-03-19' + title: 'Restricted Page' + description: 'This is a restricted page. Restricted pages are published pages that require an authentication to see the content. You can cut the content with a hr-line,' diff --git a/content/01-publish-status/06-redirect-301.md b/content/01-publish-status/06-redirect-301.md new file mode 100644 index 0000000..ddb3775 --- /dev/null +++ b/content/01-publish-status/06-redirect-301.md @@ -0,0 +1,4 @@ +# Redirect 301 + +This page redirects to the page "published" with a 301 permanent redirect. You can choose this option in the meta-tab, section "References". + diff --git a/content/01-publish-status/06-redirect-301.yaml b/content/01-publish-status/06-redirect-301.yaml new file mode 100644 index 0000000..56e9d71 --- /dev/null +++ b/content/01-publish-status/06-redirect-301.yaml @@ -0,0 +1,13 @@ +meta: + navtitle: 'redirect 301' + owner: typemill + author: '' + created: '2024-03-19' + time: 19-00-58 + reference: /publish-status/published + referencetype: redirect301 + hide: false + noindex: false + modified: '2024-03-19' + title: 'Redirect 301' + description: 'This page redirects to another page with a 301 permanent redirect. You can choose this option in the meta-tab, section "References".' diff --git a/content/01-publish-status/07-redirect-302.md b/content/01-publish-status/07-redirect-302.md new file mode 100644 index 0000000..bc4af54 --- /dev/null +++ b/content/01-publish-status/07-redirect-302.md @@ -0,0 +1,4 @@ +# Redirect 302 + +This page redirects to the page "published" with a 302 temporary redirect. You can choose this option in the meta-tab, section "References". + diff --git a/content/01-publish-status/07-redirect-302.yaml b/content/01-publish-status/07-redirect-302.yaml new file mode 100644 index 0000000..b4e79bb --- /dev/null +++ b/content/01-publish-status/07-redirect-302.yaml @@ -0,0 +1,13 @@ +meta: + navtitle: 'redirect 302' + owner: typemill + author: '' + created: '2024-03-19' + time: 19-02-36 + reference: /publish-status/published + referencetype: redirect302 + hide: false + noindex: false + modified: '2024-03-19' + title: 'Redirect 302' + description: 'This page redirects to the published page with a 302 temporary redirect. You can choose this option in the meta-tab, section "References".' diff --git a/content/01-publish-status/08-copy.md b/content/01-publish-status/08-copy.md new file mode 100644 index 0000000..792c00d --- /dev/null +++ b/content/01-publish-status/08-copy.md @@ -0,0 +1,4 @@ +# Copy + +You can copy the content of another page to this page. This is helpful in some cases if you want to reference the content in several other navigation points on the website. You can edit the referenced page and all referenced sites will show that content. You can copy the page of another page in the meta-tab, section "references". + diff --git a/content/01-publish-status/08-copy.yaml b/content/01-publish-status/08-copy.yaml new file mode 100644 index 0000000..aab883a --- /dev/null +++ b/content/01-publish-status/08-copy.yaml @@ -0,0 +1,13 @@ +meta: + navtitle: copy + owner: typemill + author: '' + created: '2024-03-19' + time: 19-03-54 + reference: /publish-status/published + referencetype: copy + hide: false + noindex: false + modified: '2024-03-19' + title: Copy + description: 'You can copy the content of another page to this page. This is helpful in some cases if you want to reference the content in several other navigation points on' diff --git a/content/01-publish-status/09-link.md b/content/01-publish-status/09-link.md new file mode 100644 index 0000000..b723174 --- /dev/null +++ b/content/01-publish-status/09-link.md @@ -0,0 +1,4 @@ +# External link + +You can link to an external page in the navigation with the reference feature in the meta-tab. Just choose "link" and add the url to the external page. + diff --git a/content/01-publish-status/09-link.yaml b/content/01-publish-status/09-link.yaml new file mode 100644 index 0000000..bb49554 --- /dev/null +++ b/content/01-publish-status/09-link.yaml @@ -0,0 +1,13 @@ +meta: + navtitle: link + title: 'External link' + description: 'You can link to an external page in the navigation with the reference feature in the meta-tab. Just choose "link" and add the url to the external page.' + owner: typemill + author: '' + modified: '2024-03-19' + created: '2024-03-19' + time: 19-07-05 + reference: 'https://typemill.net' + referencetype: outlink + hide: false + noindex: false diff --git a/content/01-publish-status/index.md b/content/01-publish-status/index.md new file mode 100644 index 0000000..50ddabf --- /dev/null +++ b/content/01-publish-status/index.md @@ -0,0 +1,14 @@ +# Publish Status + +In Typemill, you can save drafts, publish pages, unpublish pages, and delete pages using a sticky publish panel at the bottom of each page. For published pages, you can also save modifications, discard modifications, and publish modifications. + +The current status of the page is indicated with colors in the publish panel and in the navigation. This way, an author can always see the status of each page and the current state of the whole website. + +- **Save a draft**: The page is not published and not accessible on the frontend. The status color is red. +- **Publish a page**: The page is published and accessible on the frontend. The status color is green. +- **Unpublish a page**: The page is not accessible on the frontend and only accessible in the author interface. The status color is red again. +- **Delete a page**: The page is completely deleted from the frontend and the author interface. +- **Save modifications**: The page is published and accessible on the frontend. The modified draft is only visible in the author interface. The status color is orange. +- **Discard modifications**: The modifications are deleted, and the live version is restored in the author interface. The status color returns to green. +- **Publish modifications**: The modifications are published to the live version. The status color returns to green. + diff --git a/content/01-publish-status/index.yaml b/content/01-publish-status/index.yaml new file mode 100644 index 0000000..2e9f9e0 --- /dev/null +++ b/content/01-publish-status/index.yaml @@ -0,0 +1,12 @@ +meta: + navtitle: 'page status' + title: 'Publish Status' + description: 'In Typemill you can save drafts, publish pages, unpublish pages, and delete pages with a sticky publish panel at the bottom of each page. For published pages,' + owner: typemill + author: '' + modified: '2024-03-19' + created: '2024-03-19' + time: 18-40-10 + contains: pages + hide: false + noindex: false diff --git a/content/02-news/202405171834-fast-websites.md b/content/02-news/202405171834-fast-websites.md new file mode 100644 index 0000000..e558abf --- /dev/null +++ b/content/02-news/202405171834-fast-websites.md @@ -0,0 +1,22 @@ +# Fast Websites + +Typemill is a lightweight, flat-file CMS specifically designed for efficient web publishing. It excels in creating documentation, manuals, knowledge bases, wikis, and eBooks. Its performance-driven architecture ensures rapid page loading and a streamlined user experience. + +## Performance Features + +1. **Full Points for Google Page Speed**: Typemill websites typically achieve perfect scores on Google PageSpeed Insights without additional optimizations. This is due to its optimized code, lightweight architecture, and refined frontend code. +2. **SEO Benefits**: The excellent performance makes Typemill a great choice for SEO. Additionally, it offers an SEO plugin that integrates data from Google Search Console, providing valuable insights for optimizing content. +3. **Efficient Workflow**: With its simple approach and excellent user experience, website managers and authors can accomplish tasks quickly and efficiently. + +## Benefits + +- **Quick Setup and Maintenance**: Typemill's straightforward installation and lack of a database simplify setup and maintenance. +- **Scalability**: The flat-file approach ensures consistent performance even as the site grows. +- **SEO Friendly**: Fast loading times and SEO tools contribute to better search engine rankings, improving visibility. + +## Conclusion + +Typemill is an ideal choice for small to medium-sized websites focused on documentation and knowledge management. Its flat-file structure, lightweight design, and optimized performance make it a robust solution for fast and reliable web publishing. + +For more information, visit [Typemill](https://typemill.net). + diff --git a/content/02-news/202405171834-fast-websites.yaml b/content/02-news/202405171834-fast-websites.yaml new file mode 100644 index 0000000..24cfb69 --- /dev/null +++ b/content/02-news/202405171834-fast-websites.yaml @@ -0,0 +1,12 @@ +meta: + navtitle: 'Fast Websites' + title: 'Fast Websites' + description: 'Typemill is a lightweight, flat-file CMS specifically designed for efficient web publishing. It excels in creating documentation, manuals, knowledge bases,' + heroimage: '' + owner: trendschau + author: 'Sebastian Schürmanns' + modified: '2024-05-17' + created: '2024-05-17' + time: 18-34-46 + hide: false + noindex: false diff --git a/content/02-news/202405171843-reports-and-handbooks.md b/content/02-news/202405171843-reports-and-handbooks.md new file mode 100644 index 0000000..46805c0 --- /dev/null +++ b/content/02-news/202405171843-reports-and-handbooks.md @@ -0,0 +1,33 @@ +# Reports and Handbooks + +Small organizations often face the challenge of producing and publishing annual reports, impact reports, and other essential documents efficiently. Typemill offers an excellent solution by enabling users to create content once and publish it both as a website and a PDF. This not only saves time but also reduces costs, making it an ideal choice for small organizations. + +## Save Time: Write Once, Publish Twice + +One of the standout features of Typemill is its ability to streamline the reporting process. By creating your report in Typemill, you can effortlessly generate both a web-based version and a PDF version from the same source. This dual-publishing capability is particularly beneficial for: + +- **Annual Reports**: Showcase your organization's yearly achievements online and provide a downloadable PDF for stakeholders. +- **Impact Reports**: Highlight the impact of your projects with an engaging web presentation and a formal PDF document. + +## Cost-Effective Solution + +Using Typemill helps small organizations save money by minimizing the need for separate tools and platforms for web and print publishing. The simple and intuitive interface ensures that staff can quickly learn and use the system, further reducing training and operational costs. + +## Versatile Use Cases + +Beyond reporting, Typemill is perfect for creating various handbooks and company documents. Some additional use cases include: + +- **Employee Handbooks**: Develop comprehensive, web-based employee manuals that are easy to update and accessible to all staff members. +- **Company Policies and Procedures**: Maintain a centralized repository of policies and procedures that can be viewed online and downloaded as needed. +- **Training Manuals**: Create detailed training materials that are available both as interactive web pages and printable PDFs. + +## Templates for Speed + +Typemill allows you to load pre-made reporting templates and store your own templates to further speed up the reporting process. This feature ensures consistency and saves time, enabling you to focus more on content creation and less on formatting. + +## Conclusion + +For small organizations and companies looking to optimize their reporting and documentation processes, Typemill offers an efficient, cost-effective solution. Its ability to produce both web and PDF versions of reports from a single source not only saves time but also ensures consistency across formats. Additionally, its versatility in creating various handbooks and company documents makes it an invaluable tool for any small organization. + +Explore more about Typemill and how it can benefit your organization at [Typemill](https://typemill.net). + diff --git a/content/02-news/202405171843-reports-and-handbooks.yaml b/content/02-news/202405171843-reports-and-handbooks.yaml new file mode 100644 index 0000000..bbdcdc4 --- /dev/null +++ b/content/02-news/202405171843-reports-and-handbooks.yaml @@ -0,0 +1,12 @@ +meta: + navtitle: 'Reports and Handbooks' + title: 'Reports and Handbooks' + description: 'Small organizations often face the challenge of producing and publishing annual reports, impact reports, and other essential documents efficiently. Typemill' + heroimage: '' + owner: trendschau + author: 'Sebastian Schürmanns' + modified: '2024-05-17' + created: '2024-05-17' + time: 18-43-57 + hide: false + noindex: false diff --git a/content/02-news/202405171855-documentations-and-manuals.md b/content/02-news/202405171855-documentations-and-manuals.md new file mode 100644 index 0000000..2f5ff5d --- /dev/null +++ b/content/02-news/202405171855-documentations-and-manuals.md @@ -0,0 +1,18 @@ +# Documentations and Manuals + +Small companies often need to create comprehensive documentation and manuals for both digital and physical products. Typemill offers an ideal solution, allowing you to effortlessly produce manuals as both PDFs and websites. + +## Intuitive Authoring Experience + +Typemill is designed with non-technical writers in mind, featuring an intuitive author interface and a WYSIWYG-style Markdown editor. This makes it easy for anyone to create and maintain high-quality documentation. + +## Versatility and Efficiency + +Whether you’re documenting a software application or a physical product, Typemill enables you to manage your content efficiently and publish it in multiple formats. This versatility ensures that your documentation is always accessible and up-to-date. + +### Conclusion + +Typemill stands out among many tools for documentation and manuals due to its ease of use and powerful features. It's the perfect choice for small companies looking to streamline their documentation process. + +Explore more about Typemill and how it can benefit your organization at [Typemill](https://typemill.net). + diff --git a/content/02-news/202405171855-documentations-and-manuals.yaml b/content/02-news/202405171855-documentations-and-manuals.yaml new file mode 100644 index 0000000..fa97b72 --- /dev/null +++ b/content/02-news/202405171855-documentations-and-manuals.yaml @@ -0,0 +1,12 @@ +meta: + navtitle: 'Documentations and Manuals' + title: 'Documentations and Manuals' + description: 'Small companies often need to create comprehensive documentation and manuals for both digital and physical products. Typemill offers an ideal solution,' + heroimage: media/live/favicon-180x180.png + owner: trendschau + author: 'Sebastian Schürmanns' + modified: '2024-05-17' + created: '2024-05-17' + time: 18-55-26 + hide: false + noindex: false diff --git a/content/02-news/index.md b/content/02-news/index.md new file mode 100644 index 0000000..a96e883 --- /dev/null +++ b/content/02-news/index.md @@ -0,0 +1,4 @@ +# News or Blog + +You can add one or many news- or blog-sections to your website. Simply create a new folder and change the content of the folder from "pages" to "posts" in the meta-tab. You can also transform a folder with pages into a folder with posts and vice versa. The only difference between posts and pages is the sorting: Pages are sorted alphabetically, while posts are sorted by the publish date. Posts are visible as a list. They are not included in the navigation on the left side. + diff --git a/content/02-news/index.yaml b/content/02-news/index.yaml new file mode 100644 index 0000000..2b83d12 --- /dev/null +++ b/content/02-news/index.yaml @@ -0,0 +1,12 @@ +meta: + navtitle: news + title: 'Blog or News' + description: 'You can add one or many blog- or news-sections to your website. Just create a new folder and change the content of the folder from "pages" to "posts" in the' + owner: typemill + author: '' + modified: '2024-03-19' + created: '2024-03-19' + time: 19-12-31 + hide: false + noindex: false + contains: posts diff --git a/content/index.md b/content/index.md new file mode 100644 index 0000000..f89146c --- /dev/null +++ b/content/index.md @@ -0,0 +1,6 @@ +# Typemill + +Typemill is a lightweight, flat-file CMS designed for simple, fast, and flexible website and eBook creation using Markdown. Create handbooks, documentation, manuals, reports, traditional websites, online novels, and more. + +Stay in the loop and subscribe to the [Typemill newsletter](https://typemill.net/news)! + diff --git a/cypress.json b/cypress.json new file mode 100644 index 0000000..264e36e --- /dev/null +++ b/cypress.json @@ -0,0 +1,3 @@ +{ + "baseUrl": "http://localhost/EDIT_YOUR_LOCAL_BASE_URL_HERE" +} \ No newline at end of file diff --git a/cypress/fixtures/01_setup/default-settings.yaml b/cypress/fixtures/01_setup/default-settings.yaml new file mode 100644 index 0000000..1957802 --- /dev/null +++ b/cypress/fixtures/01_setup/default-settings.yaml @@ -0,0 +1,3 @@ +setup: true +language: en +welcome: true diff --git a/cypress/fixtures/01_setup/prepulate_settings_seed/settings/settings.yaml b/cypress/fixtures/01_setup/prepulate_settings_seed/settings/settings.yaml new file mode 100644 index 0000000..71492b7 --- /dev/null +++ b/cypress/fixtures/01_setup/prepulate_settings_seed/settings/settings.yaml @@ -0,0 +1,51 @@ +setup: false +language: en +welcome: false +title: TYPEMILL +author: Unknown +copyright: © +year: "2022" +langattr: en +editor: visual +formats: + - markdown + - headline + - ulist + - olist + - table + - quote + - notice + - image + - video + - file + - toc + - hr + - definition + - code + - shortcode +access: null +pageaccess: null +hrdelimiter: null +restrictionnotice: "" +wraprestrictionnotice: null +headlineanchors: null +displayErrorDetails: null +twigcache: null +proxy: null +trustedproxies: "" +headersoff: null +urlschemes: "" +svg: null +recoverpw: null +recoverfrom: "" +recoversubject: "" +recovermessage: "" +securitylog: null +oldslug: null +refreshcache: null +pingsitemap: null +images: + live: + width: "820" +logo: "" +favicon: "" diff --git a/cypress/fixtures/01_setup/prepulate_settings_seed/settings/users/trendschau.yaml b/cypress/fixtures/01_setup/prepulate_settings_seed/settings/users/trendschau.yaml new file mode 100644 index 0000000..d76b8c3 --- /dev/null +++ b/cypress/fixtures/01_setup/prepulate_settings_seed/settings/users/trendschau.yaml @@ -0,0 +1,9 @@ +username: trendschau +email: trendschau@gmail.com +userrole: administrator +password: $2y$10$SMeVwJeo/5vyjeI68uFi.OJZhSR.3KQTVb5zcKF5D65eZ8CDpX29. +lastlogin: 1646578390 +image: '' +description: '' +firstname: Sebastian +lastname: Schürmanns diff --git a/cypress/integration/01_setup/01-system-setup-signup.spec.js b/cypress/integration/01_setup/01-system-setup-signup.spec.js new file mode 100644 index 0000000..c8ed308 --- /dev/null +++ b/cypress/integration/01_setup/01-system-setup-signup.spec.js @@ -0,0 +1,165 @@ +describe("Typemill Setup with Signup", function () { + before(function () { + // reset users and settings + cy.task("resetSetup"); + }); + + it("validates form input", function () { + // visit setup form + cy.visit("/setup"); + // cy.visit('/setup',{ onBeforeLoad: (_contentWindow) => { Object.defineProperty(navigator, 'language', { value: 'fr-FR' }) } }) + cy.url().should("include", "/setup"); + + // add data and check attributes + cy.get('input[name="username"]') + .type("?1") + .should("have.value", "?1") + .and("have.attr", "required"); + + cy.get('input[name="email"]') + .type("trendschau.net") + .should("have.value", "trendschau.net") + .and("have.attr", "required"); + + cy.get('input[name="password"]') + .type("pass") + .should("have.value", "pass") + .and("have.attr", "required"); + + // submit and get validation errors + cy.get("form").submit(); + cy.get("#flash-message").should( + "contain", + "Please check your input and try again" + ); + cy.get(".error").should("contain", "invalid characters"); + cy.get(".error").should("contain", "e-mail is invalid"); + cy.get(".error").should("contain", "Length between 5 - 20"); + }); + + /* + it('fails without CSRF-token', function () + { + cy.request({ + method: 'POST', + url: '/setup', // baseUrl is prepended to url + form: true, // indicates the body should be form urlencoded and sets Content-Type: application/x-www-form-urlencoded headers + failOnStatusCode: false, + body: { + username: 'trendschau', + email: 'trendschau@gmail.com', + password: 'password' + } + }) + .its('body') + .should('include', 'The form has a timeout') + }) +*/ + it("fails without CSRF-token", function () { + cy.visit("/setup"); + + // enter correct data + cy.get('input[name="username"]').clear().type("trendschau"); + cy.get('input[name="email"]').clear().type("trendschau@gmail.com"); + cy.get('input[name="password"]').clear().type("password"); + cy.get("#csrf_value").then((elem) => { + elem.val("wrongvalue"); + }); + + // submit and get validation errors + cy.get("form").submit(); + cy.get("#flash-message").should("contain", "form has a timeout"); + }); + + it("submits valid form data and visit welcome and settings page", function () { + cy.visit("/setup"); + + // enter correct data + cy.get('input[name="username"]').clear().type("trendschau"); + cy.get('input[name="email"]').clear().type("trendschau@gmail.com"); + cy.get('input[name="password"]').clear().type("password"); + + // submits valid form + cy.get("form").submit(); + cy.url().should("include", "/welcome"); + cy.getCookie("typemill-session").should("exist"); + Cypress.Cookies.preserveOnce("typemill-session"); + + // clicks link on welcome page to settings page + // cy.get('.button').should('contain', 'Configure your website') + cy.get(".button").click(); + cy.url().should("include", "/tm/settings"); + }); + + it("creates default settings data", function () { + cy.get('input[name="settings[title]"]') + .should("have.value", "TYPEMILL") + .and("have.attr", "required"); + cy.get('input[name="settings[author]"]'); + cy.get('select[name="settings[copyright]"]'); + cy.get('input[name="settings[year]"]').should("have.attr", "required"); + cy.get('select[name="settings[language]"]'); + // cy.get('select[name="settings[langattr]"]') + cy.get('input[name="settings[sitemap]"]') + .should("have.value", `${Cypress.config().baseUrl}/cache/sitemap.xml`) + .and("have.attr", "readonly"); + cy.get('input[name="settings[logo]"]'); + cy.get('input[name="settings[deletelogo]"]'); + cy.get('input[name="settings[favicon]"]'); + cy.get('input[name="settings[deletefav]"]'); + cy.get('input[name="settings[headlineanchors]"]'); + cy.get('input[name="settings[editor]"]'); + + cy.get('select[name="settings[language]"]') + .select("en") + .should("have.value", "en"); + + cy.get("form").submit(); + cy.get("#flash-message").should("contain", "Settings are stored"); + + Cypress.Cookies.preserveOnce("typemill-session"); + }); + + it("creates default user data", function () { + cy.visit("/tm/user/trendschau"); + cy.url().should("include", "/tm/user/trendschau"); + cy.get('input[name="user[username]"]').should("have.value", "trendschau"); + cy.get('input[name="user[firstname]"]') + .clear() + .type("Sebastian") + .should("have.value", "Sebastian"); + cy.get('input[name="user[lastname]"]') + .clear() + .type("Schürmanns") + .should("have.value", "Schürmanns"); + cy.get('input[name="user[email]"]').should( + "have.value", + "trendschau@gmail.com" + ); + cy.get('select[name="user[userrole]"]').should( + "have.value", + "administrator" + ); + cy.get('input[name="user[password]"]').should("have.value", ""); + cy.get('input[name="user[newpassword]"]').should("have.value", ""); + + cy.get("#userform").submit(); + cy.get("#flash-message").should("contain", "Saved all changes"); + }); + + it("logouts out", function () { + // visits logout link + cy.visit("/tm/logout"); + cy.url().should("include", "/tm/login"); + + // tries to open setup form again and gets redirected to login + cy.visit("/setup"); + cy.url().should("include", "/login"); + }); + + it("redirects when tries to setup again", function () { + // tries to open setup form again and gets redirected to login + cy.visit("/setup"); + cy.url().should("include", "/login"); + }); +}); diff --git a/cypress/integration/01_setup/02-initial-frontend.spec.js b/cypress/integration/01_setup/02-initial-frontend.spec.js new file mode 100644 index 0000000..1d54d5a --- /dev/null +++ b/cypress/integration/01_setup/02-initial-frontend.spec.js @@ -0,0 +1,155 @@ +describe("Typemill Initial Frontend", function () { + before(function () { + cy.task("prepopulateSetup"); + }); + it("has startpage with navigation", function () { + /* visit homepage */ + cy.visit("/"); + + /* has startpage with headline */ + cy.get("h1").contains("Typemill"); + + /* has start and setup button */ + cy.get("nav") + .find("a") + .should(($a) => { + expect($a).to.have.length(11); + expect($a[0].href).to.match(/welcome/); + expect($a[1].href).to.match(/welcome\/setup-your-website/); + expect($a[2].href).to.match(/welcome\/manage-access/); + expect($a[3].href).to.match(/welcome\/write-content/); + expect($a[4].href).to.match(/welcome\/get-help/); + expect($a[5].href).to.match(/welcome\/markdown-test/); + expect($a[6].href).to.match(/cyanine-theme/); + expect($a[7].href).to.match(/cyanine-theme\/landingpage/); + expect($a[8].href).to.match(/cyanine-theme\/colors-and-fonts/); + expect($a[9].href).to.match(/cyanine-theme\/footer/); + expect($a[10].href).to.match(/cyanine-theme\/content-elements/); + }); + }); + + it("has error page", function () { + cy.request({ + url: "/error", + failOnStatusCode: false, + }).then((resp) => { + /* should return 404 not found */ + expect(resp.status).to.eq(404); + }); + + cy.visit("/error", { failOnStatusCode: false }); + cy.url().should("include", "/error"); + + cy.get("h1").contains("Not Found"); + }); + + it("has no access to cache files", function () { + cy.request({ + url: "/cache/structure.txt", + failOnStatusCode: false, + }).then((resp) => { + // redirect status code is 302 + expect(resp.status).to.eq(403); + }); + }); + + it("has no access to dashboard", function () { + cy.visit("/tm/settings"); + cy.url().should("include", "/tm/login"); + }); + + it("has proper markdown test page", function () { + cy.visit("/welcome/markdown-test"); + cy.url().should("include", "/welcome/markdown-test"); + + /* has navigation element */ + cy.get("nav").should("exist"); + + /* check if toc exists */ + cy.get(".TOC").within(($toc) => { + /* check if a certain link in toc exists */ + cy.get("a").eq(2).should("have.attr", "href", "#h-headlines"); + }); + + /* check if corresponding anchor exists */ + cy.get("#h-headlines").should("exist"); + + /* soft linebreaks */ + cy.get("br").should("exist"); + + /* emphasis */ + cy.get("em").should("exist"); + + /* strong */ + cy.get("strong").should("exist"); + + /* ordered list */ + cy.get("ol").should("exist"); + + /* linebreak */ + cy.get("hr").should("exist"); + + /* links exists? hard to test, any idea? We need to wrap it in a div... */ + + /* images */ + cy.get("img").eq(0).should("have.attr", "alt", "alt"); + cy.get("img") + .eq(0) + .should("have.attr", "src") + .should("include", "media/files/markdown.png"); + cy.get("figure") + .eq(2) + .should("have.id", "myid") + .and("have.class", "otherclass"); + cy.get("img") + .eq(2) + .should("have.attr", "alt", "alt-text") + .and("have.attr", "title", "my title") + .and("have.attr", "width", "150px"); + + /* blockquote */ + cy.get("blockquote").should("exist"); + + /* has navigation element */ + cy.get(".notice1").should("exist"); + cy.get(".notice2").should("exist"); + cy.get(".notice3").should("exist"); + + /* footnote */ + cy.get("sup").eq(0).should("have.id", "fnref1:1"); + cy.get("sup") + .eq(0) + .within(($sup) => { + cy.get("a") + .eq(0) + .should("have.attr", "href", "#fn%3A1") + .and("have.class", "footnote-ref"); + }); + + /* abbreviation */ + cy.get("abbr").should("exist"); + + /* definition list */ + cy.get("dl").should("exist"); + + /* table */ + cy.get("table").should("exist"); + + /* code */ + cy.get("pre").should("exist"); + cy.get("code").should("exist"); + + /* math */ + cy.get(".math").should("exist"); + + /* footnote end */ + cy.get(".footnotes").within(($footnotes) => { + cy.get("li").eq(0).should("have.id", "fn:1"); + cy.get("a") + .eq(0) + .should("have.class", "footnote-backref") + .and("have.attr", "href", "#fnref1%3A1") + .and("have.attr", "rev", "footnote"); + }); + }); +}); diff --git a/cypress/integration/03-system-settings.spec.js b/cypress/integration/03-system-settings.spec.js new file mode 100644 index 0000000..b7943b5 --- /dev/null +++ b/cypress/integration/03-system-settings.spec.js @@ -0,0 +1,97 @@ +describe("Typemill System Settings", function () { + before(function () { + cy.loginTypemill(); + + cy.visit("/tm/settings"); + cy.url().should("include", "/tm/settings"); + }); + + beforeEach(function () { + Cypress.Cookies.preserveOnce("typemill-session"); + }); + + after(function () { + cy.logoutTypemill(); + }); + + it("validates the form", function () { + // fill out valid data + cy.get('input[name="settings[title]"]') + .clear() + .type("Cypress a").eq(0).click(); + + /* Check dublicates cannot be made */ + + /* Check new page can be created */ + cy.get(".addNaviForm").within((naviform) => { + /* add Testpage into input */ + cy.get("input").clear().type("Testpage").should("have.value", "Testpage"); + + cy.get(".b-left").click(); + }); + + /* get Navilist */ + cy.get(".navi-list") + .should("contain", "Testpage") + .eq(2) + .find("a") + .should((a) => { + expect(a).to.have.length(6); + expect(a[5].href).to.include("/welcome/testpage"); + }); + }); + + it("edits default content", function () { + cy.visit("/tm/content/visual/welcome/testpage"); + cy.url().should("include", "/tm/content/visual/welcome/testpage"); + + cy.get("#blox").within((blox) => { + /* Change Title */ + cy.get("#blox-0").click(); + cy.get("input").clear().type("This is my Testpage"); + + cy.get(".edit").click(); + cy.get("#blox-0").should("contain", "This is my Testpage"); + + /* Change Text */ + cy.get("#blox-1").click(); + cy.get("textarea") + .clear() + .type("This is the new paragraph for the first line with some text."); + + cy.get(".edit").click(); + cy.get("#blox-1").should("contain", "new paragraph"); + }); + }); + + it("edits paragraph", function () { + cy.get("#blox").within((blox) => { + /* Get Format Bar */ + cy.get(".format-bar").within((formats) => { + /* Edit Table */ + cy.get("button").eq(0).click(); + cy.get("textarea").type("This is a second paragraph."); + + /* save table */ + cy.get(".edit").click(); + cy.get(".cancel").click(); + }); + + cy.get("#blox-2").should("contain", "second paragraph"); + }); + }); + + it("edits headline", function () { + cy.get("#blox").within((blox) => { + /* Get Format Bar */ + cy.get(".format-bar").within((formats) => { + /* Edit Table */ + cy.get("button").eq(1).click(); + cy.get("input").type("Second Level Headline"); + + /* save block */ + cy.get(".edit").click(); + + /* close new standard textarea */ + cy.get(".cancel").click(); + }); + + cy.get("#blox-3").should("contain", "Second Level Headline"); + }); + }); + + it("edits unordered list", function () { + cy.get("#blox").within((blox) => { + /* Get Format Bar */ + cy.get(".format-bar").within((formats) => { + /* Edit Table */ + cy.get("button").eq(2).click(); + cy.get("textarea").type("first list item{enter}second list item"); + + /* save block */ + cy.get(".edit").click(); + + /* close new standard textarea */ + cy.get(".cancel").click(); + }); + + cy.get("#blox-4").within((block) => { + cy.get("li").should((lis) => { + expect(lis).to.have.length(2); + expect(lis.eq(0)).to.contain("first list item"); + }); + }); + }); + }); + + it("edits ordered list", function () { + cy.get("#blox").within((blox) => { + /* Get Format Bar */ + cy.get(".format-bar").within((formats) => { + /* Edit Table */ + cy.get("button").eq(3).click(); + cy.get("textarea").type("first ordered item{enter}second ordered item"); + + /* save block */ + cy.get(".edit").click(); + + /* close new standard textarea */ + cy.get(".cancel").click(); + }); + + cy.get("#blox-5").within((block) => { + cy.get("li").should((lis) => { + expect(lis).to.have.length(2); + expect(lis.eq(0)).to.contain("first ordered item"); + }); + }); + }); + }); + + it("edits table", function () { + cy.get("#blox").within((blox) => { + /* Get Format Bar */ + cy.get(".format-bar").within((formats) => { + /* Edit Table */ + cy.get("button").eq(4).click(); + cy.get("table").within((table) => { + /* edit table head */ + cy.get("tr") + .eq(1) + .within((row) => { + cy.get("th").eq(1).click().clear().type("first Headline"); + cy.get("th").eq(2).click().clear().type("Second Headline"); + }); + + /* edit first content row */ + cy.get("tr") + .eq(2) + .within((row) => { + cy.get("td").eq(1).click().clear().type("Some"); + cy.get("td").eq(2).click().clear().type("More"); + }); + + /* edit second content row */ + cy.get("tr") + .eq(3) + .within((row) => { + cy.get("td").eq(1).click().clear().type("Beautiful"); + cy.get("td").eq(2).click().clear().type("Content"); + }); + + /* add new column on the right */ + cy.get("tr") + .eq(0) + .within((row) => { + cy.get("td").eq(2).click(); + cy.get(".actionline").eq(0).click(); + }); + }); + + cy.get("table").within((table) => { + /* edit second new column head */ + cy.get("tr") + .eq(1) + .within((row) => { + cy.get("th").eq(3).click().clear().type("New Head"); + }); + + /* edit second new column head */ + cy.get("tr") + .eq(2) + .within((row) => { + cy.get("td").eq(3).click().clear().type("With"); + }); + + /* edit second new column head */ + cy.get("tr") + .eq(3) + .within((row) => { + cy.get("td").eq(3).click().clear().type("new Content"); + }); + }); + + /* save table */ + cy.get(".edit").click(); + }); + + cy.get("#blox-6").should("contain", "Beautiful").click(); + + cy.get(".editactive").within((activeblock) => { + cy.get(".component").should("contain", "Beautiful"); + }); + }); + }); + + it("Publishes new page", function () { + cy.visit("/tm/content/visual/welcome/testpage"); + cy.url().should("include", "/tm/content/visual/welcome/testpage"); + + cy.get("#publish").click().wait(500); + + cy.visit("/welcome/testpage"); + cy.url().should("include", "/welcome/testpage"); + + cy.get(".cy-nav").should("contain", "Testpage"); + }); + + it("has sitemap xml", function () { + cy.request({ + url: "/cache/sitemap.xml", + }).then((resp) => { + /* should return xml-format */ + expect(resp.headers).to.have.property("content-type", "application/xml"); + }); + }); + + it("Deletes new page", function () { + cy.visit("/tm/content/visual/welcome/testpage"); + cy.url().should("include", "/tm/content/visual/welcome/testpage"); + + cy.get(".danger").click(); + + cy.get("#modalWindow").within((modal) => { + cy.get("button").click(); + }); + + cy.visit("/tm/content/visual/welcome"); + cy.get(".navi-list") + .not("contain", "Testpage") + .eq(2) + .find("a") + .should((a) => { + expect(a).to.have.length(5); + }); + }); +}); diff --git a/cypress/integration/99-login.spec.js b/cypress/integration/99-login.spec.js new file mode 100644 index 0000000..6428ee5 --- /dev/null +++ b/cypress/integration/99-login.spec.js @@ -0,0 +1,67 @@ +describe("Typemill Login", function() { + before(function() { + cy.clearCookies(); + }); + it("redirects if visits dashboard without login", function() { + cy.visit("/tm/content"); + cy.url().should("include", "/tm/login"); + }); + + it("submits a valid form and logout", function() { + // visits login page and adds valid input + cy.visit("/tm/login"); + cy.url().should("include", "/tm/login"); + + cy.get('input[name="username"]') + .type("trendschau") + .should("have.value", "trendschau") + .and("have.attr", "required"); + + cy.get('input[name="password"]') + .type("password") + .should("have.value", "password") + .and("have.attr", "required"); + + // can login + cy.get("form").submit(); + cy.url().should("include", "/tm/content"); + cy.getCookie("typemill-session").should("exist"); + + Cypress.Cookies.preserveOnce("typemill-session"); + }); + + it("redirects if visits login form when logged in", function() { + cy.visit("/tm/login"); + cy.url().should("include", "/tm/content"); + + Cypress.Cookies.preserveOnce("typemill-session"); + }); + + it("logs out", function() { + cy.contains("Logout").click(); + cy.url().should("include", "/tm/login"); + }); + + it("captcha after 1 fail", function() { + cy.visit("/tm/login"); + + // validation fails first + cy.get('input[name="username"]').clear().type("wrong"); + cy.get('input[name="password"]').clear().type("pass"); + cy.get("form").submit(); + cy.get("#flash-message").should("contain", "wrong password or username"); + cy.get('input[name="username"]').should("have.value", "wrong"); + cy.get('input[name="password"]').should("have.value", ""); + cy.get('input[name="captcha"]').should("have.value", ""); + + // captcha fails first + cy.get('input[name="username"]').clear().type("trendschau"); + cy.get('input[name="password"]').clear().type("password"); + cy.get('input[name="captcha"]').clear().type("wrong"); + cy.get("form").submit(); + cy.get("#flash-message").should("contain", "Captcha is wrong"); + cy.get('input[name="username"]').should("have.value", "trendschau"); + cy.get('input[name="password"]').should("have.value", ""); + cy.get('input[name="captcha"]').should("have.value", ""); + }); +}); \ No newline at end of file diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js new file mode 100644 index 0000000..9b34b67 --- /dev/null +++ b/cypress/plugins/index.js @@ -0,0 +1,48 @@ +const fs = require("fs-extra"); + +/// +// *********************************************************** +// This example plugins/index.js can be used to load plugins +// +// You can change the location of this file or turn off loading +// the plugins file with the 'pluginsFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/plugins-guide +// *********************************************************** + +// This function is called when a project is opened or re-opened (e.g. due to +// the project's config changing) + +/** + * @type {Cypress.PluginConfig} + */ +// eslint-disable-next-line no-unused-vars +module.exports = (on, config) => { + on("task", { + resetSetup() { + const users = "settings/users"; + const settings = "settings/settings.yaml"; + // of course files need to exist in order to perform a delete + if (fs.existsSync(settings) && fs.existsSync(users)) { + fs.rmSync(users, { recursive: true, force: true }); + fs.rmSync(settings); + fs.copyFileSync( + "cypress/fixtures/01_setup/default-settings.yaml", + "settings/settings.yaml" + ); + } + + return null; + }, + prepopulateSetup() { + const settings = "settings"; + const settingsFixture = + "cypress/fixtures/01_setup/prepulate_settings_seed/settings"; + // of course files need to exist in order to perform a delete + fs.copySync(settingsFixture, settings); + + return null; + }, + }); +}; diff --git a/cypress/support/commands.js b/cypress/support/commands.js new file mode 100644 index 0000000..45cd5a2 --- /dev/null +++ b/cypress/support/commands.js @@ -0,0 +1,41 @@ +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add('login', (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) + +Cypress.Commands.add("loginTypemill", () => { + cy.visit("/tm/login"); + cy.url().should("include", "/tm/login"); + + cy.get('input[name="username"]').type("trendschau"); + cy.get('input[name="password"]').type("password"); + + cy.get("form").submit(); + cy.url().should("include", "/tm/content"); + cy.getCookie("typemill-session").should("exist"); +}); + +Cypress.Commands.add("logoutTypemill", () => { + cy.visit("/tm/logout"); +}); diff --git a/cypress/support/index.js b/cypress/support/index.js new file mode 100644 index 0000000..d68db96 --- /dev/null +++ b/cypress/support/index.js @@ -0,0 +1,20 @@ +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands' + +// Alternatively you can use CommonJS syntax: +// require('./commands') diff --git a/data/security/securitylog.txt b/data/security/securitylog.txt new file mode 100644 index 0000000..5a0192f --- /dev/null +++ b/data/security/securitylog.txt @@ -0,0 +1,10 @@ +127.0.0.1;2024-03-25 21:48:49;login: wrong password +127.0.0.1;2024-04-20 12:51:39;login: wrong password +127.0.0.1;2024-04-21 19:24:11;login: invalid data +127.0.0.1;2024-04-22 14:38:20;loginlink: loginlink for user member is not activated. +127.0.0.1;2024-04-23 11:16:24;loginlink: invalid data +127.0.0.1;2024-09-01 13:59:35;login: invalid data +127.0.0.1;2025-02-27 19:22:45;login: wrong password +127.0.0.1;2025-02-27 19:23:07;login: wrong password +127.0.0.1;2025-02-27 19:25:24;login: invalid data +127.0.0.1;2025-02-27 20:14:02;login: wrong password diff --git a/docker-utils/init-server b/docker-utils/init-server new file mode 100644 index 0000000..f3bacd9 --- /dev/null +++ b/docker-utils/init-server @@ -0,0 +1,5 @@ +#!/bin/sh +find /var/www/html/content -type d -empty -exec cp -R /var/www/html/content.default/* /var/www/html/content \; +find /var/www/html/themes -type d -empty -exec cp -R /var/www/html/themes.default/* /var/www/html/themes \; +chown -R www-data:www-data /var/www/html/ +apache2-foreground \ No newline at end of file diff --git a/docker-utils/install-composer b/docker-utils/install-composer new file mode 100644 index 0000000..585031d --- /dev/null +++ b/docker-utils/install-composer @@ -0,0 +1,17 @@ +#!/bin/sh + +EXPECTED_CHECKSUM="$(php -r 'copy("https://composer.github.io/installer.sig", "php://stdout");')" +php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" +ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384', 'composer-setup.php');")" + +if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ] +then + >&2 echo 'ERROR: Invalid installer checksum' + rm composer-setup.php + exit 1 +fi + +php composer-setup.php --quiet +RESULT=$? +rm composer-setup.php +exit $RESULT \ No newline at end of file diff --git a/index.php b/index.php new file mode 100644 index 0000000..d8b9dd9 --- /dev/null +++ b/index.php @@ -0,0 +1,5 @@ +vGZ1xiA})M+sipGn)>Uzi2f0< zawwCo6Iht@KcDlaZhVK)8&i({E)z$|DM(0V1CP=1WgNGz$TXKfi%1FB+1GIOw2{K< zEe~UNCs`@1p7tQ}Wr~-A^nuNdnTHg>lot1CyA3PIru9OTBQX$!t1ftLm<(NsJ4xeXssydZNK; z-x*E$1uTbduuC~}v-nFqOVp1#&b9t?qQKz?d`=Q8EVW)qn%V>HzT?QjoM@{3Y^Lr< zkQ|Sb2FF$}LI0&~{y^7+3mkHc@VWQhN34S(NkYbvgMHz}>g$)Y6_`2_3)mE8S|09R zps3UU^39(Ghd#bj;5#APRwVmS;n?rL3C9@|-|#tEbbRFFFY^(2_D-RtS>pYNgySrR zJUmVo8aDTM%2GhyZfQRB_-w*)W{_~AK-D~uj>m>}3`u)z5BBjNYL_d2!M{z6 zL$-dyR)?PL);Ee2j@_2cu@!jymT#LJ$9|tX2dA+b-s9P3)&VqTtK*9u&1<=n_DJTW z3Ou%r1_pVB@r_Fg$7UZ4V*{!xllmWGA>$rB}@w|>6NxNG+0+CLx_l+`Z(`1%xw{maT4ewZCqz9($evVZC;Q_hz$DF^?SO@Dvz x{g1@k-QQ9Trt4aK*d^zBrvLiaKb#L%vFaWBxB4$A<9oXLxvX!R0|Ej890*0F3MfdgBE5qUiXuj;G(`jiL}V1i0)ikS zy@nP<2LUL>Asv)_3WhI0} zxoz$2t$faTb9?w$opbUIpui6TM*%WYQV1yt83Y0$CnuwzVx*>`q@-e{XP{x^WP@^X zvT<;5^N9;^^NPYaI0O$1iAqSq;c%#coT99hg7_i0)SeO$IXO8MB^3)bHH#Dv2anVr ze&JsLbY!3-L=s>SFF-^G0@H!;wEz?VKqLgTdw@T^Kty0-5>f~mIRzy_;0Y~21OkJJ zh`}Tz#KZ*YV8VTXn2v;=M^c59LC*@p>%k}$mT-#S8O*dhHzCTq`5iv4`>(F03+(0)PoW5B}yDP&)OeZ@5ZP=P^& z$pg~?2q2zF1EAMY7H)X$iU+29Q9J8SqNoWT7d&vrcniILE85saE0$`DgA`<)!$0ZF zC9=+r*;>uS?Pf0C-l3kxZtLKI5X`3Wh1|je;JW8E0eCT^Vuu2O+2j)Y8t|O2HqE=$en%ut=^K+w^KiYjORo_Ra+!ocCXh{z?z!=ehk(1TBf|g<9fj;#P2wE5YHPfH+_UcSyb=z*zKm<}AOM!vhpb z)n7xwG%uU;kp%yMDklU#hGE~wF#Nyu?d6ZfZT#aj{kd`bW4?W*(B7PW$F*P2>EB4R zU(M;4TJjyWKk@C8Z=Vxqk8j@=oX>pw6t^Gn?Q_xjLEoyj)(CYjtpE>r;36wqV?}E2 z%pDv*-JL(})!r~-#E{Ze+4TOKTA5%jW`hTKDBZSat6GhpP_)pMD{{n*WESj43l`c8 zQ3z&-{*{Ba?~+chMSW#$-=#M`sY`|F2!rXB;Vr+FUGjV1ptxl&@*N6E_Uwm<@R0n| zg4tzp{({+P;IAA<&7mH9AYS|@fOuJK>WqhT_1}nk%5aWF`cKN z)d)i|Mm@8J3PwFO;75l||Lk(xm?HAnbtZkzJP5RI878YV@)S$+M8%^}qr7gd;IYT) zf&Pm7M|jR_JQWD*0cygA(b_jm zS5kHhlPLiX^3t$OeFO{*be8m3mS20BZ8Ak!)siBkYMiHzgl*a1di9}DEa$PQ!%qPp z_Vc#i2!+Uv!@9C*G5Kc`%#J&YJ*KVZvptgUCvKeAuDN4H*8SzpRs1)9JqwB4RpV&rvgk{r$} zV2uQ^u1{wik(Sxf)__l5IZ>h}iQ&MV9$}?;Dv;O{FP}Kmrd#!-#|MlOJ{1sRt1Bu5 zzmg&ZJLFm|%K`_v>GpPdx2%^+)%h^xCe|Lfn0Ul^fx0H0Bg97WY|B{>(-zq@m&r3Q zlbp*}Tt(77HXilGM$DWVxPSjcG4bX@e~MEWdeOq`0i5nYHH^bY>6PfZsNM4b7yj$E zX@{e#xsCN{sb5@`j7aFK@=&JNz8Cc*$X~17I<0?#j*O+u@`ZQ3I!NiQaKrljM=bW* z$+U3#R>RuCDAikWC2vN;4`GT6wtenzR+W}jvf7pS_g`i&b2sGhxS8d^VSo2|qW{Sy z+9~!`sKlJ;t;7}ZVwQYb$#&b#o`hT%?iIs|b4yb9(DKQAmTTCuyoSQpEqr8by5j8T z_H{fxb%K1$B$^APv|B)Z^jO-5_U5-C$D`i8r+M?1k{=}{oV)V2qWr+)5>ld6?jy|l zx%b>QBs`I<6Z-_`EDvW)@s-8C&7^O<*C-J{8NITJ{&}~^L99TB`ekzRP2S+#@Md;} zKZ?uSxl)(vL)%M z@myroQzrSrLH=ps`KxO-kwjy?@a^ZR4$cxOEI>SUL2Zh#B55Fvm9bzJ5&EAS#L}M% zA+>*S!zmQoOwF1SN)ZpFq7WTv-;{5rv664=Vh&xo2y9be_F^_#4;vUx?aqZ*nyPdgH|m13PesaxtDb}z4GT@IL3(IjCWo%0 zMKDS*XL(HIdFH$pr+lg5fTcS44&!x%Z{1TEm=9d%F`yG&;Wrke2ybKeF4dbXbdx~4 zYYs@cGso3-K+5&ZAgee z1|d!pu83Jp@SHL`qj3asY?#Y`+VDD@|LPD6&P|-u(4?nU zh;bT`GD;eG4ULL12_D_*fRuKcrPhksFK8|vug}Rd+cJo}Vp{-qZ&g&`6lr^m;Mkn5 z-S3t=bEMFRdP-BgUpK&G1b~2$auX|UK+qGWm1!*hkn|!dN(pn%Oj}Nss$?a;x(5&V zT++$EA2jbZCV;-r-wXG*f7F%e5yN9=WXGwaP`vFiqs-NB?zSXu!Q6ndRDd6BRp(!M z-h3nS{ZfXa$iY!mEvJsrD?Csv9q_!%R^3s6o0LRdE8S;YVdrEV8Yj|=Rm`91bTV!` zceS>4gttUPX`};U?Ce#bve@~-EyHn5V|9klH{PmO`NG6bANgWVo%~ek*p(|T+WHw& zOphJNd042+)KLa!ZdcCN=Pag~wvyV?O(0hUcG_`lo?ZAY``7=z|Q$k-Q|j;ZW5g2B_(Wf%#gSXBKd_j zWAyM0qF0?v*P~$|b`BqsSM!_&xmpSWqtMH+&Z$@P;=|{l4$fIL_R*X*vq)ToA&&jE<`^m} zuHa2f3s`X?cXddo8F46cliL2gTIY=#oOER@txe=az>>@Or9w8|Dr zDV*62GCUR`TKXq?D+Ez*X?URd&}7@Q#zQ=tx5vncWlS{cYkz{YT-2Pr9=A2(VP1zC zxe-)!X}pH+DVLcrZxSBBsI-r-$zggrT_C|a0*j=@W4i~n$}ILd##@*+$=If7^ha4j zSoa-4$tTtY`o&_ceB8Y9{hgby5)0-HNtF>#-)u!S6_j&Tjvb6VykV4j+L^@`p1|Ph(5#I?oJZ=}IHFWze&- z^IboMWJQ8An+vd1YCEKl_?5gwoiootm+s?%%e`H9YMkb4G(C9X!e-Hr;%oTEE3L6~ z;U?adrmZpEhxiW*v5s_WH#>l(bmD!-(@^h@o41VS>mP*7=w*+pC7A{58pmPn zN;BM8lsju+MXx*T3==leDpQq7&Wq&{k?}TjtIRJ`SlA`u`1}vn@Wfs21-#!9`a+Fv zXJ{IrTa+`28y|`pWBI6B6)%XGsqsxLBY_glbGQl%JaANN^$fopq`H_@eFgE*mffS>_b^Kl zVpGyh!Ca!ui6;T_I^8ZQZd|MAaoxIx5**dIz`PrP!~?gagIvQ1Nivj*2h0;t?}#ca z#t%HH9g@DB9d2wG&WVs^pq> z+-A%zy5@eF0gvHJBK;wrH<_(Fi5BYpr;l3Mkfc+Vu)s_@0s#qOja#wA{ZvkDC?7`9 zwqcU@11^W9Qk)v)!EHQXvCDiwXVC^yO+SHZnaJFx=*#X@?I5Q3`DH#-MVoLWj4r65 z%8K()2B`x;AjN4xkdPz6Q^p|uPG!gw!gh^3R&dXCa90Xi5Xk=V0s6GP*#Ljs%ayzD z>SSV2lf#%*{_jqB{`cds7sRUO2%R5E^B%!kS9BweeESxmf=?-q{Ek7;5q+ylk0* zo}cbU1AW<#MtjBbGWZOWkcSx2p4T*4Y)7CkMiL?hr8%EhI{Qq(^)M6dz72|l|(Tz;>1<%!uzt8!;;N516rX&Iab`>1a_d>tX(CEu_5(*5$fiyBEsIYyUGNOFTw{H&0j$poCCv4DBAOGgonLWv zkQ^lrL(gL@DkRfwhC<=*{=nAebMx>|-Qs*U+VIG9o}a-!MSYf7pz*34lZL)0Qv_YK zcVoMUR>IKhw?h?1nU1D+7=%tS`O4JkvnxK=^*q#fhp9*nu%2Nv9PnurZ*CTYMLpbb zZ#M;ZPqE~g&TJT!vYpR3u@);gmgpRp+UjljW{t(5JHy#CB~-3WKwT!GfAF{sj9nL! z;d(iGo`s6p*Q+}mV4XeNBkxih$aT|Eb*p57K^6lQA#%1y1DLMSw0>916yBV9`sS;o z#Ik<&T;r?_(nQFByfbD2GI_JT0aOKaugql-{#H@M4DDVSI(rV<*SRQ^Uw@V&$yS*k z74st5r8Z$3ee3oNY44FEr|$8IIfsfJC*vqf^bdQy4o+n2P{T(mX0_=_djUMeeT3_o=A?mb)pq zvHI>4DLp^&_(vyRB4JUlj_m9caGWJQ)H8ma^oBd!=B(;gx{}{^O17e`g|m*7Z3dz+Dyc zA>U_27LiJ$EUZ#zeXwe4`vwlj7LJ}N9ubm$B(UQ$jK#L$0TeaV^LKb~$pS7UkMQe#WLEYS?)9r5zWSy#=tuZ3g8^!| zMpsWQBI8UG?#l(_7qe$61b^el@K-5g7j|aU__dVia@9eB1EJM0s_*J zD!mhmA|=$&k{8aq@7^Qhje9-!$Md}R+!=F@z2{nckNxer)|_j8EBHbDBtUafO;Zg3 zfk40w!VkdX09lo*s>*uWS7o4f)&h=qZ0w+3!h%9j9#3}%XFI6oMRn*!9aVLxmz2<1 zeyFRb`I;o314(rz^ckaC|x$?0n$|hO6DHT%tGKLXML&GBLBT zo<76PbCy?3TtZSxT1Mr9s+zin=EZAz`UZwZ#wJ$QHnw*54vy{~o?hNQzJ8%$;SrHh z_YesWA3aWd^7L6!MrKy_idx#4Jfd3QL-;o``MN7a%L`)1OCOyOjBJw5(n3kC2l<+Y+ zWnEHB*WWlrg30L5$EO#5I1UrNy2^0Vt(%;YOKj>i<`CNN$bJl1$iIc`@4)_nYZ#ye zg9y$8(*lY>3PZ|g9Y$LzfHJ|cbIWsOe`gdAOuWDY z+xyKA{X@0XyLzq*io6QNd!)N1 z)H_(*%#S=iHecZbE?)(ASFwDjX-{Sw7qARp>4k?wh6ZZZ=iTwZ-RJv!3(NAPv+5k7 zYnhAMZ{T@4cmNr@tl*0K^qz1gi`?6om{R`^`>t%P@{Vp(+I|kK#C&<&na)a4p;8g^sgLDcp(0XCLTbQ!#9L2&JT2&AF*^J#;7CkK)9ks z7f%KPtq@1hz|R!Ec@#aoxh)SqFgmfzf=YM#?z*5#@Y_xS7mi+Dr4A3QiF{Gq+@{7w z@GY3^EFLiko2IW$tiD{_C85{79@#_CNM4SEKmP9B?+t2v9ttOM^apUI3w$z< zi{{E7Vs$pDDeypZTNWM|7**V5{@uMtEcPFrBkSRHNYnzouBsbEgP?(*DGcia2t~Bb z=bFW3mjlXy!vOKeYjY;AfB`xoKw6iG><*)9TNVOb_sbAy2tw3@yxU0%H|gj z@0?sdV?R=JQ@=V0r1{|Fy5m-t%!P4}^&KaeK-Gyoy^WiI)Ta}q=CZwtQ_$YBu~>=A zrD3nvd@mO=!;jyxl8Yggi`CWVA_xiA}bcrpw;f!dpa)Qmopm(H;T1ciw9ZLH8I zsrEX^gy}Yhc=xHI9ovC8uAIr=Mr4JTlG0iGO-f3}I)}Qn>G$M5YEId=2`X_pgQc1~ zU#R)@DR5O*6cvSj(`LOa-mc^9F-|WVbVWp#^)Ss6kpV8Cqo6Hq(Y z1OQjuq#MsB4Ma{OuONDc&vHmqMi?7tKC9Nhqs{N$<&_KC@_XI?WINHXc(o0Eker3N zp!1vPTV9dbiqXvE4a?fx0IgT~5zp>+wZkgP!n70FcoQ4le7-pfXsgArv#0k6z|=LT zD%J+bZG>B6l({R8M?d^x{O;q6+*bt!m@hb;#8xS&Ci+rkq^)de0)Y13OrsJ|X`net zG396*u_SQ1Jn~cULm&OUw(=X3GqJ88FNsIxw?}-PlqRC#jA1ZcN1`{Jt~soQuTh70iF+1}PWIZaf6>dee$ITZ)jw+N_I{xYZx*8Ygt4Y& z$O&1TM{Vkixshic^E2lQOpLu!q2HF~MDrR++oGe1#wW7PVhu}EmTV%>6y7m**7l~Z zYt2+&igTNx(Kvl%=8&V}uz{pW6o2=~^$TXLE1>N2!KIkWn``PU3}zmA-9ie6Z8+p7 zLZA|`CXwBy4;t*w;4`m3Kg(Va8C*QAtG;DT zLmDcOU~6sE$^Ircd3&9T9Y`H%aN@@drzP6@GrTi>R2r{)9~|0zO1aTjVa!pR3L8SD z)^v%ks&Q_P#1f{|oKRY8>U0AB%gL%PioGSC?)=I`n0s40`-9fXR&LfoxM(^X|?4utBe(gQyrpr(D3KsQC=(X#OH z?#}_uTY=BK6~^klNPDX9_KC;dmkG=&Gkoi`YI>nED^Vj#pCfC!y0AcfdVc?*1ucBw zd5@Jz(cF8cMhn`_7IdU~-=_bNmDzHf;990sOD*aWR>c)Vr_Fl4nvb=%XaAXG;49AD zO2|2>#iV(-4Sa{yGj?|kvddhsvK8FFuQioNC8@W%l`W8;>lHG^C}vr{mcI^;iB{xw zQHBlJ7yLF^gRsR|1W&%Je*UQs8lP6~2JqL%-I1ErK#5^{F|YL)F@+Ow+V<0~zpP@^Rj{918=js5xIE5Qj*nA^B0p8x)mhLfK(UBcvy-Z0@Vw?Z zgDUH2U*Q{+Cx~L)TQ;YI6jC#)b&*XIGy%t_U0tU6a$kDZw>Y`Ae&K{Hzxgr|Z+0Sk z+TvcSRB3s?+_~GXFBfzf*AkfYe2F%t6-ALR*>l#x3&U#`F`_;_d@0;Gn!;TAv)sc9 zjo}e)mF`Xb6{uh&z4!yu23;(?;o1SJQ%&f~8(R*7lynHBI?dvNLMQez=gUA*h-k_Nv%FHyM%LJf#XVd7Zx$Fqt+fZa5^ zf0KyuqD)E0?3lveJi4-L{j#q6HVf880u_W zwHkBJ(-WrYDjqGOvGnWXazxdiv))0z)#+Dvt{2%?cr2Y)Kzg?cl%lrmNO3rT}LsBLxdH4 ziWRP^iBPMIRYQFO1GFa(u4xWaK0JTPc!_hr87{^J(jkrV zPG3%a=#X{{D^oB(NC~)-bfP5RVfX%yLC=VM3svJl7~Mq7*u-{+4p!6V+=J?K68S#i z83@BZ+fFFk{(U?U$godh7vS1uQ4gm*mo(wwwY|8N(~7A_OW}d)GS>yCK}FkGYCK@) zBNJ$1-LU(p;=KC)+e^VaDNSoHIVLx{o@^0n<^IPPMp}HiNym-303!}BBSKAC*Sifn)MXpfh8((hk*M}cTTY@ zow~<{q8dnF8BX{rQ7TWdm4kp~+lw+-$X(_HPdMmke2D)uqU2hnnzLZ zPsxbUWJCITeG0N$C3;(9!~C!f!&ZKBTd87eXR#RDX(MNGY?DHTYv2iO_B*j?Nx3jB z4x2hi6h7b^zYF2x4wh4RP5$Oh5RDqy z!C+Q3qh6W94eSMpeY;*@k|H+}vaD%Vwq#6*+T(<|THp>BD^Q+8#X9VX$t7t(+-OqBzf9R~O%u7&8p@ zE4s_|aAE^h;9(|11<`oho392f3AfbFh_-z_Vp{LS(Pp1{Pc)@4M|W56(Mon`ekbhU zCH1~(o685UXR8w`0(5WA_ z4cAu=2dSqqXa62bgAe(adZK7uRnVOb1P{#*EU&)H1pm{~9^8v?hV0d^z0_5rs)zgB z)w8UrQsnid2Sz2gBWKl4{=aLw!m;sBNVLDk1R)G80vT3Y;(y@usXSs!i$eVIbhzaM z7WCjpE#W)$#Gyfe)V$mz;L>gUg!Pq4y8y8_(-Tc*Ut-$OVvkWbC6*7hY?#`dEDTlR zqQ+9c|FeP_U&|Qg7XDXCx8^~2jKGy_nEoIMo&wWr{SU?(W2x@FHxfiYZ1>S2JqaQ? zu^c8e|HW|0v%I>)F~_#1{Hg4#dKv@X{E!AZqTpKe`f!j$&g%|Y)1p2~&atd9zKg-; zB3WW~C&=W<swW5ubM`iLypGU3hDwd&i1WT3=qYb9`nvNQReBaQ=E(gj1N2;7d~% zEH;68oBz5UX@wLtE-n62Iff+5`?3qMx1?LvS+}}z`AM~*db{*by|w7AV?p8zuRr!L z*`zWr(fOl9#GV%Ol3fT`x~a*2$IlwmQ@rqR5spV`!JkVMGKAW3q-XAw78;c-r9YmH z(8$)e9{-3azbQqWOry;_GvJ2+>S!m791Xp7jh<&!U9i=d)#+}|D>#D-uYi%&6fhSGQ^ucwllEb?aKo>}GGmh8WCoq_mlf=~mv z&wX%G=R&yz{dp~{;PMngg7x{> zBkD?P+tEhp%@&q|B<9QzdIVXIR2+$(a!{HFa3Tb$#O89(NK^FpTJ}mtTSwn8>nmOyx>81b=a|-npvLStK%%dg%kI85|ALPY(510feZ}=~ zs{Dkp(+!jhJE6OXcX!>M7v5`eDlSq^b4YZ&q|f8cW;Z&blhY zwQgs##z%&}=nfB8nM5>`5jiRVjaJr`$POY0lx3j|kH1CW$It=gAba4Jy}Fv+p1*8l$_Dh^ACoGQ~*h5Ta`K6yfx?(Opc3H>CZw zx*v@~6@=8M{l`7BcdO5f=CFr5VxsW~nYRhAUqVAesEl#lRfN9?<;CvpI%YErIU%{S zW{L#xfc-eU^K;X|ff)S1&cv@Gq;<`E0^8ujlF0Qm*#Lkaq6G5B&$%x-%32 literal 0 HcmV?d00001 diff --git a/media/live/youtube-7yvlwxjl9dc-2.jpg b/media/live/youtube-7yvlwxjl9dc-2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b6a0ac8fb1bab2bf5b82e3a79d15ce992a4fd5f9 GIT binary patch literal 11230 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 literal 0 HcmV?d00001 diff --git a/media/live/youtube-7yvlwxjl9dc.jpeg b/media/live/youtube-7yvlwxjl9dc.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..c563b5a7e99f4e000c6fe06610486d7e9b519c18 GIT binary patch literal 10368 zcmeHNcUV(fwm%_2=tvXkUAoew83>{w6sgjCZ#KI0P%fa6CQT6pBuG<=6qP0_QY;uy z1f(cU=}2f&Lh{0$J1WlooU1eQ?tAmy+Is{;D5@G*W2IWl!sps6=4uK3Q&-fL&(V}AP@*8B?T1?Gc64@H4Pgh6FoB*I}bM( zJ0~Zv-~nM?ehC3iP7wuB2`OngIXNC-B^5;(=mA+dnVpM(C@CpvsA=}m((aStfz(4^yOdDH%D0f|81w=uo={AOV5F zB&1+6GE!2acNlRTAY~w9#>gpf0JZ^1k?|Aa`<*T08y?y;}21dum zC*FUUoSL4&E-kODu6FQR^%>bWxEP4INJvS+q>vq4Ad+)L0W*-2@kx_2 z9x;SCcr)?KL{c!TU%Gz#A*Fz<(IShZPdC+GLAg;O><+YFk^M1XQ9p(3Gq5kX1^^l` zh!{LD0{{ckjjPEFbeJL_MxMyZVFG};MF2j{^$Z-u#npV=+(;q->MV0txYGxXiUd@3 zK$mYBTW6O!wW_uR5dhU{0`RUH(SL>jO!gwT)=mzrukfUE2Gc*S-fWpWM*tjJHr@>d zR(y7F-=cko+r$un@Xio3?&u|S(vc0r)wq6GD3gkVam&YF861R+ppu%E5;=|ksK(p)R@A_?j_DlK#uCLa63MQsVoGOob zSo9Vh4`!WPe2t@dV>K7QeO0xEIPt$Bvwhv3hbtcM3O5dOSfig)M8Zb?6JY+kCMo*6 zE7|vtD_QirX<_&Mv?%y0gMQC7_jfZW^m`e!D>JbFkQra_*nK~RzBqoJLjMx*KaA!} z3Z;J85C0z2e;ABM`1g0tPsRKD$G{iZyMp-tetSoz-DI&p3XYj|1=4iyOEr$#YmUj1YXDcq z?hOSGMbOD&xICH%0}hu%u56vYzy!+}G)NkKA)R{`TiS~q_UJQ{keIlaIPMgCA(`18 z6{9z%xZy*w3cJX2yiWP_g!{!0wTtx$TBB%=QdsN`Togjgu#zW$CKB7c4cVRKwY< zGtKi$*q{@1zTI)!sTMsk=`n){5v|elIw^`&dPI~7h>#nIy;r)X*E()h!&O^HI*j0_ z7(g;bqjc#tl{9@RBg~+CR-B?S_N|2l zbp`{ z-cVGNTt2P-VocFl>Yf(Ic19chYU#`AR2`=q4-;Zp2Kh)sGh2W6a7ibC)h1nu@mr^vwNMHL?uijT z&`<@VE!!|wUajp}(-Mj}5kwlLd5)82sfYk@xt7XX%4Gy!r$Wz3AFb!Sqkq+yZ~V=P ziY?a&p*R~0R~;8d%}&?5B%}8J8iM{4_VgudMN^wY3cVS811IDbZ$jKNjWh-`2E3z( zupblCZQI(Gx+S{jGph@4RHwT&jsVwoK|CB zF@yumKOijQ!F{;6TAs~~R3g1QG?%_x?~WVJ;SRzn2FG3`0DSX@@%NnwKvztNP~h8d z4EqwgoRKZL#fET}7a@^wW&_Z_D#jx(=gNwh-~gitpO)Ypq}X}|C-$BhC}K`CR3l^l z2?=~#yMqFOzwb;$k+6eem%-2BlUrO4l8*a&J^|=Br$;QZt`L9~KHDd@3OkmNuPhn@ zer!C8lQYD{_RSeFgfjzwKtP>=*wtSa3+D5-^WJS-sGgg>+_n(7X_jO&lglJgR!~w4X}zJ))gdlKJd87miXleRq_8a;)0f3ypM?zX<%Qo*1zrcVj?^*@*yL|r3$2YA969XSX0A^;4HLFMM_ zjULKev+9-k0;T-7gE(?V*2F}>Z`>=|@kgeQD5h}f?MImfaLL*CA+5HB=yHs8nvWGNW zVTSGF^YgaPLlH&v)CAy>x--mS35xsFUepH-a|hd^7Ln`6L%qkZ*_!*2rwj;g{9>@@ zI_sTD6$0==EQFqC{pQ1g8ISv7TEY>R8nww+jl!2Ix87Cg^?(-S%tXgKJtuV+xjvNK zu-@>AWP-~?49OOEbO?ADDkX zG_@^UaWUXNZ|?z@%*!-u%a#8As=xSs>?q6XM2^v49LX~7D*I6V5{=2YEptkjJ~l1nRLEJIt2YnMnb*@mX^u&3(R^`iwhQ_up0+2z`Hczbl|z z73JmKW6u-ZwO&1z;MUToY&osad8%44R*k_bfNH#0wJ<_Cfj`U?7N;JjRj8{g@&25L z2B#ElRM@6jE>0Y4TX$={Zr)!~FXRf@&3gy|HZm`y<$|A$`mD*i`N6ZUveumrvRZt= z8AXRKZ(FNHCp_XZG24}3D?)`Cd20%I@%19B?oi#+XA5Ue4665*z84T6yDNp667PYk zK=0bJzbqH_S=RAjYRI|O3fkXjH*SYZ=*?aI(7efEc6;3l59eaVH#!g#k4&gir8D?P zc*brH@Kc=NH~#Z?PC#S=lPVALQ6j(}k#gVmcT4;yVXJemyZa80*LXl_Zi1cnvNcd=ms8E8ZJwmS1r`R2Eq{!kWSFh;ierm>~ucDS2}RteaPlUek=_eyJTy8b9al0o z_0w`UJ~Yzzotr)u5eALw2M> zwvod4h}B7M>RudtR9AW2qVo2ZYpTS3lSjTW{y=oQc#Hq^ra^u5RDc~P|M}^G;|26` z+B5WF_ek-1rkt;c?K>*+VVObN>7)3MSM--}g;|+kA+5ANZ^c6x8TsxgqMv9+&OC2l zoE${0i9%V4-9dUpOmw`&;bZp*Kt|?p1Ag;EYz@~c^ztaaVr*EfZ5hwdxtDsXAT6j$ zi$j_Q0*>WwE;GlvBqc$e4%O7h#Woh*H8qjPrgtdLW~kJHkHKQuZ*(@j$hKtD zWQ2?GaVEwmihaZha?4>qozG9z+|rG=r)YmO;eryv2xZj~0NxYt3{1Q*4lY)izLON)Cr+Yp2ul7F8Izbo9ScszI4CO`qpwIW$5_@l1z$cN{sup z@cx)k`Y7|$tGV*R#~aOu3fyARHz8F@1R&ul1U0A0nZ=O1H9y;nTfRFZPVWD$4L?Z{zT%<-&Tr9 zyh$44##L&T1FK%=$0F>iM4pxid^{9P0Ae26QMx~U5GCP2%8j02k?ue1YxyJm_0Q%o z+8;vCj(u1RxO_HjjizB^*B+j{-LCY|dJ&XN|27 zh&!^awH0TIlG?8dV}%c`m|kC%}XgzuF_li$p&*KWkxRM?Nu-9Ak5}I}f zCg#r!3=wU`Ex}1oufYL@9h($!u*gu2cE=X~U+{x#-zf^s5@zj|xO(L9bHs7VEIVv2vL;&c*ufv(mloo!xBsPW8hm>xlwg;>QJQG0d zxxm~oFQg-FuR#d`K<(jyK!1L*PH|adem~++&s5~Zp1hIZ9JNVE;{Degvl&@sDJ)lm z4mf%6Yo-du@XGrp+!Lhp#&EfV^-vF*Uh6&|IAz-GXmD};Y_B{PS{BfZP-fx1-a9pa5DZxy}k2jfwxeVxP3!|+3 zYwh!Wxko!ryiz3q>A-12i)GJ6xI{5qI#oYhYAwfrP$3A=1;?^1%U6I(-Kk1mYWH*PxIg=7 z+i8o6a}OW%YnFL?Sib&_4D=GzN5o~Q6;NIB&&lK(6uZ+2O5=SLU8i`xLht z!D%@n8(cqCmfbpv&gMwvZ$gq;WlumIFH6$Zt(m3R_I`fGj2I7{cJ`(P~jPUWR4P<>}sf_A>vXH*eFoOm4}t zkcwUcJy3zu&~P0($gWsR8tLNPSoe-uPs!9r)yRvEf;;m=(cM=cZI{He&Z9M0=e>+o z1Frv)hx0`ZC(6YhN)$9sAZIbQ2ThSFM<|uK9q&0r*5j~b>6&#hi1wj5+9uCF4;FH` zfpfyQ-=eXyA$cabfcM7R_C(zlAjU)tVU4R>DH{+0vnzg_qa8A^xEOQQG(&iqMLlDN z2fFCn#Vxw2m%cwsw!|JQd99IZyT5QTaK6X1me~)SZ6m)xZKJ9vnRB?`0VV5~5)lDj z7B3HXh#!yci}x=-|J!-}!+Z1_hWbw^>(>PJ-$Yse2(A4opWR2^pL7ELL=)kAjfcOY z_4TU)|MvUU|FLL&)gWrWD$Q~I*Zx~ z+EP>`?|>w8ngQ!xl(q=nJT}@qg>GY{Px;x#m0IT9?!1&>mbvwgZUkyOAF>( z{(U%{E&)K&@|>wBzRz)?`QvEm_S8psvhTAxr};j+Wvs$BPA;r|79Z|Pe1{|UX7`t& n<-dmhT8+PQ literal 0 HcmV?d00001 diff --git a/media/live/youtube-7yvlwxjl9dc.jpg b/media/live/youtube-7yvlwxjl9dc.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b6a0ac8fb1bab2bf5b82e3a79d15ce992a4fd5f9 GIT binary patch literal 11230 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 literal 0 HcmV?d00001 diff --git a/media/live/youtube-uw-m-4g1kaa.jpeg b/media/live/youtube-uw-m-4g1kaa.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..7ddc47fc3294d3753554691ac9d81fc826c3b740 GIT binary patch literal 10482 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 ZDZU8{=wJbP^>pZN*)9{2=N1$@@IOHdMH&DA literal 0 HcmV?d00001 diff --git a/media/original/youtube-6i2-uv88gke.jpeg b/media/original/youtube-6i2-uv88gke.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..ce9cedf79c3b32f999337386236f828deceaaf37 GIT binary patch literal 29956 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 w(extalM@hof%wtkhrnr%|IX{b0kGuHlmGw# literal 0 HcmV?d00001 diff --git a/media/original/youtube-7yvlwxjl9dc-1.jpg b/media/original/youtube-7yvlwxjl9dc-1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..49e90060ff0a925a52469b6e264de0a2ad42162d GIT binary patch literal 25738 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 LX7V+&|GxfRHN6 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 LX7V+&|GxfRHN6 z45=begb)KHfRL~eLPQ9WDIp1i5C$Ox2#|z82yc4!aC)xm>~qfUdEfp0@pW;r)-$bj zuROy&{O3pbj z9H@U3sH1o6sE+Ot-D7%sn(R@XV>&uVb&l%m>FWP-)zQ&3I0DqsRA^2*+kXaVy|?rA zF2FlG3;;W|cD$ptW4i+Y)V$!%cQj>yzaP8aduR8a9q+!sQ&a8pfu?%rjvYJq?R@Y3 zJ^S8yXBS|{&UfD3rM3IRJ@08BKJt;p7hgW;|5LWXIqT~&@pbh&mglYF6284xd(7~& z*zX>-eQb0!wl;6FH7yo{pJX^&(r+P`Bb+<{TLIbYB!P{U>XXjP(Qh+3joPpFQ`3Yy?V ze{k=GeJ1e6na-CNFvkBzZ(;-Dyx#gvpVATR8v6TGw1i!@t?Du-&x1+eD!zP z@&9MR8jLyS=LYINXZ=|zU~9$zurGS&^U**4%M_-+6WVbV;G6e3z^rkvIN|Sw|GLDk z%R54@{}DiYV@ewQ*HQgDC7nO)O8Wt@Q}|6y!{>i5{MRME{N{+!;opb;_o4sSO}W2N z`hC^^Us?4#|L~gz?l&#_lY}aG;ssaC9t$+O!PX?`HqMHm9urR}u z!$@&{J+p!b0>%WLUtj1WvEy{Ds0|Jl!qk97poyfr%9`nU0lEf-(!mvnPw&xcy^VtI z=29Q8qnAbOTT;2G>oOg_ig|L^-%2s0v!E3Ff$A)QLuxfLT&=eWFT6ELPC0&Oe55d> zU*V)V);1{4I|Jv98}hUDl+FIP@98JOj)D?y{cD4)DL1@n<} zH(2gTr4?0V3|wdlO7Vi=I-O<4w99KdizRSVGwa7(yn-%+hZg+qp z;z+eKY!lPNDH)dI!8^#=ISY%uOL3V5^QMkyRMg^p)EqZN2;w*}+zU1>E>kCE^{J;j zI%LTUS^Q7)7Zd*Uc0N|nWVMXEzHg2+7;PPs|k#`Uu-lmKp!okgBX>`te%MA zp>hxjD_HHPmgBthiSf<>B_3ml@S+qLm$r)@{~PVg7k@|Y)d^Q2R45wZh&pWR5=zINHsO$+z_9brojzcV(ZFSksq~QC0HVIh#cRjNDLBH9Tl1SelWPj1l#75)e&o5uP(@+v-^! zmqnqB)l`R8_gkOymsR`sSzKNcKIyqY8P}ebxUDZJ+>^2GwLSGq2fPd9i3J5c9jxPZ z7FPLbX)AFHd9ugJsNJ@r1GQhP2&NTXR~^}X zzDWG2uKgOTBicp3DJ2L26Vm9`2;VzQn=~f=%Z`4tU21T3svX${8{xWvek;SPe%qp%?2quL9 zYd>U4&1&(?VKCQXR*TBc)0*~sr%$)viV5wWS`LPlJ1UW`{j@nQC`CKSVJq@aF0@(g zb#kcp+cPPJRuwc%y?9-lpOT?4P1ah<0YLL^=9(%Gz4+7DVTS%s-6RmNxMxT0=9+?3a(b{u?n-sr0 zq!b1@jxT|P*DlDxum^2e@$B%Mir5@pR}{KEXWh^^(9g7gXf9Q8MgE}tlaAic93n2B zBq?sU7wR!|kvHxtD{lkiqAnVqywUCNeJ(WJ<>@^{hwus3frBV5NV-04ov`^4rGZRz zGE0vP54N0SHiaB4{~;8(wi(X)IP-Iwlp@AcbR#y*?GnCMv58F?r2)pCGG@Jm>s-3X|*b^aN=@%$7WjhRW#&!U2t=gaCYxharZsS-jGti2P6-YUOtf{}m@(-erbt(tZW(WZP14P7pTyAP zu>KDToN(h>)B6r#KSvcGvxylSUhz4g%5f7>W2P4ss^XbCoz~begDuU}PDN2TxSg>9 zd|Q_YqFg?AN=}uZ6Cm$Xj@OA=pk^>(KV0}hnlyGH$NWvO z@sGu0KCdoX3fZuS!p!4ybp>?(z>?CWdQEIGz_UEJl+X|Bmb)ZGYyk4HWhGRvjTO8>pBvt zl=vFA;31WrYHtUxx~B^*{I;YQ#_ai882CfXJQBgX_j-6-IL74<4RZ)kD^<>zTQBHo z@?|>Gj}Xf8yzhb%D$||%_T1fgKKxIpiIsrU?w}gRHao&M&o7qNQ^M0S z(9Lprv64p~i!qQk1YjhYJg#Io-B03ocyRG7fvy(Ib;x zb?VKk;yd+-r6t&z1~z`e(+zfyJ-y_X7#F3$t}#c;u#KfnKm!b?foFb_7rJJxJaEtr zrjJG3sR`${^myPoEOeMGPh~>BQm|?7@kH(H*}iFc!oLFH;6O-UfPeHT9otv+0@>-F zJwVU4k_IbRgyV>5s$`1{r9yx7!!I(-M`9WlhhmO39RQJl8NrRo2cn)Uk?DxzrtbAL z2M@FGyo~q~3fFjeE&1T9$c3-v6SYvQgxZ+3*G2VIL-4&C-HEg%4+n- zx@N|>#tuDicz*b4#idvu23d4@xeR64E=OUcPNs(ehzU_4kTDfTJh&d`g;dq9-m@{LSl~W#xoM}lQ zBvfMj!LEq*Js)KRd^=qmR&pf_``2nU>)mw9*vA=JC zL_tonDE@FJIWR7I@p6HplqBYjMkwetHf;*HY(>N&w@dSC-_%2o1maSu=5AaJS4NHL z;Px%uVvnYV7>~(B=Un11JY4q{$>hy0{L?4$&MsW(cWn0nKIP@FiG_2eLoJ zm?!VB89PxI82qqYh1V~u#CBX3!`)|w8SN3(HEC4-%0Oqc20G0c_Yf4 zvawu>f<_kF$z;z$*yIumsJEb@8|R>#Z|bQwyHnI4>*;ZaT&ZUHe$r`9-fAr~m(6Vh zpd!SWfO!+R3a7_Rwq+^LO}DI(EJW1;1(B_#4IZ8xLDO92`|>#V>mU=3CmCu-v?_8! z$Sypir)aHl@E(8sdKBGqa!QyUGHX`@$sS-P)rW^=f})OxFkVIX=Il?lp5r}PqdwOR zp7Ah$(~U1dE`nj1-Q!Gh0||-fZQnO`t6+`OQ|{Lev$wF7EZn<|kjM@E`qz_6Ep23`gGWU7zd1WGWn-rc{djt6 zIdX#S=HXb3s5oAFQgo-<-v~$Fkmbu=JWCkQMwYHg!$wbl;}f|M*uR*J}G)CyJ%-v*g(s6(@UHtrZPFBX*p zQyVG%$na_bmZEL(;o>p+_aQvx)`f1pnNO$6D(@Q%b_HCO5pPt5&xI2Q4({XQ_|^&Q zHQm$o0u>xYz4_x(Vnu7`*A3;1Ekbpf$Xx%jSfY_Vt)Y=EsF1a1vCT!j<|ebaoc+F* z&SQ=bd`@098WwA52aH*Sq?OdEf;^RXN|r_v^+ArQsk+eUru5~LtsgZnu-NESWsI{6 z)TApW&Q^NWQ2kIE)ei>W&zT(#-aP`}Q(1uzcGpcon{kclb@yrAb$A~d+-e0PZ;^{B zn3J5mZNPh%JX^fwpdQQgK{94gKG8j1)+~^hEC?w5@Fr*gY?z#yQ&DT?M^<#Opo%6> zr+G`4*t?P~gj0oj7zO;48MKoLje=BF^{gjfdVBlBpB(?^dg0z5HGw(b>_Gn6C*bG* zu`&q&IJ*Cx#*UBwo!S8~p3 zWSq)Iz(6y2_yF$2!>Qy0>_s1}4l*bu%jJw+c|G(=Xl4)+7vFxnRZGiKN37h69g()0 zbI&HHV#%)${TcAj9l!Ax{&PhBaqCb2xKDd8U{}MApy;3eajRd|yu<#}o3F<82%<*F z+lFlbvz+wJMx+z$<)MmqW^8{s*-O?RrMh3+ibKhN690o|+AZCMOtH*2sCZP`XV*%) ze$JX7_uPe$9#1_a-7V>?H z)Gz+eX(3P4>U#crD zz~8^U)w&IMfM3mt+I4*!@bS_nej8xC4VX2x?sS@7@}y&PsF+Rrwfro)69+a|HIrzl zLZjwTn{F*1=Pi)HZ`x~H+i^7V@oANi13#V2y0>Yh`lafl1=B%eSoL)mi{Pb`oJx`h;@%gB!s+3gGo*X2(>uaI;SvL00Q}y0R@XC0o zOF(M+E54Rc=bN8fh<7>RlrVOBL6M8QUv0lpf&$ayb*f4mywY2YapCv9cHEhSm`bLz zdvI)9Nf7eDKBUlTFT8y;eTwDgu|x!kb>^a5;m?-I%a9akE=DK&1@!L7z^P?4JV4LY zi=;7;VjD`f0lumzU8z+8PmApO>Es}XTz8pq za+ow$CI&}A_~1|$&B*9uysfusSVv-?Y=^>&=YI}c1uT%*EoeTjs9kSmQ=F0@FGvL{AKk$-VS|Y*`KW`9?ejZnE+TQuzfC;Ha;GWQCM{LrWV_$>IK~?2q4d} zp#5o+s_UEAZ+{FuBL_ZZZ3A3jF8=kz0sA!;ez9SAAH#0Xx2S`dK)LH&w5TT6a5TRx zWHsspXt)si(jP{Ob#@juMdZ&pRW^_-pu6c4N9*t}RAI9xrd!52luKaqsTOiv*<30E z(i*IoTvLV18;zA39wgK-1MoJ$Zq(5feH*OZj#{yQz{ClB5xDqak|aGW*1Oo;KbV%M zGOz7h@V1pcmKe7!kDJoB0gOGE<>PPa`qEKe9M$IfrFC;2+fmr1)SV`KmOzd1FG@EVHodGq@GkuxM;qBC!s7Zs zA5%t%_>*nlwAq7qQP=(}k@4?!zeS{j&nX{u;q`Mj0*Ru!xdb}7;3QG1#Fw?MFJxt% zbG6N$)$4y-qG98Kzp(Mf1#;}2^=Im@VxVGVs;HLUHM>#jA)Aep?|l zgn|pOCEqqP=0}E-s=|p2xxK;s<^1*D3#bcW;NI{B=zfmpz3?aMgRU}}0vRkRUG>+$ zV$27ZgC{FX?CJMc9B);ae^)#bn4|wuU3gaON^z3veIh5yQUluXADnMCIt9kLpC=xP z9^f1ofX>L88m^(VEoWDz5S&!HhE2z_JXu+7~GRQ6ks!^3=RO z=$_(76{gH^lt^P@pXQdkb;QpFwq_KRkT>j1!?(2FpWREyp3Qm{j4w|W#Knt}B%><2 z(R2HzxJ2Xvy~=M9Chku`x1t%GHI2PBt2cwa(UpD1uP{^tWuFU9q0uGQ)QYtp(s&j= zd=`3#C!+K)mB=)juiRZc&qjqe@WpIf{PHW}Po+Xj6V<1Qt=b2>FLBK3UQUw(ahDF} zkt~3YvylxfKSD!w$lWcxhxb;G%HP4l5elc8-Z!vZ(Q?S2G~{bMUHfz}n<>6H;?tDk z8w1*-4)e;D`Y#P@8`!CMWiUtVpig_*c01A9{st@KR23mi<1F*WTypi6b@@XpX!H&5 zQIL!frvb26=wxo3S|B0(nHV39>+kC|>m+GtOZ>(uAfH!0)9W82$SfV#7uE0@HYR#Ja#&hSMwSci{gm0OFslW3@ zgzub|mfp{I2r$;=nCVq3p~?$q5GqpYN>BKKryLoVYxer5_}Um(!wM2q{!`+^M9$P! zNj)SJ6-J5K`l%}=JbhyNEHBnn@6`;IL;$NzMJvRL(@owb%;hOo;bAuF=Baa{dMpBm z&fvn<{QvOCvvGCl%!5x0Xij-md$RU?UrlXY-lKm%0{Ch?o958tG5hgFDI$S!n?;Lt?#7p=|q{hu4@q{GBgVrmPyFS;=Hsp zwZqBKvKYuD$8(KC=Jmr$KbMBQSw_hVKPx<+AsVVlY4+~p8Vy_YOy(2VIT0wi4}&6> zVYMfj$)EES9)5=tU(9~f8}Lm#^7&M`I?L`3qw^%1m3tV1HsCM&BS)SHM(vp-Ju(#H z6Z{pKlC3kByGueibAo4dy1t&fMh`Qhk*X{#!t+wjP9;!gSlI_LK1ODD(ac=nq!EpE z-=U$pV=MbZWs{)h+=K(LjG(cE*Wj) zxOr&|Mc2{-F5Ck~6c{-5JeYG@TnjcF9#3q|=-L9dwtt#2>C+=TWZ6)maY@*`3}-*r z=&M0kW~Chex~p)LyM$kFA@&JX=d$M=%JCm&O^<6SaU6JCod#YD&{>+IofFZQx%k~m z3+@n;+dAidais%)C2Ws}G-QIZsOh|>$7UYB-3=DuVxR=$K?$hvmES0e+xmgi>4Zi< z*fgLG4HJ$;2PE#YTq(?J95?l)TB&}Z(Mi8n>n#*ZK47QWWWKDlvLK~Z{^k6HR)9?KP30M5&xXk#p zxuHE^X=Od3P5??e8xausFkjO!&J&jcU-O2ocHd39wBWrBc!!>H(3QIKuv!x^a=2%b zojw}UWWm;;u)g)~?#>uYXwx#>}Ybs2WVDD~bEYN9lF6cp#*TQqq!4dsq9axQ`3WG1ckFvDe$2h|MRS_V;P~- zcmZ-TTMHrXby?Rb@i%QOfrBa-urjBjR8eW%vt*V>^SFks0(DeVjJRa{c zF}-m`rY{~!83MDgO1#cS4odJvl+Ld*K*uEIgn7J01WxLc_-hG2?x63iI*dQNJU*lg zZWcjYj&ea|?}}U=&_36qfAjA`{l7~6(?+!BVX6RuM&fS+G$KD>8<52*lgk<5v3SEwPZ^&r zYb3Mnj03poJL=Lbms~vW?Bcf~klHMEU?qfCUR#Z*x&`8ZzVrrb{paXnir081Di0k1 z{xG_)HyWInL4VHCFKJr7SW&G6)+ka9hZbsF-M0aX>urwqa<6s$lJP-}Bh5UwH^BgW z^kq~k&=kss1c5zH&Hfo54gSW?e+f#rC6S1`aS7W1&xQ&-Fm)TCr@xpR9rREM3UV-d zvXrSG3I5*U0yP8pd}6ZF<8+J$U)K+qB_=}6u)J!#x-nvGAJ!&1AosOMM2ZR5JA}bjH&YHYP?Z!-;Fte6unH4e z9h_VCc!-JwA|iXR2n$dM-l3;)A@S+$tfkf4M}L{YFIs|ls;K4*8QA)xOmg&=EgcgX z95M>)_U3iv7>;V$8MKz?s1_j=RXAnLwRYe_;qjPB7BpqW-^5oePoVzv2zXDtC=q-; zJNunleo3Cy6EkpG^~r*|p0^b~$gqaZrU>xg51oxcfT zmzgG%Xn|znMiSY&* zB`;V?JdL`dam;Hhf`qpT-jV~E*J8_j|00q2`i9?Te?Js_^0O~ z8?5Kp9LMArw|hvp@6Ziw$Gpd*mL}7-blSOK=BLDSv|vLp8%LI9fk)>z&Lq{hw1%6k zC#%asPWj_7t=Y@O{uS_>LB9xZq8@qD7CM)FXgSge6Ra2UbCMELOa2>cb~>}FaZEO% zhK%J+hfj=eXbicFN@*fdNor561XEvbeDVt`aoy`Xc3+jmRJ*O}S9!_TADl{Q3E2jm zQWAzLH*u~XDn%#y^)H_GU42+8J)(y6rcz4P2Od83CTpRlRI_4T@Eg9LPhUdk8X@$s zBW`wdmG}a_$}`(S&^LQ8wNFkcy*luaLPeki`=6J;RTs8*uVv4Jc~3W=;)^K%)DN&l7RJ4p(puRP=gQE|mn7 z6gLuIP%GYSq>vB8X!Q?b;4|Kje$Av!A>XzFT4oQh=VBd%Cy~n$y~*?hxwU!DK=y8T z9TFnj{~_^3x3)y??d^Ad(uH$aOCv6|{;({s3AP`7u<{(^NYix|x}2VuaIT1_EHTSt z<8XA7!O)h&2Tv2^nm+klWQAT2hU{MBHky}~ps+4bYs-24$3?hx@yX07s5e1oav@>}8&(^ofjBVU3c-D^@4sFg;Gn8t#u z7gB|WuU8TqCcU|f9fDCoj{*TSLPnRoVzsW))vvb!pK5p~OwNnP>o{6<2~r%B4a_vr zoAq=vEh{RrGRMeFdp>WD3#J7-6+SnVZlEQZmddTx;Jq!5E2<;9bK^jpHI39bdebQY z#!d&neKTAQi7ig;_U_Y%L!dz!G=^nM`51jU#@~0nJ1i|@LD?gL-^rVEx&+rTO&=SF zqT!?cA*ZP8zEXHiRFn0lxyDH9vP8rPLF>$b`20K`$2Y$$3^wHEa=9?|yvyY*uWY7* z76UT8ODbKUChLKpYy(0UG)hs^Vpu~#(F>GyKD8W)aA%Vu#Fm!g{Cr|mb8b5Hrf_LR zXU3Ys$&azK7bl8UsY@Dr1wTJ23x=z6vkCYsOS{#lv3lN3%^r1Gc@b9m{r&Y*w99j2 z1);+Q9WX*D1Z^w?neDS|mtq8%b!t7uYxjXj=Gh5mu*Afd5c-*Oth~&-+$eu+W`|kQ_x0(3n+-r_E>{7wO1tTRir>l7;hc&LxS!g6etDlm49!8H{Q|Slt z=paxWD1i4p@rVcVZoW0vrY^`Kw4Pm$eyrf75#ibF;HIa&R78qFa^boV097A#- znr-#u((z4%zca@(+fv)Y5j}9Gp<}%n-E4ii9O;qb%7Ls3Pdt}byq&9x$`#LV14b{K4zWchX zEB4q-VG#*&jN)GxaD9l>JhbGzA;x9*59o*b=+|P}>zD9<5iY^3%NmU#G+u@UU(g$J z7^0>W&-Wy3S@dy!e(}i3YeBL)%|MsqXHv15x7*Q^N= z)u3!cuW^8+!x~ksyH~@5i5HAa2H51ogWYwwY#fbg0Xiqvpxo&YA&+;+$L?v0F(1g& zXr>d_Bn!m8GdDyoO5<1ROvre`sP$Oa)&YlhpD=WCiFRUUKjUPw!tr)CgH3k-Vv1%F z){r_phj4k*KOVZV@zGyJ()naYat|r@4rdi=x^JJ53>&EH9TZqNfT>1{Y;F+00;C5BY&uvnBB?JN#bvzdMgvlDnVpraZH*bh4NyZIkBuBnUXF6t?_G|-mqUjjy z^n$6wrTKHwkj9vCdGl-$Zf#F>q|CI zjLS0gVn-Y=wq~yqt2I*p=8YSHH<=A>xu^IoxJ*0uf_!8A89F7oJg~|BlI{$tS?zPc zK4Ily-W*RN?&A%NhCL6yxA5Iwv%>xWImlrztN50n)}c9IB#rV%2TrC@%ayzue^DucruxE#w8J! zMFX0SDaskh!s+?|9jW>NlH@D%5J_CtM*IG17{PyS3q_rD=`?NAHxYnHjxEv1(_i)T z7^m+wWP^;52=Bg;6<>30prPcg6r$&px3^8)L%3K zQ@)tbdLAQ|ZP7s)e#6l=M!6>*RTM--71-MglxA6B5r_$9AiBW4!<|R>o10fpt;M9k z1fP4JPjyA^qGn%}x}sL$Mx7+U*1!gKxlhNfnwF*m zz}2%`G?yz~oj8YcY@J^kN>ZwM>3@uQ z`zwt}liV>ewFj`X;&%Rzd%yd5=i?g_Q+vPJ^BY4Le){o2Y^mPCE zAHL8(QSsj!_51Zu&{PPcTLS<)D(NXoCk;Ht-QRwwkT;&aw3-6{>-AcXW&_M$Ym@($ z_`TU*y6g82(aepsJ zJ5UJ(X&Y@m)E%o^Kr3L1js(h9xSFsI+1EZbZoF!22xB?wov#*JzxU*IE|+Bf60fymkMQ7NgfxjVC0uD>bo#beRaT|*<+M2W zguDLhh;_xBwo}s`0`0o^>*CrWPI|sgOjL}`Fn={DB|5UPUT!(P$n863GL1a`7&~g% zYzYcU3H~YSr@y{F2vp+bqldF^LTh$PMw=s35HqYmKI|y@<4KnFQfNAh76O9&Pa}T* zLzn#a?ZvyZC*tab8og&@1PYlr6Bk`@5+^W?oTN`8vm<51&hnE9RQ0fURIEL;h&FyU zg4gj>pN=6Wnomep3N4N|pe@}~_1yE9fa__*wU7|QQHglh#72`x@XL`Ox)WX7^ z6l7bKEU#s6ruOV z29c)Bn|v8g-E-lSeN5B~5;{?gQ_>zA4;@#P*k3iScnY=?eKFyG6=$-%R?s^VtLs}k zR?lj9DRtPuW&`w6nfx5n^QoDD7p6>5+QnH=j7sYH8y~r zGoHQY zVC<+_)$>B z8wpn8>12Ay@_4H{_obqWg0nr_G5G|csOw3LP1P|N%3_QMx-SLhTJlHdrPveHyCu+j zcLTqYuWuFHsF7g@wA_=Inacs?rVvb@zxn{2OwkgWzv^YH%QObp%`cS8eX5E&eMKQu z5!66fb>XO2S3ZQmN7vJ~0fbjVp3STIw{N~i=MXUq&n z^RLS=-y3M7Y@S)&LPp6+F{nl9$y>C{nN?rb0K)pcG6ZM|+59X`az=LrR3%!oE`1fb zJQC~)d+8q%6FS1+6d=aWI5ApJK}{lMn@DA`DIBU7g=UsK?8-0;%eNS#SkaA)>fF3x zPq-mr_VJs6S@Y_|!P!iR%XhQq6{Vp1SY_lqa%O-NUhaAkZ8c+f#&+dlmd4>A!|%1{ zYW&>6Seu^t75m19Bvpw|K$w|rtl}JO=^`o$t=dl|iI%IvWvt;@^_iAbP|z7@z#*t{ zl_1*ZpwpTxuiW&yW;d-f6_zy$=;Bk7kI4RPI} zG|%E!7jb2H-4HmricBu2PVCX(X0^oX*lmE?{&T$#*3(hw=DNH%7IrttNwYWCP8C>! zLPj+rlEmv@-jy+=f{Mm z6->7wi@v&BFH0%mEQ)nX6IQDPH$UQtQViwqMCwiexB zoc1&K*MxL=4~P|*$CxslK1($vNnC5uK;BuJjfG^^;=42IV1p6D31k7R!6X+7GX`SC zV|-Pn$rb+MR6#rwIPy>yj98b?=?xMh94X;9ei=FM&nLV9E3Y@|Hyg7qh9-7hCHGpo5-P3PQYHTM2v z4Nq1@iMfH^~zO-u=79^nrgz;bjX~x6%qQ>4|_@= z(YqQq0O>W!b&cud(q>9}OK_ICty7chatm%ss;9b_(fLEy5(KVXbjhAxhPMKH5p!Ng zO>XL}`dr!=mZS?dzhY|RY*88{Bq?hN69RVeoU}6wYB@jL$IgK5_7xMX_do2)>f=>B z9m*%5eCH%-1b^GbYV5WDph5iincuFZ4dP5RsDN>0R(x#dL6-~xlioQ~pYSB=x>$`e z7f!``bDya5h)~ZvH78z-AGOYm#=vh*#iSlgIpgY>=`f@^&R`5ZJ{yJTVZ9NUiSKLy zF(;1n(oRzs!&vSQiEFV}X@tf4%T^_(FZADOXvlE6fIl+RnUtSCcTpa3nPfCz7dKHo z!PNVi@S;8;|Bjs6+aDG1?Mz*q9c8AgW7WQ3-R|_Th0v^?7GG*+MT-?Z@mC{oen#2h zSq}?%WE#*MdeAW;9aa|Mx@mGboeOsM*021|LG1Wx#L3nlqI<6~kG8)QM^ASF&qhf3 zh^B_Zk6Hd9O!9Zg)U}?DgnDm-F#B%qa!Q*dLh5)c^%8}USTH;~rLYo5tFAHF_AuVk zHlV_Gc|a#DMQSEGn8k4Lpj>CVK`St8EPnysy#q7W@h3N$@vFxUBKDJ{U8;hK)+(tc zmi;#E3r*}}v*iZaVH0SibmHX`whZ(pAI4X$jzeVv%P*bncu}pU4t5*4mYX1#*>=cE ziN^XLL*hr##rmb)*`r#TsOzV%yn^s~RSBN3iJknU61ZEvCE-lUp|f3`aeANCWgF4n z=L8jiM%o#>wMuMyytum3PHdY})3nyXddD@m~o z?PjZrfB*u2U|h)0zpINoKPeRQ*#pZlYeP$k_A(V>jex58qQ7ABqZxyW(n*nnW$CNl zErdrEy6_>b*WTT~o{hLpbG`W3u-bdTVvFF_W*)Jw*7hFRV^?>ZAe>m0_#7hMC})@z zJ1m5Dlj_KYrP=Q5VrAN9t z#i7$N*T?b-L?go`P6GuAkNdQN?Kj1aTQwjU*9SR-wO>Sn2XZ`AHdHcQb2L~+H<14NZ+G$Q( zu&vNNH(HG3u%pLGQw>xq47%G9+?F$UVx4?B-LpQtBN;KB;gYCPcv2{qdJR`JduGJj zfJNp^W0?ACjA$iWzf)m44VJFWyWGGRG}yV($U%!=y@A6~V6Y%FbHSS1T^FspHq@nc z{Op_JAc9SKIN_@}7Pk=$PST~1+0dj^oa&MUX)4s3)O@+9!;HB6aMd&&iuTn|0E)RlEq zwjVDIk>ICY!diW1@z?;ehS1EW~hP4K|ypJaK+~kh1AJJbTA{NA#?C3VN z$s}Z8qZku##J%q@TmOfCY@MaHUsk3|n-LJPqM8}sb-CjGI^CFC!Xs^7R3rDk4TDMuTKDF%0r-aD@5G6$gq6 LX7V+&|GxfRHN6 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 LX7V+&|GxOSie2u*)!Cq{9Z1e zId}fTsneIvQmc(_Qfp70I(`27*-PiIoYBQ)kazK7aZ&wb8d0)ShWXo$ow=L=%8?B4WBv zU0!@zJ$Fm|;rq_B9Aj&=h38+6-#!@xT%|TYO>>3@pa94N$cNPYO#L+1_b&d;h|5w3 z;Z-3g05|nI=A(L3#S=jJ`?&k>^{1|1e;hJv7V8Zp_sA)y)!29l9MJwwin~oDxgT7| zro0~pKtKMQarD>65n-e6HWsAF$wwK!ZVA^2Sk5e)$=!GyZ(tS_H14nDFot_6{kC9- z(U&F7EKc3v`h;+L?4qvEg!n`_`KMEt{cI^G!hB9M>(A#5 z`~E!{KhIsWqcgnGvzMb;T>H^v-sqiIn#3i`_`4o{yqkKXOdmOO)lUG<37yr;xloM% z$ClZ&9Vq2~+kK406Tm}ldpP{@7L)&_%;Dlc$S+6T$)){U;+8B_#_fLow`BaxtugOh zN6&em9v(w8V_`SJ4moY~>%x}XvI?~O{7%=5X?ilhaXWc%8>`cZV)VV8W|lm|n0?y{ z3%IDZ6h1#MkI^0zuZWNMPNEs>Ko!1GFlET6et_P;>qHl#FOQ#jm)-WzGVA-R*AO5) z`n;x)Ub0I5#HtRbuJMR(YdrJ!HJ-IsZ&SyJ1Dw9)Z7y9NmD9Zo7Mj9*C#Qp!Q%0&9 zu$a5<0AT*V6h_mTG{#-)9(ce>u%hrJq{iE3_t63&y>n7 zNWPBmZ6RyDsSWZaWzYT~-yi%T#r}V^nX{K8-4EEZ!r%7;;P~hNp0oT9!PTPqeSiC( zbr$C1`8f68H8=1^cz39n9^fq3cAcBs4M_GX8quQsoou@?@=b{8{@)oaeDUn9@O&%C)#{k;qs2nc8)-EYY!tU;L>p2lY16U5MT~_%i=;=6|$ioIT z@$gVd8xmKP9m5EWMk-~AMgpdIlymN}bE^yBbc}w8g?@~(0N$L-_3q?^jud}1Ti%@V zJo%{k+~eOn{O!Lk-t3;#ZW+T?s8S>}V##kZLJ_WfL}~KkOm3|_oEzg?e(~#n{KTKW z`m<8h#Wxgo&q5}=DpZ^B+d6}NTW9rdZtQ+JC&fWP-&Qx;EQDp*SDhf50Y zx2(7DXOzAnMxbj8qT2(RUG#{j<&|I~=1I)aq{%qfS*Z{fcdOT}ze&CsnhGr%3Oi=;`C~w|K+41H*9yV z(j4pI)+}MmS`X7zF2~B9>>aje$0FdBZW~gG@B0;1w`x$-sfn-nZPP1`+5V_Z+HIzUAbS_{4sjnfLZxhMr*uE&l$(%`k5i|OtsKZ=P#-pXHadtSK z+c#j}R4pIX31&ji1_uYfn}uZRlUugMVmE9$ z@LIRr&8p59ZS<=$s{5GLNesp@*Y=RWk%~oEiRN8GBAm$KK28Xv%_P+`X>Z57??X#V z^(Y>nbA`v-mf(6Pux{uLIkY!LW5#`6K@YR(6yWN@exCU?BCSl;4LilxVdkb+cUH$q zZaz34s<=8EdY-$~%@k$p+`nT)GA#aBnX)b#Z~z3ZE6?iif}TSAz;JEpUG3~=9�W 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 E1DGa(I{*Lx literal 0 HcmV?d00001 diff --git a/media/thumbs/youtube-7yvlwxjl9dc-1.jpg b/media/thumbs/youtube-7yvlwxjl9dc-1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..990454f513cc4d1ef278ea5ef041885f483b786f GIT binary patch literal 3780 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 Q$hsZ=R~P@JiyWx^@5FA&^#A|> literal 0 HcmV?d00001 diff --git a/media/thumbs/youtube-7yvlwxjl9dc-2.jpg b/media/thumbs/youtube-7yvlwxjl9dc-2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..990454f513cc4d1ef278ea5ef041885f483b786f GIT binary patch literal 3780 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 Q$hsZ=R~P@JiyWx^@5FA&^#A|> literal 0 HcmV?d00001 diff --git a/media/thumbs/youtube-7yvlwxjl9dc.jpeg b/media/thumbs/youtube-7yvlwxjl9dc.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..f651fb03e172b7164a4e9ae9fc3a23a2f49cd718 GIT binary patch literal 3532 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 Q$hsZ=R~P@JiyWx^@5FA&^#A|> literal 0 HcmV?d00001 diff --git a/media/thumbs/youtube-uw-m-4g1kaa.jpeg b/media/thumbs/youtube-uw-m-4g1kaa.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..79acaccac62b3a610af5aab5cdaf23a53c097207 GIT binary patch literal 3637 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" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/foreground-child": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", + "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", + "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz", + "integrity": "sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "1.21.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "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/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "dev": true, + "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.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/postcss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.11" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz", + "integrity": "sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz", + "integrity": "sha512-ZoyXOdJjISB7/BcLTR6SEsLgKtDStYyYZVLsUtWChO4Ps20CBad7lfJKVDiejocV4ME1hLmyY0WJE3hSDcmQ2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.0", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yaml": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", + "integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3bc2e24 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "typemill", + "version": "1.0.0", + "description": "TYPEMILL is a lightweight flat file cms for micro-publishers. You can use it for documentations, manuals, special interest websites, and any other information-driven web-project. You can also enhance Typemill with plugins and generate professional e-books in pdf-format with it. The website http://typemill.net runs with Typemill.", + "main": "index.js", + "scripts": { + "e2e": "cypress open --project .", + "install-and-e2e": "npm i && composer update && cypress open --project ." + }, + "repository": { + "type": "git", + "url": "git+https://github.com/typemill/typemill.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/typemill/typemill/issues" + }, + "homepage": "https://github.com/typemill/typemill#readme", + "devDependencies": { + "fs-extra": "^10.0.1", + "tailwindcss": "^3.1.6" + } +} diff --git a/plugins/demo/DemoController.php b/plugins/demo/DemoController.php new file mode 100644 index 0000000..e1adfa1 --- /dev/null +++ b/plugins/demo/DemoController.php @@ -0,0 +1,17 @@ + ['onSettingsLoaded', 0], + 'onPluginsLoaded' => ['onPluginsLoaded', 0], + 'onSessionSegmentsLoaded' => ['onSessionSegmentsLoaded', 0], + 'onRolesPermissionsLoaded' => ['onRolesPermissionsLoaded', 0], + 'onResourcesLoaded' => ['onResourcesLoaded', 0], + + # admin area fired from navigation model + 'onSystemnaviLoaded' => ['onSystemnaviLoaded', 0], + + # all pages fired from controller + 'onTwigLoaded' => ['onTwigLoaded', 0], + + # only content pages fired from parsedown extension and controllerApiShortcode???? + 'onShortcodeFound' => ['onShortcodeFound', 0], + + # frontend pages fired from ControllerWebFrontend + 'onPagetreeLoaded' => ['onPagetreeLoaded', 0], + 'onBreadcrumbLoaded' => ['onBreadcrumbLoaded', 0], + 'onItemLoaded' => ['onItemLoaded', 0], + 'onMarkdownLoaded' => ['onMarkdownLoaded', 0], + 'onMetaLoaded' => ['onMetaLoaded', 0], + 'onRestrictionsLoaded' => ['onRestrictionsLoaded', 0], + 'onContentArrayLoaded' => ['onContentArrayLoaded', 0], + 'onHtmlLoaded' => ['onHtmlLoaded', 0], + 'onPageReady' => ['onPageReady', 0] + ]; + } + + # you can add new routes for public, api, or admin-area + public static function addNewRoutes() + { + return [ + + # add a frontend route with a form + [ + 'httpMethod' => 'get', + 'route' => '/demo', + 'name' => 'demo.frontend', + 'class' => 'Plugins\demo\DemoController:index', + # optionallly restrict page: + # 'resource' => 'account', + # 'privilege' => 'view' + ], + + # add a frontend route to receive form data + [ + 'httpMethod' => 'post', + 'route' => '/demo', + 'name' => 'demo.send', + 'class' => 'Plugins\demo\DemoController:formdata', + # optionallly restrict page: + # 'resource' => 'account', + # 'privilege' => 'view' + ], + + # add an admin route + [ + 'httpMethod' => 'get', + 'route' => '/tm/demo', + 'name' => 'demo.admin', + 'class' => 'Typemill\Controllers\ControllerWebSystem:blankSystemPage', + 'resource' => 'system', + 'privilege' => 'view' + ], + + # add an api route + [ + 'httpMethod' => 'get', + 'route' => '/api/v1/demo', + 'name' => 'demo.api', + 'class' => 'Plugins\demo\demo:getDemoData', + 'resource' => 'system', + 'privilege' => 'view' + ], + + # add an api route + [ + 'httpMethod' => 'post', + 'route' => '/api/v1/demo', + 'name' => 'demo.api', + 'class' => 'Plugins\demo\demo:storeDemoData', + 'resource' => 'system', + 'privilege' => 'view' + ], + ]; + } + + # you can add new middleware function, for example + public static function addNewMiddleware() + { + + } + + + # settings are read only, you do not need to return anything + public function onSettingsLoaded($settings) + { + $data = $settings->getData(); + + # you also have access to settings from container through + # $this->getSettings() + + # or access to the plugin settings (optionally with pluginname) + # $this->getPluginSettings() + + } + + + # use this if you have any dependencies with other plugins and want to check if they are active + public function onPluginsLoaded($plugins) + { + $pluginnames = $plugins->getData(); + + $plugins->setData($pluginnames); + } + + + # you can add a new session segment in frontend, for example if you add frontend fomrs + public function onSessionSegmentsLoaded($segments) + { + $arrayOfSegments = $segments->getData(); + + $segments->setData($arrayOfSegments); + } + + + # add new roles and permission + public function onRolesPermissionsLoaded($rolespermissions) + { + $data = $rolespermissions->getData(); + + $rolespermissions->setData($data); + } + + + # add new resources for roles and permissions + public function onResourcesLoaded($resources) + { + $data = $resources->getData(); + + $resources->setData($data); + } + + + # add new navi-items into the system area + public function onSystemnaviLoaded($navidata) + { + $this->addSvgSymbol(''); + + $navi = $navidata->getData(); + + $navi['Demo'] = ['title' => 'Demo','routename' => 'demo.admin', 'icon' => 'icon-download', 'aclresource' => 'system', 'aclprivilege' => 'view']; + + # if the use visits the system page of the plugin + if(trim($this->route,"/") == 'tm/demo') + { + # set the navigation item active + $navi['Demo']['active'] = true; + + # add the system application + $this->addJS('/demo/js/systemdemo.js'); + } + + $navidata->setData($navi); + } + + + # the twig function is for everything you want to add and render in frontend + public function onTwigLoaded() + { + if($this->editorroute) + { + $this->addJS('/demo/js/editordemo.js'); + } + + # get the twig-object + # $twig = $this->getTwig(); + + # get the twig-template-loader + # $loader = $twig->getLoader(); + # $loader->addPath(__DIR__ . '/templates'); + + # return $twig->render($this->container['response'], '/demo.twig', ['data' => 'data']); + + # you can add assets to all twig-views + # $this->addInlineJS("console.info('my inline script;')"); + # $this->addJS('/demo/js/script.js'); + + # you can add styles to all twig-views + # $this->addInlineCSS('h1{color:red;}'); + # $this->addCSS('/demo/css/demo.css'); + + # you can add your own global variables to all twig-views. + # $this->addTwigGlobal('text', new Text()); + + # you can add your own filter function to twig. + # $this->addTwigFilter('rot13', function ($string) { + # return str_rot13($string); + # }); + + # you can add your own function to a twig-views * + # $this->addTwigFunction('myName', function(){ + # return 'My name is '; + # }); + } + + + # add a shortcode function to enhance the content area with new features + public function onShortcodeFound($shortcode) + { + # read the data of the shortcode + $shortcodeArray = $shortcode->getData(); + + # register your shortcode + if(is_array($shortcodeArray) && $shortcodeArray['name'] == 'registershortcode') + { + $shortcodeArray['data']['contactform'] = []; + + $shortcode->setData($shortcodeArray); + } + + # check if it is the shortcode name that we where looking for + if(is_array($shortcodeArray) && $shortcodeArray['name'] == 'contactform') + { + # we found our shortcode, so stop firing the event to other plugins + $shortcode->stopPropagation(); + + # get the public forms for the plugin + $contactform = $this->generateForm('demo.send'); + # add to a page + # add as shortcode + # create new page + + # and return a html-snippet that replaces the shortcode on the page. + $shortcode->setData($contactform); + } + } + + # returns an array of item-objects, that represents the neavigation + public function onPagetreeLoaded($pagetree) + { + $data = $pagetree->getData(); + + $pagetree->setData($data); + } + + # returns array of item objects that represent the breadcrumb + public function onBreadcrumbLoaded($breadcrumb) + { + $data = $breadcrumb->getData(); + + $breadcrumb->setData($data); + } + + # returns the item of the current page + public function onItemLoaded($item) + { + $data = $item->getData(); + + $item->setData($data); + } + + # returns the markdown of the current page + public function onMarkdownLoaded($markdown) + { + $data = $markdown->getData(); + + $markdown->setData($data); + } + + # returns the metadata (array) of the current page + public function onMetaLoaded($meta) + { + $data = $meta->getData(); + + $meta->setData($data); + } + + # returns array with restriced role, defaultcontent and full markdown array + public function onRestrictionsLoaded($restrictions) + { + $data = $restrictions->getData(); + + $restrictions->setData($data); + } + + # returns the full content with ormats as an array + public function onContentArrayLoaded($contentArray) + { + $data = $contentArray->getData(); + + $contentArray->setData($data); + } + + # returns the full content as html + public function onHtmlLoaded($html) + { + $data = $html->getData(); + + $html->setData($data); + } + + + # add a new page into the system area + public function onPageReady($data) + { + /* + # admin stuff + if($this->adminroute && $this->route == 'tm/demo') + { + $this->addJS('/ebookproducts/js/vue-ebookproducts.js'); + + $pagedata = $data->getData(); + + $twig = $this->getTwig(); + $loader = $twig->getLoader(); + $loader->addPath(__DIR__ . '/templates'); + + # fetch the template and render it with twig + $content = $twig->fetch('/ebookproducts.twig', []); + + $pagedata['content'] = $content; + + $data->setData($pagedata); + } + */ + } + + + ######################################### + # Add methods for new routes # + ######################################### + + # gets the centrally stored ebook-data for ebook-plugin in settings-area + public function getDemoData(Request $request, Response $response, $args) + { + # gets file from /data/demo automatically, use getPluginData or getPluginYamlData + $formdata = $this->getPluginYamlData('demotest.yaml'); + + $response->getBody()->write(json_encode([ + 'formdata' => $formdata + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + + # gets the centrally stored ebook-data for ebook-plugin in settings-area + public function storeDemoData(Request $request, Response $response, $args) + { + $params = $request->getParsedBody(); + + # gets file from /data/demo automatically, use getPluginData or getPluginYamlData + $result = $this->storePluginYamlData('demotest.yaml', $params['formdata']); + + if($result !== true) + { + $response->getBody()->write(json_encode([ + 'errors' => $result, + 'message' => 'please correct the errors in the form.' + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(422); + } + + $response->getBody()->write(json_encode([ + 'data' => $result, + 'message' => 'data stored successfully.' + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } +} \ No newline at end of file diff --git a/plugins/demo/demo.twig b/plugins/demo/demo.twig new file mode 100644 index 0000000..3b2634d --- /dev/null +++ b/plugins/demo/demo.twig @@ -0,0 +1 @@ +

Hello

\ No newline at end of file diff --git a/plugins/demo/demo.yaml b/plugins/demo/demo.yaml new file mode 100644 index 0000000..ba435fd --- /dev/null +++ b/plugins/demo/demo.yaml @@ -0,0 +1,186 @@ +name: Demo Plugin +version: 1.0.0 +description: Demonstrates the power of Typemill Plugins +author: Sebastian Schürmanns +homepage: http://typemill.net +license: MAKER +dependencies: + - register + - mail + +settings: + theme: 'edgeless' + message: 'You can enter a message here.' + website: 'http://typemill.net' + background: '#ffffff' + +forms: + fields: + + text: + type: text + label: Text Input + + message: + type: textarea + label: Message + placeholder: 'Message for cookie-popup' + required: true + + code: + type: codearea + label: Code your stuff + spellcheck: false + + theme: + type: select + label: Select + placeholder: 'Add name of theme' + required: true + options: + edgeless: Edgeless + block: Block + classic: Classic + mail: PHP Mail + + userrole: + type: select + label: Which role should a new user get? + dataset: userroles + description: The standard userrole is "member". A member can only edit his account. Be careful if you select other roles. + + number: + type: number + label: Number + + date: + type: date + label: Date + + email: + type: email + label: Email + description: Please help me here. + + tel: + type: tel + label: Phone number + description: Please help me here. + + pass: + type: password + label: Password + description: Please help me here. + + website: + type: url + label: Add valid url + placeholder: 'Add valid URL' + help: Please help me here or make me cry. I don't think that this is a good Idea, but we will see. Otherwise we will get this done. + required: true + + background: + type: color + label: Color + placeholder: 'Add hex color value like #ffffff' + required: true + + singlecheckbox: + type: checkbox + label: Simple checkbox + checkboxlabel: Please check me + + multiplecheckbox: + type: checkboxlist + label: Multiple Checkboxes + options: + first: First + second: Second + third: Third + fourth: Fourth + + radio: + type: radio + label: Radio + options: + red: Red + green: Green + blue: Blue + yellow: Yellow + + mediaimage: + type: image + label: Upload image + description: Please only upload some stuff you like. + +metatabs: + demo: + fields: + demoimage: + type: image + label: Image Field Demo + description: Maximum size for an image is 5 MB. Hero images are not supported by all themes. + demoimagealt: + type: text + label: Alt-Text for Hero-Image + democheckbox: + type: checkboxlist + label: Multiple Checkboxes + options: + first: First + second: Second + third: Third + fourth: Fourth + democustomfield: + type: customfields + label: try it out + data: array + +system: + fields: + title: + type: text + label: Title of your eBook + subtitle: + type: text + label: Subtitle of your eBook + author: + type: text + label: Author + edition: + type: text + label: Edition + flytitle: + type: checkbox + label: Fly title + checkboxlabel: Add a fly title after the cover. + +public: + fields: + + name: + type: text + label: name_label + required: true + class: 'tm-input' + + email: + type: email + label: email_label + required: true + class: 'tm-input' + + subject: + type: text + label: subject_label + required: true + class: 'tm-input' + + message: + type: textarea + label: message_label + required: true + class: 'tm-textarea' + + legalnotice: + type: paragraph \ No newline at end of file diff --git a/plugins/demo/js/editordemo.js b/plugins/demo/js/editordemo.js new file mode 100644 index 0000000..8b52b83 --- /dev/null +++ b/plugins/demo/js/editordemo.js @@ -0,0 +1,48 @@ +app.component('tab-demo', { + props: ['item', 'formData', 'formDefinitions', 'saved', 'errors', 'message', 'messageClass'], + template: `
+
+
+
+ {{ fieldDefinition.legend }} + + +
+ + +
+
+
+ +
{{ $filters.translate(message) }}
+
+
+ +
+
+
`, + methods: { + selectComponent: function(type) + { + return 'component-' + type; + }, + saveInput: function() + { + this.$emit('saveform'); + }, + } +}) diff --git a/plugins/demo/js/systemdemo.js b/plugins/demo/js/systemdemo.js new file mode 100644 index 0000000..9625d5e --- /dev/null +++ b/plugins/demo/js/systemdemo.js @@ -0,0 +1,112 @@ +const app = Vue.createApp({ + template: ` +
+

{{ plugin.name }}

+
+
+
+ {{ fieldDefinition.legend }} + + +
+ + +
+
+
{{ $filters.translate(message) }}
+ +
+
+
+
`, + data() { + return { + plugin: data.plugin, + formData: {'title': 'bla'}, + message: false, + messageClass: '', + errors: {}, + } + }, + mounted() { + + eventBus.$on('forminput', formdata => { + this.formData[formdata.name] = formdata.value; + }); + + var self = this; + + tmaxios.get('/api/v1/demo',{ + params: { + 'url': data.urlinfo.route, + } + }) + .then(function (response) + { + if(response.data.formdata) + { + self.formData = response.data.formdata; + } + }) + .catch(function (error) + { + self.messageClass = 'bg-rose-500'; + self.message = error.response.data.message; + if(error.response.data.errors !== undefined) + { + self.errors = error.response.data.errors; + } + }); + + }, + methods: { + selectComponent: function(type) + { + return 'component-'+type; + }, + save: function() + { + this.reset(); + + var self = this; + + tmaxios.post('/api/v1/demo',{ + 'formdata': this.formData + }) + .then(function (response) + { + self.messageClass = 'bg-teal-500'; + self.message = response.data.message; + }) + .catch(function (error) + { + self.messageClass = 'bg-rose-500'; + self.message = error.response.data.message; + if(error.response.data.errors !== undefined) + { + self.errors = error.response.data.errors; + } + }); + }, + reset: function() + { + this.errors = {}; + this.message = ''; + this.messageClass = ''; + } + }, +}) \ No newline at end of file diff --git a/plugins/demo/templates/demo.twig b/plugins/demo/templates/demo.twig new file mode 100644 index 0000000..e3d337c --- /dev/null +++ b/plugins/demo/templates/demo.twig @@ -0,0 +1 @@ +I am twig \ No newline at end of file diff --git a/plugins/search b/plugins/search new file mode 160000 index 0000000..d97fe18 --- /dev/null +++ b/plugins/search @@ -0,0 +1 @@ +Subproject commit d97fe18149958053a52e17ace38d486aea97433d diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..1f38add --- /dev/null +++ b/readme.md @@ -0,0 +1,89 @@ +# Typemill: A Flat File CMS for Publishers + +Typemill is a lightweight, flat-file CMS designed for simple, fast, and flexible website and eBook creation using Markdown. + +![Typemill Screenshot](/typemill.png) + +With a focus on content and text, it perfectly fits use cases such as documentations, manuals, and other text-heavy websites. + +## Key Features (Selection) + +* No database required (flat-file approach). +* High performance, with a modern tech stack including Vue.js, Tailwind CSS, and Slim PHP. +* Lightweight, with a gzip size of about 2MB. +* Markdown editing with a visual block editor or a raw markdown editor. +* Easy extendible with plugins, themes, and page-tabs. +* Generation of ebooks (pdf, epub) with an ebook-plugin. +* Flexible form-generation. +* API-architecture and headless mode. + +## Resources + +* Download and Documentation: [Typemill Website](https://typemill.net) +* Plugins: [Typemill Plugins](https://plugins.typemill.net) +* Themes: [Typemill Themes](https://themes.typemill.net) +* Book Layouts: [Typemill Book Layouts](https://books.typemill.net) +* Issues and Bug Reports: [GitHub Issues](https://github.com/typemill/typemill/issues) +* Discussions: [GitHub Discussions](https://github.com/typemill/typemill/discussions) +* Newsletter: [Typemill Newsletter](https://typemill.net/news) + +## Requirements + +To run Typemill, you need the following: + +* Web server (Apache, not tested on other servers). +* PHP 8.0 or higher. +* Standard PHP libraries like mod_rewrite, gd-image, mbstring, fileinfo, session, iconv, and more. + +## Installation + +Check installations with different setups on [typemill.net](https://typemill.net/getting-started/installation) + +### Using ZIP File and FTP + +1. Download and unpack the latest version of Typemill as a zip file from [Typemill Website](https://typemill.net). +2. Upload all files to your server. +3. Visit your new website at `www.your-typemill-website.com/tm/setup` and create an admin user. +4. Log in and start publishing. + +### Using GitHub and Composer + +Clone this repository: + +``` +git clone https://github.com/typemill/typemill.git +``` + +Then update composer to load the libraries: + +``` +composer update +``` + +### Using Docker + +See description on [typemill.net](https://typemill.net/getting-started/installation/docker) + +## Folder Permissions + +Ensure that the following folders are writable: + +* `/cache` +* `/content` +* `/data` +* `/media` +* `/settings` + +## Tech Stack + +* PHP (Slim Framework Version 4) +* JavaScript (Vue.js Version 3) +* CSS (Tailwind) + +## Security Issues + +If you discover a potential security issue related to Typemill, please report it via email to security@typemill.net, and we'll address it promptly. + +## License + +Typemill is an open-source project published under the MIT License. Plugins, themes, and services are published under MIT and commercial licenses. diff --git a/settings/public_key.pem b/settings/public_key.pem new file mode 100644 index 0000000..06e3ea7 --- /dev/null +++ b/settings/public_key.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuyT54vacgBn6HbzCQ2q4 +Bkxpp9ULqNrFckkoXRbp/NQK7d0jB8ah0NdElkWkUcZf6ax01a4DO7bfD81mF/wQ +djtXn7UoCBkE/wyrFfZ/A9FGAnXekosogguWFTpctN7mFsDoX07RfDrvrXq020hb +uZPpX/XQrPs6e6N8m9plDzAcqiZdx9XdvvZlzXnqQTNLcPsnQhzVMrxwGKJ5DTez +/YwaZoK3UrzWdcsW+teIG9uacIrK4txVJ/i0+65p7iY1BeKxljdU2Daouo8ObgFV +2jy5YTwJvIi5DJjupXb6V6C9mX3yfViul+30t8BMPDD+lf1Rx7Zp6fraYtx/ZDAo +swIDAQAB +-----END PUBLIC KEY----- diff --git a/system/autoload.php b/system/autoload.php new file mode 100644 index 0000000..be0a2ad --- /dev/null +++ b/system/autoload.php @@ -0,0 +1,7 @@ +baseUrl = $baseUrl; + $this->CSS = array(); + $this->inlineCSS = array(); + $this->JS = array(); + $this->inlineJS = array(); + $this->bloxConfigJS = array(); + $this->bloxConfigInlineJS = array(); + $this->editorJS = array(); # deprecated + $this->editorCSS = array(); # deprecated + $this->editorInlineJS = array(); # deprecated + $this->svgSymbols = array(); + $this->meta = array(); + $this->imageUrl = false; + $this->imageFolder = 'originalFolder'; + } + + public function setUri($uri) + { + $this->uri = $uri; + } + + public function setBaseUrl($baseUrl) + { + $this->baseUrl = $baseUrl; + } + + public function addCSS($CSS) + { + $CSSfile = $this->getFileUrl($CSS); + + if($CSSfile) + { + $this->CSS[] = ''; + } + } + + public function addInlineCSS($CSS) + { + $this->inlineCSS[] = ''; + } + + public function addJS($JS, $attr) + { + $JSfile = $this->getFileUrl($JS); + + if($JSfile) + { + $this->JS[] = ''; + } + } + + public function addInlineJS($JS, $attr) + { + $this->inlineJS[] = '' . $JS . ''; + } + + public function addBloxConfigJS($JS) + { + $JSfile = $this->getFileUrl($JS); + + if($JSfile) + { + $this->bloxConfigJS[] = ''; + } + } + + public function addBloxConfigInlineJS($JS) + { + $this->bloxConfigInlineJS[] = ''; + } + + public function activateVue() + { + $vueUrl = ''; + if(!in_array($vueUrl, $this->JS)) + { + $this->JS[] = $vueUrl; + } + } + + public function activateAxios() + { + $axiosUrl = ''; + if(!in_array($axiosUrl, $this->JS)) + { + $this->JS[] = $axiosUrl; + + $axios = ''; + $this->JS[] = $axios; + } + } + + public function activateTachyons() + { + die('Hi from asset class, Tachyons not available in Typemill v2'); + $tachyonsUrl = ''; + if(!in_array($tachyonsUrl, $this->CSS)) + { + $this->CSS[] = $tachyonsUrl; + } + } + + public function addSvgSymbol($symbol) + { + $this->svgSymbols[] = $symbol; + } + + public function renderCSS() + { + return implode("\n", $this->CSS) . implode("\n", $this->inlineCSS); + } + + public function renderJS() + { + return implode("\n", $this->JS) . implode("\n", $this->inlineJS); + } + + public function renderBloxConfigJS() + { + return implode("\n", $this->bloxConfigJS) . implode("\n", $this->bloxConfigInlineJS); + } + + public function renderSvg() + { + return implode('', $this->svgSymbols); + } + + public function getFileUrl($path) + { + # check system path of file without parameter for fingerprinting + $internalFile = __DIR__ . '/../../plugins' . strtok($path, "?"); + + if(file_exists($internalFile)) + { + return $this->baseUrl . '/plugins' . $path; + } + + return $path; + + if(fopen($path, "r")) + { + return $path; + } + + return false; + } + + /********************** + * META FEATURES * + **********************/ + + public function addMeta($key,$meta) + { + $this->meta[$key] = $meta; + } + + public function renderMeta() + { + $metaLines = ''; + foreach($this->meta as $meta) + { + $metaLines .= "\n"; + $metaLines .= $meta; + } + return $metaLines; + } + + /********************** + * IMAGE MANIPULATION * + **********************/ + + public function image($url) + { + # image url is passed with twig-function + $this->imageUrl = $url; + + $this->media = new Media(); + + return $this; + } + + public function resize($width, $height) + { + $this->imageUrl = $this->media->createCustomSize($this->imageUrl, $width, $height); + + return $this; + } + + public function grayscale() + { + $this->imageUrl = $this->media->createGrayscale($this->imageUrl); + + return $this; + } + + public function src() + { + # create absolute image url + $absImageUrl = $this->baseUrl . '/' . $this->imageUrl; + + # reset image url + $this->imageUrl = false; + + return $absImageUrl; + } + + /****************** + * DEPRECATED * + * ****************/ + + # deprecated, not in use + public function addEditorJS($JS) + { + $JSfile = $this->getFileUrl($JS); + + if($JSfile) + { + $this->editorJS[] = ''; + } + } + + # deprecated, not in use + public function addEditorInlineJS($JS) + { + $this->editorInlineJS[] = ''; + } + + # deprecated, not in use + public function addEditorCSS($CSS) + { + $CSSfile = $this->getFileUrl($CSS); + + if($CSSfile) + { + $this->editorCSS[] = ''; + } + } + + # deprecated, not in use + public function renderEditorJS() + { + return implode("\n", $this->editorJS) . implode("\n", $this->editorInlineJS); + } + + # deprecated, not in use + public function renderEditorCSS() + { + return implode("\n", $this->editorCSS); + } +} \ No newline at end of file diff --git a/system/typemill/Controllers/Controller.php b/system/typemill/Controllers/Controller.php new file mode 100644 index 0000000..bf68e52 --- /dev/null +++ b/system/typemill/Controllers/Controller.php @@ -0,0 +1,253 @@ +c = $container; + + $this->settings = $container->get('settings'); + + $this->routeParser = $container->get('routeParser'); + + $this->c->get('dispatcher')->dispatch(new OnTwigLoaded(false), 'onTwigLoaded'); + } + + protected function settingActive($setting) + { + if(isset($this->settings[$setting]) && $this->settings[$setting]) + { + return true; + } + + return false; + } + + protected function hasChanged($input, $stored, $field) + { + if(isset($input[$field]) && isset($stored[$field]) && $input[$field] == $stored[$field]) + { + return false; + } + if(!isset($input[$field]) && !isset($input[$field])) + { + return false; + } + return true; + } + + protected function getItem($navigation, $url, $urlinfo) + { + die('getItem in controller'); + + $url = $this->removeEditorFromUrl($url); + + if($url == '/') + { + return $navigation->getHomepageItem($urlinfo['baseurl']); + } + + else + { + $langattr = $this->settings['langattr']; + + # get the first level navigation + $firstLevel = $navigation->getExtendedNavigation($urlinfo, $langattr, '/'); + + + $extendedNavigation = $navigation->getExtendedNavigation($urlinfo, $langattr); + + $pageinfo = $extendedNavigation[$url] ?? false; + if(!$pageinfo) + { + # page not found + return false; + } + + $keyPathArray = explode(".", $pageinfo['keyPath']); + + } + + $draftNavigation = $navigation->getFullDraftNavigation($urlinfo, $langattr); + + $item = $navigation->getItemWithKeyPath($draftNavigation, $keyPathArray, $urlinfo['basepath']); + + return $item; + } + +/* + protected function getFolderForItem($item) + { + if(count($item->keyPathArray) > 1) + { + $segments = explode('/', $item->path); + + return $segments[1]; + } + + # it is a base-item + return '/'; + } +*/ + + protected function getFolderForItem($item) + { + die('moved getFolderForItem from controller to navigation'); + $segments = explode('/', $item->path); + + # we always return base (parent) folder, so for base folder /myfolder the parent is '/'. Makes sure that base-level navigation in navigation cache is deleted + if(isset($segments[2])) + { + return $segments[1]; + } + + return 'navi-draft'; + } + + protected function removeEditorFromUrl($url) + { + die('moved removeEditorFromUrl from controller to navigation'); + + $url = trim($url, '/'); + + $url = str_replace('tm/content/visual', '', $url); + $url = str_replace('tm/content/raw', '', $url); + + $url = trim($url, '/'); + + return '/' . $url; + } + + protected function addDatasets(array $formDefinitions) + { + foreach($formDefinitions as $fieldname => $field) + { + if(isset($field['type']) && $field['type'] == 'fieldset') + { + $formDefinitions[$fieldname]['fields'] = $this->addDatasets($field['fields']); + } + + if(isset($field['type']) && ($field['type'] == 'select' ) ) + { + # always add null as first option in selectboxes. + $options = [null => null]; + + if(isset($field['options']) && is_array($field['options'])) + { + if ($this->has_sequential_numeric_keys($field['options'])) + { + $options = array_merge($options, $field['options']); + } + else + { + $options = $options + $field['options']; + } + } + + if(isset($field['dataset']) && ($field['dataset'] == 'userroles' )) + { + foreach($this->c->get('acl')->getRoles() as $userrole) + { + $options[$userrole] = $userrole; + } + } + + $formDefinitions[$fieldname]['options'] = $options; + } + } + + return $formDefinitions; + } + + # Function to check if an array has sequential numeric keys starting from 0 + protected function has_sequential_numeric_keys($array) + { + $keys = array_keys($array); + return $keys === range(0, count($keys) - 1); + } + + protected function userroleIsAllowed($userrole, $resource, $action) + { + $acl = $this->c->get('acl'); + + if($acl->isAllowed($userrole, $resource, $action)) + { + return true; + } + + return false; + } + + protected function userIsAllowed($username, $pagemeta) + { + if( + isset($pagemeta['meta']['owner']) && + $pagemeta['meta']['owner'] && + $pagemeta['meta']['owner'] !== '' + ) + { + $allowedusers = array_map('trim', explode(",", $pagemeta['meta']['owner'])); + if( + in_array($username, $allowedusers) + ) + { + return true; + } + } + + return false; + } + + + # used to protect api access, can we do it with middleware? + protected function validateRights($userrole, $resource, $action) + { + # check ownership. THIS WILL FAIL ANYWAY!!! + # MAYBE WE SHOUD ADD THIS CHECK INTO MIDDLEWARE, TOO ? + + $acl = $this->c->get('acl'); + + if($acl->isAllowed($userrole, $resource, $action)) + { + return true; + } + + die("PLEASE UPDATE THE METHOD validateRights in controller.php"); + + $writeMeta = new writeMeta(); + $pagemeta = $writeMeta->getPageMeta($this->settings, $this->item); + + if( + isset($pagemeta['meta']['owner']) && + $pagemeta['meta']['owner'] && + $pagemeta['meta']['owner'] !== '' + ) + { + $allowedusers = array_map('trim', explode(",", $pagemeta['meta']['owner'])); + if( + isset($_SESSION['user']) && + in_array($_SESSION['user'], $allowedusers) + ) + { + return true; + } + } + + return false; + } +} \ No newline at end of file diff --git a/system/typemill/Controllers/ControllerApiAuthorArticle.php b/system/typemill/Controllers/ControllerApiAuthorArticle.php new file mode 100644 index 0000000..bc4fd05 --- /dev/null +++ b/system/typemill/Controllers/ControllerApiAuthorArticle.php @@ -0,0 +1,1165 @@ +getParsedBody(); + $validate = new Validation(); + $validInput = $validate->articlePublish($params); + if($validInput !== true) + { + $errors = $validate->returnFirstValidationErrors($validInput); + $response->getBody()->write(json_encode([ + 'message' => reset($errors), + 'errors' => $errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $urlinfo = $this->c->get('urlinfo'); + $langattr = $this->settings['langattr']; + $navigation = new Navigation(); + $item = $navigation->getItemForUrl($params['url'], $urlinfo, $langattr); + if(!$item) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('page not found'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + # if user is not allowed to perform this action (e.g. not admin) + if(!$this->userroleIsAllowed($request->getAttribute('c_userrole'), 'content', 'publish')) + { + # then check if user is the owner of this content + $meta = new Meta(); + $metadata = $meta->getMetaData($item); + if(!$this->userIsAllowed($request->getAttribute('c_username'), $metadata)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('You do not have enough rights.'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(403); + } + } + + # publish content + $content = new Content($urlinfo['baseurl'], $this->settings, $this->c->get('dispatcher')); + $draftMarkdown = $content->getDraftMarkdown($item); + $publish = $content->publishMarkdown($item, $draftMarkdown); + if($publish !== true) + { + $response->getBody()->write(json_encode([ + 'message' => $publish, + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + $naviFileName = $navigation->getNaviFileNameForPath($item->path); + $navigation->clearNavigation([$naviFileName]); + + $draftNavigation = $navigation->getFullDraftNavigation($urlinfo, $this->settings['langattr']); + $draftNavigation = $navigation->setActiveNaviItemsWithKeyPath($draftNavigation, $item->keyPathArray); + $item = $navigation->getItemWithKeyPath($draftNavigation, $item->keyPathArray); + + if(!isset($this->settings['disableSitemap']) OR !$this->settings['disableSitemap']) + { + $sitemap = new Sitemap(); + $sitemap->updateSitemap($draftNavigation, $urlinfo); + } + + # META is important e.g. for newsletter, so send it, too + $meta = new Meta(); + $metadata = $meta->getMetaData($item); + $metadata = $meta->addMetaDefaults($metadata, $item, $this->settings['author'], $request->getAttribute('c_username')); + $metadata = $meta->addMetaTitleDescription($metadata, $item, $draftMarkdown); + + # dispatch event, e.g. send newsletter and more + $data = [ + 'markdown' => $content->markdownArrayToText($draftMarkdown), + 'item' => $item, + 'metadata' => $metadata, + 'username' => $request->getAttribute('c_username') + ]; + + $message = $this->c->get('dispatcher')->dispatch(new OnPagePublished($data), 'onPagePublished')->getData(); + + # validate message + + $response->getBody()->write(json_encode([ + 'navigation' => $draftNavigation, + 'item' => $item, + 'metadata' => $metadata, + 'message' => $message + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function unpublishArticle(Request $request, Response $response, $args) + { + $params = $request->getParsedBody(); + $validate = new Validation(); + $validInput = $validate->articlePublish($params); + if($validInput !== true) + { + $errors = $validate->returnFirstValidationErrors($validInput); + $response->getBody()->write(json_encode([ + 'message' => reset($errors), + 'errors' => $errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $urlinfo = $this->c->get('urlinfo'); + $langattr = $this->settings['langattr']; + $navigation = new Navigation(); + $item = $navigation->getItemForUrl($params['url'], $urlinfo, $langattr); + if(!$item) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('page not found'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + # if user is not allowed to perform this action (e.g. not admin) + if(!$this->userroleIsAllowed($request->getAttribute('c_userrole'), 'content', 'publish')) + { + # then check if user is the owner of this content + $meta = new Meta(); + $metadata = $meta->getMetaData($item); + if(!$this->userIsAllowed($request->getAttribute('c_username'), $metadata)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('You do not have enough rights.'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(403); + } + } + + # publish content + $content = new Content($urlinfo['baseurl'], $this->settings, $this->c->get('dispatcher')); + $draftMarkdown = $content->getDraftMarkdown($item); + $content->unpublishMarkdown($item, $draftMarkdown); + + $naviFileName = $navigation->getNaviFileNameForPath($item->path); + $navigation->clearNavigation([$naviFileName]); + + $draftNavigation = $navigation->getFullDraftNavigation($urlinfo, $this->settings['langattr']); + $draftNavigation = $navigation->setActiveNaviItemsWithKeyPath($draftNavigation, $item->keyPathArray); + $item = $navigation->getItemWithKeyPath($draftNavigation, $item->keyPathArray); + + if(!isset($this->settings['disableSitemap']) OR !$this->settings['disableSitemap']) + { + $sitemap = new Sitemap(); + $sitemap->updateSitemap($draftNavigation, $urlinfo); + } + + # check if it is a folder and if the folder has published pages. + $message = false; + if($item->elementType == 'folder' && isset($item->folderContent)) + { + foreach($item->folderContent as $folderContent) + { + if($folderContent->status == 'published' OR $folderContent->status == 'modified') + { + $message = Translations::translate('There are published pages within this folder. The pages are not visible on your website anymore.'); + } + } + } + + $data = [ + 'markdown' => $content->markdownArrayToText($draftMarkdown), + 'item' => $item, + 'username' => $request->getAttribute('c_username') + ]; + + # dispatch event + $this->c->get('dispatcher')->dispatch(new OnPageUnpublished($data), 'onPageUnpublished'); + + $response->getBody()->write(json_encode([ + 'message' => $message, + 'navigation' => $draftNavigation, + 'item' => $item + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function updateDraft(Request $request, Response $response, $args) + { + $params = $request->getParsedBody(); + $validate = new Validation(); + $validInput = $validate->articleUpdate($params); + if($validInput !== true) + { + $errors = $validate->returnFirstValidationErrors($validInput); + $response->getBody()->write(json_encode([ + 'message' => reset($errors), + 'errors' => $errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $urlinfo = $this->c->get('urlinfo'); + $langattr = $this->settings['langattr']; + $navigation = new Navigation(); + $item = $navigation->getItemForUrl($params['url'], $urlinfo, $langattr); + if(!$item) + { + $response->getBody()->write(json_encode([ + 'message' => 'page not found', + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + # if user is not allowed to perform this action (e.g. not admin) + if(!$this->userroleIsAllowed($request->getAttribute('c_userrole'), 'content', 'update')) + { + # then check if user is the owner of this content + $meta = new Meta(); + $metadata = $meta->getMetaData($item); + if(!$this->userIsAllowed($request->getAttribute('c_username'), $metadata)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('You do not have enough rights.'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(403); + } + } + + # save draft content + $content = new Content($urlinfo['baseurl'], $this->settings, $this->c->get('dispatcher')); + $oldMarkdown = $content->getDraftMarkdown($item); + $markdown = $params['title'] . PHP_EOL . PHP_EOL . $params['body']; + $markdownArray = $content->markdownTextToArray($markdown); + $content->saveDraftMarkdown($item, $markdownArray); + + $naviFileName = $navigation->getNaviFileNameForPath($item->path); + $navigation->clearNavigation([$naviFileName]); + $draftNavigation = $navigation->getFullDraftNavigation($urlinfo, $this->settings['langattr']); + $draftNavigation = $navigation->setActiveNaviItemsWithKeyPath($draftNavigation, $item->keyPathArray); + $item = $navigation->getItemWithKeyPath($draftNavigation, $item->keyPathArray); + + # refresh content + $draftMarkdown = $content->getDraftMarkdown($item); + $draftMarkdownHtml = $content->addDraftHtml($draftMarkdown); + + $data = [ + 'oldMarkdown' => $content->markdownArrayToText($oldMarkdown), + 'newMarkdown' => $content->markdownArrayToText($draftMarkdown), + 'username' => $request->getAttribute('c_username'), + 'item' => $item, + ]; + + $this->c->get('dispatcher')->dispatch(new OnPageUpdated($data), 'onPageUpdated'); + + $response->getBody()->write(json_encode([ + 'item' => $item, + 'navigation' => $draftNavigation, + 'content' => $draftMarkdownHtml + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function publishDraft(Request $request, Response $response, $args) + { + $params = $request->getParsedBody(); + $validate = new Validation(); + $validInput = $validate->articleUpdate($params); + if($validInput !== true) + { + $errors = $validate->returnFirstValidationErrors($validInput); + $response->getBody()->write(json_encode([ + 'message' => reset($errors), + 'errors' => $errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $urlinfo = $this->c->get('urlinfo'); + $langattr = $this->settings['langattr']; + $navigation = new Navigation(); + $item = $navigation->getItemForUrl($params['url'], $urlinfo, $langattr); + if(!$item) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('page not found'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + # if user is not allowed to perform this action (e.g. not admin) + if(!$this->userroleIsAllowed($request->getAttribute('c_userrole'), 'content', 'update')) + { + # then check if user is the owner of this content + $meta = new Meta(); + $metadata = $meta->getMetaData($item); + if(!$this->userIsAllowed($request->getAttribute('c_username'), $metadata)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('You do not have enough rights.'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(403); + } + } + + # save draft content + $content = new Content($urlinfo['baseurl'], $this->settings, $this->c->get('dispatcher')); + $markdown = $params['title'] . PHP_EOL . PHP_EOL . $params['body']; + $markdownArray = $content->markdownTextToArray($markdown); + $content->publishMarkdown($item, $markdownArray); + + $naviFileName = $navigation->getNaviFileNameForPath($item->path); + $navigation->clearNavigation([$naviFileName]); + + $draftNavigation = $navigation->getFullDraftNavigation($urlinfo, $this->settings['langattr']); + $draftNavigation = $navigation->setActiveNaviItemsWithKeyPath($draftNavigation, $item->keyPathArray); + $item = $navigation->getItemWithKeyPath($draftNavigation, $item->keyPathArray); + + if(!isset($this->settings['disableSitemap']) OR !$this->settings['disableSitemap']) + { + $sitemap = new Sitemap(); + $sitemap->updateSitemap($draftNavigation, $urlinfo); + } + + # refresh content + $draftMarkdown = $content->getDraftMarkdown($item); + $draftMarkdownHtml = $content->addDraftHtml($draftMarkdown); + + # META is important e.g. for newsletter, so send it, too + $meta = new Meta(); + $metadata = $meta->getMetaData($item); + $metadata = $meta->addMetaDefaults($metadata, $item, $this->settings['author'], $request->getAttribute('c_username')); +# $metadata = $meta->addMetaTitleDescription($metadata, $item, $markdownArray); + + # dispatch event, e.g. send newsletter and more + $data = [ + 'markdown' => $content->markdownArrayToText($draftMarkdown), + 'item' => $item, + 'metadata' => $metadata, + 'username' => $request->getAttribute('c_username') + ]; + $this->c->get('dispatcher')->dispatch(new OnPagePublished($data), 'onPagePublished'); + + $response->getBody()->write(json_encode([ + 'item' => $item, + 'navigation' => $draftNavigation, + 'content' => $draftMarkdownHtml + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function discardArticleChanges(Request $request, Response $response, $args) + { + $params = $request->getParsedBody(); + $validate = new Validation(); + $validInput = $validate->articlePublish($params); + if($validInput !== true) + { + $errors = $validate->returnFirstValidationErrors($validInput); + $response->getBody()->write(json_encode([ + 'message' => reset($errors), + 'errors' => $errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $urlinfo = $this->c->get('urlinfo'); + $langattr = $this->settings['langattr']; + $navigation = new Navigation(); + $item = $navigation->getItemForUrl($params['url'], $urlinfo, $langattr); + if(!$item) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('page not found'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + # if user is not allowed to perform this action (e.g. not admin) + if(!$this->userroleIsAllowed($request->getAttribute('c_userrole'), 'content', 'update')) + { + # then check if user is the owner of this content + $meta = new Meta(); + $metadata = $meta->getMetaData($item); + if(!$this->userIsAllowed($request->getAttribute('c_username'), $metadata)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('You do not have enough rights.'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(403); + } + } + + # publish content + $content = new Content($urlinfo['baseurl'], $this->settings, $this->c->get('dispatcher')); + $content->deleteDraft($item); + + $naviFileName = $navigation->getNaviFileNameForPath($item->path); + $navigation->clearNavigation([$naviFileName]); + + $draftNavigation = $navigation->getFullDraftNavigation($urlinfo, $this->settings['langattr']); + $draftNavigation = $navigation->setActiveNaviItemsWithKeyPath($draftNavigation, $item->keyPathArray); + $item = $navigation->getItemWithKeyPath($draftNavigation, $item->keyPathArray); + + # refresh content + $draftMarkdown = $content->getDraftMarkdown($item); + $draftMarkdownHtml = $content->addDraftHtml($draftMarkdown); + + # dispatch event, e.g. send newsletter and more + $data = [ + 'oldMarkdown' => false, + 'newMarkdown' => $content->markdownArrayToText($draftMarkdown), + 'username' => $request->getAttribute('c_username'), + 'item' => $item, + ]; + + $this->c->get('dispatcher')->dispatch(new OnPageDiscard($data), 'onPageDiscard'); + + $response->getBody()->write(json_encode([ + 'item' => $item, + 'navigation' => $draftNavigation, + 'content' => $draftMarkdownHtml + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function createArticle(Request $request, Response $response, $args) + { + $params = $request->getParsedBody(); + $validate = new Validation(); + $validInput = $validate->navigationItem($params); + if($validInput !== true) + { + $errors = $validate->returnFirstValidationErrors($validInput); + $response->getBody()->write(json_encode([ + 'message' => reset($errors), + 'errors' => $errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + # set variables + $urlinfo = $this->c->get('urlinfo'); + $langattr = $this->settings['langattr'] ?? 'en'; + + # get navigation + $navigation = new Navigation(); + $draftNavigation = $navigation->getFullDraftNavigation($urlinfo, $langattr); + if($params['folder_id'] == 'root') + { + if($params['item_name'] == 'tm') + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('You cannot create an item with the slug /tm in the root folder because this is the system path.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(402); + } + + $folderContent = $draftNavigation; + } + else + { + # get the ids (key path) for item, old folder and new folder + $folderKeyPath = explode('.', $params['folder_id']); + + # get the item from structure + $folder = $navigation->getItemWithKeyPath($draftNavigation, $folderKeyPath); + + if(!$folder) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('We could not find this page. Please refresh and try again.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + $folderContent = $folder->folderContent; + } + + $slug = Slug::createSlug($params['item_name'], $langattr); + + # iterate through the whole content of the new folder + $index = 0; + $writeError = false; + $folderPath = isset($folder) ? $folder->path : ''; + $storage = new StorageWrapper('\Typemill\Models\Storage'); + + foreach($folderContent as $folderItem) + { + # check, if the same name as new item, then return an error + if($folderItem->slug == $slug) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('There is already a page with this name. Please choose another name.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(402); + } + + # rename files just in case that index is not in line (because file has been moved before) + if(!$storage->moveContentFile($folderItem, $folderPath, $index)) + { + $writeError = true; + } + $index++; + } + + if($writeError) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Something went wrong. Please refresh the page and check, if all folders and files are writable.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + # add prefix number to the name + $namePath = $index > 9 ? $index . '-' . $slug : '0' . $index . '-' . $slug; + + # create default content + $markdown = '# ' . $params['item_name'] . '/n/n' . 'Content'; + $content = json_encode(['# ' . $params['item_name'], 'Content']); + + # for initial metadata + $meta = new Meta(); + + if($params['type'] == 'file') + { + if(!$storage->writeFile('contentFolder', $folderPath, $namePath . '.txt', $content)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('We could not create the file. Please refresh the page and check, if all folders and files are writable.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + $metadata = $meta->createInitialMeta($request->getAttribute('c_username'), $params['item_name']); + + $storage->updateYaml('contentFolder', $folderPath, $namePath . '.yaml', $metadata); + } + elseif($params['type'] == 'folder') + { + if(!$storage->checkFolder('contentFolder', $folderPath, $namePath)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('We could not create the folder. Please refresh the page and check, if all folders and files are writable.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + $storage->writeFile('contentFolder', $folderPath . DIRECTORY_SEPARATOR . $namePath, 'index.txt', $content); + + $metadata = $meta->createInitialMeta($request->getAttribute('c_username'), $params['item_name']); + + $storage->updateYaml('contentFolder', $folderPath . DIRECTORY_SEPARATOR . $namePath, 'index.yaml', $metadata); + + # always redirect to a folder +# $url = $urlinfo['baseurl'] . '/tm/content/' . $this->settings['editor'] . $folder->urlRelWoF . '/' . $slug; + } + + $itempath = $folderPath . DIRECTORY_SEPARATOR . $namePath; + $naviFileName = $navigation->getNaviFileNameForPath($itempath); + + $navigation->clearNavigation([$naviFileName, $naviFileName . '-extended']); + $draftNavigation = $navigation->getFullDraftNavigation($urlinfo, $this->settings['langattr']); + + $data = [ + 'markdown' => $markdown, + 'metadata' => $metadata, + 'itempath' => $itempath, + 'username' => $request->getAttribute('c_username') + ]; + + $this->c->get('dispatcher')->dispatch(new OnPageCreated($data), 'onPageCreated'); + + $response->getBody()->write(json_encode([ + 'navigation' => $draftNavigation, + 'message' => '', + 'url' => false + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function createPost(Request $request, Response $response, $args) + { + $params = $request->getParsedBody(); + $validate = new Validation(); + $validInput = $validate->navigationItem($params); + if($validInput !== true) + { + $errors = $validate->returnFirstValidationErrors($validInput); + $response->getBody()->write(json_encode([ + 'message' => reset($errors), + 'errors' => $errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + # set variables + $urlinfo = $this->c->get('urlinfo'); + $langattr = $this->settings['langattr'] ?? 'en'; + + # get navigation + $navigation = new Navigation(); + $draftNavigation = $navigation->getFullDraftNavigation($urlinfo, $langattr); + if($params['folder_id'] == 'root') + { + $folderContent = $draftNavigation; + } + else + { + # get the ids (key path) for item, old folder and new folder + $folderKeyPath = explode('.', $params['folder_id']); + + # get the item from structure + $folder = $navigation->getItemWithKeyPath($draftNavigation, $folderKeyPath); + + if(!$folder) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('We could not find this page. Please refresh and try again.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + $folderContent = $folder->folderContent; + } + + $slug = Slug::createSlug($params['item_name'], $langattr); + + # iterate through the whole content of the new folder + $index = 0; + $writeError = false; + $folderPath = isset($folder) ? $folder->path : ''; + $storage = new StorageWrapper('\Typemill\Models\Storage'); + + foreach($folderContent as $folderItem) + { + # check, if the same name as new item, then return an error + if($folderItem->slug == $slug) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('There is already a page with this name. Please choose another name.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(402); + } + } + + # add prefix date to the name + $namePath = date("YmdHi") . '-' . $slug; + + # create default content + $markdown = '# ' . $params['item_name'] . '/n/n' . 'Content'; + $content = json_encode(['# ' . $params['item_name'], 'Content']); + + # for initial metadata + $meta = new Meta(); + + if($params['type'] == 'file') + { + if(!$storage->writeFile('contentFolder', $folderPath, $namePath . '.txt', $content)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('We could not create the file. Please refresh the page and check, if all folders and files are writable.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + $metadata = $meta->createInitialMeta($request->getAttribute('c_username'), $params['item_name']); + + $storage->updateYaml('contentFolder', $folderPath, $namePath . '.yaml', $metadata); + } + elseif($params['type'] == 'folder') + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('We cannot create a folder, only files.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(402); + } + + $itempath = $folderPath . DIRECTORY_SEPARATOR . $namePath; + $naviFileName = $navigation->getNaviFileNameForPath($itempath); + + $navigation->clearNavigation([$naviFileName, $naviFileName . '-extended']); + $draftNavigation = $navigation->getFullDraftNavigation($urlinfo, $this->settings['langattr']); +# $item = $navigation->getItemForUrl($url, $urlinfo, $langattr); +# $draftNavigation = $navigation->setActiveNaviItemsWithKeyPath($draftNavigation, $item->keyPathArray); + + $item = $draftNavigation; + if($folder) + { + $item = $navigation->getItemWithKeyPath($draftNavigation, $folder->keyPathArray); + } + + $data = [ + 'markdown' => $markdown, + 'metadata' => $metadata, + 'itempath' => $itempath, + 'username' => $request->getAttribute('c_username') + ]; + + $this->c->get('dispatcher')->dispatch(new OnPageCreated($data), 'onPageCreated'); + + $response->getBody()->write(json_encode([ + 'navigation' => $draftNavigation, + 'item' => $item + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function renameArticle(Request $request, Response $response, $args) + { + $params = $request->getParsedBody(); + $validate = new Validation(); + $validInput = $validate->articleRename($params); + if($validInput !== true) + { + $errors = $validate->returnFirstValidationErrors($validInput); + $response->getBody()->write(json_encode([ + 'message' => reset($errors), + 'errors' => $errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $urlinfo = $this->c->get('urlinfo'); + $langattr = $this->settings['langattr']; + $navigation = new Navigation(); + $item = $navigation->getItemForUrl($params['url'], $urlinfo, $langattr); + if(!$item) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('page not found'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + # if user is not allowed to perform this action (e.g. not admin) + if(!$this->userroleIsAllowed($request->getAttribute('c_userrole'), 'content', 'update')) + { + # then check if user is the owner of this content + $meta = new Meta(); + $metadata = $meta->getMetaData($item); + if(!$this->userIsAllowed($request->getAttribute('c_username'), $metadata)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('You do not have enough rights.'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(403); + } + } + + # check if name exists + $parentUrl = str_replace($item->slug, '', $item->urlRelWoF); + if($parentUrl == '/') + { + $parentItem = new \stdClass; + $parentItem->folderContent = $navigation->getDraftNavigation($urlinfo, $this->settings['langattr'], '/'); + } + else + { + $parentItem = $navigation->getItemForUrl($parentUrl, $urlinfo, $langattr); + } + + foreach($parentItem->folderContent as $sibling) + { + if($sibling->slug == $params['slug']) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('There is already a page with that slug'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(402); + } + } + + $navigation->renameItem($item, $params['slug']); + + $naviFileName = $navigation->getNaviFileNameForPath($item->path); + $navigation->clearNavigation([$naviFileName, $naviFileName . '-extended']); + + $draftNavigation = $navigation->getFullDraftNavigation($urlinfo, $this->settings['langattr']); + $draftNavigation = $navigation->setActiveNaviItemsWithKeyPath($draftNavigation, $item->keyPathArray); + $newitem = $navigation->getItemWithKeyPath($draftNavigation, $item->keyPathArray); + + if(!isset($this->settings['disableSitemap']) OR !$this->settings['disableSitemap']) + { + $sitemap = new Sitemap(); + $sitemap->updateSitemap($draftNavigation, $urlinfo); + } + + # create the new url for redirects + $newUrlRel = str_replace($newitem->slug, $params['slug'], $newitem->urlRelWoF); + $url = $urlinfo['baseurl'] . '/tm/content/' . $this->settings['editor'] . $newUrlRel; + + $data = [ + 'item' => $item, + 'newUrl' => $newUrlRel + ]; + $this->c->get('dispatcher')->dispatch(new OnPageRenamed($data), 'onPageRenamed'); + + $response->getBody()->write(json_encode([ + 'navigation' => $draftNavigation, + 'message' => '', + 'url' => $url + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function sortArticle(Request $request, Response $response, $args) + { + $params = $request->getParsedBody(); + $validate = new Validation(); + $validInput = $validate->navigationSort($params); + if($validInput !== true) + { + $errors = $validate->returnFirstValidationErrors($validInput); + $response->getBody()->write(json_encode([ + 'message' => reset($errors), + 'errors' => $errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $itemKeyPath = explode('.', $params['item_id']); + $parentKeyFrom = explode('.', $params['parent_id_from']); + $parentKeyTo = explode('.', $params['parent_id_to']); + + # get navigation + $urlinfo = $this->c->get('urlinfo'); + $dispatchurl = false; # without editor + $redirecturl = false; # with editor + $langattr = $this->settings['langattr']; + $navigation = new Navigation(); + $item = $navigation->getItemForUrl($params['url'], $urlinfo, $langattr); + if(!$item) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('page not found'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + # if user is not allowed to perform this action (e.g. not admin) + if(!$this->userroleIsAllowed($request->getAttribute('c_userrole'), 'content', 'update')) + { + # then check if user is the owner of this content + $meta = new Meta(); + $metadata = $meta->getMetaData($item); + if(!$this->userIsAllowed($request->getAttribute('c_username'), $metadata)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('You do not have enough rights.'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(403); + } + } + +/* + $extendedNavigation = $navigation->getExtendedNavigation($urlinfo, $langattr); + $pageinfo = $extendedNavigation[$params['url']] ?? false; + if(!$pageinfo) + { + $response->getBody()->write(json_encode([ + 'message' => 'page not found', + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } +*/ + $draftNavigation = $navigation->getFullDraftNavigation($urlinfo, $langattr); + $clearFullNavi = true; + + # if an item is moved to the first level + if($params['parent_id_to'] == '') + { + # create empty and default values so that the logic below still works + $newFolder = new \stdClass(); + $newFolder->path = ''; + $folderContent = $draftNavigation; + } + else + { + # get the target folder from navigation + $newFolder = $navigation->getItemWithKeyPath($draftNavigation, $parentKeyTo); + + # get the content of the target folder + $folderContent = $newFolder->folderContent; + } + + # if the item has been moved within the same folder + if($params['parent_id_from'] == $params['parent_id_to']) + { + if($params['parent_id_to'] != '') + { + # we do not have to generate the full navigation, only this part + $clearFullNavi = false; + $naviFileName = $navigation->getNaviFileNameForPath($item->path); + } + + # get key of item + $itemKey = end($itemKeyPath); + reset($itemKeyPath); + + # delete item from folderContent + unset($folderContent[$itemKey]); + } + else + { + # we always need to know the new url to get and dispatch the new item later + $dispatchurl = $newFolder->urlRelWoF . '/' . $item->slug; + + # an active file has been moved to another folder, so send new url with response + if($params['active'] == 'active') + { + # we only want to send the new url to redirect in frontend if user is on page. + $redirecturl = $urlinfo['baseurl'] . '/tm/content/' . $this->settings['editor'] . $newFolder->urlRelWoF . '/' . $item->slug; + } + } + + # add item to newFolder + array_splice($folderContent, $params['index_new'], 0, array($item)); + + # move and rename files in the new folder + $index = 0; + $writeError = false; + $storage = new StorageWrapper('\Typemill\Models\Storage'); + foreach($folderContent as $folderItem) + { + if(!$storage->moveContentFile($folderItem, $newFolder->path, $index)) + { + $writeError = true; + } + $index++; + } + if($writeError) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Something went wrong. Please refresh the page and check, if all folders and files are writable.'), + 'navigation' => $draftNavigation, + 'url' => false + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + # refresh navigation and item + if($clearFullNavi) + { + $navigation->clearNavigation(); + } + else + { + $navigation->clearNavigation([$naviFileName, $naviFileName . '-extended']); + } + $draftNavigation = $navigation->getFullDraftNavigation($urlinfo, $langattr); + + # update the sitemap + if(!isset($this->settings['disableSitemap']) OR !$this->settings['disableSitemap']) + { + $sitemap = new Sitemap(); + $sitemap->updateSitemap($draftNavigation, $urlinfo); + } + + # get the new item to dispatch it + $newurl = $dispatchurl ? $dispatchurl : $params['url']; + $newitem = $navigation->getItemForUrl($newurl, $urlinfo, $langattr); + + $data = [ + 'olditem' => $item, + 'newitem' => $newitem + ]; + + $this->c->get('dispatcher')->dispatch(new OnPageSorted($data), 'onPageSorted'); + + $response->getBody()->write(json_encode([ + 'navigation' => $draftNavigation, + 'message' => '', + 'url' => $redirecturl + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function deleteArticle(Request $request, Response $response, $args) + { + $params = $request->getParsedBody(); + $validate = new Validation(); + $validInput = $validate->articlePublish($params); + if($validInput !== true) + { + $errors = $validate->returnFirstValidationErrors($validInput); + $response->getBody()->write(json_encode([ + 'message' => reset($errors), + 'errors' => $errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $urlinfo = $this->c->get('urlinfo'); + $langattr = $this->settings['langattr']; + $navigation = new Navigation(); + $item = $navigation->getItemForUrl($params['url'], $urlinfo, $langattr); + if(!$item) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('page not found'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + # if user is not allowed to perform this action (e.g. not admin) + if(!$this->userroleIsAllowed($request->getAttribute('c_userrole'), 'content', 'delete')) + { + # then check if user is the owner of this content + $meta = new Meta(); + $metadata = $meta->getMetaData($item); + if(!$this->userIsAllowed($request->getAttribute('c_username'), $metadata)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('You do not have enough rights.'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(403); + } + } + + $content = new Content($urlinfo['baseurl'], $this->settings, $this->c->get('dispatcher')); + + # check if it is a folder and if the folder has published pages. + if($item->elementType == 'folder') + { + # check if folder has published pages + if($content->hasPublishedItems($item)) + { + $result = Translations::translate('The folder contains published pages. Please unpublish or delete them first.'); + } + else + { + $result = $content->deleteFolder($item); + } + } + else + { + $result = $content->deletePage($item); + } + + if($result !== true) + { + $response->getBody()->write(json_encode([ + 'message' => $result, + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + if(count($item->keyPathArray) == 1) + { + # if item on base level is deleted, clear the whole navigation + $navigation->clearNavigation(); + } + else + { + $naviFileName = $navigation->getNaviFileNameForPath($item->path); + $navigation->clearNavigation([$naviFileName, $naviFileName . '-extended']); + } + + $draftNavigation = $navigation->getFullDraftNavigation($urlinfo, $this->settings['langattr']); + + if(!isset($this->settings['disableSitemap']) OR !$this->settings['disableSitemap']) + { + $sitemap = new Sitemap(); + $sitemap->updateSitemap($draftNavigation, $urlinfo); + } + + # check if it is a subfile or subfolder and set the redirect-url to the parent item + $url = $urlinfo['baseurl'] . '/tm/content/' . $this->settings['editor']; + if(count($item->keyPathArray) > 1) + { + array_pop($item->keyPathArray); + + $parentItem = $navigation->getItemWithKeyPath($draftNavigation, $item->keyPathArray); + + if($parentItem) + { + # an active file has been moved to another folder + $url .= $parentItem->urlRelWoF; + } + } + + # dispatch event + $this->c->get('dispatcher')->dispatch(new OnPageDeleted($item), 'onPageDeleted'); + + $response->getBody()->write(json_encode([ + 'url' => $url + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } +} \ No newline at end of file diff --git a/system/typemill/Controllers/ControllerApiAuthorBlock.php b/system/typemill/Controllers/ControllerApiAuthorBlock.php new file mode 100644 index 0000000..a6a3ed7 --- /dev/null +++ b/system/typemill/Controllers/ControllerApiAuthorBlock.php @@ -0,0 +1,485 @@ +getParsedBody(); + + $validate = new Validation(); + $validInput = $validate->blockInput($params); + if($validInput !== true) + { + $errors = $validate->returnFirstValidationErrors($validInput); + $response->getBody()->write(json_encode([ + 'message' => reset($errors), + 'errors' => $errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $urlinfo = $this->c->get('urlinfo'); + $langattr = $this->settings['langattr']; + $navigation = new Navigation(); + $item = $navigation->getItemForUrl($params['url'], $urlinfo, $langattr); + if(!$item) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('page not found'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + # if user is not allowed to perform this action (e.g. not admin) + if(!$this->userroleIsAllowed($request->getAttribute('c_userrole'), 'content', 'update')) + { + # then check if user is the owner of this content + $meta = new Meta(); + $metadata = $meta->getMetaData($item); + if(!$this->userIsAllowed($request->getAttribute('c_username'), $metadata)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('You do not have enough rights.'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(403); + } + } + + $content = new Content($urlinfo['baseurl'], $this->settings, $this->c->get('dispatcher')); + $draftMarkdown = $content->getDraftMarkdown($item); + $oldMarkdown = $draftMarkdown; + + # if it is a new content-block + if($params['block_id'] > 9999) + { + # set the id of the markdown-block (it will be one more than the actual array, so count is perfect) + $id = count($draftMarkdown); + + # add the new markdown block to the page content + $draftMarkdown[] = $params['markdown']; + } + elseif(($params['block_id'] == 0) OR !isset($draftMarkdown[$params['block_id']])) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Block-id not found.'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + else + { + # insert new markdown block + array_splice( $draftMarkdown, $params['block_id'], 0, $params['markdown'] ); + $id = $params['block_id']; + } + + $store = $content->saveDraftMarkdown($item, $draftMarkdown); + if($store !== true) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('We could not store the content: ') . $store, + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + $draftMarkdownHtml = $content->addDraftHtml($draftMarkdown); + + $onPageUpdated = [ + 'oldMarkdown' => $content->markdownArrayToText($oldMarkdown), + 'newMarkdown' => $content->markdownArrayToText($draftMarkdown), + 'username' => $request->getAttribute('c_username'), + 'item' => $item, + ]; + $this->c->get('dispatcher')->dispatch(new OnPageUpdated($onPageUpdated), 'onPageUpdated'); + + # if it was published before, then we need to refresh the navigation + if($item->status == 'published') + { + $naviFileName = $navigation->getNaviFileNameForPath($item->path); + $navigation->clearNavigation([$naviFileName]); + + $draftNavigation = $navigation->getFullDraftNavigation($urlinfo, $this->settings['langattr']); + $draftNavigation = $navigation->setActiveNaviItemsWithKeyPath($draftNavigation, $item->keyPathArray); + $item = $navigation->getItemWithKeyPath($draftNavigation, $item->keyPathArray); + + $response->getBody()->write(json_encode([ + 'content' => $draftMarkdownHtml, + 'navigation' => $draftNavigation, + 'item' => $item, + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + $response->getBody()->write(json_encode([ + 'content' => $draftMarkdownHtml, + 'navigation' => false, + 'item' => false, + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function moveBlock(Request $request, Response $response, $args) + { + $params = $request->getParsedBody(); + $validate = new Validation(); + $validInput = $validate->blockMove($params); + if($validInput !== true) + { + $errors = $validate->returnFirstValidationErrors($validInput); + $response->getBody()->write(json_encode([ + 'message' => reset($errors), + 'errors' => $errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $urlinfo = $this->c->get('urlinfo'); + $langattr = $this->settings['langattr']; + $navigation = new Navigation(); + $item = $navigation->getItemForUrl($params['url'], $urlinfo, $langattr); + if(!$item) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('page not found'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + # if user is not allowed to perform this action (e.g. not admin) + if(!$this->userroleIsAllowed($request->getAttribute('c_userrole'), 'content', 'update')) + { + # then check if user is the owner of this content + $meta = new Meta(); + $metadata = $meta->getMetaData($item); + if(!$this->userIsAllowed($request->getAttribute('c_username'), $metadata)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('You do not have enough rights.'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(403); + } + } + + $content = new Content($urlinfo['baseurl'], $this->settings, $this->c->get('dispatcher')); + + $draftMarkdown = $content->getDraftMarkdown($item); + $oldMarkdown = $draftMarkdown; + + if(!isset($draftMarkdown[$params['index_old']])) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Block-id not found.'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + $extract = array_splice($draftMarkdown, $params['index_old'], 1); + array_splice($draftMarkdown, $params['index_new'], 0, $extract); + + $store = $content->saveDraftMarkdown($item, $draftMarkdown); + if($store !== true) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('We could not store the content: ') . $store, + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + $draftMarkdownHtml = $content->addDraftHtml($draftMarkdown); + + $onPageUpdated = [ + 'oldMarkdown' => $content->markdownArrayToText($oldMarkdown), + 'newMarkdown' => $content->markdownArrayToText($draftMarkdown), + 'username' => $request->getAttribute('c_username'), + 'item' => $item, + ]; + $this->c->get('dispatcher')->dispatch(new OnPageUpdated($onPageUpdated), 'onPageUpdated'); + + # if it was published before, then we need to refresh the navigation + if($item->status == 'published') + { + $naviFileName = $navigation->getNaviFileNameForPath($item->path); + $navigation->clearNavigation([$naviFileName]); + + $draftNavigation = $navigation->getFullDraftNavigation($urlinfo, $this->settings['langattr']); + $draftNavigation = $navigation->setActiveNaviItemsWithKeyPath($draftNavigation, $item->keyPathArray); + $item = $navigation->getItemWithKeyPath($draftNavigation, $item->keyPathArray); + + $response->getBody()->write(json_encode([ + 'content' => $draftMarkdownHtml, + 'navigation' => $draftNavigation, + 'item' => $item, + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + $response->getBody()->write(json_encode([ + 'content' => $draftMarkdownHtml, + 'navigation' => false, + 'item' => false, + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function updateBlock(Request $request, Response $response, $args) + { + $params = $request->getParsedBody(); + $validate = new Validation(); + $validInput = $validate->blockInput($params); + if($validInput !== true) + { + $errors = $validate->returnFirstValidationErrors($validInput); + $response->getBody()->write(json_encode([ + 'message' => reset($errors), + 'errors' => $errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $urlinfo = $this->c->get('urlinfo'); + $langattr = $this->settings['langattr']; + $navigation = new Navigation(); + $item = $navigation->getItemForUrl($params['url'], $urlinfo, $langattr); + if(!$item) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('page not found'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + # if user is not allowed to perform this action (e.g. not admin) + if(!$this->userroleIsAllowed($request->getAttribute('c_userrole'), 'content', 'update')) + { + # then check if user is the owner of this content + $meta = new Meta(); + $metadata = $meta->getMetaData($item); + if(!$this->userIsAllowed($request->getAttribute('c_username'), $metadata)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('You do not have enough rights.'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(403); + } + } + + $content = new Content($urlinfo['baseurl'], $this->settings, $this->c->get('dispatcher')); + + $draftMarkdown = $content->getDraftMarkdown($item); + $oldMarkdown = $draftMarkdown; + + if(!isset($draftMarkdown[$params['block_id']])) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Block-id not found.'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + elseif($params['block_id'] == 0) + { + # if it is the title, then delete the "# " if it exists + $blockMarkdown = trim($params['markdown'], "#"); + + # store the markdown-headline in a separate variable + $blockMarkdownTitle = '# ' . trim($blockMarkdown); + + # add the markdown-headline to the page-markdown + $draftMarkdown[0] = $blockMarkdownTitle; + } + else + { + # update the markdown block in the page content + $draftMarkdown[$params['block_id']] = $params['markdown']; + } + + $store = $content->saveDraftMarkdown($item, $draftMarkdown); + if($store !== true) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('We could not store the content: ') . $store, + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + $draftMarkdownHtml = $content->addDraftHtml($draftMarkdown); + + $onPageUpdated = [ + 'oldMarkdown' => $content->markdownArrayToText($oldMarkdown), + 'newMarkdown' => $content->markdownArrayToText($draftMarkdown), + 'username' => $request->getAttribute('c_username'), + 'item' => $item, + ]; + $this->c->get('dispatcher')->dispatch(new OnPageUpdated($onPageUpdated), 'onPageUpdated'); + + # if it was published before, then we need to refresh the navigation + if($item->status == 'published') + { + $naviFileName = $navigation->getNaviFileNameForPath($item->path); + $navigation->clearNavigation([$naviFileName]); + + $draftNavigation = $navigation->getFullDraftNavigation($urlinfo, $this->settings['langattr']); + $draftNavigation = $navigation->setActiveNaviItemsWithKeyPath($draftNavigation, $item->keyPathArray); + $item = $navigation->getItemWithKeyPath($draftNavigation, $item->keyPathArray); + + $response->getBody()->write(json_encode([ + 'content' => $draftMarkdownHtml, + 'navigation' => $draftNavigation, + 'item' => $item, + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + $response->getBody()->write(json_encode([ + 'content' => $draftMarkdownHtml, + 'navigation' => false, + 'item' => false, + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function deleteBlock(Request $request, Response $response, $args) + { + $params = $request->getParsedBody(); + $validate = new Validation(); + $validInput = $validate->blockDelete($params); + if($validInput !== true) + { + $errors = $validate->returnFirstValidationErrors($validInput); + $response->getBody()->write(json_encode([ + 'message' => reset($errors), + 'errors' => $errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $urlinfo = $this->c->get('urlinfo'); + $langattr = $this->settings['langattr']; + $navigation = new Navigation(); + $item = $navigation->getItemForUrl($params['url'], $urlinfo, $langattr); + if(!$item) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('page not found'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + # if user is not allowed to perform this action (e.g. not admin) + if(!$this->userroleIsAllowed($request->getAttribute('c_userrole'), 'content', 'update')) + { + # then check if user is the owner of this content + $meta = new Meta(); + $metadata = $meta->getMetaData($item); + if(!$this->userIsAllowed($request->getAttribute('c_username'), $metadata)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('You do not have enough rights.'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(403); + } + } + + $content = new Content($urlinfo['baseurl'], $this->settings, $this->c->get('dispatcher')); + + $draftMarkdown = $content->getDraftMarkdown($item); + $oldMarkdown = $draftMarkdown; + + # check if id exists + if(!isset($draftMarkdown[$params['block_id']])) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('The ID of the content-block is wrong.'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + $contentBlock = $draftMarkdown[$params['block_id']]; + + # delete the block + unset($draftMarkdown[$params['block_id']]); + $draftMarkdown = array_values($draftMarkdown); + + $store = $content->saveDraftMarkdown($item, $draftMarkdown); + if($store !== true) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('We could not store the content: ') . $store, + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + $draftMarkdownHtml = $content->addDraftHtml($draftMarkdown); + + $onPageUpdated = [ + 'oldMarkdown' => $content->markdownArrayToText($oldMarkdown), + 'newMarkdown' => $content->markdownArrayToText($draftMarkdown), + 'username' => $request->getAttribute('c_username'), + 'item' => $item, + ]; + $this->c->get('dispatcher')->dispatch(new OnPageUpdated($onPageUpdated), 'onPageUpdated'); + + # if it was published before, then we need to refresh the navigation + if($item->status == 'published') + { + $naviFileName = $navigation->getNaviFileNameForPath($item->path); + $navigation->clearNavigation([$naviFileName]); + + $draftNavigation = $navigation->getFullDraftNavigation($urlinfo, $this->settings['langattr']); + $draftNavigation = $navigation->setActiveNaviItemsWithKeyPath($draftNavigation, $item->keyPathArray); + $item = $navigation->getItemWithKeyPath($draftNavigation, $item->keyPathArray); + + $response->getBody()->write(json_encode([ + 'content' => $draftMarkdownHtml, + 'navigation' => $draftNavigation, + 'item' => $item, + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + $response->getBody()->write(json_encode([ + 'content' => $draftMarkdownHtml, + 'navigation' => false, + 'item' => false, + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } +} \ No newline at end of file diff --git a/system/typemill/Controllers/ControllerApiAuthorMeta.php b/system/typemill/Controllers/ControllerApiAuthorMeta.php new file mode 100644 index 0000000..9465712 --- /dev/null +++ b/system/typemill/Controllers/ControllerApiAuthorMeta.php @@ -0,0 +1,369 @@ +getQueryParams()['url'] ?? false; + + $urlinfo = $this->c->get('urlinfo'); + $langattr = $this->settings['langattr']; + $navigation = new Navigation(); + $item = $navigation->getItemForUrl($url, $urlinfo, $langattr); + if(!$item) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('page not found'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + $meta = new Meta(); + + $metadata = $meta->getMetaData($item); + + if(!$metadata or !isset($metadata['meta']['owner']) OR !$metadata['meta']['owner']) + { + $metadata = $meta->addMetaDefaults($metadata, $item, $this->settings['author'], $request->getAttribute('c_username')); + } + + #fix for version 1 because owner in meta is often 'false' + if(!isset($metadata['meta']['owner']) OR !$metadata['meta']['owner']) + { + $metadata = $meta->addMetaDefaults($metadata, $item, $this->settings['author'], $request->getAttribute('c_username')); + } + + # if user is not allowed to perform this action (e.g. not admin) + if(!$this->userroleIsAllowed($request->getAttribute('c_userrole'), 'content', 'read')) + { + # then check if user is the owner of this content + if(!$this->userIsAllowed($request->getAttribute('c_username'), $metadata)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('You do not have enough rights.'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(403); + } + } + + # if item is a folder + if($item->elementType == "folder" && isset($item->contains)) + { + $metadata['meta']['contains'] = isset($metadata['meta']['contains']) ? $metadata['meta']['contains'] : $item->contains; + + # get global metadefinitions + $metadefinitions = $meta->getMetaDefinitions($this->settings, $folder = true); + } + else + { + # get global metadefinitions + $metadefinitions = $meta->getMetaDefinitions($this->settings, $folder = false); + } + + # update metadefinitions from plugins. + $metadefinitions = $this->c->get('dispatcher')->dispatch(new OnMetaDefinitionsLoaded($metadefinitions),'onMetaDefinitionsLoaded')->getData(); + + # cleanup metadata to the current metadefinitions (e.g. strip out deactivated plugins) + $metacleared = []; + + # store the metadata-scheme for frontend, so frontend does not use obsolete data + $metascheme = []; + + foreach($metadefinitions as $tabname => $tabfields ) + { + # add userroles and other datasets + $metadefinitions[$tabname]['fields'] = $this->addDatasets($tabfields['fields']); + + $tabfields = $this->flattenTabFields($tabfields['fields'],[]); + + $metacleared[$tabname] = []; + + foreach($tabfields as $fieldname => $fielddefinitions) + { + $metascheme[$tabname][$fieldname] = true; + + $metacleared[$tabname][$fieldname] = isset($metadata[$tabname][$fieldname]) ? $metadata[$tabname][$fieldname] : null; + } + } + + # store the metascheme in cache for frontend +# $writeMeta->updateYaml('cache', 'metatabs.yaml', $metascheme); + + $response->getBody()->write(json_encode([ + 'metadata' => $metacleared, + 'metadefinitions' => $metadefinitions, + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function updateMeta(Request $request, Response $response, $args) + { + $params = $request->getParsedBody(); + $validate = new Validation(); + $validInput = $validate->metaInput($params); + if($validInput !== true) + { + $errors = $validate->returnFirstValidationErrors($validInput); + $response->getBody()->write(json_encode([ + 'message' => reset($errors), + 'errors' => $errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $urlinfo = $this->c->get('urlinfo'); + $langattr = $this->settings['langattr']; + $navigation = new Navigation(); + $item = $navigation->getItemForUrl($params['url'], $urlinfo, $langattr); + + if(!$item) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('page not found'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + $meta = new Meta(); + $metadata = $meta->getMetaData($item); + + # if user is not allowed to perform this action (e.g. not admin) + if(!$this->userroleIsAllowed($request->getAttribute('c_userrole'), 'content', 'update')) + { + # then check if user is the owner of this content + if(!$this->userIsAllowed($request->getAttribute('c_username'), $metadata)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('You do not have enough rights.'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(403); + } + } + + # if item is a folder + if($item->elementType == "folder" && isset($item->contains)) + { + $metadata['meta']['contains'] = isset($metadata['meta']['contains']) ? $metadata['meta']['contains'] : $item->contains; + + # get global metadefinitions + $metadefinitions = $meta->getMetaDefinitions($this->settings, $folder = true); + } + else + { + # get global metadefinitions + $metadefinitions = $meta->getMetaDefinitions($this->settings, $folder = false); + } + + # update metadefinitions from plugins. + $metadefinitions = $this->c->get('dispatcher')->dispatch(new OnMetaDefinitionsLoaded($metadefinitions),'onMetaDefinitionsLoaded')->getData(); + + $tabdefinitions = $metadefinitions[$params['tab']] ?? false; + if(!$tabdefinitions) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Tab not found'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + $tabdefinitions['fields'] = $this->addDatasets($tabdefinitions['fields']); + $tabdefinitions = $this->flattenTabFields($tabdefinitions['fields'], []); + + $validated['data'] = $validate->recursiveValidation($tabdefinitions, $params['data']); + + if(!empty($validate->errors)) + { + $errors[$params['tab']] = $validate->errors; + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Please correct your input.'), + 'errors' => $errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $navigation = new Navigation(); + $naviFileName = $navigation->getNaviFileNameForPath($item->path); + $extended = $navigation->getExtendedNavigation($urlinfo, $this->settings['langattr'], $naviFileName); + $draftNavigation = false; + $updateDraftNavi = false; + + if($params['tab'] == 'meta') + { + # if manual date has been modified + if( $this->hasChanged($params['data'], $metadata['meta'], 'manualdate')) + { + # update the time + $validated['data']['time'] = date('H-i-s', time()); + + # if it is a post, then rename the post + if($item->elementType == "file" && strlen($item->order) == 12) + { + # create file-prefix with date + $metadate = $params['data']['manualdate']; + if($metadate == '') + { + $metadate = $metadata['meta']['created']; + } + $datetime = $metadate . '-' . $params['data']['time']; + $datetime = implode(explode('-', $datetime)); + $datetime = substr($datetime,0,12); + + # create the new filename + $pathWithoutFile = str_replace($item->originalName, "", $item->path); + $newPathWithoutType = $pathWithoutFile . $datetime . '-' . $item->slug; + + $renameresults = $meta->renamePost($item->pathWithoutType, $newPathWithoutType); + + # make sure the whole navigation with base-navigation and folder-navigation is updated. Bad for performance but rare case + $navigation->clearNavigation(); + + # RECREATE ITEM AND NAVIGATION, because we rename filename first and later update the meta-content + $draftNavigation = $navigation->getFullDraftNavigation($urlinfo, $this->settings['langattr']); + $draftNavigation = $navigation->setActiveNaviItemsWithKeyPath($draftNavigation, $item->keyPathArray); + $item = $navigation->getItemWithKeyPath($draftNavigation, $item->keyPathArray); + } + } + + # if folder has changed and contains pages instead of posts or posts instead of pages + if($item->elementType == "folder" && isset($params['data']['contains']) && isset($metadata['meta']['contains']) && $this->hasChanged($params['data'], $metadata['meta'], 'contains')) + { + if($meta->folderContainsFolders($item)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('The folder contains another folder so we cannot transform it. Please make sure there are only files in this folder.'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + if($params['data']['contains'] == "posts" && !$meta->transformPagesToPosts($item)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('One or more files could not be transformed.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + if($params['data']['contains'] == "pages" && !$meta->transformPostsToPages($item)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('One or more files could not be transformed.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + # this will only update the basic draft navigation but we also have to update the folder navigation + # $navigation->clearNavigation([$naviFileName, $naviFileName. "-extened"]); + # other solution: + # $fakeFolderpath = $item->getPath; + # $fakeFolderpath .= '/getMyParent'; + # $naviFolderName = $navigation->getNaviFileNameForPath($fakeFolderPath); + # $navigation->clearNavigation([$naviFolderName, $naviFolderName. "-extened"]); + + # clear the whole navigation to make sure that folder navigation is recreated correctly + $navigation->clearNavigation(); + $updateDraftNavi = true; + } + + # normalize the meta-input + $validated['data']['navtitle'] = (isset($params['data']['navtitle']) && $params['data']['navtitle'] !== null )? $params['data']['navtitle'] : ''; + $validated['data']['hide'] = (isset($params['data']['hide']) && $params['data']['hide'] !== null) ? $params['data']['hide'] : false; + $validated['data']['noindex'] = (isset($params['data']['noindex']) && $params['data']['noindex'] !== null) ? $params['data']['noindex'] : false; + + # input values are empty but entry in structure exists + if( + !$params['data']['hide'] + && $params['data']['navtitle'] == "" + && isset($extended[$item->urlRelWoF]) + ) + { + $navigation->clearNavigation([$naviFileName, $naviFileName . '-extended']); + $updateDraftNavi = true; + } + elseif( + # check if navtitle or hide-value has been changed + ($this->hasChanged($params['data'], $metadata['meta'], 'navtitle')) + OR + ($this->hasChanged($params['data'], $metadata['meta'], 'hide')) + OR + ($this->hasChanged($params['data'], $metadata['meta'], 'noindex')) + ) + { + $navigation->clearNavigation([$naviFileName, $naviFileName . '-extended']); + $updateDraftNavi = true; + } + } + + # add the new/edited metadata + $metadata[$params['tab']] = $validated['data']; + + # store the metadata + $store = $meta->updateMeta($metadata, $item); + + if($store === true) + { + if($updateDraftNavi) + { + $draftNavigation = $navigation->getFullDraftNavigation($urlinfo, $this->settings['langattr']); + $draftNavigation = $navigation->setActiveNaviItemsWithKeyPath($draftNavigation, $item->keyPathArray); + $item = $navigation->getItemWithKeyPath($draftNavigation, $item->keyPathArray); + } + + $response->getBody()->write(json_encode([ + 'navigation' => $draftNavigation, + 'item' => $item, + 'rename' => $renameresults ?? false + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + $response->getBody()->write(json_encode([ + 'message' => $store, + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + # we have to flatten field definitions for tabs if there are fieldsets in it + public function flattenTabFields($tabfields, $flattab, $fieldset = null) + { + foreach($tabfields as $name => $field) + { + if($field['type'] == 'fieldset') + { + $flattab = $this->flattenTabFields($field['fields'], $flattab, $name); + } + else + { + # add the name of the fieldset so we know to which fieldset it belongs for references + if($fieldset) + { + $field['fieldset'] = $fieldset; + } + $flattab[$name] = $field; + } + } + return $flattab; + } +} \ No newline at end of file diff --git a/system/typemill/Controllers/ControllerApiAuthorShortcode.php b/system/typemill/Controllers/ControllerApiAuthorShortcode.php new file mode 100644 index 0000000..b6f80a2 --- /dev/null +++ b/system/typemill/Controllers/ControllerApiAuthorShortcode.php @@ -0,0 +1,31 @@ +c->get('dispatcher')->dispatch(new OnShortcodeFound(['name' => 'registershortcode', 'data' => []]), 'onShortcodeFound')->getData(); + + if(empty($shortcodeData['data'])) + { + $response->getBody()->write(json_encode([ + 'shortcodedata' => false + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + $response->getBody()->write(json_encode([ + 'shortcodedata' => $shortcodeData['data'] + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } +} \ No newline at end of file diff --git a/system/typemill/Controllers/ControllerApiFile.php b/system/typemill/Controllers/ControllerApiFile.php new file mode 100644 index 0000000..2640992 --- /dev/null +++ b/system/typemill/Controllers/ControllerApiFile.php @@ -0,0 +1,468 @@ +getQueryParams()['url'] ?? false; + $path = $request->getQueryParams()['path'] ?? false; + + $storage = new StorageWrapper('\Typemill\Models\Storage'); + + $filelist = $storage->getFileList(); + + $response->getBody()->write(json_encode([ + 'files' => $filelist, + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function getFile(Request $request, Response $response, $args) + { + $name = $request->getQueryParams()['name'] ?? false; + + # VALIDATE NAME + + if(!$name) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Filename is missing.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $storage = new StorageWrapper('\Typemill\Models\Storage'); + + $filedetails = $storage->getFileDetails($name); + + if(!$filedetails) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('No file found.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + $response->getBody()->write(json_encode([ + 'file' => $filedetails, + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function getFileRestrictions(Request $request, Response $response, $args) + { + $params = $request->getQueryParams(); + + $restriction = 'all'; + + $userroles = $this->c->get('acl')->getRoles(); + + if(isset($params['filename']) && $params['filename'] != '') + { + $storage = new StorageWrapper('\Typemill\Models\Storage'); + $restrictions = $storage->getYaml('fileFolder', '', 'filerestrictions.yaml'); + + if(isset($restrictions[$params['filename']])) + { + $restriction = $restrictions[$params['filename']]; + } + } + + $response->getBody()->write(json_encode([ + 'userroles' => $userroles, + 'restriction' => $restriction + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function updateFileRestrictions(Request $request, Response $response, $args) + { + # get params from call + $params = $request->getParsedBody(); + $filename = $params['filename'] ?? false; + $role = $params['role'] ?? false; + + if(!$filename OR !$role) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Filename or userrole is missing.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $userroles = $this->c->get("acl")->getRoles(); + + if($role != 'all' AND !in_array($role, $userroles)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Userrole is unknown.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $storage = new StorageWrapper('\Typemill\Models\Storage'); + $restrictions = $storage->getYaml('fileFolder', '', 'filerestrictions.yaml'); + if(!$restrictions) + { + $restrictions = []; + } + + # make sure you always add live path to the restriction registry, not temporary path + $filename = str_replace('media/tmp', 'media/files', $filename); + + if($role == 'all') + { + unset($restrictions[$filename]); + } + else + { + $restrictions[$filename] = $role; + } + + $result = $storage->updateYaml('fileFolder', '', 'filerestrictions.yaml', $restrictions); + if(!$result) + { + $result = $storage->getError(); + } + + $response->getBody()->write(json_encode([ + 'restrictions' => $restrictions, + 'storage' => $result + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function uploadFile(Request $request, Response $response, $args) + { + $params = $request->getParsedBody(); + + if (!isset($params['file'])) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('File not found.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $size = (int) (strlen(rtrim($params['file'], '=')) * 3 / 4); + $extension = pathinfo($params['name'], PATHINFO_EXTENSION); + $finfo = finfo_open( FILEINFO_MIME_TYPE ); + $mtype = @finfo_file( $finfo, $params['file'] ); + finfo_close($finfo); + + if ($size === 0) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('File is empty.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + # 20 MB (1 byte * 1024 * 1024 * 20 (for 20 MB)) + if ($size > 20971520) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('File is bigger than 20MB.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + # check extension first + if (!$this->checkAllowedExtensions($extension)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Filetype is not allowed.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + # check mimetype and extension if there is a mimetype. + # in some environments the finfo_file does not work with a base64 string. + if($mtype) + { + if(!$this->checkAllowedMimeTypes($mtype, $extension)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('The mime-type is missing, not allowed or does not fit to the file extension.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + } + + $media = new Media(); + + $fileinfo = $media->storeFile($params['file'], $params['name']); + if(!$fileinfo OR !isset($fileinfo['url'])) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('We Could not store file to temporary folder.'), + 'fullerrors' => $media->errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + # if the previous check of the mtype with the base64 string failed, then do it now again with the temporary file + if(!$mtype) + { + $filePath = str_replace('media/files', 'media/tmp', $fileinfo['url']); + $filePath = str_replace('/', DIRECTORY_SEPARATOR, $filePath); + $fullPath = $this->settings['rootPath'] . DIRECTORY_SEPARATOR . $filePath; + $finfo = finfo_open( FILEINFO_MIME_TYPE ); + $mtype = @finfo_file( $finfo, $fullPath ); + finfo_close($finfo); + + if(!$mtype OR !$this->checkAllowedMimeTypes($mtype, $extension)) + { + $media->clearTempFolder($immediate = true); + + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('The mime-type is missing, not allowed or does not fit to the file extension.') . ' mtype: ' . $mtype . ', ext: ' . $extension + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + } + + $filePath = str_replace('media/files', 'media/tmp', $fileinfo['url']); + + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('File has been stored'), + 'fileinfo' => $fileinfo, + 'filepath' => $filePath + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function publishFile(Request $request, Response $response, $args) + { + $params = $request->getParsedBody(); + + if(!isset($params['file']) OR !$params['file']) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('filename is missing.'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $storage = new StorageWrapper('\Typemill\Models\Storage'); + + $result = $storage->publishFile($params['file']); + + if(!$result) + { + $response->getBody()->write(json_encode([ + 'message' => $storage->getError() + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('File saved successfully'), + 'path' => $result, + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function deleteFile(Request $request, Response $response, $args) + { + $params = $request->getParsedBody(); + + if(!isset($params['name'])) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Filename is missing.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $storage = new StorageWrapper('\Typemill\Models\Storage'); + + $deleted = $storage->deleteMediaFile($params['name']); + + if($deleted) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('File deleted successfully.') + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + $response->getBody()->write(json_encode([ + 'message' => $storage->getError() + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + # https://www.sitepoint.com/mime-types-complete-list/ + # https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types + # https://wiki.selfhtml.org/wiki/MIME-Type/%C3%9Cbersicht + # http://www.mime-type.net/application/x-latex/ + private function getAllowedMtypes() + { + return array( + 'application/vnd.oasis.opendocument.chart' => 'odc', + 'application/vnd.oasis.opendocument.formula' => 'odf', + 'application/vnd.oasis.opendocument.graphics' => 'odg', + 'application/vnd.oasis.opendocument.image' => 'odi', + 'application/vnd.oasis.opendocument.presentation' => 'odp', + 'application/vnd.oasis.opendocument.spreadsheet' => 'ods', + 'application/vnd.oasis.opendocument.text' => 'odt', + 'application/vnd.oasis.opendocument.text-master' => 'odm', + + 'application/powerpoint' => 'ppt', + 'application/mspowerpoint' => ['ppt','ppz','pps','pot'], + 'application/x-mspowerpoint' => 'ppt', + 'application/vnd.ms-powerpoint' => 'ppt', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx', + + 'application/x-visio' => ['vsd','vst','msw'], + 'application/vnd.visio' => ['vsd','vst','msw'], + 'application/x-project' => ['mpc','mpt','mpv','mpx'], + 'application/vnd.ms-project' => 'mpp', + + 'application/excel' => ['xla','xlb','xlc','xld','xlk','xll','xlm','xls','xlt','xlv','xlw'], + 'application/msexcel' => ['xls','xla'], + 'application/x-excel' => ['xla','xlb','xlc','xld','xlk','xll','xlm','xls','xlt','xlv','xlw'], + 'application/x-msexcel' => ['xls', 'xla','xlw'], + 'application/vnd.ms-excel' => ['xlb','xlc','xll','xlm','xls','xlw'], + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx', + + 'application/mshelp' => ['hlp','chm'], + 'application/msword' => ['doc','dot'], + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx', + + 'application/vnd.apple.keynote' => 'key', + 'application/vnd.apple.numbers' => 'numbers', + 'application/vnd.apple.pages' => 'pages', + + 'application/x-latex' => ['ltx','latex'], + 'application/pdf' => 'pdf', + + 'application/vnd.amazon.mobi8-ebook' => 'azw3', + 'application/x-mobipocket-ebook' => 'mobi', + 'application/epub+zip' => 'epub', + + 'application/x-gtar' => 'gtar', + 'application/x-tar' => 'tar', + 'application/zip' => 'zip', + 'application/gzip' => 'gz', + 'application/x-gzip' => ['gz', 'gzip'], + 'application/x-compressed' => ['gz','tgz','z','zip'], + 'application/x-zip-compressed' => 'zip', + 'application/vnd.rar' => 'rar', + 'application/x-7z-compressed' => '7z', + + 'application/rtf' => 'rtf', + 'application/x-rtf' => 'rtf', + + 'text/calendar' => 'ics', + 'text/comma-separated-values' => 'csv', + 'text/css' => 'css', + 'text/plain' => 'txt', + 'text/richtext' => 'rtx', + 'text/rtf' => 'rtf', + + 'audio/basic' => ['au','snd'], + 'audio/mpeg' => 'mp3', + 'audio/mp4' => 'mp4', + 'audio/ogg' => 'ogg', + 'audio/wav' => 'wav', + 'audio/x-aiff' => ['aif','aiff','aifc'], + 'audio/x-midi' => ['mid','midi'], + 'audio/x-mpeg' => 'mp2', + 'audio/x-pn-realaudio' => ['ram','ra'], + + 'image/png' => 'png', + 'image/jpeg' => ['jpeg','jpe','jpg'], + 'image/gif' => 'gif', + 'image/tiff' => ['tiff','tif'], + 'image/svg+xml' => 'svg', + 'image/x-icon' => 'ico', + 'image/webp' => 'webp', + + 'video/mpeg' => ['mpeg','mpg','mpe'], + 'video/mp4' => 'mp4', + 'video/ogg' => ['ogg','ogv'], + 'video/quicktime' => ['qt','mov'], + 'video/vnd.vivo' => ['viv','vivo'], + 'video/webm' => 'webm', + 'video/x-msvideo' => 'avi', + 'video/x-sgi-movie' => 'movie', + 'video/3gpp' => '3gp', + ); + } + + protected function checkAllowedMimeTypes($mtype, $extension) + { + $allowedMimes = $this->getAllowedMtypes(); + + if(!isset($allowedMimes[$mtype])) + { + return false; + } + + if( + (is_array($allowedMimes[$mtype]) && !in_array($extension, $allowedMimes[$mtype])) OR + (!is_array($allowedMimes[$mtype]) && $allowedMimes[$mtype] != $extension ) + ) + { + return false; + } + + return true; + } + + protected function checkAllowedExtensions($extension) + { + $mtypes = $this->getAllowedMtypes(); + foreach($mtypes as $mtExtension) + { + if(is_array($mtExtension)) + { + if(in_array($extension, $mtExtension)) + { + return true; + } + } + else + { + if($extension == $mtExtension) + { + return true; + } + } + } + + return false; + } +} diff --git a/system/typemill/Controllers/ControllerApiGlobals.php b/system/typemill/Controllers/ControllerApiGlobals.php new file mode 100644 index 0000000..9bd7c3f --- /dev/null +++ b/system/typemill/Controllers/ControllerApiGlobals.php @@ -0,0 +1,411 @@ +getSystemNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo'), + $dispatcher = $this->c->get('dispatcher'), + $parser = $this->routeParser + ); + + # won't work because api has no session, instead you have to pass user + $response->getBody()->write(json_encode([ + 'systemnavi' => $systemNavigation + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(200); + } + + public function getMainNavi(Request $request, Response $response) + { + $navigation = new Navigation(); + $mainNavigation = $navigation->getMainNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo'), + $editor = $this->settings['editor'] + ); + + $response->getBody()->write(json_encode([ + 'mainnavi' => $mainNavigation + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(200); + } + + public function getNavigation(Request $request, Response $response, $args) + { + $params = $request->getQueryParams(); + + $urlinfo = $this->c->get('urlinfo'); + $langattr = $this->settings['langattr']; + $navigation = new Navigation(); + + if(isset($params['draft']) && $params['draft'] == true) + { + $contentnavi = $navigation->getFullDraftNavigation($urlinfo, $langattr); + } + else + { + $contentnavi = $navigation->getLiveNavigation($urlinfo, $langattr); + } + + if(!$contentnavi) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('navigation not found'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + $response->getBody()->write(json_encode([ + 'navigation' => $contentnavi + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function clearNavigation(Request $request, Response $response) + { + $navigation = new Navigation(); + + $result = $navigation->clearNavigation(); + + $response->getBody()->write(json_encode([ + 'result' => $result + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(200);; + } + + public function getItemForUrl(Request $request, Response $response, $args) + { + $params = $request->getQueryParams(); + $validate = new Validation(); + $validInput = $validate->articleUrl($params); + if($validInput !== true) + { + $errors = $validate->returnFirstValidationErrors($validInput); + $response->getBody()->write(json_encode([ + 'message' => reset($errors), + 'errors' => $errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $urlinfo = $this->c->get('urlinfo'); + $langattr = $this->settings['langattr']; + $navigation = new Navigation(); + $item = $navigation->getItemForUrl($params['url'], $urlinfo, $langattr); + + if(!$item) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('page not found'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + $response->getBody()->write(json_encode([ + 'item' => $item + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function getItemsForSlug(Request $request, Response $response, $args) + { + $params = $request->getQueryParams(); + $validate = new Validation(); + $validInput = $validate->articleSlug($params); + if($validInput !== true) + { + $errors = $validate->returnFirstValidationErrors($validInput); + $response->getBody()->write(json_encode([ + 'message' => reset($errors), + 'errors' => $errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $urlinfo = $this->c->get('urlinfo'); + $langattr = $this->settings['langattr']; + $navigation = new Navigation(); + $items = $navigation->getItemsForSlug($params['slug'], $urlinfo, $langattr); + + if(!$items) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('page not found'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + $response->getBody()->write(json_encode([ + 'items' => $items + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function getArticleContent(Request $request, Response $response, $args) + { + $params = $request->getQueryParams(); + $validate = new Validation(); + $validInput = $validate->articleUrl($params); + if($validInput !== true) + { + $errors = $validate->returnFirstValidationErrors($validInput); + $response->getBody()->write(json_encode([ + 'message' => reset($errors), + 'errors' => $errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $urlinfo = $this->c->get('urlinfo'); + $langattr = $this->settings['langattr']; + $navigation = new Navigation(); + $item = $navigation->getItemForUrl($params['url'], $urlinfo, $langattr); + if(!$item) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('page not found'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + # if user is not allowed to perform this action (e.g. not admin) + if(!$this->userroleIsAllowed($request->getAttribute('c_userrole'), 'content', 'read')) + { + # then check if user is the owner of this content + $meta = new Meta(); + $metadata = $meta->getMetaData($item); + if(!$this->userIsAllowed($request->getAttribute('c_username'), $metadata)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('You do not have enough rights.'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(403); + } + } + + # GET THE CONTENT + $content = new Content($urlinfo['baseurl'], $this->settings, $this->c->get('dispatcher')); + $markdown = $content->getLiveMarkdown($item); + + if(isset($params['draft']) && $params['draft'] == true) + { + # if draft is explicitly requested + $markdown = $content->getDraftMarkdown($item); + } + + if(!$markdown) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('page not found'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + if(!is_array($markdown)) + { + $markdown = $content->markdownTextToArray($markdown); + } + $markdownHtml = $content->addDraftHtml($markdown); + + $response->getBody()->write(json_encode([ + 'content' => $markdownHtml + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function getArticleMeta(Request $request, Response $response, $args) + { + $params = $request->getQueryParams(); + $validate = new Validation(); + $validInput = $validate->articleUrl($params); + if($validInput !== true) + { + $errors = $validate->returnFirstValidationErrors($validInput); + $response->getBody()->write(json_encode([ + 'message' => reset($errors), + 'errors' => $errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $urlinfo = $this->c->get('urlinfo'); + $langattr = $this->settings['langattr']; + $navigation = new Navigation(); + $item = $navigation->getItemForUrl($params['url'], $urlinfo, $langattr); + if(!$item) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('page not found'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + # if user is not allowed to perform this action (e.g. not admin) + if(!$this->userroleIsAllowed($request->getAttribute('c_userrole'), 'content', 'read')) + { + # then check if user is the owner of this content + $meta = new Meta(); + $metadata = $meta->getMetaData($item); + if(!$this->userIsAllowed($request->getAttribute('c_username'), $metadata)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('You do not have enough rights.'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(403); + } + } + + # GET THE META + $meta = new Meta(); + $metadata = $meta->getMetaData($item); + $metadata = $meta->addMetaDefaults($metadata, $item, $this->settings['author']); +# $metadata = $meta->addMetaTitleDescription($metadata, $item, $markdown); + + $response->getBody()->write(json_encode([ + 'meta' => $metadata + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function showSecurityLog(Request $request, Response $response) + { + $storage = new StorageWrapper('\Typemill\Models\Storage'); + $logfile = $storage->getFile('dataFolder', 'security', 'securitylog.txt'); + + if($logfile) + { + $logfile = trim($logfile); + if($logfile == '') + { + $lines = ['Logfile is empty']; + } + else + { + $lines = preg_split('/\r\n|\n|\r/', $logfile); + } + + $response->getBody()->write(json_encode([ + 'lines' => $lines + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(200);; + + } + + $response->getBody()->write(json_encode([ + 'error' => 'No logfile found' + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + public function deleteSecurityLog(Request $request, Response $response) + { + $storage = new StorageWrapper('\Typemill\Models\Storage'); + $result = $storage->deleteFile('dataFolder', 'security', 'securitylog.txt'); + + $response->getBody()->write(json_encode([ + 'result' => $result + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(200);; + } + + public function deleteCache(Request $request, Response $response) + { + $storage = new StorageWrapper('\Typemill\Models\Storage'); + + $cacheFolder = $storage->getFolderPath('cacheFolder'); + + $iterator = new \RecursiveDirectoryIterator($cacheFolder, \RecursiveDirectoryIterator::SKIP_DOTS); + $files = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST); + + $error = false; + + foreach($files as $file) + { + if ($file->isDir()) + { + if(!rmdir($file->getRealPath())) + { + $error = 'Could not delete some folders.'; + } + } + elseif($file->getExtension() !== 'css') + { + if(!unlink($file->getRealPath()) ) + { + $error = 'Could not delete some files.'; + } + } + } + + $sitemap = new Sitemap(); + $navigation = new Navigation(); + $urlinfo = $this->c->get('urlinfo'); + $liveNavigation = $navigation->getLiveNavigation($urlinfo, $this->settings['langattr']); + $sitemap->updateSitemap($liveNavigation, $urlinfo); + + if($error) + { + $response->getBody()->write(json_encode([ + 'error' => $error + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + $response->getBody()->write(json_encode([ + 'result' => true + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(200); + } + + public function getTranslations(Request $request, Response $response) + { + $response->getBody()->write(json_encode([ + 'translations' => $this->c->get('translations'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(200); + } +} \ No newline at end of file diff --git a/system/typemill/Controllers/ControllerApiImage.php b/system/typemill/Controllers/ControllerApiImage.php new file mode 100644 index 0000000..5a6b053 --- /dev/null +++ b/system/typemill/Controllers/ControllerApiImage.php @@ -0,0 +1,410 @@ +getQueryParams()['url'] ?? false; + $path = $request->getQueryParams()['path'] ?? false; + $pagemedia = []; + + if(!$path) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Path is missing.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $storage = new StorageWrapper('\Typemill\Models\Storage'); + + $markdown = $storage->getFile('contentFolder', '', $path . '.txt'); + if($markdown) + { + $markdownArray = json_decode($markdown); + $parsedown = new ParsedownExtension(); + $markdown = $parsedown->arrayBlocksToMarkdown($markdownArray); + } + else + { + $markdown = $storage->getFile('contentFolder', '', $path . '.md'); + } + + $mdmedia = $this->findMediaInText($markdown); + + $meta = $storage->getFile('contentFolder', '', $path . '.yaml'); + + $mtmedia = $this->findMediaInText($meta); + + $pagemedia = array_merge($mdmedia[2], $mtmedia[2]); + + $response->getBody()->write(json_encode([ + 'pagemedia' => $pagemedia + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + protected function findMediaInText($text) + { + preg_match_all('/media\/(live|files)\/(.+?\.[a-zA-Z]{2,4})/', $text, $matches); + + return $matches; + } + + public function getImages(Request $request, Response $response, $args) + { + $url = $request->getQueryParams()['url'] ?? false; + $path = $request->getQueryParams()['path'] ?? false; + + $storage = new StorageWrapper('\Typemill\Models\Storage'); + + $imagelist = $storage->getImageList(); + + $response->getBody()->write(json_encode([ + 'images' => $imagelist + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function getImage(Request $request, Response $response, $args) + { + $name = $request->getQueryParams()['name'] ?? false; + + # VALIDATE NAME + + if(!$name) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Imagename is missing.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $storage = new StorageWrapper('\Typemill\Models\Storage'); + + $imagedetails = $storage->getImageDetails($name); + + if(!$imagedetails) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('No image found.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + $response->getBody()->write(json_encode([ + 'image' => $imagedetails, + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + + public function saveImage(Request $request, Response $response, $args) + { + $params = $request->getParsedBody(); + + if(!isset($params['image']) OR !isset($params['name'])) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Image or name is missing.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $media = new Media(); + + if($this->settingActive('allowsvg')) + { + $media->addAllowedExtension('svg'); + } + + # prepare the image + if(!$media->prepareImage($params['image'], $params['name'])) + { + $response->getBody()->write(json_encode([ + 'message' => $media->errors[0], + 'fullerrors' => $media->errors, + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + # check if image name already exisits in live folder and create an unique name (do not overwrite existing files) + $storage = new StorageWrapper('\Typemill\Models\Storage'); + $uniqueImageName = $storage->createUniqueImageName($media->getFilename(), $media->getExtension()); + $media->setFilename($uniqueImageName); + + # store the original image + if(!$media->storeOriginalToTmp()) + { + $response->getBody()->write(json_encode([ + 'message' => $media->errors[0], + 'fullerrors' => $media->errors, + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + # if image is not resizable (animated gif or svg) + if(!$media->isResizable()) + { + if($media->saveOriginalForAll()) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Image saved successfully'), + 'name' => 'media/live/' . $media->getFullName(), + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + $response->getBody()->write(json_encode([ + 'message' => $media->errors[0], + 'fullerrors' => $media->errors, + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + # for all other image types, check if they should be transformed to webp + if(!isset($params['keepformat']) && $this->settingActive('convertwebp')) + { + $media->setExtension('webp'); + } + + if(!$media->storeRenditionsToTmp($this->settings['images'])) + { + $response->getBody()->write(json_encode([ + 'message' => $media->errors[0], + 'fullerrors' => $media->errors, + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Image saved successfully'), + 'name' => 'media/tmp/' . $media->getFullName(), + ])); + + return $response->withHeader('Content-Type', 'application/json'); + + } + + public function publishImage(Request $request, Response $response, $args) + { + $params = $request->getParsedBody(); + $noresize = (isset($params['noresize']) && $params['noresize'] == true) ? true : false; + + if(!isset($params['imgfile']) OR !$params['imgfile']) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Image or filename is missing.'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $storage = new StorageWrapper('\Typemill\Models\Storage'); + + $result = $storage->publishImage($params['imgfile'], $noresize); + + if(!$result) + { + $response->getBody()->write(json_encode([ + 'message' => $storage->getError() + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Image saved successfully'), + 'path' => $result, + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function saveVideoImage(Request $request, Response $response, $args) + { + $params = $request->getParsedBody(); + + if(!isset($params['videourl']) OR !$params['videourl']) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Markdown is missing.'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $videoUrl = $params['videourl']; + $class = false; + if(strpos($videoUrl, 'https://www.youtube.com/watch?v=') !== false) + { + $videoID = str_replace('https://www.youtube.com/watch?v=', '', $videoUrl); + $videoID = strpos($videoID, '&') ? substr($videoID, 0, strpos($videoID, '&')) : $videoID; + $class = 'youtube'; + } + elseif(strpos($videoUrl, 'https://youtu.be/') !== false) + { + $videoID = str_replace('https://youtu.be/', '', $videoUrl); + $videoID = strpos($videoID, '?') ? substr($videoID, 0, strpos($videoID, '?')) : $videoID; + $class = 'youtube'; + } + + if($class == 'youtube') + { + $videoURLmaxres = 'https://i1.ytimg.com/vi/' . $videoID . '/maxresdefault.jpg'; + $videoURL0 = 'https://i1.ytimg.com/vi/' . $videoID . '/0.jpg'; + } + + $ctx = stream_context_create(array( + 'https' => array( + 'timeout' => 1 + ) + ) + ); + + $imageData = @file_get_contents($videoURLmaxres, 0, $ctx); + if($imageData === false) + { + $imageData = @file_get_contents($videoURL0, 0, $ctx); + if($imageData === false) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('could not get the video image'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + } + + $imageData64 = 'data:image/jpeg;base64,' . base64_encode($imageData); + + $media = new Media(); + + # prepare the image + if(!$media->prepareImage($imageData64, $class . '-' . $videoID . '.jpg')) + { + $response->getBody()->write(json_encode([ + 'message' => $media->errors[0], + 'fullerrors' => $media->errors, + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + # check if image name already exisits in live folder and create an unique name (do not overwrite existing files) + $storage = new StorageWrapper('\Typemill\Models\Storage'); + $uniqueImageName = $storage->createUniqueImageName($media->getFilename(), $media->getExtension()); + $media->setFilename($uniqueImageName); + + # store the original image + if(!$media->storeOriginalToTmp()) + { + $response->getBody()->write(json_encode([ + 'message' => $media->errors[0], + 'fullerrors' => $media->errors, + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + # for all other image types, check if they should be transformed to webp + if($this->settingActive('convertwebp')) + { + $media->setExtension('webp'); + } + + # set to youtube size + $sizes = $this->settings['images']; + $sizes['live'] = ['width' => 560, 'height' => 315]; + + if(!$media->storeRenditionsToTmp($sizes)) + { + $response->getBody()->write(json_encode([ + 'message' => $media->errors[0], + 'fullerrors' => $media->errors, + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + # now publish directly + $livePath = $storage->publishImage($media->getFullName()); + + if($livePath) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Image saved successfully'), + 'path' => $livePath, + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + $response->getBody()->write(json_encode([ + 'message' => $storage->getError(), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + public function deleteImage(Request $request, Response $response, $args) + { + $params = $request->getParsedBody(); + + if(!isset($params['name'])) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Imagename is missing.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $storage = new StorageWrapper('\Typemill\Models\Storage'); + + $deleted = $storage->deleteImage($params['name']); + + if($deleted) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Image deleted successfully.') + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + $response->getBody()->write(json_encode([ + 'message' => $storage->getError() + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } +} diff --git a/system/typemill/Controllers/ControllerApiKixote.php b/system/typemill/Controllers/ControllerApiKixote.php new file mode 100644 index 0000000..94ea913 --- /dev/null +++ b/system/typemill/Controllers/ControllerApiKixote.php @@ -0,0 +1,377 @@ +getKixoteSettings(); + + if(!$kixoteSettings) + { + $response->getBody()->write(json_encode([ + 'message' => 'could not load kixote settings.' + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + # send to Kixote + $response->getBody()->write(json_encode([ + 'settings' => $kixoteSettings + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(200); + + } + + public function updateKixoteSettings(Request $request, Response $response) + { + $params = $request->getParsedBody(); + $kixoteSettings = $params['kixotesettings'] ?? false; + $validate = new Validation(); + + if(isset($kixoteSettings['promptlist'])) + { + $promptErrors = false; + foreach($kixoteSettings['promptlist'] as $name => $values) + { + $validInput = $validate->kixotePrompt($values); + if($validInput !== true) + { + $promptErrors = true; + $kixoteSettings['promptlist'][$name]['errors'] = $validInput; + } + else + { + unset($kixoteSettings['promptlist'][$name]['errors']); + } + } + + if($promptErrors) + { + $response->getBody()->write(json_encode([ + 'message' => 'please correct the errors in the form', + 'kixotesettings' => $kixoteSettings + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + } + + $settingsModel = new Settings(); + $result = $settingsModel->updateKixoteSettings($kixoteSettings); + + if(!$result) + { + # restore the current kixote-settings + $kixoteSettings = $settingsModel->getKixoteSettings(); + + $response->getBody()->write(json_encode([ + 'message' => 'error while saving settings.', + 'kixotesettings' => $kixoteSettings + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + # send to Kixote + $response->getBody()->write(json_encode([ + 'kixotesettings' => $kixoteSettings + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(200); + } + + # initial token statistics + public function getTokenStats(Request $request, Response $response): Response + { + $aiservice = false; + $tokenstats = 0; + $useragreement = false; + $user = new User(); + $username = $request->getAttribute('c_username'); + + if(!$user->setUser($username)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('We did not find the a user.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + if(isset($this->settings['aiservice']) && $this->settings['aiservice'] !== 'none') + { + $aiservice = $this->settings['aiservice']; + } + + if($aiservice) + { + $userdata = $user->getUserData(); + if(isset($userdata['aiservices']) && in_array($aiservice, $userdata['aiservices'])) + { + $useragreement = true; + } + } + + # get toke stats for AI service + if($aiservice && $useragreement) + { + switch ($aiservice) + { + case 'chatgpt': + $tokenstats = [ + 'url' => 'https://platform.openai.com/settings/organization/billing/overview', + 'label' => 'ChatGPT Billing' + ]; + break; + + default: + $tokenstats = 0; + break; + } + } + + if($tokenstats === false) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Could not get tokenstats.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $response->getBody()->write(json_encode([ + 'message' => 'Success', + 'aiservice' => $aiservice, + 'useragreement' => $useragreement, + 'tokenstats' => $tokenstats + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(200); + } + + # initial token statistics + public function agreeToAiService(Request $request, Response $response): Response + { + $aiservice = false; + $user = new User(); + $username = $request->getAttribute('c_username'); + + if(!$user->setUserWithPassword($username)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('We did not find the a user or usermail.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + if(isset($this->settings['aiservice']) && $this->settings['aiservice'] !== 'none') + { + $aiservice = $this->settings['aiservice']; + } + else + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('No valid ai service has been selected.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $agreements = $user->getValue('aiservices'); + + if(!$agreements) + { + $agreements = [$aiservice]; + } + elseif(!isset($agreements[$aiservice])) + { + $agreements[] = $aiservice; + } + + $user->setValue('aiservices', $agreements); + if($user->updateUser() !== true) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('We could not update your user settings, please try again or agree to ' . $aiservice . ' in your user profile.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $response->getBody()->write(json_encode([ + 'message' => 'Success' + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(200); + } + + private function getKixoteJWT(Request $request, Response $response) + { + # this will authenticate from service.typemill.net (e.g. for template service) + $license = new License(); + $jwt = $license->getToken(); + if($jwt) + { + $this->error = $license->getMessage(); + return false; + } + + # if no agb-confirmation + $confirm = $settings['kixote_confirm'] ?? false; + if(!$confirm) + { + $this->error = 'Please read and accept the AGB before you start with our service.'; + return false; + } + + return $jwt; + } + + public function promptKixote(Request $request, Response $response) + { + $jwt = $this->getKixoteJWT(); + if(!$jwt) + { + $response->getBody()->write(json_encode([ + 'message' => $this->error + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $params = $request->getParsedBody(); + + $params['name'] = ''; # will trigger some cool stuff in kixote + $params['prompt'] = ''; # the prompt itself + $params['article'] = ''; # the current article + $params['tone'] = ''; # the tone + + if(!isset($params['prompt']) OR !is_array($params['article'])) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Prompt or article missing.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + # validate input + $validate = new Validation(); + $validationresult = $validate->newLicense($params['license']); + if($validationresult !== true) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Please correct your input.'), + 'errors' => $validate->returnFirstValidationErrors($validationresult) + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + # send to Kixote + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Licence has been stored'), + 'licensedata' => $licensedata + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(200); + } + + public function promptChatGPT(Request $request, Response $response): Response + { + # check if user has accepted + + $params = $request->getParsedBody(); + + $params['name'] = $params['name'] ?? ''; + $params['prompt'] = $params['prompt'] ?? ''; + $params['article'] = $params['article'] ?? ''; + $params['tone'] = $params['tone'] ?? ''; + + $settingsModel = new Settings(); + $model = $this->settings['chatgptModel'] ?? false; + $apikey = $settingsModel->getSecret('chatgptKey'); + + if (empty($params['prompt']) || !is_string($params['prompt'])) + { + $response->getBody()->write(json_encode([ + 'message' => 'Prompt is missing or invalid.' + ])); + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + if (empty($params['article']) || !is_string($params['article'])) + { + $response->getBody()->write(json_encode([ + 'message' => 'Article is missing or invalid.' + ])); + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + if (!$model || !$apikey) + { + $response->getBody()->write(json_encode([ + 'message' => 'Model or api key for chatgpt is missing, please add it in the system settings.' + ])); + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $url = 'https://api.openai.com/v1/chat/completions'; + $authHeader = "Authorization: Bearer $apikey"; + + $postdata = [ + 'model' => $model, + 'messages' => [ + [ + 'role' => 'system', + 'content' => 'You are a content editor and writing assistant. If the user prompt does not explicitly specify otherwise, apply the prompt to the provided article and return only the updated article in Markdown syntax, without any extra comments or explanations. If you find the tag , modify only the content inside these tags and leave everything else unchanged. Always return the full article.' + ], + [ + 'role' => 'user', + 'content' => $params['prompt'] . "\n" . $params['article'] + ], + ], + 'temperature' => 0.7, + 'max_tokens' => 2000, + ]; + + $apiservice = new ApiCalls(); + $apiResponse = $apiservice->makePostCall($url, $postdata, $authHeader); + + if (!$apiResponse) + { + $response->getBody()->write(json_encode([ + 'message' => 'Failed to communicate with ChatGPT', + 'error' => $apiservice->getError() + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $data = json_decode($apiResponse, true); + $response->getBody()->write(json_encode([ + 'message' => 'Success', + 'data' => $data, + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(200); + } +} \ No newline at end of file diff --git a/system/typemill/Controllers/ControllerApiSystemExtensions.php b/system/typemill/Controllers/ControllerApiSystemExtensions.php new file mode 100644 index 0000000..40cb4b2 --- /dev/null +++ b/system/typemill/Controllers/ControllerApiSystemExtensions.php @@ -0,0 +1,104 @@ +getParsedBody(); + + # validate input + $validate = new Validation(); + $vresult = $validate->activateExtension($params); + + if($vresult !== true) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Something went wrong, the input is not valid.'), + 'errors' => $vresult + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + if($params['checked'] == true) + { + $extension = new Extension(); + + $definitions = false; + if($params['type'] == 'plugins') + { + $definitions = $extension->getPluginDefinition($params['name']); + } + elseif($params['type'] == 'themes') + { + $definitions = $extension->getThemeDefinition($params['name']); + } + if(!$definitions) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('The plugin or themes was not found.'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + if(isset($definitions['license']) && in_array($definitions['license'], ['MAKER', 'BUSINESS'])) + { + $license = new License(); + $urlinfo = $this->c->get('urlinfo'); + + $test = $license->checkIfTest($urlinfo); + + if($license->checkIfTest($urlinfo) !== true) + { + # checks if license is valid and returns scope + $licenseScope = $license->getLicenseScope($urlinfo); + + if(!isset($licenseScope[$definitions['license']])) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Activation failed because you need a valid ') . $definitions['license'] . Translations::translate('-license and your website must run under the domain of your license.'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + } + } + } + + # store updated settings here + $settings = new Settings(); + + if($params['type'] == 'plugins') + { + $pluginsettings = $this->settings['plugins'][$params['name']] ?? []; + $pluginsettings['active'] = $params['checked']; + $updatedSettings = $settings->updateSettings($pluginsettings, 'plugins', $params['name']); + } + elseif($params['type'] == 'themes') + { + $themesettings = $this->settings['themes'][$params['name']] ?? []; + $updatedSettings = $settings->updateSettings($themesettings, 'themes', $params['name']); + if($updatedSettings) + { + $updatedSettings = $settings->updateSettings($params['name'], 'theme'); + } + } + + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('settings have been saved') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(200); + } +} \ No newline at end of file diff --git a/system/typemill/Controllers/ControllerApiSystemLicense.php b/system/typemill/Controllers/ControllerApiSystemLicense.php new file mode 100644 index 0000000..63bd9ab --- /dev/null +++ b/system/typemill/Controllers/ControllerApiSystemLicense.php @@ -0,0 +1,84 @@ +testLicenseCall(); + + if(!$testresult) + { + $response->getBody()->write(json_encode([ + 'message' => $license->getMessage() + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('License server call was successful'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(200); + } + + public function createLicense(Request $request, Response $response) + { + $params = $request->getParsedBody(); + + if(!isset($params['license']) OR !is_array($params['license'])) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('License data missing.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + # validate input + $validate = new Validation(); + $validationresult = $validate->newLicense($params['license']); + if($validationresult !== true) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Please correct your input.'), + 'errors' => $validate->returnFirstValidationErrors($validationresult) + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $license = new License(); + + $licensedata = $license->activateLicense($params['license']); + + if(!$licensedata) + { + $response->getBody()->write(json_encode([ + 'message' => $license->getMessage() + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $licensedata = $license->getLicenseFile(); + unset($licensedata['signature']); + + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Licence has been stored'), + 'licensedata' => $licensedata + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(200); + } +} \ No newline at end of file diff --git a/system/typemill/Controllers/ControllerApiSystemPlugins.php b/system/typemill/Controllers/ControllerApiSystemPlugins.php new file mode 100644 index 0000000..d79fb7e --- /dev/null +++ b/system/typemill/Controllers/ControllerApiSystemPlugins.php @@ -0,0 +1,67 @@ +getParsedBody(); + $pluginname = $params['plugin']; + $plugininput = $params['settings']; + + $extension = new Extension(); + $formdefinitions = $extension->getPluginDefinition($pluginname); + $formdefinitions = $this->addDatasets($formdefinitions['forms']['fields']); +# $plugindata = []; + + # validate input + $validator = new Validation(); + $validatedOutput = $validator->recursiveValidation($formdefinitions, $plugininput); + if(!empty($validator->errors)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Please correct your input.'), + 'errors' => $validator->errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + # keep the active setting + $validatedOutput['active'] = false; + if(isset($plugininput['active']) && $plugininput['active'] == true) + { + $validatedOutput['active'] = true; + } + + # store updated settings here + $settingsModel = new Settings(); + $securityFields = $settingsModel->findSecurityDefinitions($formdefinitions); + if(!empty($securityFields)) + { + $splitSettings = $settingsModel->extractSecuritySettings($validatedOutput, $securityFields); + $validatedOutput = $splitSettings['settings']; + + if($splitSettings['securitySettings'] && !empty($splitSettings['securitySettings'])) + { + $settingsModel->updateSecuritySettings($splitSettings['securitySettings'], 'plugins', $pluginname); + } + } + + $updatedSettings = $settingsModel->updateSettings($validatedOutput, 'plugins', $pluginname); + + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('settings have been saved') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(200); + } +} \ No newline at end of file diff --git a/system/typemill/Controllers/ControllerApiSystemSettings.php b/system/typemill/Controllers/ControllerApiSystemSettings.php new file mode 100644 index 0000000..043b600 --- /dev/null +++ b/system/typemill/Controllers/ControllerApiSystemSettings.php @@ -0,0 +1,88 @@ +getBody()->write(json_encode([ + 'settings' => $this->settings + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(200); + } + + public function updateSettings(Request $request, Response $response) + { + $params = $request->getParsedBody(); + $settingsinput = $params['settings']; + $settingsModel = new Settings(); + + $formdefinitions = $settingsModel->getSettingsDefinitions(); + + # validate input + $validator = new Validation(); + $validatedOutput = $validator->recursiveValidation($formdefinitions, $settingsinput); + + if(!empty($validator->errors)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Please correct your input.'), + 'errors' => $validator->errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + # if everything is fine, create customsizes for favicon + if(isset($validatedOutput['favicon']) && $validatedOutput['favicon'] != '' && ($validatedOutput['favicon'] != $this->settings['favicon'])) + { + $media = new Media(); + + $sizes = [ + '16' => ['width' => 16, 'height' => 16], + '32' => ['width' => 32, 'height' => 32], + '72' => ['width' => 72, 'height' => 72], + '114' => ['width' => 114, 'height' => 114], + '144' => ['width' => 144, 'height' => 144], + '180' => ['width' => 180, 'height' => 180], + ]; + + foreach ($sizes as $size) + { + $favicon = $media->createCustomSize($validatedOutput['favicon'], $size['width'], $size['height'], 'favicon'); + } + } + + $securityFields = $settingsModel->findSecurityDefinitions($formdefinitions); + + if(!empty($securityFields)) + { + $splitSettings = $settingsModel->extractSecuritySettings($validatedOutput, $securityFields); + $validatedOutput = $splitSettings['settings']; + + if($splitSettings['securitySettings'] && !empty($splitSettings['securitySettings'])) + { + $settingsModel->updateSecuritySettings($splitSettings['securitySettings']); + } + } + + $updatedSettings = $settingsModel->updateSettings($validatedOutput); + + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('settings have been saved'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(200); + } +} \ No newline at end of file diff --git a/system/typemill/Controllers/ControllerApiSystemThemes.php b/system/typemill/Controllers/ControllerApiSystemThemes.php new file mode 100644 index 0000000..9048e74 --- /dev/null +++ b/system/typemill/Controllers/ControllerApiSystemThemes.php @@ -0,0 +1,205 @@ +getParsedBody(); + $themename = $params['theme']; + $themeinput = $params['settings']; + + $extension = new Extension(); + $formdefinitions = $extension->getThemeDefinition($themename); + $formdefinitions = $this->addDatasets($formdefinitions['forms']['fields']); + $themedata = []; + + # validate input + $validator = new Validation(); + $validatedOutput = $validator->recursiveValidation($formdefinitions, $themeinput); + if(!empty($validator->errors)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Please correct your input.'), + 'errors' => $validator->errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + # delete themecss + unset($validatedOutput['customcss']); + + # store updated settings here + $settingsModel = new Settings(); + $securityFields = $settingsModel->findSecurityDefinitions($formdefinitions); + if(!empty($securityFields)) + { + $splitSettings = $settingsModel->extractSecuritySettings($validatedOutput, $securityFields); + $validatedOutput = $splitSettings['settings']; + + if($splitSettings['securitySettings'] && !empty($splitSettings['securitySettings'])) + { + $settingsModel->updateSecuritySettings($splitSettings['securitySettings'], 'themes', $themename); + } + } + + $updatedSettings = $settingsModel->updateSettings($validatedOutput, 'themes', $themename); + + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('settings have been saved') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(200); + } + + public function updateThemeCss(Request $request, Response $response) + { + $params = $request->getParsedBody(); + $themename = $params['theme']; + $themecss = $params['css']; + + # validate css input + $themecss = strip_tags($themecss); + + # store updated css + $settings = new Settings(); + $updatedSettings = $settings->updateThemeCss($themename, $themecss); + + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('settings have been saved'), + 'code' => $updatedSettings + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(200); + } + + public function updateReadymade(Request $request, Response $response) + { + $params = $request->getParsedBody(); + $themename = $params['theme'] ?? false; + $themeinput = $params['settings'] ?? false; + $readymadetitle = $params['readymadetitle'] ?? false; + $readymadedesc = $params['readymadedesc'] ?? false; + + $extension = new Extension(); + $formdefinitions = $extension->getThemeDefinition($themename); + $formdefinitions = $this->addDatasets($formdefinitions['forms']['fields']); + $themedata = []; + + # validate input + $validator = new Validation(); + $validatedOutput = $validator->recursiveValidation($formdefinitions, $themeinput); + if(!empty($validator->errors)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Please correct your input.'), + 'errors' => $validator->errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $validator = $validator->returnValidator(['themename' => $themename, 'title' => $readymadetitle, 'description' => $readymadedesc]); + $validator->rule('required', ['themename', 'title', 'description']); + $validator->rule('lengthBetween', 'description', 3, 100)->message("Length between 3 - 100"); + $validator->rule('noHTML', 'description')->message(" contains HTML"); + $validator->rule('regex', 'themename', '/^[a-zA-Z0-9\-]{3,40}$/')->message("only a-zA-Z0-9 with 3 - 40 chars allowed"); + $validator->rule('regex', 'title', '/^[a-zA-Z0-9\-\_ ]{3,20}$/')->message("only a-zA-Z0-9 with 3 - 20 chars allowed"); + + if(!$validator->validate()) + { + $message = 'There was an error, please try again'; + $errors = $validator->errors(); + $firstKey = array_key_first($errors); + if(isset($errors[$firstKey][0])) + { + $message = $firstKey . ': ' . $errors[$firstKey][0]; + } + + $response->getBody()->write(json_encode([ + 'message' => $message + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $readymadeSlug = Slug::createSlug($readymadetitle); + + $readymade = [ + $readymadeSlug => [ + 'name' => $readymadetitle, + 'description' => $readymadedesc, + 'delete' => true, + 'settings' => $validatedOutput + ] + ]; + + $extension->storeThemeReadymade($themename, $readymadeSlug, $readymade); + + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Readymade has been saved'), + 'readymade' => $readymade, + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(200); + } + + public function deleteReadymade(Request $request, Response $response) + { + $params = $request->getParsedBody(); + $themename = $params['theme'] ?? false; + $readymadeslug = $params['readymadeslug'] ?? false; + + $validation = new Validation(); + $validator = $validation->returnValidator($params); + $validator->rule('required', ['theme', 'readymadeslug']); + $validator->rule('regex', 'theme', '/^[a-zA-Z0-9\-]{3,40}$/')->message("only a-zA-Z0-9 with 3 - 40 chars allowed"); + $validator->rule('regex', 'readymadeslug', '/^[a-zA-Z0-9\-\_ ]{3,20}$/')->message("only a-zA-Z0-9 with 3 - 20 chars allowed"); + + if(!$validator->validate()) + { + $message = 'There was an error, please try again'; + $errors = $validator->errors(); + $firstKey = array_key_first($errors); + if(isset($errors[$firstKey][0])) + { + $message = $firstKey . ': ' . $errors[$firstKey][0]; + } + + $response->getBody()->write(json_encode([ + 'message' => $message + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $extension = new Extension(); + $result = $extension->deleteThemeReadymade($themename, $readymadeslug); + + if($result !== true) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('We could not delete the readymade.'), + 'errors' => $result + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('readymade has been deleted') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(200); + } +} diff --git a/system/typemill/Controllers/ControllerApiSystemUsers.php b/system/typemill/Controllers/ControllerApiSystemUsers.php new file mode 100644 index 0000000..cd04799 --- /dev/null +++ b/system/typemill/Controllers/ControllerApiSystemUsers.php @@ -0,0 +1,420 @@ +getQueryParams()['usernames'] ?? false; + $user = new User(); + $userdata = []; + + $validate = new Validation(); + + if($usernames && is_array($usernames)) + { + foreach($usernames as $username) + { + if($validate->username(['username' => $username]) === true) + { + $existinguser = $user->setUser($username); + if($existinguser) + { + $userdata[] = $user->getUserData(); + } + } + } + } + + $response->getBody()->write(json_encode([ + 'userdata' => $userdata + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(200); + } + + # returns userdata + public function getUsersByEmail(Request $request, Response $response, $args) + { + $email = $request->getQueryParams()['email'] ?? false; + $user = new User(); + $userdata = []; + + $validate = new Validation(); + $valresult = $validate->emailsearch(['email' => $email]); + + if($valresult === true) + { + $usernames = $user->findUsersByEmail($email); + + if($usernames) + { + foreach($usernames as $username) + { + $user->setUser($username); + $userdata[] = $user->getUserData(); + } + } + } + + $response->getBody()->write(json_encode([ + 'userdata' => $userdata + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(200); + } + + #returns userdata + public function getUsersByRole(Request $request, Response $response, $args) + { + $role = $request->getQueryParams()['role'] ?? false; + $user = new User(); + $userdata = []; + + $userroles = $this->c->get('acl')->getRoles(); + + if($role && in_array($role, $userroles)) + { + $usernames = $user->findUsersByRole($role); + + if($usernames) + { + foreach($usernames as $username) + { + if($user->setUser($username)) + { + $userdata[] = $user->getUserData(); + } + } + } + } + + $response->getBody()->write(json_encode([ + 'userdata' => $userdata + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(200); + } + + public function updateUser(Request $request, Response $response, $args) + { + $params = $request->getParsedBody(); + $userdata = $params['userdata'] ?? false; + $username = $params['userdata']['username'] ?? false; + $isAdmin = $this->c->get('acl')->isAllowed($request->getAttribute('c_userrole'), 'user', 'update'); + + if(!$userdata OR !$username) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Userdata or username is missing.'), + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $validate = new Validation(); + + # standard validation for new users + $userroles = $this->c->get('acl')->getRoles(); + $valresult = $validate->existingUser($userdata, $userroles); + if($valresult !== true) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Please correct your input.'), + 'errors' => $validate->returnFirstValidationErrors($valresult) + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + # if it is a non-admin-user + if($isAdmin !== true) + { + # do not change userrole + unset($userdata['userrole']); + + # if a non-admin-user tries to update another account + if(($username !== $request->getAttribute('c_username'))) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('You are not allowed to update another user.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(403); + } + } + + # make sure you set a user with password when you update, otherwise it will delete the password completely + $user = new User(); + $user->setUserWithPassword($username); + $userrole = $user->getValue('userrole'); + + # password validation + $pwerrors = []; + $oldpassword = ( isset($userdata['password']) AND ($userdata['password'] != '') ) ? $userdata['password'] : false; + $newpassword = ( isset($userdata['newpassword']) AND ($userdata['newpassword'] != '') ) ? $userdata['newpassword'] : false; + unset($userdata['password']); + unset($userdata['newpassword']); + + if($isAdmin === true) + { + # admins can change passwords without old password + if($newpassword) + { + $validpass = $validate->newPasswordAdmin(['newpassword' => $newpassword]); + + if($validpass === true) + { + # encrypt new password + $userdata['password'] = $user->generatePassword($newpassword); + } + elseif(is_array($validpass)) + { + foreach($validpass as $fieldname => $errors) + { + $pwerrors[$fieldname] = $errors[0]; + } + } + } + } + else + { + # non-admins can change password only with new and old password + if($oldpassword OR $newpassword) + { + $validpass = $validate->newPassword(['password' => $oldpassword, 'newpassword' => $newpassword]); + + if(is_array($validpass)) + { + foreach($validpass as $fieldname => $errors) + { + $pwerrors[$fieldname] = $errors[0]; + } + } + elseif(!password_verify($oldpassword, $user->getValue('password'))) + { + $pwerrors['password'] = 'Old password is wrong.'; + } + elseif($validpass === true) + { + # encrypt new password + $userdata['password'] = $user->generatePassword($newpassword); + } + } + } + + if(!empty($pwerrors)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Please correct your input.'), + 'errors' => $pwerrors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + # check if loginlink is activated, only admins, for other user userrole is removed + $loginlink = false; + if($isAdmin === true && isset($userdata['userrole']) && $userdata['userrole'] == 'guest' && isset($this->settings['loginlink']) && $this->settings['loginlink']) + { + $loginlink = true; + } + + # we have to validate again because of additional dynamic fields + $formdefinitions = $user->getUserFields($this->c->get('acl'), $this->c->get('dispatcher'), $userrole, $request->getAttribute('c_userrole'), $loginlink); + $validatedOutput = $validate->recursiveValidation($formdefinitions, $userdata); + if(!empty($validate->errors)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Please correct your input.'), + 'errors' => $validate->errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + # if input is valid, overwrite value in original user + foreach($validatedOutput as $fieldname => $value) + { + $user->setValue($fieldname, $value); + } + + if(!$user->updateUser()) + { + $response->getBody()->write(json_encode([ + 'message' => $user->getError() + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(422); + } + + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('User has been updated.') + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function getNewUserForm(Request $request, Response $response, $args) + { + $userrole = $request->getQueryParams()['userrole'] ?? false; + if(!$userrole) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Userrole is required.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $user = new User(); + $userform = $user->getUserFields($this->c->get('acl'), $this->c->get('dispatcher'), $userrole, $inspectorrole = $request->getAttribute('c_userrole'), $loginlink = NULL); + + # fix the standard form + $userform['password']['label'] = 'Password'; + $userform['password']['generator'] = true; + $userform['username']['label'] = 'Username'; + unset($userform['username']['readonly']); + unset($userform['userrole']); + unset($userform['newpassword']); + + $response->getBody()->write(json_encode([ + 'userform' => $userform, + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + public function createUser(Request $request, Response $response, $args) + { + $params = $request->getParsedBody(); + $userdata = $params['userdata'] ?? false; + if(!$userdata) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Userdata are required.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $validate = new Validation(); + + # standard validation for new users + $userroles = $this->c->get('acl')->getRoles(); + $valresult = $validate->newUser($userdata, $userroles); + if($valresult !== true) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Please correct your input.'), + 'errors' => $validate->returnFirstValidationErrors($valresult) + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + + # additional validation for extra fields and image handling + $user = new User(); + $formdefinitions = $user->getUserFields($this->c->get('acl'), $this->c->get('dispatcher'), $userdata['userrole'],$inspectorrole = $request->getAttribute('c_userrole'), $userlink = NULL); + unset($formdefinitions['username']['readonly']); + $validatedOutput = $validate->recursiveValidation($formdefinitions, $userdata); + if(!empty($validate->errors)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Please correct your input.'), + 'errors' => $validate->errors + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + if(!$user->createUser($validatedOutput)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('We could not store the new user'), + 'error' => $user->error, + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('New user created.'), + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } + + + public function deleteUser(Request $request, Response $response, $args) + { + $params = $request->getParsedBody(); + $username = $params['username'] ?? false; + $isAdmin = $this->c->get('acl')->isAllowed($request->getAttribute('c_userrole'), 'user', 'delete'); + + if(!$username) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Username is required.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + # if a non-admin-user tries to delete another account + if(!$isAdmin AND ($username !== $request->getAttribute('c_username')) ) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('You are not allowed to delete another user.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(403); + } + + $user = new User(); + if(!$user->setUser($username)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('We could not find the user'), + 'error' => $user->error + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(404); + } + + if(!$user->deleteUser()) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('We could not delete the user'), + 'error' => $user->error + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + $logout = false; + # if user deleted his own account + if($username == $request->getAttribute('c_username')) + { + $logout = true; + Session::stopSession(); + } + + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('User deleted.'), + 'logout' => $logout + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } +} \ No newline at end of file diff --git a/system/typemill/Controllers/ControllerApiSystemVersions.php b/system/typemill/Controllers/ControllerApiSystemVersions.php new file mode 100644 index 0000000..a33e29d --- /dev/null +++ b/system/typemill/Controllers/ControllerApiSystemVersions.php @@ -0,0 +1,163 @@ +getParsedBody(); + $error = false; + + # validate input + $validate = new Validation(); + $vresult = $validate->checkVersions($params); + if($vresult !== true) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('The version check failed because of invalid parameters.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $type = $params['type']; + $data = $params['data']; + $url = 'https://typemill.net/api/v1/checkversion'; +# $url2 = 'http://localhost/typemillPlugins/api/v1/checkversion'; + + if($type == 'plugins') + { + $pluginList = ''; + foreach($data as $name => $plugin) + { + $pluginList .= $name . ','; + } + + $url = 'https://plugins.typemill.net/api/v1/getplugins?plugins=' . urlencode($pluginList); + } + if($type == 'themes') + { + $themeList = ''; + foreach($data as $name => $theme) + { + $themeList .= $name . ','; + } + + $url = 'https://themes.typemill.net/api/v1/getthemes?themes=' . urlencode($themeList); + } + + $license = new License(); + $authstring = $license->getPublicKeyPem(); + if(!$authstring) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('Please check if there is a readable file public_key.pem in your settings folder.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + $authstring = hash('sha256', substr($authstring, 0, 50)); + + if (function_exists('curl_version')) + { + $curl = curl_init(); + + if (defined('CURLSSLOPT_NATIVE_CA') && version_compare(curl_version()['version'], '7.71', '>=')) + { + curl_setopt($curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA); + } + curl_setopt($curl, CURLOPT_URL, $url); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_TIMEOUT, 5); + curl_setopt($curl, CURLOPT_HTTPHEADER, [ + "Accept: application/json", + "Authorization: $authstring", + "Connection: close" + ]); + + $curl_response = curl_exec($curl); + + if (curl_errno($curl)) + { + $error = curl_error($curl); + } + else + { + $versions = json_decode($curl_response, true); + } + + curl_close($curl); + } + else + { + $opts = array( + 'http' => array( + 'method' =>"GET", + 'ignore_errors' => true, + 'timeout' => 5, + 'header' => + "Accept: application/json\r\n" . + "Authorization: $authstring\r\n" . + "Connection: close\r\n", + ) + ); + + $context = stream_context_create($opts); + $versions = file_get_contents($url, false, $context); + if ($versions === false) + { + $error = "file_get_contents error: failed to fetch data from $url"; + } + else + { + $versions = json_decode($versions, true); + } + } + + if($error) + { + $response->getBody()->write(json_encode([ + 'message' => $error + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + $updateVersions = []; + + if($type == 'system') + { + $latestVersion = $versions['system']['typemill'] ?? false; + $installedVersion = $data ?? false; + if($latestVersion && $installedVersion && version_compare($latestVersion, $installedVersion) > 0) + { + $updateVersions['system'] = $latestVersion; + } + } + elseif(isset($versions[$type])) + { + foreach($versions[$type] as $name => $details) + { + $latestVersion = $details['version'] ?? false; + $installedVersion = $data[$name] ?? false; + if($latestVersion && $installedVersion && version_compare($latestVersion, $installedVersion) > 0) + { + $updateVersions[$name] = $details; + } + } + } + + $response->getBody()->write(json_encode([ + $type => $updateVersions + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(200); + } +} \ No newline at end of file diff --git a/system/typemill/Controllers/ControllerApiTestmail.php b/system/typemill/Controllers/ControllerApiTestmail.php new file mode 100644 index 0000000..e68c1b1 --- /dev/null +++ b/system/typemill/Controllers/ControllerApiTestmail.php @@ -0,0 +1,61 @@ +settings['mailfrom']) or !filter_var($this->settings['mailfrom'], FILTER_VALIDATE_EMAIL)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('The from mail is missing or it is not a valid e-mail address.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $user = new User(); + $username = $request->getAttribute('c_username'); + + if(!$user->setUser($username)) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('We did not find the a user or usermail.') + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(400); + } + + $userdata = $user->getUserData(); + + $mail = new SimpleMail($this->settings); + + $subject = Translations::translate('Testmail from Typemill'); + $message = Translations::translate('This is a testmail from Typemill and if you read this e-mail, then everything works fine.'); + + $send = $mail->send($userdata['email'], $subject, $message); + + if(!$send) + { + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('We could not send the testmail to your e-mail address. Reason: ') . $mail->error + ])); + + return $response->withHeader('Content-Type', 'application/json')->withStatus(500); + } + + $response->getBody()->write(json_encode([ + 'message' => Translations::translate('The testmail has been send, please check your inbox and your spam-folder to varify that you received the mail.') + ])); + + return $response->withHeader('Content-Type', 'application/json'); + } +} \ No newline at end of file diff --git a/system/typemill/Controllers/ControllerWebAuth.php b/system/typemill/Controllers/ControllerWebAuth.php new file mode 100644 index 0000000..8a27fbc --- /dev/null +++ b/system/typemill/Controllers/ControllerWebAuth.php @@ -0,0 +1,542 @@ +c->get('view')->render($response, 'auth/login.twig', [ + 'recover' => $this->settings['recoverpw'] ?? false, + 'captcha' => $this->settings['authcaptcha'] ?? false, + ]); + } + + public function login(Request $request, Response $response) + { + $input = $request->getParsedBody(); + $validation = new Validation(); + $securitylog = $this->settings['securitylog'] ?? false; + $authcodeactive = $this->isAuthcodeActive($this->settings); + $authtitle = Translations::translate('Verification code missing?'); + $authtext = Translations::translate('If you did not receive an email with the verification code, then the username or password you entered was wrong. Please try again.'); + + if($validation->signin($input) !== true) + { + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('login: invalid data'); + } + + if($this->c->get('flash')) + { + $this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.')); + } + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); + } + + $user = new User(); + + if(!$user->setUserWithPassword($input['username'])) + { + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('login: user not found'); + } + + if($this->c->get('flash')) + { + $this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.')); + } + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); + } + + $userdata = $user->getUserData(); + $authcodedata = $this->checkAuthcode($userdata); + + if($userdata && !password_verify($input['password'], $userdata['password'])) + { + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('login: wrong password'); + } + + # always show authcode page, so attacker does not know if email or password was wrong or mail was send. + if($authcodeactive && !$authcodedata['valid']) + { + # a bit slower because send mail takes some time usually + usleep(rand(100000, 200000)); + + # show authcode page + return $this->c->get('view')->render($response, 'auth/authcode.twig', [ + 'username' => $userdata['username'], + 'authtitle' => $authtitle, + 'authtext' => $authtext + ]); + } + + if($this->c->get('flash')) + { + $this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.')); + } + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); + } + + # check device fingerprint + if($authcodeactive) + { + $fingerprint = $this->generateDeviceFingerprint(); + if(!$this->findDeviceFingerprint($fingerprint, $userdata)) + { + # invalidate authcodedata so user has to use a new authcode again + $authcodedata['valid'] = false; + $authcodedata['validated'] = 12345; + } + } + + if($authcodeactive && !$authcodedata['valid'] ) + { + # generate new authcode + $authcodevalue = rand(10000, 99999); + + $mail = new SimpleMail($this->settings); + + $subject = Translations::translate('Your Typemill verification code'); + $message = Translations::translate('Dear user') . ',

'; + $message .= Translations::translate('Someone tried to log in to your Typemill website and we want to make sure it is you. Enter the following verification code to finish your login. The code will be valid for 5 minutes.'); + $message .= '

' . $authcodevalue . '

'; + $message .= Translations::translate('If you did not make this login attempt, please reset your password immediately.'); + + $send = $mail->send($userdata['email'], $subject, $message); + + if(!$send) + { + $authtitle = Translations::translate('Error sending email'); + $authtext = Translations::translate('We could not send the email with the verification code to your address. Reason: ') . $mail->error; + } + else + { + # store authcode + $user->setValue('authcodedata', $authcodevalue . ':' . time() . ':' . $authcodedata['validated']); + $user->updateUser(); + } + + # show authcode page + return $this->c->get('view')->render($response, 'auth/authcode.twig', [ + 'username' => $userdata['username'], + 'authtitle' => $authtitle, + 'authtext' => $authtext + ]); + } + + # check if user has confirmed the account + if(isset($userdata['optintoken']) && $userdata['optintoken']) + { + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('login: user not confirmed yet.'); + } + + if($this->c->get('flash')) + { + $this->c->get('flash')->addMessage('error', Translations::translate('Your registration is not confirmed yet. Please check your e-mails and use the confirmation link.')); + } + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); + } + + $user->login(); + + $redirect = $this->getRedirectDestination($userdata['userrole']); + + return $response->withHeader('Location', $this->routeParser->urlFor($redirect))->withStatus(302); + } + + # login a user with valid authcode + public function loginWithAuthcode(Request $request, Response $response) + { + $input = $request->getParsedBody(); + $validation = new Validation(); + $securitylog = $this->settings['securitylog'] ?? false; + + if($validation->authcode($input) !== true) + { + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('login: invalid verification code format'); + } + + if($this->c->get('flash')) + { + $this->c->get('flash')->addMessage('error', Translations::translate('Invalid verification code format, please try again.')); + } + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); + } + + $user = new User(); + + if(!$user->setUserWithPassword($input['username'])) + { + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('login: user not found'); + } + + if($this->c->get('flash')) + { + $this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.')); + } + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); + } + + $userdata = $user->getUserData(); + $authcodevalue = $input['code-1'] . $input['code-2'] . $input['code-3'] . $input['code-4'] . $input['code-5']; + $authcodedata = $this->checkAuthcode($userdata); + + if(!$this->validateAuthcode($authcodevalue, $authcodedata)) + { + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('login: verification code wrong or outdated.'); + } + + if($this->c->get('flash')) + { + $this->c->get('flash')->addMessage('error', Translations::translate('The verification was wrong or outdated, please start again.')); + } + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); + } + + # add the device fingerprint if not set yet + $fingerprints = $userdata['fingerprints'] ?? []; + $fingerprint = $this->generateDeviceFingerprint(); + if(!$this->findDeviceFingerprint($fingerprint, $userdata)) + { + $fingerprints[] = $fingerprint; + $user->setValue('fingerprints', $fingerprints); + } + + # update authcode lastValidation and store + $user->setValue('authcodedata', $authcodevalue . ':' . $authcodedata['generated'] . ':' . time()); + $user->updateUser(); + + $user->login(); + + $redirect = $this->getRedirectDestination($userdata['userrole']); + + return $response->withHeader('Location', $this->routeParser->urlFor($redirect))->withStatus(302); + } + + public function loginlink(Request $request, Response $response, $args) + { + if(!isset($this->settings['loginlink']) OR !$this->settings['loginlink']) + { + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('loginlink: not activated'); + } + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); + } + + # optionally check trusted ips + $trustedLogin = ( isset($this->settings['trustedloginreferrer']) && !empty($this->settings['trustedloginreferrer']) ) ? explode(",", $this->settings['trustedloginreferrer']) : []; + $ipAddress = $_SERVER['REMOTE_ADDR'] ?? null; + if ( + !empty($trustedLogin) + && !in_array($ipAddress, $trustedLogin) + ) + { + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('loginlink: remote address is not a trusted ip'); + } + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); + } + + $input = $request->getQueryParams(); + $validation = new Validation(); + $securitylog = $this->settings['securitylog'] ?? false; + + if($validation->signin($input) !== true) + { + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('loginlink: invalid data'); + } + + if($this->c->get('flash')) + { + $this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.')); + } + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); + } + + $user = new User(); + + if(!$user->setUserWithPassword($input['username'])) + { + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('loginlink: user not found'); + } + + if($this->c->get('flash')) + { + $this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.')); + } + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); + } + + $userdata = $user->getUserData(); + + if($userdata['userrole'] != 'guest') + { + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('loginlink: user has not a member role. Only members can use loginlinks.'); + } + + if($this->c->get('flash')) + { + $this->c->get('flash')->addMessage('error', Translations::translate('User is not a member.')); + } + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); + } + + if(!isset($userdata['linkaccess']) OR ($userdata['linkaccess'] !== true)) + { + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('loginlink: loginlink for user ' . $userdata['username'] . ' is not activated.'); + } + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); + } + + if($userdata && !password_verify($input['password'], $userdata['password'])) + { + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('login: wrong password'); + } + + if($this->c->get('flash')) + { + $this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.')); + } + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); + } + + # check if user has confirmed the account + if(isset($userdata['optintoken']) && $userdata['optintoken']) + { + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('login: user not confirmed yet.'); + } + + if($this->c->get('flash')) + { + $this->c->get('flash')->addMessage('error', Translations::translate('Your registration is not confirmed yet. Please check your e-mails and use the confirmation link.')); + } + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); + } + + $user->login(); + + $redirect = $this->getRedirectDestination($userdata['userrole']); + + return $response->withHeader('Location', $this->routeParser->urlFor($redirect))->withStatus(302); + } + + + private function getRedirectDestination(string $userrole) + { + # decide where to redirect after login, configurable in settings -> system.yaml + $redirect = 'home'; + $acl = $this->c->get('acl'); + if($acl->hasRole($userrole)) + { + if($acl->isAllowed($userrole, 'system', 'read')) + { + # defaults to content editor + $redirect = 'content'; + if(isset($this->settings['redirectadminrights']) && $this->settings['redirectadminrights']) + { + $redirect = $this->settings['redirectadminrights']; + } + } + elseif($acl->isAllowed($userrole, 'content', 'read')) + { + # defaults to content editor + $redirect = 'content'; + if(isset($this->settings['redirectcontentrights']) && $this->settings['redirectcontentrights']) + { + $redirect = $this->settings['redirectcontentrights']; + } + } + elseif($acl->isAllowed($userrole, 'account', 'read')) + { + $redirect = 'user.account'; + if(isset($this->settings['redirectaccountrights']) && $this->settings['redirectaccountrights']) + { + $redirect = $this->settings['redirectaccountrights']; + } + } + + if($redirect == 'content') + { + $editor = (isset($this->settings['editor']) && $this->settings['editor'] == 'visual') ? 'visual' : 'raw'; + $redirect = 'content.' . $editor; + } + } + + return $redirect; + } + + private function isAuthcodeActive($settings) + { + if( + isset($settings['authcode']) && + $settings['authcode'] && + isset($settings['mailfrom']) && + filter_var($settings['mailfrom'], FILTER_VALIDATE_EMAIL) + ) + { + return true; + } + + return false; + } + + # log out a user + public function logout(Request $request, Response $response) + { + \Typemill\Static\Session::stopSession(); + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); + } + + + # check if the stored authcode in userdata is valid and/or fresh + private function checkAuthcode($userdata) + { + # format: 12345:time(generated):time(validated) + + $authcodedata = $userdata['authcodedata'] ?? false; + + if(!$authcodedata) + { + return $authcode = [ + 'value' => 12345, + 'generated' => 12345, + 'validated' => 12345, + 'valid' => false, + 'fresh' => false + ]; + } + + $validation = new Validation(); + $authcodedata = explode(":", $authcodedata); + + # validate format here, do we need it? + + $now = time(); + $lastValidation = 60 * 60 * 24; + $lastGeneration = 60 * 5; + $valid = false; + $fresh = false; + + # if last validation is less than 24 hours old + if($now - $lastValidation < $authcodedata[2]) + { + $valid = true; + } + + # if last generation is less than 5 minutes old + if($now - $lastGeneration < $authcodedata[1]) + { + $fresh = true; + } + + $authcode = [ + 'value' => $authcodedata[0], + 'generated' => $authcodedata[1], + 'validated' => $authcodedata[2], + 'valid' => $valid, + 'fresh' => $fresh + ]; + + return $authcode; + } + + # check if the submitted authcode is the same as the stored authcode + private function validateAuthcode($authcodevalue, $authcodedata) + { + if($authcodedata['valid'] === true) + { + return true; + } + + if($authcodedata['fresh'] === false) + { + return false; + } + + if($authcodevalue == $authcodedata['value']) + { + return true; + } + + return false; + } + + # create a simple device fingerprint + private function generateDeviceFingerprint() + { + $userAgent = $_SERVER['HTTP_USER_AGENT']; + $ipAddress = $_SERVER['REMOTE_ADDR']; + $acceptLanguage = isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : ''; + + $fingerprint = md5($userAgent . $ipAddress . $acceptLanguage); + + return $fingerprint; + } + + # create a simple device fingerprint + private function findDeviceFingerprint($fingerprint, $userdata) + { + if(!isset($userdata['fingerprints']) or empty($userdata['fingerprints'])) + { + return false; + } + + if(!in_array($fingerprint, $userdata['fingerprints'])) + { + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/system/typemill/Controllers/ControllerWebAuthor.php b/system/typemill/Controllers/ControllerWebAuthor.php new file mode 100644 index 0000000..c72a54e --- /dev/null +++ b/system/typemill/Controllers/ControllerWebAuthor.php @@ -0,0 +1,149 @@ +c->get('urlinfo'); + $fullUrl = $urlinfo['baseurl'] . $url; + $langattr = $this->settings['langattr']; + + $navigation = new Navigation(); + $draftNavigation = $navigation->getFullDraftNavigation($urlinfo, $langattr); + $home = $navigation->getHomepageItem($urlinfo['baseurl']); + + if($url == '/') + { + $item = $home; + $item->active = true; + } + else + { + $pageinfo = $navigation->getPageInfoForUrl($url, $urlinfo, $langattr); + if(!$pageinfo) + { + return $this->c->get('view')->render($response->withStatus(404), '404.twig', [ + 'title' => 'Blox editor', + 'description' => 'Edit your content with the visual blox editor' + ]); + } + + $keyPathArray = explode(".", $pageinfo['keyPath']); + + $draftNavigation = $navigation->setActiveNaviItemsWithKeyPath($draftNavigation, $keyPathArray); + $draftNavigation = $this->c->get('dispatcher')->dispatch(new OnPagetreeLoaded($draftNavigation), 'onPagetreeLoaded')->getData(); + + $item = $navigation->getItemWithKeyPath($draftNavigation, $keyPathArray); + $item = $this->c->get('dispatcher')->dispatch(new OnItemLoaded($item), 'onItemLoaded')->getData(); + } + + # $item->modified = ($item->published OR $item->drafted) ? filemtime($this->settings['contentFolder'] . $this->path) : false; + + $mainNavigation = $navigation->getMainNavigation($request->getAttribute('c_userrole'), $this->c->get('acl'), $urlinfo, $this->settings['editor']); + + $content = new Content($urlinfo['baseurl'], $this->settings, $this->c->get('dispatcher')); + + $draftMarkdown = $content->getDraftMarkdown($item); + $draftMarkdown = $this->c->get('dispatcher')->dispatch(new OnMarkdownLoaded($draftMarkdown), 'onMarkdownLoaded')->getData(); + + $draftMarkdownHtml = $content->addDraftHtml($draftMarkdown); + + + return $this->c->get('view')->render($response, 'content/blox-editor.twig', [ + 'settings' => $this->settings, + 'darkmode' => $request->getAttribute('c_darkmode'), + 'mainnavi' => $mainNavigation, + 'content' => $draftMarkdownHtml, + 'jsdata' => [ + 'settings' => $this->settings, + 'urlinfo' => $urlinfo, + 'labels' => $this->c->get('translations'), + 'navigation' => $draftNavigation, + 'item' => $item, + 'home' => $home, + 'content' => $draftMarkdownHtml + ] + ]); + } + + public function showRaw(Request $request, Response $response, $args) + { + # get url for requested page + $url = isset($args['route']) ? '/' . $args['route'] : '/'; + $urlinfo = $this->c->get('urlinfo'); + $fullUrl = $urlinfo['baseurl'] . $url; + $langattr = $this->settings['langattr']; + + $navigation = new Navigation(); + $draftNavigation = $navigation->getFullDraftNavigation($urlinfo, $langattr); + $home = $navigation->getHomepageItem($urlinfo['baseurl']); + + if($url == '/') + { + $item = $home; + $item->active = true; + } + else + { + $pageinfo = $navigation->getPageInfoForUrl($url, $urlinfo, $langattr); + if(!$pageinfo) + { + return $this->c->get('view')->render($response->withStatus(404), '404.twig', [ + 'title' => 'Raw editor', + 'description' => 'Edit your content with the raw editor in pure markdown syntax.' + ]); + } + + $keyPathArray = explode(".", $pageinfo['keyPath']); + + # extend : $request->getAttribute('c_userrole') + $draftNavigation = $navigation->setActiveNaviItemsWithKeyPath($draftNavigation, $keyPathArray); + $draftNavigation = $this->c->get('dispatcher')->dispatch(new OnPagetreeLoaded($draftNavigation), 'onPagetreeLoaded')->getData(); + + $item = $navigation->getItemWithKeyPath($draftNavigation, $keyPathArray); + $item = $this->c->get('dispatcher')->dispatch(new OnItemLoaded($item), 'onItemLoaded')->getData(); + } + + # $item->modified = ($item->published OR $item->drafted) ? filemtime($this->settings['contentFolder'] . $this->path) : false; + + $mainNavigation = $navigation->getMainNavigation($request->getAttribute('c_userrole'), $this->c->get('acl'), $urlinfo, $this->settings['editor']); + + $content = new Content($urlinfo['baseurl'], $this->settings, $this->c->get('dispatcher')); + + $draftMarkdown = $content->getDraftMarkdown($item); + $draftMarkdown = $this->c->get('dispatcher')->dispatch(new OnMarkdownLoaded($draftMarkdown), 'onMarkdownLoaded')->getData(); + + $draftMarkdownHtml = $content->addDraftHtml($draftMarkdown); + + return $this->c->get('view')->render($response, 'content/raw-editor.twig', [ + 'settings' => $this->settings, + 'darkmode' => $request->getAttribute('c_darkmode'), + 'mainnavi' => $mainNavigation, + 'content' => $draftMarkdownHtml, + 'jsdata' => [ + 'settings' => $this->settings, + 'urlinfo' => $urlinfo, + 'labels' => $this->c->get('translations'), + 'navigation' => $draftNavigation, + 'item' => $item, + 'home' => $home, + 'content' => $draftMarkdownHtml, + ] + ]); + } +} \ No newline at end of file diff --git a/system/typemill/Controllers/ControllerWebDownload.php b/system/typemill/Controllers/ControllerWebDownload.php new file mode 100644 index 0000000..c04a38a --- /dev/null +++ b/system/typemill/Controllers/ControllerWebDownload.php @@ -0,0 +1,163 @@ +getBody()->write(Translations::translate('the requested file does not exist.')); + return $response->withStatus(404); + } + + $storage = new StorageWrapper('\Typemill\Models\Storage'); + $restrictions = $storage->getYaml('fileFolder', '', 'filerestrictions.yaml'); + + $filepath = $storage->getFolderPath('fileFolder'); + $filefolder = 'media/files/'; + + # validate + $allowedFiletypes = []; + if(!$this->validate($filepath, $filename, $allowedFiletypes)) + { + $response->getBody()->write(Translations::translate('the requested filetype does not exist.')); + return $response->withStatus(404); + } + + if($restrictions && isset($restrictions[$filefolder . $filename])) + { + $userrole = $request->getAttribute('c_userrole'); + $allowedrole = $restrictions[$filefolder . $filename]; + + if(!$userrole) + { + $this->c->get('flash')->addMessage('error', Translations::translate('To download this file you need to be authenticated with the role') . ' ' . $allowedrole ); + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); + } + elseif( + $userrole != 'administrator' + AND $userrole != $allowedrole + AND !$this->c->get('acl')->inheritsRole($userrole, $allowedrole) + ) + { + $this->c->get('flash')->addMessage('error', Translations::translate('To download this file you need to be authenticated with the role') . ' ' . $allowedrole ); + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302); + } + } + + $file = $filepath . $filename; + + # Dynamically determine MIME type based on the file extension + $pathinfo = pathinfo($file); + $extension = strtolower($pathinfo['extension']); + + # You can extend this list based on the file types you expect to serve + # http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types + $mime_types = [ + 'zip' => 'application/zip', + 'pdf' => 'application/pdf', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'png' => 'image/png', + 'default' => 'application/octet-stream', + ]; + + $mimetype = $mime_types[$extension] ?? $mime_types['default']; + + # Disable zlib.output_compression for this download + if (ini_get('zlib.output_compression')) + { + ini_set('zlib.output_compression', 'Off'); + } + + try { + + # Read the file content + $fileContent = file_get_contents($file); + if ($fileContent === false) { + throw new Exception('Failed to read file content.'); + } + + # Clear the response body and write the file content + $body = new \Slim\Psr7\Stream(fopen('php://temp', 'r+')); + $body->write($fileContent); + $response = $response->withBody($body); + + # Set headers + $response = $response->withBody($body) + ->withHeader('Content-Type', $mimetype) + ->withHeader('Content-Disposition', 'attachment; filename="' . basename($file) . '"') + ->withHeader('Content-Length', filesize($file)) + ->withHeader('Pragma', 'public') + ->withHeader('Cache-Control', 'max-age=0, no-cache, no-store, must-revalidate') + ->withHeader('Content-Encoding', 'none') + ->withHeader('Accept-Ranges', 'bytes'); + + return $response; + + } catch (Exception $e) { + + # Log the error + error_log('Error sending file: ' . $e->getMessage()); + + # Return an error response + $response->getBody()->write("Error downloading file. Please try again later."); + return $response->withStatus(500); + } + } + + /** + * Validate if the file exists and if + * there is a permission (download dir) to download this file + * + * You should ALWAYS call this method if you don't want + * somebody to download files not intended to be for the public. + * + * @param string $file GET parameter + * @param array $allowedFiletypes (defined in the head of this file) + * @return bool true if validation was successfull + */ + private function validate($path, $filename, $allowedFiletypes) + { + $filepath = $path . $filename; + + # check if file exists + if (!isset($filepath) || empty($filepath) || !file_exists($filepath) ) + { + return false; + } + + # check allowed filetypes + if(!empty($allowedFiletypes)) + { + $fileAllowed = false; + foreach ($allowedFiletypes as $filetype) + { + if (strpos($filename, $filetype) === (strlen($filename) - strlen($filetype))) + { + $fileAllowed = true; //ends with $filetype + } + } + + if (!$fileAllowed) return false; + } + + # check download directory + if (strpos($filename, '..') !== false) + { + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/system/typemill/Controllers/ControllerWebFrontend.php b/system/typemill/Controllers/ControllerWebFrontend.php new file mode 100644 index 0000000..aaa7e08 --- /dev/null +++ b/system/typemill/Controllers/ControllerWebFrontend.php @@ -0,0 +1,443 @@ +c->get('urlinfo'); + $langattr = $this->settings['langattr']; + $userrole = $request->getAttribute('c_userrole'); + $username = $request->getAttribute('c_username'); + + # GET THE NAVIGATION + $navigation = new Navigation(); + $draftNavigation = $navigation->getFullDraftNavigation($urlinfo, $langattr); + $home = false; + + # GET THE PAGINATION + $currentpage = $navigation->getCurrentPage($args); + if($currentpage) + { + $url = str_replace("/p/" . $currentpage, "", $url); + } + $fullUrl = $urlinfo['baseurl'] . $url; + + $pagedata = [ + 'home' => $home, + 'title' => 'Page not found', + 'description' => 'Sorry, but we did not find the page you where looking for.', + 'settings' => $this->settings, + 'base_url' => $urlinfo['baseurl'], + 'logo' => false, + 'favicon' => false, + ]; + + + # FIND THE PAGE/ITEM IN NAVIGATION + if($url == '/') + { + $item = $navigation->getHomepageItem($urlinfo['baseurl']); + $item->active = true; + $home = true; + } + else + { + $pageinfo = $navigation->getPageInfoForUrl($url, $urlinfo, $langattr); + + if(!$pageinfo) + { + return $this->c->get('view')->render($response->withStatus(404), '404.twig', $pagedata); + } + + $keyPathArray = explode(".", $pageinfo['keyPath']); + + $item = $navigation->getItemWithKeyPath($draftNavigation, $keyPathArray); + + if(!$item) + { + return $this->c->get('view')->render($response->withStatus(404), '404.twig', $pagedata); + } + } + + # CREATE THE BREADCRUMB + $breadcrumb = $navigation->getBreadcrumb($draftNavigation, $item->keyPathArray); + $breadcrumb = $this->c->get('dispatcher')->dispatch(new OnBreadcrumbLoaded($breadcrumb), 'onBreadcrumbLoaded')->getData(); + + # CHECK IF WHOLE TREE IS PUBLISHED + if($breadcrumb) + { + foreach($breadcrumb as $page) + { + if($page->status == 'unpublished') + { + return $this->c->get('view')->render($response->withStatus(404), '404.twig', $pagedata); + } + } + } + + $liveNavigation = $navigation->generateLiveNavigationFromDraft($draftNavigation); + + # STRIP OUT HIDDEN PAGES + $liveNavigation = $navigation->removeHiddenPages($liveNavigation); + + # SET PAGEs ACTIVE + $liveNavigation = $navigation->setActiveNaviItemsWithKeyPath($liveNavigation, $item->keyPathArray); + + # DISPATCH LIVE NAVIGATION + $liveNavigation = $this->c->get('dispatcher')->dispatch(new OnPagetreeLoaded($liveNavigation), 'onPagetreeLoaded')->getData(); + + # For FOLDERS use item without drafts and hidden pages + if(!$home && $item->elementType == 'folder') + { + # if folder itself is not hidden + if($item->hide && count($item->folderContent) > 0) + { + $item->folderContent = $navigation->generateLiveNavigationFromDraft($item->folderContent); + } + else + { + # $item = $navigation->getItemWithUrl($liveNavigation, $item->urlRelWoF); + $item = $navigation->getItemWithKeyPath($liveNavigation, $item->keyPathArray); + } + } + + # ADD BACKWARD-/FORWARD PAGINATION + $item = $navigation->getPagingForItem($liveNavigation, $item); + $item = $this->c->get('dispatcher')->dispatch(new OnItemLoaded($item), 'onItemLoaded')->getData(); + + + # GET THE CONTENT + $content = new Content($urlinfo['baseurl'], $this->settings, $this->c->get('dispatcher')); + $liveMarkdown = $content->getLiveMarkdown($item); + $liveMarkdown = $this->c->get('dispatcher')->dispatch(new OnMarkdownLoaded($liveMarkdown), 'onMarkdownLoaded')->getData(); + $markdownArray = $content->markdownTextToArray($liveMarkdown); + + + # GET THE META + $meta = new Meta(); + $metadata = $meta->getMetaData($item); + $metadata = $meta->addMetaDefaults($metadata, $item, $this->settings['author']); + $metadata = $meta->addMetaTitleDescription($metadata, $item, $markdownArray); + $metadata = $this->c->get('dispatcher')->dispatch(new OnMetaLoaded($metadata),'onMetaLoaded')->getData(); + + + # REFERENCE FEATURE + if(isset($metadata['meta']['referencetype']) && $metadata['meta']['referencetype'] != 'disable') + { + $referenceurl = rtrim($urlinfo['baseurl'], '/') . '/' . trim($metadata['meta']['reference'], '/'); + + switch ($metadata['meta']['referencetype']) { + case 'redirect301': + return $response->withHeader('Location', $referenceurl)->withStatus(301); + break; + case 'redirect302': + return $response->withHeader('Location', $referenceurl)->withStatus(302); + break; + case 'outlink': + return $response->withHeader('Location', $metadata['meta']['reference'])->withStatus(301); + break; + case 'copy': + $refpageinfo = $navigation->getPageInfoForUrl($metadata['meta']['reference'], $urlinfo, $langattr); + if(!$refpageinfo) + { + return $this->c->get('view')->render($response->withStatus(404), '404.twig', $pagedata); + } + + $refKeyPathArray = explode(".", $refpageinfo['keyPath']); + $refitem = $navigation->getItemWithKeyPath($draftNavigation, $refKeyPathArray); + + # GET THE CONTENT FROM REFENCED PAGE + $liveMarkdown = $content->getLiveMarkdown($refitem); + $liveMarkdown = $this->c->get('dispatcher')->dispatch(new OnMarkdownLoaded($liveMarkdown), 'onMarkdownLoaded')->getData(); + $markdownArray = $content->markdownTextToArray($liveMarkdown); + + # GET THE META FROM REFERENCED PAGE + $refmeta = $meta->getMetaData($refitem); + if($refmeta && isset($refmeta['meta'])) + { + $metadata = $meta->getMetaData($refitem); + $metadata = $meta->addMetaDefaults($metadata, $refitem, $this->settings['author']); + $metadata = $meta->addMetaTitleDescription($metadata, $refitem, $markdownArray); + } + + break; + } + } + + + # CHECK ACCESS RESTRICTIONS + $restricted = $this->checkRestrictions($metadata['meta'], $username, $userrole); + if($restricted) + { + # infos that plugins need to add restriction content + $restrictions = [ + 'restricted' => $restricted, + 'defaultContent' => true, + 'markdownBlocks' => $markdownArray, + ]; + + # dispatch the data + $restrictions = $this->c->get('dispatcher')->dispatch(new OnRestrictionsLoaded( $restrictions ), 'onRestrictionsLoaded')->getData(); + + # use the returned markdown + $markdownArray = $restrictions['markdownBlocks']; + + # if no plugin has disabled the default behavior + if($restrictions['defaultContent']) + { + # cut the restricted content + $shortenedPage = $this->cutRestrictedContent($markdownArray); + + # check if there is customized content + $restrictionnotice = $this->prepareRestrictionNotice(); + + # add notice to shortened content + $shortenedPage[] = $restrictionnotice; + + # Use the shortened page + $markdownArray = $shortenedPage; + } + } + + + # EXTRACT THE ARTICLE TITLE/HEADLINE + $title = trim(array_shift($markdownArray), "# "); + + + # TRANSFORM THE ARTICLE BODY TO HTML + $body = $content->markdownArrayToText($markdownArray); + $contentArray = $content->getContentArray($body); + $contentArray = $this->c->get('dispatcher')->dispatch(new OnContentArrayLoaded($contentArray), 'onContentArrayLoaded')->getData(); + $contentHtml = $content->getContentHtml($contentArray); + $contentHtml = $this->c->get('dispatcher')->dispatch(new OnHtmlLoaded($contentHtml), 'onHtmlLoaded')->getData(); + + + # ADD LOGO + $logo = false; + if(isset($this->settings['logo']) && $this->settings['logo'] != '' && $content->checkLogoFile($this->settings['logo'])) + { + $logo = $this->settings['logo']; + } + + + # ADD ASSETS + $assets = $this->c->get('assets'); + + + # ADD CUSTOM THEME CSS + $theme = $this->settings['theme']; + $customcss = $content->checkCustomCSS($theme); + if($customcss) + { + $assets->addCSS($urlinfo['baseurl'] . '/cache/' . $theme . '-custom.css'); + } + + + # ADD FAVICON + $favicon = false; + if(isset($this->settings['favicon']) && $this->settings['favicon'] != '') + { + $favicon = true; + $assets->addMeta('tilecolor',''); + $assets->addMeta('tileimage',''); + $assets->addMeta('icon16',''); + $assets->addMeta('icon32',''); + $assets->addMeta('icon72',''); + $assets->addMeta('icon114',''); + $assets->addMeta('icon144',''); + $assets->addMeta('icon180',''); + } + + + # ADD META TAGS + if(isset($metadata['meta']['noindex']) && $metadata['meta']['noindex']) + { + $assets->addMeta('noindex',''); + } + $assets->addMeta('og_site_name',''); + $assets->addMeta('og_title',''); + $assets->addMeta('og_description',''); + $assets->addMeta('og_type',''); + $assets->addMeta('og_url',''); + + + # meta image + $metaImageUrl = $metadata['meta']['heroimage'] ?? false; + $metaImageAlt = $metadata['meta']['heroimagealt'] ?? false; + if(!$metaImageUrl OR $metaImageUrl == '') + { + # extract first image from content + $firstImageMD = $content->getFirstImage($contentArray); + if($firstImageMD) + { + preg_match('#\((.*?)\)#', $firstImageMD, $img_url_result); + $metaImageUrl = isset($img_url_result[1]) ? $img_url_result[1] : false; + if($metaImageUrl) + { + preg_match('#\[(.*?)\]#', $firstImageMD, $img_alt_result); + $metaImageAlt = isset($img_alt_result[1]) ? $img_alt_result[1] : false; + } + } + elseif($logo) + { + $metaImageUrl = $logo; + $pathinfo = pathinfo($this->settings['logo']); + $metaImageAlt = $pathinfo['filename']; + } + } + if($metaImageUrl) + { + $assets->addMeta('og_image',''); + $assets->addMeta('twitter_image_alt',''); + $assets->addMeta('twitter_card',''); + } + + $pagedata = [ + 'home' => $home, + 'navigation' => $liveNavigation, + 'title' => $title, + 'content' => $contentHtml, + 'item' => $item, + 'breadcrumb' => $breadcrumb, + 'settings' => $this->settings, + 'base_url' => $urlinfo['baseurl'], + 'metatabs' => $metadata, + 'logo' => $logo, + 'favicon' => $favicon, + 'currentpage' => $currentpage + ]; + + $morepagedata = $this->c->get('dispatcher')->dispatch(new OnPageReady([]), 'onPageReady')->getData(); + + $pagedata = array_merge($pagedata, $morepagedata); + + $route = empty($args) && isset($this->settings['themes'][$theme]['cover']) ? 'cover.twig' : 'index.twig'; + + return $this->c->get('view')->render($response, $route, $pagedata); + } + + + # checks if a page has a restriction in meta and if the current user is blocked by that restriction + public function checkRestrictions($meta, $username, $userrole) + { + # check if content restrictions are active + if(isset($this->settings['pageaccess']) && $this->settings['pageaccess']) + { + + # check if page is restricted to certain user + if(isset($meta['alloweduser']) && $meta['alloweduser'] && $meta['alloweduser'] !== '' ) + { + $alloweduser = array_map('trim', explode(",", $meta['alloweduser'])); + if(isset($username) && in_array($username, $alloweduser)) + { + # user has access to the page, so there are no restrictions + return false; + } + + # otherwise return array with type of restriction and allowed username + return [ 'alloweduser' => $meta['alloweduser'] ]; + } + + # check if page is restricted to certain userrole + if(isset($meta['allowedrole']) && $meta['allowedrole'] && $meta['allowedrole'] !== '' ) + { + if( + $userrole + AND ( + $userrole == 'administrator' + OR $userrole == $meta['allowedrole'] + OR $this->c->get('acl')->inheritsRole($userrole, $meta['allowedrole']) + ) + ) + { + # role has access to page, so there are no restrictions + return false; + } + + return [ 'allowedrole' => $meta['allowedrole'] ]; + } + + } + + return false; + + } + + protected function cutRestrictedContent($markdown) + { + #initially add only the title of the page. + $restrictedMarkdown = [$markdown[0]]; + unset($markdown[0]); + + if(isset($this->settings['hrdelimiter']) && $this->settings['hrdelimiter'] !== NULL ) + { + foreach ($markdown as $block) + { + $firstCharacters = substr($block, 0, 3); + if($firstCharacters == '---' OR $firstCharacters == '***') + { + return $restrictedMarkdown; + } + $restrictedMarkdown[] = $block; + } + + # no delimiter found, so use the title only + $restrictedMarkdown = [$restrictedMarkdown[0]]; + } + + return $restrictedMarkdown; + } + + protected function prepareRestrictionNotice() + { + if( isset($this->settings['restrictionnotice']) && $this->settings['restrictionnotice'] != '' ) + { + $restrictionNotice = $this->settings['restrictionnotice']; + } + else + { + $restrictionNotice = 'You are not allowed to access this content.'; + } + + if( isset($this->settings['wraprestrictionnotice']) && $this->settings['wraprestrictionnotice'] ) + { + # standardize line breaks + $text = str_replace(array("\r\n", "\r"), "\n", $restrictionNotice); + + # remove surrounding line breaks + $text = trim($text, "\n"); + + # split text into lines + $lines = explode("\n", $text); + + $restrictionNotice = ''; + + foreach($lines as $key => $line) + { + $restrictionNotice .= "!!!! " . $line . "\n"; + } + } + + return $restrictionNotice; + } +} \ No newline at end of file diff --git a/system/typemill/Controllers/ControllerWebRecover.php b/system/typemill/Controllers/ControllerWebRecover.php new file mode 100644 index 0000000..772fc7c --- /dev/null +++ b/system/typemill/Controllers/ControllerWebRecover.php @@ -0,0 +1,334 @@ +c->get('view')->render($response, '/auth/recover.twig', [ + + ]); + } + + public function recoverPassword(Request $request, Response $response) + { + $params = $request->getParsedBody(); + $settings = $this->c->get('settings'); + $urlinfo = $this->c->get('urlinfo'); + + if(!isset($params['email']) OR filter_var($params['email'], \FILTER_VALIDATE_EMAIL) === false ) + { + $this->c->get('flash')->addMessage('error', Translations::translate('Please enter a valid email.')); + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.recoverform'))->withStatus(302); + } + + $title = Translations::translate('Check your inbox'); + $message = Translations::translate('Please check the inbox of your email account for more instructions.'); + + $user = new User(); + $requiredUser = $user->findUsersByEmail($params['email']); + + if($requiredUser) + { + $user->setUserWithPassword($requiredUser[0]); + + $requiredUser = $user->getUserData(); + $recoverdate = date("Y-m-d H:i:s"); + $recovertoken = bin2hex(random_bytes(32)); + + $url = $urlinfo['baseurl'] . '/tm/reset?username=' . $requiredUser['username'] . '&recovertoken=' . $recovertoken; + $link = '' . $url . ''; + + # define the headers + $mail = new SimpleMail($settings); + + $subject = (isset($settings['recoversubject']) && ($settings['recoversubject'] != '') ) ? $settings['recoversubject'] : 'Recover your password'; + + $messagetext = Translations::translate('Dear user'); + $messagetext .= ",

"; + $messagetext .= Translations::translate('please use the following link to set a new password') . ':'; + if(isset($settings['recovermessage']) && ($settings['recovermessage'] != '')) + { + $parsedown = new ParsedownExtension($urlinfo['baseurl']); + $parsedown->setSafeMode(true); + + $contentArray = $parsedown->text($settings['recovermessage']); + $messagetext = $parsedown->markup($contentArray); + } + + $message = $messagetext . "

" . $link; + + $send = $mail->send($requiredUser['email'], $subject, $message); + + if(!$send) + { + $title = Translations::translate('Error sending email'); + $message = Translations::translate('Dear ') . $requiredUser['username'] . ', ' . Translations::translate('we could not send the email with the password instructions to your address. Reason: ') . $mail->error; + } + else + { + # update user + $user->setValue('recoverdate', $recoverdate); + $user->setValue('recovertoken', $recovertoken); + $user->updateUser(); + + $title = Translations::translate('Check your inbox'); + $message = Translations::translate('Dear ') . $requiredUser['username'] . ', ' . Translations::translate('please check the inbox of your email account for more instructions.') . ' ' . Translations::translate('Do not forget to check your spam-folder if your inbox is empty.'); + } + } + elseif(isset($settings['securitylog']) && $settings['securitylog']) + { + \Typemill\Static\Helpers::addLogEntry('wrong input for password recovery'); + } + + return $this->c->get('view')->render($response, '/auth/recoverconf.twig', [ + 'title' => $title, + 'message' => $message + ]); + } + + public function showPasswordResetForm(Request $request, Response $response, $args) + { + $params = $request->getQueryParams(); + $securitylog = ( isset($settings['securitylog']) && $settings['securitylog'] ) ? true : false; + + if(!isset($params['username']) OR !isset($params['recovertoken'])) + { + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('wrong password reset link'); + } + + $this->c->get('flash')->addMessage('error', Translations::translate('You tried to open the password reset page but the link was invalid.')); + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.login'))->withStatus(302); + } + + $user = new User(); + $requiredUser = $user->setUserWithPassword($params['username']); + + if(!$requiredUser) + { + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('password reset link user not found'); + } + + $this->c->get('flash')->addMessage('error', Translations::translate('You tried to open the password reset page but the link was invalid.')); + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.login'))->withStatus(302); + } + + $requiredUser = $user->getUserData(); + + if(!isset($requiredUser['recovertoken']) OR $requiredUser['recovertoken'] != $params['recovertoken'] ) + { + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('password reset link wrong token'); + } + + $this->c->get('flash')->addMessage('error', Translations::translate('You tried to open the password reset page but the link was invalid.')); + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.login'))->withStatus(302); + } + + $recoverdate = isset($requiredUser['recoverdate']) ? $requiredUser['recoverdate'] : false; + + if(!$recoverdate) + { + $user->unsetValue('recovertoken'); + $user->updateUser(); + + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('password reset link outdated'); + } + + $this->c->get('flash')->addMessage('error', Translations::translate('The link to recover the password was too old. Please create a new one.')); + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.login'))->withStatus(302); + } + + $now = new \DateTime('NOW'); + $recoverdate = new \DateTime($recoverdate); + + if(!$recoverdate) + { + $user->unsetValue('recovertoken'); + $user->unsetValue('recoverdate'); + $user->updateUser(); + + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('password reset link wrong date format'); + } + + $this->c->get('flash')->addMessage('error', Translations::translate('The link to recover the password was too old. Please create a new one.')); + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.login'))->withStatus(302); + } + +# here we should make the interval editable + $validDate = $recoverdate->add(new \DateInterval('P1D')); + + if($validDate <= $now) + { + $user->unsetValue('recovertoken'); + $user->unsetValue('recoverdate'); + $user->updateUser(); + + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('password reset link outdated'); + } + + $this->c->get('flash')->addMessage('error', Translations::translate('The link to recover the password was too old. Please create a new one.')); + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.login'))->withStatus(302); + } + + return $this->c->get('view')->render($response, '/auth/reset.twig', [ + 'recovertoken' => $params['recovertoken'], + 'username' => $requiredUser['username'] + ]); + } + + public function resetPassword(Request $request, Response $response, $args) + { + $params = $request->getParsedBody(); + $settings = $this->c->get('settings'); + $urlinfo = $this->c->get('urlinfo'); + + if(!isset($params['username']) OR !isset($params['recovertoken'])) + { + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('create reset password username or token missing'); + } + + $this->c->get('flash')->addMessage('error', Translations::translate('You tried to set a new password but username or token was invalid.')); + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.login'))->withStatus(302); + } + + $validation = new Validation(); + + if($validation->recoverPassword($params) !== true) + { + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('create reset password wrong input'); + } + + $this->c->get('flash')->addMessage('error', Translations::translate('Please correct your input.')); + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.resetform', [], ['username' => $params['username'], 'recovertoken' => $params['recovertoken']]))->withStatus(302); + } + + $user = new User(); + $requiredUser = $user->setUserWithPassword($params['username']); + if(!$requiredUser) + { + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('create reset password user not found'); + } + + $this->c->get('flash')->addMessage('error', Translations::translate('You tried to open the password reset page but the link was invalid.')); + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.login'))->withStatus(302); + } + + $requiredUser = $user->getUserData(); + + if(!isset($requiredUser['recovertoken']) OR $requiredUser['recovertoken'] != $params['recovertoken'] ) + { + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('create reset password wrong token'); + } + + $this->c->get('flash')->addMessage('error', Translations::translate('You tried to open the password reset page but the link was invalid.')); + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.login'))->withStatus(302); + } + + $recoverdate = isset($requiredUser['recoverdate']) ? $requiredUser['recoverdate'] : false; + + if(!$recoverdate) + { + $user->unsetValue('recovertoken'); + $user->updateUser(); + + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('create reset password date outdated'); + } + + $this->c->get('flash')->addMessage('error', Translations::translate('The link to recover the password was too old. Please create a new one.')); + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.login'))->withStatus(302); + } + + $now = new \DateTime('NOW'); + $recoverdate = new \DateTime($recoverdate); + + if(!$recoverdate) + { + $user->unsetValue('recovertoken'); + $user->unsetValue('recoverdate'); + $user->updateUser(); + + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('create reset password wrong date format'); + } + + $this->c->get('flash')->addMessage('error', Translations::translate('The link to recover the password was too old. Please create a new one.')); + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.login'))->withStatus(302); + } + +# here we should make the interval editable + $validDate = $recoverdate->add(new \DateInterval('P1D')); + + if($validDate <= $now) + { + $user->unsetValue('recovertoken'); + $user->unsetValue('recoverdate'); + $user->updateUser(); + + if($securitylog) + { + \Typemill\Static\Helpers::addLogEntry('create reset password outdated'); + } + + $this->c->get('flash')->addMessage('error', Translations::translate('The link to recover the password was too old. Please create a new one.')); + + return $response->withHeader('Location', $this->routeParser->urlFor('auth.login'))->withStatus(302); + } + + $user->unsetValue('recovertoken'); + $user->unsetValue('recoverdate'); + $password = $user->generatePassword($params['password']); + $user->setValue('password', $password); + $user->updateUser(); + + unset($_SESSION['old']); + + $this->c->get('flash')->addMessage('info', Translations::translate('Please login with your new password.')); + return $response->withHeader('Location', $this->routeParser->urlFor('auth.login'))->withStatus(302); + } +} \ No newline at end of file diff --git a/system/typemill/Controllers/ControllerWebSetup.php b/system/typemill/Controllers/ControllerWebSetup.php new file mode 100644 index 0000000..abba868 --- /dev/null +++ b/system/typemill/Controllers/ControllerWebSetup.php @@ -0,0 +1,116 @@ +checkFolder('settingsFolder')) + { + $systemerrors[] = $storage->getError(); + } + if( !$storage->checkFolder('settingsFolder', 'users')){ $systemerrors[] = $storage->getError(); } + if( !$storage->checkFolder('contentFolder')){ $systemerrors[] = $storage->getError(); } + if( !$storage->checkFolder('dataFolder')){ $systemerrors[] = $storage->getError(); } + if( !$storage->checkFolder('cacheFolder')){ $systemerrors[] = $storage->getError(); } + if( !$storage->checkFolder('tmpFolder')){ $systemerrors[] = $storage->getError(); } + if( !$storage->checkFolder('originalFolder')){ $systemerrors[] = $storage->getError(); } + if( !$storage->checkFolder('liveFolder')){ $systemerrors[] = $storage->getError(); } + if( !$storage->checkFolder('thumbsFolder')){ $systemerrors[] = $storage->getError(); } + if( !$storage->checkFolder('customFolder')){ $systemerrors[] = $storage->getError(); } + if( !$storage->checkFolder('fileFolder')){ $systemerrors[] = $storage->getError(); } + + # check php-version + if (version_compare(phpversion(), '8.0.0', '<')) + { + $systemerrors[] = 'The PHP-version of your server is ' . phpversion() . ' and Typemill needs at least 8.0.0'; + } + + # check if extensions are loaded + if(!extension_loaded('gd')){ $systemerrors[] = 'The php-extension GD for image manipulation is not enabled.'; } + if(!extension_loaded('mbstring')){ $systemerrors[] = 'The php-extension mbstring is not enabled.'; } + if(!extension_loaded('fileinfo')){ $systemerrors[] = 'The php-extension fileinfo is not enabled.'; } + if(!extension_loaded('session')){ $systemerrors[] = 'The php-extension session is not enabled.'; } + if(!extension_loaded('iconv')){ $systemerrors[] = 'The php-extension iconv is not enabled.'; } + + $systemerrors = empty($systemerrors) ? false : $systemerrors; + + return $this->c->get('view')->render($response, 'auth/setup.twig', [ + 'systemerrors' => $systemerrors + ]); + } + + public function create(Request $request, Response $response, $args) + { + $params = $request->getParsedBody(); + $params['userrole'] = 'administrator'; + $validate = new Validation(); + $user = new User(); + + # get userroles for validation + $userroles = $this->c->get('acl')->getRoles(); + + # validate user + if($validate->newSetupUser($params, $userroles) !== true) + { + $this->c->get('flash')->addMessage('error', Translations::translate('Please correct your input.')); + + return $response->withHeader('Location', $this->routeParser->urlFor('setup.show'))->withStatus(302); + } + + $userdata = [ + 'username' => $params['username'], + 'email' => $params['email'], + 'userrole' => $params['userrole'], + 'password' => $params['password'] + ]; + + $user = new User(); + + # create initial user + $username = $user->createUser($userdata); + + if($username) + { + # create initial settings file + $settingsModel = new Settings(); + $settingsModel->createSettings([ + 'author' => $params['username'], + 'mailfrom' => $params['email'], + 'mailfromname' => $params['username'] + ]); + + $user->setUser($username); + + $user->login(); + + $urlinfo = $this->c->get('urlinfo'); + $route = $urlinfo['baseurl'] . '/tm/system'; + + usleep(30000); + + $this->c->get('flash')->addMessage('error', Translations::translate('Account created. Please login with your username and password now.')); + + return $response->withHeader('Location', $route)->withStatus(302); + } + + $this->c->get('flash')->addMessage('error', Translations::translate('We could not create the user. Please check if the settings folde is writable.')); + + return $response->withHeader('Location', $this->routeParser->urlFor('setup.show'))->withStatus(302); + } +} \ No newline at end of file diff --git a/system/typemill/Controllers/ControllerWebSystem.php b/system/typemill/Controllers/ControllerWebSystem.php new file mode 100644 index 0000000..1e11056 --- /dev/null +++ b/system/typemill/Controllers/ControllerWebSystem.php @@ -0,0 +1,460 @@ +getMainNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo'), + $editor = $this->settings['editor'] + ); + + $systemNavigation = $navigation->getSystemNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo'), + $dispatcher = $this->c->get('dispatcher'), + $parser = $this->routeParser + ); + + $settingsModel = new Settings(); + $systemfields = $settingsModel->getSettingsDefinitions(); + $systemfields = $this->addDatasets($systemfields); + + # add full url for sitemap to settings + $this->settings['sitemap'] = $this->c->get('urlinfo')['baseurl'] . '/cache/sitemap.xml'; + + return $this->c->get('view')->render($response, 'system/system.twig', [ +# 'captcha' => $this->checkIfAddCaptcha(), +# 'basicauth' => $user->getBasicAuth(), + 'settings' => $this->settings, + 'darkmode' => $request->getAttribute('c_darkmode'), + 'mainnavi' => $mainNavigation, + 'jsdata' => [ + 'settings' => $this->settings, + 'system' => $systemfields, + 'systemnavi' => $systemNavigation, + 'labels' => $this->c->get('translations'), + 'urlinfo' => $this->c->get('urlinfo') + ] + ]); + } + + public function showThemes(Request $request, Response $response, $args) + { + $navigation = new Navigation(); + $mainNavigation = $navigation->getMainNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo'), + $editor = $this->settings['editor'] + ); + + $systemNavigation = $navigation->getSystemNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo'), + $dispatcher = $this->c->get('dispatcher'), + $parser = $this->routeParser + ); + + $extension = new Extension(); + $themeDefinitions = $extension->getThemeDetails($this->settings['theme']); + $themeSettings = $extension->getThemeSettings($this->settings['themes']); + + # add userroles and other datasets + foreach($themeDefinitions as $name => $definitions) + { + if(isset($definitions['forms']['fields'])) + { + $themeDefinitions[$name]['forms']['fields'] = $this->addDatasets($definitions['forms']['fields']); + } + + if(isset($definitions['settings'])) + { + foreach($definitions['settings'] as $settingName => $settingValue) + { + if(!isset($themeSettings[$name][$settingName])) + { + $themeSettings[$name][$settingName] = $settingValue; + } + } + } + + # get stored indvidual readymades + $builtinReadymades = $definitions['readymades'] ?? []; + $individualReadymades = $extension->getThemeReadymades($name); + $themeDefinitions[$name]['readymades'] = $builtinReadymades + $individualReadymades; + } + + $license = []; + if(is_array($this->settings['license'])) + { + $license = array_keys($this->settings['license']); + } + + return $this->c->get('view')->render($response, 'system/themes.twig', [ + 'settings' => $this->settings, + 'darkmode' => $request->getAttribute('c_darkmode'), + 'mainnavi' => $mainNavigation, + 'jsdata' => [ + 'systemnavi' => $systemNavigation, + 'settings' => $themeSettings, + 'definitions' => $themeDefinitions, + 'theme' => $this->settings['theme'], + 'license' => $license, + 'labels' => $this->c->get('translations'), + 'urlinfo' => $this->c->get('urlinfo') + ] + ]); + } + + public function showPlugins(Request $request, Response $response, $args) + { + $navigation = new Navigation(); + $mainNavigation = $navigation->getMainNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo'), + $editor = $this->settings['editor'] + ); + + $systemNavigation = $navigation->getSystemNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo'), + $dispatcher = $this->c->get('dispatcher'), + $parser = $this->routeParser + ); + + + $pluginSettings = $this->settings['plugins'] ?? false; + $pluginDefinitions = []; + + if($pluginSettings) + { + $extension = new Extension(); + $pluginDefinitions = $extension->getPluginDetails($pluginSettings); + + # add userroles and other datasets + foreach($pluginDefinitions as $name => $definitions) + { + if(isset($definitions['forms']['fields'])) + { + $pluginDefinitions[$name]['forms']['fields'] = $this->addDatasets($definitions['forms']['fields']); + } + + if(isset($definitions['settings'])) + { + foreach($definitions['settings'] as $settingName => $settingValue) + { + if(!isset($pluginSettings[$name][$settingName])) + { + $pluginSettings[$name][$settingName] = $settingValue; + } + } + } + } + } + + $license = []; + if(isset($this->settings['license']) && is_array($this->settings['license'])) + { + $license = array_keys($this->settings['license']); + } + + return $this->c->get('view')->render($response, 'system/plugins.twig', [ + 'settings' => $this->settings, + 'darkmode' => $request->getAttribute('c_darkmode'), + 'mainnavi' => $mainNavigation, + 'jsdata' => [ + 'systemnavi' => $systemNavigation, + 'settings' => $pluginSettings, + 'definitions' => $pluginDefinitions, + 'license' => $license, + 'labels' => $this->c->get('translations'), + 'urlinfo' => $this->c->get('urlinfo') + ] + ]); + } + + public function showLicense(Request $request, Response $response, $args) + { + $navigation = new Navigation(); + $mainNavigation = $navigation->getMainNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo'), + $editor = $this->settings['editor'] + ); + + $systemNavigation = $navigation->getSystemNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo'), + $dispatcher = $this->c->get('dispatcher'), + $parser = $this->routeParser + ); + + $message = false; + $license = new License(); + $licensefields = $license->getLicenseFields(); + $licensedata = $license->getLicenseFile(); + + # disable input fields if license data exist (readonly) + if($licensedata) + { + foreach($licensefields as $key => $licensefield) + { + $licensefields[$key]['disabled'] = true; + } + + # check license data + $licensecheck = $license->checkLicense($licensedata, $this->c->get('urlinfo'), $forceUpdateCheck = true); + if(!$licensecheck) + { + $message = $license->getMessage(); + } + + unset($licensedata['signature']); + } + + return $this->c->get('view')->render($response, 'system/license.twig', [ + 'settings' => $this->settings, + 'darkmode' => $request->getAttribute('c_darkmode'), + 'mainnavi' => $mainNavigation, + 'jsdata' => [ + 'systemnavi' => $systemNavigation, + 'licensedata' => $licensedata, + 'licensefields' => $licensefields, + 'message' => $message, + 'labels' => $this->c->get('translations'), + 'urlinfo' => $this->c->get('urlinfo') ] + ]); + } + + public function showAccount(Request $request, Response $response, $args) + { + $navigation = new Navigation(); + $mainNavigation = $navigation->getMainNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo'), + $editor = $this->settings['editor'] + ); + + $systemNavigation = $navigation->getSystemNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo'), + $dispatcher = $this->c->get('dispatcher'), + $parser = $this->routeParser + ); + + $username = $request->getAttribute('c_username'); + $user = new User(); + $user->setUser($username); + + $userdata = $user->getUserData(); + $userfields = $user->getUserFields($this->c->get('acl'), $this->c->get('dispatcher'),$userdata['userrole'], $inspector = NULL, $loginlink = NULL); + + return $this->c->get('view')->render($response, 'system/account.twig', [ + 'settings' => $this->settings, + 'darkmode' => $request->getAttribute('c_darkmode'), + 'mainnavi' => $mainNavigation, + 'jsdata' => [ + 'systemnavi' => $systemNavigation, + 'userdata' => $userdata, + 'userfields' => $userfields, + 'userroles' => $this->c->get('acl')->getRoles(), + 'labels' => $this->c->get('translations'), + 'urlinfo' => $this->c->get('urlinfo') + ] + ]); + } + + public function showUsers(Request $request, Response $response, $args) + { + $navigation = new Navigation(); + $mainNavigation = $navigation->getMainNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo'), + $editor = $this->settings['editor'] + ); + + $systemNavigation = $navigation->getSystemNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo'), + $dispatcher = $this->c->get('dispatcher'), + $parser = $this->routeParser + ); + + $user = new User(); + $usernames = $user->getAllUsers(); + $userdata = []; + $count = 0; + foreach($usernames as $username) + { + if($count == 10) break; + $user->setUser($username); + $userdata[] = $user->getUserData(); + $count++; + } + + return $this->c->get('view')->render($response, 'system/users.twig', [ + 'settings' => $this->settings, + 'darkmode' => $request->getAttribute('c_darkmode'), + 'mainnavi' => $mainNavigation, + 'jsdata' => [ + 'systemnavi' => $systemNavigation, + 'totalusers' => count($usernames), + 'usernames' => $usernames, + 'userdata' => $userdata, + 'userroles' => $this->c->get('acl')->getRoles(), + 'labels' => $this->c->get('translations'), + 'urlinfo' => $this->c->get('urlinfo') + ] + ]); + } + + public function showUser(Request $request, Response $response, $args) + { + $navigation = new Navigation(); + $mainNavigation = $navigation->getMainNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo'), + $editor = $this->settings['editor'] + ); + + $systemNavigation = $navigation->getSystemNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo'), + $dispatcher = $this->c->get('dispatcher'), + $parser = $this->routeParser + ); + + $user = new User(); + $username = $args['username'] ?? false; + if(!$user->setUser($username)) + { + die("return a not found page"); + } + + $userdata = $user->getUserData(); + $inspector = $request->getAttribute('c_userrole'); + $loginlink = false; + if($userdata['userrole'] == 'guest' && isset($this->settings['loginlink']) && $this->settings['loginlink']) + { + $loginlink = true; + } + $userfields = $user->getUserFields($this->c->get('acl'), $this->c->get('dispatcher'), $userdata['userrole'], $inspector, $loginlink); + + return $this->c->get('view')->render($response, 'system/user.twig', [ + 'settings' => $this->settings, + 'darkmode' => $request->getAttribute('c_darkmode'), + 'mainnavi' => $mainNavigation, + 'jsdata' => [ + 'systemnavi' => $systemNavigation, + 'userdata' => $userdata, + 'userfields' => $userfields, + 'userroles' => $this->c->get('acl')->getRoles(), + 'labels' => $this->c->get('translations'), + 'urlinfo' => $this->c->get('urlinfo') + ] + ]); + } + + public function newUser(Request $request, Response $response, $args) + { + $navigation = new Navigation(); + $mainNavigation = $navigation->getMainNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo'), + $editor = $this->settings['editor'] + ); + + $systemNavigation = $navigation->getSystemNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo'), + $dispatcher = $this->c->get('dispatcher'), + $parser = $this->routeParser + ); + + return $this->c->get('view')->render($response, 'system/usernew.twig', [ + 'settings' => $this->settings, + 'darkmode' => $request->getAttribute('c_darkmode'), + 'mainnavi' => $mainNavigation, + 'jsdata' => [ + 'systemnavi' => $systemNavigation, + 'userroles' => $this->c->get('acl')->getRoles(), + 'labels' => $this->c->get('translations'), + 'urlinfo' => $this->c->get('urlinfo') + ] + ]); + } + + public function blankSystemPage(Request $request, Response $response, $args) + { + $navigation = new Navigation(); + $mainNavigation = $navigation->getMainNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo'), + $editor = $this->settings['editor'] + ); + $userrole = $request->getAttribute('c_userrole'); + $acl = $this->c->get('acl'); + $urlinfo = $this->c->get('urlinfo'); + $editor = $this->settings['editor']; + + $systemNavigation = $navigation->getSystemNavigation( + $userrole = $request->getAttribute('c_userrole'), + $acl = $this->c->get('acl'), + $urlinfo = $this->c->get('urlinfo'), + $dispatcher = $this->c->get('dispatcher'), + $parser = $this->routeParser + ); + + $pluginDefinitions = false; + $pluginname = strtolower(trim(str_replace('tm/', '', $urlinfo['route']), '/')); + if($pluginname && $pluginname != '' && isset($this->settings['plugins'][$pluginname])) + { + $extension = new Extension(); + $pluginDefinitions = $extension->getPluginDefinition($pluginname); + } + + return $this->c->get('view')->render($response, 'layouts/layoutSystemBlank.twig', [ + 'settings' => $this->settings, + 'darkmode' => $request->getAttribute('c_darkmode'), + 'mainnavi' => $mainNavigation, + 'jsdata' => [ + 'systemnavi' => $systemNavigation, + 'settings' => $this->settings, + 'labels' => $this->c->get('translations'), + 'urlinfo' => $this->c->get('urlinfo'), + 'acl' => $this->c->get('acl'), + 'userroles' => $this->c->get('acl')->getRoles(), + 'plugin' => $pluginDefinitions + ] + ]); + } +} \ No newline at end of file diff --git a/system/typemill/Events/BaseEvent.php b/system/typemill/Events/BaseEvent.php new file mode 100644 index 0000000..9333e1f --- /dev/null +++ b/system/typemill/Events/BaseEvent.php @@ -0,0 +1,27 @@ +data = $data; + } + + public function getData() + { + return $this->data; + } + + public function setData($data) + { + $this->data = $data; + } +} \ No newline at end of file diff --git a/system/typemill/Events/OnBreadcrumbLoaded.php b/system/typemill/Events/OnBreadcrumbLoaded.php new file mode 100644 index 0000000..56c1fa9 --- /dev/null +++ b/system/typemill/Events/OnBreadcrumbLoaded.php @@ -0,0 +1,14 @@ +data = $data; + } + + public function getMarkdown() + { + return $this->data; + } + + public function getHTML($urlrel) + { + $parsedown = new ParsedownExtension(); + $contentArray = $parsedown->text($this->data); + $contentHTML = $parsedown->markup($contentArray, $urlrel); + + return $contentHTML; + } +} \ No newline at end of file diff --git a/system/typemill/Events/OnPageCreated.php b/system/typemill/Events/OnPageCreated.php new file mode 100644 index 0000000..8d042ab --- /dev/null +++ b/system/typemill/Events/OnPageCreated.php @@ -0,0 +1,14 @@ + '', 'params' => '']; + +/* + public function setData($data) + { + # validate and fix data structure here + $this->data = $data; + } +*/ + +} \ No newline at end of file diff --git a/system/typemill/Events/OnSystemnaviLoaded.php b/system/typemill/Events/OnSystemnaviLoaded.php new file mode 100644 index 0000000..3f8f278 --- /dev/null +++ b/system/typemill/Events/OnSystemnaviLoaded.php @@ -0,0 +1,14 @@ +rootpath = $rootpath; + $this->baseurl = $baseurl; + } + + public static function getSubscribedEvents() + { + return [ + 'onShortcodeFound' => 'onShortcodeFound', + ]; + } + + public function onShortcodeFound($shortcode) + { + $shortcodeArray = $shortcode->getData(); + + if(is_array($shortcodeArray) && $shortcodeArray['name'] == 'video' && isset($shortcodeArray['params']['path'])) + { + $relUrl = $shortcodeArray['params']['path']; + $relUrl = '/' . trim($relUrl, '/'); + + # Convert the relative URL to an absolute file path + $filePath = $this->rootpath . $relUrl; + + # check file exists + if(!file_exists($filePath)) + { + $html = '

File not found

'; + } + else + { + # Get file extension using pathinfo() + $fileInfo = pathinfo($filePath); + $extension = strtolower($fileInfo['extension']); // Get file extension and convert to lowercase + $absUrl = $this->baseurl . $relUrl; + + # Determine the correct file type for the video tag + $type = ''; + switch ($extension) { + case 'mp4': + $type = 'mp4'; + break; + case 'webm': + $type = 'webm'; + break; + case 'ogg': + $type = 'ogg'; + break; + default: + $html = '

Unsupported file type

'; + return; // Exit if file type is not supported + } + + $width = $shortcodeArray['params']['width'] ?? '500'; + if (!preg_match('/^(\d+)(px|%)?$/', $width)) + { + $width = '500'; + } + + $preload = 'none'; + if(isset($shortcodeArray['params']['preload']) && ($shortcodeArray['params']['preload'] == 'auto' or $shortcodeArray['params']['preload'] == 'metadata')) + { + $preload = $shortcodeArray['params']['preload']; + } + + $poster = ''; + + if(isset($shortcodeArray['params']['poster'])) + { + $relImgUrl = $shortcodeArray['params']['poster']; + $relImgUrl = '/' . trim($relImgUrl, '/'); + + # Convert the relative URL to an absolute file path + $imgPath = $this->rootpath . $relImgUrl; + + # check file exists + if(file_exists($imgPath)) + { + $absImgUrl = $this->baseurl . $relImgUrl; + $poster = ' poster="' . $absImgUrl . '"'; + } + } + + $html = ''; + } + + $shortcode->setData($html); + } + + if(is_array($shortcodeArray) && $shortcodeArray['name'] == 'audio' && isset($shortcodeArray['params']['path'])) + { + $relUrl = $shortcodeArray['params']['path']; + $relUrl = '/' . trim($relUrl, '/'); + + # Convert the relative URL to an absolute file path + $filePath = $this->rootpath . $relUrl; + + # check file exists + if(!file_exists($filePath)) + { + $html = '

File not found

'; + } + else + { + # Get file extension using pathinfo() + $fileInfo = pathinfo($filePath); + $extension = strtolower($fileInfo['extension']); // Get file extension and convert to lowercase + $absUrl = $this->baseurl . $relUrl; + + # Determine the correct file type for the video tag + $type = ''; + switch ($extension) { + case 'mp3': + $type = 'mp3'; + break; + case 'ogg': + $type = 'ogg'; + break; + default: + $html = '

Unsupported file type

'; + return; // Exit if file type is not supported + } + + $preload = 'none'; + if(isset($shortcodeArray['params']['preload']) && ($shortcodeArray['params']['preload'] == 'auto' or $shortcodeArray['params']['preload'] == 'metadata')) + { + $preload = $shortcodeArray['params']['preload']; + } + + $width = $shortcodeArray['params']['width'] ?? '500'; + if (!preg_match('/^(\d+)(px|%)?$/', $width)) + { + $width = '500'; + } + + $html = ''; + } + + $shortcode->setData($html); + } + } + +} \ No newline at end of file diff --git a/system/typemill/Extensions/ParsedownExtension.php b/system/typemill/Extensions/ParsedownExtension.php new file mode 100644 index 0000000..083d8a6 --- /dev/null +++ b/system/typemill/Extensions/ParsedownExtension.php @@ -0,0 +1,1358 @@ +settings = $settings; + + $this->dispatcher = $dispatcher; + + # show anchor next to headline? + $this->showAnchor = isset($settings['headlineanchors']) ? $settings['headlineanchors'] : false; + + $this->headlines = []; + + # extend link schemes + $urlschemes = ( isset($settings['urlschemes']) && !empty($settings['urlschemes']) ) ? explode(",", $settings['urlschemes']) : false; + if($urlschemes) + { + foreach($urlschemes as $urlschema) + { + $this->safeLinksWhitelist[] = $urlschema; + } + } + + # base url is needed for media/images and relative links (e.g. if www.mydomain.com/mywebsite) + $this->baseUrl = $baseUrl; + + # math support + $this->BlockTypes['\\'][] = 'Math'; + $this->BlockTypes['$'][] = 'Math'; + + $this->InlineTypes['\\'][] = 'Math'; + $this->InlineTypes['$'][] = 'Math'; + $this->InlineTypes['['][] = 'Shortcode'; + $this->inlineMarkerList .= '\\'; + $this->inlineMarkerList .= '$'; + + $this->BlockTypes['!'][] = 'Image'; + $this->BlockTypes['!'][] = "Notice"; + + $this->visualMode = false; + + # identify Shortcodes after footnotes and links + array_unshift($this->BlockTypes['['], 'Shortcode'); + + # identify Table Of contents after footnotes and links and shortcodes + array_unshift($this->BlockTypes['['], 'TableOfContents'); + } + + public function extendLinksWhitelist($linktypes) + { + /* + if($linktypes) + { + $this->safeLinksWhitelist[] = ; + } + */ + } + + public function setVisualMode() + { + $this->visualMode = true; + } + + public function text($text, $relurl = null) + { + $Elements = $this->textElements($text); + + return $Elements; + } + + public function markup($Elements) + { + # convert to markup + $markup = $this->elements($Elements); + + # trim line breaks + $markup = trim($markup, "\n"); + + # merge consecutive dl elements + $markup = preg_replace('/<\/dl>\s+
\s+/', '', $markup); + + # create table of contents + if(isset($this->DefinitionData['TableOfContents'])) + { + $TOC = $this->buildTOC($this->headlines); + + $markup = preg_replace('%(]*>\[TOC\]

)%i', $TOC, $markup); + } + + # add footnotes + if (isset($this->DefinitionData['Footnote'])) + { + $Element = $this->buildFootnoteElement(); + + $markup .= "\n" . $this->element($Element); + } + return $markup; + } + + protected $imageAttributes = true; + + public function withoutImageAttributes() + { + $this->imageAttributes = false; + } + + # BlockImages with html5 figure and figcaption + # No, this is not the most elegant code on planet earth!! + protected function blockImage($line, $block) + { + if (preg_match('/^\!\[/', $line['text'], $matches)) + { + + $Block = array( + 'element' => array( + 'name' => 'figure', + 'elements' => array( + ) + ), + ); + + $Elements = array( + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $line['text'], + 'destination' => 'elements', + ) + ); + + if (preg_match('/[ ]*{('.$this->regexAttribute.'+)}/', $line['text'], $matches, PREG_OFFSET_CAPTURE)) + { + $attributeString = $matches[1][0]; + $dataAttributes = $this->parseAttributeData($attributeString); + + # move classes and ids from img to the figure element + $figureAttributes = array(); + if(isset($dataAttributes['class'])) + { + $figureAttributes['class'] = $dataAttributes['class']; + $classes = explode(' ', $dataAttributes['class']); + foreach($classes as $class) + { + $attributeString = str_replace('.'.$class, '', $attributeString); + } + } + if(isset($dataAttributes['id'])) + { + $figureAttributes['id'] = $dataAttributes['id']; + $attributeString = str_replace('#'.$dataAttributes['id'], '', $attributeString); + } + + $attributeString = trim(str_replace(' ', ' ', $attributeString)); + $line['text'] = substr($line['text'], 0, $matches[0][1]); + if(str_replace(' ', '', $attributeString) != '' && $this->imageAttributes) + { + $line['text'] .= '{' . $attributeString . '}'; + } + + $Block['element']['attributes'] = $figureAttributes; + + $Elements['handler']['argument'] = $line['text']; + } + + $Block['element']['elements'][] = $Elements; + + return $Block; + } + } + + protected function blockImageContinue($line, $block) + { + if (isset($block['complete'])) + { + return; + } + + # A blank newline has occurred, so it is a new content-block and not a caption + if (isset($block['interrupted'])) + { + return; + } + + $block['element']['elements'][] = array( + 'name' => 'figcaption', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $line['text'], + 'destination' => 'elements', + ) + ); + + $block['complete'] = true; + + return $block; + } + + protected function blockImageComplete($block) + { + return $block; + } + + protected function inlineImage($excerpt) + { + $image = parent::inlineImage($excerpt); + + if ( ! isset($image)) + { + return null; + } + + $image['element']['attributes']['loading'] = "lazy"; + + return $image; + } + + protected function blockTable($Line, array $Block = null) + { + + $Block = parent::blockTable($Line, $Block); + + if($Block) + { + $table = $Block['element']; + + $Block['element'] = [ + 'name' => 'div', + 'element' => $table, + 'attributes' => [ + 'class' => "tm-table", + ], + ]; + } + + return $Block; + } + + protected function blockTableContinue($Line, array $Block) + { + if (isset($Block['interrupted'])) + { + return; + } + + if (count($Block['alignments']) === 1 or $Line['text'][0] === '|' or strpos($Line['text'], '|')) + { + $Elements = array(); + + $row = $Line['text']; + + $row = trim($row); + $row = trim($row, '|'); + + preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]++`|`)++/', $row, $matches); + + $cells = array_slice($matches[0], 0, count($Block['alignments'])); + + foreach ($cells as $index => $cell) + { + $cell = trim($cell); + + $Element = array( + 'name' => 'td', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $cell, + 'destination' => 'elements', + ) + ); + + if (isset($Block['alignments'][$index])) + { + $Element['attributes'] = array( + 'style' => 'text-align: ' . $Block['alignments'][$index] . ';', + ); + } + + $Elements []= $Element; + } + + $Element = array( + 'name' => 'tr', + 'elements' => $Elements, + ); + + $Block['element']['element']['elements'][1]['elements'] []= $Element; + + return $Block; + } + } + + # Handle notice blocks + # adopted from grav: https://github.com/getgrav/grav-plugin-markdown-notices/blob/develop/markdown-notices.php + # and yellow / datenstrom: https://raw.githubusercontent.com/datenstrom/yellow-extensions/master/features/markdown/markdownx.php + protected function blockNotice($Line, $Block) + { + if (preg_match("/^!(?!\[)[ ]?+(.*+)/", $Line["text"], $matches)) + { + $level = strspn(str_replace(array("![", " "), "", $Line["text"]), "!"); + $text = substr($matches[0], $level); + + $Block = [ + 'element' => [ + 'name' => 'div', + 'handler' => array( + 'function' => 'linesElements', + 'argument' => (array) $text, + 'destination' => 'elements', + ), + 'attributes' => [ + 'class' => "notice$level", + ], + ], + ]; + + return $Block; + } + } + + # Handle notice blocks over multiple lines + # adopted from grav: https://github.com/getgrav/grav-plugin-markdown-notices/blob/develop/markdown-notices.php + # and yellow / datenstrom: https://raw.githubusercontent.com/datenstrom/yellow-extensions/master/features/markdown/markdownx.php + protected function blockNoticeContinue($Line, $Block) + { + if (isset($Block['interrupted'])) + { + return; + } + + if (preg_match("/^!(?!\[)[ ]?+(.*+)/", $Line["text"], $matches) ) + { + $level = strspn(str_replace(array("![", " "), "", $Line["text"]), "!"); + $text = substr($matches[0], $level); + + $Block['element']['handler']['argument'][] = $text; + return $Block; + } + } + + + # Headlines + public $headlines = []; + + protected function blockHeader($Line) + { + if (isset($Line['text'][1])) + { + $level = strspn($Line['text'], '#'); + + if ($level > 6) + { + return; + } + + $text = trim($Line['text'], '#'); + $lang = $this->settings['langattr'] ?? false; + $headline = Slug::createSlug($Line['text'], $lang); + + if ($this->strictMode and isset($text[0]) and $text[0] !== ' ') + { + return; + } + + $text = trim($text, ' '); + + $tocText = $text; + + if($this->showAnchor && $level > 1) + { + # should we add more info like level so duplicate headline text is possible? + $text = "[#](#h-$headline){.tm-heading-anchor}" . $text; + } + + $Block = array( + 'element' => array( + 'name' => 'h' . min(6, $level), + 'attributes' => array( + 'id' => "h-$headline" + ), + 'handler' => array( + 'function' => 'lineElements', + 'argument' => $text, + 'destination' => 'elements', + ), + ) + ); + + # fix: make sure no duplicates in headlines if user logged in and restrictions on + if(!isset($this->headlines[$headline])) + { + $this->headlines[$headline] = array('level' => $level, 'name' => $Block['element']['name'], 'attribute' => $Block['element']['attributes']['id'], 'text' => $tocText); + } + + return $Block; + } + } + + + # TableOfContents + protected function blockTableOfContents($line, $block) + { + if ($line['text'] == '[TOC]') + { + $this->DefinitionData['TableOfContents'] = true; + } + } + + + # build the markup for table of contents, thanks gtp for fixing! + public function buildTOC($headlines) + { + $markup = '
    '; + + # we have to reindex the array + $headlines = array_values($headlines); + + # Initialize previous level to the highest possible level + $prevLevel = 1; + + # Stack to keep track of open
      tags + $stack = array(); + + foreach($headlines as $key => $headline) + { + $thisLevel = $headline['level']; + + # Close any open
    • tags if the current level is lower than the previous level + while ($thisLevel < $prevLevel) + { + $markup .= '
    • '; + $markup .= array_pop($stack); // Close the corresponding
        tag + $prevLevel--; + } + + # Open a new
          tag if the current level is higher than the previous level + if ($thisLevel > $prevLevel) + { + $markup .= '
            '; + $stack[] = '
          '; // Add the closing
        tag to the stack + } + + $markup .= '
      • ' . $headline['text'] . ''; + + $prevLevel = $thisLevel; + } + + # Close any remaining open
      • tags and
          tags + while (!empty($stack)) { + $markup .= ''; + $markup .= array_pop($stack); + } + + # Close the top-level
            tag + $markup .= '
          '; + + return $markup; + } + + + # + # Footnotes + protected $spanFootnotes = false; + public $footnoteCount = 0; + + # set spanFootnotes (W3C style) to true + public function withSpanFootnotes() + { + $this->spanFootnotes = true; + } + + # used for ??? + public function getFootnotes() + { + # add footnotes + if (isset($this->DefinitionData['Footnote'])) + { + $Element = $this->buildFootnoteElement(); + + $footnotes = "\n" . $this->element($Element); + } + + return $footnotes; + } + + protected function inlineFootnoteMarker($Excerpt) + { + if (preg_match('/^\[\^(.+?)\]/', $Excerpt['text'], $matches)) + { + $name = $matches[1]; + + if ( ! isset($this->DefinitionData['Footnote'][$name])) + { + return; + } + + $this->DefinitionData['Footnote'][$name]['count'] ++; + + if ( ! isset($this->DefinitionData['Footnote'][$name]['number'])) + { + $this->DefinitionData['Footnote'][$name]['number'] = ++ $this->footnoteCount; # » & + } + + # Optionally use w3c inline footnote markup for books + if($this->spanFootnotes) + { + $Element = array( + 'name' => 'span', + 'attributes' => array('class' => 'footnote'), + 'text' => $this->DefinitionData['Footnote'][$name]['text'], + ); + } + else + { + $Element = array( + 'name' => 'sup', + 'attributes' => array('id' => 'fnref'.$this->DefinitionData['Footnote'][$name]['count'].':'.$name), + 'element' => array( + 'name' => 'a', + 'attributes' => array('href' => '#fn:'.$name, 'class' => 'footnote-ref'), + 'text' => $this->DefinitionData['Footnote'][$name]['number'], + ), + ); + } + + return array( + 'extent' => strlen($matches[0]), + 'element' => $Element, + ); + } + } + + # has a fix for visual editor mode and option for spanFootnotes + public function buildFootnoteElement() + { + # we do not need a footnote element if we use w3c inline style with spans for footnotes + if($this->spanFootnotes) + { + return []; + } + + $Element = array( + 'name' => 'div', + 'attributes' => array('class' => 'footnotes'), + 'elements' => array( + array('name' => 'hr'), + array( + 'name' => 'ol', + 'elements' => array(), + ), + ), + ); + + uasort($this->DefinitionData['Footnote'], [$this, 'sortFootnotes']); + + foreach ($this->DefinitionData['Footnote'] as $definitionId => $DefinitionData) + { + if ( ! isset($DefinitionData['number'])) + { + # fix the footnote logic in visual mode, this might break for more complex footnotes. + if($this->visualMode) + { + $DefinitionData['number'] = $definitionId; + $DefinitionData['count'] = 1; + } + else + { + continue; + } + } + + $text = $DefinitionData['text']; + + $textElements = parent::textElements($text); + + $numbers = range(1, $DefinitionData['count']); + + $backLinkElements = array(); + + foreach ($numbers as $number) + { + $backLinkElements[] = array('text' => ' '); + $backLinkElements[] = array( + 'name' => 'a', + 'attributes' => array( + 'href' => "#fnref$number:$definitionId", + 'rev' => 'footnote', + 'class' => 'footnote-backref', + ), + 'rawHtml' => '↩', + 'allowRawHtmlInSafeMode' => true, + 'autobreak' => false, + ); + } + + unset($backLinkElements[0]); + + $n = count($textElements) -1; + + if ($textElements[$n]['name'] === 'p') + { + $backLinkElements = array_merge( + array( + array( + 'rawHtml' => ' ', + 'allowRawHtmlInSafeMode' => true, + ), + ), + $backLinkElements + ); + + unset($textElements[$n]['name']); + + $textElements[$n] = array( + 'name' => 'p', + 'elements' => array_merge( + array($textElements[$n]), + $backLinkElements + ), + ); + } + else + { + $textElements[] = array( + 'name' => 'p', + 'elements' => $backLinkElements + ); + } + + $Element['elements'][1]['elements'] []= array( + 'name' => 'li', + 'attributes' => array('id' => 'fn:'.$definitionId), + 'elements' => array_merge( + $textElements + ), + ); + } + + return $Element; + } + + # Inline Math + # check https://github.com/BenjaminHoegh/ParsedownMath + # check https://github.com/cben/mathdown/wiki/math-in-markdown + + protected function inlineMath($Excerpt) + { + if(preg_match('/^(? strlen($matches[0]), + 'element' => array( + 'text' => '\(' . $matches[1] . '\)', + ), + ); + } + } + + protected $specialCharacters = array( + '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '<', '>', '#', '+', '-', '.', '!', '|', '~', '^', '=' + ); + + // + // Inline Escape + // ------------------------------------------------------------------------- + protected function inlineEscapeSequence($Excerpt) + { + if (isset($Excerpt['text'][1]) + && in_array($Excerpt['text'][1], $this->specialCharacters) + && !preg_match('/(? array( + 'rawHtml' => $Excerpt['text'][1], + ), + 'extent' => 2, + ); + } + } + + # Block Math + protected function blockMath($Line) + { + $Block = array( + 'element' => array( + 'text' => '', + ), + ); + if (preg_match('/^(? 'math'); + + return $Block; + } + elseif ($Block['end'] === '$$' && preg_match('/^(? 'math'); + + return $Block; + } + + $Block['element']['text'] .= "\n" . $Line['body']; + + // ~ + return $Block; + } + + // ~ + protected function blockMathComplete($Block) + { + return $Block; + } + + protected function blockShortcode($Line) + { + if ($this->dispatcher && preg_match('/^\[:.*?:\]/', $Line['text'], $matches)) + { + if(is_array($this->allowedShortcodes) && empty($this->allowedShortcodes)) + { + return array( + 'element' => array(), // No element to render + 'extent' => strlen($matches[0]), // Consume the entire shortcode + ); + } + + $shortcodeString = substr($matches[0], 2, -2); + $shortcodeArray = explode(' ', $shortcodeString, 2); + $shortcode = []; + + $shortcode['name'] = $shortcodeArray[0]; + $shortcode['params'] = false; + + if(is_array($this->allowedShortcodes) && !in_array($shortcode['name'], $this->allowedShortcodes)) + { + return array( + 'element' => array(), // No element to render + 'extent' => strlen($matches[0]), // Consume the entire shortcode + ); + } + + $params = $this->shortcodeParams($shortcodeArray); + if($params) + { + $shortcode['params'] = $params; + } + + $html = $this->dispatcher->dispatch(new OnShortcodeFound($shortcode), 'onShortcodeFound')->getData(); + + # if no shortcode has been processed, add the original string + if(is_array($html) OR is_object($html)) + { + $html = '

          No shortcode found.

          '; + } + + // Handle the inline content after the block shortcode. + $inlineContent = trim(substr($Line['text'], strlen($matches[0]))); + $inlineElements = ''; + if($inlineContent && $inlineContent != '') + { + # make sure inline-text with more shortcodes are properly parsed after an opening block-element + $inlineElements = $this->line($inlineContent); + } + + $Block = [ + 'element' => [ + 'rawHtml' => $html . $inlineElements, + 'allowRawHtmlInSafeMode' => true, + ], + 'extent' => strlen($matches[0]), + 'complete' => true, // Close the block at the end of the line. + ]; + + return $Block; + } + + return; + } + + protected function inlineShortcode($Excerpt) + { + $remainder = $Excerpt['text']; + + if ($this->dispatcher && preg_match('/\[:.*?:\]/', $remainder, $matches)) + { + if(is_array($this->allowedShortcodes) && empty($this->allowedShortcodes)) + { + return array( + 'element' => array(), // No element to render + 'extent' => strlen($matches[0]), // Consume the entire shortcode + ); + } + + $shortcodeString = substr($matches[0], 2, -2); + $shortcodeArray = explode(' ', $shortcodeString, 2); + $shortcode = []; + + $shortcode['name'] = $shortcodeArray[0]; + $shortcode['params'] = false; + + if(is_array($this->allowedShortcodes) && !in_array($shortcode['name'], $this->allowedShortcodes)) + { + return array( + 'element' => array(), // No element to render + 'extent' => strlen($matches[0]), // Consume the entire shortcode + ); + } + + $params = $this->shortcodeParams($shortcodeArray); + if($params) + { + $shortcode['params'] = $params; + } + + $html = $this->dispatcher->dispatch(new OnShortcodeFound($shortcode), 'onShortcodeFound')->getData(); + + # if no shortcode has been processed, add the original string + if(is_array($html) OR is_object($html)) + { + $html = 'No shortcode found.'; + } + + return array( + 'element' => array( + 'rawHtml' => $html, + 'allowRawHtmlInSafeMode' => true, + ), + 'extent' => strlen($matches[0]), + ); + } + else + { + return; + } + } + + + protected $allowedShortcodes = false; + + public function setAllowedShortcodes(array $shortcodelist) + { + $this->allowedShortcodes = $shortcodelist; + } + + protected function shortcodeParams($shortcodeArray) + { + if(isset($shortcodeArray[1])) + { + $params = []; + + # see: https://www.thetopsites.net/article/58136180.shtml + $pattern = '/(\\w+)\s*=\\s*("[^"]*"|\'[^\']*\'|[^"\'\\s>]*)/'; + preg_match_all($pattern, $shortcodeArray[1], $attributes, PREG_SET_ORDER); + + foreach($attributes as $attribute) + { + if(isset($attribute[1]) && isset($attribute[2])) + { + $params[$attribute[1]] = trim($attribute[2], " \""); + } + } + + return $params; + } + + return false; + } + + protected function inlineLink($Excerpt) + { + $Element = array( + 'name' => 'a', + 'handler' => array( + 'function' => 'lineElements', + 'argument' => null, + 'destination' => 'elements', + ), + 'nonNestables' => array('Url', 'Link'), + 'attributes' => array( + 'href' => null, + 'title' => null, + ), + ); + + $extent = 0; + + $remainder = $Excerpt['text']; + + if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches)) + { + $Element['handler']['argument'] = $matches[1]; + + $extent += strlen($matches[0]); + + $remainder = substr($remainder, $extent); + } + else + { + return; + } + + if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*+"|\'[^\']*+\'))?\s*+[)]/', $remainder, $matches)) + { + # start typemill: if relative link or media-link + $href = $matches[1]; + if($href[0] == '/') + { + $href = $this->baseUrl . $href; + } + elseif(substr( $href, 0, 6 ) === "media/") + { + $href = $this->baseUrl . '/' . $href; + } + # end typemill + + $Element['attributes']['href'] = $href; + + if (isset($matches[2])) + { + $Element['attributes']['title'] = substr($matches[2], 1, - 1); + } + + $extent += strlen($matches[0]); + } + else + { + if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches)) + { + $definition = strlen($matches[1]) ? $matches[1] : $Element['handler']['argument']; + $definition = strtolower($definition); + + $extent += strlen($matches[0]); + } + else + { + $definition = strtolower($Element['handler']['argument']); + } + + if ( ! isset($this->DefinitionData['Reference'][$definition])) + { + return; + } + + $Definition = $this->DefinitionData['Reference'][$definition]; + + $Element['attributes']['href'] = $Definition['url']; + $Element['attributes']['title'] = $Definition['title']; + } + + $Link = array( + 'extent' => $extent, + 'element' => $Element, + ); + + # Parsedown Extra + $remainder = $Link !== null ? substr($Excerpt['text'], $Link['extent']) : ''; + + if (preg_match('/^[ ]*{('.$this->regexAttribute.'+)}/', $remainder, $matches)) + { + $Link['element']['attributes'] += $this->parseAttributeData($matches[1]); + + $Link['extent'] += strlen($matches[0]); + } + + return $Link; + + } + + # advanced attribute data, check parsedown extra plugin: https://github.com/tovic/parsedown-extra-plugin + protected function parseAttributeData($text) { + + // Allow compact attributes ... + $text = str_replace(array('#', '.'), array(' #', ' .'), $text); + if (strpos($text, '="') !== false || strpos($text, '=\'') !== false) { + $text = preg_replace_callback('#([-\w]+=)(["\'])([^\n]*?)\2#', function($m) { + $s = str_replace(array( + ' #', + ' .', + ' ' + ), array( + '#', + '.', + "\x1A" + ), $m[3]); + return $m[1] . $m[2] . $s . $m[2]; + }, $text); + } + $attrs = array(); + foreach (explode(' ', $text) as $v) { + if (!$v) continue; + // `{#foo}` + if ($v[0] === '#' && isset($v[1])) { + $attrs['id'] = substr($v, 1); + // `{.foo}` + } else if ($v[0] === '.' && isset($v[1])) { + $attrs['class'][] = substr($v, 1); + // ~ + } else if (strpos($v, '=') !== false) { + $vv = explode('=', $v, 2); + // `{foo=}` + if ($vv[1] === "") { + $attrs[$vv[0]] = ""; + // `{foo="bar baz"}` + // `{foo='bar baz'}` + } else if ($vv[1][0] === '"' && substr($vv[1], -1) === '"' || $vv[1][0] === "'" && substr($vv[1], -1) === "'") { + $attrs[$vv[0]] = str_replace("\x1A", ' ', substr(substr($vv[1], 1), 0, -1)); + // `{foo=bar}` + } else { + $attrs[$vv[0]] = $vv[1]; + } + // `{foo}` + } else { + $attrs[$v] = $v; + } + } + if (isset($attrs['class'])) { + $attrs['class'] = implode(' ', $attrs['class']); + } + return $attrs; + } + + protected $regexAttribute = '(?:[#.][-\w:\\\]+[ ]*|[-\w:\\\]+(?:=(?:["\'][^\n]*?["\']|[^\s]+)?)?[ ]*)'; + + # ++ + # blocks that belong to a "magneticType" would "merge" if they are next to each other + protected $magneticTypes = array('DefinitionList', 'Footnote'); + + public function markdownToArrayBlocks($markdown) + { + # make sure no definitions are set + $this->DefinitionData = array(); + + # standardize line breaks + $text = str_replace(array("\r\n", "\r"), "\n", $markdown); + + # remove surrounding line breaks + $text = trim($text, "\n"); + + # split text into lines + $lines = explode("\n", $text); + + # manipulated method linesElements + + # $Elements = array(); + + # the block that's being built + # when a block is complete we add it to $Elements + $CurrentBlock = null; + + # ++ + $done = array(); + $current = null; + + foreach ($lines as $line) { + + # is it a blank line + if (chop($line) === '') { + # mark current block as interrupted + if (isset($CurrentBlock)) { + # set or increment interrupted + $CurrentBlock['interrupted'] = (isset($CurrentBlock['interrupted']) + ? $CurrentBlock['interrupted'] + 1 : 1 + ); + } + + # keep empty lines in pre-tags + if($CurrentBlock['type'] == 'FencedCode' && isset($current['text'])) + { + # Append a newline only if it's not the last blank line in the block + if (empty($CurrentBlock['complete'])) { + $current['text'] .= "\n"; + } + } + continue; + } + + # ~ + + # figure out line indent and text + + while (($beforeTab = strstr($line, "\t", true)) !== false) { + $shortage = 4 - mb_strlen($beforeTab, 'utf-8') % 4; + + $line = $beforeTab + . str_repeat(' ', $shortage) + . substr($line, strlen($beforeTab) + 1); + } + + $indent = strspn($line, ' '); + + $text = $indent > 0 ? substr($line, $indent) : $line; + + # ~ + + $Line = array('body' => $line, 'indent' => $indent, 'text' => $text); + + # ~ + + if (isset($CurrentBlock['continuable'])) { + # current block is continuable + # let's attempt to continue it + $methodName = 'block' . $CurrentBlock['type'] . 'Continue'; + $Block = $this->$methodName($Line, $CurrentBlock); + + if (isset($Block)) { + # attempt to continue was successful + # let's update it + $CurrentBlock = $Block; + + # ++ + $current['text'] .= "\n$line"; + + # move to next line + continue; + } else { + # attempt to continue failed + # this means current block is complete + # let's call its "complete" method if it has one + if ($this->isBlockCompletable($CurrentBlock['type'])) { + $methodName = 'block' . $CurrentBlock['type'] . 'Complete'; + $CurrentBlock = $this->$methodName($CurrentBlock); + } + } + } + + # ~ + + # current block failed to "eat" current line + # let's see if we can start a new block + $marker = $text[0]; + + # ~ + + # make a list of the block types that current line can start + $blockTypes = $this->unmarkedBlockTypes; + if (isset($this->BlockTypes[$marker])) { + foreach ($this->BlockTypes[$marker] as $blockType) { + $blockTypes [] = $blockType; + } + } + + # + # ~ + + foreach ($blockTypes as $blockType) { + # let's see if current line can start a block of type $blockType + $Block = $this->{"block$blockType"}($Line, $CurrentBlock); + + if (isset($Block)) { + # echo "[$blockType]"; + # current line managed to start a block of type $blockType + # let's set its type + $Block['type'] = $blockType; + + # on start block, we "ship" current block and flag started block as identified + # except when the started block has already flagged itself as identified + # this is the case of table + # blocks flag themselves as identified to "absorb" current block + # setext function doesn't set "identified" but it inherits it from the $Block param + if (!isset($Block['identified'])) { + # if (isset($CurrentBlock)) { + # $Elements[] = $this->extractElement($CurrentBlock); + # } + + # ++ + # $current would be null if this is the first block + if ($current !== null) { + $done[] = $current; + } + + # ++ + # line doesn't belong to $current + $current = ['text' => $line, 'type' => $blockType]; + + $Block['identified'] = true; + } else { + # ++ + $current['text'] .= "\n$line"; + $current['type'] = $blockType; + } + + # does block have a "continue" method + if ($this->isBlockContinuable($blockType)) { + $Block['continuable'] = true; + } + + $CurrentBlock = $Block; + + # we're done with this line + # move on to next line + continue 2; + } + } + + # ~ + + if (isset($CurrentBlock) and $CurrentBlock['type'] === 'Paragraph') { + # we continue paragraphs here because they are "lazy" + # they "eat" the line only if no other block type has "eaten" it + $Block = $this->paragraphContinue($Line, $CurrentBlock); + } + + if (isset($Block)) { + $CurrentBlock = $Block; + + # ++ + $current['text'] .= "\n$line"; + } else { + # is this "isset" might be here to handle $lines[0] (first line) + # version 1.7.x doesn't have it but it does unset($Blocks[0]) + if (isset($CurrentBlock)) { + # $Elements[] = $this->extractElement($CurrentBlock); + + # ++ + $done[] = $current; + } + + $CurrentBlock = $this->paragraph($Line); + + # ++ + $current = ['text' => $line, 'type' => 'Paragraph']; + + $CurrentBlock['identified'] = true; + } + } + + # ~ + + # at this point, we're out of the $lines loop + + # handles the case where the last block is continuable + # since there are no more lines, it won't get completed in the loop + # we need to complete it here + if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type'])) { + $methodName = 'block' . $CurrentBlock['type'] . 'Complete'; + $CurrentBlock = $this->$methodName($CurrentBlock); + } + + # ~ + + if (isset($CurrentBlock)) { + # $Elements[] = $this->extractElement($CurrentBlock); + + # ++ + $done[] = $current; + } + + # ~ + + # ++ + # merge blocks that have magnetic types + $done = array_reduce($done, function (array $accumulator, array $current) { + if ($accumulator) { + $last = array_pop($accumulator); + + if ($current['type'] === $last['type'] and in_array($current['type'], $this->magneticTypes)) { + $last['text'] .= "\n\n" . $current['text']; + $accumulator[] = $last; + } else { + $accumulator[] = $last; + $accumulator[] = $current; + } + } else { + # first iteration + $accumulator[] = $current; + } + + return $accumulator; + }, []); + + # ~ + + # return $Elements; + + # ++ + # return just the text of each item + return array_map(function (array $item) { + return $item['text']; + }, $done); + } + + public function arrayBlocksToMarkdown(array $arrayBlocks) + { + $markdown = ''; + + foreach($arrayBlocks as $block) + { + $markdown .= $block . "\n\n"; + } + + return $markdown; + } + + protected function isComplete($codeblock) + { + $lines = explode("\n", $codeblock); + if(count($lines) > 1) + { + $lastLine = array_pop($lines); + if(substr($lastLine,0,2) == '``') + { + return true; + } + return false; + } + return false; + } +} \ No newline at end of file diff --git a/system/typemill/Extensions/TwigCaptchaExtension.php b/system/typemill/Extensions/TwigCaptchaExtension.php new file mode 100644 index 0000000..696d269 --- /dev/null +++ b/system/typemill/Extensions/TwigCaptchaExtension.php @@ -0,0 +1,56 @@ +build(); + + if(isset($_SESSION['captcha']) && $_SESSION['captcha'] === 'error') + { + $template = '
          ' . + '' . + '' . + '
          The captcha was wrong.
          ' . + '' . + '
          '; + } + else + { + $template = '
          ' . + '' . + '' . + '' . + '
          '; + } + + $_SESSION['phrase'] = $builder->getPhrase(); + + $_SESSION['captcha'] = true; + + return $template; + } + } +} \ No newline at end of file diff --git a/system/typemill/Extensions/TwigCsrfExtension.php b/system/typemill/Extensions/TwigCsrfExtension.php new file mode 100644 index 0000000..ef80782 --- /dev/null +++ b/system/typemill/Extensions/TwigCsrfExtension.php @@ -0,0 +1,31 @@ +csrf = $csrf; + } + + public function getFunctions() + { + return [ + new \Twig\TwigFunction('csrf', [$this, 'csrf']) + ]; + } + + public function csrf() + { + $csrf = '

          TokenNameValue: '. $this->csrf->getTokenName() .'

          + '; + + return $csrf; + } +} \ No newline at end of file diff --git a/system/typemill/Extensions/TwigLanguageExtension.php b/system/typemill/Extensions/TwigLanguageExtension.php new file mode 100644 index 0000000..a569745 --- /dev/null +++ b/system/typemill/Extensions/TwigLanguageExtension.php @@ -0,0 +1,62 @@ +labels = $labels; + } +/* + public function getFilters() + { + return [ + new TwigFilter('translate', [$this, 'translate'] ), + ]; + } +*/ + public function getFunctions() + { + return [ + new TwigFunction('translate', array($this, 'translate' )) + ]; + } + + public function translate( $label, $labels_from_plugin = NULL ) + { + # replaces spaces, dots, comma and dash with underscores + $string = str_replace(" ", "_", $label); + $string = str_replace(".", "_", $string); + $string = str_replace(",", "_", $string); + $string = str_replace("-", "_", $string); + + # transforms to uppercase + $string = strtoupper( $string ); + + # translates the string + if(isset($labels_from_plugin)) + { + $translated_label = isset($labels_from_plugin[$string]) ? $labels_from_plugin[$string] : null; + } + else + { + $translated_label = isset($this->labels[$string]) ? $this->labels[$string] : null; + } + + # if the string is not present, set the original string + if( empty($translated_label) ) + { + $translated_label = $label; + } + + # returns the string in the set language + return $translated_label; + } +} diff --git a/system/typemill/Extensions/TwigMarkdownExtension.php b/system/typemill/Extensions/TwigMarkdownExtension.php new file mode 100644 index 0000000..0685974 --- /dev/null +++ b/system/typemill/Extensions/TwigMarkdownExtension.php @@ -0,0 +1,41 @@ +dispatcher = $dispatcher; + + $this->settings = $settings; + + $this->baseurl = $baseurl; + } + + public function getFunctions() + { + return [ + new TwigFunction('markdown', array($this, 'renderMarkdown' )) + ]; + } + + public function renderMarkdown($markdown) + { + $parsedown = new ParsedownExtension($this->baseurl, $this->settings, $this->dispatcher); + + $markdownArray = $parsedown->text($markdown); + + return $parsedown->markup($markdownArray); + } +} \ No newline at end of file diff --git a/system/typemill/Extensions/TwigMetaExtension.php b/system/typemill/Extensions/TwigMetaExtension.php new file mode 100644 index 0000000..b3716dd --- /dev/null +++ b/system/typemill/Extensions/TwigMetaExtension.php @@ -0,0 +1,38 @@ +getMetaData($item); + + if( + !$metadata + OR !isset($metadata['meta']['title']) + OR $metadata['meta']['title'] == '' + OR !isset($metadata['meta']['description']) + OR $metadata['meta']['description'] == '' + ) + { + $metadata = $meta->addMetaDefaults($metadata, $item, $settings['author']); + } + + return $metadata; + } +} \ No newline at end of file diff --git a/system/typemill/Extensions/TwigPagelistExtension.php b/system/typemill/Extensions/TwigPagelistExtension.php new file mode 100644 index 0000000..4eb5fea --- /dev/null +++ b/system/typemill/Extensions/TwigPagelistExtension.php @@ -0,0 +1,35 @@ + $item) + { + # set item active, needed to move item in navigation + if($item->urlRelWoF === $url) + { + $item->active = true; + $result = $item; + } + elseif($item->elementType === "folder") + { + $result = $this->getList($item->folderContent, $url, $result); + } + } + + return $result; + } +} \ No newline at end of file diff --git a/system/typemill/Extensions/TwigUrlExtension.php b/system/typemill/Extensions/TwigUrlExtension.php new file mode 100644 index 0000000..a8056f0 --- /dev/null +++ b/system/typemill/Extensions/TwigUrlExtension.php @@ -0,0 +1,39 @@ +urlinfo = $urlinfo; + } + + public function getFunctions() + { + return [ + new \Twig\TwigFunction('base_url', array($this, 'baseUrl' )), + new \Twig\TwigFunction('current_url', array($this, 'currentUrl' )), + new \Twig\TwigFunction('current_path', array($this, 'currentPath' )) + ]; + } + + public function baseUrl() + { + return $this->urlinfo['baseurl']; + } + + public function currentUrl() + { + return $this->urlinfo['currenturl']; + } + + public function currentPath() + { + return $this->urlinfo['route']; + } +} \ No newline at end of file diff --git a/system/typemill/Extensions/TwigUserExtension.php b/system/typemill/Extensions/TwigUserExtension.php new file mode 100644 index 0000000..dc38940 --- /dev/null +++ b/system/typemill/Extensions/TwigUserExtension.php @@ -0,0 +1,110 @@ +acl = $acl; + } + + public function getFunctions() + { + return [ + new \Twig\TwigFunction('get_username', array($this, 'getUsername' )), + new \Twig\TwigFunction('is_loggedin', array($this, 'isLoggedin' )), + new \Twig\TwigFunction('is_allowed', array($this, 'isAllowed' )), + new \Twig\TwigFunction('is_role', array($this, 'isRole' )), + new \Twig\TwigFunction('get_role', array($this, 'getRole' )), + ]; + } + + public function isLoggedin() + { + if(isset($_SESSION['login']) && $_SESSION['login']) + { + return true; + } + + return false; + } + + public function getUsername() + { + if(isset($_SESSION['username'])) + { + return $_SESSION['username']; + } + + return false; + } + + public function isRole($role) + { + if(isset($_SESSION['username'])) + { + $username = $_SESSION['username']; + + $usermodel = new User(); + $user = $usermodel->setUser($username); + + if($user) + { + $userrole = $usermodel->getValue('userrole'); + if($userrole === $role) + { + return true; + } + } + } + + return false; + } + + public function getRole() + { + if(isset($_SESSION['username'])) + { + $username = $_SESSION['username']; + + $usermodel = new User(); + $user = $usermodel->setUser($username); + + if($user) + { + $userrole = $usermodel->getValue('userrole'); + return $userrole; + } + } + + return false; + } + + public function isAllowed($resource, $action) + { + if(isset($_SESSION['username'])) + { + $username = $_SESSION['username']; + $usermodel = new User(); + $user = $usermodel->setUser($username); + + if($user) + { + $userrole = $usermodel->getValue('userrole'); + + if($this->acl->isAllowed($userrole, $resource, $action)) + { + return true; + } + } + } + + return false; + } +} \ No newline at end of file diff --git a/system/typemill/Middleware/ApiAuthentication.php b/system/typemill/Middleware/ApiAuthentication.php new file mode 100644 index 0000000..1b7529e --- /dev/null +++ b/system/typemill/Middleware/ApiAuthentication.php @@ -0,0 +1,162 @@ +settings = $settings; + } + + public function __invoke(Request $request, RequestHandler $handler) + { + $routeContext = RouteContext::fromRequest($request); + $baseURL = $routeContext->getBasePath(); + + # check if it is a session based authentication + if ($request->hasHeader('X-Session-Auth')) + { + # proceed, if a session based authentication has been done before in middleware + if($request->getAttribute('c_username') && $request->getAttribute('c_userrole')) + { + $response = $handler->handle($request); + + return $response; + } + } + + # api authentication with basic auth, inspired by tuupola + $ipAddress = $_SERVER['REMOTE_ADDR'] ?? null; + $host = $request->getUri()->getHost(); + $scheme = $request->getUri()->getScheme(); + $server_params = $request->getServerParams(); + + $apiaccess = true; + if(isset($this->settings['trustedipsforapi']) && $this->settings['trustedipsforapi'] !== '') + { + $trustedIPs = array_map('trim', explode(',', $this->settings['trustedipsforapi'])); + + if(!in_array($ipAddress, $trustedIPs)) + { + $apiaccess = false; + } + } + if(isset($this->settings['trustedhostsforapi']) && $this->settings['trustedhostsforapi'] !== '') + { + $trustedHosts = array_map('trim', explode(',', $this->settings['trustedhostsforapi'])); + + if(!in_array($host, $trustedHosts)) + { + $apiaccess = false; + } + } + + if(!$apiaccess) + { + $response = new Response(); + + $response->getBody()->write(json_encode([ + 'message' => 'Not trusted.' + ])); + + return $response->withHeader('WWW-Authenticate', 'Basic realm=')->withStatus(401); + } + + /* + # HTTP allowed only if secure is false or server is in relaxed array. + # use own logic for https proto forwarding + if($scheme !== "https" && $this->options["secure"] !== true) + { + $allowedHost = in_array($host, $this->options["relaxed"]); + + # if 'headers' is in the 'relaxed' key, then we check for forwarding + $allowedForward = false; + if (in_array("headers", $this->options["relaxed"])) + { + if ( $request->getHeaderLine("X-Forwarded-Proto") === "https" && $request->getHeaderLine('X-Forwarded-Port') === "443") + { + $allowedForward = true; + } + } + + if (!($allowedHost || $allowedForward)) + { + $message = sprintf("Insecure use of middleware over %s denied by configuration.", strtoupper($scheme)); + throw new \RuntimeException($message); + } + } + */ + + $params = []; + + if (preg_match("/Basic\s+(.*)$/i", $request->getHeaderLine("Authorization"), $matches)) + { + $explodedCredential = explode(":", base64_decode($matches[1]), 2); + if (count($explodedCredential) == 2) + { + [$params["user"], $params["password"]] = $explodedCredential; + } + } + + if(!empty($params)) + { + # load userdata + $user = new User(); + + if($user->setUserWithPassword($params['user'])) + { + $userdata = $user->getUserData(); + + # this might be unsecure, check for === comparator + $apiaccess = ( isset($userdata['apiaccess']) && $userdata['apiaccess'] == true ) ? true : false; + + if($userdata && $apiaccess && password_verify($params['password'], $userdata['password'])) + { + $request = $request->withAttribute('c_username', $userdata['username']); + $request = $request->withAttribute('c_userrole', $userdata['userrole']); + + # this executes code from routes first and then executes middleware + $response = $handler->handle($request); + + return $response; + } + else + { + # if basic auth is set but with wrong credentials + $response = new Response(); + + $response->getBody()->write(json_encode([ + 'message' => 'Authentication failed.' + ])); + + return $response->withHeader('WWW-Authenticate', 'Basic realm=')->withStatus(401); + } + } + } + +# elseif ($request->getHeaderLine('X-Requested-With') === 'XMLHttpRequest') { + # if you use this, then all xhr-calls need a session. + # no direct xhr calls without session are possible + # might increase security, but can have unwanted cases e.g. when you + # want to provide public api accessible for all by javascript (do you ever want??) +# } + + $response = new Response(); + + $response->getBody()->write(json_encode([ + 'message' => 'Authentication required.' + ])); + + return $response->withStatus(401); + } +} \ No newline at end of file diff --git a/system/typemill/Middleware/ApiAuthorization.php b/system/typemill/Middleware/ApiAuthorization.php new file mode 100644 index 0000000..c21c940 --- /dev/null +++ b/system/typemill/Middleware/ApiAuthorization.php @@ -0,0 +1,50 @@ +acl = $acl; + $this->resource = $resource; + $this->action = $action; + } + + public function process(Request $request, RequestHandler $handler) :Response + { + if(!$this->acl->isAllowed($request->getAttribute('c_userrole'), $this->resource, $this->action)) + { + $message = Translations::translate('Permission denied') . '. '; + $message .= Translations::translate('Your are an '); + $message .= $request->getAttribute('c_userrole'); + $message .= Translations::translate(' and you cannot '); + $message .= $this->action . Translations::translate(' this ') . $this->resource; + + $response = new Response(); + $response->getBody()->write(json_encode([ + 'message' => $message + ])); + + return $response->withStatus(403); + } + + $response = $handler->handle($request); + + return $response; + } +} \ No newline at end of file diff --git a/system/typemill/Middleware/AssetMiddleware.php b/system/typemill/Middleware/AssetMiddleware.php new file mode 100644 index 0000000..3d40632 --- /dev/null +++ b/system/typemill/Middleware/AssetMiddleware.php @@ -0,0 +1,42 @@ +assets = $assets; + + $this->view = $view; + } + + public function process(Request $request, RequestHandler $handler) :response + { + # get url from request + + # update the asset object in the container (for plugins) with the new url +# $this->container->assets->setBaseUrl($uri->getBaseUrl()); + + # add the asset object to twig-frontend for themes + $this->view->getEnvironment()->addGlobal('assets', $this->assets); + + # use {{ base_url() }} in twig templates +# $this->container['view']['base_url'] = $uri->getBaseUrl(); +# $this->container['view']['current_url'] = $uri->getPath(); + + $response = $handler->handle($request); + + return $response; + } +} \ No newline at end of file diff --git a/system/typemill/Middleware/CorsHeadersMiddleware.php b/system/typemill/Middleware/CorsHeadersMiddleware.php new file mode 100644 index 0000000..fe47f5f --- /dev/null +++ b/system/typemill/Middleware/CorsHeadersMiddleware.php @@ -0,0 +1,63 @@ +settings = $settings; + + $this->urlinfo = $urlinfo; + } + + public function process(Request $request, RequestHandler $handler) :response + { + # not in use right now, see here: https://chatgpt.com/c/67aba713-a7b0-8005-993e-a43ec4add72a + # add the custom headers to the response after everything is processed + $response = $handler->handle($request); + + ################### + # CORS HEADER # + ################### + + $origin = $request->getHeaderLine('Origin'); + $corsdomains = isset($this->settings['corsdomains']) ? trim($this->settings['corsdomains']) : false; + $whitelist = []; + + if($corsdomains && $corsdomains != '') + { + $corsdomains = explode(",", $corsdomains); + foreach($corsdomains as $domain) + { + $domain = trim($domain); + if($domain != '') + { + $whitelist[] = $domain; + } + } + } + + if(!$origin OR $origin == '' OR !isset($whitelist[$origin])) + { + # set current website as default origin and block all cross origin calls + $origin = $this->urlinfo['baseurl']; + } + + $response = $response->withHeader('Access-Control-Allow-Origin', $origin) + ->withHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Accept, Origin, Authorization') + ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS') + ->withHeader('Access-Control-Allow-Credentials', 'true'); + + return $response; + } +} \ No newline at end of file diff --git a/system/typemill/Middleware/CspHeadersMiddleware.php b/system/typemill/Middleware/CspHeadersMiddleware.php new file mode 100644 index 0000000..661fc9d --- /dev/null +++ b/system/typemill/Middleware/CspHeadersMiddleware.php @@ -0,0 +1,84 @@ +settings = $settings; + + $this->cspFromPlugins = $cspFromPlugins; + + $this->cspFromTheme = $cspFromTheme; + } + + public function process(Request $request, RequestHandler $handler) :response + { + # add the custom headers to the response after everything is processed + $response = $handler->handle($request); + + if(isset($this->settings['cspdisabled']) && $this->settings['cspdisabled']) + { + return $response; + } + + $whitelist = ["'unsafe-inline'", "'unsafe-eval'", "'self'", "data:", "*.youtube-nocookie.com", "*.youtube.com"]; + + $cspdomains = isset($this->settings['cspdomains']) ? trim($this->settings['cspdomains']) : false; + + if($cspdomains && $cspdomains != '') + { + $cspdomains = explode(",", $cspdomains); + foreach($cspdomains as $cspdomain) + { + $cspdomain = trim($cspdomain); + if($cspdomain != '') + { + $whitelist[] = $cspdomain; + } + } + } + + # add csp from plugins + if($this->cspFromPlugins && is_array($this->cspFromPlugins) && !empty($this->cspFromPlugins)) + { + $whitelist = array_merge($whitelist, $this->cspFromPlugins); + } + + # add csp from current theme + if($this->cspFromTheme && is_array($this->cspFromTheme) && !empty($this->cspFromTheme)) + { + $whitelist = array_merge($whitelist, $this->cspFromTheme); + } + + $whitelist = array_unique($whitelist); + + # do not add csp header if disabled-flag is found + if(in_array("disable", $whitelist)) + { + return $response; + } + + $whitelist = implode(' ', $whitelist); + + # Define the Content Security Policy header + $cspHeader = "default-src " . $whitelist . ";"; + + # Add the Content Security Policy header to the response + $response = $response->withHeader('Content-Security-Policy', $cspHeader); + + return $response; + } +} \ No newline at end of file diff --git a/system/typemill/Middleware/CustomHeadersMiddleware.php b/system/typemill/Middleware/CustomHeadersMiddleware.php new file mode 100644 index 0000000..98a993f --- /dev/null +++ b/system/typemill/Middleware/CustomHeadersMiddleware.php @@ -0,0 +1,47 @@ +settings = $settings; + } + + public function process(Request $request, RequestHandler $handler) :response + { + $scheme = $request->getUri()->getScheme(); + + # add the custom headers to the response after everything is processed + $response = $handler->handle($request); + + $response = $response->withoutHeader('Server'); + $response = $response->withHeader('X-Powered-By', 'Typemill'); + + $headersOff = $this->settings['headersoff'] ?? false; + + if(!$headersOff) + { + $response = $response + ->withHeader('X-Content-Type-Options', 'nosniff') + ->withHeader('X-Frame-Options', 'SAMEORIGIN') + ->withHeader('X-XSS-Protection', '1;mode=block') + ->withHeader('Referrer-Policy', 'no-referrer-when-downgrade'); + + if($scheme == 'https') + { + $response = $response->withHeader('Strict-Transport-Security', 'max-age=63072000'); + } + } + + return $response; + } +} \ No newline at end of file diff --git a/system/typemill/Middleware/FlashMessages.php b/system/typemill/Middleware/FlashMessages.php new file mode 100644 index 0000000..65eb88e --- /dev/null +++ b/system/typemill/Middleware/FlashMessages.php @@ -0,0 +1,34 @@ +container = $container; + } + + public function __invoke(Request $request, RequestHandler $handler) + { + if(isset($_SESSION)) + { + $this->container->set('flash', function(){ return new Messages(); }); + } + + if(isset($_SESSION['slimFlash']) && is_array($_SESSION['slimFlash'])) + { + $this->container->get('view')->getEnvironment()->addGlobal('flash', $_SESSION['slimFlash']); + + unset($_SESSION['slimFlash']); + } + + return $handler->handle($request); + } +} \ No newline at end of file diff --git a/system/typemill/Middleware/JsonBodyParser.php b/system/typemill/Middleware/JsonBodyParser.php new file mode 100644 index 0000000..f3d79de --- /dev/null +++ b/system/typemill/Middleware/JsonBodyParser.php @@ -0,0 +1,27 @@ +getHeaderLine('Content-Type'); + + if (strstr($contentType, 'application/json')) + { + $contents = json_decode(file_get_contents('php://input'), true); + if (json_last_error() === JSON_ERROR_NONE) + { + $request = $request->withParsedBody($contents); + } + } + + return $handler->handle($request); + } +} diff --git a/system/typemill/Middleware/OldInputMiddleware.php b/system/typemill/Middleware/OldInputMiddleware.php new file mode 100644 index 0000000..2da4a77 --- /dev/null +++ b/system/typemill/Middleware/OldInputMiddleware.php @@ -0,0 +1,37 @@ +view = $view; + } + + public function __invoke(Request $request, RequestHandler $handler) + { + if(isset($_SESSION)) + { + if(isset($_SESSION['old'])) + { + $this->view->getEnvironment()->addGlobal('old', $_SESSION['old']); + unset($_SESSION['old']); + } + if(!empty($request->getParsedBody())) + { + $_SESSION['old'] = $request->getParsedBody(); + } + } + + $response = $handler->handle($request); + + return $response; + } +} \ No newline at end of file diff --git a/system/typemill/Middleware/RemoveCredentialsMiddleware.php b/system/typemill/Middleware/RemoveCredentialsMiddleware.php new file mode 100644 index 0000000..a0fe634 --- /dev/null +++ b/system/typemill/Middleware/RemoveCredentialsMiddleware.php @@ -0,0 +1,28 @@ +getUri(); + + # Remove user information (username:password) from the URI + $uri = $uri->withUserInfo(''); + + # Create a new request with the modified URI + $request = $request->withUri($uri); + + # we could add basic auth credentials to request for later usage + + $response = $handler->handle($request); + + return $response; + } +} \ No newline at end of file diff --git a/system/typemill/Middleware/SecurityMiddleware.php b/system/typemill/Middleware/SecurityMiddleware.php new file mode 100644 index 0000000..2e9d1c7 --- /dev/null +++ b/system/typemill/Middleware/SecurityMiddleware.php @@ -0,0 +1,144 @@ +router = $router; + + $this->settings = $settings; + +# $this->flash = $flash; + } + + public function process(Request $request, RequestHandler $handler) :Response + { + if($request->getMethod() == 'POST') + { + $params = $request->getParsedBody(); + $referer = $request->getHeader('HTTP_REFERER'); + + if(!$referer OR $referer == '') + { + $response = new Response(); + + return $response->withHeader('Location', $this->router->urlFor('auth.login'))->withStatus(302); + } + + # simple bot check with honeypot + if( + (isset($params['personal-honey-mail'])) + && (null !== $params['personal-honey-mail']) + && ($params['personal-honey-mail'] != '') + ) + { + if(isset($this->settings['securitylog']) && $this->settings['securitylog']) + { + \Typemill\Static\Helpers::addLogEntry('honeypot ' . $referer[0]); + } + + $response = new Response(); + + return $response->withHeader('Location', $referer[0])->withStatus(302); + } + + # check captcha + if(isset($_SESSION['captcha'])) + { + # if captcha field was filled correctly + if( + (isset($params['captcha'])) + && (null !== $params['captcha']) + && \Gregwar\Captcha\PhraseBuilder::comparePhrases($_SESSION['phrase'], $params['captcha'] ) + ) + { + # delete captcha because it is solved and should not show up again + unset($_SESSION['captcha']); + + # delete phrase because can't use twice + unset($_SESSION['phrase']); + } + else + { + # delete phrase because can't use twice, but keep captcha so it shows up again + unset($_SESSION['phrase']); + + # set session to error + $_SESSION['captcha'] = 'error'; + + if( + isset($this->settings['securitylog']) + && $this->settings['securitylog'] + ) + { + \Typemill\Static\Helpers::addLogEntry('wrong captcha ' . $referer[0]); + } + + # and add message that captcha is empty +# $this->flash->addMessage('error', 'Captcha is wrong.'); + + $response = new Response(); + + return $response->withHeader('Location', $referer[0])->withStatus(302); + } + } + +/* + #check google recaptcha + if( null !== $request->getParam('g-recaptcha-response') ) + { + $recaptchaApi = 'https://www.google.com/recaptcha/api/siteverify'; + $settings = $this->c->get('settings'); + $secret = isset($settings['plugins'][$pluginName]['recaptcha_secretkey']) ? $settings['plugins'][$pluginName]['recaptcha_secretkey'] : false; + $recaptchaRequest = ['secret' => $secret, 'response' => $request->getParam('g-recaptcha-response')]; + + # use key 'http' even if you send the request to https://... + $options = array( + 'http' => array( + 'header' => "Content-type: application/x-www-form-urlencoded\r\n", + 'method' => 'POST', + 'content' => http_build_query($recaptchaRequest), + 'timeout' => 5 + ) + ); + + $context = stream_context_create($options); + $result = file_get_contents($recaptchaApi, false, $context); + $result = json_decode($result); + + if ($result === FALSE || $result->success === FALSE) + { + if(isset($this->settings['securitylog']) && $this->settings['securitylog']) + { + \Typemill\Models\Helpers::addLogEntry('wrong google recaptcha ' . $referer[0]); + } + + # and add message that captcha is empty + $this->flash->addMessage('error', 'Captcha is wrong.'); + return $response->withRedirect($referer[0]); + } + } +*/ + } + + $response = $handler->handle($request); + + return $response; + } +} \ No newline at end of file diff --git a/system/typemill/Middleware/SessionMiddleware.php b/system/typemill/Middleware/SessionMiddleware.php new file mode 100644 index 0000000..14afca3 --- /dev/null +++ b/system/typemill/Middleware/SessionMiddleware.php @@ -0,0 +1,62 @@ +segments = $segments; + + $this->route = $route; + } + + public function process(Request $request, RequestHandler $handler) :response + { + $uri = $request->getUri(); + + $scheme = $request->getUri()->getScheme(); + + # start session + Session::startSessionForSegments($this->segments, $this->route, $scheme); + + $authenticated = ( + (isset($_SESSION['username'])) && + (isset($_SESSION['login'])) + ) + ? true : false; + + if($authenticated) + { + # add userdata to the request for later use + $user = new User(); + + if($user->setUser($_SESSION['username'])) + { + $userdata = $user->getUserData(); + + $request = $request->withAttribute('c_username', $userdata['username']); + $request = $request->withAttribute('c_userrole', $userdata['userrole']); + if(isset($userdata['darkmode'])) + { + $request = $request->withAttribute('c_darkmode', $userdata['darkmode']); + } + } + } + + $response = $handler->handle($request); + + return $response; + } +} \ No newline at end of file diff --git a/system/typemill/Middleware/ValidationErrorsMiddleware.php b/system/typemill/Middleware/ValidationErrorsMiddleware.php new file mode 100644 index 0000000..084c949 --- /dev/null +++ b/system/typemill/Middleware/ValidationErrorsMiddleware.php @@ -0,0 +1,39 @@ +view = $view; + } + + public function __invoke(Request $request, RequestHandler $handler) + { + if(isset($_SESSION['errors'])) + { + $this->view->getEnvironment()->addGlobal('errors', $_SESSION['errors']); + + unset($_SESSION['errors']); + } + + if(isset($_SESSION['phrase'])) + { + $this->view->getEnvironment()->addGlobal('errors', ['captcha' => Translations::translate('the captcha is wrong, please try again')]); + + unset($_SESSION['phrase']); + } + + $response = $handler->handle($request); + + return $response; + } +} \ No newline at end of file diff --git a/system/typemill/Middleware/WebAuthorization.php b/system/typemill/Middleware/WebAuthorization.php new file mode 100644 index 0000000..da44d89 --- /dev/null +++ b/system/typemill/Middleware/WebAuthorization.php @@ -0,0 +1,43 @@ +router = $router; + $this->acl = $acl; + $this->resource = $resource; + $this->action = $action; + } + + public function process(Request $request, RequestHandler $handler) :Response + { + if(!$this->acl->isAllowed($request->getAttribute('c_userrole'), $this->resource, $this->action)) + { + $response = new Response(); + + return $response->withHeader('Location', $this->router->urlFor('home'))->withStatus(302); + } + + $response = $handler->handle($request); + + return $response; + } +} \ No newline at end of file diff --git a/system/typemill/Middleware/WebRedirectIfAuthenticated.php b/system/typemill/Middleware/WebRedirectIfAuthenticated.php new file mode 100644 index 0000000..500bc8f --- /dev/null +++ b/system/typemill/Middleware/WebRedirectIfAuthenticated.php @@ -0,0 +1,45 @@ +router = $router; + $this->settings = $settings; + } + + public function process(Request $request, RequestHandler $handler) :Response + { + $authenticated = ( + (isset($_SESSION['username'])) && + (isset($_SESSION['login'])) + ) + ? true : false; + + if($authenticated) + { + + $editor = (isset($this->settings['editor']) && $this->settings['editor'] == 'visual') ? 'visual' : 'raw'; + + $response = new Response(); + + return $response->withHeader('Location', $this->router->urlFor('content.' . $editor))->withStatus(302); + } + + $response = $handler->handle($request); + + return $response; + } +} \ No newline at end of file diff --git a/system/typemill/Middleware/WebRedirectIfUnauthenticated.php b/system/typemill/Middleware/WebRedirectIfUnauthenticated.php new file mode 100644 index 0000000..6d089fc --- /dev/null +++ b/system/typemill/Middleware/WebRedirectIfUnauthenticated.php @@ -0,0 +1,56 @@ +router = $router; + } + + public function process(Request $request, RequestHandler $handler) :response + { + # session authentication + if( + (isset($_SESSION['username'])) && + (isset($_SESSION['login'])) + ) + { + # load userdata + $user = new User(); + + if($user->setUser($_SESSION['username'])) + { + # pass username and userrole + $userdata = $user->getUserData(); + + $request = $request->withAttribute('c_username', $userdata['username']); + $request = $request->withAttribute('c_userrole', $userdata['userrole']); + if(isset($userdata['darkmode'])) + { + $request = $request->withAttribute('c_darkmode', $userdata['darkmode']); + } + + # this executes code from routes first and then executes middleware + $response = $handler->handle($request); + + return $response; + } + } + + # this executes only middleware code and not code from route + $response = new Response(); + + return $response->withHeader('Location', $this->router->urlFor('auth.show'))->withStatus(302); + } +} \ No newline at end of file diff --git a/system/typemill/Models/ApiCalls.php b/system/typemill/Models/ApiCalls.php new file mode 100644 index 0000000..70cdf59 --- /dev/null +++ b/system/typemill/Models/ApiCalls.php @@ -0,0 +1,106 @@ +error; + } + + public function makePostCall(string $url, array $data, $authHeader = '') + { + if (in_array('curl', get_loaded_extensions())) { + return $this->makeCurlCall($url, 'POST', $data, $authHeader); + } + + return $this->makeFileGetContentsCall($url, 'POST', $data, $authHeader); + } + + public function makeGetCall($url, $authHeader = '') + { + if (in_array('curl', get_loaded_extensions())) { + return $this->makeCurlCall($url, 'GET', null, $authHeader); + } + + return $this->makeFileGetContentsCall($url, 'GET', null, $authHeader); + } + + private function makeCurlCall($url, $method, $data = false, $authHeader = '') + { + $this->error = null; + + $headers = [ + "Content-Type: application/json", + ]; + + if (!empty($authHeader)) { + $headers[] = $authHeader; + } + + $curl = curl_init($url); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); + if ($method === 'POST' && $data) { + $postdata = json_encode($data); + if ($postdata === false) { + $this->error = "JSON encoding error: " . json_last_error_msg(); + return false; + } + curl_setopt($curl, CURLOPT_POSTFIELDS, $postdata); + curl_setopt($curl, CURLOPT_POST, true); + } + curl_setopt($curl, CURLOPT_FAILONERROR, true); + + $response = curl_exec($curl); + + if ($response === false) { + $this->error = curl_error($curl); + } + curl_close($curl); + + return $response !== false ? $response : false; + } + + private function makeFileGetContentsCall($url, $method, $data = null, $authHeader = '') + { + $this->error = null; + + $headers = [ + "Content-Type: application/json" + ]; + + if (!empty($authHeader)) { + $headers[] = $authHeader; + } + + $options = [ + 'http' => [ + 'method' => $method, + 'ignore_errors' => true, + 'header' => implode("\r\n", $headers), + ] + ]; + + if ($method === 'POST' && $data !== null) { + $postdata = json_encode($data); + if ($postdata === false) { + $this->error = "JSON encoding error: " . json_last_error_msg(); + return false; + } + $options['http']['content'] = $postdata; + } + + $context = stream_context_create($options); + $response = file_get_contents($url, false, $context); + + if ($response === false) { + $this->error = 'file_get_contents failed for ' . $method . ' request.'; + } + + return $response !== false ? $response : false; + } +} diff --git a/system/typemill/Models/Content.php b/system/typemill/Models/Content.php new file mode 100644 index 0000000..3eaadeb --- /dev/null +++ b/system/typemill/Models/Content.php @@ -0,0 +1,384 @@ +storage = new StorageWrapper('\Typemill\Models\Storage'); + $this->parsedown = new ParsedownExtension($baseurl, $settings, $dispatcher); + } + + public function getDraftMarkdown($item) + { + # needed for ToC links + # $relurl = '/tm/content/' . $this->settings['editor'] . '/' . $this->item->urlRel; + + # to fix footnote-logic in parsedown, set visual mode to true + # $this->parsedown->setVisualMode(); + + # make sure you get the txt version unless page is published +# $filetype = ($item->status == 'published') ? '.md' : '.txt'; + + $filetype = '.txt'; + + $markdown = $this->storage->getFile('contentFolder', '', $item->pathWithoutType . $filetype); + + if(!$markdown) + { + $filetype = '.md'; + $markdown = $this->storage->getFile('contentFolder', '', $item->pathWithoutType . $filetype); + } + + # if !$mardkown ? + + if($markdown == '') + { + $markdownArray = []; + } + elseif($filetype == '.txt') + { + $markdownArray = json_decode($markdown); + } + else + { + $markdownArray = $this->parsedown->markdownToArrayBlocks($markdown); + } + + return $markdownArray; + } + + public function getLiveMarkdown($item) + { + $filetype = '.md'; + + $markdown = $this->storage->getFile('contentFolder', '', $item->pathWithoutType . $filetype); + + return $markdown; + } + + public function saveDraftMarkdown($item, array $markdownArray) + { + $markdown = json_encode($markdownArray); + + if($this->storage->writeFile('contentFolder', '', $item->pathWithoutType . '.txt', $markdown)) + { + return true; + } + + return $this->storage->getError(); + } + + public function publishMarkdown($item, array $markdownArray) + { + $markdown = $this->parsedown->arrayBlocksToMarkdown($markdownArray); + + if($this->storage->writeFile('contentFolder', '', $item->pathWithoutType . '.md', $markdown)) + { + $this->storage->deleteFile('contentFolder', '', $item->pathWithoutType . '.txt'); + + return true; + } + + return $this->storage->getError(); + } + + public function unpublishMarkdown($item, array $markdownArray) + { + $markdown = json_encode($markdownArray); + + if($this->storage->writeFile('contentFolder', '', $item->pathWithoutType . '.txt', $markdown)) + { + $this->storage->deleteFile('contentFolder', '', $item->pathWithoutType . '.md'); + + return true; + } + + return $this->storage->getError(); + } + + public function deleteDraft($item) + { + if($this->storage->deleteFile('contentFolder', '', $item->pathWithoutType . '.txt')) + { + return true; + } + + return $this->storage->getError(); + } + + public function deletePage($item) + { + $extensions = ['.md', '.txt', '.yaml']; + + foreach($extensions as $extension) + { + $result = $this->storage->deleteFile('contentFolder', '', $item->pathWithoutType . $extension); + } + + if($result !== true) + { + return $this->storage->getError(); + } + + return true; + } + + # overcomplicated. Next time just use a recursive delete everything method in storage + public function deleteFolder($item, $result = true) + { + if($this->storage->deleteContentFolderRecursive($item->path)) + { + return true; + } + + return $this->storage->getError(); + } + + public function hasPublishedItems($folder, $published = false) + { + $published = false; + + if(isset($folder->folderContent) && is_array($folder->folderContent) && !empty($folder->folderContent)) + { + foreach($folder->folderContent as $item) + { + if($item->status == 'published') + { + return true; + } + + if($item->elementType == 'folder') + { + $published = $this->hasPublishedItems($item); + } + } + } + + return $published; + } + + public function addDraftHtml($markdownArray) + { + $content = []; + + $toc_id = false; + + $this->parsedown->setSafeMode(true); + + foreach($markdownArray as $key => $markdown) + { + if($markdown == "[TOC]") + { + $toc_id = $key; + } + + $contentArray = $this->parsedown->text($markdown); + $html = $this->parsedown->markup($contentArray); + + $content[$key] = [ + 'id' => $key, + 'markdown' => $markdown, + 'html' => $html + ]; + } + + $this->parsedown->setSafeMode(false); + + + if($toc_id) + { + # generate the toc markup + $tocMarkup = $this->parsedown->buildTOC($this->parsedown->headlines); + + # add to content html + $content[$toc_id]['html'] = $tocMarkup; + } + + return $content; + } + + public function getDraftHtml($markdownArray) + { + $this->parsedown->setSafeMode(true); + + foreach($markdownArray as $key => $block) + { + # parse markdown-file to content-array + $contentArray = $this->parsedown->text($block); + + # parse markdown-content-array to content-string + $content[$key] = $this->parsedown->markup($contentArray); + } + + $this->parsedown->setSafeMode(false); + + return $content; + } + + public function getContentArray($markdown) + { + $this->parsedown->setSafeMode(true); + + $contentArray = $this->parsedown->text($markdown); + + $this->parsedown->setSafeMode(false); + + return $contentArray; + } + + public function getContentHtml($contentArray) + { + $this->parsedown->setSafeMode(true); + + $markdown = $this->parsedown->markup($contentArray); + + $this->parsedown->setSafeMode(false); + + return $markdown; + } + + public function arrayBlocksToMarkdown($arrayBlocks) + { + die("please use markdownToArrayText in content model"); + + $markdown = ''; + + foreach($arrayBlocks as $block) + { + $markdown .= $block . "\n\n"; + } + + return $markdown; + } + + public function markdownArrayToText(array $markdownArray) + { + return $this->parsedown->arrayBlocksToMarkdown($markdownArray); + } + + public function markdownTextToArray(string $markdown) + { + return $this->parsedown->markdownToArrayBlocks($markdown); + } + + public function getFirstImage(array $contentArray) + { + foreach($contentArray as $block) + { + if(isset($block['name']) && $block['name'] == 'p') + { + if(isset($block['handler']['argument']) && substr($block['handler']['argument'], 0, 2) == '![' ) + { + return $block['handler']['argument']; + } + } + } + + return false; + } + + +########## FIX + public function generateToc($content, $relurl) + { + die('Please fix generateToc in content.php'); + + # we assume that page has no table of content + $toc = false; + + # needed for ToC links + $relurl = '/tm/content/' . $this->settings['editor'] . '/' . $this->item->urlRel; + + # loop through mardkown-array and create html-blocks + foreach($content as $key => $block) + { + # parse markdown-file to content-array + $contentArray = $parsedown->text($block); + + if($block == '[TOC]') + { + # toc is true and holds the key of the table of content now + $toc = $key; + } + + # parse markdown-content-array to content-string + $content[$key] = ['id' => $key, 'html' => $parsedown->markup($contentArray)]; + } + + # if page has a table of content + if($toc) + { + # generate the toc markup + $tocMarkup = $parsedown->buildTOC($parsedown->headlines); + + # toc holds the id of the table of content and the html-markup now + $toc = ['id' => $toc, 'html' => $tocMarkup]; + } + + return $toc; + } + + # MOVE SOMEWHERE ELSE + public function checkCustomCSS($theme) + { + return $this->storage->checkFile('cacheFolder', '', $theme . '-custom.css'); + } + + public function checkLogoFile($logo) + { + return $this->storage->checkFile('basepath', '', $logo); + } + + public function getTitle(array $markdown) + { + if(!is_array($markdown)) + { + $markdown = $this->markdownTextToArray($markdown); + } + + return trim($markdown[0], "# "); + } + + public function getDescription(array $markdown) + { + if(!is_array($markdown)) + { + $markdown = $this->markdownTextToArray($markdown); + } + + $description = isset($markdown[1]) ? $markdown[1] : ''; + + # create description or abstract from content + if($description !== '') + { + $firstLineArray = $this->parsedown->text($description); + $description = strip_tags($this->parsedown->markup($firstLineArray)); + + # if description is very short + if(strlen($description) < 100 && isset($markdown[2])) + { + $secondLineArray = $this->parsedown->text($markdown[2]); + $description .= ' ' . strip_tags($this->parsedown->markup($secondLineArray)); + } + + # if description is too long + if(strlen($description) > 160) + { + $description = substr($description, 0, 160); + $lastSpace = strrpos($description, ' '); + $description = substr($description, 0, $lastSpace); + } + } + + return $description; + } +} \ No newline at end of file diff --git a/system/typemill/Models/Extension.php b/system/typemill/Models/Extension.php new file mode 100644 index 0000000..ab4a503 --- /dev/null +++ b/system/typemill/Models/Extension.php @@ -0,0 +1,207 @@ +storage = new StorageWrapper('\Typemill\Models\Storage'); + } + + public function getThemeDetails($activeThemeName = NULL) + { + $themes = $this->getThemes(); + + $themeDetails = []; + foreach($themes as $themeName) + { + $details = $this->getThemeDefinition($themeName); + if($details && isset($details['name'])) + { + # add to first position if active + if($activeThemeName && ($activeThemeName == $themeName)) + { + $themeDetails = array_merge(array($themeName => $details), $themeDetails); + } + else + { + $themeDetails[$themeName] = $details; + } + } + } + + return $themeDetails; + } + + public function getThemeSettings($themesInSettings) + { + # WHAT ABOUT DEFAULT-SETTINGS FROM THEME YAMLs? + + $themes = $this->getThemes(); + + $themeSettings = []; + foreach($themes as $themename) + { + $themeinputs = []; + if(isset($themesInSettings[$themename]) && is_array($themesInSettings[$themename])) + { + $themeinputs = $themesInSettings[$themename]; + } + + $themeSettings[$themename] = $themeinputs; + $customcss = $this->storage->getFile('cacheFolder', '', $themename . '-custom.css'); + $themeSettings[$themename]['customcss'] = $customcss ? $customcss : ''; + } + + return $themeSettings; + } + + + public function getThemes() + { + $themeFolder = $this->storage->getFolderPath('themesFolder'); + $themeFolderC = scandir($themeFolder); + $themes = []; + foreach ($themeFolderC as $key => $theme) + { + if (!in_array($theme, [".",".."])) + { + if (is_dir($themeFolder . DIRECTORY_SEPARATOR . $theme)) + { + $themes[] = $theme; + } + } + } + + return $themes; + } + + public function getThemeDefinition($themeName) + { + $themeSettings = $this->storage->getYaml('themesFolder', $themeName, $themeName . '.yaml'); + + if($themeSettings) + { + # add standard-textarea for custom css + $themeSettings['forms']['fields']['fieldsetcss'] = [ + 'type' => 'fieldset', + 'legend' => Translations::translate('Custom CSS'), + 'fields' => [ + 'customcss' => [ + 'type' => 'codearea', + 'label' => Translations::translate('Add your individual CSS'), + 'class' => 'codearea', + 'description' => Translations::translate('You can overwrite the theme-css with your own css here.') + ] + ] + ]; + + $themeSettings['preview'] = '/themes/' . $themeName . '/' . $themeName . '.png'; + } + + return $themeSettings; + } + + public function getThemeReadymades($themeName) + { + $readymades = []; + $folder = 'readymades' . DIRECTORY_SEPARATOR . $themeName; + $readymadeFolder = $this->storage->getFolderPath('dataFolder', $folder); + + if(is_dir($readymadeFolder)) + { + $readymadeFiles = scandir($readymadeFolder); + + foreach($readymadeFiles as $readymadeFile) + { + if (!in_array($readymadeFile, array(".","..")) && substr($readymadeFile, 0, 1) != '.') + { + $readymadeData = $this->storage->getYaml('dataFolder', $folder, $readymadeFile); + if($readymadeData && !empty($readymadeData)) + { + $readymades = $readymades + $readymadeData; + } + } + } + } + + return $readymades; + } + + public function storeThemeReadymade($themeName, $readymadeName, $data) + { + $folder = 'readymades' . DIRECTORY_SEPARATOR . $themeName; + + $result = $this->storage->updateYaml('dataFolder', $folder, $readymadeName . '.yaml', $data); + } + + public function deleteThemeReadymade($themeName, $readymadeName) + { + $folder = 'readymades' . DIRECTORY_SEPARATOR . $themeName; + + $result = $this->storage->deleteFile('dataFolder', $folder, $readymadeName . '.yaml'); + + if(!$result) + { + return $this->storage->getError(); + } + + return true; + } + + public function getPluginDetails($userSettings = NULL) + { + $plugins = $this->getPlugins(); + + $pluginDetails = []; + foreach($plugins as $pluginName) + { + $details = $this->getPluginDefinition($pluginName); + if($details && $details['name']) + { + # add active plugins first + if( + $userSettings + && isset($userSettings[$pluginName]) + && ($userSettings[$pluginName]['active'] == true) + ) + { + $pluginDetails = array_merge(array($pluginName => $details), $pluginDetails); + } + else + { + $pluginDetails[$pluginName] = $details; + } + } + } + return $pluginDetails; + } + + public function getPlugins() + { + + $pluginlist = \Typemill\Static\Plugins::loadPlugins(); + + $plugins = []; + + foreach($pluginlist as $plugin) + { + $plugins[] = $plugin['name']; + } + + return $plugins; + } + + public function getPluginDefinition($pluginName) + { + $pluginSettings = $this->storage->getYaml('pluginsFolder', $pluginName, $pluginName . '.yaml'); + + return $pluginSettings; + } +} \ No newline at end of file diff --git a/system/typemill/Models/Field.php b/system/typemill/Models/Field.php new file mode 100644 index 0000000..d8824cb --- /dev/null +++ b/system/typemill/Models/Field.php @@ -0,0 +1,297 @@ +setName($fieldName); + + $type = isset($fieldConfigs['type']) ? $fieldConfigs['type'] : false; + $this->setType($type); + + $label = isset($fieldConfigs['label']) ? $fieldConfigs['label'] : false; + $this->setLabel($label); + + $checkboxlabel = isset($fieldConfigs['checkboxlabel']) ? $fieldConfigs['checkboxlabel'] : false; + $this->setCheckboxLabel($checkboxlabel); + + $options = isset($fieldConfigs['options']) ? $fieldConfigs['options'] : array(); + $this->setOptions($options); + + $this->setAttributes($fieldConfigs); + + $this->setAttributeValues($fieldConfigs); + + $this->setHelpers($fieldConfigs); + } + + private function setName($name) + { + $this->name = $name; + } + + public function getName() + { + return $this->name; + } + + private function setType($type) + { + if(in_array($type, $this->types)) + { + $this->type = $type; + } + } + + public function getType() + { + return $this->type; + } + + public function setLabel($label) + { + $this->label = $label; + } + + public function getLabel() + { + return $this->label; + } + + public function setCheckboxLabel($label) + { + $this->checkboxLabel = $label; + } + + public function getCheckboxLabel() + { + return $this->checkboxLabel; + } + + public function setContent($content) + { + $this->content = $content; + } + + public function getContent() + { + return $this->content; + } + + private function setOptions(array $options) + { + foreach($options as $key => $value) + { + $this->options[$key] = $value; + } + } + + public function getOptions() + { + if(isset($this->options)) + { + return $this->options; + } + return false; + } + + private function setAttributes($fieldConfigs) + { + foreach($fieldConfigs as $key => $value) + { + if(is_string($key) && in_array($key, $this->attr)) + { + $this->attributes[$key] = $value; + } + } + } + + /* get all attributes of the field and return them as a string. For usage in templates */ + public function getAttributes() + { + $string = false; + + foreach($this->attributes as $key => $attribute) + { + $string .= ' ' . $key; + } + + return $string; + } + + /* set a single attribute. Used e.g. in controller to change the value */ + public function setAttribute($key, $value) + { + $this->attributes[$key] = $value; + } + + public function unsetAttribute($key) + { + unset($this->attributes[$key]); + } + + /* get a single attribute, if it is defined. For usage in templates like getAttribute('required') */ + public function getAttribute($key) + { + if(isset($this->attributes[$key])) + { + return $this->attributes[$key]; + } + + return false; + } + + private function setAttributeValues($fieldConfigs) + { + foreach($fieldConfigs as $key => $value) + { + if(is_string($key) && in_array($key, $this->attrValues)) + { + $this->attributeValues[$key] = $value; + } + } + } + + /* get all attributes as string. For usage in template */ + public function getAttributeValues() + { + $string = false; + + foreach($this->attributeValues as $key => $attribute) + { + $string .= ' ' . $key . '="' . $attribute . '"'; + } + + return $string; + } + + public function setAttributeValue($key, $value) + { + /* pretty dirty, but you should not add a value for a simple checkbox */ + if($key == 'value' && $this->type == 'checkbox') + { + return; + } + + $this->attributeValues[$key] = $value; + } + + public function getAttributeValue($key) + { + if(isset($this->attributeValues[$key])) + { + return $this->attributeValues[$key]; + } + + return false; + } + + + public function setHelpers($fieldConfigs) + { + foreach($fieldConfigs as $key => $config) + { + if(is_string($key) && in_array($key, $this->helpers)) + { + $this->$key = $config; + } + } + } + + public function getHelper($helperName) + { + if(isset($this->$helperName)) + { + return $this->$helperName; + } + return false; + } +} \ No newline at end of file diff --git a/system/typemill/Models/Fields.php b/system/typemill/Models/Fields.php new file mode 100644 index 0000000..e810559 --- /dev/null +++ b/system/typemill/Models/Fields.php @@ -0,0 +1,138 @@ +c = $c; + } + + public function getFields($userSettings, $objectType, $objectName, $objectSettings, $formType = false) + { + # hold all fields in array + $fields = array(); + + # formtype are backend forms or public forms, only relevant for plugins for now + $formType = $formType ? $formType : 'forms'; + + # iterate through all fields of the objectSetting (theme or plugin) + foreach($objectSettings[$formType]['fields'] as $fieldName => $fieldConfigurations) + { + if($fieldConfigurations['type'] == 'fieldset') + { + # if it is a fieldset, then create a subset for the containing field and read them with a recursive function + $subSettings = $objectSettings; + $subSettings[$formType] = $fieldConfigurations; + + $fieldset = array(); + $fieldset['type'] = 'fieldset'; + $fieldset['legend'] = $fieldConfigurations['legend']; + $fieldset['fields'] = $this->getFields($userSettings, $objectType, $objectName, $subSettings, $formType); + $fields[] = $fieldset; + } + else + { + # For label, helptext and description you can use the value of another field. This is useful e.g. to localize the label of public forms via plugin settings. + if(isset($fieldConfigurations['label']) && isset($userSettings[$objectType][$objectName][$fieldConfigurations['label']])) + { + $fieldConfigurations['label'] = $userSettings[$objectType][$objectName][$fieldConfigurations['label']]; + } + if(isset($fieldConfigurations['help']) && isset($userSettings[$objectType][$objectName][$fieldConfigurations['help']])) + { + $fieldConfigurations['help'] = $userSettings[$objectType][$objectName][$fieldConfigurations['help']]; + } + if(isset($fieldConfigurations['description']) && isset($userSettings[$objectType][$objectName][$fieldConfigurations['description']])) + { + $fieldConfigurations['description'] = $userSettings[$objectType][$objectName][$fieldConfigurations['description']]; + } + + # check if the field is a select field with dataset = userroles + if(isset($this->c) && isset($fieldConfigurations['type']) && ($fieldConfigurations['type'] == 'select' ) && isset($fieldConfigurations['dataset']) && ($fieldConfigurations['dataset'] == 'userroles' ) ) + { + $userroles = [null => null]; + foreach($this->c->acl->getRoles() as $userrole) + { + $userroles[$userrole] = $userrole; + } + $fieldConfigurations['options'] = $userroles; + } + + # for each field generate a new field object with the field name and the field configurations + $field = new Field($fieldName, $fieldConfigurations); + + # handle the value for the field + $userValue = false; + + # first, add the default value from the original plugin or theme settings + if(isset($objectSettings['settings'][$fieldName])) + { + $userValue = $objectSettings['settings'][$fieldName]; + } + + # now overwrite the default values with the user values stored in the user settings + if(isset($userSettings[$objectType][$objectName][$fieldName])) + { + $userValue = $userSettings[$objectType][$objectName][$fieldName]; + } + + # now overwrite user-values, if there are old-input values from the actual form (e.g. after input error) + if(isset($_SESSION['old'][$objectName][$fieldName])) + { + $userValue = $_SESSION['old'][$objectName][$fieldName]; + } + + # Now prepopulate the field object with the value */ + if($field->getType() == "textarea") + { + if($fieldName == "publicformdefinitions" && $userValue == '') + { + $userValue = $objectSettings['settings'][$fieldName]; + } + if($userValue) + { + $field->setContent($userValue); + } + } + elseif($field->getType() == 'paragraph') + { + if(isset($fieldConfigurations['value'])) + { + $field->setContent($fieldConfigurations['value']); + } + if($userValue) + { + $field->setContent($userValue); + } + } + elseif($field->getType() == "checkbox") + { + # checkboxes need a special treatment, because field does not exist in settings if unchecked by user + if(isset($userSettings[$objectType][$objectName][$fieldName])) + { + $field->setAttribute('checked', 'true'); + } + else + { + $field->unsetAttribute('checked'); + } + } + else + { + $field->setAttributeValue('value', $userValue); + } + + # add the field to the field-List + $fields[] = $field; + + } + } + return $fields; + } +} \ No newline at end of file diff --git a/system/typemill/Models/Folder.php b/system/typemill/Models/Folder.php new file mode 100644 index 0000000..8c09287 --- /dev/null +++ b/system/typemill/Models/Folder.php @@ -0,0 +1,294 @@ + $item) + { + if (!in_array($item, array(".","..")) && substr($item, 0, 1) != '.') + { + if (is_dir($folderPath . DIRECTORY_SEPARATOR . $item)) + { + if($flat) + { + if($flat === $item) + { + $folderContent[$item] = $this->scanFolder($folderPath . DIRECTORY_SEPARATOR . $item); + } + else + { + # We need to include the index.txt or index.md for the folder + $index = 'index.'; + if( file_exists($folderPath . DIRECTORY_SEPARATOR . $item . DIRECTORY_SEPARATOR . 'index.txt') ) + { + $index .= 'txt'; + } + if( file_exists($folderPath . DIRECTORY_SEPARATOR . $item . DIRECTORY_SEPARATOR . 'index.md') ) + { + $index .= 'md'; + } + + $folderContent[$item] = [$index]; + } + } + else + { + $folderContent[$item] = $this->scanFolder($folderPath . DIRECTORY_SEPARATOR . $item); + } + } + else + { + $nameParts = $this->getStringParts($item); + $fileType = array_pop($nameParts); + + if($fileType == 'md') + { + $folderContent[] = $item; + } + + if($fileType == 'txt') + { + if(isset($last) && ($last == implode($nameParts)) ) + { + array_pop($folderContent); + $item = $item . 'md'; + } + $folderContent[] = $item; + } + + # store the name of the last file + $last = implode($nameParts); + } + } + } + return $folderContent; + } + + /* + * Transforms array of folder item into an array of item-objects with additional information for each item + * vars: multidimensional array with folder- and file-names + * returns: array of objects. Each object contains information about an item (file or folder). + */ + + public function getFolderContentDetails(array $folderContent, $language, $baseUrl, $fullSlugWithFolder = NULL, $fullSlugWithoutFolder = NULL, $fullPath = NULL, $keyPath = NULL, $chapter = NULL) + { + $contentDetails = []; + $iteration = 0; + $chapternr = 1; + + foreach($folderContent as $key => $name) + { + $item = new \stdClass(); + + if(is_array($name)) + { + $nameParts = $this->getStringParts($key); + + $fileType = ''; + $status = 'undefined'; + if(in_array('index.md', $name)) + { + $fileType = 'md'; + $status = 'published'; + } + if(in_array('index.txt', $name)) + { + $fileType = 'txt'; + $status = 'unpublished'; + } + if(in_array('index.txtmd', $name)) + { + $fileType = 'txt'; + $status = 'modified'; + } + + $item->originalName = $key; + $item->elementType = 'folder'; + $item->contains = $this->getFolderContentType($name, $fullPath . DIRECTORY_SEPARATOR . $key . DIRECTORY_SEPARATOR . 'index.yaml'); + $item->status = $status; + $item->fileType = $fileType; + $item->order = count($nameParts) > 1 ? array_shift($nameParts) : NULL; + $item->name = implode(" ",$nameParts); + $item->name = iconv(mb_detect_encoding($item->name, mb_detect_order(), true), "UTF-8", $item->name); + $item->slug = implode("-",$nameParts); + $item->slug = $this->createSlug($item->slug, $language); + $item->path = $fullPath . DIRECTORY_SEPARATOR . $key; + $item->pathWithoutType = $fullPath . DIRECTORY_SEPARATOR . $key . DIRECTORY_SEPARATOR . 'index'; + $item->urlRelWoF = $fullSlugWithoutFolder . '/' . $item->slug; + $item->urlRel = $fullSlugWithFolder . '/' . $item->slug; + $item->urlAbs = $baseUrl . $fullSlugWithoutFolder . '/' . $item->slug; + $item->key = $iteration; + $item->keyPath = isset($keyPath) ? $keyPath . '.' . $iteration : $iteration; + $item->keyPathArray = explode('.', $item->keyPath); + $item->chapter = $chapter ? $chapter . '.' . $chapternr : $chapternr; + $item->active = false; + $item->activeParent = false; + $item->hide = false; + + # sort posts in descending order + if($item->contains == "posts") + { + rsort($name); + } + + $item->folderContent = []; + + if(!empty($name)) + { + $item->folderContent = $this->getFolderContentDetails($name, $language, $baseUrl, $item->urlRel, $item->urlRelWoF, $item->path, $item->keyPath, $item->chapter); + } + } + elseif($name) + { + # do not use index files + if($name == 'index.md' || $name == 'index.txt' || $name == 'index.txtmd' ) continue; + + $nameParts = $this->getStringParts($name); + $fileType = array_pop($nameParts); + $nameWithoutType = $this->getNameWithoutType($name); + + if($fileType == 'md') + { + $status = 'published'; + } + elseif($fileType == 'txt') + { + $status = 'unpublished'; + } + else + { + $fileType = 'txt'; + $status = 'modified'; + } + + $item->originalName = $name; + $item->elementType = 'file'; + $item->status = $status; + $item->fileType = $fileType; + $item->order = count($nameParts) > 1 ? array_shift($nameParts) : NULL; + $item->name = implode(" ",$nameParts); + $item->name = iconv(mb_detect_encoding($item->name, mb_detect_order(), true), "UTF-8", $item->name); + $item->slug = implode("-",$nameParts); + $item->slug = $this->createSlug($item->slug, $language); + $item->path = $fullPath . DIRECTORY_SEPARATOR . $name; + $item->pathWithoutType = $fullPath . DIRECTORY_SEPARATOR . $nameWithoutType; + $item->key = $iteration; + $item->keyPath = isset($keyPath) ? $keyPath . '.' . $iteration : $iteration; + $item->keyPathArray = explode('.',$item->keyPath); + $item->chapter = $chapter . '.' . $chapternr; + $item->urlRelWoF = $fullSlugWithoutFolder . '/' . $item->slug; + $item->urlRel = $fullSlugWithFolder . '/' . $item->slug; + $item->urlAbs = $baseUrl . $fullSlugWithoutFolder . '/' . $item->slug; + $item->active = false; + $item->activeParent = false; + $item->hide = false; + } + + $iteration++; + $chapternr++; + $contentDetails[] = $item; + } + return $contentDetails; + } + + + public function getFolderContentType($folder, $yamlpath) + { + # check if folder is empty or has only index.yaml-file. This is a rare case so make it quick and dirty + if(count($folder) <= 1) + { + # check if in folder yaml file contains "posts", then return posts + $folderyamlpath = getcwd() . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $yamlpath; + + $fileContent = false; + if(file_exists($folderyamlpath)) + { + $fileContent = file_get_contents($folderyamlpath); + } + + if($fileContent && strpos($fileContent, 'contains: posts') !== false) + { + return 'posts'; + } + return 'pages'; + } + else + { + $firstKey = array_key_first($folder); + $file = $folder[$firstKey]; + if(is_array($file)) + { + # first item in folder is folder again, so return pages + return 'pages'; + } + + $nameParts = $this->getStringParts($file); + $order = count($nameParts) > 1 ? array_shift($nameParts) : NULL; + + if($order && strlen($order > 8)) + { + $order = substr($order, 0, 7); + + if(\DateTime::createFromFormat('Ymd', $order) !== FALSE) + { + return "posts"; + } + } + + return "pages"; + } + } + + public function getStringParts($name) + { + return preg_split('/[\-\.\_\=\+\?\!\*\#\(\)\/ ]/',$name); + } + + public function getFileType($fileName) + { + $parts = preg_split('/\./',$fileName); + return end($parts); + } + + public function splitFileName($fileName) + { + $parts = preg_split('/\./',$fileName); + return $parts; + } + + public function getNameWithoutType($fileName) + { + $parts = preg_split('/\./',$fileName); + return $parts[0]; + } + + public function createSlug($name, $language = null) + { + $name = iconv(mb_detect_encoding($name, mb_detect_order(), true), "UTF-8", $name); + $language = $language ? $language : ""; + + return URLify::filter( + $name, + $length = 60, + $language, + $file_name = false, + $use_remove_list = false, + $lower_case = true, + $treat_underscore_as_space = true + ); + } +} \ No newline at end of file diff --git a/system/typemill/Models/License.php b/system/typemill/Models/License.php new file mode 100644 index 0000000..0141ad2 --- /dev/null +++ b/system/typemill/Models/License.php @@ -0,0 +1,587 @@ + [ + 'name' => 'MAKER', + 'scope' => ['MAKER' => true] + ], + 'BUSINESS' => [ + 'name' => 'BUSINESS', + 'scope' => ['MAKER' => true, 'BUSINESS' => true] + ] + ]; + + public function getMessage() + { + return $this->message; + } + + public function getLicenseFile() + { + $storage = new StorageWrapper('\Typemill\Models\Storage'); + + $licensefile = $storage->getYaml('basepath', 'settings', 'license.yaml'); + + if($licensefile) + { + return $licensefile; + } + + $this->message = 'Error loading license: ' . $storage->getError(); + + return false; + } + + public function getLicenseFields() + { + $storage = new StorageWrapper('\Typemill\Models\Storage'); + $licensefields = $storage->getYaml('systemSettings', '', 'license.yaml'); + + return $licensefields; + } + + # used to activate or deactivate features that require a license + public function getLicenseScope(array $urlinfo) + { + $licensedata = $this->getLicenseFile(); + if(!$licensedata) + { + return false; + } + + # this means that a check (and update or call to cache timer) will take place when visit system license + $licensecheck = $this->checkLicense($licensedata,$urlinfo); + if(!$licensecheck) + { + return false; + } + + return $this->plans[$licensedata['plan']]['scope']; + } + + # check the local licence file (like pem or pub) + public function checkLicense($licensedata, array $urlinfo, $forceUpdateCheck = NULL) + { + if(!isset( + $licensedata['license'], + $licensedata['email'], + $licensedata['domain'], + $licensedata['plan'], + $licensedata['payed_until'], + $licensedata['signature'] + )) + { + $this->message = Translations::translate('License data are incomplete'); + + return false; + } + + # check if license data are valid and not manipulated + $licenseStatus = $this->validateLicense($licensedata); + if($licenseStatus !== true) + { + $this->message = Translations::translate('The license data are invalid. ') . $this->message; + + return false; + } + + # check if website uses licensed domain + $licenseDomain = $this->checkLicenseDomain($licensedata['domain'], $urlinfo); + if(!$licenseDomain) + { + $this->message = Translations::translate('The website is running not under the domain of your license.'); + + return false; + } + + # check if subscription period is paid + $subscriptionPaid = $this->checkLicenseDate($licensedata['payed_until']); + if(!$subscriptionPaid) + { + $storage = new StorageWrapper('\Typemill\Models\Storage'); + if(!$forceUpdateCheck && !$storage->timeoutIsOver('licenseupdate', 3600)) + { + $this->message = Translations::translate('The subscription period has not been paid yet. We will check it every 60 minutes.') . $this->message; + + return false; + } + + $update = $this->updateLicense($licensedata); + if(!$update) + { + $this->message = Translations::translate('The subscription period has not been paid yet and we got an error. ') . $this->message; + + return false; + } + } + + return true; + } + + private function checkLicenseDate(string $payed_until) + { + # check here if payed until is in past + $nextBillDate = new \DateTime($payed_until); + $currentDate = new \DateTime(); + + if($nextBillDate > $currentDate) + { + return true; + } + + return false; + } + + # NOT IN USE ANYMORE: Check licese domain deeply with subdomains + private function checkLicenseDomainDeep(string $licensedomain, array $urlinfo) + { + $licensehost = parse_url($licensedomain, PHP_URL_HOST); + $licensehost = str_replace("www.", "", $licensehost); + + $thishost = parse_url($urlinfo['baseurl'], PHP_URL_HOST); + $thishost = str_replace("www.", "", $thishost); + + $whitelist = ['localhost', '127.0.0.1', 'typemilltest.', 'try.', $licensehost]; + + foreach($whitelist as $domain) + { + if(substr($thishost, 0, strlen($domain)) == $domain) + { + return true; + } + } + + return false; + } + + # Compare only domain without subdomains or subfolders + private function checkLicenseDomain(string $licensedomain, array $urlinfo) + { + $licensehost = parse_url($licensedomain, PHP_URL_HOST); + $licensehost = preg_replace('/^www\./', '', $licensehost); + $licensehost = $this->extractBaseDomain($licensehost); + + $thishost = parse_url($urlinfo['baseurl'], PHP_URL_HOST); + $thishost = preg_replace('/^www\./', '', $thishost); + $thishost = $this->extractBaseDomain($thishost); + + $whitelist = ['localhost', '127.0.0.1', 'typemilltest.', 'try.', $licensehost]; + + foreach($whitelist as $domain) + { + if ($thishost === $domain) + { + return true; + } + } + + return false; + } + + # Function to extract the base domain (ignoring subdomains) + private function extractBaseDomain($host) + { + $parts = explode('.', $host); + $numParts = count($parts); + + # Check if we have at least 2 parts for a valid base domain + if ($numParts >= 2) + { + return $parts[$numParts - 2] . '.' . $parts[$numParts - 1]; + } + + # Return original host if it's not a valid domain + return $host; + } + + public function checkIfTest(array $urlinfo) + { + $thishost = parse_url($urlinfo['baseurl'], PHP_URL_HOST); + $thishost = str_replace("www.", "", $thishost); // Remove "www." + + if ($thishost === 'localhost' || $thishost === '127.0.0.1') + { + return true; + } + + $subdomains = ['typemilltest.', 'try.']; + foreach ($subdomains as $subdomain) + { + if (strpos($thishost, $subdomain) === 0) + { + return true; + } + } + + return false; + } + + private function validateLicense($data) + { + # if openssl-extension is missing, check the license once a day remotely on license server + if(!extension_loaded('openssl')) + { + $storage = new StorageWrapper('\Typemill\Models\Storage'); + if($storage->timeoutIsOver('licensecheck', 86400)) + { + $readableMail = trim($data['email']); + + $licensedata = [ + 'license' => $data['license'], + 'email' => $this->hashMail($readableMail), + 'domain' => $data['domain'], + 'signature' => $data['signature'], + 'plan' => $data['plan'], + 'payed_until' => $data['payed_until'] + ]; + + # make remote check on the license server + $url = 'https://service.typemill.net/api/v1/licensecheck'; + $remoteCheck = $this->callLicenseServer($licensedata, $url); + + if(isset($remoteCheck['status']) && $remoteCheck['status']) + { + $key = md5($data['domain']); + $storage->writeFile('cacheFolder', '', 'lstatus.txt', $key); + + return true; + } + + $key = md5($this->getMessage()); + $storage->writeFile('cacheFolder', '', 'lstatus.txt', $key); + + $this->message = Translations::translate('We will check it again in 24 hours.'); + + return false; + } + + $key = $storage->getFile('cacheFolder', '', 'lstatus.txt'); + if($key == md5($data['domain'])) + { + return true; + } + + $this->message = Translations::translate('We will check it again in 24 hours.'); + + return false; + } + else + { + $licensedata = [ + 'email' => $this->hashMail($data['email']), + 'domain' => $data['domain'], + 'license' => $data['license'], + 'plan' => $data['plan'], + 'payed_until' => $data['payed_until'] + ]; + + ksort($licensedata); + + # test manipulate data + # $licensedata['plan'] = 'wrong'; + + # Check signature + $public_key_pem = $this->getPublicKeyPem(); + + if(!$public_key_pem) + { + $this->message = Translations::translate('We could not find or read the public_key.pem in the settings-folder.'); + + return false; + } + + $binary_signature = base64_decode($data['signature']); + + $licensedata = json_encode($licensedata); + + $verified = openssl_verify($licensedata, $binary_signature, $public_key_pem, OPENSSL_ALGO_SHA256); + + if ($verified == 1) + { + return true; + } + elseif ($verified == 0) + { + $this->message = Translations::translate('License validation failed'); + + return false; + } + else + { + $this->message = Translations::translate('There was an error checking the license signature'); + + return false; + } + } + } + + + # THE FOLLOWING METHODS INTERACT WITH LICENSE SERVER + + public function testLicenseCall() + { + # make the call to the license server + $url = 'https://service.typemill.net/api/v1/testcall'; + $testcall = $this->callLicenseServer(['test' => 'test'], $url); + + if(!$testcall) + { + return false; + } + + return true; + } + + public function activateLicense($params) + { + # prepare data for call to licence server + $readableMail = trim($params['email']); + + $licensedata = [ + 'license' => $params['license'], + 'email' => $this->hashMail($readableMail), + 'domain' => $params['domain'] + ]; + + # make the call to the license server + $url = 'https://service.typemill.net/api/v1/activate'; + $signedLicense = $this->callLicenseServer($licensedata, $url); + + if(!$signedLicense) + { + return false; + } + + $signedLicense['license']['email'] = $readableMail; + $storage = new StorageWrapper('\Typemill\Models\Storage'); + + $result = $storage->updateYaml('settingsFolder', '', 'license.yaml', $signedLicense['license']); + + if(!$result) + { + $this->message = $storage->getError(); + return false; + } + + return true; + } + + # if license not valid anymore, check server for update + private function updateLicense($data) + { + $readableMail = trim($data['email']); + + $licensedata = [ + 'license' => $data['license'], + 'email' => $this->hashMail($readableMail), + 'domain' => $data['domain'], + 'signature' => $data['signature'], + 'plan' => $data['plan'], + 'payed_until' => $data['payed_until'] + ]; + + # make the call to the license server + $url = 'https://service.typemill.net/api/v1/update'; + $signedLicense = $this->callLicenseServer($licensedata, $url); + + if(!$signedLicense) + { + return false; + } + + $signedLicense['license']['email'] = $readableMail; + $storage = new StorageWrapper('\Typemill\Models\Storage'); + + $result = $storage->updateYaml('settingsFolder', '', 'license.yaml', $signedLicense['license']); + + if(!$result) + { + $this->message = 'We could not store the updated license: ' . $storage->getError(); + + return false; + } + + return true; + } + + # get the local token to make calls to premium services + public function getToken($refresh = false) + { + $storage = new StorageWrapper('\Typemill\Models\Storage'); + + # if not force a token refresh, use the stored token if not valid + if(!$refresh) + { + $tokenfile = $storage->getFile('settingsFolder', '', 'token.txt'); + + if($tokenfile) + { + # check if still valid + $filepath = $storage->getFolderPath('settingsFolder') . 'token.txt'; + $filetime = filemtime($filepath); + $oneHourAgo = time() - 3600; + + if ($filetime > $oneHourAgo) + { + return $tokenfile; + } + } + } + + # if we try to get a fresh token, make sure old token is deleted + $storage->deleteFile('settingsFolder', '', 'token.txt'); + + $licensefile = $this->getLicenseFile(); + if(!$licensefile) + { + $this->message = Translations::translate('Please check if there is a readable file license.yaml in your settings folder.'); + + return false; + } + + $readableMail = trim($licensefile['email']); + + $licensedata = [ + 'license' => $licensefile['license'], + 'email' => $this->hashMail($readableMail), + 'domain' => $licensefile['domain'], + 'signature' => $licensefile['signature'], + 'plan' => $licensefile['plan'], + 'payed_until' => $licensefile['payed_until'] + ]; + + $url = 'https://service.typemill.net/api/v1/gettoken'; + + $tokenresponse = $this->callLicenseServer($licensedata, $url); + + if($tokenresponse && isset($tokenresponse['token']) && $tokenresponse['token']) + { + $storage->writeFile('settingsFolder', '', 'token.txt', $tokenresponse['token']); + + return $tokenresponse['token']; + } + + return false; + } + + private function callLicenseServer( $licensedata, $url ) + { + $authstring = $this->getPublicKeyPem(); + + if(!$authstring) + { + $this->message = Translations::translate('Please check if there is a readable file public_key.pem in your settings folder.'); + + return false; + } + + $authstring = hash('sha256', substr($authstring, 0, 50)); + + $postdata = http_build_query($licensedata); + + if(in_array('curl', get_loaded_extensions())) + { + $curl = curl_init($url); + if (defined('CURLSSLOPT_NATIVE_CA') && version_compare(curl_version()['version'], '7.71', '>=')) + { + curl_setopt($curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA); + } + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_POST, true); + curl_setopt($curl, CURLOPT_POSTFIELDS, $postdata); + curl_setopt($curl, CURLOPT_HTTPHEADER, array( + "Content-Type: application/x-www-form-urlencoded", + "Accept: application/json", + "Authorization: $authstring", + "Connection: close" + )); + + $response = curl_exec($curl); + + if (curl_errno($curl)) + { + $this->message = Translations::translate('We got a curl error: ') . curl_error($curl); + + return false; + } + + curl_close($curl); + } + else + { + $options = array ( + 'http' => array ( + 'method' => 'POST', + 'ignore_errors' => true, + 'header' => "Content-Type: application/x-www-form-urlencoded\r\n" . + "Accept: application/json\r\n" . + "Authorization: $authstring\r\n" . + "Connection: close\r\n", + 'content' => $postdata + ) + ); + + $context = stream_context_create($options); + + $response = file_get_contents($url, false, $context); + + if ($response === FALSE) + { + if (!empty($http_response_header)) + { + list($version, $status_code, $msg) = explode(' ', $http_response_header[0], 3); + + $this->message = Translations::translate('We got an error from file_get_contents: ') . $status_code . ' ' . $msg; + } + else + { + $this->message = Translations::translate('No HTTP response received or file_get_contents is blocked.'); + } + + return false; + } + } + + $responseJson = json_decode($response,true); + + if(isset($responseJson['code'])) + { + $this->message = $responseJson['code']; + + return false; + } + + return $responseJson; + } + + public function hashMail(string $mail) + { + return hash('sha256', trim($mail) . 'TYla5xa8JUur'); + } + + public function getPublicKeyPem() + { + $pkeyfile = getcwd() . DIRECTORY_SEPARATOR . 'settings' . DIRECTORY_SEPARATOR . "public_key.pem"; + + if(file_exists($pkeyfile) && is_readable($pkeyfile)) + { + # fetch public key from file and ready it + $fp = fopen($pkeyfile, "r"); + $public_key_pem = fread($fp, 8192); + fclose($fp); + + return $public_key_pem; + } + + return false; + } +} \ No newline at end of file diff --git a/system/typemill/Models/Media.php b/system/typemill/Models/Media.php new file mode 100644 index 0000000..80dd2b6 --- /dev/null +++ b/system/typemill/Models/Media.php @@ -0,0 +1,678 @@ + true, 'jpg' => true, 'jpeg' => true, 'webp' => true, 'gif' => true]; + + protected $animated = false; + + protected $resizable = true; + + protected $sizes = []; + + public function __construct() + { + ini_set('memory_limit', '512M'); + + $this->basepath = getcwd() . DIRECTORY_SEPARATOR; + + $this->tmpFolder = $this->basepath . 'media' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR; + } + + public function clearTempFolder($immediate = NULL) + { + $files = scandir($this->tmpFolder); + $now = time(); + $result = true; + + foreach($files as $file) + { + if (!in_array($file, array(".",".."))) + { + $filelink = $this->tmpFolder . $file; + if(file_exists($filelink)) + { + $filetime = filemtime($filelink); + $delete = $immediate ? $immediate : ($now - $filetime > 1800); + + if($delete) + { + if(!unlink($filelink)) + { + $result = false; + } + } + } + } + } + + return $result; + } + + # set the pathinfo (name and extension) and slugify a unique name if option to overwrite existing files is false + public function setPathInfo(string $name) + { + $pathinfo = pathinfo($name); + if(!$pathinfo) + { + $this->errors[] = Translations::translate('Could not read pathinfo') . '.'; + + return false; + } + + $this->extension = isset($pathinfo['extension']) ? strtolower($pathinfo['extension']) : false; + $this->filename = Slug::createSlug($pathinfo['filename']); + + if(!$this->extension OR !$this->filename) + { + $this->errors[] = Translations::translate('Extension or filename are missing') . '.'; + + return false; + } + + return true; + } + + public function decode(string $file) + { + $fileParts = explode(";base64,", $file); + + if(!isset($fileParts[0]) OR !isset($fileParts[1])) + { + $this->errors[] = Translations::translate('Could not decode image or file, probably not a base64 encoding') . '.'; + + return false; + } + + $type = explode("/", $fileParts[0]); + $this->filetype = strtolower($type[1]); + $this->filedata = base64_decode($fileParts[1]); + + return true; + } + + public function getExtension() + { + return $this->extension; + } + + public function getFiletype() + { + return $this->filetype; + } + + public function getFilename() + { + return $this->filename; + } + + public function setFilename($filename) + { + $this->filename = $filename; + } + + public function getFullName() + { + return $this->filename . '.' . $this->extension; + } + + public function getFiledata() + { + return $this->filedata; + } + + public function getFullPath() + { + return $this->tmpFolder . $this->filename . '.' . $this->extension; + } + + public function formatSizeUnits($bytes) + { + if ($bytes >= 1073741824) + { + $bytes = number_format($bytes / 1073741824, 2) . ' GB'; + } + elseif ($bytes >= 1048576) + { + $bytes = number_format($bytes / 1048576, 2) . ' MB'; + } + elseif ($bytes >= 1024) + { + $bytes = number_format($bytes / 1024, 2) . ' KB'; + } + elseif ($bytes > 1) + { + $bytes = $bytes . ' bytes'; + } + elseif ($bytes == 1) + { + $bytes = $bytes . ' byte'; + } + else + { + $bytes = '0 bytes'; + } + + return $bytes; + } + + public function is_dir_empty($dir) + { + return (count(scandir($dir)) == 2); + } + + + ############################# + # FILE HANDLING # + ############################# + + public function storeFile($file, $name) + { + $this->clearTempFolder(); + + $this->setPathInfo($name); + + $this->decode($file); + + if($this->extension == "svg") + { + $svg = new SvgSanitizer(); + + $loaded = $svg->loadSVG($this->filedata); + if($loaded === false) + { + $this->errors[] = Translations::translate('We could not load the svg file, it is probably corrupted.'); + return false; + } + + $svg->sanitize(); + $sanitized = $svg->saveSVG(); + if($sanitized === false) + { + $this->errors[] = Translations::translate('We could not create a sanitized version of the svg, it probably has invalid content.'); + return false; + } + + $this->filedata = $sanitized; + } + + + $fullpath = $this->getFullPath(); + + if($this->filedata !== false && file_put_contents($fullpath, $this->filedata)) + { + $size = filesize($this->getFullPath()); + $size = $this->formatSizeUnits($size); + + $title = str_replace('-', ' ', $this->filename); + $title = $title . ' (' . strtoupper($this->extension) . ', ' . $size .')'; + + return [ + 'title' => $title, + 'name' => $this->filename, + 'extension' => $this->extension, + 'size' => $size, + 'url' => 'media/files/' . $this->getFullName() + ]; + } + + return false; + } + + + ############################# + # IMAGE HANDLING # + ############################# + + public function prepareImage($image, $name) + { + # change clear tmp folder and delete only old ones + $this->clearTempFolder(); + #$this->checkFolders('image'); + $this->decode($image); + $this->setPathInfo($name); + $this->checkAllowedExtension(); + + if(empty($this->errors)) + { + return true; + } + + return false; + } + + public function storeOriginalToTmp() + { + # $this->saveName(); + $this->saveOriginal(); + + if(empty($this->errors)) + { + return true; + } + + return false; + } + + public function storeRenditionsToTmp($sizes) + { + # transform image-stream into image + $image = $this->createImage(); + + $originalsize = $this->getImageSize($image); + + foreach($sizes as $destinationfolder => $desiredsize) + { + $desiredsize = $this->calculateSize($originalsize, $desiredsize); + + $resizedImage = $this->resizeImage($image, $desiredsize, $originalsize); + + $this->saveResizedImage($resizedImage, $destinationfolder, $this->extension); + + imagedestroy($resizedImage); + } + + imagedestroy($image); + + if(empty($this->errors)) + { + return true; + } + + return false; + } + + # add an allowed image extension like svg + public function addAllowedExtension(string $extension) + { + $this->allowedExtensions[$extension] = true; + } + + # force an image type like webp + public function setExtension(string $extension) + { + $this->extension = $extension; + } + + public function checkAllowedExtension() + { + if(!isset($this->allowedExtensions[$this->extension])) + { + $this->errors[] = Translations::translate('Images with this extension are not allowed.'); + + return false; + } + + if($this->extension == "svg") + { + $svg = new SvgSanitizer(); + + $loaded = $svg->loadSVG($this->filedata); + if($loaded === false) + { + $this->errors[] = Translations::translate('We could not load the svg file, it is probably corrupted.'); + return false; + } + + $svg->sanitize(); + $sanitized = $svg->saveSVG(); + if($sanitized === false) + { + $this->errors[] = Translations::translate('We could not create a sanitized version of the svg, it probably has invalid content.'); + return false; + } + + $this->filedata = $sanitized; + } + + return true; + } + + # check if image should not be resized (animated gif and svg) + public function isResizable() + { + if($this->filetype == 'gif' && $this->detectAnimatedGif()) + { + $this->resizable = false; + } + + if($this->filetype == 'svg+xml') + { + $this->resizable = false; + } + + return $this->resizable; + } + + public function detectAnimatedGif() + { + $is_animated = preg_match('#(\x00\x21\xF9\x04.{4}\x00\x2C.*){2,}#s', $this->filedata); + if ($is_animated == 1) + { + $this->animated = true; + } + + return $this->animated; + } + + # save the original image to temp folder + public function saveOriginal($destinationfolder = 'ORIGINAL') + { + $path = $this->tmpFolder . $destinationfolder . '+' . $this->filename . '.' . $this->extension; + + $result = file_put_contents($path, $this->filedata); + if($result === false) + { + $this->errors[] = Translations::translate('could not store the image in the temporary folder'); + } + } + + # save the original image for all sizes/folders + public function saveOriginalForAll() + { + $this->saveOriginal('LIVE'); + $this->saveOriginal('THUMBS'); + + if(empty($this->errors)) + { + return true; + } + return false; + } + + public function createImage() + { + return imagecreatefromstring($this->filedata); + } + + public function getImageSize($image) + { + return ['width' => imagesx($image), 'height' => imagesy($image)]; + } + + public function calculateSize(array $originalsize, array $desiredsize) + { + # if desired size is bigger than the actual image, then drop the desired sizes and use the actual image size instead + if($desiredsize['width'] > $originalsize['width']) + { + return $originalsize; + } + + if(!isset($desiredsize['height'])) + { + $resizeFactor = $originalsize['width'] / $desiredsize['width']; + $desiredsize['height'] = round( ($originalsize['height'] / $resizeFactor), 0); + } + + return $desiredsize; + } + + public function resizeImage($image, array $desired, array $original) + { + # resize + $ratio = max($desired['width']/$original['width'], $desired['height']/$original['height']); + $h = $desired['height'] / $ratio; + $x = ($original['width'] - $desired['width'] / $ratio) / 2; + $y = ($original['height'] - $desired['height'] / $ratio) / 2; + $w = $desired['width'] / $ratio; + + $resizedImage = imagecreatetruecolor($desired['width'], $desired['height']); + + # preserve transparency + if($this->extension == "gif" or $this->extension == "png" or $this->extension == "webp") + { + imagecolortransparent($resizedImage, imagecolorallocatealpha($resizedImage, 0, 0, 0, 127)); + imagealphablending($resizedImage, false); + imagesavealpha($resizedImage, true); + } + + imagecopyresampled($resizedImage, $image, 0, 0, intval($x), intval($y), $desired['width'], $desired['height'], intval($w), intval($h)); + + return $resizedImage; + } + + public function saveResizedImage($resizedImage, string $destinationfolder, string $extension) + { + # use method in storage class??? + $destinationfolder = strtoupper($destinationfolder); + + switch($extension) + { + case "png": + $storedImage = imagepng( $resizedImage, $this->tmpFolder . $destinationfolder . '+' . $this->filename . '.png', 9 ); + break; + case "gif": + $storedImage = imagegif( $resizedImage, $this->tmpFolder . $destinationfolder . '+' . $this->filename . '.gif' ); + break; + case "webp": + $storedImage = imagewebp( $resizedImage, $this->tmpFolder . $destinationfolder . '+' . $this->filename . '.webp', 80); + break; + case "jpg": + case "jpeg": + $storedImage = imagejpeg( $resizedImage, $this->tmpFolder . $destinationfolder . '+' . $this->filename . '.' . $extension, 80); + break; + default: + $storedImage = false; + } + + if(!$storedImage) + { + $failedImage = $this->tmpFolder . $destinationfolder . '+' . $this->filename . '.' . $extension; + + $this->errors[] = Translations::translate('Could not store the resized version') . ' ' . $failedImage; + + return false; + } + + return true; + } + + public function createCustomSize($imageUrl, $width = NULL, $height = NULL, $forcename = NULL) + { + $this->setPathInfo($imageUrl); + + $resizeName = '-'; + if(is_int($width) && $width < 10000) + { + $resizeName .= $width; + $desiredSize['width'] = $width; + } + $resizeName .= 'x'; + if(is_int($height) && $height < 10000) + { + $resizeName .= $height; + $desiredSize['height'] = $height; + } + + $extension = $this->getExtension(); + $origExtension = $extension; + $originalName = $this->getFilename(); + $originalFile = $originalName . '.' . $extension; + $customName = $forcename ? $forcename . $resizeName : $originalName . $resizeName; + $customFile = $customName . '.' . $extension; + + $storage = new StorageWrapper('\Typemill\Models\Storage'); + + if(!$forcename && $storage->checkFile('customFolder', '', $customFile)) + { + # we should get the custom folder url dynamically from storage class + return '/media/custom/' . $customFile; + } + # if name is in customfolder (resized already) + if(!$forcename && $storage->checkFile('customFolder', '', $originalFile)) + { + $imagePath = $storage->getFolderPath('customFolder') . $originalFile; + } + # or in originalfolder (not resized yet) + elseif($storage->checkFile('originalFolder', '', $originalFile)) + { + $imagePath = $storage->getFolderPath('originalFolder') . $originalFile; + } + elseif($origExtension = $this->findImageWithName($originalName, $storage)) + { + $originalFile = $originalName . '.' . $origExtension; + $imagePath = $storage->getFolderPath('originalFolder') . $originalFile; + } + else + { + return 'image not found'; + } + + $image = $this->createImageFromPath($imagePath, $origExtension); + $originalSize = $this->getImageSize($image); + $resizedImage = $this->resizeImage($image, $desiredSize, $originalSize); + + if($resizedImage && $storage->storeCustomImage($resizedImage, $extension, $customName)) + { + return '/media/custom/' . $customFile; + } + + return 'error resizing image'; + } + + private function findImageWithName($originalName, $storage) + { + foreach($this->allowedExtensions as $extension => $bool) + { + $filename = $originalName . '.' . $extension; + if($storage->checkFile('originalFolder', '', $filename)) + { + return $extension; + } + } + return false; + } + + public function createGrayscale($imageUrl) + { + $this->setPathInfo($imageUrl); + + $extension = $this->getExtension(); + $originalName = $this->getFilename(); + $originalFile = $originalName . '.' . $extension; + $customName = $originalName . '-grayscale'; + $customFile = $customName . '.' . $extension; + + $storage = new StorageWrapper('\Typemill\Models\Storage'); + + # if the grayscaled image is there already + if($storage->checkFile('customFolder', '', $customFile)) + { + # we should get the custom folder url dynamically from storage class + return '/media/custom/' . $customFile; + } + + # if name is in customfolder (resized already) + if($storage->checkFile('customFolder', '', $originalFile)) + { + $imagePath = $storage->getFolderPath('customFolder') . $originalFile; + } + # or in originalfolder (not resized yet) + elseif($storage->checkFile('originalFolder', '', $originalFile)) + { + $imagePath = $storage->getFolderPath('originalFolder') . $originalFile; + } + else + { + return 'image not found'; + } + + $image = $this->createImageFromPath($imagePath, $extension); + imagefilter($image, IMG_FILTER_GRAYSCALE); + + if($storage->storeCustomImage($image, $extension, $customName)) + { + return '/media/custom/' . $customFile; + } + + return 'error grayscaling image'; + } + + public function createImageFromPath($imagePath, $extension) + { + switch($extension) + { + case 'gif': $image = imagecreatefromgif($imagePath); break; + case 'jpg' : + case 'jpeg': $image = imagecreatefromjpeg($imagePath); break; + case 'png': $image = imagecreatefrompng($imagePath); break; + case 'webp': $image = imagecreatefromwebp($imagePath); break; + default: return 'image type not supported'; + } + + return $image; + } + + + + + + + + + + + + # REFACTOR IF NEEDED + + public function findPagesWithUrl($structure, $url, $result) + { + foreach ($structure as $key => $item) + { + if($item->elementType == 'folder') + { + $result = $this->findPagesWithUrl($item->folderContent, $url, $result); + } + else + { + $live = getcwd() . DIRECTORY_SEPARATOR . 'content' . $item->pathWithoutType . '.md'; + $draft = getcwd() . DIRECTORY_SEPARATOR . 'content' . $item->pathWithoutType . '.txt'; + + # check live first + if(file_exists($live)) + { + $content = file_get_contents($live); + + if (stripos($content, $url) !== false) + { + $result[] = $item->urlRelWoF; + } + # if not in live, check in draft + elseif(file_exists($draft)) + { + $content = file_get_contents($draft); + + if (stripos($content, $url) !== false) + { + $result[] = $item->urlRelWoF; + } + } + } + } + } + return $result; + } + +} \ No newline at end of file diff --git a/system/typemill/Models/Meta.php b/system/typemill/Models/Meta.php new file mode 100644 index 0000000..ceb9300 --- /dev/null +++ b/system/typemill/Models/Meta.php @@ -0,0 +1,273 @@ +storage = new StorageWrapper('\Typemill\Models\Storage'); + } + + # used by contentApiController (backend) and pageController (frontend) and TwigMetaExtension (list pages) + public function getMetaData($item) + { + $metadata = $this->storage->getYaml('contentFolder', '', $item->pathWithoutType . '.yaml'); + + return $metadata; + } + + public function getMetaDefinitions($settings, $folder) + { + $metadefinitions = $this->storage->getYaml('systemSettings', '', 'metatabs.yaml'); + $settingsModel = new Settings(); + + # loop through all plugins + if(!empty($settings['plugins'])) + { + foreach($settings['plugins'] as $name => $plugin) + { + if($plugin['active']) + { + $pluginSettings = $settingsModel->getObjectSettings('pluginsFolder', $name); + if($pluginSettings && isset($pluginSettings['metatabs'])) + { + $metadefinitions = array_merge_recursive($metadefinitions, $pluginSettings['metatabs']); + } + } + } + } + + # add the meta from theme settings here + $themeSettings = $settingsModel->getObjectSettings('themesFolder', $settings['theme']); + + if($themeSettings && isset($themeSettings['metatabs'])) + { + $metadefinitions = array_merge_recursive($metadefinitions, $themeSettings['metatabs']); + } + + # conditional fieldset for user or role based access + if(!isset($settings['pageaccess']) || $settings['pageaccess'] === NULL ) + { + unset($metadefinitions['meta']['fields']['fieldsetrights']); + } + + # conditional fieldset for folders + if(!$folder) + { + unset($metadefinitions['meta']['fields']['fieldsetfolder']); + } + + # dispatch meta +# $metatabs = $this->c->dispatcher->dispatch('onMetaDefinitionsLoaded', new OnMetaDefinitionsLoaded($metatabs))->getData(); + + return $metadefinitions; + } + + # used if new articel/post is created + public function createInitialMeta(string $username, string $navtitle) + { + $author = $username; + $user = new User(); + if($user->setUser($username)) + { + $fullname = $user->getFullName(); + if($fullname) + { + $author = $fullname; + } + } + + $meta = []; + + $meta['meta'] = []; + + $meta['meta']['owner'] = $username; + + $meta['meta']['author'] = $author; + + $meta['meta']['created'] = date("Y-m-d"); + + $meta['meta']['time'] = date("H-i-s"); + + $meta['meta']['navtitle'] = $navtitle; + + return $meta; + } + + # used to fill meta data for existing page + public function addMetaDefaults($meta, $item, $authorFromSettings, $currentuser = false) + { + $modified = false; + + if(!is_array($meta)) + { + $meta = []; + } + if(!isset($meta['meta']) OR !is_array($meta['meta'])) + { + $meta['meta'] = []; + } + + if(!isset($meta['meta']['owner']) OR !$meta['meta']['owner']) + { + if($currentuser) + { + $meta['meta']['owner'] = $currentuser; + $modified = true; + } + } + + if(!isset($meta['meta']['author'])) + { + $author = $authorFromSettings; + + if($currentuser) + { + $user = new User(); + if($user->setUser($currentuser)) + { + $fullname = $user->getFullName(); + if($fullname) + { + $author = $fullname; + } + } + } + + $meta['meta']['author'] = $author; + $modified = true; + } + + if(!isset($meta['meta']['created'])) + { + $meta['meta']['created'] = date("Y-m-d"); + $modified = true; + } + + if(!isset($meta['meta']['time'])) + { + $meta['meta']['time'] = date("H-i-s"); + $modified = true; + } + + if(!isset($meta['meta']['navtitle'])) + { + $meta['meta']['navtitle'] = $item->name; + $modified = true; + } + + if($modified) + { + $this->updateMeta($meta, $item); + } + + $filePath = $item->path; + if($item->elementType == 'folder') + { + $filePath = $item->path . DIRECTORY_SEPARATOR . 'index.md'; + } + $meta['meta']['modified'] = $this->storage->getFileTime('contentFolder', '', $filePath); + + return $meta; + } + + public function addMetaTitleDescription(array $meta, $item, array $markdown) + { + $title = (isset($meta['meta']['title']) && $meta['meta']['title'] != '') ? $meta['meta']['title'] : false; + $description = (isset($meta['meta']['description']) && $meta['meta']['description'] != '') ? $meta['meta']['description'] : false; + + if(!$title OR !$description) + { + $content = new Content(); + + if(!$title) + { + $meta['meta']['title'] = $content->getTitle($markdown); + } + + if(!$description) + { + $meta['meta']['description'] = $content->getDescription($markdown); + } + + $this->updateMeta($meta, $item); + } + + return $meta; + } + + public function updateMeta($meta, $item) + { + $filename = $item->pathWithoutType . '.yaml'; + + if($this->storage->updateYaml('contentFolder', '', $filename, $meta)) + { + return true; + } + + return $this->storage->getError(); + } + + public function folderContainsFolders($folder) + { + foreach($folder->folderContent as $page) + { + if($page->elementType == 'folder') + { + return true; + } + } + + return false; + } + + public function renamePost($oldPathWithoutType,$newPathWithoutType) + { + $filetypes = [ + 'txt' => true, + 'md' => true, + 'yaml' => true + ]; + + foreach($filetypes as $filetype => $result) + { + if(!$this->storage->renameFile('contentFolder', '', $oldPathWithoutType . '.' . $filetype, $newPathWithoutType . '.' . $filetype)) + { + $filetypes[$filetype] = $this->storage->getError(); + } + } + + return $filetypes; + } + + # just route it to storageWrapper because wrapper is initialized here and we dont want to initialize it in controllers + public function transformPostsToPages($folder) + { + if($this->storage->transformPostsToPages($folder)) + { + return true; + } + +# return $this->storage->getError(); + return false; + } + + public function transformPagesToPosts($folder) + { + if($this->storage->transformPagesToPosts($folder)) + { + return true; + } + +# return $this->storage->getError(); + return false; + } +} \ No newline at end of file diff --git a/system/typemill/Models/Navigation.php b/system/typemill/Models/Navigation.php new file mode 100644 index 0000000..4652abc --- /dev/null +++ b/system/typemill/Models/Navigation.php @@ -0,0 +1,924 @@ +storage = new StorageWrapper('\Typemill\Models\Storage'); + + $this->naviFolder = 'navigation'; + + $this->draftNaviName = 'draft-navi'; + + $this->DS = DIRECTORY_SEPARATOR; + } + + public function getMainNavigation($userrole, $acl, $urlinfo, $editor) + { + $mainnavi = $this->storage->getYaml('systemSettings', '', 'mainnavi.yaml'); + + $allowedmainnavi = []; + + $activeitem = false; + + foreach($mainnavi as $name => $naviitem) + { + if($acl->isAllowed($userrole, $naviitem['aclresource'], $naviitem['aclprivilege'])) + { + # set the navi of current route active + $thisRoute = '/tm/' . $name; + + if(strpos($urlinfo['route'], $thisRoute) !== false) + { + $naviitem['active'] = true; + $activeitem = true; + } + + $allowedmainnavi[$name] = $naviitem; + } + } + + # if system is there, then we do not need the account item + if(isset($allowedmainnavi['system'])) + { + unset($allowedmainnavi['account']); + + # if no active item has been found, then it is submenu under system + if(!$activeitem) + { + $allowedmainnavi['system']['active'] = true; + } + } + + # set correct editor mode according to user settings + if(isset($allowedmainnavi['content']) && $editor == 'raw') + { + $allowedmainnavi['content']['routename'] = "content.raw"; + } + + return $allowedmainnavi; + } + + public function getSystemNavigation($userrole, $acl, $urlinfo, $dispatcher, $routeparser) + { + $systemnavi = $this->storage->getYaml('systemSettings', '', 'systemnavi.yaml'); + $systemnavi = $dispatcher->dispatch(new OnSystemnaviLoaded($systemnavi), 'onSystemnaviLoaded')->getData(); + + $allowedsystemnavi = []; + + $route = trim($urlinfo['route'], '/'); + + foreach($systemnavi as $name => $naviitem) + { + $naviitem['url'] = $routeparser->urlFor($naviitem['routename']); + $itemurl = trim($naviitem['url'], '/'); + + if(strpos( $itemurl, $route ) !== false) + { + $naviitem['active'] = true; + } + + if($acl->isAllowed($userrole, $naviitem['aclresource'], $naviitem['aclprivilege'])) + { + $allowedsystemnavi[$name] = $naviitem; + } + } + + return $allowedsystemnavi; + } + + + # use array ['extended' => true, 'draft' => true, 'live' => true] to clear files + public function clearNavigation($deleteItems = NULL) + { + $result = false; + + $dataPath = $this->storage->getFolderPath('dataFolder'); + $naviPath = $dataPath . DIRECTORY_SEPARATOR . $this->naviFolder; + $naviFiles = scandir($naviPath); + + if($deleteItems) + { + # replace the placeholder '/' for a base-item with the cached base navigation + foreach ($deleteItems as &$value) + { + if ($value === '/') + { + $value = $this->draftNaviName; + } + else + { + $value .= '.txt'; + } + } + + $naviFiles = array_intersect($naviFiles, $deleteItems); + } + + foreach($naviFiles as $naviFile) + { + if (!in_array($naviFile, array(".","..")) && substr($naviFile, 0, 1) != '.') + { + $result = $this->storage->deleteFile('dataFolder', $this->naviFolder, $naviFile); + } + } + + return $result; + } + + public function getItemsForSlug($slug, $urlinfo, $langattr) + { + $draftNavigation = $this->getFullDraftNavigation($urlinfo, $langattr); + + if(!$draftNavigation) + { + return false; + } + + $items = $this->findItemsWithSlug($draftNavigation, $slug); + + return $items; + } + + public function getItemForUrl($url, $urlinfo, $langattr) + { + $url = $this->removeEditorFromUrl($url); + + if($url == '/') + { + return $this->getHomepageItem($urlinfo['baseurl']); + } + + $pageinfo = $this->getPageInfoForUrl($url, $urlinfo, $langattr); + + if(!$pageinfo) + { + return false; + } + + $foldername = $this->getNaviFileNameForPath($pageinfo['path']); + + $draftNavigation = $this->getFullDraftNavigation($urlinfo, $langattr, $foldername); + if(!$draftNavigation) + { + return false; + } + + $keyPathArray = explode(".", $pageinfo['keyPath']); + $item = $this->getItemWithKeyPath($draftNavigation, $keyPathArray); + + return $item; + } + + public function getPageInfoForUrl($url, $urlinfo, $langattr) + { + # fix for pages like /system/ + $url = '/' . trim($url, '/'); + + # get the first level navigation + $firstLevelExtended = $this->getExtendedNavigation($urlinfo, $langattr, '/'); + + $firstUrlSegment = $this->getFirstUrlSegment($url); + $firstUrlSegment = '/' . $firstUrlSegment; + + $pageinfo = $firstLevelExtended[$firstUrlSegment] ?? false; + + # first level does not exist + if(!$pageinfo) + { + return false; + } + + # url is first level + if($url == $firstUrlSegment) + { + return $pageinfo; + } + + $foldername = trim($pageinfo['path'], $this->DS); + + $extendedNavigation = $this->getExtendedNavigation($urlinfo, $langattr, $foldername); + + $pageinfo = $extendedNavigation[$url] ?? false; + if(!$pageinfo) + { + return false; + } + + return $pageinfo; + } + + private function removeEditorFromUrl($url) + { + $url = trim($url, '/'); + + $url = str_replace('tm/content/visual', '', $url); + $url = str_replace('tm/content/raw', '', $url); + + $url = trim($url, '/'); + + return '/' . $url; + } + + public function getFirstUrlSegment($url) + { + $segments = explode('/', $url); + + if(isset($segments[1])) + { + return $segments[1]; + } + + return ''; + } + + public function getNaviFileNameForPath($path) + { + $segments = explode($this->DS, $path); + + # navi-file-name for a base-folder is draftNaviName where first level items are cached. + if(isset($segments[2])) + { + return $segments[1]; + } + + return $this->draftNaviName; + } + + + + public function getLiveNavigation($urlinfo, $langattr) + { + $draftNavigation = $this->getFullDraftNavigation($urlinfo, $langattr); + + $liveNavigation = $this->generateLiveNavigationFromDraft($draftNavigation); + + $liveNavigation = $this->removeHiddenPages($liveNavigation); + + return $liveNavigation; + } + + # ASK FOR THE FULL DRAFT NAVIGATION AND MERGE ALL SEPARATED NAVIGATIONS + public function getFullDraftNavigation($urlinfo, $language, $userrole = null, $username = null) + { + # get first level + $draftNavigation = $this->getDraftNavigation($urlinfo, $language, '/'); + + foreach($draftNavigation as $key => $item) + { + if($item->elementType == 'folder') + { + $subfolder = $this->getDraftNavigation($urlinfo, $language, $item->originalName); + + $draftNavigation[$key]->folderContent = $subfolder[$key]->folderContent; + } + } + + return $draftNavigation; + } + + # ASK FOR A STATIC DRAFT NAVIGATION AND CREATE ONE IF NOT THERE + public function getDraftNavigation($urlinfo, $language, $foldername) + { + $draftFileName = $this->getDraftFileName($foldername); + $extendedFileName = $this->getExtendedFileName($foldername); + + $draftNavigation = $this->getDraftNavigationFile($draftFileName); + + if($draftNavigation) + { + return $draftNavigation; + } + + $rawDraftNavigation = $this->generateRawDraftNavigation($urlinfo, $language, $foldername); + if(!$rawDraftNavigation) + { + return false; + } + + $extendedNavigation = $this->getExtendedNavigationFile($extendedFileName); + if(!$extendedNavigation) + { + $extendedNavigation = $this->generateExtendedFromDraft($rawDraftNavigation); + + if(!$extendedNavigation) + { + return false; + } + + $this->storeStaticNavigation($extendedFileName, $extendedNavigation); + } + + $draftNavigation = $this->mergeExtendedWithDraft($rawDraftNavigation, $extendedNavigation); + if(!$draftNavigation) + { + return false; + } + + $this->storeStaticNavigation($draftFileName, $draftNavigation); + + return $draftNavigation; + } + + public function getExtendedNavigation($urlinfo, $language, $foldername) + { + $draftFileName = $this->getDraftFileName($foldername); + $extendedFileName = $this->getExtendedFileName($foldername); + + $extendedNavigation = $this->getExtendedNavigationFile($extendedFileName); + if($extendedNavigation) + { + return $extendedNavigation; + } + + $draftNavigation = $this->getDraftNavigationFile($draftFileName); + if(!$draftNavigation) + { + # we have to create and store extended and draft in this case + + $rawDraftNavigation = $this->generateRawDraftNavigation($urlinfo, $language, $foldername); + + if(!$rawDraftNavigation) + { + return false; + } + + $extendedNavigation = $this->generateExtendedFromDraft($rawDraftNavigation); + + if(!$extendedNavigation) + { + return false; + } + + $this->storeStaticNavigation($extendedFileName, $extendedNavigation); + + $draftNavigation = $this->mergeExtendedWithDraft($rawDraftNavigation, $extendedNavigation); + if(!$draftNavigation) + { + return false; + } + + $this->storeStaticNavigation($draftFileName, $draftNavigation); + + return $extendedNavigation; + } + + # we only have to create and store extended in this case + + $extendedNavigation = $this->generateExtendedFromDraft($draftNavigation); + + if(!$extendedNavigation) + { + return false; + } + + $this->storeStaticNavigation($extendedFileName, $extendedNavigation); + + return $extendedNavigation; + } + + public function generateLiveNavigationFromDraft($draftNavigation) + { + foreach($draftNavigation as $key => $item) + { + if($item->status == 'unpublished') + { + unset($draftNavigation[$key]); + } + else + { + if($item->status == 'modified') + { + $draftNavigation[$key]->fileType = 'md'; + $draftNavigation[$key]->path = $draftNavigation[$key]->pathWithoutType . '.md'; + } + + if(isset($item->folderContent) && $item->folderContent) + { + $item->folderContent = $this->generateLiveNavigationFromDraft($item->folderContent); + } + } + } + + return $draftNavigation; + } + + private function storeStaticNavigation($filename, $data) + { + if($filename == '.txt' OR $filename == '-extended.txt') + { + return false; + } + + if($this->storage->writeFile('dataFolder', $this->naviFolder, $filename, $data, 'serialize')) + { + return true; + } + + return false; + } + + # gets the cached draft navigation of a folder or of the first level + private function getDraftNavigationFile($filename) + { + $draftNavigation = $this->storage->getFile('dataFolder', $this->naviFolder, $filename, 'unserialize'); + + if($draftNavigation) + { + return $draftNavigation; + } + + return false; + } + + # generates a raw draft navigation + private function generateRawDraftNavigation($urlinfo, $language, $foldername = false) + { + # convert basefolder '/' to true + $flat = ($foldername == '/') ? true : $foldername; + + # scan the content of the folder + $draftContentTree = $this->scanFolder($this->storage->getFolderPath('contentFolder'), $flat); + + # if there is content, then get the content details + if(count($draftContentTree) > 0) + { + $draftNavigation = $this->getFolderContentDetails($draftContentTree, $language, $urlinfo['baseurl'], $urlinfo['basepath']); + + return $draftNavigation; + } + + return false; + } + + # get the extended Navigation file for a folder or base + private function getExtendedNavigationFile($filename) + { + $extendedNavigation = $this->storage->getFile('dataFolder', $this->naviFolder, $filename, 'unserialize'); + + if($extendedNavigation) + { + return $extendedNavigation; + } + + return false; + } + + # reads all meta-files and creates an array with url => ['hide' => bool, 'navtitle' => 'bla'] + private function generateExtendedFromDraft($navigation, $extended = NULL) + { + if(!$extended) + { + $extended = []; + } + + foreach ($navigation as $key => $item) + { + # $filename = ($item->elementType == 'folder') ? DIRECTORY_SEPARATOR . 'index.yaml' : $item->pathWithoutType . '.yaml'; + $filename = $item->pathWithoutType . '.yaml'; + + # read file + $meta = $this->storage->getYaml('contentFolder', '', $filename); + + if(!$meta) + { + # create initial yaml + $meta = []; + $meta['meta']['navtitle'] = $item->name; + + $this->storage->updateYaml('contentFolder', '', $filename, $meta); + } + + $extended[$item->urlRelWoF]['navtitle'] = isset($meta['meta']['navtitle']) ? $meta['meta']['navtitle'] : ''; + $extended[$item->urlRelWoF]['hide'] = isset($meta['meta']['hide']) ? $meta['meta']['hide'] : false; + $extended[$item->urlRelWoF]['noindex'] = isset($meta['meta']['noindex']) ? $meta['meta']['noindex'] : false; + $extended[$item->urlRelWoF]['path'] = $item->path; + $extended[$item->urlRelWoF]['keyPath'] = $item->keyPath; + + if ($item->elementType == 'folder') + { + $extended = $this->generateExtendedFromDraft($item->folderContent, $extended); + } + } + + return $extended; + } + + # takes a draft navigation and extended navigation and merges both + private function mergeExtendedWithDraft($draftNavigation, $extendedNavigation) + { + $mergedNavigation = []; + + foreach($draftNavigation as $key => $item) + { + if($extendedNavigation && isset($extendedNavigation[$item->urlRelWoF])) + { + $item->name = ($extendedNavigation[$item->urlRelWoF]['navtitle'] != '') ? $extendedNavigation[$item->urlRelWoF]['navtitle'] : $item->name; + $item->hide = ($extendedNavigation[$item->urlRelWoF]['hide'] === true) ? true : false; + $item->noindex = (isset($extendedNavigation[$item->urlRelWoF]['noindex']) && $extendedNavigation[$item->urlRelWoF]['noindex'] === true) ? true : false; + } + + if($item->elementType == 'folder') + { + $item->folderContent = $this->mergeExtendedWithDraft($item->folderContent, $extendedNavigation); + } + + $mergedNavigation[$key] = $item; + } + + return $mergedNavigation; + } + + protected function getDraftFileName($foldername) + { + $draftFileName = $foldername; + + if($draftFileName == '/') + { + $draftFileName = $this->draftNaviName; + } + + return $draftFileName . '.txt'; + } + + protected function getExtendedFileName($foldername) + { + $draftFileName = $foldername; + + if($draftFileName == '/') + { + $draftFileName = $this->draftNaviName; + } + + return $draftFileName . '-extended.txt'; + } + + public function getItemWithKeyPath($navigation, array $searchArray, $baseUrl = null) + { + $item = false; + + # if it is the homepage + if(isset($searchArray[0]) && $searchArray[0] == '') + { + return $this->getHomepageItem($baseUrl); + } + + foreach($searchArray as $key => $itemKey) + { + $item = isset($navigation[$itemKey]) ? clone($navigation[$itemKey]) : false; + + unset($searchArray[$key]); + if(!empty($searchArray) && $item) + { + return $this->getItemWithKeyPath($item->folderContent, $searchArray); + } + } + + return $item; + } + + # used with scan folder that keeps index from draft version + public function setActiveNaviItemsWithKeyPath($navigation, array $searchArray) + { + foreach($searchArray as $key => $itemKey) + { + if(isset($navigation[$itemKey])) + { + unset($searchArray[$key]); + + # active, if there are no more subitems + if(empty($searchArray)) + { + $navigation[$itemKey]->active = true; + } + + # activeParent, if there are more subitems + if(!empty($searchArray) && isset($navigation[$itemKey]->folderContent)) + { + $navigation[$itemKey]->activeParent = true; + $navigation[$itemKey]->folderContent = $this->setActiveNaviItemsWithKeyPath($navigation[$itemKey]->folderContent, $searchArray); + } + + # break to avoid other items with that key are set active + break; + } + } + + return $navigation; + } + + public function getHomepageItem($baseUrl) + { +# $live = $this->storage->getFile('contentFolder', '', 'index.md'); + $draft = $this->storage->getFile('contentFolder', '', 'index.txt'); + + # return a standard item-object + $item = new \stdClass; + + $item->status = $draft ? 'modified' : 'published'; + $item->originalName = 'home'; + $item->elementType = 'folder'; + $item->fileType = $draft ? 'mdtxt' : 'md'; + $item->order = false; + $item->name = 'home'; + $item->slug = ''; + $item->path = ''; + $item->pathWithoutType = DIRECTORY_SEPARATOR . 'index'; + $item->key = false; + $item->keyPath = ''; + $item->keyPathArray = ['']; + $item->chapter = false; + $item->urlRel = '/'; + $item->urlRelWoF = '/'; + $item->urlAbs = $baseUrl; + $item->active = false; + $item->activeParent = false; + $item->hide = false; + + return $item; + } + + public function renameItem($item, $newslug) + { + $folder = str_replace($item->originalName, '', $item->path); + $oldname = $item->order . '-' . $item->slug; + $newname = $item->order . '-' . $newslug; + $result = true; + + if($item->elementType == 'folder') + { + $result = $this->storage->renameFile('contentFolder', $folder, $oldname, $newname); + } + + if($item->elementType == 'file') + { + $filetypes = array('md', 'txt', 'yaml'); + $result = true; + foreach($filetypes as $filetype) + { + $oldfilename = $oldname . '.' . $filetype; + $newfilename = $newname . '.' . $filetype; + + $result = $this->storage->renameFile('contentFolder', $folder, $oldfilename, $newfilename); + } + } + + return $result; + } + + public function getCurrentPage($args) + { + if(isset($args['route'])) + { + $argSegments = explode("/", $args['route']); + + # check if the last url segment is a number + $pageNumber = array_pop($argSegments); + if(is_numeric($pageNumber) && $pageNumber < 10000) + { + # then check if the segment before the page is a "p" that indicates a paginator + $pageIndicator = array_pop($argSegments); + if($pageIndicator == "p") + { + return $pageNumber; + } + } + } + + return false; + } + + public function removeHiddenPages($liveNavigation) + { + foreach($liveNavigation as $key => $item) + { + if(isset($item->hide) && $item->hide == true) + { + unset($liveNavigation[$key]); + } + elseif($item->elementType == 'folder' && !empty($item->folderContent)) + { + $item->folderContent = $this->removeHiddenPages($item->folderContent); + } + } + + return $liveNavigation; + } + + public function getBreadcrumb($navigation, $searchArray, $i = NULL, $breadcrumb = NULL) + { + # if it is the first round, create an empty array + if(!$i){ $i = 0; $breadcrumb = array();} + + if(!$searchArray){ return $breadcrumb;} + + while($i < count($searchArray)) + { + if(!isset($navigation[$searchArray[$i]])){ return false; } + $item = $navigation[$searchArray[$i]]; + + + if($i == count($searchArray)-1) + { + $item->active = true; + } + else + { + $item->activeParent = true; + } + + $copy = clone($item); + if($copy->elementType == 'folder') + { + unset($copy->folderContent); + $navigation = $item->folderContent; + } + $breadcrumb[] = $copy; + + $i++; + return $this->getBreadcrumb($navigation, $searchArray, $i++, $breadcrumb); + } + + return $breadcrumb; + } + + public function getPagingForItem($navigation, $item) + { + if(!$item) + { + return $item; + } + + # if page is home + if(trim($item->pathWithoutType, DIRECTORY_SEPARATOR) == 'index') + { + return $item; + } + + $keyPos = count($item->keyPathArray)-1; + $thisChapArray = $item->keyPathArray; + + $item->thisChapter = false; + $item->prevItem = false; + $item->nextItem = false; + + if($keyPos > 0) + { + array_pop($thisChapArray); + $item->thisChapter = $this->getItemWithKeyPath($navigation, $thisChapArray); + } + + $flat = $this->flatten($navigation, $item->urlRel); + + $itemkey = $flat[0]; + + # if no previous or next is found (e.g. hidden page) + if(!is_int($itemkey)) + { + return $item; + } + + if($itemkey > 1) + { + $item->prevItem = $flat[$itemkey-1]; + } + if(isset($flat[$itemkey+1])) + { + $item->nextItem = $flat[$itemkey+1]; + } + + return $item; + } + + public function flatten($navigation, $urlRel, $flat = []) + { + foreach($navigation as $key => $item) + { + $flat[] = clone($item); + + if($item->urlRel == $urlRel) + { + array_unshift($flat, count($flat)); + } + + if($item->elementType == 'folder' && !empty($item->folderContent)) + { + $last = array_key_last($flat); + unset($flat[$last]->folderContent); + $flat = $this->flatten($item->folderContent, $urlRel, $flat); + } + } + + return $flat; + } + + # only used by public api + public function findItemsWithSlug($navigation, $slug, $result = NULL) + { + foreach($navigation as $key => $item) + { + # set item active, needed to move item in navigation + if($item->slug === $slug) + { + $result[] = $item; + } + elseif($item->elementType === "folder") + { + $result = self::findItemsWithSlug($item->folderContent, $slug, $result); + } + } + + return $result; + } + + + # NOT IN USE ANYMORE BUT KEEP IT + public function getItemWithUrl($navigation, $url, $result = NULL) + { + die('getItemWithURL in navigation model not in use.'); + + foreach($navigation as $key => $item) + { + # set item active, needed to move item in navigation + if($item->urlRelWoF === $url) + { + $result = $item; + break; + } + elseif($item->elementType === "folder") + { + $result = self::getItemWithUrl($item->folderContent, $url, $result); + + if($result) + { + break; + } + } + } + + return $result; + } + + + # NOT IN USE ANYMORE BUT KEEP IT + public function setActiveNaviItems($navigation, $breadcrumb) + { + die('setActiveNaviItems in navigation model not in use.'); + + if($breadcrumb) + { + foreach($breadcrumb as $crumbkey => $page) + { + foreach($navigation as $itemkey => $item) + { + if($page->urlRelWoF == $item->urlRelWoF) + { + unset($breadcrumb[$crumbkey]); + + if(empty($breadcrumb)) + { + $navigation[$itemkey]->active = true; + } + elseif(isset($navigation[$itemkey]->folderContent)) + { + $navigation[$itemkey]->activeParent = true; + $navigation[$itemkey]->folderContent = $this->setActiveNaviItems($navigation[$itemkey]->folderContent, $breadcrumb); + } + + break; + } + } + } + } + + return $navigation; + } + + # NOT IN USE ANYMORE + public function getLastItemOfFolder($folder) + { + die('getLastItemOfFolder in navimodel not in use.'); + + $lastItem = end($folder->folderContent); + if(is_object($lastItem) && $lastItem->elementType == 'folder' && !empty($lastItem->folderContent)) + { + return $this->getLastItemOfFolder($lastItem); + } + return $lastItem; + } + +} \ No newline at end of file diff --git a/system/typemill/Models/Settings.php b/system/typemill/Models/Settings.php new file mode 100644 index 0000000..44be506 --- /dev/null +++ b/system/typemill/Models/Settings.php @@ -0,0 +1,386 @@ +storage = new StorageWrapper('\Typemill\Models\Storage'); + } + + public function loadSettings() + { + $defaultsettings = $this->getDefaultSettings(); + $usersettings = $this->getUserSettings(); + + $settings = $defaultsettings; + $settings['setup'] = true; + + if($usersettings) + { + $settings = array_merge($defaultsettings, $usersettings); + + # make sure all image-size information are there + if(isset($usersettings['images'])) + { + $images = array_merge($defaultsettings['images'], $settings['images']); + $settings['images'] = $images; + } + } +#### + $settings['rootPath'] = getcwd(); +#### + $settings = self::addThemeSettings($settings); + + return $settings; + } + + public function addThemeSettings($settings) + { + # we have to check if the theme has been deleted + $themefolder = $this->storage->getFolderPath('themesFolder'); + + # if there is no theme in settings or theme has been deleted + if(!isset($settings['theme']) OR !file_exists($themefolder . $settings['theme'])) + { + # scan theme folder and get the first theme + $themes = array_filter(scandir($themefolder), function ($item) use($themefolder) + { + return is_dir($themefolder . $item) && strpos($item, '.') !== 0; + }); + + $firsttheme = reset($themes); + + # if there is a theme with an index.twig-file + if($firsttheme && file_exists($themefolder . $firsttheme . DIRECTORY_SEPARATOR . 'index.twig')) + { + $settings['theme'] = $firsttheme; + } + else + { + die('You need at least one theme with an index.twig-file in your theme-folder.'); + } + } + + # We have the theme so create the theme path +# $settings['themePath'] = $settings['rootPath'] . $settings['themeFolder'] . DIRECTORY_SEPARATOR . $settings['theme']; + + # if there are no theme settings yet (e.g. no setup yet) use default theme settings + if(!isset($settings['themes'])) + { + $themeSettings = $this->getObjectSettings('themes', $settings['theme']); + $settings['themes'][$settings['theme']] = isset($themeSettings['settings']) ? $themeSettings['settings'] : false; + } + + return $settings; + } + + public function getDefaultSettings() + { + $defaultSettings = $this->storage->getYaml('systemSettings', '', 'defaults.yaml'); + + if($defaultSettings) + { + $defaultSettings['systemSettingsPath'] = $this->storage->getFolderPath('systemSettings'); + + return $defaultSettings; + } + + return false; + } + + public function getUserSettings() + { + $userSettings = $this->storage->getYaml('settingsFolder', '', 'settings.yaml'); + + if($userSettings) + { + return $userSettings; + } + + return false; + } + + public function getKixoteSettings() + { + $defaultSettings = $this->storage->getYaml('systemSettings', '', 'kixote.yaml'); + $userSettings = $this->storage->getYaml('settingsFolder', '', 'kixote.yaml'); + + if ($userSettings) + { + foreach ($defaultSettings['promptlist'] as $key => $prompt) + { + if (isset($userSettings['promptlist'][$key])) + { + # Use active setting from user but keep system settings intact + $active = $userSettings['promptlist'][$key]['active']; + $userSettings['promptlist'][$key] = $prompt; + $userSettings['promptlist'][$key]['active'] = $active; + } + else + { + # New prompt from system settings, add it to user settings + $userSettings['promptlist'][$key] = $prompt; + } + } + } + else + { + $userSettings = $defaultSettings; + } + + return $userSettings; + } + + public function updateKixoteSettings($kixoteSettings) + { + if($this->storage->updateYaml('settingsFolder', '', 'kixote.yaml', $kixoteSettings)) + { + return true; + } + + return false; + } + + public function getObjectSettings($objectType, $objectName) + { + $objectSettings = $this->storage->getYaml($objectType, $objectName, $objectName . '.yaml'); + + if($objectSettings) + { + return $objectSettings; + } + + return false; + } + + public function updateSettings($newSettings, $key1 = false, $key2 = false) + { + $userSettings = $this->getUserSettings(); + + # only allow if usersettings already exists (setup has been done) + if($userSettings) + { + # hard overwrite + if($key1 && $key2) + { + $userSettings[$key1][$key2] = $newSettings; + $settings = $userSettings; + } + # hard overwrite + elseif($key1) + { + $userSettings[$key1] = $newSettings; + $settings = $userSettings; + } + # only merge + else + { + # merge usersettings with new settings + $settings = array_merge($userSettings, $newSettings); + + # make sure that multidimensional arrays are merged correctly + # for example: only one plugin data will be passed with new settings, with array merge all others will be deleted. + foreach($newSettings as $key => $settingsItems) + { + if(is_array($settingsItems) && isset($userSettings[$key])) + { + if($this->array_is_list($settingsItems)) + { + # for numeric/list arrays instead of associative arrays we only use new values + $settings[$key] = $newSettings[$key]; + } + else + { + $settings[$key] = array_merge($userSettings[$key], $newSettings[$key]); + } + } + } + + } + + if($this->storage->updateYaml('settingsFolder', '', 'settings.yaml', $settings)) + { + return true; + } + } + + return false; + } + + public function updateThemeCss(string $name, string $css) + { + if($css == '') + { + if($this->storage->deleteFile('cacheFolder', '', $name . '-custom.css', $css)) + { + return true; + } + } + else + { + if($this->storage->writeFile('cacheFolder', '', $name . '-custom.css', $css)) + { + return true; + } + } + + return false; + } + + private function array_is_list(array $arr) + { + if ($arr === []) + { + return true; + } + return array_keys($arr) === range(0, count($arr) - 1); + } + + public function getSettingsDefinitions() + { + $settingsDefinitions = $this->storage->getYaml('systemSettings', '', 'system.yaml'); + + if(!isset($settingsDefinitions['fieldsetsystem']['fields']['language'])) + { + die('languages in settings-definitions are missing'); + } + + # get languages dynamically from existing translation-files + $languages = Translations::getLanguages(); + $langs = []; + foreach($languages as $language) + { + $langs[$language] = $language; + } + + $settingsDefinitions['fieldsetsystem']['fields']['language']['options'] = $langs; + + return $settingsDefinitions; + } + + public function createSettings(array $defaultSettings = NULL) + { + $defaults = [ + 'language' => Translations::whichLanguage() + ]; + + if($defaultSettings) + { + $defaults = array_merge($defaults, $defaultSettings); + } + + $initialSettings = $this->storage->updateYaml('settingsFolder', '', 'settings.yaml', $defaults); + + if($initialSettings) + { + return true; + } + + return false; + } + + public function findSecurityDefinitions($definitions, $securityDefinitions = []) + { + foreach ($definitions as $fieldname => $definition) + { + if (isset($definition['fields'])) + { + $securityDefinitions = $this->findSecurityDefinitions($definition['fields'], $securityDefinitions); + } + + if (isset($definition['type']) && $definition['type'] === 'password') + { + $securityDefinitions[] = $fieldname; + } + } + + return $securityDefinitions; + } + + public function extractSecuritySettings($settings, $securityFields) + { + $securitySettings = []; + + foreach ($securityFields as $fieldname) + { + if (isset($settings[$fieldname])) + { + $securitySettings[$fieldname] = $settings[$fieldname]; + unset($settings[$fieldname]); + } + } + + return [ + 'settings' => $settings, + 'securitySettings' => $securitySettings + ]; + } + + public function updateSecuritySettings($newSecuritySettings, $themeorplugin = null, $themeorpluginname = null) + { + # problem that settings with same name will overwrite (e.g. from theme and plugins) + $securitySettings = $this->getSecuritySettings(); + foreach($newSecuritySettings as $fieldname => $value) + { + if($themeorplugin && $themeorpluginname) + { + $securitySettings[$themeorplugin][$themeorpluginname][$fieldname] = $value; + } + else + { + $securitySettings[$fieldname] = $value; + } + } + + $secrets = $this->storage->updateYaml('settingsFolder', '', 'secrets.yaml', $securitySettings); + if($secrets) + { + return true; + } + + return false; + } + + private function getSecuritySettings() + { + $secrets = $this->storage->getYaml('settingsFolder', '', 'secrets.yaml'); + + if($secrets) + { + return $secrets; + } + return []; + } + + public function getSecret(string $fieldname, $objecttype = null, $objectname = null) + { + $secrets = $this->storage->getYaml('settingsFolder', '', 'secrets.yaml'); + + if(!$secrets) + { + return false; + } + + if($fieldname && $objecttype && $objectname) + { + if(isset($secrets[$objecttype][$objectname][$fieldname])) + { + return $secrets[$objecttype][$objectname][$fieldname]; + } + } + + if($fieldname && isset($secrets[$fieldname])) + { + return $secrets[$fieldname]; + } + + return false; + } +} \ No newline at end of file diff --git a/system/typemill/Models/SimpleMail.php b/system/typemill/Models/SimpleMail.php new file mode 100644 index 0000000..8342938 --- /dev/null +++ b/system/typemill/Models/SimpleMail.php @@ -0,0 +1,66 @@ +from = trim($settings['mailfrom']); + + if(isset($settings['mailfromname']) && $settings['mailfromname'] != '') + { + $this->from = '=?UTF-8?B?' . base64_encode($settings['mailfromname']) . '?= <' . trim($settings['mailfrom']) . '>'; + } + } + + if(isset($settings['mailreply']) && $settings['mailreply'] != '') + { + $this->reply = trim($settings['mailreply']); + } + } + + public function send(string $to, string $subject, string $message) + { + if(!$this->from) + { + $this->error = Translations::translate('Email address in system settings is missing.'); + + return false; + } + + # 'Reply-To: webmaster@example.com' . "\r\n" . + + $headers = 'Content-Type: text/html; charset=utf-8' . "\r\n"; + $headers .= 'Content-Transfer-Encoding: base64' . "\r\n"; + $headers .= 'From: ' . $this->from . "\r\n"; + if($this->reply) + { + $headers .= 'Reply-To: base64' . $this->reply . "\r\n"; + } + $headers .= 'X-Mailer: PHP/' . phpversion(); + + $subject = '=?UTF-8?B?' . base64_encode($subject) . '?='; + $message = base64_encode($message); + + $send = mail($to, $subject, $message, $headers); + + if ($send !== true) + { + $lastError = error_get_last(); + $this->error = $lastError ? $lastError['message'] : 'Unknown error occurred while sending mail.'; + } + + return $send; + } +} \ No newline at end of file diff --git a/system/typemill/Models/Sitemap.php b/system/typemill/Models/Sitemap.php new file mode 100644 index 0000000..6a6f79f --- /dev/null +++ b/system/typemill/Models/Sitemap.php @@ -0,0 +1,64 @@ +storage = new StorageWrapper('\Typemill\Models\Storage'); + } + + public function updateSitemap($navigation, $urlinfo) + { + $sitemap = '' . "\n"; + $sitemap .= '' . "\n"; + $sitemap = $this->addUrlSet($sitemap, $urlinfo['baseurl']); + $sitemap .= $this->generateUrlSets($navigation); + $sitemap .= ''; + + $this->storage->writeFile('cacheFolder', '', 'sitemap.xml', $sitemap); + } + + public function generateUrlSets($navigation) + { + $urlset = ''; + + foreach($navigation as $item) + { + if($item->status == "published" OR $item->status == "modified") + { + if($item->elementType == 'folder' && isset($item->noindex) && $item->noindex === true) + { + $urlset .= $this->generateUrlSets($item->folderContent, $urlset); + } + elseif($item->elementType == 'folder') + { + $urlset = $this->addUrlSet($urlset, $item->urlAbs); + $urlset .= $this->generateUrlSets($item->folderContent, $urlset); + } + elseif(isset($item->noindex) && $item->noindex === true ) + { + continue; + } + else + { + $urlset = $this->addUrlSet($urlset, $item->urlAbs); + } + } + } + return $urlset; + } + + public function addUrlSet($urlset, $url) + { + $urlset .= ' ' . "\n"; + $urlset .= ' ' . $url . '' . "\n"; + $urlset .= ' ' . "\n"; + return $urlset; + } +} \ No newline at end of file diff --git a/system/typemill/Models/Storage.php b/system/typemill/Models/Storage.php new file mode 100644 index 0000000..63bcb26 --- /dev/null +++ b/system/typemill/Models/Storage.php @@ -0,0 +1,1014 @@ +basepath = getcwd() . DIRECTORY_SEPARATOR; + + $this->tmpFolder = $this->basepath . 'media' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR; + + $this->originalFolder = $this->basepath . 'media' . DIRECTORY_SEPARATOR . 'original' . DIRECTORY_SEPARATOR; + + $this->liveFolder = $this->basepath . 'media' . DIRECTORY_SEPARATOR . 'live' . DIRECTORY_SEPARATOR; + + $this->thumbsFolder = $this->basepath . 'media' . DIRECTORY_SEPARATOR . 'thumbs' . DIRECTORY_SEPARATOR; + + $this->customFolder = $this->basepath . 'media' . DIRECTORY_SEPARATOR . 'custom' . DIRECTORY_SEPARATOR; + + $this->fileFolder = $this->basepath . 'media' . DIRECTORY_SEPARATOR . 'files' . DIRECTORY_SEPARATOR; + + $this->contentFolder = $this->basepath . 'content'; + + $this->dataFolder = $this->basepath . 'data'; + + $this->cacheFolder = $this->basepath . 'cache'; + + $this->settingsFolder = $this->basepath . 'settings'; + + $this->pluginsFolder = $this->basepath . 'plugins'; + + $this->themesFolder = $this->basepath . 'themes'; + + $this->translationFolder = $this->basepath . 'system' . DIRECTORY_SEPARATOR . 'typemill' . DIRECTORY_SEPARATOR . 'author' . DIRECTORY_SEPARATOR . 'translations' . DIRECTORY_SEPARATOR; + + $this->systemSettings = $this->basepath . 'system' . DIRECTORY_SEPARATOR . 'typemill' . DIRECTORY_SEPARATOR . 'settings'; + + $this->isWritable = [ + 'tmpFolder' => true, + 'originalFolder' => true, + 'liveFolder' => true, + 'thumbsFolder' => true, + 'customFolder' => true, + 'fileFolder' => true, + 'contentFolder' => true, + 'dataFolder' => true, + 'cacheFolder' => true, + 'settingsFolder' => true + ]; + } + + public function getError() + { + return $this->error; + } + + public function getFolderPath($location, $folder = NULL) + { + if(isset($this->$location)) + { + $path = rtrim($this->$location, DIRECTORY_SEPARATOR); + $path .= DIRECTORY_SEPARATOR; + + # check if folder is no hack like "../" + if($folder && $folder != '' && preg_match('/^(?:[\/\\a-z0-9_-]|\.(?!\.))+$/iD', $folder)) + { + $folder = trim($folder, DIRECTORY_SEPARATOR); + $path .= $folder . DIRECTORY_SEPARATOR; + } + elseif($location == 'basepath') + { + # do not allow direct access to basepath files + + $this->error = Translations::translate('Access to basepath is not allowed.'); + + return false; + } + + return $path; + } + + $this->error = Translations::translate('We could not find a folderPath for') . ' ' . $location; + + return false; + } + + public function checkFolder($location, $folder = NULL) + { + $folderpath = $this->getFolderPath($location, $folder); + + if(!is_dir($folderpath) OR !is_writable($folderpath)) + { + $this->error = $folderpath . ' ' . Translations::translate('does not exist or is not writable') . '.'; + + return false; + } + + return true; + } + + public function createFolder($location, $folder) + { + $folderpath = $this->getFolderPath($location, $folder); + + if(is_dir($folderpath)) + { + return true; + } + + if(!mkdir($folderpath, 0755, true)) + { + $this->error = Translations::translate('Could not create folder') . ' ' . $folderpath; + + return false; + } + + return true; + } + + public function deleteFolder($location, $folder, $filename) + { + if(!isset($this->isWritable[$location])) + { + $this->error = Translations::translate('It is not allowed to write into') . ' ' . $location; + + return false; + } + + $filepath = $this->getFolderPath($location, $folder) . $filename; + + if(is_dir($filepath)) + { + if(rmdir($dir)) + { + return true; + } + + $this->error = Translations::translate('We found the folder but could not delete') . ' ' . $filepath; + + return false; + } + + $this->error = $filepath . ' ' .Translations::translate('is not a folder') . '.'; + + return false; + } + + public function deleteContentFolder($filepath) + { + $filepath = $this->getFolderPath('contentFolder') . $filepath; + + if(is_dir($filepath)) + { + if(rmdir($filepath)) + { + return true; + } + + $this->error = Translations::translate('We found the folder but could not delete it') . ' ' . $filepath; + + return false; + } + + return true; + } + + public function deleteContentFolderRecursive($folderpath) + { + $folderdir = $this->getFolderPath('contentFolder'); + + if(!is_dir($folderdir . $folderpath)) + { + $this->error = $folderpath . ' ' . Translations::translate('is not a directory'); + return false; + } + + $filelist = array_diff(scandir($folderdir . $folderpath), array('..', '.')); + if(!empty($filelist)) + { + foreach($filelist as $filepath) + { + $fullfilepath = $folderdir . $folderpath . DIRECTORY_SEPARATOR . $filepath; + if(is_dir($fullfilepath)) + { + $this->deleteContentFolderRecursive($folderpath . DIRECTORY_SEPARATOR . $filepath); + } + else + { + if(!unlink($fullfilepath)) + { + $this->error = Translations::translate('Could not delete file') . ' ' . $fullfilepath; + + return false; + } + } + } + } + + if(!rmdir($folderdir . $folderpath)) + { + $this->error = Translations::translate('Could not delete folder') . ' ' . $folderpath; + + return false; + } + + return true; + } + + public function checkFile($location, $folder, $filename) + { + $filepath = $this->getFolderPath($location, $folder) . $filename; + + if(!file_exists($filepath)) + { + $this->error = $filepath . ' ' . Translations::translate('does not exist'); + + return false; + } + + return true; + } + + public function getFile($location, $folder, $filename, $method = NULL) + { + if($this->checkFile($location, $folder, $filename)) + { + $filepath = $this->getFolderPath($location, $folder) . $filename; + + $fileContent = file_get_contents($filepath); + + # use unserialise or json_decode + if($method && is_callable($method)) + { + $fileContent = $method($fileContent); + } + + return $fileContent; + } + + return false; + } + + public function getFileTime($location, $folder, $filename) + { + $filepath = $this->getFolderPath($location, $folder) . $filename; + + if(!file_exists($filepath)) + { + $this->error = $filepath . ' ' . Translations::translate('does not exist'); + + return false; + } + + return date("Y-m-d",filemtime($filepath)); + } + + public function writeFile($location, $folder, $filename, $data, $method = NULL) + { + if(!isset($this->isWritable[$location])) + { + $this->error = Translations::translate('It is not allowed to write into') . ' ' . $location; + + return false; + } + + # CLEAN EVERYTHING UP FUNCTION + $folder = trim($folder, DIRECTORY_SEPARATOR); + $folder = ($folder == '') ? '' : $folder . DIRECTORY_SEPARATOR; + $filename = trim($filename, DIRECTORY_SEPARATOR); + + if(!$this->checkFolder($location, $folder)) + { + if(!$this->createFolder($location, $folder)) + { + return false; + } + } + + $filepath = $this->getFolderPath($location, $folder) . $filename; + + $openfile = @fopen($filepath, "w"); + if(!$openfile) + { + $this->error = Translations::translate('Could not open and read the file') . ' ' . $filepath; + + return false; + } + + # serialize, json_decode + if($method && is_callable($method)) + { + $data = $method($data); + } + + $writefile = fwrite($openfile, $data); + if($writefile === false) + { + $this->error = Translations::translate('Could not write to the file') . ' ' . $filepath; + + return false; + } + + fclose($openfile); + + return true; + } + + public function renameFile($location, $folder, $oldname, $newname) + { + if(!isset($this->isWritable[$location])) + { + $this->error = Translations::translate('It is not allowed to write into') . ' ' . $location; + + return false; + } + + $folder = trim($folder, DIRECTORY_SEPARATOR); + + $oldFilePath = $this->getFolderPath($location) . $folder . DIRECTORY_SEPARATOR . $oldname; + $newFilePath = $this->getFolderPath($location) . $folder . DIRECTORY_SEPARATOR . $newname; + + if($oldFilePath != $newFilePath) + { + if(!file_exists($oldFilePath)) + { + return false; + } + + if(!rename($oldFilePath, $newFilePath)) + { + return false; + } + } + + return true; + } + + public function deleteFile($location, $folder, $filename) + { + if(!isset($this->isWritable[$location])) + { + $this->error = Translations::translate('It is not allowed to write into') . ' ' . $location; + + return false; + } + + if($this->checkFile($location, $folder, $filename)) + { + $filepath = $this->getFolderPath($location) . $folder . DIRECTORY_SEPARATOR . $filename; + + if(unlink($filepath)) + { + return true; + } + + $this->error = Translations::translate('We found the file but could not delete') . ' ' . $filepath; + + return false; + } + + $this->error = Translations::translate('We did not find a file with that name'); + + # we do not want to stop delete operation just because a file was not there, so return a message and true. + return true; + } + + # used to sort the navigation / files + public function moveContentFile($item, $folderPath, $index, $date = null) + { + $filetypes = array('md', 'txt', 'yaml'); + + # set new order as string + $newOrder = ($index < 10) ? '0' . $index : $index; + + $newPath = $this->contentFolder . $folderPath . DIRECTORY_SEPARATOR . $newOrder . '-' . $item->slug; + + if($item->elementType == 'folder') + { + $oldPath = $this->contentFolder . $item->path; + + if(is_dir($oldPath)) + { + if(@rename($oldPath, $newPath)) + { + return true; + } + return false; + } + } + + # create old path but without filetype + $oldPath = substr($item->path, 0, strpos($item->path, ".")); + $oldPath = $this->contentFolder . $oldPath; + + $result = true; + + foreach($filetypes as $filetype) + { + $oldFilePath = $oldPath . '.' . $filetype; + $newFilePath = $newPath . '.' . $filetype; + + #check if file with filetype exists and rename + if($oldFilePath != $newFilePath && file_exists($oldFilePath)) + { + if(@rename($oldFilePath, $newFilePath)) + { + $result = $result; + } + else + { + $result = false; + } + } + } + + return $result; + } + + public function getYaml($location, $folder, $filename) + { + $yaml = $this->getFile($location, $folder, $filename); + + if($yaml) + { + return \Symfony\Component\Yaml\Yaml::parse($yaml); + } + + return false; + } + + public function updateYaml($location, $folder, $filename, $contentArray) + { + if(!isset($this->isWritable[$location])) + { + $this->error = Translations::translate('It is not allowed to write into') . ' ' . $location; + + return false; + } + + $yaml = \Symfony\Component\Yaml\Yaml::dump($contentArray,6); + if($this->writeFile($location, $folder, $filename, $yaml)) + { + return true; + } + + return false; + } + + ###################### + ## Timeout ## + ###################### + + public function timeoutIsOver($name, $timespan) + { + $location = 'cacheFolder'; + $folder = ''; + $filename = 'timer.yaml'; + + // Get current timers from the YAML file, if it exists + $timers = $this->getYaml($location, $folder, $filename) ?: []; + + $currentTime = time(); + $timeThreshold = $currentTime - $timespan; + + # Check if the name exists and if the timestamp is older than the current time minus the timespan + if (!isset($timers[$name]) || !is_numeric($timers[$name]) || $timers[$name] <= $timeThreshold) + { + # If the name doesn't exist or the timestamp is older, update the timer + $timers[$name] = $currentTime; + + # Update the YAML file with the new or updated timer + $this->updateYaml($location, $folder, $filename, $timers); + + return true; + } + + # If the name exists and the timestamp is not older, return false + return false; + } + + + ################## + ## IMAGES ## + ################## + + public function createUniqueImageName($filename, $extension) + { + $defaultfilename = $filename; + + $suffix = 1; + + while(file_exists($this->originalFolder . $filename . '.' . $extension)) + { + $filename = $defaultfilename . '-' . $suffix; + $suffix++; + } + + return $filename; + } + + public function publishImage($name, $noresize = false) + { + $pathinfo = pathinfo($name); + if(!$pathinfo) + { + $this->error = Translations::translate('Could not read pathinfo') . '.'; + + return false; + } + + $extension = isset($pathinfo['extension']) ? strtolower($pathinfo['extension']) : false; + $imagename = isset($pathinfo['filename']) ? $pathinfo['filename'] : false; + + if(!$extension OR !$imagename) + { + $this->error = Translations::translate('Extension or name for image is missing') . '.'; + return false; + } + + $imagesInTmp = glob($this->tmpFolder . "*$imagename.*"); + if(empty($imagesInTmp) OR !$imagesInTmp) + { + $this->error = Translations::translate('We did not find the image in the tmp-folder or could not read it') . '.'; + return false; + } + + # case: image is not published yet and in tmp + foreach( $imagesInTmp as $imagepath) + { + $tmpimagename = explode("+", basename($imagepath)); + $destinationfolder = strtolower($tmpimagename[0]); + $filename = $tmpimagename[1]; + + switch($destinationfolder) + { + case 'original': + + $result = rename($imagepath, $this->originalFolder . $filename); + + if($noresize) + { + $result = copy($this->originalFolder . $filename, $this->liveFolder . $filename); + $extension = pathinfo($this->originalFolder . $filename, PATHINFO_EXTENSION); + } + + if(!$result) + { + $this->error = Translations::translate('We could not store the original image') . '.'; + } + + break; + case 'live': + if($noresize) + { + break; + } + if(!rename($imagepath, $this->liveFolder . $filename)) + { + $this->error = Translations::translate('We could not store the live image to the live folder'); + } + break; + case 'thumbs': + if(!rename($imagepath, $this->thumbsFolder . $filename)) + { + $this->error = Translations::translate('We could not store the thumb to the thumb folder'); + } + break; + } + } + + if(!$this->error) + { + # return true; + return 'media/live/' . $imagename . '.' . $extension; + } + + return false; + } + + # check if an image exists in the live folder or in the original folder independent from extension + public function checkImage($imagepath) + { + $original = stripos($imagepath, '/original/'); + $live = stripos($imagepath, '/live/'); + + $pathinfo = pathinfo($imagepath); + if(!$pathinfo) + { + $this->error = Translations::translate('Could not read pathinfo'); + + return false; + } + + $extension = isset($pathinfo['extension']) ? strtolower($pathinfo['extension']) : false; + $imagename = isset($pathinfo['filename']) ? $pathinfo['filename'] : false; + $newpath = false; + + if($original) + { + $image = glob($this->originalFolder . "$imagename.*"); + if(isset($image[0])) + { + $newpath = 'media/original/' . basename($image[0]); + } + } + elseif($live) + { + $image = glob($this->liveFolder . "$imagename.*"); + if(isset($image[0])) + { + $newpath = 'media/live/' . basename($image[0]); + } + } + + return $newpath; + + } + + public function getImageList() + { + $thumbs = array_diff(scandir($this->thumbsFolder), array('..', '.')); + $imagelist = array(); + + foreach ($thumbs as $key => $name) + { + $imagelist[] = [ + 'name' => $name, + 'timestamp' => filemtime($this->thumbsFolder . $name), + 'src_thumb' => 'media/thumbs/' . $name, + 'src_live' => 'media/live/' . $name, + ]; + } + + $imagelist = Helpers::array_sort($imagelist, 'timestamp', SORT_DESC); + + return $imagelist; + } + + # get details from existing image for media library + public function getImageDetails($name) + { + $name = basename($name); + + if (!in_array($name, array(".","..")) && file_exists($this->liveFolder . $name)) + { + $imageinfo = getimagesize($this->liveFolder . $name); + + if(!$imageinfo && pathinfo($this->liveFolder . $name, PATHINFO_EXTENSION) == 'svg') + { + $imagedetails = [ + 'name' => $name, + 'timestamp' => filemtime($this->liveFolder . $name), + 'bytes' => filesize($this->liveFolder . $name), + 'width' => '---', + 'height' => '---', + 'type' => 'svg', + 'src_thumb' => 'media/thumbs/' . $name, + 'src_live' => 'media/live/' . $name, + ]; + } + else + { + $imagedetails = [ + 'name' => $name, + 'timestamp' => filemtime($this->liveFolder . $name), + 'bytes' => filesize($this->liveFolder . $name), + 'width' => $imageinfo[0], + 'height' => $imageinfo[1], + 'type' => $imageinfo['mime'], + 'src_thumb' => 'media/thumbs/' . $name, + 'src_live' => 'media/live/' . $name, + ]; + } + + return $imagedetails; + } + + return false; + } + + public function storeCustomImage($image, $extension, $imageName) + { + switch($extension) + { + case "png": + $storedImage = imagepng( $image, $this->customFolder . $imageName . '.png', 9 ); + break; + case "gif": + $storedImage = imagegif( $image, $this->customFolder . $imageName . '.gif' ); + break; + case "webp": + $storedImage = imagewebp( $image, $this->customFolder . $imageName . '.webp', 80); + break; + case "jpg": + case "jpeg": + $storedImage = imagejpeg( $image, $this->customFolder . $imageName . '.' . $extension, 80); + break; + default: + $storedImage = false; + } + + if(!$storedImage) + { + $this->error = Translations::translate('Could not store the custom size of') . ' ' . $imageName; + + return false; + } + + return true; + } + + public function deleteImage($name) + { + # validate name + $name = basename($name); + + if(!file_exists($this->liveFolder . $name) OR !unlink($this->liveFolder . $name)) + { + $this->error .= Translations::translate('We could not delete the live image.') . ' '; + } + + if(!file_exists($this->thumbsFolder . $name) OR !unlink($this->thumbsFolder . $name)) + { + $this->error .= Translations::translate('We could not delete the thumb image.') . ' '; + } + + # delete custom images (resized and grayscaled) array_map('unlink', glob("some/dir/*.txt")); + $pathinfo = pathinfo($name); + + foreach(glob($this->originalFolder . $pathinfo['filename'] . '\.*') as $image) + { + # you could check if extension is the same here + if(!unlink($image)) + { + $this->error = Translations::translate('We could not delete the original image in') . ' ' . $this->originalFolder; + } + } + + foreach(glob($this->customFolder . $pathinfo['filename'] . '\-*.' . $pathinfo['extension']) as $image) + { + # you could check if extension is the same here + if(!unlink($image)) + { + $this->error .= Translations::translate('we could not delete a custom image (grayscale or resized).') . ' '; + } + } + + if(!$this->error) + { + return true; + } + + return false; + } + + ################## + ## FILES ## + ################## + + public function checkFileExists($filepath) + { + $pathinfo = pathinfo($filepath); + if(!$pathinfo) + { + $this->error = Translations::translate('Could not read pathinfo'); + + return false; + } + + $filename = $pathinfo['filename'] . '.' . $pathinfo['extension']; + $newpath = false; + + if($this->checkFile('fileFolder', '', $filename)) + { + $newpath = 'media/files/' . $filename; + } + + return $newpath; + } + + public function publishFile($name) + { + $pathinfo = pathinfo($name); + if(!$pathinfo) + { + $this->error = Translations::translate('Could not read pathinfo'); + + return false; + } + + $filename = $pathinfo['filename'] . '.' . $pathinfo['extension']; + $filepath = $this->tmpFolder . $filename; + + if(!file_exists($this->tmpFolder . $filename)) + { + $this->error = Translations::translate('We did not find the file in the tmp-folder or could not read it') . '.'; + return false; + } + + $success = rename($this->tmpFolder . $filename, $this->fileFolder . $filename); + + if($success === true) + { + # return true; + return 'media/files/' . $filename; + } + + return false; + } + + public function getFileList() + { + $files = scandir($this->fileFolder); + $filelist = array(); + + foreach ($files as $key => $name) + { + if (!in_array($name, array(".","..","filerestrictions.yaml")) && file_exists($this->fileFolder . $name)) + { + $filelist[] = [ + 'name' => $name, + 'timestamp' => filemtime($this->fileFolder . $name), + 'bytes' => filesize($this->fileFolder . $name), + 'info' => pathinfo($this->fileFolder . $name), + 'url' => 'media/files/' . $name, + ]; + } + } + + $filelist = Helpers::array_sort($filelist, 'timestamp', SORT_DESC); + + return $filelist; + } + + public function deleteMediaFile($name) + { + # validate name + $name = basename($name); + + if(file_exists($this->fileFolder . $name) && unlink($this->fileFolder . $name)) + { + return true; + } + + return false; + } + + public function deleteFileWithName($name) + { + # e.g. delete $name = 'logo'; + + $name = basename($name); + + if($name != '' && !in_array($name, array(".",".."))) + { + foreach(glob($this->fileFolder . $name) as $file) + { + unlink($file); + } + } + } + + ################## + ## POST PAGES ## + ################## + + public function transformPagesToPosts($folder) + { + $filetypes = array('md', 'txt', 'yaml'); + $result = true; + + foreach($folder->folderContent as $page) + { + # create old filename without filetype + $oldFile = $this->contentFolder . $page->pathWithoutType; + + # set default date + $date = date('Y-m-d', time()); + $time = date('H-i', time()); + + $meta = $this->getYaml('contentFolder', '', $page->pathWithoutType . '.yaml'); + + if($meta) + { + # get dates from meta + if(isset($meta['meta']['manualdate'])){ $date = $meta['meta']['manualdate']; } + elseif(isset($meta['meta']['created'])){ $date = $meta['meta']['created']; } + elseif(isset($meta['meta']['modified'])){ $date = $meta['meta']['modified']; } + + # set time + if(isset($meta['meta']['time'])) + { + $time = $meta['meta']['time']; + } + } + + $datetime = $date . '-' . $time; + $datetime = implode(explode('-', $datetime)); + $datetime = substr($datetime,0,12); + + # create new file-name without filetype + $newFile = $this->contentFolder . $folder->path . DIRECTORY_SEPARATOR . $datetime . '-' . $page->slug; + + foreach($filetypes as $filetype) + { + $oldFilePath = $oldFile . '.' . $filetype; + $newFilePath = $newFile . '.' . $filetype; + + #check if file with filetype exists and rename + if($oldFilePath != $newFilePath && file_exists($oldFilePath)) + { + if(@rename($oldFilePath, $newFilePath)) + { + $result = $result; + } + else + { + $this->error = "could not rename $oldFilePath to $newFilePath"; + $result = false; + } + } + } + } + + return $result; + } + + public function transformPostsToPages($folder) + { + $filetypes = array('md', 'txt', 'yaml'); + $index = 0; + $result = true; + + foreach($folder->folderContent as $page) + { + # create old filename without filetype + $oldFile = $this->contentFolder . $page->pathWithoutType; + + $order = $index; + + if($index < 10) + { + $order = '0' . $index; + } + + # create new file-name without filetype + $newFile = $this->contentFolder . $folder->path . DIRECTORY_SEPARATOR . $order . '-' . $page->slug; + + foreach($filetypes as $filetype) + { + $oldFilePath = $oldFile . '.' . $filetype; + $newFilePath = $newFile . '.' . $filetype; + + #check if file with filetype exists and rename + if($oldFilePath != $newFilePath && file_exists($oldFilePath)) + { + if(@rename($oldFilePath, $newFilePath)) + { + $result = $result; + } + else + { + $this->error = "could not rename $oldFilePath to $newFilePath"; + $result = false; + } + } + } + + $index++; + } + + return $result; + } +} \ No newline at end of file diff --git a/system/typemill/Models/StorageWrapper.php b/system/typemill/Models/StorageWrapper.php new file mode 100644 index 0000000..306280d --- /dev/null +++ b/system/typemill/Models/StorageWrapper.php @@ -0,0 +1,28 @@ +object = new $classname(); + } + + function __call($method, $args) + { + if(!method_exists($this->object, $method)) + { + throw new \RuntimeException(sprintf('Callable method %s does not exist', $method)); + } + + # Invoke original method on our proxied object + return call_user_func_array([$this->object, $method], $args); + } +} \ No newline at end of file diff --git a/system/typemill/Models/SvgSanitizer.php b/system/typemill/Models/SvgSanitizer.php new file mode 100644 index 0000000..261f686 --- /dev/null +++ b/system/typemill/Models/SvgSanitizer.php @@ -0,0 +1,116 @@ + ['class' => true, 'clip-path' => true, 'clip-rule' => true, 'fill' => true, 'fill-opacity' => true, 'fill-rule' => true, 'filter' => true, 'id' => true, 'mask' => true, 'opacity' => true, 'stroke' => true, 'stroke-dasharray' => true, 'stroke-dashoffset' => true, 'stroke-linecap' => true, 'stroke-linejoin' => true, 'stroke-miterlimit' => true, 'stroke-opacity' => true, 'stroke-width' => true, 'style' => true, 'systemLanguage' => true, 'transform' => true, 'xlink:title' => true], + 'circle' => ['class' => true, 'clip-path' => true, 'clip-rule' => true, 'cx' => true, 'cy' => true, 'fill' => true, 'fill-opacity' => true, 'fill-rule' => true, 'filter' => true, 'id' => true, 'mask' => true, 'opacity' => true, 'r' => true, 'requiredFeatures' => true, 'stroke' => true, 'stroke-dasharray' => true, 'stroke-dashoffset' => true, 'stroke-linecap' => true, 'stroke-linejoin' => true, 'stroke-miterlimit' => true, 'stroke-opacity' => true, 'stroke-width' => true, 'style' => true, 'systemLanguage' => true, 'transform' => true], + 'clipPath' => ['class' => true, 'clipPathUnits' => true, 'id' => true], + 'defs' => [], + 'style' => ['type' => true], + 'desc' => [], + 'ellipse' => ['class' => true, 'clip-path' => true, 'clip-rule' => true, 'cx' => true, 'cy' => true, 'fill' => true, 'fill-opacity' => true, 'fill-rule' => true, 'filter' => true, 'id' => true, 'mask' => true, 'opacity' => true, 'requiredFeatures' => true, 'rx' => true, 'ry' => true, 'stroke' => true, 'stroke-dasharray' => true, 'stroke-dashoffset' => true, 'stroke-linecap' => true, 'stroke-linejoin' => true, 'stroke-miterlimit' => true, 'stroke-opacity' => true, 'stroke-width' => true, 'style' => true, 'systemLanguage' => true, 'transform' => true], + 'feGaussianBlur' => ['class' => true, 'color-interpolation-filters' => true, 'id' => true, 'requiredFeatures' => true, 'stdDeviation' => true], + 'filter' => ['class' => true, 'color-interpolation-filters' => true, 'filterRes' => true, 'filterUnits' => true, 'height' => true, 'id' => true, 'primitiveUnits' => true, 'requiredFeatures' => true, 'width' => true, 'x' => true, 'y' => true], + 'foreignObject' => ['class' => true, 'font-size' => true, 'height' => true, 'id' => true, 'opacity' => true, 'requiredFeatures' => true, 'style' => true, 'transform' => true, 'width' => true, 'x' => true, 'y' => true], + 'g' => ['class' => true, 'clip-path' => true, 'clip-rule' => true, 'id' => true, 'display' => true, 'fill' => true, 'fill-opacity' => true, 'fill-rule' => true, 'filter' => true, 'mask' => true, 'opacity' => true, 'requiredFeatures' => true, 'stroke' => true, 'stroke-dasharray' => true, 'stroke-dashoffset' => true, 'stroke-linecap' => true, 'stroke-linejoin' => true, 'stroke-miterlimit' => true, 'stroke-opacity' => true, 'stroke-width' => true, 'style' => true, 'systemLanguage' => true, 'transform' => true, 'font-family' => true, 'font-size' => true, 'font-style' => true, 'font-weight' => true, 'text-anchor' => true], + 'image' => ['class' => true, 'clip-path' => true, 'clip-rule' => true, 'filter' => true, 'height' => true, 'id' => true, 'mask' => true, 'opacity' => true, 'requiredFeatures' => true, 'style' => true, 'systemLanguage' => true, 'transform' => true, 'width' => true, 'x' => true, 'xlink:title' => true, 'y' => true], + 'line' => ['class' => true, 'clip-path' => true, 'clip-rule' => true, 'fill' => true, 'fill-opacity' => true, 'fill-rule' => true, 'filter' => true, 'id' => true, 'marker-end' => true, 'marker-mid' => true, 'marker-start' => true, 'mask' => true, 'opacity' => true, 'requiredFeatures' => true, 'stroke' => true, 'stroke-dasharray' => true, 'stroke-dashoffset' => true, 'stroke-linecap' => true, 'stroke-linejoin' => true, 'stroke-miterlimit' => true, 'stroke-opacity' => true, 'stroke-width' => true, 'style' => true, 'systemLanguage' => true, 'transform' => true, 'x1' => true, 'x2' => true, 'y1' => true, 'y2' => true], + 'linearGradient' => ['class' => true, 'id' => true, 'gradientTransform' => true, 'gradientUnits' => true, 'requiredFeatures' => true, 'spreadMethod' => true, 'systemLanguage' => true, 'x1' => true, 'x2' => true, 'y1' => true, 'y2' => true], + 'marker' => ['id' => true, 'class' => true, 'markerHeight' => true, 'markerUnits' => true, 'markerWidth' => true, 'orient' => true, 'preserveAspectRatio' => true, 'refX' => true, 'refY' => true, 'systemLanguage' => true, 'viewBox' => true], + 'mask' => ['class' => true, 'height' => true, 'id' => true, 'maskContentUnits' => true, 'maskUnits' => true, 'width' => true, 'x' => true, 'y' => true], + 'metadata' => ['class' => true, 'id' => true], + 'path' => ['class' => true, 'clip-path' => true, 'clip-rule' => true, 'd' => true, 'fill' => true, 'fill-opacity' => true, 'fill-rule' => true, 'filter' => true, 'id' => true, 'marker-end' => true, 'marker-mid' => true, 'marker-start' => true, 'mask' => true, 'opacity' => true, 'requiredFeatures' => true, 'stroke' => true, 'stroke-dasharray' => true, 'stroke-dashoffset' => true, 'stroke-linecap' => true, 'stroke-linejoin' => true, 'stroke-miterlimit' => true, 'stroke-opacity' => true, 'stroke-width' => true, 'style' => true, 'systemLanguage' => true, 'transform' => true], + 'pattern' => ['class' => true, 'height' => true, 'id' => true, 'patternContentUnits' => true, 'patternTransform' => true, 'patternUnits' => true, 'requiredFeatures' => true, 'style' => true, 'systemLanguage' => true, 'viewBox' => true, 'width' => true, 'x' => true, 'y' => true], + 'polygon' => ['class' => true, 'clip-path' => true, 'clip-rule' => true, 'id' => true, 'fill' => true, 'fill-opacity' => true, 'fill-rule' => true, 'filter' => true, 'id' => true, 'class' => true, 'marker-end' => true, 'marker-mid' => true, 'marker-start' => true, 'mask' => true, 'opacity' => true, 'points' => true, 'requiredFeatures' => true, 'stroke' => true, 'stroke-dasharray' => true, 'stroke-dashoffset' => true, 'stroke-linecap' => true, 'stroke-linejoin' => true, 'stroke-miterlimit' => true, 'stroke-opacity' => true, 'stroke-width' => true, 'style' => true, 'systemLanguage' => true, 'transform' => true], + 'polyline' => ['class' => true, 'clip-path' => true, 'clip-rule' => true, 'id' => true, 'fill' => true, 'fill-opacity' => true, 'fill-rule' => true, 'filter' => true, 'marker-end' => true, 'marker-mid' => true, 'marker-start' => true, 'mask' => true, 'opacity' => true, 'points' => true, 'requiredFeatures' => true, 'stroke' => true, 'stroke-dasharray' => true, 'stroke-dashoffset' => true, 'stroke-linecap' => true, 'stroke-linejoin' => true, 'stroke-miterlimit' => true, 'stroke-opacity' => true, 'stroke-width' => true, 'style' => true, 'systemLanguage' => true, 'transform' => true], + 'radialGradient' => ['class' => true, 'cx' => true, 'cy' => true, 'fx' => true, 'fy' => true, 'gradientTransform' => true, 'gradientUnits' => true, 'id' => true, 'r' => true, 'requiredFeatures' => true, 'spreadMethod' => true, 'systemLanguage' => true], + 'rect' => ['class' => true, 'clip-path' => true, 'clip-rule' => true, 'fill' => true, 'fill-opacity' => true, 'fill-rule' => true, 'filter' => true, 'height' => true, 'id' => true, 'mask' => true, 'opacity' => true, 'requiredFeatures' => true, 'rx' => true, 'ry' => true, 'stroke' => true, 'stroke-dasharray' => true, 'stroke-dashoffset' => true, 'stroke-linecap' => true, 'stroke-linejoin' => true, 'stroke-miterlimit' => true, 'stroke-opacity' => true, 'stroke-width' => true, 'style' => true, 'systemLanguage' => true, 'transform' => true, 'width' => true, 'x' => true, 'y' => true], + 'stop' => ['class' => true, 'id' => true, 'offset' => true, 'requiredFeatures' => true, 'stop-color' => true, 'stop-opacity' => true, 'style' => true, 'systemLanguage' => true], + 'svg' => ['class' => true, 'clip-path' => true, 'clip-rule' => true, 'filter' => true, 'id' => true, 'height' => true, 'mask' => true, 'preserveAspectRatio' => true, 'requiredFeatures' => true, 'style' => true, 'systemLanguage' => true, 'viewBox' => true, 'width' => true, 'x' => true, 'xmlns' => true, 'xmlns:se' => true, 'xmlns:xlink' => true, 'y' => true], + 'switch' => ['class' => true, 'id' => true, 'requiredFeatures' => true, 'systemLanguage' => true], + 'symbol' => ['class' => true, 'fill' => true, 'fill-opacity' => true, 'fill-rule' => true, 'filter' => true, 'font-family' => true, 'font-size' => true, 'font-style' => true, 'font-weight' => true, 'id' => true, 'opacity' => true, 'preserveAspectRatio' => true, 'requiredFeatures' => true, 'stroke' => true, 'stroke-dasharray' => true, 'stroke-dashoffset' => true, 'stroke-linecap' => true, 'stroke-linejoin' => true, 'stroke-miterlimit' => true, 'stroke-opacity' => true, 'stroke-width' => true, 'style' => true, 'systemLanguage' => true, 'transform' => true, 'viewBox' => true], + 'text' => ['class' => true, 'clip-path' => true, 'clip-rule' => true, 'fill' => true, 'fill-opacity' => true, 'fill-rule' => true, 'filter' => true, 'font-family' => true, 'font-size' => true, 'font-style' => true, 'font-weight' => true, 'id' => true, 'mask' => true, 'opacity' => true, 'requiredFeatures' => true, 'stroke' => true, 'stroke-dasharray' => true, 'stroke-dashoffset' => true, 'stroke-linecap' => true, 'stroke-linejoin' => true, 'stroke-miterlimit' => true, 'stroke-opacity' => true, 'stroke-width' => true, 'style' => true, 'systemLanguage' => true, 'text-anchor' => true, 'transform' => true, 'x' => true, 'xml:space' => true, 'y' => true], + 'textPath' => ['class' => true, 'id' => true, 'method' => true, 'requiredFeatures' => true, 'spacing' => true, 'startOffset' => true, 'style' => true, 'systemLanguage' => true, 'transform' => true], + 'title' => [], + 'tspan' => ['class' => true, 'clip-path' => true, 'clip-rule' => true, 'dx' => true, 'dy' => true, 'fill' => true, 'fill-opacity' => true, 'fill-rule' => true, 'filter' => true, 'font-family' => true, 'font-size' => true, 'font-style' => true, 'font-weight' => true, 'id' => true, 'mask' => true, 'opacity' => true, 'requiredFeatures' => true, 'rotate' => true, 'stroke' => true, 'stroke-dasharray' => true, 'stroke-dashoffset' => true, 'stroke-linecap' => true, 'stroke-linejoin' => true, 'stroke-miterlimit' => true, 'stroke-opacity' => true, 'stroke-width' => true, 'style' => true, 'systemLanguage' => true, 'text-anchor' => true, 'textLength' => true, 'transform' => true, 'x' => true, 'xml:space' => true, 'y' => true], + 'use' => ['class' => true, 'clip-path' => true, 'clip-rule' => true, 'fill' => true, 'fill-opacity' => true, 'fill-rule' => true, 'filter' => true, 'height' => true, 'id' => true, 'mask' => true, 'stroke' => true, 'stroke-dasharray' => true, 'stroke-dashoffset' => true, 'stroke-linecap' => true, 'stroke-linejoin' => true, 'stroke-miterlimit' => true, 'stroke-opacity' => true, 'stroke-width' => true, 'style' => true, 'transform' => true, 'width' => true, 'x' => true, 'y' => true], + ]; + + function __construct() { + $this->xmlDoc = new \DOMDocument(); + $this->xmlDoc->preserveWhiteSpace = false; + } + + //Load the SVG data from a file + function load($file) { + $this->xmlDoc->load($file); + } + + function loadSVG(string $string) { + $result = $this->xmlDoc->loadXML($string); + } + + //Remove any elements from the XML that are unrelated to SVGs + function sanitize() { + + //Get every element in the document, and loop through them all + $allElements = $this->xmlDoc->getElementsByTagName("*"); + + for ($i = 0; $i < $allElements->length; $i++) { + $currentNode = $allElements->item($i); + + //Remove any elements not on the whitelist + if (!isset(self::$whitelist[$currentNode->tagName])) { + $currentNode->parentNode->removeChild($currentNode); + $i--; + + } else { + $attributesWhitelist = self::$whitelist[$currentNode->tagName]; + $attributesToRemove = []; + + //If the element is allowed, loop through checking its attributes v.s. the attributes allowed for that element + for ($j = 0; $j < $currentNode->attributes->length; $j++) { + $attrName = $currentNode->attributes->item($j)->name; + + if (!isset($attributesWhitelist[$attrName])) { + $attributesToRemove[] = $attrName; + } + } + + //Remove any blocked attributes + if (!empty($attributesToRemove)) { + foreach ($attributesToRemove as $attrName) { + $currentNode->removeAttribute($attrName); + } + } + } + } + } + + function saveSVG() { + $this->xmlDoc->formatOutput = true; + return($this->xmlDoc->saveXML()); + } + + function save($file) { + $this->xmlDoc->formatOutput = true; + return($this->xmlDoc->save($file)); + } +} diff --git a/system/typemill/Models/User.php b/system/typemill/Models/User.php new file mode 100644 index 0000000..0c3a08f --- /dev/null +++ b/system/typemill/Models/User.php @@ -0,0 +1,452 @@ +userDir = getcwd() . '/settings/users'; + $this->storage = new StorageWrapper('\Typemill\Models\Storage'); + } + + public function setUser(string $username) + { + $this->user = $this->storage->getYaml('settingsFolder', 'users', $username . '.yaml'); + + if(!$this->user) + { + $this->error = Translations::translate('User not found'); + + return false; + } + + # delete password from public userdata + unset($this->user['password']); + + return $this; + } + + public function setUserWithPassword(string $username) + { + $this->user = $this->storage->getYaml('settingsFolder', 'users', $username . '.yaml'); + + if(!$this->user) + { + $this->error = Translations::translate('User not found'); + + return false; + } + + return $this; + } + + public function getValue($key) + { + if(isset($this->user[$key])) + { + return $this->user[$key]; + } + return false; + } + + public function setValue($key, $value) + { + $this->user[$key] = $value; + } + + public function unsetValue($key) + { + unset($this->user[$key]); + } + + public function getUserData() + { + return $this->user; + } + + public function getFullName() + { + $firstname = $this->user['firstname'] ?? ''; + $lastname = $this->user['lastname'] ?? ''; + $fullname = trim($firstname . ' ' . $lastname); + + if($fullname != '') + { + return $fullname; + } + + return false; + } + + public function getError() + { + return $this->error; + } + + public function getAllUsers() + { + # check if users directory exists + if(!is_dir($this->userDir)) + { + $this->error = $this->userDir . Translations::translate('does not exist'); + + return false; + } + + # get all user files + $userfiles = array_diff(scandir($this->userDir), array('..', '.', '.logins', 'tmuserindex-mail.txt', 'tmuserindex-role.txt')); + + $usernames = []; + + if(!empty($userfiles)) + { + foreach($userfiles as $key => $userfile) + { + $usernames[] = str_replace('.yaml', '', $userfile); + } + + usort($usernames, 'strnatcasecmp'); + } + + return $usernames; + } + + public function createUser(array $params) + { + $params['password'] = $this->generatePassword($params['password']); + + if($this->storage->updateYaml('settingsFolder', 'users', $params['username'] . '.yaml', $params)) + { + $this->deleteUserIndex(); + + return true; + } + + $this->error = $this->storage->getError(); + + return false; + } + + public function updateUser() + { + if($this->storage->updateYaml('settingsFolder', 'users', $this->user['username'] . '.yaml', $this->user)) + { + $this->deleteUserIndex(); + + return true; + } + + $this->error = $this->storage->getError(); + + return false; + } + + public function deleteUser() + { + if($this->storage->deleteFile('settingsFolder', 'users', $this->user['username'] . '.yaml')) + { + $this->deleteUserIndex(); + + return true; + } + + $this->error = $this->storage->getError(); + + return false; + } + + public function getUserFields($acl, $dispatcher, $userrole, $inspectorrole = NULL, $loginlink = NULL) + { + $storage = new StorageWrapper('\Typemill\Models\Storage'); + $userfields = $storage->getYaml('systemSettings', '', 'user.yaml'); + + if(!$inspectorrole) + { + # if there is no inspector-role we assume that it is the same role like the userrole + # for example account is always visible by the same user + # edit user can be done by another user like admin. + $inspectorrole = $userrole; + } + + # if a plugin with a role has been deactivated, then users with the role throw an error, so set them back to member... + if(!$acl->hasRole($userrole)) + { + $userrole = 'member'; + } + + # dispatch fields; + $customfields = $dispatcher->dispatch(new OnUserfieldsLoaded(['userrole' => $userrole, 'inspectorrole' => $inspectorrole, 'userfields' => $userfields]), 'onUserfieldsLoaded')->getData(); + if($customfields && isset($customfields['userfields']) && is_array($customfields['userfields'])) + { + $userfields = $customfields['userfields']; + } + + # only roles who can edit content need profile image and description + if($acl->isAllowed($userrole, 'mycontent', 'create')) + { + $newfield['image'] = ['label' => Translations::translate('Profile-Image'), 'type' => 'image']; + $newfield['description'] = ['label' => Translations::translate('Author-Description (Markdown)'), 'type' => 'textarea']; + + $userfields = array_slice($userfields, 0, 1, true) + $newfield + array_slice($userfields, 1, NULL, true); + # array_splice($fields,1,0,$newfield); + } + + # Only admin ... + if($acl->isAllowed($inspectorrole, 'user', 'update')) + { + # can change userroles + $definedroles = $acl->getRoles(); + $options = []; + + # we need associative array to make select-field with key/value work + foreach($definedroles as $role) + { + $options[$role] = $role; + } + + $userfields['userrole'] = ['label' => Translations::translate('Role'), 'type' => 'select', 'options' => $options]; + + # can activate api access + $userfields['apiaccess'] = ['label' => Translations::translate('API access'), 'checkboxlabel' => Translations::translate('Activate API access for this user. Use username and password for api calls. Whitelist calling domains in the developer settings.'), 'type' => 'checkbox']; + + if($loginlink) + { + $userfields['linkaccess'] = ['label' => Translations::translate('Link access'), 'checkboxlabel' => Translations::translate('Activate link access for this user (only for member role). Use username and password for the link. Optionally whitelist IPs in the developer settings.'), 'type' => 'checkbox']; + } + } + + return $userfields; + } + + public function login() + { + if($this->user) + { + $this->user['lastlogin'] = time(); + + $_SESSION['username'] = $this->user['username']; + $_SESSION['login'] = $this->user['lastlogin']; + + if(isset($this->user['recovertoken']) OR isset($this->user['recoverdate'])) + { + $this->unsetValue('recovertoken'); + $this->unsetValue('recoverdate'); + } + + # update user last login + $this->updateUser(); + } + } + + public function generatePassword(string $password) + { + return \password_hash($password, PASSWORD_DEFAULT, ['cost' => 10]); + } + +/* + public function getBasicAuth() + { + $basicauth = $this->user['username'] . ":" . $this->user['internalApiKey']; + + return base64_encode($basicauth); + } +*/ + + # accepts email with or without asterix and returns userdata + public function findUsersByEmail(string $email) + { + $usernames = []; + + # Make sure that we scan only the first 11 files even if there are some thousand users. + if ($dh = opendir($this->userDir)) + { + $count = 0; + $exclude = array('..', '.', '.logins', 'tmuserindex-mail.txt', 'tmuserindex-role.txt'); + + while ( ($userfile = readdir($dh)) !== false && $count <= 10 ) + { + if(in_array($userfile, $exclude)){ continue; } + + $usernames[] = str_replace('.yaml', '', $userfile); + $count++; + } + + closedir($dh); + } + + if(count($usernames) == 0) + { + return false; + } + elseif(count($usernames) <= 9) + { + # perform a simple search because we have less than 10 registered users + return $this->searchEmailSimple($usernames,$email); + } + else + { + # perform search in an index for many users + return $this->searchEmailByIndex($email); + } + } + + private function searchEmailSimple(array $usernames, string $email) + { + foreach($usernames as $username) + { + $this->setUser($username); + $user = $this->getUserData(); + + if($user['email'] == $email) + { + return [$username]; + } + } + return false; + } + + private function searchEmailByIndex(string $email) + { + # if there are more than 10 users, search with an index + $usermails = $this->getUserMailIndex(); + $usernames = []; + + # search with starting asterix, ending asterix or without asterix + if($email[0] == '*') + { + $search = substr($email, 1); + $length = strlen($search); + + foreach($usermails as $usermail => $username) + { + if(substr($usermail, -$length) == $search) + { + $usernames[] = $username; + } + } + } + elseif(substr($email, -1) == '*') + { + $search = substr($email, 0, -1); + $length = strlen($search); + + foreach($usermails as $usermail => $username) + { + if(substr($usermail, 0, $length) == $search) + { + $usernames[] = $username; + } + } + } + elseif(isset($usermails[$email])) + { + $usernames[] = $usermails[$email]; + } + + if(empty($usernames)) + { + return false; + } + + return $usernames; + } + + public function getUserMailIndex() + { + if(file_exists($this->userDir . DIRECTORY_SEPARATOR . 'tmuserindex-mail.txt')) + { + # unserialize and return the file + $usermailindex = unserialize(file_get_contents($this->userDir . DIRECTORY_SEPARATOR . 'tmuserindex-mail.txt')); + + if($usermailindex) + { + return $usermailindex; + } + } + + $usernames = $this->getAllUsers(); + $usermailindex = []; + + foreach($usernames as $username) + { + $this->setUser($username); + $userdata = $this->getUserData(); + + $usermailindex[$userdata['email']] = $username; + } + + file_put_contents($this->userDir . DIRECTORY_SEPARATOR . 'tmuserindex-mail.txt', serialize($usermailindex)); + + return $usermailindex; + } + + public function findUsersByRole($role) + { + $userroles = $this->getUserRoleIndex(); + + if(isset($userroles[$role])) + { + return $userroles[$role]; + } + + return false; + } + + public function getUserRoleIndex() + { + if(file_exists($this->userDir . DIRECTORY_SEPARATOR . 'tmuserindex-role.txt')) + { + # unserialize and return the file + $userroleindex = unserialize(file_get_contents($this->userDir . DIRECTORY_SEPARATOR . 'tmuserindex-role.txt')); + if($userroleindex) + { + return $userroleindex; + } + } + + $usernames = $this->getAllUsers(); + $userroleindex = []; + + foreach($usernames as $username) + { + + $this->setUser($username); + $userdata = $this->getUserData(); + + $userroleindex[$userdata['userrole']][] = $username; + } + + file_put_contents($this->userDir . DIRECTORY_SEPARATOR . 'tmuserindex-role.txt', serialize($userroleindex)); + + return $userroleindex; + } + + protected function deleteUserIndex() + { + + if(file_exists($this->userDir . DIRECTORY_SEPARATOR . 'tmuserindex-mail.txt')) + { + unlink($this->userDir . DIRECTORY_SEPARATOR . 'tmuserindex-mail.txt'); + } + + if(file_exists($this->userDir . DIRECTORY_SEPARATOR . 'tmuserindex-role.txt')) + { + unlink($this->userDir . DIRECTORY_SEPARATOR . 'tmuserindex-role.txt'); + } + } +} \ No newline at end of file diff --git a/system/typemill/Models/Validation.php b/system/typemill/Models/Validation.php new file mode 100644 index 0000000..0e14332 --- /dev/null +++ b/system/typemill/Models/Validation.php @@ -0,0 +1,1011 @@ +findUsersByEmail($email)){ return false; } + return true; + }, 'taken'); + + # checks if email is available if userdata is updated + Validator::addRule('emailChanged', function($field, $value, array $params, array $fields) use ($user) + { + $user->setUserWithPassword($fields['username']); + $userdata = $user->getUserData(); + if($userdata['email'] == $value){ return true; } # user has not updated his email + + $email = trim($value); + if($user->findUsersByEmail($email)){ return false; } + return true; + }, 'taken'); + + # checks if username is free when create new user + Validator::addRule('userAvailable', function($field, $value, array $params, array $fields) use ($user) + { + $activeUser = $user->setUser($value); + $inactiveUser = $user->setUser("_" . $value); + if($activeUser OR $inactiveUser){ return false; } + return true; + }, 'taken'); + + # checks if user exists when userdata is updated + Validator::addRule('userExists', function($field, $value, array $params, array $fields) use ($user) + { + if($user->setUser($value)){ return true; } + return false; + }, 'does not exist'); + + Validator::addRule('iplist', function($field, $value, array $params, array $fields) use ($user) + { + $iplist = explode(",", $value); + foreach($iplist as $ip) + { + if( filter_var( trim($ip), \FILTER_VALIDATE_IP) === false) + { + return false; + } + } + return true; + }, 'contains one or more invalid ip-adress.'); + + Validator::addRule('customfields', function($field, $customfields, array $params, array $fields) use ($user) + { + if(empty($customfields)) + { + return true; + } + foreach($customfields as $key => $value) + { + if(!isset($key) OR empty($key) OR (preg_match('/^([a-z0-9])+$/i', $key) == false) ) + { + return false; + } + + if (!isset($value) OR empty($value) OR ( $value != strip_tags($value) ) ) + { + return false; + } + } + return true; + }, 'some fields are empty or contain invalid values.'); + + Validator::addRule('checkPassword', function($field, $value, array $params, array $fields) use ($user) + { + if($user->setUserWithPassword($fields['username'])) + { + $userdata = $user->getUserData(); + if(password_verify($value, $userdata['password'])) + { + return true; + } + } + return false; + }, 'wrong password'); + + Validator::addRule('navigation', function($field, $value, array $params, array $fields) + { +# $format = '/[@#^*()=\[\]{};:"\\|,.<>\/]/'; + $format = '/^(?![ .])[^\0\/\\?%*:|"<>]+(?]+[!?]$/'; + + if ( preg_match($format, $value) === 1) + { + return true; + } + return false; + }, 'contains invalid characters or patterns'); + + Validator::addRule('noSpecialChars', function($field, $value, array $params, array $fields) + { + $format = '/[!@#$%^&*()_+=\[\]{};\':"\\|,.<>\/?]/'; + if ( preg_match($format, $value)) + { + return false; + } + return true; + }, 'contains special characters'); + + Validator::addRule('noHTML', function($field, $value, array $params, array $fields) + { + if ( $value == strip_tags($value) ) + { + return true; + } + return false; + }, 'contains html'); + + Validator::addRule('markdownSecure', function($field, $value, array $params, array $fields) + { + # strip out code blocks + $value = preg_replace('/`{4,}[\s\S]+?`{4,}/', '', $value); + $value = preg_replace('/`{3,}[\s\S]+?`{3,}/', '', $value); + $value = preg_replace('/`{2,}[\s\S]+?`{2,}/', '', $value); + $value = preg_replace('/`{1,}[\s\S]+?`{1,}/', '', $value); + + # check blockquotes + preg_match_all('/^ ?>[\s\S]+?[\n\r]/m', $value, $matches); + foreach($matches as $match) + { + $content = $match[0] ?? false; + if($content && (strip_tags($content) !== $content) ) + { + return false; + } + } + + # strip out blockquotes + $value = preg_replace('/^ ?>[\s\S]+?[\n\r]/m', '', $value); + + if ( $value == strip_tags($value) ) + { + return true; + } + return false; + + }, 'not secure. For code please use markdown `inline-code` or ````fenced code blocks````.'); + + Validator::addRule('checkLicense', function($field, $value, array $params, array $fields) + { + $parts = explode("-",$value); + if(count($parts) != 5) + { + return false; + } + if($parts[0] != "TM2") + { + return false; + } + unset($parts[0]); + foreach($parts as $key => $part) + { + if(strlen($part) != 5 OR !preg_match("/^[A-Z0-9]*$/",$part) ) + { + return false; + } + } + + return true; + }, 'format is not valid.'); + + Validator::addRule('version', function($field, $value, array $params, array $fields) + { + if( version_compare( $value, '0.0.1', '>=' ) >= 0 ) + { + return true; + } + return false; + }, 'not a valid version format.'); + + Validator::addRule('version_array', function($field, $value, array $params, array $fields) + { + foreach($value as $name => $version) + { + if(!preg_match("/^[A-Za-z0-9_\- ]+$/", $name)) + { + return false; + } + + if( version_compare( $version, '0.0.1', '>=' ) <= 0 ) + { + return false; + } + } + + return true; + }, 'not a valid version format.'); + } + + # return valitron standard object + public function returnValidator(array $params) + { + return new Validator($params); + } + + public function returnFirstValidationErrors($errors) + { + foreach($errors as $key => $error) + { + $errors[$key] = $error[0]; + } + + return $errors; + } + + /** + * validation for signin form + * + * @param array $params with form data. + * @return obj $v the validation object passed to a result method. + */ + + public function signin(array $params) + { + $v = new Validator($params); + $v->rule('required', ['username', 'password'])->message("Required"); + $v->rule('alphaNum', 'username')->message("Invalid characters"); + $v->rule('lengthBetween', 'password', 5, 256)->message("Length between 5 - 256"); + $v->rule('lengthBetween', 'username', 3, 40)->message("Length between 3 - 40"); + + if($v->validate()) + { + return true; + } + + return false; + } + + /** + * validation for authcode confirmation + * + * @param array $params with form data. + * @return obj $v the validation object passed to a result method. + */ + + public function authcode(array $params) + { + $v = new Validator($params); + $v->rule('required', ['username', 'code-1', 'code-2', 'code-3', 'code-4', 'code-5'])->message("Required"); + $v->rule('alphaNum', 'username')->message("Invalid characters"); + $v->rule('regex', 'code-1', '/^[0-9]{1}$/')->message("Must be 1-9"); + $v->rule('regex', 'code-2', '/^[0-9]{1}$/')->message("Must be 1-9"); + $v->rule('regex', 'code-3', '/^[0-9]{1}$/')->message("Must be 1-9"); + $v->rule('regex', 'code-4', '/^[0-9]{1}$/')->message("Must be 1-9"); + $v->rule('regex', 'code-5', '/^[0-9]{1}$/')->message("Must be 1-9"); + + if($v->validate()) + { + return true; + } + + return false; + } + + + /** + * validation for setup user (in backoffice) + * + * @param array $params with form data. + * @return obj $v the validation object passed to a result method. + */ + + public function newSetupUser(array $params, array $userroles) + { + $v = new Validator($params); + $v->rule('required', ['username', 'email', 'password'])->message("required"); + $v->rule('alphaNum', 'username')->message("invalid characters"); + $v->rule('lengthBetween', 'password', 5, 256)->message("Length between 5 - 256"); + $v->rule('lengthBetween', 'username', 3, 40)->message("Length between 3 - 40"); + $v->rule('userAvailable', 'username')->message("User already exists"); + $v->rule('noHTML', 'firstname')->message(" contains HTML"); + $v->rule('lengthBetween', 'firstname', 2, 40); + $v->rule('noHTML', 'lastname')->message(" contains HTML"); + $v->rule('lengthBetween', 'lastname', 2, 40); + $v->rule('email', 'email')->message("e-mail is invalid"); + $v->rule('emailAvailable', 'email')->message("Email already taken"); + $v->rule('in', 'userrole', $userroles); + + if($v->validate()) + { + return true; + } + + if(isset($_SESSION)) + { + $_SESSION['errors'] = $v->errors(); + } + + return $v->errors(); + } + + + /** + * validation for new user (in backoffice) + * + * @param array $params with form data. + * @return obj $v the validation object passed to a result method. + */ + + public function newUser(array $params, array $userroles) + { + $v = new Validator($params); + $v->rule('required', ['username', 'email', 'password'])->message("required"); + $v->rule('alphaNum', 'username')->message("invalid characters"); + $v->rule('lengthBetween', 'password', 5, 256)->message("Length between 5 - 256"); + $v->rule('lengthBetween', 'username', 3, 40)->message("Length between 3 - 40"); + $v->rule('userAvailable', 'username')->message("User already exists"); + $v->rule('noHTML', 'firstname')->message(" contains HTML"); + $v->rule('lengthBetween', 'firstname', 2, 40); + $v->rule('noHTML', 'lastname')->message(" contains HTML"); + $v->rule('lengthBetween', 'lastname', 2, 40); + $v->rule('email', 'email')->message("e-mail is invalid"); + $v->rule('emailAvailable', 'email')->message("Email already taken"); + $v->rule('in', 'userrole', $userroles); + + if($v->validate()) + { + return true; + } + + return $v->errors(); + } + + # change user in backoffice + public function existingUser(array $params, $userroles) + { + $v = new Validator($params); + $v->rule('required', ['username', 'email', 'userrole'])->message("required"); + $v->rule('alphaNum', 'username')->message("invalid"); + $v->rule('lengthBetween', 'username', 3, 40)->message("Length between 3 - 40"); + $v->rule('userExists', 'username')->message("user does not exist"); + $v->rule('noHTML', 'firstname')->message(" contains HTML"); + $v->rule('lengthBetween', 'firstname', 2, 40); + $v->rule('noHTML', 'lastname')->message(" contains HTML"); + $v->rule('lengthBetween', 'lastname', 2, 40); + $v->rule('email', 'email')->message("e-mail is invalid"); + $v->rule('emailChanged', 'email')->message("Email already taken"); + $v->rule('in', 'userrole', $userroles); + + if($v->validate()) + { + return true; + } + + return $v->errors(); + } + + public function username(array $params) + { + $v = new Validator($params); + $v->rule('required', ['username'])->message("required"); + $v->rule('alphaNum', 'username')->message("invalid"); + $v->rule('lengthBetween', 'username', 3, 40)->message("Length between 3 - 40"); + + if($v->validate()) + { + return true; + } + + return $v->errors(); + } + + public function emailsearch(array $params) + { + # param can be "trend*" + $v = new Validator($params); + $v->rule('required', ['email'])->message("required"); + $v->rule('noHTML', 'email')->message(" contains HTML"); + $v->rule('lengthBetween', 'email', 3, 50)->message("Length between 3 - 50"); + + if($v->validate()) + { + return true; + } + + return $v->errors(); + } + + public function newLicense(array $params) + { + $v = new Validator($params); + $v->rule('required', ['license', 'email', 'domain']); + $v->rule('checkLicense', 'license'); + $v->rule('email', 'email'); + $v->rule('url', 'domain'); + + if($v->validate()) + { + return true; + } + + return $v->errors(); + } + + public function activateExtension(array $params) + { + $v = new Validator($params); + $v->rule('required', ['name', 'type', 'checked']); + $v->rule('in', 'type', ['plugins', 'themes']); + $v->rule('boolean', 'checked'); + + if($v->validate()) + { + return true; + } + + return $v->errors(); + } + + public function checkVersions(array $params) + { + $v = new Validator($params); + $v->rule('required', ['type', 'data']); + $v->rule('in', 'type', ['plugins', 'themes', 'system']); + + if(!$v->validate()) + { + return $v->errors(); + } + + if($params['type'] == 'plugins' OR $params['type'] == 'themes') + { + $v->rule('version_array', 'data'); + } + else + { + $v->rule('version', 'data'); + } + + if(!$v->validate()) + { + return $v->errors(); + } + + return true; + } + + public function navigationSort(array $params) + { + $v = new Validator($params); + + $v->rule('required', ['item_id']); + $v->rule('regex', 'item_id', '/^[0-9.]+$/i'); + $v->rule('regex', 'parent_id_from', '/^[a-zA-Z0-9.]+$/i'); + $v->rule('regex', 'parent_id_to', '/^[a-zA-Z0-9.]+$/i'); + $v->rule('integer', 'index_new'); + $v->rule('integer', 'index_old'); + + if($v->validate()) + { + return true; + } + + return $v->errors(); + } + + public function navigationItem(array $params) + { + $v = new Validator($params); + + $v->rule('required', ['folder_id', 'item_name', 'type']); + $v->rule('regex', 'folder_id', '/^(root)|([0-9.]+)$/i'); + $v->rule('navigation', 'item_name'); + $v->rule('lengthBetween', 'item_name', 1, 60); + $v->rule('in', 'type', ['file', 'folder']); + + if($v->validate()) + { + return true; + } + + return $v->errors(); + } + + public function articleUrl(array $params) + { + $v = new Validator($params); + + $v->rule('required', 'url'); + $v->rule('regex', 'url', '/^\/?[a-z0-9\-\/]+(?:\?[a-z0-9\-=&]*)?$/i'); + + if($v->validate()) + { + return true; + } + + return $v->errors(); + } + + public function articleSlug(array $params) + { + $v = new Validator($params); + + $v->rule('required', 'slug'); + $v->rule('regex', 'slug', '/^[a-z0-9-]+$/i'); + + if($v->validate()) + { + return true; + } + + return $v->errors(); + } + + public function blockInput(array $params) + { + $v = new Validator($params); + + $v->rule('required', ['markdown', 'block_id', 'url']); + $v->rule('markdownSecure', 'markdown'); + $v->rule('regex', 'block_id', '/^[0-9.]+$/i'); + + if($v->validate()) + { + return true; + } + + return $v->errors(); + } + + public function blockMove(array $params) + { + $v = new Validator($params); + + $v->rule('required', ['index_new', 'index_old', 'url']); + $v->rule('regex', 'index_new', '/^[0-9.]+$/i'); + $v->rule('regex', 'index_old', '/^[0-9.]+$/i'); + + if($v->validate()) + { + return true; + } + + return $v->errors(); + } + + public function blockDelete(array $params) + { + $v = new Validator($params); + + $v->rule('required', ['block_id', 'url']); + $v->rule('regex', 'block_id', '/^[0-9.]+$/i'); + + if($v->validate()) + { + return true; + } + + return $v->errors(); + } + + public function articlePublish(array $params) + { + $v = new Validator($params); + + # special conditions for startpage + if(isset($params['item_id']) && $params['item_id'] == '') + { + $v->rule('required', ['url']); + $v->rule('markdownSecure', 'markdown'); + } + else + { + $v->rule('required', ['item_id', 'url']); + $v->rule('regex', 'item_id', '/^[0-9.]+$/i'); + $v->rule('markdownSecure', 'markdown'); + } + + if($v->validate()) + { + return true; + } + + return $v->errors(); + } + + public function articleUpdate(array $params) + { + $v = new Validator($params); + + # special conditions for startpage + if(isset($params['item_id']) && $params['item_id'] == '') + { + $v->rule('required', ['url', 'title', 'body']); + $v->rule('markdownSecure', 'title'); + $v->rule('markdownSecure', 'body'); + } + else + { + $v->rule('required', ['item_id', 'url', 'title', 'body']); + $v->rule('regex', 'item_id', '/^[0-9.]+$/i'); + $v->rule('markdownSecure', 'title'); + $v->rule('markdownSecure', 'body'); + } + + if($v->validate()) + { + return true; + } + + return $v->errors(); + } + + public function articleRename(array $params) + { + $v = new Validator($params); + + $v->rule('required', ['url', 'slug', 'oldslug']); + $v->rule('regex', 'slug', '/^[a-z0-9\-]*$/'); + $v->rule('lengthBetween', 'slug', 1, 50)->message("Length between 1 - 50"); + $v->rule('different', 'slug', 'oldslug'); + + if($v->validate()) + { + return true; + } + + return $v->errors(); + } + + public function metaInput(array $params) + { + $v = new Validator($params); + + $v->rule('required', ['url', 'tab', 'data']); + + if($v->validate()) + { + return true; + } + + return $v->errors(); + } + + public function kixotePrompt(array $params) + { + $v = new Validator($params); + + $v->rule('required', 'title', 'content', 'active', 'system'); + $v->rule('regex', 'title', '/^[a-z0-9 ]+$/i'); + $v->rule('lengthBetween', 'title', 2,20); + $v->rule('noHTML', 'content'); + $v->rule('lengthBetween', 'content',2,5000); + $v->rule('boolean', 'active'); + $v->rule('boolean', 'system'); + + if($v->validate()) + { + return true; + } + + return $v->errors(); + } + + /** + * validation for password recovery + * + * @param array $params with form data. + * @return obj $v the validation object passed to a result method. + */ + + public function recoverPassword(array $params) + { + $v = new Validator($params); + $v->rule('required', ['password', 'passwordrepeat']); + $v->rule('lengthBetween', 'password', 5, 256); + $v->rule('equals', 'passwordrepeat', 'password'); + + if($v->validate()) + { + return true; + } + + return $v->errors(); + } + + public function newPassword(array $params) + { + $v = new Validator($params); + $v->rule('required', ['password', 'newpassword']); + $v->rule('lengthBetween', 'newpassword', 5, 256); + + if($v->validate()) + { + return true; + } + + return $v->errors(); + } + + public function newPasswordAdmin(array $params) + { + $v = new Validator($params); + $v->rule('required', ['newpassword']); + $v->rule('lengthBetween', 'newpassword', 5, 256); + + if($v->validate()) + { + return true; + } + + return $v->errors(); + } + + + /** + * validation for content editor + * + * @param array $params with form data. + * @return true or $v->errors with array of errors to use in json-response + */ + + public function editorInput(array $params) + { + $v = new Validator($params); + + $v->rule('required', ['title', 'content', 'url']); + $v->rule('lengthBetween', 'title', 2, 100); + $v->rule('noHTML', 'title'); + $v->rule('markdownSecure', 'content'); + + if($v->validate()) + { + return true; + } + + return $v->errors(); + } + + /** + * validation for dynamic fields ( settings for themes and plugins) + * + * @param string $fieldName with the name of the field. + * @param array or string $fieldValue with the values of the field. + * @param string $objectName with the name of the plugin or theme. + * @param array $fieldDefinitions with the field definitions as multi-dimensional array. + * @return obj $v the validation object passed to a result method. + */ + + public function field($fieldName, $fieldValue, $fieldDefinitions) + { + $v = new Validator(array($fieldName => $fieldValue)); + + if(isset($fieldDefinitions['required'])) + { + $v->rule('required', $fieldName); + } + if(isset($fieldDefinitions['maxlength'])) + { + $v->rule('lengthMax', $fieldName, $fieldDefinitions['maxlength']); + } + if(isset($fieldDefinitions['max'])) + { + $v->rule('max', $fieldName, $fieldDefinitions['max']); + } + if(isset($fieldDefinitions['min'])) + { + $v->rule('min', $fieldName, $fieldDefinitions['min']); + } + if(isset($fieldDefinitions['pattern'])) + { + $v->rule('regex', $fieldName, '/^' . $fieldDefinitions['pattern'] . '$/'); + } + + switch($fieldDefinitions['type']) + { + case "checkbox": + if(isset($fieldDefinitions['required'])) + { + $v->rule('accepted', $fieldName); + } + break; + case "checkboxlist": + if(isset($fieldValue) && is_array($fieldValue)) + { + # create array with option keys as value + $options = array(); + foreach($fieldDefinitions['options'] as $key => $value){ $options[] = $key; } + + # loop over input values and check, if the options of the field definitions (options for checkboxlist) contains the key (input from user, key is used as value, value is used as label) + foreach($fieldValue as $key => $value) + { + $v->rule('in', $key, $options); + } + } + break; + case "codearea": + $v->rule('lengthMax', $fieldName, 50000); +# how prevent bad code here? + break; + case "color": + $v->rule('regex', $fieldName, '/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/'); + break; + case "customfields": + $v->rule('array', $fieldName); + $v->rule('customfields', $fieldName); + break; + case "date": + $v->rule('date', $fieldName); + break; + case "email": + $v->rule('email', $fieldName); + break; + case "image": + $v->rule('noHTML', $fieldName); + $v->rule('lengthMax', $fieldName, 1000); + $v->rule('image_types', $fieldName); + break; + case "file": + $v->rule('noHTML', $fieldName); + $v->rule('lengthMax', $fieldName, 1000); + break; + case "number": + $v->rule('integer', $fieldName); + break; + case "paragraph": + $v->rule('noHTML', $fieldName); + $v->rule('lengthMax', $fieldName, 10000); + break; + case "password": + $v->rule('lengthMax', $fieldName, 500); + break; + case "radio": + $v->rule('in', $fieldName, $fieldDefinitions['options']); + break; + case "select": + # create array with option keys as value + $options = array(); + foreach($fieldDefinitions['options'] as $key => $value){ $options[] = $key; } + $v->rule('in', $fieldName, $options); + break; + case "text": + $v->rule('noHTML', $fieldName); + $v->rule('lengthMax', $fieldName, 1000); +# $v->rule('regex', $fieldName, '/^[\pL0-9_ \-\.\?\!\/\:]*$/u'); + break; + case "textarea": + # it understands array, json, yaml + if(is_array($fieldValue)) + { + $v = $this->checkArray($fieldValue, $v); + } + else + { + $v->rule('noHTML', $fieldName); + $v->rule('lengthMax', $fieldName, 10000); + } + break; + case "url": + $v->rule('url', $fieldName); + $v->rule('lengthMax', $fieldName, 200); + break; + default: + $v->rule('lengthMax', $fieldName, 1000); + $v->rule('regex', $fieldName, '/^[\pL0-9_ \-]*$/u'); + } + + if(!$v->validate()) + { + return $v->errors(); + } + + return true; + } + + + # validate a whole formdefinition with all values + public function recursiveValidation(array $formdefinitions, $input, $output = []) + { + # loop through form-definitions, ignores everything that is not defined in yaml + foreach($formdefinitions as $fieldname => $fielddefinitions) + { + if(is_array($fielddefinitions) && $fielddefinitions['type'] == 'fieldset') + { + $output = $this->recursiveValidation($fielddefinitions['fields'], $input, $output); + } + + # do not store values for disabled fields + if(isset($fielddefinitions['disabled']) && $fielddefinitions['type']) + { + continue; + } + + if(isset($input[$fieldname])) + { + $fieldvalue = $input[$fieldname]; + + # fix false or null values for selectboxes + if($fielddefinitions['type'] == "select" && ($fieldvalue === 'NULL' OR $fieldvalue === false)) + { + $fieldvalue = NULL; + } + + $validationresult = $this->field($fieldname, $fieldvalue, $fielddefinitions); + + if($validationresult === true) + { + # MOVE THIS TO A SEPARATE FUNCTION SO YOU CAN STORE IMAGES ONLY IF ALL FIELDS SUCCESSFULLY VALIDATED + # images have special treatment, check ProcessImage-Model and ImageApiController + if($fielddefinitions['type'] == 'image') + { + # then check if file is there already: check for name and maybe correct image extension (if quality has been changed) + $storage = new StorageWrapper('\Typemill\Models\Storage'); + $existingImagePath = $storage->checkImage($fieldvalue); + + if($existingImagePath) + { + $fieldvalue = $existingImagePath; + } + else + { + # there is no published image with that name, so check if there is an unpublished image in tmp folder and publish it + $newImagePath = $storage->publishImage($fieldvalue); + if($newImagePath) + { + $fieldvalue = $newImagePath; + } + else + { + $fieldvalue = ''; + } + } + } + + if($fielddefinitions['type'] == 'file') + { + # then check if file is there already: check for name and maybe correct image extension (if quality has been changed) + $storage = new StorageWrapper('\Typemill\Models\Storage'); + $existingFilePath = $storage->checkFileExists($fieldvalue); + + if($existingFilePath) + { + $fieldvalue = $existingFilePath; + } + else + { + # there is no published file with that name, so check if there is an unpublished image in tmp folder and publish it + $newFilePath = $storage->publishFile($fieldvalue); + if($newFilePath) + { + $fieldvalue = $newFilePath; + } + else + { + $fieldvalue = ''; + } + } + } + + $output[$fieldname] = $fieldvalue; + } + else + { + $this->errors[$fieldname] = $validationresult[$fieldname][0]; + } + } + } + + return $output; + } +} diff --git a/system/typemill/Plugin.php b/system/typemill/Plugin.php new file mode 100644 index 0000000..709a618 --- /dev/null +++ b/system/typemill/Plugin.php @@ -0,0 +1,385 @@ +container = $container; + $this->urlinfo = $this->container->get('urlinfo'); + $this->route = $this->urlinfo['route']; + + if($this->route != '/') + { + $this->route = ltrim($this->route, '/'); + } + + if(str_starts_with($this->route, 'tm/')) + { + $this->adminroute = true; + } + + if(str_starts_with($this->route, 'tm/content/')) + { + $this->editorroute = true; + } + } + + protected function getSettings() + { + return $this->container->get('settings'); + } + + protected function getPluginSettings($pluginname = false) + { +# $pluginClass = debug_backtrace(!DEBUG_BACKTRACE_PROVIDE_OBJECT|DEBUG_BACKTRACE_IGNORE_ARGS,2)[1]['class']; + + $pluginname = $this->getPluginName($pluginname); + + if($pluginname && isset($this->container->get('settings')['plugins'][$pluginname])) + { + return $this->container->get('settings')['plugins'][$pluginname]; + } + + return false; + } + + protected function getPluginData($filename, $pluginname = false) + { + $pluginname = $this->getPluginName($pluginname); + + $storageClass = $this->container->get('settings')['storage']; + $storage = new StorageWrapper($storageClass); + + $data = $storage->getFile('dataFolder', $pluginname, $filename); + + return $data; + } + + protected function getPluginYamlData($filename, $pluginname = false) + { + $pluginname = $this->getPluginName($pluginname); + + $storageClass = $this->container->get('settings')['storage']; + $storage = new StorageWrapper($storageClass); + + $data = $storage->getYaml('dataFolder', $pluginname, $filename); + + return $data; + } + + protected function storePluginData($filename, $data, $method = NULL, $pluginname = false) + { + $pluginname = $this->getPluginName($pluginname); + + $storageClass = $this->container->get('settings')['storage']; + $storage = new StorageWrapper($storageClass); + + $result = $storage->writeFile('dataFolder', $pluginname, $filename, $data, $method = NULL); + + if($result) + { + return true; + } + + return $storage->getError(); + } + + protected function storePluginYamlData(string $filename, array $data, $pluginname = false) + { + $pluginname = $this->getPluginName($pluginname); + + # validation + $extension = new Extension(); + $pluginDefinitions = $extension->getPluginDefinition($pluginname); + $formDefinitions = $pluginDefinitions['system']['fields'] ?? false; + + if($formDefinitions) + { +# where can we add this method so we can use it everywhere? +# $formdefinitions = $this->addDatasets($formdefinitions); + + $validate = new Validation(); + + $validatedOutput = $validate->recursiveValidation($formDefinitions, $data); + if(!empty($validate->errors)) + { + return $validate->errors; + } + } + + $storageClass = $this->container->get('settings')['storage']; + $storage = new StorageWrapper($storageClass); + + $result = $storage->updateYaml('dataFolder', $pluginname, $filename, $data); + + if($result) + { + return true; + } + + return $storage->getError(); + } + + protected function deletePluginData($filename, $pluginname = false) + { + $pluginname = $this->getPluginName($pluginname); + + $storageClass = $this->container->get('settings')['storage']; + $storage = new StorageWrapper($storageClass); + + $data = $storage->deleteFile('dataFolder', $pluginname, $filename); + + return $data; + } + + private function getPluginName($pluginname = NULL) + { + if(!$pluginname) + { + $classname = get_called_class(); + + if ($pos = strrpos($classname, '\\')) + { + $pluginname = strtolower(substr($classname, $pos + 1)); + } + } + + return $pluginname; + } + + protected function urlinfo() + { + return $this->container->get('urlinfo'); + } + + protected function getDispatcher() + { + return $this->container->get('dispatcher'); + } + + protected function getTwig() + { + return $this->container->get('view'); + } + + protected function addTwigGlobal($name, $class) + { + $this->container->get('view')->getEnvironment()->addGlobal($name, $class); + } + + protected function addTwigFilter($name, $filter) + { + $filter = new \Twig_SimpleFilter($name, $filter); + $this->container->get('view')->getEnvironment()->addFilter($filter); + } + + protected function addTwigFunction($name, $function) + { + $function = new \Twig_SimpleFunction($name, $function); + $this->container->get('view')->getEnvironment()->addFunction($function); + } + + protected function addJS($JS, $attr="") + { + $this->container->get('assets')->addJS($JS, $attr); + } + + protected function addInlineJS($JS, $attr="") + { + $this->container->get('assets')->addInlineJS($JS, $attr); + } + + protected function addBloxConfigJS($JS) + { + $this->container->get('assets')->addBloxConfigJS($JS); + } + + protected function addBloxConfigInlineJS($JS) + { + $this->container->get('assets')->addBloxConfigInlineJS($JS); + } + + protected function addSvgSymbol($symbol) + { + $this->container->get('assets')->addSvgSymbol($symbol); + } + + protected function addCSS($CSS) + { + $this->container->get('assets')->addCSS($CSS); + } + + protected function addInlineCSS($CSS) + { + $this->container->get('assets')->addInlineCSS($CSS); + } + + protected function getMeta() + { + return $this->container->get('assets')->meta; + } + + public function addMeta($key,$meta) + { + $this->container->get('assets')->addMeta($key, $meta); + } + + protected function activateAxios() + { + $this->container->get('assets')->activateAxios(); + } + + protected function activateVue() + { + $this->container->get('assets')->activateVue(); + } + + protected function markdownToHtml($markdown) + { + $parsedown = new ParsedownExtension(); + + $contentArray = $parsedown->text($markdown); + $html = $parsedown->markup($contentArray); + + return $html; + } + + protected function getFormData($pluginName) + { + return true; + $flash = $this->container->flash->getMessages(); + + if(isset($flash['formdata'])) + { + $yaml = new Models\WriteYaml(); + $formdata = $yaml->getYaml('settings', 'formdata.yaml'); + $yaml->updateYaml('settings', 'formdata.yaml', ''); + + if($flash['formdata'][0] == $pluginName && isset($formdata[$pluginName])) + { + return $formdata[$pluginName]; + } + } + elseif(isset($flash['publicform']) && $flash['publicform'][0] == 'bot') + { + return 'bot'; + } + return false; + } + + protected function generateForm($routename, $pluginname = NULL) + { + $form = false; + + $fieldsModel = new Fields(); + $extensionModel = new Extension(); + + $pluginSettings = $this->getPluginSettings(); + $pluginName = $this->getPluginName($pluginname); + $pluginDefinitions = $extensionModel->getPluginDefinition($pluginName); + + # add field-definitions entered into author interface + if(isset($pluginSettings['publicformdefinitions']) && $pluginSettings['publicformdefinitions'] != '') + { + $arrayFromYaml = \Symfony\Component\Yaml\Yaml::parse($pluginSettings['publicformdefinitions']); + $pluginDefinitions['public']['fields'] = $arrayFromYaml; + } + + $buttonlabel = isset($pluginSettings['button_label']) ? $pluginSettings['button_label'] : false; + $captchaoptions = isset($pluginSettings['captchaoptions']) ? $pluginSettings['captchaoptions'] : false; + $recaptcha = isset($pluginSettings['recaptcha']) ? $pluginSettings['recaptcha_webkey'] : false; + + if($captchaoptions == 'disabled') + { + # in case a captcha has failed on another page like login, the captcha-session must be deleted, otherwise it will not pass the security middleware + unset($_SESSION['captcha']); + } + + $fieldsModel = new Fields(); + + if(isset($pluginDefinitions['public']['fields'])) + { + $settings = $this->container->get('settings'); + + # get all the fields and prefill them with the dafault-data, the user-data or old input data + $fields = $fieldsModel->getFields($settings, 'plugins', $pluginName, $pluginDefinitions, 'public'); + + # get Twig Instance + $twig = $this->getTwig(); + + # render each field and add it to the form + $form = $twig->fetch('/partials/form.twig', [ + 'routename' => $routename, + 'fields' => $fields, + 'itemName' => $pluginName, + 'object' => 'plugins', + 'buttonlabel' => $buttonlabel, + 'captchaoptions' => $captchaoptions, + 'recaptcha_webkey' => $recaptcha, + ]); + } + + return $form; + } + + protected function validateParams($params) + { + $pluginname = $this->getPluginName(); + $userinput = $params[$pluginname] ?? false; + + if(!$userinput) + { + return false; + } + + $pluginsettings = $this->getPluginSettings($pluginname); + $extension = new Extension(); + $formdefinitions = $extension->getPluginDefinition($pluginname); + + # if there are public form definitions, add them to the formdefinitions + if(isset($pluginsettings['publicformdefinitions']) && $pluginsettings['publicformdefinitions'] != '') + { + $arrayFromYaml = \Symfony\Component\Yaml\Yaml::parse($pluginsettings['publicformdefinitions']); + $formdefinitions['public']['fields'] = $arrayFromYaml; + } + elseif(isset($formdefinitions['settings']['publicformdefinitions'])) + { + $arrayFromYaml = \Symfony\Component\Yaml\Yaml::parse($formdefinitions['settings']['publicformdefinitions']); + $formdefinitions['public']['fields'] = $arrayFromYaml; + } + + $validate = new Validation(); + $validatedOutput = $validate->recursiveValidation($formdefinitions['public']['fields'], $userinput); + + if(!empty($validate->errors)) + { + $_SESSION['errors'] = $validate->errors; + + return false; + } + + return $validatedOutput; + } +} \ No newline at end of file diff --git a/system/typemill/Static/Helpers.php b/system/typemill/Static/Helpers.php new file mode 100644 index 0000000..818602f --- /dev/null +++ b/system/typemill/Static/Helpers.php @@ -0,0 +1,114 @@ +getFile('dataFolder', 'security', 'securitylog.txt'); + + if($logfile) + { + $logfile .= $line . PHP_EOL; + } + else + { + $logfile = $line . PHP_EOL; + } + + $result = $storage->writeFile('dataFolder', 'security', 'securitylog.txt', $logfile); + + if($result) + { + return true; + } + + return $storage->getError(); + } + + public static function array_sort($array, $on, $order=SORT_ASC) + { + $new_array = array(); + $sortable_array = array(); + + if (count($array) > 0) { + foreach ($array as $k => $v) { + if (is_array($v)) { + foreach ($v as $k2 => $v2) { + if ($k2 == $on) { + $sortable_array[$k] = $v2; + } + } + } else { + $sortable_array[$k] = $v; + } + } + + switch ($order) { + case SORT_ASC: + asort($sortable_array); + break; + case SORT_DESC: + arsort($sortable_array); + break; + } + + foreach ($sortable_array as $k => $v) { + $new_array[] = $array[$k]; + } + } + + return $new_array; + } + + public static function printTimer($timer) + { + $lastTime = NULL; + $table = ''; + $table .= ''; + foreach($timer as $breakpoint => $time) + { + $duration = $time - $lastTime; + + $table .= ''; + $table .= ''; + $table .= ''; + $table .= ''; + $table .= ''; + + $lastTime = $time; + } + $table .= '
          BreakpointTimeDuration
          ' . $breakpoint . '' . $time . '' . $duration . '
          '; + echo $table; + } +} \ No newline at end of file diff --git a/system/typemill/Static/Permissions.php b/system/typemill/Static/Permissions.php new file mode 100644 index 0000000..31d9ffc --- /dev/null +++ b/system/typemill/Static/Permissions.php @@ -0,0 +1,68 @@ +addResource(new Resource($resource)); + } + + # add all other roles dynamically + foreach($roles as $role) + { + $acl->addRole(new Role($role['name']), $role['inherits']); + + foreach($role['permissions'] as $resource => $permissions) + { + $acl->allow($role['name'], $resource, $permissions); + } + } + + # add administrator role + $acl->addRole(new Role('administrator')); + $acl->allow('administrator'); + + return $acl; + } +} \ No newline at end of file diff --git a/system/typemill/Static/Plugins.php b/system/typemill/Static/Plugins.php new file mode 100644 index 0000000..def143a --- /dev/null +++ b/system/typemill/Static/Plugins.php @@ -0,0 +1,129 @@ + $className, 'name' => $plugin]; + } + } + + return $classNames; + } + + public static function scanPluginFolder($rootpath) + { + $pluginsDir = $rootpath . '/plugins'; + + # check if plugins directory exists + if(!is_dir($pluginsDir)){ return array(); } + + # get all plugin folders + $plugins = array_diff(scandir($pluginsDir), array('..', '.')); + + return $plugins; + } + + public static function getNewRoutes($className, $routes) + { + # if route-method exists in plugin-class + if(method_exists($className, 'addNewRoutes')) + { + # add the routes + $pluginRoutes = $className::addNewRoutes(); + + foreach($pluginRoutes as $pluginRoute) + { + if(self::checkRouteArray($routes,$pluginRoute)) + { + $routeType = (substr($pluginRoute['route'], 0,5) == '/api/') ? 'api' : 'web'; + $pluginRoute['route'] = strtolower($pluginRoute['route']); + $routes[$routeType][] = $pluginRoute; + } + } + } + + return $routes; + } + + public static function getNewMiddleware($className, $middleware) + { + if(method_exists($className, 'addNewMiddleware')) + { + $pluginMiddleware = $className::addNewMiddleware(); + + if($pluginMiddleware) + { + $middleware[] = $pluginMiddleware; + } + } + + return $middleware; + } + + public static function getPremiumLicense($className) + { + $premiumlist = [ + '\Plugins\html\html' => 'MAKER', + '\Plugins\html\register' => 'MAKER', + '\Plugins\html\seo' => 'MAKER', + '\Plugins\html\embed' => 'MAKER', + '\Plugins\html\ebookproducts' => 'MAKER', + '\Plugins\html\bettersearch' => 'MAKER', + '\Plugins\html\templates' => 'BUSINESS', + '\Plugins\html\revisions' => 'BUSINESS', + ]; + + if(isset($premiumList['className'])) + { + return $premiumList['className']; + } + + if(method_exists($className, 'setPremiumLicense')) + { + return $className::setPremiumLicense(); + } + + return false; + } + + private static function checkRouteArray($routes,$route) + { + if( + isset($route['httpMethod']) AND in_array($route['httpMethod'], array('get','post','put','delete','head','patch','options')) + AND isset($route['route']) AND is_string($route['route']) + AND isset($route['class']) AND is_string($route['class']) + AND isset($route['name']) AND is_string($route['name']) + ) + { + return true; + } + return false; + } + + private function in_array_r($needle, $haystack, $strict = false) + { + foreach ($haystack as $item) + { + if (($strict ? $item === $needle : $item == $needle) || (is_array($item) && $this->in_array_r($needle, $item, $strict))) + { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/system/typemill/Static/Session.php b/system/typemill/Static/Session.php new file mode 100644 index 0000000..907378e --- /dev/null +++ b/system/typemill/Static/Session.php @@ -0,0 +1,72 @@ +getYaml('themeFolder', $settings['theme'], $language . '.yaml') ?? []; + + if($environment == 'admin') + { + $system_translations = $storage->getYaml('translationFolder', '', $language . '.yaml'); + + # Next change, to provide labels for the admin and user environments. + # There may be plugins that only work in the user environment, only in the admin environment, or in both environments. + $plugin_labels = []; + if(isset($settings['plugins']) && !empty($settings['plugins'])) + { + foreach($settings['plugins'] as $plugin => $config) + { + if(isset($config['active']) && $config['active']) + { + $plugins_translations[$plugin] = $storage->getYaml('pluginFolder', $plugin, $language . '.yaml'); + } + } + + foreach($plugins_translations as $key => $value) + { + if(is_array($value)) + { + $plugins_translations = array_merge($plugins_translations, $value); + } + } + } + } + + $translations = []; + if(is_array($plugins_translations) && !empty($plugins_translations)) + { + $translations = array_merge($translations, $plugins_translations); + } + if(is_array($system_translations) && !empty($system_translations)) + { + $translations = array_merge($translations, $system_translations); + } + if(is_array($theme_translations) && !empty($theme_translations)) + { + $translations = array_merge($translations, $theme_translations); + } + return $translations; + } + + public static function whichLanguage() + { + # Check which languages are available + $langs = self::getLanguages(); + + # Detect browser language + $accept_lang = isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2) : false; + $lang = in_array($accept_lang, $langs) ? $accept_lang : 'en'; + + return $lang; + } + + public static function getLanguages() + { + # Check which languages are available + $langs = []; + $path = __DIR__ . '/../author/translations/*.yaml'; + + foreach (glob($path) as $filename) + { + $langs[] = basename($filename,'.yaml'); + } + + return $langs; + } + + # this just returns the string so you can use translate-function in system files. Everything that is wrapped in translate function will be added to translation files + public static function translate(string $string) + { + return $string; + } +} \ No newline at end of file diff --git a/system/typemill/Static/Urlinfo.php b/system/typemill/Static/Urlinfo.php new file mode 100644 index 0000000..bdb5445 --- /dev/null +++ b/system/typemill/Static/Urlinfo.php @@ -0,0 +1,159 @@ +withUserInfo(''); + + # remove standard ports to fix csp error + # alternatively add ports to csp header + $uri = self::removeStandardPorts($uri); + + $currentpath = $uri->getPath(); + $route = $currentpath; + if(strpos($currentpath, $basepath) === 0) + { + $route = substr_replace($currentpath, '', 0, strlen($basepath)); + } + + $query = $uri->getQuery(); + parse_str($query, $params); + + # proxy detection + if(isset($settings['proxy']) && $settings['proxy']) + { + $trustedProxies = ( isset($settings['trustedproxies']) && !empty($settings['trustedproxies']) ) ? explode(",", $settings['trustedproxies']) : []; + + if(self::checkIp($trustedProxies)) + { + $uri = self::updateHost($uri); + + $uri = self::updateProto($uri); + + $uri = self::updatePort($uri); + + $basepath = self::updateBasepath($basepath); + } + } + + $scheme = $uri->getScheme(); + $authority = $uri->getAuthority(); + $protocol = ($scheme ? $scheme . ':' : '') . ($authority ? '//' . $authority : ''); + $baseurl = $protocol . $basepath; + $currenturl = $baseurl . $route; + + return [ + 'basepath' => $basepath, + 'currentpath' => $currentpath, + 'route' => $route, + 'scheme' => $scheme, + 'authority' => $authority, + 'protocol' => $protocol, + 'baseurl' => $baseurl, + 'baseurlWithoutProxy' => false, # add the base url without proxy maybe needed for license? + 'currenturl' => $currenturl, + 'params' => $params + ]; + } + + private static function removeStandardPorts($uri) + { + $port = $uri->getPort(); + if (!$port || $port == 80 || $port == 443) + { + $uri = $uri->withPort(null); + } + + return $uri; + } + + private static function updateProto($uri) + { + # get host from proxy + $host = $_SERVER['HTTP_X_FORWARDED_HOST'] ?? null; + if ( + $host + ) + { + $host = trim(current(explode(',', $host))); + + $pos = strpos($host, ':'); + if ($pos !== false) + { + $host = strstr($host, ':', true); + } + $uri = $uri->withHost($host); + } + + return $uri; + } + + private static function updateHost($uri) + { + # get scheme from proxy + $scheme = $_SERVER['HTTP_X_FORWARDED_PROTO'] ?? null; + if ( + $scheme + && in_array($scheme, ['http', 'https']) + ) + { + $uri = $uri->withScheme($scheme); + } + + return $uri; + } + + private static function updateBasepath($basepath) + { + $basepath = ""; + + # if proxy has basepath, then + if (isset($_SERVER['HTTP_X_FORWARDED_PREFIX'])) + { + # Use X-Forwarded-Prefix if available + $basepath = rtrim($_SERVER['HTTP_X_FORWARDED_PREFIX'], '/') . '/'; + } + + return $basepath; + } + + protected static function updatePort($uri) + { + $port = $_SERVER['HTTP_X_FORWARDED_PORT'] ?? null; + + if ($port) + { + $port = trim(current(explode(',', $port))); + + if (preg_match('/^\d+\z/', $port)) + { + return $uri->withPort((int) $port); + } + } + + return $uri; + } + + private static function checkIp($trustedProxies) + { + # optionally check trusted proxies + $ipAddress = $_SERVER['REMOTE_ADDR'] ?? null; + if ( + $ipAddress + && !empty($trustedProxies) + && !in_array($ipAddress, $trustedProxies) + ) + { + return false; + } + + return true; + } +} \ No newline at end of file diff --git a/system/typemill/author/404.twig b/system/typemill/author/404.twig new file mode 100644 index 0000000..23c2911 --- /dev/null +++ b/system/typemill/author/404.twig @@ -0,0 +1 @@ +404 from author diff --git a/system/typemill/author/auth/authcode.twig b/system/typemill/author/auth/authcode.twig new file mode 100644 index 0000000..dc0094d --- /dev/null +++ b/system/typemill/author/auth/authcode.twig @@ -0,0 +1,109 @@ +{% extends 'layouts/layoutAuth.twig' %} + +{% block title %}Login Verification Code{% endblock %} + +{% block content %} + +
          + +
          +
          + +

          Login Verification Code

          + +

          {{ translate('Enter the verification code from your email:') }} + +

          + +
          + +
          + + + + + +
          + + + + + + + +
          + +
          +
          +
          + +
          +
          +

          {{ authtitle }}

          +

          {{ authtext }}

          + → {{ translate('Back to login') }} +
          +
          + +
          +{% endblock %} \ No newline at end of file diff --git a/system/typemill/author/auth/login.twig b/system/typemill/author/auth/login.twig new file mode 100644 index 0000000..24c1a52 --- /dev/null +++ b/system/typemill/author/auth/login.twig @@ -0,0 +1,89 @@ +{% extends 'layouts/layoutAuth.twig' %} + +{% block title %}Login{% endblock %} + +{% block content %} + +
          + +
          +
          + +

          Login

          + +
          + +
          + +
          + + + {% if errors.signup_username %} + {{ errors.username|first }} + {% endif %} +
          + +
          + + + {% if errors.password %} + {{ errors.password|first }} + {% endif %} +
          + + + + {% if captcha == 'standard' %} + + {{ captcha(true) }} + + {% elseif captcha == 'aftererror' %} + + {{ captcha(old) }} + + {% else %} + + {{ clearcaptcha() }} + + {% endif %} + + + + {% if recover %} + + {% endif %} + +
          + +
          +
          +
          + +
          +
          +

          {{ translate('Welcome back') }}

          +

          {{ translate('Login to the author area or go to the') }} {{ translate('homepage') }}

          +
          +
          + +
          +{% endblock %} \ No newline at end of file diff --git a/system/typemill/author/auth/recover.twig b/system/typemill/author/auth/recover.twig new file mode 100644 index 0000000..e469e40 --- /dev/null +++ b/system/typemill/author/auth/recover.twig @@ -0,0 +1,68 @@ +{% extends 'layouts/layoutAuth.twig' %} + +{% block title %}Login{% endblock %} + +{% block content %} + +
          + +
          +
          + +
          + +
          + +
          + + + {% if errors.email %} + {{ errors.email|first }} + {% endif %} +
          + + + + {% if captcha == 'standard' %} + + {{ captcha(true) }} + + {% elseif captcha == 'aftererror' %} + + {{ captcha(old) }} + + {% endif %} + + + + + +
          + +
          +
          +
          + +
          +
          +

          {{ translate('Forgot your password') }}?

          +

          {{ translate('Enter the email of your user-account, click the recover-button and check your mailbox for further instructions.') }}

          +
          +
          + +
          +{% endblock %} \ No newline at end of file diff --git a/system/typemill/author/auth/recoverconf.twig b/system/typemill/author/auth/recoverconf.twig new file mode 100644 index 0000000..ee83b29 --- /dev/null +++ b/system/typemill/author/auth/recoverconf.twig @@ -0,0 +1,32 @@ +{% extends 'layouts/layoutAuth.twig' %} + +{% block title %}Login{% endblock %} + +{% block content %} + +
          + +
          +
          + +
          +

          {{ translate('Done') }}!

          +
          + +
          +
          + +
          + +
          +

          {{ title }}

          +

          {{ message }}

          +

          {{ translate('go to login') }}

          +
          + +
          + +
          +{% endblock %} \ No newline at end of file diff --git a/system/typemill/author/auth/reset.twig b/system/typemill/author/auth/reset.twig new file mode 100644 index 0000000..ec5fbbc --- /dev/null +++ b/system/typemill/author/auth/reset.twig @@ -0,0 +1,75 @@ +{% extends 'layouts/layoutAuth.twig' %} + +{% block title %}Login{% endblock %} + +{% block content %} + +
          + +
          +
          + +
          + +
          + +
          + + + {% if errors.password %} + {{ errors.password|first }} + {% endif %} +
          + +
          + + + {% if errors.passwordrepeat %} + {{ errors.passwordrepeat|first }} + {% endif %} +
          + + + + + + + + +
          + +
          +
          +
          + +
          +
          +

          {{ translate('Forgot your password') }}?

          +

          {{ translate('No problem, you can create a new one here.') }} {{ translate('And you might find the following tips helpful') }}:

          +
            +
          • {{ translate('Use a strong and individual password for every account.') }}
          • +
          • {{ translate('Your browser can remember all of your passwords.') }}

            +
          • {{ translate('The best option is a separate password manager like keepass and others.') }}
          • +
          +
          +
          + +
          +{% endblock %} \ No newline at end of file diff --git a/system/typemill/author/auth/setup.twig b/system/typemill/author/auth/setup.twig new file mode 100644 index 0000000..3a3c599 --- /dev/null +++ b/system/typemill/author/auth/setup.twig @@ -0,0 +1,93 @@ +{% extends 'layouts/layoutAuth.twig' %} + +{% block title %}{{ translate('Setup') }}{% endblock %} + +{% block content %} + +
          + +
          +
          + +

          {{ translate('Setup') }}

          + +
          + +
          + +
          + + + {% if errors.username %} + {{ errors.username|first }} + {% endif %} +
          + +
          + + + {% if errors.email %} + {{ errors.email|first }} + {% endif %} +
          + +
          + + + {% if errors.password %} + {{ errors.password|first }} + {% endif %} +
          + + + + + +
          + +
          +
          +
          + +
          +
          + {% if systemerrors %} +

          {{ translate('Systemcheck') }}

          +

          {{ translate('The following requirements for the installation are missing') }}:

          +
            + {% for systemerror in systemerrors %} +
          • {{ systemerror }}
          • + {% endfor %} +
          + {% else %} +

          {{ translate('Welcome to Typemill') }}

          +

          {{ translate('Hey writer, author, editor, content-guru, or website-manager.') }} {{ translate(' We hope you like Typemill, because we coded it just for you.') }}

          +

          {{ translate('Get inspired and enjoy your writing') }}!

          + {% endif %} +
          +
          + +
          +{% endblock %} \ No newline at end of file diff --git a/system/typemill/author/content/blox-editor.twig b/system/typemill/author/content/blox-editor.twig new file mode 100644 index 0000000..d28bf59 --- /dev/null +++ b/system/typemill/author/content/blox-editor.twig @@ -0,0 +1,66 @@ +{% extends 'layouts/layoutContent.twig' %} +{% block title %}{{ translate('Visual Editor') }}{% endblock %} + +{% block content %} + +
          + +
          + content + meta +
          + +
          + {% for block in content %} +
          {{ block.html|raw }}
          + {% endfor %} +
          + +
          + + {% if (acl.isAllowed(get_role(), 'content', 'update')) or ( (mycontent) and (acl.isAllowed(get_role(), 'mycontent', 'update')) ) %} + +
          you have the right!
          + + {% endif %} + +
          + +
          + +
          + +
          + +{% endblock %} + + +{% block javascript %} + + + + {{ assets.renderBloxConfigJS()|raw }} + + + + + + + + + +{% endblock %} \ No newline at end of file diff --git a/system/typemill/author/content/raw-editor.twig b/system/typemill/author/content/raw-editor.twig new file mode 100644 index 0000000..171eec6 --- /dev/null +++ b/system/typemill/author/content/raw-editor.twig @@ -0,0 +1,41 @@ +{% extends 'layouts/layoutContent.twig' %} +{% block title %}{{ translate('Raw Editor') }}{% endblock %} + +{% block content %} + +
          + +
          + +
          + +
          + +{% endblock %} + + +{% block javascript %} + + + + + + + + + +{% endblock %} \ No newline at end of file diff --git a/system/typemill/author/css/a11y-dark.min.css b/system/typemill/author/css/a11y-dark.min.css new file mode 100644 index 0000000..7820d7d --- /dev/null +++ b/system/typemill/author/css/a11y-dark.min.css @@ -0,0 +1,7 @@ +pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*! + Theme: a11y-dark + Author: @ericwbailey + Maintainer: @ericwbailey + + Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css +*/.hljs{background:#2b2b2b;color:#f8f8f2}.hljs-comment,.hljs-quote{color:#d4d0ab}.hljs-deletion,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#ffa07a}.hljs-built_in,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-type{color:#f5ab35}.hljs-attribute{color:gold}.hljs-addition,.hljs-bullet,.hljs-string,.hljs-symbol{color:#abe338}.hljs-section,.hljs-title{color:#00e0e0}.hljs-keyword,.hljs-selector-tag{color:#dcc6e0}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}@media screen and (-ms-high-contrast:active){.hljs-addition,.hljs-attribute,.hljs-built_in,.hljs-bullet,.hljs-comment,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-quote,.hljs-string,.hljs-symbol,.hljs-type{color:highlight}.hljs-keyword,.hljs-selector-tag{font-weight:700}} \ No newline at end of file diff --git a/system/typemill/author/css/custom.css b/system/typemill/author/css/custom.css new file mode 100644 index 0000000..b530b08 --- /dev/null +++ b/system/typemill/author/css/custom.css @@ -0,0 +1,703 @@ +.iconwrapper { + position:relative; + height:60px; + width:60px; + overflow: hidden; + border-radius: 30%; +} +.magicicon{ + position: absolute; + top:10px; + left: 10px; + bottom: 10px; + right: 10px; + color: white; + animation: glow .5s infinite alternate; +} +@keyframes glow { + 0% { + fill: black; + filter: drop-shadow(0 0 5px black); + } + 100% { + fill: rgb(15, 50, 46); + filter: drop-shadow(0 0 15px rgb(15, 50, 46)); + } +} + +.loader { + width: 1px; + height:80px; + background: #ccc; + box-shadow: 0 0 60px 10px #eee; + transform: translate(-20px); + clip-path: inset(0); + animation: + l4-1 1s ease-in-out infinite alternate, + l4-2 2s ease-in-out infinite; +} +@keyframes l4-1 { + 100% {transform: translateX(80px)} +} +@keyframes l4-2 { + 33% {clip-path: inset(0 0 0 -100px)} + 50% {clip-path: inset(0 0 0 0) } + 83% {clip-path: inset(0 -100px 0 0)} +} + + +@keyframes wobble-line-with-trailing-shadow { + 0% { + transform: translateY(0); + box-shadow: 0 0 15px 5px rgba(255, 255, 255, 0.8), + 0 20px 30px rgba(255, 255, 255, 0.6), + 0 40px 60px rgba(255, 255, 255, 0.4); + } + 50% { + transform: translateY(60px); + box-shadow: 0 0 15px 5px rgba(255, 255, 255, 0.8), + 0 20px 30px rgba(255, 255, 255, 0.6), + 0 40px 60px rgba(255, 255, 255, 0.4); + } + 100% { + transform: translateY(0); + box-shadow: 0 0 15px 5px rgba(255, 255, 255, 0.8), + 0 20px 30px rgba(255, 255, 255, 0.6), + 0 40px 60px rgba(255, 255, 255, 0.4); + } +} + +#loading-overlay .animate-wobble-line-with-trailing-shadow { + animation: wobble-line-with-trailing-shadow 2s ease-in-out infinite; + background: white; +} + +/******************** +* SVG ICONS * +********************/ + +.icon { + display: inline-block; + width: 1em; + height: 1em; + stroke-width: 0; + stroke: currentColor; + fill: currentColor; +} +.icon.baseline{ + top: 0.125em; + position: relative; +} + + +/******************** +* VUE * +********************/ + +[v-cloak] { + display: none; +} +.initial-enter-active, +.initial-leave-active { + transition: opacity 0.2s ease; +} +.initial-enter-from, +.initial-leave-to { + opacity: 0; +} + +.fade-enter-active { + transition: opacity 0.2s ease; +} +.fade-enter-from{ + opacity: 0; +} +.list-enter-active { + transition: opacity 0.2s ease; +} +.list-enter-from{ + opacity: 0; +} + +.fade-item { + transition: opacity 0.5s ease-in-out; +} + +.fade-enter-active, .fade-leave-active { + transition: opacity 0.5s ease-in-out; +} +.fade-enter, .fade-leave-to { + opacity: 0; +} + +/* enter or fade items in list with transition group */ +.list-enter-active, .list-leave-active { + transition: all 0.2s; +} +.list-enter, .list-leave-to { + opacity: 0; +} + + + +.accordion-enter-active, .accordion-leave-active { + transition: max-height 0.5s ease, padding 0.5s ease; + overflow: hidden; +} + +.accordion-enter, .accordion-leave-to { + max-height: 0; + padding: 0; +} + +.accordion-content { + overflow: hidden; +} + +.editableinput { + border: none; + outline: none; + background-color: transparent; + padding: 0; + resize: none; + min-height: 1rem; /* Set a minimum height */ +} + +/* CODEAREA */ + +.codearea{ + flex: 1 0 20rem; + position: relative; +} +.editor, .highlight { + width: 100%; + font-size: 1rem; +/* font-size: 1.1rem; */ + font-family: monospace; + margin: 0; + padding: 0.7rem 1.4ch; + line-height: 1.313; +} +.highlight { + position: absolute; + top: 0; + left: 0; + width: 100%; + border: 1px solid transparent; + pointer-events: none; + color: white; +} +[data-el="editor"] { + border-width: 1px; + background:rgb(68 64 60); + color: transparent; + caret-color: white; + white-space: break-spaces; + word-break: break-word; + resize: vertical; +} +[data-el="editor"][data-initialized="true"] { + color: transparent; + resize: none; + overflow: hidden; +} +[data-el="highlight"] { + font-family: inherit; + line-height: inherit; + font-size: inherit; + margin: 0; + padding: 0; + white-space: break-spaces; + word-break: break-word; +} + +/******************** +* HIGHLIGHT * +********************/ + +.hljs-comment,.hljs-quote{ + color:#d4d0ab +} +.hljs-deletion,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{ + color:#ffa07a +} +.hljs-built_in,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-type{ + color:#f5ab35 +} +.hljs-attribute{ + color:gold +} +.hljs-addition,.hljs-bullet,.hljs-string,.hljs-symbol{ + color:#abe338 +} +.hljs-section,.hljs-title{ + color:#00e0e0 +} +.hljs-keyword,.hljs-selector-tag{ + color:#dcc6e0 +} +.hljs-emphasis{ + font-style:italic +} +.hljs-strong{ + font-weight:700 +} +@media screen and (-ms-high-contrast:active){ + .hljs-addition,.hljs-attribute,.hljs-built_in,.hljs-bullet,.hljs-comment,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-quote,.hljs-string,.hljs-symbol,.hljs-type{ + color:highlight + } + .hljs-keyword,.hljs-selector-tag{ + font-weight:700 + } +} + +/******************** +* IMAGE BACKGROUND * +********************/ + +.bg-chess { + background-image: repeating-linear-gradient( + 45deg, + #D6D3D1 25%, + transparent 25%, + transparent 75%, + #D6D3D1 75%, + #D6D3D1), + repeating-linear-gradient( + 45deg, + #D6D3D1 25%, + #F5F5F4 25%, + #F5F5F4 75%, + #D6D3D1 75%, + #D6D3D1); + background-position: 0 0, 10px 10px; + background-size: 20px 20px; +} + + +/**************** +** BLOX ** +****************/ + +.transition-1{ + transition: color 0.1s ease, background-color 0.1s ease, border-color 0.1s ease; +} +.transition-2{ + transition: color 0.2s ease, background-color 0.2s ease, border-color 0.2s ease; +} + +/*** Editor Buttons ***/ +.blox-wrapper:hover .sideaction, +.edit .sideaction{ + display: block; +} +.sideaction:hover ~ .blox-preview{ + background: rgb(245 245 244); +} +.dark .sideaction:hover ~ .blox-preview{ + background: rgb(28 25 23); +} + + + +/*** BLOX PREVIEW ***/ + +/* reset margins */ +.blox-preview p, +.blox-preview ul, +.blox-preview ol, +.blox-preview dl, +.blox-preview pre, +.blox-preview blockquote, +.blox-preview .notice1, +.blox-preview .notice2, +.blox-preview .notice3, +.blox-preview .notice4, +.blox-preview .tm-table +{ + margin-top: 0px; + margin-bottom: 0px; +} +.blox-preview ul, .blox-preview ol{ + padding-left: 0px; + margin-left: 18px; +} +.blox-preview ul{ + list-style: disc; +} +.blox-preview ol{ + list-style: decimal; +} +.blox-preview table{ + width: 100%; + border-collapse: collapse; +} +.blox-preview thead{ + border-bottom: 1px solid rgb(20 184 166); + border-top: 1px solid rgb(20 184 166); + background: rgb(245 245 244); + font-weight: 700; +} +.blox-preview tbody{} +.blox-preview th{ padding: 10px 0;} +.blox-preview tr,.blox-editor tr{} +.blox-preview tr:nth-child{ } +.blox-preview tr:nth-child(even){ background-color:rgb(245 245 244); } +.blox-preview td{ padding: 5px;} + +.dark .blox-preview thead{ + background: rgb(28 25 23); +} +.dark .blox-preview tr:nth-child(even){ background-color:transparent; } +.dark table, .dark th, .dark td { + border: 1px solid rgb(87 83 78); +} + +.blox-preview dl{ + border-top: 1px solid rgb(20 184 166); + border-bottom: 1px solid rgb(20 184 166); + padding: 0.5em 0; + box-sizing: border-box; +} +.blox-preview dt, .blox-preview dd{ + width: 100%; + margin-left: 0; + margin-right: 0; + padding: 3px 5px; + box-sizing: border-box; + display: inline-block; + vertical-align: top; +} +.blox-preview dt{ + font-weight: 700; +} +.blox-preview dt::after{ + content: ":"; +} +.blox-preview dd{ + padding-left: 40px; +} +.blox-preview pre,.blox-preview code{ + white-space: pre; + color: #333; + background: rgb(245 245 244); +} +.dark .blox-preview pre,.dark .blox-preview code{ + white-space: pre; + color: #fff; + background: rgb(87 83 78); +} +.blox-preview code{ + display: inline-block; + padding: 0 0.5em; + font-size: 0.8em; + line-height: 1.4em; + border-radius: 3px; +} +.blox-preview code.hljs{ + background: transparent; +} +.blox-preview pre{ + padding: 10px; + display: block; + max-width: 100%; + overflow-x: auto; + border-left: 4px solid rgb(20 184 166); +} +.blox-preview code.language-pagebreak::after{ + content: '---pagebreak with the eBook plugin---'; +} + +.blox-preview blockquote{ + border-left: 4px solid rgb(20 184 166); + background: rgb(245 245 244); + position: relative; + font-style: italic; + font-family: serif; + padding: 5px; + padding-top:12px; + padding-bottom:12px; +} +.dark .blox-preview blockquote{ + color: #fff; + background: rgb(87 83 78); +} + +.blox-preview blockquote:before { + position: absolute; + left: 0px; + top: 0px; + color: #ccc; + content: open-quote; + font-size: 3em; +} +.blox-preview blockquote p{ + margin-left: 50px; +} +.blox-preview a, +.blox-preview a:link, +.blox-preview a:visited +{ + text-decoration: none; + color: rgb(20 184 166); +} +.blox-preview a:focus, +.blox-preview a:hover, +.blox-preview a:active +{ + text-decoration: underline; +} +.blox-preview hr{ + margin-top:24px; + margin-bottom:24px; +} +.blox-preview .notice1, +.blox-preview .notice2, +.blox-preview .notice3, +.blox-preview .notice4{ + position: relative; + border-left: 25px solid rgb(20 184 166); + background: rgb(245 245 244); + padding: 10px; +} +.dark .blox-preview .notice1, +.dark .blox-preview .notice2, +.dark .blox-preview .notice3, +.dark .blox-preview .notice4{ + color: #fff; + background: rgb(87 83 78); +} +.blox-preview .notice1:before, +.blox-preview .notice2:before, +.blox-preview .notice3:before, +.blox-preview .notice4:before{ + width: 25px; + left: -25px; + top: 16px; + position: absolute; + color: white; + text-align: center; +} +.blox-preview .notice1:before{ content: "!"; } +.blox-preview .notice2:before{ content: "!!"; } +.blox-preview .notice3:before{ content: "!!!"; } +.blox-preview .notice4:before{ content: "!!!!"; } + +.blox-preview video, .blox-preview audio, .blox-preview img, img.uploadPreview{ + display: block; + margin: auto; + max-width: 100%; + height: auto; +} +.preview-chess{ + min-height:70px; +} + +.blox-preview .video-container{ + position: relative; + text-align: center; +} +.blox-preview figure.youtube{ + position: relative; +} + +.blox-preview .youtube::before { + position: absolute; + left:50%; + top: 50%; + margin-top: -50px; + margin-left: -50px; + height: 100px; + width: 100px; + background: #e0474c; + color: #FFFFFF; + border-radius: 50%; + border: 0px; + padding: 0; + text-align: center; + content: ''; +} +.blox-preview .youtube::after { + position: absolute; + top: 50%; + left:50%; + margin: -20px 0 0 -15px; + height: 0; + width: 0; + border-style: solid; + border-width: 20px 0 20px 40px; + border-color: transparent transparent transparent rgba(255, 255, 255, 0.75); + content: ''; +} + + +.blox-preview ul.TOC{ + list-style: none; + padding-left: 0px; + margin-left: 0px; +} +.blox-preview .TOC ul{ + list-style: none; +} +.blox-preview .TOC li:before{ + content: "\2192"; + color: #bbb; + margin-left: -7px; + margin-right: 7px; +} +.blox-preview .TOC li.h1:before{ + content: ""; +} +.blox-preview ul.TOC{ + background: rgb(245 245 244); + background: transparent; + width: 100%; + padding: 20px; + box-sizing:border-box; +} + + + +/* UNKNOWN +.blox-preview li.h1{ + font-weight: 700; + height:auto; +} +.blox-preview li.h2, +.blox-preview li.h3, +.blox-preview li.h4, +.blox-preview li.h5, +.blox-preview li.h6{ + font-weight: 400; + padding-left: 25px; + height:auto; +} +*/ + + +.blox-preview h1, .blox-preview h2, .blox-preview h3, .blox-preview h4, .blox-preview h5, .blox-preview h6, +.blox-editor .h1, .blox-editor .h2, .blox-editor .h3, .blox-editor .h4, .blox-editor .h5, .blox-editor .h6 +{ + font-weight: 700; + line-height: 1em; + margin-bottom: 0.6em; +} +.blox-preview h1, +.blox-editor .h1{ + margin-top: 0.6em; + font-size: 2.2em; +} +.blox-preview h2, +.blox-editor .h2{ + margin-top: 1.3em; + font-size: 1.6em; +} +.blox-preview h3, +.blox-editor .h3{ + margin-top: 1.2em; + font-size: 1.3em; + text-transform: none; +} +.blox-preview h4, +.blox-editor .h4{ + margin-top: 1.2em; + font-size: 1.1em; +} +.blox-preview h5, +.blox-editor .h5{ + margin-top: 1.2em; + font-size: 1em; +} +.blox-preview h6, +.blox-editor .h6{ + margin-top: 1em; + font-size: 1em; + font-style: italic; + font-weight:300; +} + + +/************************ +** INLINE FORMATG BAR ** +************************/ + +/* format menu */ +.inlineFormatBar { + box-sizing:content-box; + height: 30px; + padding: 5px 10px; + background: #333; + border-radius: 3px; + position: absolute; + top: 0; + left: 0; + transform: translate(-50%, -100%); + display: flex; + justify-content: center; + align-items: center; +} +/* Triangle below format popup */ +.inlineFormatBar:after { + content: ''; + position: absolute; + left: 50%; + bottom: -5px; + transform: translateX(-50%); + width: 0; + height: 0; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-top: 6px solid #333; +} +.inlineFormatItem { + color: #FFF; + cursor: pointer; + font-size: 0.9em; + padding: 3px; +} +.inlineFormatItem:hover { + color: #1199ff; +} +.inlineFormatItem + .inlineFormatItem { + margin-left: 10px; +} +.inlineFormatItem.inlineFormatLink +{ + margin-left: 2px; + margin-right: 2px; +} +.inlineFormatBar input.urlinput{ + width: 75%; + min-height: auto; + background: #555; + color: #fff; + border: 0px; + padding: 5px; +} +.inlineFormatBar input.urlinput:focus{ + outline: 0px; + border: 0px; +} + +/**************** +** NAVIGATION ** +****************/ + +.pl-15{ + padding-left: 3.6rem; +} +.pl-18{ + padding-left: 4rem; +} +.pl-21{ + padding-left: 5rem; +} + + +/**************** +** Checkmark List ** +****************/ + +ul.list-check { + list-style-type: '\2713'; +} \ No newline at end of file diff --git a/system/typemill/author/css/input.css b/system/typemill/author/css/input.css new file mode 100644 index 0000000..bd6213e --- /dev/null +++ b/system/typemill/author/css/input.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; \ No newline at end of file diff --git a/system/typemill/author/css/output.css b/system/typemill/author/css/output.css new file mode 100644 index 0000000..3e52b95 --- /dev/null +++ b/system/typemill/author/css/output.css @@ -0,0 +1,2524 @@ +/* +! tailwindcss v3.4.4 | MIT License | https://tailwindcss.com +*/ + +/* +1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) +2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) +*/ + +*, +::before, +::after { + box-sizing: border-box; + /* 1 */ + border-width: 0; + /* 2 */ + border-style: solid; + /* 2 */ + border-color: #e5e7eb; + /* 2 */ +} + +::before, +::after { + --tw-content: ''; +} + +/* +1. Use a consistent sensible line-height in all browsers. +2. Prevent adjustments of font size after orientation changes in iOS. +3. Use a more readable tab size. +4. Use the user's configured `sans` font-family by default. +5. Use the user's configured `sans` font-feature-settings by default. +6. Use the user's configured `sans` font-variation-settings by default. +7. Disable tap highlights on iOS +*/ + +html, +:host { + line-height: 1.5; + /* 1 */ + -webkit-text-size-adjust: 100%; + /* 2 */ + -moz-tab-size: 4; + /* 3 */ + -o-tab-size: 4; + tab-size: 4; + /* 3 */ + font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + /* 4 */ + font-feature-settings: normal; + /* 5 */ + font-variation-settings: normal; + /* 6 */ + -webkit-tap-highlight-color: transparent; + /* 7 */ +} + +/* +1. Remove the margin in all browsers. +2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. +*/ + +body { + margin: 0; + /* 1 */ + line-height: inherit; + /* 2 */ +} + +/* +1. Add the correct height in Firefox. +2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) +3. Ensure horizontal rules are visible by default. +*/ + +hr { + height: 0; + /* 1 */ + color: inherit; + /* 2 */ + border-top-width: 1px; + /* 3 */ +} + +/* +Add the correct text decoration in Chrome, Edge, and Safari. +*/ + +abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +/* +Remove the default font size and weight for headings. +*/ + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} + +/* +Reset links to optimize for opt-in styling instead of opt-out. +*/ + +a { + color: inherit; + text-decoration: inherit; +} + +/* +Add the correct font weight in Edge and Safari. +*/ + +b, +strong { + font-weight: bolder; +} + +/* +1. Use the user's configured `mono` font-family by default. +2. Use the user's configured `mono` font-feature-settings by default. +3. Use the user's configured `mono` font-variation-settings by default. +4. Correct the odd `em` font sizing in all browsers. +*/ + +code, +kbd, +samp, +pre { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + /* 1 */ + font-feature-settings: normal; + /* 2 */ + font-variation-settings: normal; + /* 3 */ + font-size: 1em; + /* 4 */ +} + +/* +Add the correct font size in all browsers. +*/ + +small { + font-size: 80%; +} + +/* +Prevent `sub` and `sup` elements from affecting the line height in all browsers. +*/ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* +1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) +2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) +3. Remove gaps between table borders by default. +*/ + +table { + text-indent: 0; + /* 1 */ + border-color: inherit; + /* 2 */ + border-collapse: collapse; + /* 3 */ +} + +/* +1. Change the font styles in all browsers. +2. Remove the margin in Firefox and Safari. +3. Remove default padding in all browsers. +*/ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + /* 1 */ + font-feature-settings: inherit; + /* 1 */ + font-variation-settings: inherit; + /* 1 */ + font-size: 100%; + /* 1 */ + font-weight: inherit; + /* 1 */ + line-height: inherit; + /* 1 */ + letter-spacing: inherit; + /* 1 */ + color: inherit; + /* 1 */ + margin: 0; + /* 2 */ + padding: 0; + /* 3 */ +} + +/* +Remove the inheritance of text transform in Edge and Firefox. +*/ + +button, +select { + text-transform: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Remove default button styles. +*/ + +button, +input:where([type='button']), +input:where([type='reset']), +input:where([type='submit']) { + -webkit-appearance: button; + /* 1 */ + background-color: transparent; + /* 2 */ + background-image: none; + /* 2 */ +} + +/* +Use the modern Firefox focus style for all focusable elements. +*/ + +:-moz-focusring { + outline: auto; +} + +/* +Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) +*/ + +:-moz-ui-invalid { + box-shadow: none; +} + +/* +Add the correct vertical alignment in Chrome and Firefox. +*/ + +progress { + vertical-align: baseline; +} + +/* +Correct the cursor style of increment and decrement buttons in Safari. +*/ + +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +/* +1. Correct the odd appearance in Chrome and Safari. +2. Correct the outline style in Safari. +*/ + +[type='search'] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -2px; + /* 2 */ +} + +/* +Remove the inner padding in Chrome and Safari on macOS. +*/ + +::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Change font properties to `inherit` in Safari. +*/ + +::-webkit-file-upload-button { + -webkit-appearance: button; + /* 1 */ + font: inherit; + /* 2 */ +} + +/* +Add the correct display in Chrome and Safari. +*/ + +summary { + display: list-item; +} + +/* +Removes the default spacing and border for appropriate elements. +*/ + +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +figure, +p, +pre { + margin: 0; +} + +fieldset { + margin: 0; + padding: 0; +} + +legend { + padding: 0; +} + +ol, +ul, +menu { + list-style: none; + margin: 0; + padding: 0; +} + +/* +Reset default styling for dialogs. +*/ + +dialog { + padding: 0; +} + +/* +Prevent resizing textareas horizontally by default. +*/ + +textarea { + resize: vertical; +} + +/* +1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) +2. Set the default placeholder color to the user's configured gray 400 color. +*/ + +input::-moz-placeholder, textarea::-moz-placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +input::placeholder, +textarea::placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +/* +Set the default cursor for buttons. +*/ + +button, +[role="button"] { + cursor: pointer; +} + +/* +Make sure disabled buttons don't get the pointer cursor. +*/ + +:disabled { + cursor: default; +} + +/* +1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) +2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) + This can trigger a poorly considered lint error in some tools but is included by design. +*/ + +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; + /* 1 */ + vertical-align: middle; + /* 2 */ +} + +/* +Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) +*/ + +img, +video { + max-width: 100%; + height: auto; +} + +/* Make elements with the HTML hidden attribute stay hidden by default */ + +[hidden] { + display: none; +} + +*, ::before, ::after { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +::backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +.\!container { + width: 100% !important; +} + +.container { + width: 100%; +} + +@media (min-width: 640px) { + .\!container { + max-width: 640px !important; + } + + .container { + max-width: 640px; + } +} + +@media (min-width: 768px) { + .\!container { + max-width: 768px !important; + } + + .container { + max-width: 768px; + } +} + +@media (min-width: 1024px) { + .\!container { + max-width: 1024px !important; + } + + .container { + max-width: 1024px; + } +} + +@media (min-width: 1280px) { + .\!container { + max-width: 1280px !important; + } + + .container { + max-width: 1280px; + } +} + +@media (min-width: 1536px) { + .\!container { + max-width: 1536px !important; + } + + .container { + max-width: 1536px; + } +} + +.pointer-events-none { + pointer-events: none; +} + +.visible { + visibility: visible; +} + +.invisible { + visibility: hidden; +} + +.collapse { + visibility: collapse; +} + +.static { + position: static; +} + +.fixed { + position: fixed; +} + +.absolute { + position: absolute; +} + +.relative { + position: relative; +} + +.sticky { + position: sticky; +} + +.inset-0 { + inset: 0px; +} + +.inset-x-0 { + left: 0px; + right: 0px; +} + +.inset-y-0 { + top: 0px; + bottom: 0px; +} + +.-bottom-3 { + bottom: -0.75rem; +} + +.-left-5 { + left: -1.25rem; +} + +.-top-3 { + top: -0.75rem; +} + +.bottom-0 { + bottom: 0px; +} + +.bottom-3 { + bottom: 0.75rem; +} + +.left-0 { + left: 0px; +} + +.left-1\/2 { + left: 50%; +} + +.left-12 { + left: 3rem; +} + +.right-0 { + right: 0px; +} + +.right-4 { + right: 1rem; +} + +.top-0 { + top: 0px; +} + +.top-10 { + top: 2.5rem; +} + +.top-3 { + top: 0.75rem; +} + +.z-10 { + z-index: 10; +} + +.z-20 { + z-index: 20; +} + +.z-50 { + z-index: 50; +} + +.float-right { + float: right; +} + +.m-0 { + margin: 0px; +} + +.m-1 { + margin: 0.25rem; +} + +.m-2 { + margin: 0.5rem; +} + +.m-auto { + margin: auto; +} + +.mx-auto { + margin-left: auto; + margin-right: auto; +} + +.my-1 { + margin-top: 0.25rem; + margin-bottom: 0.25rem; +} + +.my-2 { + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} + +.my-3 { + margin-top: 0.75rem; + margin-bottom: 0.75rem; +} + +.my-4 { + margin-top: 1rem; + margin-bottom: 1rem; +} + +.my-5 { + margin-top: 1.25rem; + margin-bottom: 1.25rem; +} + +.my-8 { + margin-top: 2rem; + margin-bottom: 2rem; +} + +.my-px { + margin-top: 1px; + margin-bottom: 1px; +} + +.mb-1 { + margin-bottom: 0.25rem; +} + +.mb-10 { + margin-bottom: 2.5rem; +} + +.mb-16 { + margin-bottom: 4rem; +} + +.mb-2 { + margin-bottom: 0.5rem; +} + +.mb-3 { + margin-bottom: 0.75rem; +} + +.mb-4 { + margin-bottom: 1rem; +} + +.mb-5 { + margin-bottom: 1.25rem; +} + +.mb-8 { + margin-bottom: 2rem; +} + +.ml-1 { + margin-left: 0.25rem; +} + +.ml-2 { + margin-left: 0.5rem; +} + +.ml-3 { + margin-left: 0.75rem; +} + +.ml-4 { + margin-left: 1rem; +} + +.ml-5 { + margin-left: 1.25rem; +} + +.mr-1 { + margin-right: 0.25rem; +} + +.mr-2 { + margin-right: 0.5rem; +} + +.mr-3 { + margin-right: 0.75rem; +} + +.mr-5 { + margin-right: 1.25rem; +} + +.mt-1 { + margin-top: 0.25rem; +} + +.mt-2 { + margin-top: 0.5rem; +} + +.mt-20 { + margin-top: 5rem; +} + +.mt-3 { + margin-top: 0.75rem; +} + +.mt-4 { + margin-top: 1rem; +} + +.mt-5 { + margin-top: 1.25rem; +} + +.mt-6 { + margin-top: 1.5rem; +} + +.mt-7 { + margin-top: 1.75rem; +} + +.mt-8 { + margin-top: 2rem; +} + +.mt-auto { + margin-top: auto; +} + +.block { + display: block; +} + +.inline-block { + display: inline-block; +} + +.inline { + display: inline; +} + +.flex { + display: flex; +} + +.inline-flex { + display: inline-flex; +} + +.table { + display: table; +} + +.table-cell { + display: table-cell; +} + +.grid { + display: grid; +} + +.hidden { + display: none; +} + +.h-0 { + height: 0px; +} + +.h-12 { + height: 3rem; +} + +.h-24 { + height: 6rem; +} + +.h-32 { + height: 8rem; +} + +.h-40 { + height: 10rem; +} + +.h-48 { + height: 12rem; +} + +.h-5 { + height: 1.25rem; +} + +.h-6 { + height: 1.5rem; +} + +.h-8 { + height: 2rem; +} + +.h-80 { + height: 20rem; +} + +.h-full { + height: 100%; +} + +.max-h-80 { + max-height: 20rem; +} + +.min-h-screen { + min-height: 100vh; +} + +.w-0 { + width: 0px; +} + +.w-1\/2 { + width: 50%; +} + +.w-1\/3 { + width: 33.333333%; +} + +.w-1\/4 { + width: 25%; +} + +.w-1\/5 { + width: 20%; +} + +.w-1\/6 { + width: 16.666667%; +} + +.w-10 { + width: 2.5rem; +} + +.w-11\/12 { + width: 91.666667%; +} + +.w-16 { + width: 4rem; +} + +.w-2\/3 { + width: 66.666667%; +} + +.w-2\/5 { + width: 40%; +} + +.w-20 { + width: 5rem; +} + +.w-3\/4 { + width: 75%; +} + +.w-32 { + width: 8rem; +} + +.w-4\/5 { + width: 80%; +} + +.w-5 { + width: 1.25rem; +} + +.w-5\/6 { + width: 83.333333%; +} + +.w-6 { + width: 1.5rem; +} + +.w-60 { + width: 15rem; +} + +.w-7 { + width: 1.75rem; +} + +.w-8 { + width: 2rem; +} + +.w-80 { + width: 20rem; +} + +.w-full { + width: 100%; +} + +.max-w-4xl { + max-width: 56rem; +} + +.max-w-6xl { + max-width: 72rem; +} + +.max-w-7xl { + max-width: 80rem; +} + +.max-w-md { + max-width: 28rem; +} + +.max-w-xs { + max-width: 20rem; +} + +.flex-1 { + flex: 1 1 0%; +} + +.flex-shrink { + flex-shrink: 1; +} + +.flex-grow { + flex-grow: 1; +} + +.border-collapse { + border-collapse: collapse; +} + +.-translate-x-1\/2 { + --tw-translate-x: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.transform { + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.animate-spin { + animation: spin 1s linear infinite; +} + +.cursor-pointer { + cursor: pointer; +} + +.resize { + resize: both; +} + +.list-inside { + list-style-position: inside; +} + +.list-decimal { + list-style-type: decimal; +} + +.list-disc { + list-style-type: disc; +} + +.flex-col { + flex-direction: column; +} + +.flex-col-reverse { + flex-direction: column-reverse; +} + +.flex-wrap { + flex-wrap: wrap; +} + +.content-center { + align-content: center; +} + +.items-start { + align-items: flex-start; +} + +.items-center { + align-items: center; +} + +.justify-start { + justify-content: flex-start; +} + +.justify-end { + justify-content: flex-end; +} + +.justify-center { + justify-content: center; +} + +.justify-between { + justify-content: space-between; +} + +.justify-around { + justify-content: space-around; +} + +.space-x-2 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.5rem * var(--tw-space-x-reverse)); + margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); +} + +.space-x-4 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(1rem * var(--tw-space-x-reverse)); + margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse))); +} + +.space-y-2 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); +} + +.overflow-auto { + overflow: auto; +} + +.overflow-hidden { + overflow: hidden; +} + +.overflow-y-auto { + overflow-y: auto; +} + +.truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.rounded { + border-radius: 0.25rem; +} + +.rounded-lg { + border-radius: 0.5rem; +} + +.border { + border-width: 1px; +} + +.border-0 { + border-width: 0px; +} + +.border-2 { + border-width: 2px; +} + +.border-x-8 { + border-left-width: 8px; + border-right-width: 8px; +} + +.border-y { + border-top-width: 1px; + border-bottom-width: 1px; +} + +.border-b { + border-bottom-width: 1px; +} + +.border-b-2 { + border-bottom-width: 2px; +} + +.border-b-4 { + border-bottom-width: 4px; +} + +.border-b-8 { + border-bottom-width: 8px; +} + +.border-l { + border-left-width: 1px; +} + +.border-l-2 { + border-left-width: 2px; +} + +.border-l-4 { + border-left-width: 4px; +} + +.border-r { + border-right-width: 1px; +} + +.border-r-2 { + border-right-width: 2px; +} + +.border-t { + border-top-width: 1px; +} + +.border-t-8 { + border-top-width: 8px; +} + +.border-solid { + border-style: solid; +} + +.border-dotted { + border-style: dotted; +} + +.border-cyan-500 { + --tw-border-opacity: 1; + border-color: rgb(6 182 212 / var(--tw-border-opacity)); +} + +.border-gray-300 { + --tw-border-opacity: 1; + border-color: rgb(209 213 219 / var(--tw-border-opacity)); +} + +.border-gray-500 { + --tw-border-opacity: 1; + border-color: rgb(107 114 128 / var(--tw-border-opacity)); +} + +.border-red-500 { + --tw-border-opacity: 1; + border-color: rgb(239 68 68 / var(--tw-border-opacity)); +} + +.border-rose-100 { + --tw-border-opacity: 1; + border-color: rgb(255 228 230 / var(--tw-border-opacity)); +} + +.border-rose-500 { + --tw-border-opacity: 1; + border-color: rgb(244 63 94 / var(--tw-border-opacity)); +} + +.border-slate-200 { + --tw-border-opacity: 1; + border-color: rgb(226 232 240 / var(--tw-border-opacity)); +} + +.border-stone-100 { + --tw-border-opacity: 1; + border-color: rgb(245 245 244 / var(--tw-border-opacity)); +} + +.border-stone-200 { + --tw-border-opacity: 1; + border-color: rgb(231 229 228 / var(--tw-border-opacity)); +} + +.border-stone-300 { + --tw-border-opacity: 1; + border-color: rgb(214 211 209 / var(--tw-border-opacity)); +} + +.border-stone-50 { + --tw-border-opacity: 1; + border-color: rgb(250 250 249 / var(--tw-border-opacity)); +} + +.border-stone-500 { + --tw-border-opacity: 1; + border-color: rgb(120 113 108 / var(--tw-border-opacity)); +} + +.border-stone-700 { + --tw-border-opacity: 1; + border-color: rgb(68 64 60 / var(--tw-border-opacity)); +} + +.border-teal-500 { + --tw-border-opacity: 1; + border-color: rgb(20 184 166 / var(--tw-border-opacity)); +} + +.border-white { + --tw-border-opacity: 1; + border-color: rgb(255 255 255 / var(--tw-border-opacity)); +} + +.border-yellow-400 { + --tw-border-opacity: 1; + border-color: rgb(250 204 21 / var(--tw-border-opacity)); +} + +.border-yellow-500 { + --tw-border-opacity: 1; + border-color: rgb(234 179 8 / var(--tw-border-opacity)); +} + +.border-x-transparent { + border-left-color: transparent; + border-right-color: transparent; +} + +.border-b-black { + --tw-border-opacity: 1; + border-bottom-color: rgb(0 0 0 / var(--tw-border-opacity)); +} + +.border-b-white { + --tw-border-opacity: 1; + border-bottom-color: rgb(255 255 255 / var(--tw-border-opacity)); +} + +.border-t-black { + --tw-border-opacity: 1; + border-top-color: rgb(0 0 0 / var(--tw-border-opacity)); +} + +.border-t-white { + --tw-border-opacity: 1; + border-top-color: rgb(255 255 255 / var(--tw-border-opacity)); +} + +.bg-black { + --tw-bg-opacity: 1; + background-color: rgb(0 0 0 / var(--tw-bg-opacity)); +} + +.bg-black\/75 { + background-color: rgb(0 0 0 / 0.75); +} + +.bg-red-100 { + --tw-bg-opacity: 1; + background-color: rgb(254 226 226 / var(--tw-bg-opacity)); +} + +.bg-rose-500 { + --tw-bg-opacity: 1; + background-color: rgb(244 63 94 / var(--tw-bg-opacity)); +} + +.bg-stone-100 { + --tw-bg-opacity: 1; + background-color: rgb(245 245 244 / var(--tw-bg-opacity)); +} + +.bg-stone-200 { + --tw-bg-opacity: 1; + background-color: rgb(231 229 228 / var(--tw-bg-opacity)); +} + +.bg-stone-50 { + --tw-bg-opacity: 1; + background-color: rgb(250 250 249 / var(--tw-bg-opacity)); +} + +.bg-stone-600 { + --tw-bg-opacity: 1; + background-color: rgb(87 83 78 / var(--tw-bg-opacity)); +} + +.bg-stone-700 { + --tw-bg-opacity: 1; + background-color: rgb(68 64 60 / var(--tw-bg-opacity)); +} + +.bg-stone-900 { + --tw-bg-opacity: 1; + background-color: rgb(28 25 23 / var(--tw-bg-opacity)); +} + +.bg-teal-500 { + --tw-bg-opacity: 1; + background-color: rgb(20 184 166 / var(--tw-bg-opacity)); +} + +.bg-teal-600 { + --tw-bg-opacity: 1; + background-color: rgb(13 148 136 / var(--tw-bg-opacity)); +} + +.bg-teal-700 { + --tw-bg-opacity: 1; + background-color: rgb(15 118 110 / var(--tw-bg-opacity)); +} + +.bg-transparent { + background-color: transparent; +} + +.bg-white { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); +} + +.bg-yellow-500 { + --tw-bg-opacity: 1; + background-color: rgb(234 179 8 / var(--tw-bg-opacity)); +} + +.bg-opacity-80 { + --tw-bg-opacity: 0.8; +} + +.bg-opacity-90 { + --tw-bg-opacity: 0.9; +} + +.bg-cover { + background-size: cover; +} + +.bg-clip-padding { + background-clip: padding-box; +} + +.p-1 { + padding: 0.25rem; +} + +.p-2 { + padding: 0.5rem; +} + +.p-3 { + padding: 0.75rem; +} + +.p-4 { + padding: 1rem; +} + +.p-5 { + padding: 1.25rem; +} + +.p-6 { + padding: 1.5rem; +} + +.p-8 { + padding: 2rem; +} + +.px-1 { + padding-left: 0.25rem; + padding-right: 0.25rem; +} + +.px-12 { + padding-left: 3rem; + padding-right: 3rem; +} + +.px-2 { + padding-left: 0.5rem; + padding-right: 0.5rem; +} + +.px-3 { + padding-left: 0.75rem; + padding-right: 0.75rem; +} + +.px-4 { + padding-left: 1rem; + padding-right: 1rem; +} + +.px-5 { + padding-left: 1.25rem; + padding-right: 1.25rem; +} + +.px-6 { + padding-left: 1.5rem; + padding-right: 1.5rem; +} + +.px-8 { + padding-left: 2rem; + padding-right: 2rem; +} + +.py-1 { + padding-top: 0.25rem; + padding-bottom: 0.25rem; +} + +.py-1\.5 { + padding-top: 0.375rem; + padding-bottom: 0.375rem; +} + +.py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.py-3 { + padding-top: 0.75rem; + padding-bottom: 0.75rem; +} + +.py-4 { + padding-top: 1rem; + padding-bottom: 1rem; +} + +.py-5 { + padding-top: 1.25rem; + padding-bottom: 1.25rem; +} + +.py-8 { + padding-top: 2rem; + padding-bottom: 2rem; +} + +.pb-0 { + padding-bottom: 0px; +} + +.pb-3 { + padding-bottom: 0.75rem; +} + +.pb-4 { + padding-bottom: 1rem; +} + +.pb-6 { + padding-bottom: 1.5rem; +} + +.pl-10 { + padding-left: 2.5rem; +} + +.pl-12 { + padding-left: 3rem; +} + +.pl-2 { + padding-left: 0.5rem; +} + +.pl-24 { + padding-left: 6rem; +} + +.pl-3 { + padding-left: 0.75rem; +} + +.pl-4 { + padding-left: 1rem; +} + +.pl-6 { + padding-left: 1.5rem; +} + +.pl-8 { + padding-left: 2rem; +} + +.pl-9 { + padding-left: 2.25rem; +} + +.pr-1 { + padding-right: 0.25rem; +} + +.pr-10 { + padding-right: 2.5rem; +} + +.pr-2 { + padding-right: 0.5rem; +} + +.pr-3 { + padding-right: 0.75rem; +} + +.pr-6 { + padding-right: 1.5rem; +} + +.pt-2 { + padding-top: 0.5rem; +} + +.pt-3 { + padding-top: 0.75rem; +} + +.pt-6 { + padding-top: 1.5rem; +} + +.text-left { + text-align: left; +} + +.text-center { + text-align: center; +} + +.text-right { + text-align: right; +} + +.text-justify { + text-align: justify; +} + +.align-middle { + vertical-align: middle; +} + +.font-mono { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +} + +.text-2xl { + font-size: 1.5rem; + line-height: 2rem; +} + +.text-3xl { + font-size: 1.875rem; + line-height: 2.25rem; +} + +.text-4xl { + font-size: 2.25rem; + line-height: 2.5rem; +} + +.text-base { + font-size: 1rem; + line-height: 1.5rem; +} + +.text-lg { + font-size: 1.125rem; + line-height: 1.75rem; +} + +.text-sm { + font-size: 0.875rem; + line-height: 1.25rem; +} + +.text-xl { + font-size: 1.25rem; + line-height: 1.75rem; +} + +.text-xs { + font-size: 0.75rem; + line-height: 1rem; +} + +.font-bold { + font-weight: 700; +} + +.font-medium { + font-weight: 500; +} + +.font-normal { + font-weight: 400; +} + +.font-semibold { + font-weight: 600; +} + +.uppercase { + text-transform: uppercase; +} + +.lowercase { + text-transform: lowercase; +} + +.capitalize { + text-transform: capitalize; +} + +.leading-tight { + line-height: 1.25; +} + +.text-black { + --tw-text-opacity: 1; + color: rgb(0 0 0 / var(--tw-text-opacity)); +} + +.text-gray-500 { + --tw-text-opacity: 1; + color: rgb(107 114 128 / var(--tw-text-opacity)); +} + +.text-gray-700 { + --tw-text-opacity: 1; + color: rgb(55 65 81 / var(--tw-text-opacity)); +} + +.text-red-500 { + --tw-text-opacity: 1; + color: rgb(239 68 68 / var(--tw-text-opacity)); +} + +.text-rose-300 { + --tw-text-opacity: 1; + color: rgb(253 164 175 / var(--tw-text-opacity)); +} + +.text-rose-500 { + --tw-text-opacity: 1; + color: rgb(244 63 94 / var(--tw-text-opacity)); +} + +.text-stone-200 { + --tw-text-opacity: 1; + color: rgb(231 229 228 / var(--tw-text-opacity)); +} + +.text-stone-300 { + --tw-text-opacity: 1; + color: rgb(214 211 209 / var(--tw-text-opacity)); +} + +.text-stone-400 { + --tw-text-opacity: 1; + color: rgb(168 162 158 / var(--tw-text-opacity)); +} + +.text-stone-50 { + --tw-text-opacity: 1; + color: rgb(250 250 249 / var(--tw-text-opacity)); +} + +.text-stone-500 { + --tw-text-opacity: 1; + color: rgb(120 113 108 / var(--tw-text-opacity)); +} + +.text-stone-700 { + --tw-text-opacity: 1; + color: rgb(68 64 60 / var(--tw-text-opacity)); +} + +.text-stone-900 { + --tw-text-opacity: 1; + color: rgb(28 25 23 / var(--tw-text-opacity)); +} + +.text-teal-300 { + --tw-text-opacity: 1; + color: rgb(94 234 212 / var(--tw-text-opacity)); +} + +.text-teal-500 { + --tw-text-opacity: 1; + color: rgb(20 184 166 / var(--tw-text-opacity)); +} + +.text-teal-600 { + --tw-text-opacity: 1; + color: rgb(13 148 136 / var(--tw-text-opacity)); +} + +.text-white { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +.underline { + text-decoration-line: underline; +} + +.caret-white { + caret-color: #fff; +} + +.accent-white { + accent-color: #fff; +} + +.opacity-0 { + opacity: 0; +} + +.opacity-25 { + opacity: 0.25; +} + +.opacity-75 { + opacity: 0.75; +} + +.shadow { + --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.shadow-lg { + --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.shadow-md { + --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.outline-none { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.outline { + outline-style: solid; +} + +.\!filter { + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow) !important; +} + +.filter { + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} + +.\!transition { + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter !important; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter !important; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter !important; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1) !important; + transition-duration: 150ms !important; +} + +.transition { + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.transition-all { + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.transition-colors { + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.transition-opacity { + transition-property: opacity; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.duration-100 { + transition-duration: 100ms; +} + +.ease-in-out { + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); +} + +.hover\:border-rose-500:hover { + --tw-border-opacity: 1; + border-color: rgb(244 63 94 / var(--tw-border-opacity)); +} + +.hover\:border-stone-700:hover { + --tw-border-opacity: 1; + border-color: rgb(68 64 60 / var(--tw-border-opacity)); +} + +.hover\:border-teal-500:hover { + --tw-border-opacity: 1; + border-color: rgb(20 184 166 / var(--tw-border-opacity)); +} + +.hover\:bg-rose-500:hover { + --tw-bg-opacity: 1; + background-color: rgb(244 63 94 / var(--tw-bg-opacity)); +} + +.hover\:bg-rose-600:hover { + --tw-bg-opacity: 1; + background-color: rgb(225 29 72 / var(--tw-bg-opacity)); +} + +.hover\:bg-rose-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(190 18 60 / var(--tw-bg-opacity)); +} + +.hover\:bg-stone-100:hover { + --tw-bg-opacity: 1; + background-color: rgb(245 245 244 / var(--tw-bg-opacity)); +} + +.hover\:bg-stone-200:hover { + --tw-bg-opacity: 1; + background-color: rgb(231 229 228 / var(--tw-bg-opacity)); +} + +.hover\:bg-stone-300:hover { + --tw-bg-opacity: 1; + background-color: rgb(214 211 209 / var(--tw-bg-opacity)); +} + +.hover\:bg-stone-50:hover { + --tw-bg-opacity: 1; + background-color: rgb(250 250 249 / var(--tw-bg-opacity)); +} + +.hover\:bg-stone-600:hover { + --tw-bg-opacity: 1; + background-color: rgb(87 83 78 / var(--tw-bg-opacity)); +} + +.hover\:bg-stone-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(68 64 60 / var(--tw-bg-opacity)); +} + +.hover\:bg-stone-800:hover { + --tw-bg-opacity: 1; + background-color: rgb(41 37 36 / var(--tw-bg-opacity)); +} + +.hover\:bg-stone-900:hover { + --tw-bg-opacity: 1; + background-color: rgb(28 25 23 / var(--tw-bg-opacity)); +} + +.hover\:bg-teal-500:hover { + --tw-bg-opacity: 1; + background-color: rgb(20 184 166 / var(--tw-bg-opacity)); +} + +.hover\:bg-teal-600:hover { + --tw-bg-opacity: 1; + background-color: rgb(13 148 136 / var(--tw-bg-opacity)); +} + +.hover\:bg-teal-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(15 118 110 / var(--tw-bg-opacity)); +} + +.hover\:bg-yellow-600:hover { + --tw-bg-opacity: 1; + background-color: rgb(202 138 4 / var(--tw-bg-opacity)); +} + +.hover\:text-rose-500:hover { + --tw-text-opacity: 1; + color: rgb(244 63 94 / var(--tw-text-opacity)); +} + +.hover\:text-stone-100:hover { + --tw-text-opacity: 1; + color: rgb(245 245 244 / var(--tw-text-opacity)); +} + +.hover\:text-stone-50:hover { + --tw-text-opacity: 1; + color: rgb(250 250 249 / var(--tw-text-opacity)); +} + +.hover\:text-stone-700:hover { + --tw-text-opacity: 1; + color: rgb(68 64 60 / var(--tw-text-opacity)); +} + +.hover\:text-stone-800:hover { + --tw-text-opacity: 1; + color: rgb(41 37 36 / var(--tw-text-opacity)); +} + +.hover\:text-teal-300:hover { + --tw-text-opacity: 1; + color: rgb(94 234 212 / var(--tw-text-opacity)); +} + +.hover\:text-teal-500:hover { + --tw-text-opacity: 1; + color: rgb(20 184 166 / var(--tw-text-opacity)); +} + +.hover\:text-white:hover { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +.hover\:underline:hover { + text-decoration-line: underline; +} + +.hover\:opacity-100:hover { + opacity: 1; +} + +.hover\:shadow-lg:hover { + --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.focus\:border-blue-600:focus { + --tw-border-opacity: 1; + border-color: rgb(37 99 235 / var(--tw-border-opacity)); +} + +.focus\:bg-stone-200:focus { + --tw-bg-opacity: 1; + background-color: rgb(231 229 228 / var(--tw-bg-opacity)); +} + +.focus\:bg-stone-50:focus { + --tw-bg-opacity: 1; + background-color: rgb(250 250 249 / var(--tw-bg-opacity)); +} + +.focus\:bg-white:focus { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); +} + +.focus\:text-gray-700:focus { + --tw-text-opacity: 1; + color: rgb(55 65 81 / var(--tw-text-opacity)); +} + +.focus\:outline-none:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.focus\:ring-0:focus { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} + +.active\:bg-stone-50:active { + --tw-bg-opacity: 1; + background-color: rgb(250 250 249 / var(--tw-bg-opacity)); +} + +.disabled\:cursor-not-allowed:disabled { + cursor: not-allowed; +} + +.disabled\:bg-stone-200:disabled { + --tw-bg-opacity: 1; + background-color: rgb(231 229 228 / var(--tw-bg-opacity)); +} + +.disabled\:text-stone-800:disabled { + --tw-text-opacity: 1; + color: rgb(41 37 36 / var(--tw-text-opacity)); +} + +.disabled\:text-stone-900:disabled { + --tw-text-opacity: 1; + color: rgb(28 25 23 / var(--tw-text-opacity)); +} + +.group:hover .group-hover\:visible { + visibility: visible; +} + +.dark\:border:is(.dark *) { + border-width: 1px; +} + +.dark\:border-0:is(.dark *) { + border-width: 0px; +} + +.dark\:border-stone-200:is(.dark *) { + --tw-border-opacity: 1; + border-color: rgb(231 229 228 / var(--tw-border-opacity)); +} + +.dark\:border-stone-600:is(.dark *) { + --tw-border-opacity: 1; + border-color: rgb(87 83 78 / var(--tw-border-opacity)); +} + +.dark\:border-stone-700:is(.dark *) { + --tw-border-opacity: 1; + border-color: rgb(68 64 60 / var(--tw-border-opacity)); +} + +.dark\:border-stone-900:is(.dark *) { + --tw-border-opacity: 1; + border-color: rgb(28 25 23 / var(--tw-border-opacity)); +} + +.dark\:bg-stone-200:is(.dark *) { + --tw-bg-opacity: 1; + background-color: rgb(231 229 228 / var(--tw-bg-opacity)); +} + +.dark\:bg-stone-600:is(.dark *) { + --tw-bg-opacity: 1; + background-color: rgb(87 83 78 / var(--tw-bg-opacity)); +} + +.dark\:bg-stone-700:is(.dark *) { + --tw-bg-opacity: 1; + background-color: rgb(68 64 60 / var(--tw-bg-opacity)); +} + +.dark\:bg-stone-900:is(.dark *) { + --tw-bg-opacity: 1; + background-color: rgb(28 25 23 / var(--tw-bg-opacity)); +} + +.dark\:text-gray-400:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(156 163 175 / var(--tw-text-opacity)); +} + +.dark\:text-stone-200:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(231 229 228 / var(--tw-text-opacity)); +} + +.dark\:text-stone-300:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(214 211 209 / var(--tw-text-opacity)); +} + +.dark\:text-stone-50:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(250 250 249 / var(--tw-text-opacity)); +} + +.dark\:text-stone-600:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(87 83 78 / var(--tw-text-opacity)); +} + +.dark\:text-stone-900:is(.dark *) { + --tw-text-opacity: 1; + color: rgb(28 25 23 / var(--tw-text-opacity)); +} + +.hover\:dark\:border-rose-500:is(.dark *):hover { + --tw-border-opacity: 1; + border-color: rgb(244 63 94 / var(--tw-border-opacity)); +} + +.hover\:dark\:border-stone-200:is(.dark *):hover { + --tw-border-opacity: 1; + border-color: rgb(231 229 228 / var(--tw-border-opacity)); +} + +.hover\:dark\:border-stone-600:is(.dark *):hover { + --tw-border-opacity: 1; + border-color: rgb(87 83 78 / var(--tw-border-opacity)); +} + +.dark\:hover\:bg-stone-200:hover:is(.dark *) { + --tw-bg-opacity: 1; + background-color: rgb(231 229 228 / var(--tw-bg-opacity)); +} + +.dark\:hover\:bg-teal-500:hover:is(.dark *) { + --tw-bg-opacity: 1; + background-color: rgb(20 184 166 / var(--tw-bg-opacity)); +} + +.hover\:dark\:bg-rose-500:is(.dark *):hover { + --tw-bg-opacity: 1; + background-color: rgb(244 63 94 / var(--tw-bg-opacity)); +} + +.hover\:dark\:bg-stone-200:is(.dark *):hover { + --tw-bg-opacity: 1; + background-color: rgb(231 229 228 / var(--tw-bg-opacity)); +} + +.hover\:dark\:bg-stone-500:is(.dark *):hover { + --tw-bg-opacity: 1; + background-color: rgb(120 113 108 / var(--tw-bg-opacity)); +} + +.hover\:dark\:bg-stone-600:is(.dark *):hover { + --tw-bg-opacity: 1; + background-color: rgb(87 83 78 / var(--tw-bg-opacity)); +} + +.hover\:dark\:bg-stone-900:is(.dark *):hover { + --tw-bg-opacity: 1; + background-color: rgb(28 25 23 / var(--tw-bg-opacity)); +} + +.hover\:dark\:bg-teal-500:is(.dark *):hover { + --tw-bg-opacity: 1; + background-color: rgb(20 184 166 / var(--tw-bg-opacity)); +} + +.hover\:dark\:text-stone-900:is(.dark *):hover { + --tw-text-opacity: 1; + color: rgb(28 25 23 / var(--tw-text-opacity)); +} + +.focus\:dark\:border-stone-600:is(.dark *):focus { + --tw-border-opacity: 1; + border-color: rgb(87 83 78 / var(--tw-border-opacity)); +} + +.focus\:dark\:text-stone-900:is(.dark *):focus { + --tw-text-opacity: 1; + color: rgb(28 25 23 / var(--tw-text-opacity)); +} + +.active\:dark\:border-stone-600:is(.dark *):active { + --tw-border-opacity: 1; + border-color: rgb(87 83 78 / var(--tw-border-opacity)); +} + +.active\:dark\:text-stone-900:is(.dark *):active { + --tw-text-opacity: 1; + color: rgb(28 25 23 / var(--tw-text-opacity)); +} + +.disabled\:dark\:bg-stone-600:is(.dark *):disabled { + --tw-bg-opacity: 1; + background-color: rgb(87 83 78 / var(--tw-bg-opacity)); +} + +.disabled\:dark\:text-stone-200:is(.dark *):disabled { + --tw-text-opacity: 1; + color: rgb(231 229 228 / var(--tw-text-opacity)); +} + +@media (min-width: 768px) { + .md\:max-w-md { + max-width: 28rem; + } +} + +@media (min-width: 1024px) { + .lg\:absolute { + position: absolute; + } + + .lg\:right-0 { + right: 0px; + } + + .lg\:ml-2 { + margin-left: 0.5rem; + } + + .lg\:mr-2 { + margin-right: 0.5rem; + } + + .lg\:mr-3 { + margin-right: 0.75rem; + } + + .lg\:mt-0 { + margin-top: 0px; + } + + .lg\:block { + display: block; + } + + .lg\:inline { + display: inline; + } + + .lg\:flex { + display: flex; + } + + .lg\:table-cell { + display: table-cell; + } + + .lg\:hidden { + display: none; + } + + .lg\:w-1\/2 { + width: 50%; + } + + .lg\:w-1\/3 { + width: 33.333333%; + } + + .lg\:w-1\/4 { + width: 25%; + } + + .lg\:w-2\/5 { + width: 40%; + } + + .lg\:w-24 { + width: 6rem; + } + + .lg\:w-3\/4 { + width: 75%; + } + + .lg\:w-3\/5 { + width: 60%; + } + + .lg\:w-32 { + width: 8rem; + } + + .lg\:w-48 { + width: 12rem; + } + + .lg\:w-54rem { + width: 54rem; + } + + .lg\:w-80 { + width: 20rem; + } + + .lg\:w-half { + width: 48%; + } + + .lg\:flex-row { + flex-direction: row; + } + + .lg\:border-b-4 { + border-bottom-width: 4px; + } + + .lg\:bg-white { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); + } + + .lg\:p-3 { + padding: 0.75rem; + } + + .lg\:px-12 { + padding-left: 3rem; + padding-right: 3rem; + } + + .lg\:px-16 { + padding-left: 4rem; + padding-right: 4rem; + } + + .lg\:px-4 { + padding-left: 1rem; + padding-right: 1rem; + } + + .lg\:px-6 { + padding-left: 1.5rem; + padding-right: 1.5rem; + } + + .lg\:py-16 { + padding-top: 4rem; + padding-bottom: 4rem; + } + + .lg\:pb-0 { + padding-bottom: 0px; + } + + .lg\:pb-3 { + padding-bottom: 0.75rem; + } + + .lg\:pr-3 { + padding-right: 0.75rem; + } + + .lg\:pt-4 { + padding-top: 1rem; + } + + .lg\:text-black { + --tw-text-opacity: 1; + color: rgb(0 0 0 / var(--tw-text-opacity)); + } +} \ No newline at end of file diff --git a/system/typemill/author/js/autosize.min.js b/system/typemill/author/js/autosize.min.js new file mode 100644 index 0000000..2a162d3 --- /dev/null +++ b/system/typemill/author/js/autosize.min.js @@ -0,0 +1,6 @@ +/*! + Autosize 4.0.0 + license: MIT + http://www.jacklmoore.com/autosize +*/ +!function(e,t){if("function"==typeof define&&define.amd)define(["exports","module"],t);else if("undefined"!=typeof exports&&"undefined"!=typeof module)t(exports,module);else{var n={exports:{}};t(n.exports,n),e.autosize=n.exports}}(this,function(e,t){"use strict";function n(e){function t(){var t=window.getComputedStyle(e,null);"vertical"===t.resize?e.style.resize="none":"both"===t.resize&&(e.style.resize="horizontal"),s="content-box"===t.boxSizing?-(parseFloat(t.paddingTop)+parseFloat(t.paddingBottom)):parseFloat(t.borderTopWidth)+parseFloat(t.borderBottomWidth),isNaN(s)&&(s=0),l()}function n(t){var n=e.style.width;e.style.width="0px",e.offsetWidth,e.style.width=n,e.style.overflowY=t}function o(e){for(var t=[];e&&e.parentNode&&e.parentNode instanceof Element;)e.parentNode.scrollTop&&t.push({node:e.parentNode,scrollTop:e.parentNode.scrollTop}),e=e.parentNode;return t}function r(){var t=e.style.height,n=o(e),r=document.documentElement&&document.documentElement.scrollTop;e.style.height="";var i=e.scrollHeight+s;return 0===e.scrollHeight?void(e.style.height=t):(e.style.height=i+"px",u=e.clientWidth,n.forEach(function(e){e.node.scrollTop=e.scrollTop}),void(r&&(document.documentElement.scrollTop=r)))}function l(){r();var t=Math.round(parseFloat(e.style.height)),o=window.getComputedStyle(e,null),i="content-box"===o.boxSizing?Math.round(parseFloat(o.height)):e.offsetHeight;if(i!==t?"hidden"===o.overflowY&&(n("scroll"),r(),i="content-box"===o.boxSizing?Math.round(parseFloat(window.getComputedStyle(e,null).height)):e.offsetHeight):"hidden"!==o.overflowY&&(n("hidden"),r(),i="content-box"===o.boxSizing?Math.round(parseFloat(window.getComputedStyle(e,null).height)):e.offsetHeight),a!==i){a=i;var l=d("autosize:resized");try{e.dispatchEvent(l)}catch(e){}}}if(e&&e.nodeName&&"TEXTAREA"===e.nodeName&&!i.has(e)){var s=null,u=e.clientWidth,a=null,c=function(){e.clientWidth!==u&&l()},p=function(t){window.removeEventListener("resize",c,!1),e.removeEventListener("input",l,!1),e.removeEventListener("keyup",l,!1),e.removeEventListener("autosize:destroy",p,!1),e.removeEventListener("autosize:update",l,!1),Object.keys(t).forEach(function(n){e.style[n]=t[n]}),i.delete(e)}.bind(e,{height:e.style.height,resize:e.style.resize,overflowY:e.style.overflowY,overflowX:e.style.overflowX,wordWrap:e.style.wordWrap});e.addEventListener("autosize:destroy",p,!1),"onpropertychange"in e&&"oninput"in e&&e.addEventListener("keyup",l,!1),window.addEventListener("resize",c,!1),e.addEventListener("input",l,!1),e.addEventListener("autosize:update",l,!1),e.style.overflowX="hidden",e.style.wordWrap="break-word",i.set(e,{destroy:p,update:l}),t()}}function o(e){var t=i.get(e);t&&t.destroy()}function r(e){var t=i.get(e);t&&t.update()}var i="function"==typeof Map?new Map:function(){var e=[],t=[];return{has:function(t){return e.indexOf(t)>-1},get:function(n){return t[e.indexOf(n)]},set:function(n,o){e.indexOf(n)===-1&&(e.push(n),t.push(o))},delete:function(n){var o=e.indexOf(n);o>-1&&(e.splice(o,1),t.splice(o,1))}}}(),d=function(e){return new Event(e,{bubbles:!0})};try{new Event("test")}catch(e){d=function(e){var t=document.createEvent("Event");return t.initEvent(e,!0,!1),t}}var l=null;"undefined"==typeof window||"function"!=typeof window.getComputedStyle?(l=function(e){return e},l.destroy=function(e){return e},l.update=function(e){return e}):(l=function(e,t){return e&&Array.prototype.forEach.call(e.length?e:[e],function(e){return n(e,t)}),e},l.destroy=function(e){return e&&Array.prototype.forEach.call(e.length?e:[e],o),e},l.update=function(e){return e&&Array.prototype.forEach.call(e.length?e:[e],r),e}),t.exports=l}); \ No newline at end of file diff --git a/system/typemill/author/js/axios.min.js b/system/typemill/author/js/axios.min.js new file mode 100644 index 0000000..0ac6c50 --- /dev/null +++ b/system/typemill/author/js/axios.min.js @@ -0,0 +1,2 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).axios=t()}(this,(function(){"use strict";function e(e){var r,n;function o(r,n){try{var a=e[r](n),u=a.value,s=u instanceof t;Promise.resolve(s?u.v:u).then((function(t){if(s){var n="return"===r?"return":"next";if(!u.k||t.done)return o(n,t);t=e[n](t).value}i(a.done?"return":"normal",t)}),(function(e){o("throw",e)}))}catch(e){i("throw",e)}}function i(e,t){switch(e){case"return":r.resolve({value:t,done:!0});break;case"throw":r.reject(t);break;default:r.resolve({value:t,done:!1})}(r=r.next)?o(r.key,r.arg):n=null}this._invoke=function(e,t){return new Promise((function(i,a){var u={key:e,arg:t,resolve:i,reject:a,next:null};n?n=n.next=u:(r=n=u,o(e,t))}))},"function"!=typeof e.return&&(this.return=void 0)}function t(e,t){this.v=e,this.k=t}function r(e){var r={},n=!1;function o(r,o){return n=!0,o=new Promise((function(t){t(e[r](o))})),{done:!1,value:new t(o,1)}}return r["undefined"!=typeof Symbol&&Symbol.iterator||"@@iterator"]=function(){return this},r.next=function(e){return n?(n=!1,e):o("next",e)},"function"==typeof e.throw&&(r.throw=function(e){if(n)throw n=!1,e;return o("throw",e)}),"function"==typeof e.return&&(r.return=function(e){return n?(n=!1,e):o("return",e)}),r}function n(e){var t,r,n,i=2;for("undefined"!=typeof Symbol&&(r=Symbol.asyncIterator,n=Symbol.iterator);i--;){if(r&&null!=(t=e[r]))return t.call(e);if(n&&null!=(t=e[n]))return new o(t.call(e));r="@@asyncIterator",n="@@iterator"}throw new TypeError("Object is not async iterable")}function o(e){function t(e){if(Object(e)!==e)return Promise.reject(new TypeError(e+" is not an object."));var t=e.done;return Promise.resolve(e.value).then((function(e){return{value:e,done:t}}))}return o=function(e){this.s=e,this.n=e.next},o.prototype={s:null,n:null,next:function(){return t(this.n.apply(this.s,arguments))},return:function(e){var r=this.s.return;return void 0===r?Promise.resolve({value:e,done:!0}):t(r.apply(this.s,arguments))},throw:function(e){var r=this.s.return;return void 0===r?Promise.reject(e):t(r.apply(this.s,arguments))}},new o(e)}function i(e){return new t(e,0)}function a(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function u(e){for(var t=1;t=0;--i){var a=this.tryEntries[i],u=a.completion;if("root"===a.tryLoc)return o("end");if(a.tryLoc<=this.prev){var s=n.call(a,"catchLoc"),c=n.call(a,"finallyLoc");if(s&&c){if(this.prev=0;--r){var o=this.tryEntries[r];if(o.tryLoc<=this.prev&&n.call(o,"finallyLoc")&&this.prev=0;--t){var r=this.tryEntries[t];if(r.finallyLoc===e)return this.complete(r.completion,r.afterLoc),A(r),y}},catch:function(e){for(var t=this.tryEntries.length-1;t>=0;--t){var r=this.tryEntries[t];if(r.tryLoc===e){var n=r.completion;if("throw"===n.type){var o=n.arg;A(r)}return o}}throw new Error("illegal catch attempt")},delegateYield:function(t,r,n){return this.delegate={iterator:L(t),resultName:r,nextLoc:n},"next"===this.method&&(this.arg=e),y}},t}function c(e){var t=function(e,t){if("object"!=typeof e||!e)return e;var r=e[Symbol.toPrimitive];if(void 0!==r){var n=r.call(e,t||"default");if("object"!=typeof n)return n;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(e,"string");return"symbol"==typeof t?t:String(t)}function f(e){return f="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},f(e)}function l(t){return function(){return new e(t.apply(this,arguments))}}function p(e,t,r,n,o,i,a){try{var u=e[i](a),s=u.value}catch(e){return void r(e)}u.done?t(s):Promise.resolve(s).then(n,o)}function h(e){return function(){var t=this,r=arguments;return new Promise((function(n,o){var i=e.apply(t,r);function a(e){p(i,n,o,a,u,"next",e)}function u(e){p(i,n,o,a,u,"throw",e)}a(void 0)}))}}function d(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function v(e,t){for(var r=0;re.length)&&(t=e.length);for(var r=0,n=new Array(t);r2&&void 0!==arguments[2]?arguments[2]:{},i=o.allOwnKeys,a=void 0!==i&&i;if(null!=e)if("object"!==f(e)&&(e=[e]),N(e))for(r=0,n=e.length;r0;)if(t===(r=n[o]).toLowerCase())return r;return null}var Q="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:global,Z=function(e){return!_(e)&&e!==Q};var ee,te=(ee="undefined"!=typeof Uint8Array&&j(Uint8Array),function(e){return ee&&e instanceof ee}),re=P("HTMLFormElement"),ne=function(e){var t=Object.prototype.hasOwnProperty;return function(e,r){return t.call(e,r)}}(),oe=P("RegExp"),ie=function(e,t){var r=Object.getOwnPropertyDescriptors(e),n={};$(r,(function(r,o){var i;!1!==(i=t(r,o,e))&&(n[o]=i||r)})),Object.defineProperties(e,n)},ae="abcdefghijklmnopqrstuvwxyz",ue="0123456789",se={DIGIT:ue,ALPHA:ae,ALPHA_DIGIT:ae+ae.toUpperCase()+ue};var ce,fe,le,pe,he=P("AsyncFunction"),de=(ce="function"==typeof setImmediate,fe=U(Q.postMessage),ce?setImmediate:fe?(le="axios@".concat(Math.random()),pe=[],Q.addEventListener("message",(function(e){var t=e.source,r=e.data;t===Q&&r===le&&pe.length&&pe.shift()()}),!1),function(e){pe.push(e),Q.postMessage(le,"*")}):function(e){return setTimeout(e)}),ve="undefined"!=typeof queueMicrotask?queueMicrotask.bind(Q):"undefined"!=typeof process&&process.nextTick||de,ye={isArray:N,isArrayBuffer:C,isBuffer:function(e){return null!==e&&!_(e)&&null!==e.constructor&&!_(e.constructor)&&U(e.constructor.isBuffer)&&e.constructor.isBuffer(e)},isFormData:function(e){var t;return e&&("function"==typeof FormData&&e instanceof FormData||U(e.append)&&("formdata"===(t=A(e))||"object"===t&&U(e.toString)&&"[object FormData]"===e.toString()))},isArrayBufferView:function(e){return"undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(e):e&&e.buffer&&C(e.buffer)},isString:F,isNumber:B,isBoolean:function(e){return!0===e||!1===e},isObject:D,isPlainObject:I,isReadableStream:G,isRequest:K,isResponse:V,isHeaders:X,isUndefined:_,isDate:q,isFile:M,isBlob:z,isRegExp:oe,isFunction:U,isStream:function(e){return D(e)&&U(e.pipe)},isURLSearchParams:J,isTypedArray:te,isFileList:H,forEach:$,merge:function e(){for(var t=Z(this)&&this||{},r=t.caseless,n={},o=function(t,o){var i=r&&Y(n,o)||o;I(n[i])&&I(t)?n[i]=e(n[i],t):I(t)?n[i]=e({},t):N(t)?n[i]=t.slice():n[i]=t},i=0,a=arguments.length;i3&&void 0!==arguments[3]?arguments[3]:{},o=n.allOwnKeys;return $(t,(function(t,n){r&&U(t)?e[n]=R(t,r):e[n]=t}),{allOwnKeys:o}),e},trim:function(e){return e.trim?e.trim():e.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"")},stripBOM:function(e){return 65279===e.charCodeAt(0)&&(e=e.slice(1)),e},inherits:function(e,t,r,n){e.prototype=Object.create(t.prototype,n),e.prototype.constructor=e,Object.defineProperty(e,"super",{value:t.prototype}),r&&Object.assign(e.prototype,r)},toFlatObject:function(e,t,r,n){var o,i,a,u={};if(t=t||{},null==e)return t;do{for(i=(o=Object.getOwnPropertyNames(e)).length;i-- >0;)a=o[i],n&&!n(a,e,t)||u[a]||(t[a]=e[a],u[a]=!0);e=!1!==r&&j(e)}while(e&&(!r||r(e,t))&&e!==Object.prototype);return t},kindOf:A,kindOfTest:P,endsWith:function(e,t,r){e=String(e),(void 0===r||r>e.length)&&(r=e.length),r-=t.length;var n=e.indexOf(t,r);return-1!==n&&n===r},toArray:function(e){if(!e)return null;if(N(e))return e;var t=e.length;if(!B(t))return null;for(var r=new Array(t);t-- >0;)r[t]=e[t];return r},forEachEntry:function(e,t){for(var r,n=(e&&e[Symbol.iterator]).call(e);(r=n.next())&&!r.done;){var o=r.value;t.call(e,o[0],o[1])}},matchAll:function(e,t){for(var r,n=[];null!==(r=e.exec(t));)n.push(r);return n},isHTMLForm:re,hasOwnProperty:ne,hasOwnProp:ne,reduceDescriptors:ie,freezeMethods:function(e){ie(e,(function(t,r){if(U(e)&&-1!==["arguments","caller","callee"].indexOf(r))return!1;var n=e[r];U(n)&&(t.enumerable=!1,"writable"in t?t.writable=!1:t.set||(t.set=function(){throw Error("Can not rewrite read-only method '"+r+"'")}))}))},toObjectSet:function(e,t){var r={},n=function(e){e.forEach((function(e){r[e]=!0}))};return N(e)?n(e):n(String(e).split(t)),r},toCamelCase:function(e){return e.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,(function(e,t,r){return t.toUpperCase()+r}))},noop:function(){},toFiniteNumber:function(e,t){return null!=e&&Number.isFinite(e=+e)?e:t},findKey:Y,global:Q,isContextDefined:Z,ALPHABET:se,generateString:function(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:16,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:se.ALPHA_DIGIT,r="",n=t.length;e--;)r+=t[Math.random()*n|0];return r},isSpecCompliantForm:function(e){return!!(e&&U(e.append)&&"FormData"===e[Symbol.toStringTag]&&e[Symbol.iterator])},toJSONObject:function(e){var t=new Array(10);return function e(r,n){if(D(r)){if(t.indexOf(r)>=0)return;if(!("toJSON"in r)){t[n]=r;var o=N(r)?[]:{};return $(r,(function(t,r){var i=e(t,n+1);!_(i)&&(o[r]=i)})),t[n]=void 0,o}}return r}(e,0)},isAsyncFn:he,isThenable:function(e){return e&&(D(e)||U(e))&&U(e.then)&&U(e.catch)},setImmediate:de,asap:ve};function me(e,t,r,n,o){Error.call(this),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=(new Error).stack,this.message=e,this.name="AxiosError",t&&(this.code=t),r&&(this.config=r),n&&(this.request=n),o&&(this.response=o,this.status=o.status?o.status:null)}ye.inherits(me,Error,{toJSON:function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:ye.toJSONObject(this.config),code:this.code,status:this.status}}});var be=me.prototype,ge={};["ERR_BAD_OPTION_VALUE","ERR_BAD_OPTION","ECONNABORTED","ETIMEDOUT","ERR_NETWORK","ERR_FR_TOO_MANY_REDIRECTS","ERR_DEPRECATED","ERR_BAD_RESPONSE","ERR_BAD_REQUEST","ERR_CANCELED","ERR_NOT_SUPPORT","ERR_INVALID_URL"].forEach((function(e){ge[e]={value:e}})),Object.defineProperties(me,ge),Object.defineProperty(be,"isAxiosError",{value:!0}),me.from=function(e,t,r,n,o,i){var a=Object.create(be);return ye.toFlatObject(e,a,(function(e){return e!==Error.prototype}),(function(e){return"isAxiosError"!==e})),me.call(a,e.message,t,r,n,o),a.cause=e,a.name=e.name,i&&Object.assign(a,i),a};function we(e){return ye.isPlainObject(e)||ye.isArray(e)}function Ee(e){return ye.endsWith(e,"[]")?e.slice(0,-2):e}function Oe(e,t,r){return e?e.concat(t).map((function(e,t){return e=Ee(e),!r&&t?"["+e+"]":e})).join(r?".":""):t}var Se=ye.toFlatObject(ye,{},null,(function(e){return/^is[A-Z]/.test(e)}));function xe(e,t,r){if(!ye.isObject(e))throw new TypeError("target must be an object");t=t||new FormData;var n=(r=ye.toFlatObject(r,{metaTokens:!0,dots:!1,indexes:!1},!1,(function(e,t){return!ye.isUndefined(t[e])}))).metaTokens,o=r.visitor||c,i=r.dots,a=r.indexes,u=(r.Blob||"undefined"!=typeof Blob&&Blob)&&ye.isSpecCompliantForm(t);if(!ye.isFunction(o))throw new TypeError("visitor must be a function");function s(e){if(null===e)return"";if(ye.isDate(e))return e.toISOString();if(!u&&ye.isBlob(e))throw new me("Blob is not supported. Use a Buffer instead.");return ye.isArrayBuffer(e)||ye.isTypedArray(e)?u&&"function"==typeof Blob?new Blob([e]):Buffer.from(e):e}function c(e,r,o){var u=e;if(e&&!o&&"object"===f(e))if(ye.endsWith(r,"{}"))r=n?r:r.slice(0,-2),e=JSON.stringify(e);else if(ye.isArray(e)&&function(e){return ye.isArray(e)&&!e.some(we)}(e)||(ye.isFileList(e)||ye.endsWith(r,"[]"))&&(u=ye.toArray(e)))return r=Ee(r),u.forEach((function(e,n){!ye.isUndefined(e)&&null!==e&&t.append(!0===a?Oe([r],n,i):null===a?r:r+"[]",s(e))})),!1;return!!we(e)||(t.append(Oe(o,r,i),s(e)),!1)}var l=[],p=Object.assign(Se,{defaultVisitor:c,convertValue:s,isVisitable:we});if(!ye.isObject(e))throw new TypeError("data must be an object");return function e(r,n){if(!ye.isUndefined(r)){if(-1!==l.indexOf(r))throw Error("Circular reference detected in "+n.join("."));l.push(r),ye.forEach(r,(function(r,i){!0===(!(ye.isUndefined(r)||null===r)&&o.call(t,r,ye.isString(i)?i.trim():i,n,p))&&e(r,n?n.concat(i):[i])})),l.pop()}}(e),t}function Re(e){var t={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+","%00":"\0"};return encodeURIComponent(e).replace(/[!'()~]|%20|%00/g,(function(e){return t[e]}))}function Te(e,t){this._pairs=[],e&&xe(e,this,t)}var ke=Te.prototype;function je(e){return encodeURIComponent(e).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}function Ae(e,t,r){if(!t)return e;var n=r&&r.encode||je;ye.isFunction(r)&&(r={serialize:r});var o,i=r&&r.serialize;if(o=i?i(t,r):ye.isURLSearchParams(t)?t.toString():new Te(t,r).toString(n)){var a=e.indexOf("#");-1!==a&&(e=e.slice(0,a)),e+=(-1===e.indexOf("?")?"?":"&")+o}return e}ke.append=function(e,t){this._pairs.push([e,t])},ke.toString=function(e){var t=e?function(t){return e.call(this,t,Re)}:Re;return this._pairs.map((function(e){return t(e[0])+"="+t(e[1])}),"").join("&")};var Pe=function(){function e(){d(this,e),this.handlers=[]}return y(e,[{key:"use",value:function(e,t,r){return this.handlers.push({fulfilled:e,rejected:t,synchronous:!!r&&r.synchronous,runWhen:r?r.runWhen:null}),this.handlers.length-1}},{key:"eject",value:function(e){this.handlers[e]&&(this.handlers[e]=null)}},{key:"clear",value:function(){this.handlers&&(this.handlers=[])}},{key:"forEach",value:function(e){ye.forEach(this.handlers,(function(t){null!==t&&e(t)}))}}]),e}(),Le={silentJSONParsing:!0,forcedJSONParsing:!0,clarifyTimeoutError:!1},Ne={isBrowser:!0,classes:{URLSearchParams:"undefined"!=typeof URLSearchParams?URLSearchParams:Te,FormData:"undefined"!=typeof FormData?FormData:null,Blob:"undefined"!=typeof Blob?Blob:null},protocols:["http","https","file","blob","url","data"]},_e="undefined"!=typeof window&&"undefined"!=typeof document,Ce="object"===("undefined"==typeof navigator?"undefined":f(navigator))&&navigator||void 0,Fe=_e&&(!Ce||["ReactNative","NativeScript","NS"].indexOf(Ce.product)<0),Ue="undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope&&"function"==typeof self.importScripts,Be=_e&&window.location.href||"http://localhost",De=u(u({},Object.freeze({__proto__:null,hasBrowserEnv:_e,hasStandardBrowserWebWorkerEnv:Ue,hasStandardBrowserEnv:Fe,navigator:Ce,origin:Be})),Ne);function Ie(e){function t(e,r,n,o){var i=e[o++];if("__proto__"===i)return!0;var a=Number.isFinite(+i),u=o>=e.length;return i=!i&&ye.isArray(n)?n.length:i,u?(ye.hasOwnProp(n,i)?n[i]=[n[i],r]:n[i]=r,!a):(n[i]&&ye.isObject(n[i])||(n[i]=[]),t(e,r,n[i],o)&&ye.isArray(n[i])&&(n[i]=function(e){var t,r,n={},o=Object.keys(e),i=o.length;for(t=0;t-1,i=ye.isObject(e);if(i&&ye.isHTMLForm(e)&&(e=new FormData(e)),ye.isFormData(e))return o?JSON.stringify(Ie(e)):e;if(ye.isArrayBuffer(e)||ye.isBuffer(e)||ye.isStream(e)||ye.isFile(e)||ye.isBlob(e)||ye.isReadableStream(e))return e;if(ye.isArrayBufferView(e))return e.buffer;if(ye.isURLSearchParams(e))return t.setContentType("application/x-www-form-urlencoded;charset=utf-8",!1),e.toString();if(i){if(n.indexOf("application/x-www-form-urlencoded")>-1)return function(e,t){return xe(e,new De.classes.URLSearchParams,Object.assign({visitor:function(e,t,r,n){return De.isNode&&ye.isBuffer(e)?(this.append(t,e.toString("base64")),!1):n.defaultVisitor.apply(this,arguments)}},t))}(e,this.formSerializer).toString();if((r=ye.isFileList(e))||n.indexOf("multipart/form-data")>-1){var a=this.env&&this.env.FormData;return xe(r?{"files[]":e}:e,a&&new a,this.formSerializer)}}return i||o?(t.setContentType("application/json",!1),function(e,t,r){if(ye.isString(e))try{return(t||JSON.parse)(e),ye.trim(e)}catch(e){if("SyntaxError"!==e.name)throw e}return(r||JSON.stringify)(e)}(e)):e}],transformResponse:[function(e){var t=this.transitional||qe.transitional,r=t&&t.forcedJSONParsing,n="json"===this.responseType;if(ye.isResponse(e)||ye.isReadableStream(e))return e;if(e&&ye.isString(e)&&(r&&!this.responseType||n)){var o=!(t&&t.silentJSONParsing)&&n;try{return JSON.parse(e)}catch(e){if(o){if("SyntaxError"===e.name)throw me.from(e,me.ERR_BAD_RESPONSE,this,null,this.response);throw e}}}return e}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,env:{FormData:De.classes.FormData,Blob:De.classes.Blob},validateStatus:function(e){return e>=200&&e<300},headers:{common:{Accept:"application/json, text/plain, */*","Content-Type":void 0}}};ye.forEach(["delete","get","head","post","put","patch"],(function(e){qe.headers[e]={}}));var Me=qe,ze=ye.toObjectSet(["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"]),He=Symbol("internals");function Je(e){return e&&String(e).trim().toLowerCase()}function We(e){return!1===e||null==e?e:ye.isArray(e)?e.map(We):String(e)}function Ge(e,t,r,n,o){return ye.isFunction(n)?n.call(this,t,r):(o&&(t=r),ye.isString(t)?ye.isString(n)?-1!==t.indexOf(n):ye.isRegExp(n)?n.test(t):void 0:void 0)}var Ke=function(e,t){function r(e){d(this,r),e&&this.set(e)}return y(r,[{key:"set",value:function(e,t,r){var n=this;function o(e,t,r){var o=Je(t);if(!o)throw new Error("header name must be a non-empty string");var i=ye.findKey(n,o);(!i||void 0===n[i]||!0===r||void 0===r&&!1!==n[i])&&(n[i||t]=We(e))}var i=function(e,t){return ye.forEach(e,(function(e,r){return o(e,r,t)}))};if(ye.isPlainObject(e)||e instanceof this.constructor)i(e,t);else if(ye.isString(e)&&(e=e.trim())&&!/^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(e.trim()))i(function(e){var t,r,n,o={};return e&&e.split("\n").forEach((function(e){n=e.indexOf(":"),t=e.substring(0,n).trim().toLowerCase(),r=e.substring(n+1).trim(),!t||o[t]&&ze[t]||("set-cookie"===t?o[t]?o[t].push(r):o[t]=[r]:o[t]=o[t]?o[t]+", "+r:r)})),o}(e),t);else if(ye.isHeaders(e)){var a,u=function(e,t){var r="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(!r){if(Array.isArray(e)||(r=O(e))||t&&e&&"number"==typeof e.length){r&&(e=r);var n=0,o=function(){};return{s:o,n:function(){return n>=e.length?{done:!0}:{done:!1,value:e[n++]}},e:function(e){throw e},f:o}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var i,a=!0,u=!1;return{s:function(){r=r.call(e)},n:function(){var e=r.next();return a=e.done,e},e:function(e){u=!0,i=e},f:function(){try{a||null==r.return||r.return()}finally{if(u)throw i}}}}(e.entries());try{for(u.s();!(a=u.n()).done;){var s=b(a.value,2),c=s[0];o(s[1],c,r)}}catch(e){u.e(e)}finally{u.f()}}else null!=e&&o(t,e,r);return this}},{key:"get",value:function(e,t){if(e=Je(e)){var r=ye.findKey(this,e);if(r){var n=this[r];if(!t)return n;if(!0===t)return function(e){for(var t,r=Object.create(null),n=/([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g;t=n.exec(e);)r[t[1]]=t[2];return r}(n);if(ye.isFunction(t))return t.call(this,n,r);if(ye.isRegExp(t))return t.exec(n);throw new TypeError("parser must be boolean|regexp|function")}}}},{key:"has",value:function(e,t){if(e=Je(e)){var r=ye.findKey(this,e);return!(!r||void 0===this[r]||t&&!Ge(0,this[r],r,t))}return!1}},{key:"delete",value:function(e,t){var r=this,n=!1;function o(e){if(e=Je(e)){var o=ye.findKey(r,e);!o||t&&!Ge(0,r[o],o,t)||(delete r[o],n=!0)}}return ye.isArray(e)?e.forEach(o):o(e),n}},{key:"clear",value:function(e){for(var t=Object.keys(this),r=t.length,n=!1;r--;){var o=t[r];e&&!Ge(0,this[o],o,e,!0)||(delete this[o],n=!0)}return n}},{key:"normalize",value:function(e){var t=this,r={};return ye.forEach(this,(function(n,o){var i=ye.findKey(r,o);if(i)return t[i]=We(n),void delete t[o];var a=e?function(e){return e.trim().toLowerCase().replace(/([a-z\d])(\w*)/g,(function(e,t,r){return t.toUpperCase()+r}))}(o):String(o).trim();a!==o&&delete t[o],t[a]=We(n),r[a]=!0})),this}},{key:"concat",value:function(){for(var e,t=arguments.length,r=new Array(t),n=0;n1?r-1:0),o=1;o1&&void 0!==arguments[1]?arguments[1]:Date.now();o=i,r=null,n&&(clearTimeout(n),n=null),e.apply(null,t)};return[function(){for(var e=Date.now(),t=e-o,u=arguments.length,s=new Array(u),c=0;c=i?a(s,e):(r=s,n||(n=setTimeout((function(){n=null,a(r)}),i-t)))},function(){return r&&a(r)}]}ye.inherits(Ye,me,{__CANCEL__:!0});var tt=function(e,t){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:3,n=0,o=Ze(50,250);return et((function(r){var i=r.loaded,a=r.lengthComputable?r.total:void 0,u=i-n,s=o(u);n=i;var c=m({loaded:i,total:a,progress:a?i/a:void 0,bytes:u,rate:s||void 0,estimated:s&&a&&i<=a?(a-i)/s:void 0,event:r,lengthComputable:null!=a},t?"download":"upload",!0);e(c)}),r)},rt=function(e,t){var r=null!=e;return[function(n){return t[0]({lengthComputable:r,total:e,loaded:n})},t[1]]},nt=function(e){return function(){for(var t=arguments.length,r=new Array(t),n=0;n1?t-1:0),n=1;n1?"since :\n"+u.map(At).join("\n"):" "+At(u[0]):"as no adapter specified"),"ERR_NOT_SUPPORT")}return r};function Nt(e){if(e.cancelToken&&e.cancelToken.throwIfRequested(),e.signal&&e.signal.aborted)throw new Ye(null,e)}function _t(e){return Nt(e),e.headers=Ve.from(e.headers),e.data=Xe.call(e,e.transformRequest),-1!==["post","put","patch"].indexOf(e.method)&&e.headers.setContentType("application/x-www-form-urlencoded",!1),Lt(e.adapter||Me.adapter)(e).then((function(t){return Nt(e),t.data=Xe.call(e,e.transformResponse,t),t.headers=Ve.from(t.headers),t}),(function(t){return $e(t)||(Nt(e),t&&t.response&&(t.response.data=Xe.call(e,e.transformResponse,t.response),t.response.headers=Ve.from(t.response.headers))),Promise.reject(t)}))}var Ct="1.7.9",Ft={};["object","boolean","number","function","string","symbol"].forEach((function(e,t){Ft[e]=function(r){return f(r)===e||"a"+(t<1?"n ":" ")+e}}));var Ut={};Ft.transitional=function(e,t,r){function n(e,t){return"[Axios v1.7.9] Transitional option '"+e+"'"+t+(r?". "+r:"")}return function(r,o,i){if(!1===e)throw new me(n(o," has been removed"+(t?" in "+t:"")),me.ERR_DEPRECATED);return t&&!Ut[o]&&(Ut[o]=!0,console.warn(n(o," has been deprecated since v"+t+" and will be removed in the near future"))),!e||e(r,o,i)}},Ft.spelling=function(e){return function(t,r){return console.warn("".concat(r," is likely a misspelling of ").concat(e)),!0}};var Bt={assertOptions:function(e,t,r){if("object"!==f(e))throw new me("options must be an object",me.ERR_BAD_OPTION_VALUE);for(var n=Object.keys(e),o=n.length;o-- >0;){var i=n[o],a=t[i];if(a){var u=e[i],s=void 0===u||a(u,i,e);if(!0!==s)throw new me("option "+i+" must be "+s,me.ERR_BAD_OPTION_VALUE)}else if(!0!==r)throw new me("Unknown option "+i,me.ERR_BAD_OPTION)}},validators:Ft},Dt=Bt.validators,It=function(){function e(t){d(this,e),this.defaults=t,this.interceptors={request:new Pe,response:new Pe}}var t;return y(e,[{key:"request",value:(t=h(s().mark((function e(t,r){var n,o;return s().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.prev=0,e.next=3,this._request(t,r);case 3:return e.abrupt("return",e.sent);case 6:if(e.prev=6,e.t0=e.catch(0),e.t0 instanceof Error){n={},Error.captureStackTrace?Error.captureStackTrace(n):n=new Error,o=n.stack?n.stack.replace(/^.+\n/,""):"";try{e.t0.stack?o&&!String(e.t0.stack).endsWith(o.replace(/^.+\n.+\n/,""))&&(e.t0.stack+="\n"+o):e.t0.stack=o}catch(e){}}throw e.t0;case 10:case"end":return e.stop()}}),e,this,[[0,6]])}))),function(e,r){return t.apply(this,arguments)})},{key:"_request",value:function(e,t){"string"==typeof e?(t=t||{}).url=e:t=e||{};var r=t=st(this.defaults,t),n=r.transitional,o=r.paramsSerializer,i=r.headers;void 0!==n&&Bt.assertOptions(n,{silentJSONParsing:Dt.transitional(Dt.boolean),forcedJSONParsing:Dt.transitional(Dt.boolean),clarifyTimeoutError:Dt.transitional(Dt.boolean)},!1),null!=o&&(ye.isFunction(o)?t.paramsSerializer={serialize:o}:Bt.assertOptions(o,{encode:Dt.function,serialize:Dt.function},!0)),Bt.assertOptions(t,{baseUrl:Dt.spelling("baseURL"),withXsrfToken:Dt.spelling("withXSRFToken")},!0),t.method=(t.method||this.defaults.method||"get").toLowerCase();var a=i&&ye.merge(i.common,i[t.method]);i&&ye.forEach(["delete","get","head","post","put","patch","common"],(function(e){delete i[e]})),t.headers=Ve.concat(a,i);var u=[],s=!0;this.interceptors.request.forEach((function(e){"function"==typeof e.runWhen&&!1===e.runWhen(t)||(s=s&&e.synchronous,u.unshift(e.fulfilled,e.rejected))}));var c,f=[];this.interceptors.response.forEach((function(e){f.push(e.fulfilled,e.rejected)}));var l,p=0;if(!s){var h=[_t.bind(this),void 0];for(h.unshift.apply(h,u),h.push.apply(h,f),l=h.length,c=Promise.resolve(t);p0;)n._listeners[t](e);n._listeners=null}})),this.promise.then=function(e){var t,r=new Promise((function(e){n.subscribe(e),t=e})).then(e);return r.cancel=function(){n.unsubscribe(t)},r},t((function(e,t,o){n.reason||(n.reason=new Ye(e,t,o),r(n.reason))}))}return y(e,[{key:"throwIfRequested",value:function(){if(this.reason)throw this.reason}},{key:"subscribe",value:function(e){this.reason?e(this.reason):this._listeners?this._listeners.push(e):this._listeners=[e]}},{key:"unsubscribe",value:function(e){if(this._listeners){var t=this._listeners.indexOf(e);-1!==t&&this._listeners.splice(t,1)}}},{key:"toAbortSignal",value:function(){var e=this,t=new AbortController,r=function(e){t.abort(e)};return this.subscribe(r),t.signal.unsubscribe=function(){return e.unsubscribe(r)},t.signal}}],[{key:"source",value:function(){var t;return{token:new e((function(e){t=e})),cancel:t}}}]),e}(),zt=Mt;var Ht={Continue:100,SwitchingProtocols:101,Processing:102,EarlyHints:103,Ok:200,Created:201,Accepted:202,NonAuthoritativeInformation:203,NoContent:204,ResetContent:205,PartialContent:206,MultiStatus:207,AlreadyReported:208,ImUsed:226,MultipleChoices:300,MovedPermanently:301,Found:302,SeeOther:303,NotModified:304,UseProxy:305,Unused:306,TemporaryRedirect:307,PermanentRedirect:308,BadRequest:400,Unauthorized:401,PaymentRequired:402,Forbidden:403,NotFound:404,MethodNotAllowed:405,NotAcceptable:406,ProxyAuthenticationRequired:407,RequestTimeout:408,Conflict:409,Gone:410,LengthRequired:411,PreconditionFailed:412,PayloadTooLarge:413,UriTooLong:414,UnsupportedMediaType:415,RangeNotSatisfiable:416,ExpectationFailed:417,ImATeapot:418,MisdirectedRequest:421,UnprocessableEntity:422,Locked:423,FailedDependency:424,TooEarly:425,UpgradeRequired:426,PreconditionRequired:428,TooManyRequests:429,RequestHeaderFieldsTooLarge:431,UnavailableForLegalReasons:451,InternalServerError:500,NotImplemented:501,BadGateway:502,ServiceUnavailable:503,GatewayTimeout:504,HttpVersionNotSupported:505,VariantAlsoNegotiates:506,InsufficientStorage:507,LoopDetected:508,NotExtended:510,NetworkAuthenticationRequired:511};Object.entries(Ht).forEach((function(e){var t=b(e,2),r=t[0],n=t[1];Ht[n]=r}));var Jt=Ht;var Wt=function e(t){var r=new qt(t),n=R(qt.prototype.request,r);return ye.extend(n,qt.prototype,r,{allOwnKeys:!0}),ye.extend(n,r,null,{allOwnKeys:!0}),n.create=function(r){return e(st(t,r))},n}(Me);return Wt.Axios=qt,Wt.CanceledError=Ye,Wt.CancelToken=zt,Wt.isCancel=$e,Wt.VERSION=Ct,Wt.toFormData=xe,Wt.AxiosError=me,Wt.Cancel=Wt.CanceledError,Wt.all=function(e){return Promise.all(e)},Wt.spread=function(e){return function(t){return e.apply(null,t)}},Wt.isAxiosError=function(e){return ye.isObject(e)&&!0===e.isAxiosError},Wt.mergeConfig=st,Wt.AxiosHeaders=Ve,Wt.formToJSON=function(e){return Ie(ye.isHTMLForm(e)?new FormData(e):e)},Wt.getAdapter=Lt,Wt.HttpStatusCode=Jt,Wt.default=Wt,Wt})); +//# sourceMappingURL=axios.min.js.map diff --git a/system/typemill/author/js/highlight.min.js b/system/typemill/author/js/highlight.min.js new file mode 100644 index 0000000..476704d --- /dev/null +++ b/system/typemill/author/js/highlight.min.js @@ -0,0 +1,709 @@ +/*! + Highlight.js v11.7.0 (git: 82688fad18) + (c) 2006-2022 undefined and other contributors + License: BSD-3-Clause + */ +var hljs=function(){"use strict";var e={exports:{}};function t(e){ +return e instanceof Map?e.clear=e.delete=e.set=()=>{ +throw Error("map is read-only")}:e instanceof Set&&(e.add=e.clear=e.delete=()=>{ +throw Error("set is read-only") +}),Object.freeze(e),Object.getOwnPropertyNames(e).forEach((n=>{var i=e[n] +;"object"!=typeof i||Object.isFrozen(i)||t(i)})),e} +e.exports=t,e.exports.default=t;class n{constructor(e){ +void 0===e.data&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1} +ignoreMatch(){this.isMatchIgnored=!0}}function i(e){ +return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'") +}function r(e,...t){const n=Object.create(null);for(const t in e)n[t]=e[t] +;return t.forEach((e=>{for(const t in e)n[t]=e[t]})),n} +const s=e=>!!e.scope||e.sublanguage&&e.language;class o{constructor(e,t){ +this.buffer="",this.classPrefix=t.classPrefix,e.walk(this)}addText(e){ +this.buffer+=i(e)}openNode(e){if(!s(e))return;let t="" +;t=e.sublanguage?"language-"+e.language:((e,{prefix:t})=>{if(e.includes(".")){ +const n=e.split(".") +;return[`${t}${n.shift()}`,...n.map(((e,t)=>`${e}${"_".repeat(t+1)}`))].join(" ") +}return`${t}${e}`})(e.scope,{prefix:this.classPrefix}),this.span(t)} +closeNode(e){s(e)&&(this.buffer+="")}value(){return this.buffer}span(e){ +this.buffer+=``}}const a=(e={})=>{const t={children:[]} +;return Object.assign(t,e),t};class c{constructor(){ +this.rootNode=a(),this.stack=[this.rootNode]}get top(){ +return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){ +this.top.children.push(e)}openNode(e){const t=a({scope:e}) +;this.add(t),this.stack.push(t)}closeNode(){ +if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){ +for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)} +walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,t){ +return"string"==typeof t?e.addText(t):t.children&&(e.openNode(t), +t.children.forEach((t=>this._walk(e,t))),e.closeNode(t)),e}static _collapse(e){ +"string"!=typeof e&&e.children&&(e.children.every((e=>"string"==typeof e))?e.children=[e.children.join("")]:e.children.forEach((e=>{ +c._collapse(e)})))}}class l extends c{constructor(e){super(),this.options=e} +addKeyword(e,t){""!==e&&(this.openNode(t),this.addText(e),this.closeNode())} +addText(e){""!==e&&this.add(e)}addSublanguage(e,t){const n=e.root +;n.sublanguage=!0,n.language=t,this.add(n)}toHTML(){ +return new o(this,this.options).value()}finalize(){return!0}}function g(e){ +return e?"string"==typeof e?e:e.source:null}function d(e){return p("(?=",e,")")} +function u(e){return p("(?:",e,")*")}function h(e){return p("(?:",e,")?")} +function p(...e){return e.map((e=>g(e))).join("")}function f(...e){const t=(e=>{ +const t=e[e.length-1] +;return"object"==typeof t&&t.constructor===Object?(e.splice(e.length-1,1),t):{} +})(e);return"("+(t.capture?"":"?:")+e.map((e=>g(e))).join("|")+")"} +function b(e){return RegExp(e.toString()+"|").exec("").length-1} +const m=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./ +;function E(e,{joinWith:t}){let n=0;return e.map((e=>{n+=1;const t=n +;let i=g(e),r="";for(;i.length>0;){const e=m.exec(i);if(!e){r+=i;break} +r+=i.substring(0,e.index), +i=i.substring(e.index+e[0].length),"\\"===e[0][0]&&e[1]?r+="\\"+(Number(e[1])+t):(r+=e[0], +"("===e[0]&&n++)}return r})).map((e=>`(${e})`)).join(t)} +const x="[a-zA-Z]\\w*",w="[a-zA-Z_]\\w*",y="\\b\\d+(\\.\\d+)?",_="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",O="\\b(0b[01]+)",v={ +begin:"\\\\[\\s\\S]",relevance:0},N={scope:"string",begin:"'",end:"'", +illegal:"\\n",contains:[v]},k={scope:"string",begin:'"',end:'"',illegal:"\\n", +contains:[v]},M=(e,t,n={})=>{const i=r({scope:"comment",begin:e,end:t, +contains:[]},n);i.contains.push({scope:"doctag", +begin:"[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)", +end:/(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,excludeBegin:!0,relevance:0}) +;const s=f("I","a","is","so","us","to","at","if","in","it","on",/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/) +;return i.contains.push({begin:p(/[ ]+/,"(",s,/[.]?[:]?([.][ ]|[ ])/,"){3}")}),i +},S=M("//","$"),R=M("/\\*","\\*/"),j=M("#","$");var A=Object.freeze({ +__proto__:null,MATCH_NOTHING_RE:/\b\B/,IDENT_RE:x,UNDERSCORE_IDENT_RE:w, +NUMBER_RE:y,C_NUMBER_RE:_,BINARY_NUMBER_RE:O, +RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~", +SHEBANG:(e={})=>{const t=/^#![ ]*\// +;return e.binary&&(e.begin=p(t,/.*\b/,e.binary,/\b.*/)),r({scope:"meta",begin:t, +end:/$/,relevance:0,"on:begin":(e,t)=>{0!==e.index&&t.ignoreMatch()}},e)}, +BACKSLASH_ESCAPE:v,APOS_STRING_MODE:N,QUOTE_STRING_MODE:k,PHRASAL_WORDS_MODE:{ +begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/ +},COMMENT:M,C_LINE_COMMENT_MODE:S,C_BLOCK_COMMENT_MODE:R,HASH_COMMENT_MODE:j, +NUMBER_MODE:{scope:"number",begin:y,relevance:0},C_NUMBER_MODE:{scope:"number", +begin:_,relevance:0},BINARY_NUMBER_MODE:{scope:"number",begin:O,relevance:0}, +REGEXP_MODE:{begin:/(?=\/[^/\n]*\/)/,contains:[{scope:"regexp",begin:/\//, +end:/\/[gimuy]*/,illegal:/\n/,contains:[v,{begin:/\[/,end:/\]/,relevance:0, +contains:[v]}]}]},TITLE_MODE:{scope:"title",begin:x,relevance:0}, +UNDERSCORE_TITLE_MODE:{scope:"title",begin:w,relevance:0},METHOD_GUARD:{ +begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0},END_SAME_AS_BEGIN:e=>Object.assign(e,{ +"on:begin":(e,t)=>{t.data._beginMatch=e[1]},"on:end":(e,t)=>{ +t.data._beginMatch!==e[1]&&t.ignoreMatch()}})});function I(e,t){ +"."===e.input[e.index-1]&&t.ignoreMatch()}function T(e,t){ +void 0!==e.className&&(e.scope=e.className,delete e.className)}function L(e,t){ +t&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)", +e.__beforeBegin=I,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords, +void 0===e.relevance&&(e.relevance=0))}function B(e,t){ +Array.isArray(e.illegal)&&(e.illegal=f(...e.illegal))}function D(e,t){ +if(e.match){ +if(e.begin||e.end)throw Error("begin & end are not supported with match") +;e.begin=e.match,delete e.match}}function H(e,t){ +void 0===e.relevance&&(e.relevance=1)}const P=(e,t)=>{if(!e.beforeMatch)return +;if(e.starts)throw Error("beforeMatch cannot be used with starts") +;const n=Object.assign({},e);Object.keys(e).forEach((t=>{delete e[t] +})),e.keywords=n.keywords,e.begin=p(n.beforeMatch,d(n.begin)),e.starts={ +relevance:0,contains:[Object.assign(n,{endsParent:!0})] +},e.relevance=0,delete n.beforeMatch +},C=["of","and","for","in","not","or","if","then","parent","list","value"] +;function $(e,t,n="keyword"){const i=Object.create(null) +;return"string"==typeof e?r(n,e.split(" ")):Array.isArray(e)?r(n,e):Object.keys(e).forEach((n=>{ +Object.assign(i,$(e[n],t,n))})),i;function r(e,n){ +t&&(n=n.map((e=>e.toLowerCase()))),n.forEach((t=>{const n=t.split("|") +;i[n[0]]=[e,U(n[0],n[1])]}))}}function U(e,t){ +return t?Number(t):(e=>C.includes(e.toLowerCase()))(e)?0:1}const z={},K=e=>{ +console.error(e)},W=(e,...t)=>{console.log("WARN: "+e,...t)},X=(e,t)=>{ +z[`${e}/${t}`]||(console.log(`Deprecated as of ${e}. ${t}`),z[`${e}/${t}`]=!0) +},G=Error();function Z(e,t,{key:n}){let i=0;const r=e[n],s={},o={} +;for(let e=1;e<=t.length;e++)o[e+i]=r[e],s[e+i]=!0,i+=b(t[e-1]) +;e[n]=o,e[n]._emit=s,e[n]._multi=!0}function F(e){(e=>{ +e.scope&&"object"==typeof e.scope&&null!==e.scope&&(e.beginScope=e.scope, +delete e.scope)})(e),"string"==typeof e.beginScope&&(e.beginScope={ +_wrap:e.beginScope}),"string"==typeof e.endScope&&(e.endScope={_wrap:e.endScope +}),(e=>{if(Array.isArray(e.begin)){ +if(e.skip||e.excludeBegin||e.returnBegin)throw K("skip, excludeBegin, returnBegin not compatible with beginScope: {}"), +G +;if("object"!=typeof e.beginScope||null===e.beginScope)throw K("beginScope must be object"), +G;Z(e,e.begin,{key:"beginScope"}),e.begin=E(e.begin,{joinWith:""})}})(e),(e=>{ +if(Array.isArray(e.end)){ +if(e.skip||e.excludeEnd||e.returnEnd)throw K("skip, excludeEnd, returnEnd not compatible with endScope: {}"), +G +;if("object"!=typeof e.endScope||null===e.endScope)throw K("endScope must be object"), +G;Z(e,e.end,{key:"endScope"}),e.end=E(e.end,{joinWith:""})}})(e)}function V(e){ +function t(t,n){ +return RegExp(g(t),"m"+(e.case_insensitive?"i":"")+(e.unicodeRegex?"u":"")+(n?"g":"")) +}class n{constructor(){ +this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0} +addRule(e,t){ +t.position=this.position++,this.matchIndexes[this.matchAt]=t,this.regexes.push([t,e]), +this.matchAt+=b(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null) +;const e=this.regexes.map((e=>e[1]));this.matcherRe=t(E(e,{joinWith:"|" +}),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex +;const t=this.matcherRe.exec(e);if(!t)return null +;const n=t.findIndex(((e,t)=>t>0&&void 0!==e)),i=this.matchIndexes[n] +;return t.splice(0,n),Object.assign(t,i)}}class i{constructor(){ +this.rules=[],this.multiRegexes=[], +this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){ +if(this.multiRegexes[e])return this.multiRegexes[e];const t=new n +;return this.rules.slice(e).forEach((([e,n])=>t.addRule(e,n))), +t.compile(),this.multiRegexes[e]=t,t}resumingScanAtSamePosition(){ +return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,t){ +this.rules.push([e,t]),"begin"===t.type&&this.count++}exec(e){ +const t=this.getMatcher(this.regexIndex);t.lastIndex=this.lastIndex +;let n=t.exec(e) +;if(this.resumingScanAtSamePosition())if(n&&n.index===this.lastIndex);else{ +const t=this.getMatcher(0);t.lastIndex=this.lastIndex+1,n=t.exec(e)} +return n&&(this.regexIndex+=n.position+1, +this.regexIndex===this.count&&this.considerAll()),n}} +if(e.compilerExtensions||(e.compilerExtensions=[]), +e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.") +;return e.classNameAliases=r(e.classNameAliases||{}),function n(s,o){const a=s +;if(s.isCompiled)return a +;[T,D,F,P].forEach((e=>e(s,o))),e.compilerExtensions.forEach((e=>e(s,o))), +s.__beforeBegin=null,[L,B,H].forEach((e=>e(s,o))),s.isCompiled=!0;let c=null +;return"object"==typeof s.keywords&&s.keywords.$pattern&&(s.keywords=Object.assign({},s.keywords), +c=s.keywords.$pattern, +delete s.keywords.$pattern),c=c||/\w+/,s.keywords&&(s.keywords=$(s.keywords,e.case_insensitive)), +a.keywordPatternRe=t(c,!0), +o&&(s.begin||(s.begin=/\B|\b/),a.beginRe=t(a.begin),s.end||s.endsWithParent||(s.end=/\B|\b/), +s.end&&(a.endRe=t(a.end)), +a.terminatorEnd=g(a.end)||"",s.endsWithParent&&o.terminatorEnd&&(a.terminatorEnd+=(s.end?"|":"")+o.terminatorEnd)), +s.illegal&&(a.illegalRe=t(s.illegal)), +s.contains||(s.contains=[]),s.contains=[].concat(...s.contains.map((e=>(e=>(e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map((t=>r(e,{ +variants:null},t)))),e.cachedVariants?e.cachedVariants:q(e)?r(e,{ +starts:e.starts?r(e.starts):null +}):Object.isFrozen(e)?r(e):e))("self"===e?s:e)))),s.contains.forEach((e=>{n(e,a) +})),s.starts&&n(s.starts,o),a.matcher=(e=>{const t=new i +;return e.contains.forEach((e=>t.addRule(e.begin,{rule:e,type:"begin" +}))),e.terminatorEnd&&t.addRule(e.terminatorEnd,{type:"end" +}),e.illegal&&t.addRule(e.illegal,{type:"illegal"}),t})(a),a}(e)}function q(e){ +return!!e&&(e.endsWithParent||q(e.starts))}class J extends Error{ +constructor(e,t){super(e),this.name="HTMLInjectionError",this.html=t}} +const Y=i,Q=r,ee=Symbol("nomatch");var te=(t=>{ +const i=Object.create(null),r=Object.create(null),s=[];let o=!0 +;const a="Could not find the language '{}', did you forget to load/include a language module?",c={ +disableAutodetect:!0,name:"Plain text",contains:[]};let g={ +ignoreUnescapedHTML:!1,throwUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i, +languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-", +cssSelector:"pre code",languages:null,__emitter:l};function b(e){ +return g.noHighlightRe.test(e)}function m(e,t,n){let i="",r="" +;"object"==typeof t?(i=e, +n=t.ignoreIllegals,r=t.language):(X("10.7.0","highlight(lang, code, ...args) has been deprecated."), +X("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"), +r=e,i=t),void 0===n&&(n=!0);const s={code:i,language:r};k("before:highlight",s) +;const o=s.result?s.result:E(s.language,s.code,n) +;return o.code=s.code,k("after:highlight",o),o}function E(e,t,r,s){ +const c=Object.create(null);function l(){if(!N.keywords)return void M.addText(S) +;let e=0;N.keywordPatternRe.lastIndex=0;let t=N.keywordPatternRe.exec(S),n="" +;for(;t;){n+=S.substring(e,t.index) +;const r=y.case_insensitive?t[0].toLowerCase():t[0],s=(i=r,N.keywords[i]);if(s){ +const[e,i]=s +;if(M.addText(n),n="",c[r]=(c[r]||0)+1,c[r]<=7&&(R+=i),e.startsWith("_"))n+=t[0];else{ +const n=y.classNameAliases[e]||e;M.addKeyword(t[0],n)}}else n+=t[0] +;e=N.keywordPatternRe.lastIndex,t=N.keywordPatternRe.exec(S)}var i +;n+=S.substring(e),M.addText(n)}function d(){null!=N.subLanguage?(()=>{ +if(""===S)return;let e=null;if("string"==typeof N.subLanguage){ +if(!i[N.subLanguage])return void M.addText(S) +;e=E(N.subLanguage,S,!0,k[N.subLanguage]),k[N.subLanguage]=e._top +}else e=x(S,N.subLanguage.length?N.subLanguage:null) +;N.relevance>0&&(R+=e.relevance),M.addSublanguage(e._emitter,e.language) +})():l(),S=""}function u(e,t){let n=1;const i=t.length-1;for(;n<=i;){ +if(!e._emit[n]){n++;continue}const i=y.classNameAliases[e[n]]||e[n],r=t[n] +;i?M.addKeyword(r,i):(S=r,l(),S=""),n++}}function h(e,t){ +return e.scope&&"string"==typeof e.scope&&M.openNode(y.classNameAliases[e.scope]||e.scope), +e.beginScope&&(e.beginScope._wrap?(M.addKeyword(S,y.classNameAliases[e.beginScope._wrap]||e.beginScope._wrap), +S=""):e.beginScope._multi&&(u(e.beginScope,t),S="")),N=Object.create(e,{parent:{ +value:N}}),N}function p(e,t,i){let r=((e,t)=>{const n=e&&e.exec(t) +;return n&&0===n.index})(e.endRe,i);if(r){if(e["on:end"]){const i=new n(e) +;e["on:end"](t,i),i.isMatchIgnored&&(r=!1)}if(r){ +for(;e.endsParent&&e.parent;)e=e.parent;return e}} +if(e.endsWithParent)return p(e.parent,t,i)}function f(e){ +return 0===N.matcher.regexIndex?(S+=e[0],1):(I=!0,0)}function b(e){ +const n=e[0],i=t.substring(e.index),r=p(N,e,i);if(!r)return ee;const s=N +;N.endScope&&N.endScope._wrap?(d(), +M.addKeyword(n,N.endScope._wrap)):N.endScope&&N.endScope._multi?(d(), +u(N.endScope,e)):s.skip?S+=n:(s.returnEnd||s.excludeEnd||(S+=n), +d(),s.excludeEnd&&(S=n));do{ +N.scope&&M.closeNode(),N.skip||N.subLanguage||(R+=N.relevance),N=N.parent +}while(N!==r.parent);return r.starts&&h(r.starts,e),s.returnEnd?0:n.length} +let m={};function w(i,s){const a=s&&s[0];if(S+=i,null==a)return d(),0 +;if("begin"===m.type&&"end"===s.type&&m.index===s.index&&""===a){ +if(S+=t.slice(s.index,s.index+1),!o){const t=Error(`0 width match regex (${e})`) +;throw t.languageName=e,t.badRule=m.rule,t}return 1} +if(m=s,"begin"===s.type)return(e=>{ +const t=e[0],i=e.rule,r=new n(i),s=[i.__beforeBegin,i["on:begin"]] +;for(const n of s)if(n&&(n(e,r),r.isMatchIgnored))return f(t) +;return i.skip?S+=t:(i.excludeBegin&&(S+=t), +d(),i.returnBegin||i.excludeBegin||(S=t)),h(i,e),i.returnBegin?0:t.length})(s) +;if("illegal"===s.type&&!r){ +const e=Error('Illegal lexeme "'+a+'" for mode "'+(N.scope||"")+'"') +;throw e.mode=N,e}if("end"===s.type){const e=b(s);if(e!==ee)return e} +if("illegal"===s.type&&""===a)return 1 +;if(A>1e5&&A>3*s.index)throw Error("potential infinite loop, way more iterations than matches") +;return S+=a,a.length}const y=O(e) +;if(!y)throw K(a.replace("{}",e)),Error('Unknown language: "'+e+'"') +;const _=V(y);let v="",N=s||_;const k={},M=new g.__emitter(g);(()=>{const e=[] +;for(let t=N;t!==y;t=t.parent)t.scope&&e.unshift(t.scope) +;e.forEach((e=>M.openNode(e)))})();let S="",R=0,j=0,A=0,I=!1;try{ +for(N.matcher.considerAll();;){ +A++,I?I=!1:N.matcher.considerAll(),N.matcher.lastIndex=j +;const e=N.matcher.exec(t);if(!e)break;const n=w(t.substring(j,e.index),e) +;j=e.index+n} +return w(t.substring(j)),M.closeAllNodes(),M.finalize(),v=M.toHTML(),{ +language:e,value:v,relevance:R,illegal:!1,_emitter:M,_top:N}}catch(n){ +if(n.message&&n.message.includes("Illegal"))return{language:e,value:Y(t), +illegal:!0,relevance:0,_illegalBy:{message:n.message,index:j, +context:t.slice(j-100,j+100),mode:n.mode,resultSoFar:v},_emitter:M};if(o)return{ +language:e,value:Y(t),illegal:!1,relevance:0,errorRaised:n,_emitter:M,_top:N} +;throw n}}function x(e,t){t=t||g.languages||Object.keys(i);const n=(e=>{ +const t={value:Y(e),illegal:!1,relevance:0,_top:c,_emitter:new g.__emitter(g)} +;return t._emitter.addText(e),t})(e),r=t.filter(O).filter(N).map((t=>E(t,e,!1))) +;r.unshift(n);const s=r.sort(((e,t)=>{ +if(e.relevance!==t.relevance)return t.relevance-e.relevance +;if(e.language&&t.language){if(O(e.language).supersetOf===t.language)return 1 +;if(O(t.language).supersetOf===e.language)return-1}return 0})),[o,a]=s,l=o +;return l.secondBest=a,l}function w(e){let t=null;const n=(e=>{ +let t=e.className+" ";t+=e.parentNode?e.parentNode.className:"" +;const n=g.languageDetectRe.exec(t);if(n){const t=O(n[1]) +;return t||(W(a.replace("{}",n[1])), +W("Falling back to no-highlight mode for this block.",e)),t?n[1]:"no-highlight"} +return t.split(/\s+/).find((e=>b(e)||O(e)))})(e);if(b(n))return +;if(k("before:highlightElement",{el:e,language:n +}),e.children.length>0&&(g.ignoreUnescapedHTML||(console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."), +console.warn("https://github.com/highlightjs/highlight.js/wiki/security"), +console.warn("The element with unescaped HTML:"), +console.warn(e)),g.throwUnescapedHTML))throw new J("One of your code blocks includes unescaped HTML.",e.innerHTML) +;t=e;const i=t.textContent,s=n?m(i,{language:n,ignoreIllegals:!0}):x(i) +;e.innerHTML=s.value,((e,t,n)=>{const i=t&&r[t]||n +;e.classList.add("hljs"),e.classList.add("language-"+i) +})(e,n,s.language),e.result={language:s.language,re:s.relevance, +relevance:s.relevance},s.secondBest&&(e.secondBest={ +language:s.secondBest.language,relevance:s.secondBest.relevance +}),k("after:highlightElement",{el:e,result:s,text:i})}let y=!1;function _(){ +"loading"!==document.readyState?document.querySelectorAll(g.cssSelector).forEach(w):y=!0 +}function O(e){return e=(e||"").toLowerCase(),i[e]||i[r[e]]} +function v(e,{languageName:t}){"string"==typeof e&&(e=[e]),e.forEach((e=>{ +r[e.toLowerCase()]=t}))}function N(e){const t=O(e) +;return t&&!t.disableAutodetect}function k(e,t){const n=e;s.forEach((e=>{ +e[n]&&e[n](t)}))} +"undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(()=>{ +y&&_()}),!1),Object.assign(t,{highlight:m,highlightAuto:x,highlightAll:_, +highlightElement:w, +highlightBlock:e=>(X("10.7.0","highlightBlock will be removed entirely in v12.0"), +X("10.7.0","Please use highlightElement now."),w(e)),configure:e=>{g=Q(g,e)}, +initHighlighting:()=>{ +_(),X("10.6.0","initHighlighting() deprecated. Use highlightAll() now.")}, +initHighlightingOnLoad:()=>{ +_(),X("10.6.0","initHighlightingOnLoad() deprecated. Use highlightAll() now.") +},registerLanguage:(e,n)=>{let r=null;try{r=n(t)}catch(t){ +if(K("Language definition for '{}' could not be registered.".replace("{}",e)), +!o)throw t;K(t),r=c} +r.name||(r.name=e),i[e]=r,r.rawDefinition=n.bind(null,t),r.aliases&&v(r.aliases,{ +languageName:e})},unregisterLanguage:e=>{delete i[e] +;for(const t of Object.keys(r))r[t]===e&&delete r[t]}, +listLanguages:()=>Object.keys(i),getLanguage:O,registerAliases:v, +autoDetection:N,inherit:Q,addPlugin:e=>{(e=>{ +e["before:highlightBlock"]&&!e["before:highlightElement"]&&(e["before:highlightElement"]=t=>{ +e["before:highlightBlock"](Object.assign({block:t.el},t)) +}),e["after:highlightBlock"]&&!e["after:highlightElement"]&&(e["after:highlightElement"]=t=>{ +e["after:highlightBlock"](Object.assign({block:t.el},t))})})(e),s.push(e)} +}),t.debugMode=()=>{o=!1},t.safeMode=()=>{o=!0 +},t.versionString="11.7.0",t.regex={concat:p,lookahead:d,either:f,optional:h, +anyNumberOfTimes:u};for(const t in A)"object"==typeof A[t]&&e.exports(A[t]) +;return Object.assign(t,A),t})({});return te}() +;"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);/*! `javascript` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var e=(()=>{"use strict" +;const e="[A-Za-z$_][0-9A-Za-z$_]*",n=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],a=["true","false","null","undefined","NaN","Infinity"],t=["Object","Function","Boolean","Symbol","Math","Date","Number","BigInt","String","RegExp","Array","Float32Array","Float64Array","Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Int32Array","Uint16Array","Uint32Array","BigInt64Array","BigUint64Array","Set","Map","WeakSet","WeakMap","ArrayBuffer","SharedArrayBuffer","Atomics","DataView","JSON","Promise","Generator","GeneratorFunction","AsyncFunction","Reflect","Proxy","Intl","WebAssembly"],s=["Error","EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"],r=["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],c=["arguments","this","super","console","window","document","localStorage","module","global"],i=[].concat(r,t,s) +;return o=>{const l=o.regex,b=e,d={begin:/<[A-Za-z0-9\\._:-]+/, +end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,n)=>{ +const a=e[0].length+e.index,t=e.input[a] +;if("<"===t||","===t)return void n.ignoreMatch();let s +;">"===t&&(((e,{after:n})=>{const a="",M={ +match:[/const|var|let/,/\s+/,b,/\s*/,/=\s*/,/(async\s*)?/,l.lookahead(C)], +keywords:"async",className:{1:"keyword",3:"title.function"},contains:[S]} +;return{name:"Javascript",aliases:["js","jsx","mjs","cjs"],keywords:g,exports:{ +PARAMS_CONTAINS:p,CLASS_REFERENCE:R},illegal:/#(?![$_A-z])/, +contains:[o.SHEBANG({label:"shebang",binary:"node",relevance:5}),{ +label:"use_strict",className:"meta",relevance:10, +begin:/^\s*['"]use (strict|asm)['"]/ +},o.APOS_STRING_MODE,o.QUOTE_STRING_MODE,y,N,_,h,{match:/\$\d+/},E,R,{ +className:"attr",begin:b+l.lookahead(":"),relevance:0},M,{ +begin:"("+o.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*", +keywords:"return throw case",relevance:0,contains:[h,o.REGEXP_MODE,{ +className:"function",begin:C,returnBegin:!0,end:"\\s*=>",contains:[{ +className:"params",variants:[{begin:o.UNDERSCORE_IDENT_RE,relevance:0},{ +className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0, +excludeEnd:!0,keywords:g,contains:p}]}]},{begin:/,/,relevance:0},{match:/\s+/, +relevance:0},{variants:[{begin:"<>",end:""},{ +match:/<[A-Za-z0-9\\._:-]+\s*\/>/},{begin:d.begin, +"on:begin":d.isTrulyOpeningTag,end:d.end}],subLanguage:"xml",contains:[{ +begin:d.begin,end:d.end,skip:!0,contains:["self"]}]}]},O,{ +beginKeywords:"while if switch catch for"},{ +begin:"\\b(?!function)"+o.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{", +returnBegin:!0,label:"func.def",contains:[S,o.inherit(o.TITLE_MODE,{begin:b, +className:"title.function"})]},{match:/\.\.\./,relevance:0},x,{match:"\\$"+b, +relevance:0},{match:[/\bconstructor(?=\s*\()/],className:{1:"title.function"}, +contains:[S]},k,{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/, +className:"variable.constant"},w,T,{match:/\$[(.]/}]}}})() +;hljs.registerLanguage("javascript",e)})();/*! `xml` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var e=(()=>{"use strict";return e=>{ +const a=e.regex,n=a.concat(/[\p{L}_]/u,a.optional(/[\p{L}0-9_.-]*:/u),/[\p{L}0-9_.-]*/u),s={ +className:"symbol",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},t={begin:/\s/, +contains:[{className:"keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}] +},i=e.inherit(t,{begin:/\(/,end:/\)/}),c=e.inherit(e.APOS_STRING_MODE,{ +className:"string"}),l=e.inherit(e.QUOTE_STRING_MODE,{className:"string"}),r={ +endsWithParent:!0,illegal:/`]+/}]}]}]};return{ +name:"HTML, XML", +aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"], +case_insensitive:!0,unicodeRegex:!0,contains:[{className:"meta",begin://,relevance:10,contains:[t,l,c,i,{begin:/\[/,end:/\]/,contains:[{ +className:"meta",begin://,contains:[t,i,l,c]}]}] +},e.COMMENT(//,{relevance:10}),{begin://, +relevance:10},s,{className:"meta",end:/\?>/,variants:[{begin:/<\?xml/, +relevance:10,contains:[l]},{begin:/<\?[a-z][a-z0-9]+/}]},{className:"tag", +begin:/)/,end:/>/,keywords:{name:"style"},contains:[r],starts:{ +end:/<\/style>/,returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag", +begin:/)/,end:/>/,keywords:{name:"script"},contains:[r],starts:{ +end:/<\/script>/,returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{ +className:"tag",begin:/<>|<\/>/},{className:"tag", +begin:a.concat(//,/>/,/\s/)))), +end:/\/?>/,contains:[{className:"name",begin:n,relevance:0,starts:r}]},{ +className:"tag",begin:a.concat(/<\//,a.lookahead(a.concat(n,/>/))),contains:[{ +className:"name",begin:n,relevance:0},{begin:/>/,relevance:0,endsParent:!0}]}]}} +})();hljs.registerLanguage("xml",e)})();/*! `twig` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var e=(()=>{"use strict";return e=>{ +const a=e.regex,t=["absolute_url","asset|0","asset_version","attribute","block","constant","controller|0","country_timezones","csrf_token","cycle","date","dump","expression","form|0","form_end","form_errors","form_help","form_label","form_rest","form_row","form_start","form_widget","html_classes","include","is_granted","logout_path","logout_url","max","min","parent","path|0","random","range","relative_path","render","render_esi","source","template_from_string","url|0"] +;let r=["apply","autoescape","block","cache","deprecated","do","embed","extends","filter","flush","for","form_theme","from","if","import","include","macro","sandbox","set","stopwatch","trans","trans_default_domain","transchoice","use","verbatim","with"] +;r=r.concat(r.map((e=>"end"+e)));const n={scope:"string",variants:[{begin:/'/, +end:/'/},{begin:/"/,end:/"/}]},o={scope:"number",match:/\d+/},s={begin:/\(/, +end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:[n,o]},c={ +beginKeywords:t.join(" "),keywords:{name:t},relevance:0,contains:[s]},m={ +match:/\|(?=[A-Za-z_]+:?)/,beginScope:"punctuation",relevance:0,contains:[{ +match:/[A-Za-z_]+:?/, +keywords:["abs","abbr_class","abbr_method","batch","capitalize","column","convert_encoding","country_name","currency_name","currency_symbol","data_uri","date","date_modify","default","escape","file_excerpt","file_link","file_relative","filter","first","format","format_args","format_args_as_text","format_currency","format_date","format_datetime","format_file","format_file_from_text","format_number","format_time","html_to_markdown","humanize","inky_to_html","inline_css","join","json_encode","keys","language_name","last","length","locale_name","lower","map","markdown","markdown_to_html","merge","nl2br","number_format","raw","reduce","replace","reverse","round","slice","slug","sort","spaceless","split","striptags","timezone_name","title","trans","transchoice","trim","u|0","upper","url_encode","yaml_dump","yaml_encode"] +}]},i=(e,{relevance:t})=>({beginScope:{1:"template-tag",3:"name"}, +relevance:t||2,endScope:"template-tag",begin:[/\{%/,/\s*/,a.either(...e)], +end:/%\}/,keywords:"in",contains:[m,c,n,o]}),l=i(r,{relevance:2 +}),_=i([/[a-z_]+/],{relevance:1});return{name:"Twig",aliases:["craftcms"], +case_insensitive:!0,subLanguage:"xml",contains:[e.COMMENT(/\{#/,/#\}/),l,_,{ +className:"template-variable",begin:/\{\{/,end:/\}\}/,contains:["self",m,c,n,o] +}]}}})();hljs.registerLanguage("twig",e)})();/*! `json` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var e=(()=>{"use strict";return e=>{const a=["true","false","null"],n={ +scope:"literal",beginKeywords:a.join(" ")};return{name:"JSON",keywords:{ +literal:a},contains:[{className:"attr",begin:/"(\\.|[^\\"\r\n])*"(?=\s*:)/, +relevance:1.01},{match:/[{}[\],:]/,className:"punctuation",relevance:0 +},e.QUOTE_STRING_MODE,n,e.C_NUMBER_MODE,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE], +illegal:"\\S"}}})();hljs.registerLanguage("json",e)})();/*! `graphql` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var e=(()=>{"use strict";return e=>{const a=e.regex;return{name:"GraphQL", +aliases:["gql"],case_insensitive:!0,disableAutodetect:!1,keywords:{ +keyword:["query","mutation","subscription","type","input","schema","directive","interface","union","scalar","fragment","enum","on"], +literal:["true","false","null"]}, +contains:[e.HASH_COMMENT_MODE,e.QUOTE_STRING_MODE,e.NUMBER_MODE,{ +scope:"punctuation",match:/[.]{3}/,relevance:0},{scope:"punctuation", +begin:/[\!\(\)\:\=\[\]\{\|\}]{1}/,relevance:0},{scope:"variable",begin:/\$/, +end:/\W/,excludeEnd:!0,relevance:0},{scope:"meta",match:/@\w+/,excludeEnd:!0},{ +scope:"symbol",begin:a.concat(/[_A-Za-z][_0-9A-Za-z]*/,a.lookahead(/\s*:/)), +relevance:0}],illegal:[/[;<']/,/BEGIN/]}}})();hljs.registerLanguage("graphql",e) +})();/*! `css` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var e=(()=>{"use strict" +;const e=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","p","q","quote","samp","section","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],i=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"],r=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"],t=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"],o=["align-content","align-items","align-self","all","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","backface-visibility","background","background-attachment","background-blend-mode","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","block-size","border","border-block","border-block-color","border-block-end","border-block-end-color","border-block-end-style","border-block-end-width","border-block-start","border-block-start-color","border-block-start-style","border-block-start-width","border-block-style","border-block-width","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-inline","border-inline-color","border-inline-end","border-inline-end-color","border-inline-end-style","border-inline-end-width","border-inline-start","border-inline-start-color","border-inline-start-style","border-inline-start-width","border-inline-style","border-inline-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","caret-color","clear","clip","clip-path","clip-rule","color","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","contain","content","content-visibility","counter-increment","counter-reset","cue","cue-after","cue-before","cursor","direction","display","empty-cells","filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","flow","font","font-display","font-family","font-feature-settings","font-kerning","font-language-override","font-size","font-size-adjust","font-smoothing","font-stretch","font-style","font-synthesis","font-variant","font-variant-caps","font-variant-east-asian","font-variant-ligatures","font-variant-numeric","font-variant-position","font-variation-settings","font-weight","gap","glyph-orientation-vertical","grid","grid-area","grid-auto-columns","grid-auto-flow","grid-auto-rows","grid-column","grid-column-end","grid-column-start","grid-gap","grid-row","grid-row-end","grid-row-start","grid-template","grid-template-areas","grid-template-columns","grid-template-rows","hanging-punctuation","height","hyphens","icon","image-orientation","image-rendering","image-resolution","ime-mode","inline-size","isolation","justify-content","left","letter-spacing","line-break","line-height","list-style","list-style-image","list-style-position","list-style-type","margin","margin-block","margin-block-end","margin-block-start","margin-bottom","margin-inline","margin-inline-end","margin-inline-start","margin-left","margin-right","margin-top","marks","mask","mask-border","mask-border-mode","mask-border-outset","mask-border-repeat","mask-border-slice","mask-border-source","mask-border-width","mask-clip","mask-composite","mask-image","mask-mode","mask-origin","mask-position","mask-repeat","mask-size","mask-type","max-block-size","max-height","max-inline-size","max-width","min-block-size","min-height","min-inline-size","min-width","mix-blend-mode","nav-down","nav-index","nav-left","nav-right","nav-up","none","normal","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-wrap","overflow-x","overflow-y","padding","padding-block","padding-block-end","padding-block-start","padding-bottom","padding-inline","padding-inline-end","padding-inline-start","padding-left","padding-right","padding-top","page-break-after","page-break-before","page-break-inside","pause","pause-after","pause-before","perspective","perspective-origin","pointer-events","position","quotes","resize","rest","rest-after","rest-before","right","row-gap","scroll-margin","scroll-margin-block","scroll-margin-block-end","scroll-margin-block-start","scroll-margin-bottom","scroll-margin-inline","scroll-margin-inline-end","scroll-margin-inline-start","scroll-margin-left","scroll-margin-right","scroll-margin-top","scroll-padding","scroll-padding-block","scroll-padding-block-end","scroll-padding-block-start","scroll-padding-bottom","scroll-padding-inline","scroll-padding-inline-end","scroll-padding-inline-start","scroll-padding-left","scroll-padding-right","scroll-padding-top","scroll-snap-align","scroll-snap-stop","scroll-snap-type","scrollbar-color","scrollbar-gutter","scrollbar-width","shape-image-threshold","shape-margin","shape-outside","speak","speak-as","src","tab-size","table-layout","text-align","text-align-all","text-align-last","text-combine-upright","text-decoration","text-decoration-color","text-decoration-line","text-decoration-style","text-emphasis","text-emphasis-color","text-emphasis-position","text-emphasis-style","text-indent","text-justify","text-orientation","text-overflow","text-rendering","text-shadow","text-transform","text-underline-position","top","transform","transform-box","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","voice-balance","voice-duration","voice-family","voice-pitch","voice-range","voice-rate","voice-stress","voice-volume","white-space","widows","width","will-change","word-break","word-spacing","word-wrap","writing-mode","z-index"].reverse() +;return n=>{const a=n.regex,l=(e=>({IMPORTANT:{scope:"meta",begin:"!important"}, +BLOCK_COMMENT:e.C_BLOCK_COMMENT_MODE,HEXCOLOR:{scope:"number", +begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\b/},FUNCTION_DISPATCH:{ +className:"built_in",begin:/[\w-]+(?=\()/},ATTRIBUTE_SELECTOR_MODE:{ +scope:"selector-attr",begin:/\[/,end:/\]/,illegal:"$", +contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{ +scope:"number", +begin:e.NUMBER_RE+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?", +relevance:0},CSS_VARIABLE:{className:"attr",begin:/--[A-Za-z][A-Za-z0-9_-]*/} +}))(n),s=[n.APOS_STRING_MODE,n.QUOTE_STRING_MODE];return{name:"CSS", +case_insensitive:!0,illegal:/[=|'\$]/,keywords:{keyframePosition:"from to"}, +classNameAliases:{keyframePosition:"selector-tag"},contains:[l.BLOCK_COMMENT,{ +begin:/-(webkit|moz|ms|o)-(?=[a-z])/},l.CSS_NUMBER_MODE,{ +className:"selector-id",begin:/#[A-Za-z0-9_-]+/,relevance:0},{ +className:"selector-class",begin:"\\.[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0 +},l.ATTRIBUTE_SELECTOR_MODE,{className:"selector-pseudo",variants:[{ +begin:":("+r.join("|")+")"},{begin:":(:)?("+t.join("|")+")"}]},l.CSS_VARIABLE,{ +className:"attribute",begin:"\\b("+o.join("|")+")\\b"},{begin:/:/,end:/[;}{]/, +contains:[l.BLOCK_COMMENT,l.HEXCOLOR,l.IMPORTANT,l.CSS_NUMBER_MODE,...s,{ +begin:/(url|data-uri)\(/,end:/\)/,relevance:0,keywords:{built_in:"url data-uri" +},contains:[...s,{className:"string",begin:/[^)]/,endsWithParent:!0, +excludeEnd:!0}]},l.FUNCTION_DISPATCH]},{begin:a.lookahead(/@/),end:"[{;]", +relevance:0,illegal:/:/,contains:[{className:"keyword",begin:/@-?\w[\w]*(-\w+)*/ +},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:{ +$pattern:/[a-z-]+/,keyword:"and or not only",attribute:i.join(" ")},contains:[{ +begin:/[a-z-]+(?=:)/,className:"attribute"},...s,l.CSS_NUMBER_MODE]}]},{ +className:"selector-tag",begin:"\\b("+e.join("|")+")\\b"}]}}})() +;hljs.registerLanguage("css",e)})();/*! `plaintext` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var t=(()=>{"use strict";return t=>({name:"Plain text", +aliases:["text","txt"],disableAutodetect:!0})})() +;hljs.registerLanguage("plaintext",t)})();/*! `scss` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var e=(()=>{"use strict" +;const e=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","p","q","quote","samp","section","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],r=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"],i=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"],t=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"],o=["align-content","align-items","align-self","all","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","backface-visibility","background","background-attachment","background-blend-mode","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","block-size","border","border-block","border-block-color","border-block-end","border-block-end-color","border-block-end-style","border-block-end-width","border-block-start","border-block-start-color","border-block-start-style","border-block-start-width","border-block-style","border-block-width","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-inline","border-inline-color","border-inline-end","border-inline-end-color","border-inline-end-style","border-inline-end-width","border-inline-start","border-inline-start-color","border-inline-start-style","border-inline-start-width","border-inline-style","border-inline-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","caret-color","clear","clip","clip-path","clip-rule","color","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","contain","content","content-visibility","counter-increment","counter-reset","cue","cue-after","cue-before","cursor","direction","display","empty-cells","filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","flow","font","font-display","font-family","font-feature-settings","font-kerning","font-language-override","font-size","font-size-adjust","font-smoothing","font-stretch","font-style","font-synthesis","font-variant","font-variant-caps","font-variant-east-asian","font-variant-ligatures","font-variant-numeric","font-variant-position","font-variation-settings","font-weight","gap","glyph-orientation-vertical","grid","grid-area","grid-auto-columns","grid-auto-flow","grid-auto-rows","grid-column","grid-column-end","grid-column-start","grid-gap","grid-row","grid-row-end","grid-row-start","grid-template","grid-template-areas","grid-template-columns","grid-template-rows","hanging-punctuation","height","hyphens","icon","image-orientation","image-rendering","image-resolution","ime-mode","inline-size","isolation","justify-content","left","letter-spacing","line-break","line-height","list-style","list-style-image","list-style-position","list-style-type","margin","margin-block","margin-block-end","margin-block-start","margin-bottom","margin-inline","margin-inline-end","margin-inline-start","margin-left","margin-right","margin-top","marks","mask","mask-border","mask-border-mode","mask-border-outset","mask-border-repeat","mask-border-slice","mask-border-source","mask-border-width","mask-clip","mask-composite","mask-image","mask-mode","mask-origin","mask-position","mask-repeat","mask-size","mask-type","max-block-size","max-height","max-inline-size","max-width","min-block-size","min-height","min-inline-size","min-width","mix-blend-mode","nav-down","nav-index","nav-left","nav-right","nav-up","none","normal","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-wrap","overflow-x","overflow-y","padding","padding-block","padding-block-end","padding-block-start","padding-bottom","padding-inline","padding-inline-end","padding-inline-start","padding-left","padding-right","padding-top","page-break-after","page-break-before","page-break-inside","pause","pause-after","pause-before","perspective","perspective-origin","pointer-events","position","quotes","resize","rest","rest-after","rest-before","right","row-gap","scroll-margin","scroll-margin-block","scroll-margin-block-end","scroll-margin-block-start","scroll-margin-bottom","scroll-margin-inline","scroll-margin-inline-end","scroll-margin-inline-start","scroll-margin-left","scroll-margin-right","scroll-margin-top","scroll-padding","scroll-padding-block","scroll-padding-block-end","scroll-padding-block-start","scroll-padding-bottom","scroll-padding-inline","scroll-padding-inline-end","scroll-padding-inline-start","scroll-padding-left","scroll-padding-right","scroll-padding-top","scroll-snap-align","scroll-snap-stop","scroll-snap-type","scrollbar-color","scrollbar-gutter","scrollbar-width","shape-image-threshold","shape-margin","shape-outside","speak","speak-as","src","tab-size","table-layout","text-align","text-align-all","text-align-last","text-combine-upright","text-decoration","text-decoration-color","text-decoration-line","text-decoration-style","text-emphasis","text-emphasis-color","text-emphasis-position","text-emphasis-style","text-indent","text-justify","text-orientation","text-overflow","text-rendering","text-shadow","text-transform","text-underline-position","top","transform","transform-box","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","voice-balance","voice-duration","voice-family","voice-pitch","voice-range","voice-rate","voice-stress","voice-volume","white-space","widows","width","will-change","word-break","word-spacing","word-wrap","writing-mode","z-index"].reverse() +;return n=>{const a=(e=>({IMPORTANT:{scope:"meta",begin:"!important"}, +BLOCK_COMMENT:e.C_BLOCK_COMMENT_MODE,HEXCOLOR:{scope:"number", +begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\b/},FUNCTION_DISPATCH:{ +className:"built_in",begin:/[\w-]+(?=\()/},ATTRIBUTE_SELECTOR_MODE:{ +scope:"selector-attr",begin:/\[/,end:/\]/,illegal:"$", +contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{ +scope:"number", +begin:e.NUMBER_RE+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?", +relevance:0},CSS_VARIABLE:{className:"attr",begin:/--[A-Za-z][A-Za-z0-9_-]*/} +}))(n),l=t,s=i,d="@[a-z-]+",c={className:"variable", +begin:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b",relevance:0};return{name:"SCSS", +case_insensitive:!0,illegal:"[=/|']", +contains:[n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE,a.CSS_NUMBER_MODE,{ +className:"selector-id",begin:"#[A-Za-z0-9_-]+",relevance:0},{ +className:"selector-class",begin:"\\.[A-Za-z0-9_-]+",relevance:0 +},a.ATTRIBUTE_SELECTOR_MODE,{className:"selector-tag", +begin:"\\b("+e.join("|")+")\\b",relevance:0},{className:"selector-pseudo", +begin:":("+s.join("|")+")"},{className:"selector-pseudo", +begin:":(:)?("+l.join("|")+")"},c,{begin:/\(/,end:/\)/, +contains:[a.CSS_NUMBER_MODE]},a.CSS_VARIABLE,{className:"attribute", +begin:"\\b("+o.join("|")+")\\b"},{ +begin:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b" +},{begin:/:/,end:/[;}{]/,relevance:0, +contains:[a.BLOCK_COMMENT,c,a.HEXCOLOR,a.CSS_NUMBER_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,a.IMPORTANT,a.FUNCTION_DISPATCH] +},{begin:"@(page|font-face)",keywords:{$pattern:d,keyword:"@page @font-face"}},{ +begin:"@",end:"[{;]",returnBegin:!0,keywords:{$pattern:/[a-z-]+/, +keyword:"and or not only",attribute:r.join(" ")},contains:[{begin:d, +className:"keyword"},{begin:/[a-z-]+(?=:)/,className:"attribute" +},c,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,a.HEXCOLOR,a.CSS_NUMBER_MODE] +},a.FUNCTION_DISPATCH]}}})();hljs.registerLanguage("scss",e)})();/*! `typescript` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var e=(()=>{"use strict" +;const e="[A-Za-z$_][0-9A-Za-z$_]*",n=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],a=["true","false","null","undefined","NaN","Infinity"],t=["Object","Function","Boolean","Symbol","Math","Date","Number","BigInt","String","RegExp","Array","Float32Array","Float64Array","Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Int32Array","Uint16Array","Uint32Array","BigInt64Array","BigUint64Array","Set","Map","WeakSet","WeakMap","ArrayBuffer","SharedArrayBuffer","Atomics","DataView","JSON","Promise","Generator","GeneratorFunction","AsyncFunction","Reflect","Proxy","Intl","WebAssembly"],s=["Error","EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"],c=["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],r=["arguments","this","super","console","window","document","localStorage","module","global"],i=[].concat(c,t,s) +;function o(o){const l=o.regex,d=e,b={begin:/<[A-Za-z0-9\\._:-]+/, +end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,n)=>{ +const a=e[0].length+e.index,t=e.input[a] +;if("<"===t||","===t)return void n.ignoreMatch();let s +;">"===t&&(((e,{after:n})=>{const a="",M={ +match:[/const|var|let/,/\s+/,d,/\s*/,/=\s*/,/(async\s*)?/,l.lookahead(T)], +keywords:"async",className:{1:"keyword",3:"title.function"},contains:[S]} +;return{name:"Javascript",aliases:["js","jsx","mjs","cjs"],keywords:g,exports:{ +PARAMS_CONTAINS:v,CLASS_REFERENCE:R},illegal:/#(?![$_A-z])/, +contains:[o.SHEBANG({label:"shebang",binary:"node",relevance:5}),{ +label:"use_strict",className:"meta",relevance:10, +begin:/^\s*['"]use (strict|asm)['"]/ +},o.APOS_STRING_MODE,o.QUOTE_STRING_MODE,A,p,_,N,{match:/\$\d+/},E,R,{ +className:"attr",begin:d+l.lookahead(":"),relevance:0},M,{ +begin:"("+o.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*", +keywords:"return throw case",relevance:0,contains:[N,o.REGEXP_MODE,{ +className:"function",begin:T,returnBegin:!0,end:"\\s*=>",contains:[{ +className:"params",variants:[{begin:o.UNDERSCORE_IDENT_RE,relevance:0},{ +className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0, +excludeEnd:!0,keywords:g,contains:v}]}]},{begin:/,/,relevance:0},{match:/\s+/, +relevance:0},{variants:[{begin:"<>",end:""},{ +match:/<[A-Za-z0-9\\._:-]+\s*\/>/},{begin:b.begin, +"on:begin":b.isTrulyOpeningTag,end:b.end}],subLanguage:"xml",contains:[{ +begin:b.begin,end:b.end,skip:!0,contains:["self"]}]}]},x,{ +beginKeywords:"while if switch catch for"},{ +begin:"\\b(?!function)"+o.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{", +returnBegin:!0,label:"func.def",contains:[S,o.inherit(o.TITLE_MODE,{begin:d, +className:"title.function"})]},{match:/\.\.\./,relevance:0},I,{match:"\\$"+d, +relevance:0},{match:[/\bconstructor(?=\s*\()/],className:{1:"title.function"}, +contains:[S]},k,{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/, +className:"variable.constant"},w,C,{match:/\$[(.]/}]}}return t=>{ +const s=o(t),c=["any","void","number","boolean","string","object","never","symbol","bigint","unknown"],l={ +beginKeywords:"namespace",end:/\{/,excludeEnd:!0, +contains:[s.exports.CLASS_REFERENCE]},d={beginKeywords:"interface",end:/\{/, +excludeEnd:!0,keywords:{keyword:"interface extends",built_in:c}, +contains:[s.exports.CLASS_REFERENCE]},b={$pattern:e, +keyword:n.concat(["type","namespace","interface","public","private","protected","implements","declare","abstract","readonly","enum","override"]), +literal:a,built_in:i.concat(c),"variable.language":r},g={className:"meta", +begin:"@[A-Za-z$_][0-9A-Za-z$_]*"},u=(e,n,a)=>{ +const t=e.contains.findIndex((e=>e.label===n)) +;if(-1===t)throw Error("can not find mode to replace");e.contains.splice(t,1,a)} +;return Object.assign(s.keywords,b), +s.exports.PARAMS_CONTAINS.push(g),s.contains=s.contains.concat([g,l,d]), +u(s,"shebang",t.SHEBANG()),u(s,"use_strict",{className:"meta",relevance:10, +begin:/^\s*['"]use strict['"]/ +}),s.contains.find((e=>"func.def"===e.label)).relevance=0,Object.assign(s,{ +name:"TypeScript",aliases:["ts","tsx"]}),s}})() +;hljs.registerLanguage("typescript",e)})();/*! `ruby` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var e=(()=>{"use strict";return e=>{ +const n=e.regex,a="([a-zA-Z_]\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?)",s=n.either(/\b([A-Z]+[a-z0-9]+)+/,/\b([A-Z]+[a-z0-9]+)+[A-Z]+/),i=n.concat(s,/(::\w+)*/),t={ +"variable.constant":["__FILE__","__LINE__","__ENCODING__"], +"variable.language":["self","super"], +keyword:["alias","and","begin","BEGIN","break","case","class","defined","do","else","elsif","end","END","ensure","for","if","in","module","next","not","or","redo","require","rescue","retry","return","then","undef","unless","until","when","while","yield","include","extend","prepend","public","private","protected","raise","throw"], +built_in:["proc","lambda","attr_accessor","attr_reader","attr_writer","define_method","private_constant","module_function"], +literal:["true","false","nil"]},c={className:"doctag",begin:"@[A-Za-z]+"},r={ +begin:"#<",end:">"},b=[e.COMMENT("#","$",{contains:[c] +}),e.COMMENT("^=begin","^=end",{contains:[c],relevance:10 +}),e.COMMENT("^__END__",e.MATCH_NOTHING_RE)],l={className:"subst",begin:/#\{/, +end:/\}/,keywords:t},d={className:"string",contains:[e.BACKSLASH_ESCAPE,l], +variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{ +begin:/%[qQwWx]?\(/,end:/\)/},{begin:/%[qQwWx]?\[/,end:/\]/},{ +begin:/%[qQwWx]?\{/,end:/\}/},{begin:/%[qQwWx]?/},{begin:/%[qQwWx]?\//, +end:/\//},{begin:/%[qQwWx]?%/,end:/%/},{begin:/%[qQwWx]?-/,end:/-/},{ +begin:/%[qQwWx]?\|/,end:/\|/},{begin:/\B\?(\\\d{1,3})/},{ +begin:/\B\?(\\x[A-Fa-f0-9]{1,2})/},{begin:/\B\?(\\u\{?[A-Fa-f0-9]{1,6}\}?)/},{ +begin:/\B\?(\\M-\\C-|\\M-\\c|\\c\\M-|\\M-|\\C-\\M-)[\x20-\x7e]/},{ +begin:/\B\?\\(c|C-)[\x20-\x7e]/},{begin:/\B\?\\?\S/},{ +begin:n.concat(/<<[-~]?'?/,n.lookahead(/(\w+)(?=\W)[^\n]*\n(?:[^\n]*\n)*?\s*\1\b/)), +contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/, +contains:[e.BACKSLASH_ESCAPE,l]})]}]},o="[0-9](_?[0-9])*",g={className:"number", +relevance:0,variants:[{ +begin:`\\b([1-9](_?[0-9])*|0)(\\.(${o}))?([eE][+-]?(${o})|r)?i?\\b`},{ +begin:"\\b0[dD][0-9](_?[0-9])*r?i?\\b"},{begin:"\\b0[bB][0-1](_?[0-1])*r?i?\\b" +},{begin:"\\b0[oO][0-7](_?[0-7])*r?i?\\b"},{ +begin:"\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*r?i?\\b"},{ +begin:"\\b0(_?[0-7])+r?i?\\b"}]},_={variants:[{match:/\(\)/},{ +className:"params",begin:/\(/,end:/(?=\))/,excludeBegin:!0,endsParent:!0, +keywords:t}]},u=[d,{variants:[{match:[/class\s+/,i,/\s+<\s+/,i]},{ +match:[/\b(class|module)\s+/,i]}],scope:{2:"title.class", +4:"title.class.inherited"},keywords:t},{match:[/(include|extend)\s+/,i],scope:{ +2:"title.class"},keywords:t},{relevance:0,match:[i,/\.new[. (]/],scope:{ +1:"title.class"}},{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/, +className:"variable.constant"},{relevance:0,match:s,scope:"title.class"},{ +match:[/def/,/\s+/,a],scope:{1:"keyword",3:"title.function"},contains:[_]},{ +begin:e.IDENT_RE+"::"},{className:"symbol", +begin:e.UNDERSCORE_IDENT_RE+"(!|\\?)?:",relevance:0},{className:"symbol", +begin:":(?!\\s)",contains:[d,{begin:a}],relevance:0},g,{className:"variable", +begin:"(\\$\\W)|((\\$|@@?)(\\w+))(?=[^@$?])(?![A-Za-z])(?![@$?'])"},{ +className:"params",begin:/\|/,end:/\|/,excludeBegin:!0,excludeEnd:!0, +relevance:0,keywords:t},{begin:"("+e.RE_STARTERS_RE+"|unless)\\s*", +keywords:"unless",contains:[{className:"regexp",contains:[e.BACKSLASH_ESCAPE,l], +illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*"},{begin:/%r\{/,end:/\}[a-z]*/},{ +begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[", +end:"\\][a-z]*"}]}].concat(r,b),relevance:0}].concat(r,b) +;l.contains=u,_.contains=u;const m=[{begin:/^\s*=>/,starts:{end:"$",contains:u} +},{className:"meta.prompt", +begin:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+[>*]|(\\w+-)?\\d+\\.\\d+\\.\\d+(p\\d+)?[^\\d][^>]+>)(?=[ ])", +starts:{end:"$",keywords:t,contains:u}}];return b.unshift(r),{name:"Ruby", +aliases:["rb","gemspec","podspec","thor","irb"],keywords:t,illegal:/\/\*/, +contains:[e.SHEBANG({binary:"ruby"})].concat(m).concat(b).concat(u)}}})() +;hljs.registerLanguage("ruby",e)})();/*! `yaml` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var e=(()=>{"use strict";return e=>{ +const n="true false yes no null",a="[\\w#;/?:@&=+$,.~*'()[\\]]+",s={ +className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/ +},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable", +variants:[{begin:/\{\{/,end:/\}\}/},{begin:/%\{/,end:/\}/}]}]},i=e.inherit(s,{ +variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),l={ +end:",",endsWithParent:!0,excludeEnd:!0,keywords:n,relevance:0},t={begin:/\{/, +end:/\}/,contains:[l],illegal:"\\n",relevance:0},g={begin:"\\[",end:"\\]", +contains:[l],illegal:"\\n",relevance:0},b=[{className:"attr",variants:[{ +begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{ +begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---\\s*$", +relevance:10},{className:"string", +begin:"[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*"},{ +begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0, +relevance:0},{className:"type",begin:"!\\w+!"+a},{className:"type", +begin:"!<"+a+">"},{className:"type",begin:"!"+a},{className:"type",begin:"!!"+a +},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta", +begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"-(?=[ ]|$)", +relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:n,keywords:{literal:n}},{ +className:"number", +begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b" +},{className:"number",begin:e.C_NUMBER_RE+"\\b",relevance:0},t,g,s],r=[...b] +;return r.pop(),r.push(i),l.contains=r,{name:"YAML",case_insensitive:!0, +aliases:["yml"],contains:b}}})();hljs.registerLanguage("yaml",e)})();/*! `markdown` grammar compiled for Highlight.js 11.7.0 */ +(()=>{var e=(()=>{"use strict";return e=>{const n={begin:/<\/?[A-Za-z_]/, +end:">",subLanguage:"xml",relevance:0},a={variants:[{begin:/\[.+?\]\[.*?\]/, +relevance:0},{ +begin:/\[.+?\]\(((data|javascript|mailto):|(?:http|ftp)s?:\/\/).*?\)/, +relevance:2},{ +begin:e.regex.concat(/\[.+?\]\(/,/[A-Za-z][A-Za-z0-9+.-]*/,/:\/\/.*?\)/), +relevance:2},{begin:/\[.+?\]\([./?&#].*?\)/,relevance:1},{ +begin:/\[.*?\]\(.*?\)/,relevance:0}],returnBegin:!0,contains:[{match:/\[(?=\])/ +},{className:"string",relevance:0,begin:"\\[",end:"\\]",excludeBegin:!0, +returnEnd:!0},{className:"link",relevance:0,begin:"\\]\\(",end:"\\)", +excludeBegin:!0,excludeEnd:!0},{className:"symbol",relevance:0,begin:"\\]\\[", +end:"\\]",excludeBegin:!0,excludeEnd:!0}]},i={className:"strong",contains:[], +variants:[{begin:/_{2}(?!\s)/,end:/_{2}/},{begin:/\*{2}(?!\s)/,end:/\*{2}/}] +},s={className:"emphasis",contains:[],variants:[{begin:/\*(?![*\s])/,end:/\*/},{ +begin:/_(?![_\s])/,end:/_/,relevance:0}]},c=e.inherit(i,{contains:[] +}),t=e.inherit(s,{contains:[]});i.contains.push(t),s.contains.push(c) +;let g=[n,a];return[i,s,c,t].forEach((e=>{e.contains=e.contains.concat(g) +})),g=g.concat(i,s),{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{ +className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:g},{ +begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n", +contains:g}]}]},n,{className:"bullet",begin:"^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)", +end:"\\s+",excludeEnd:!0},i,s,{className:"quote",begin:"^>\\s+",contains:g, +end:"$"},{className:"code",variants:[{begin:"(`{3,})[^`](.|\\n)*?\\1`*[ ]*"},{ +begin:"(~{3,})[^~](.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{ +begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))", +contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{ +begin:"^[-\\*]{3,}",end:"$"},a,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{ +className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{ +className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}}})() +;hljs.registerLanguage("markdown",e)})(); \ No newline at end of file diff --git a/system/typemill/author/js/sortable.min.js b/system/typemill/author/js/sortable.min.js new file mode 100644 index 0000000..eba0614 --- /dev/null +++ b/system/typemill/author/js/sortable.min.js @@ -0,0 +1,2 @@ +/*! Sortable 1.10.2 - MIT | git://github.com/SortableJS/Sortable.git */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).Sortable=e()}(this,function(){"use strict";function o(t){return(o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function a(){return(a=Object.assign||function(t){for(var e=1;e"===e[0]&&(e=e.substring(1)),t)try{if(t.matches)return t.matches(e);if(t.msMatchesSelector)return t.msMatchesSelector(e);if(t.webkitMatchesSelector)return t.webkitMatchesSelector(e)}catch(t){return!1}return!1}}function P(t,e,n,o){if(t){n=n||document;do{if(null!=e&&(">"===e[0]?t.parentNode===n&&h(t,e):h(t,e))||o&&t===n)return t;if(t===n)break}while(t=(i=t).host&&i!==document&&i.host.nodeType?i.host:i.parentNode)}var i;return null}var f,p=/\s+/g;function k(t,e,n){if(t&&e)if(t.classList)t.classList[n?"add":"remove"](e);else{var o=(" "+t.className+" ").replace(p," ").replace(" "+e+" "," ");t.className=(o+(n?" "+e:"")).replace(p," ")}}function R(t,e,n){var o=t&&t.style;if(o){if(void 0===n)return document.defaultView&&document.defaultView.getComputedStyle?n=document.defaultView.getComputedStyle(t,""):t.currentStyle&&(n=t.currentStyle),void 0===e?n:n[e];e in o||-1!==e.indexOf("webkit")||(e="-webkit-"+e),o[e]=n+("string"==typeof n?"":"px")}}function v(t,e){var n="";if("string"==typeof t)n=t;else do{var o=R(t,"transform");o&&"none"!==o&&(n=o+" "+n)}while(!e&&(t=t.parentNode));var i=window.DOMMatrix||window.WebKitCSSMatrix||window.CSSMatrix||window.MSCSSMatrix;return i&&new i(n)}function g(t,e,n){if(t){var o=t.getElementsByTagName(e),i=0,r=o.length;if(n)for(;i=e.left-n&&r<=e.right+n,i=a>=e.top-n&&a<=e.bottom+n;return n&&o&&i?l=t:void 0}}),l}((t=t.touches?t.touches[0]:t).clientX,t.clientY);if(e){var n={};for(var o in t)t.hasOwnProperty(o)&&(n[o]=t[o]);n.target=n.rootEl=e,n.preventDefault=void 0,n.stopPropagation=void 0,e[j]._onDragOver(n)}}}function kt(t){z&&z.parentNode[j]._isOutsideThisEl(t.target)}function Rt(t,e){if(!t||!t.nodeType||1!==t.nodeType)throw"Sortable: `el` must be an HTMLElement, not ".concat({}.toString.call(t));this.el=t,this.options=e=a({},e),t[j]=this;var n={group:null,sort:!0,disabled:!1,store:null,handle:null,draggable:/^[uo]l$/i.test(t.nodeName)?">li":">*",swapThreshold:1,invertSwap:!1,invertedSwapThreshold:null,removeCloneOnHide:!0,direction:function(){return Ot(t,this.options)},ghostClass:"sortable-ghost",chosenClass:"sortable-chosen",dragClass:"sortable-drag",ignore:"a, img",filter:null,preventOnFilter:!0,animation:0,easing:null,setData:function(t,e){t.setData("Text",e.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0,delayOnTouchOnly:!1,touchStartThreshold:(Number.parseInt?Number:window).parseInt(window.devicePixelRatio,10)||1,forceFallback:!1,fallbackClass:"sortable-fallback",fallbackOnBody:!1,fallbackTolerance:0,fallbackOffset:{x:0,y:0},supportPointer:!1!==Rt.supportPointer&&"PointerEvent"in window,emptyInsertThreshold:5};for(var o in O.initializePlugins(this,t,n),n)o in e||(e[o]=n[o]);for(var i in At(e),this)"_"===i.charAt(0)&&"function"==typeof this[i]&&(this[i]=this[i].bind(this));this.nativeDraggable=!e.forceFallback&&xt,this.nativeDraggable&&(this.options.touchStartThreshold=1),e.supportPointer?u(t,"pointerdown",this._onTapStart):(u(t,"mousedown",this._onTapStart),u(t,"touchstart",this._onTapStart)),this.nativeDraggable&&(u(t,"dragover",this),u(t,"dragenter",this)),bt.push(this.el),e.store&&e.store.get&&this.sort(e.store.get(this)||[]),a(this,T())}function Xt(t,e,n,o,i,r,a,l){var s,c,u=t[j],d=u.options.onMove;return!window.CustomEvent||w||E?(s=document.createEvent("Event")).initEvent("move",!0,!0):s=new CustomEvent("move",{bubbles:!0,cancelable:!0}),s.to=e,s.from=t,s.dragged=n,s.draggedRect=o,s.related=i||e,s.relatedRect=r||X(e),s.willInsertAfter=l,s.originalEvent=a,t.dispatchEvent(s),d&&(c=d.call(u,s,a)),c}function Yt(t){t.draggable=!1}function Bt(){Dt=!1}function Ft(t){for(var e=t.tagName+t.className+t.src+t.href+t.textContent,n=e.length,o=0;n--;)o+=e.charCodeAt(n);return o.toString(36)}function Ht(t){return setTimeout(t,0)}function Lt(t){return clearTimeout(t)}Rt.prototype={constructor:Rt,_isOutsideThisEl:function(t){this.el.contains(t)||t===this.el||(ht=null)},_getDirection:function(t,e){return"function"==typeof this.options.direction?this.options.direction.call(this,t,e,z):this.options.direction},_onTapStart:function(e){if(e.cancelable){var n=this,o=this.el,t=this.options,i=t.preventOnFilter,r=e.type,a=e.touches&&e.touches[0]||e.pointerType&&"touch"===e.pointerType&&e,l=(a||e).target,s=e.target.shadowRoot&&(e.path&&e.path[0]||e.composedPath&&e.composedPath()[0])||l,c=t.filter;if(function(t){St.length=0;var e=t.getElementsByTagName("input"),n=e.length;for(;n--;){var o=e[n];o.checked&&St.push(o)}}(o),!z&&!(/mousedown|pointerdown/.test(r)&&0!==e.button||t.disabled||s.isContentEditable||(l=P(l,t.draggable,o,!1))&&l.animated||Z===l)){if(J=F(l),et=F(l,t.draggable),"function"==typeof c){if(c.call(this,e,l,this))return W({sortable:n,rootEl:s,name:"filter",targetEl:l,toEl:o,fromEl:o}),K("filter",n,{evt:e}),void(i&&e.cancelable&&e.preventDefault())}else if(c&&(c=c.split(",").some(function(t){if(t=P(s,t.trim(),o,!1))return W({sortable:n,rootEl:t,name:"filter",targetEl:l,fromEl:o,toEl:o}),K("filter",n,{evt:e}),!0})))return void(i&&e.cancelable&&e.preventDefault());t.handle&&!P(s,t.handle,o,!1)||this._prepareDragStart(e,a,l)}}},_prepareDragStart:function(t,e,n){var o,i=this,r=i.el,a=i.options,l=r.ownerDocument;if(n&&!z&&n.parentNode===r){var s=X(n);if(q=r,G=(z=n).parentNode,V=z.nextSibling,Z=n,ot=a.group,rt={target:Rt.dragged=z,clientX:(e||t).clientX,clientY:(e||t).clientY},ct=rt.clientX-s.left,ut=rt.clientY-s.top,this._lastX=(e||t).clientX,this._lastY=(e||t).clientY,z.style["will-change"]="all",o=function(){K("delayEnded",i,{evt:t}),Rt.eventCanceled?i._onDrop():(i._disableDelayedDragEvents(),!c&&i.nativeDraggable&&(z.draggable=!0),i._triggerDragStart(t,e),W({sortable:i,name:"choose",originalEvent:t}),k(z,a.chosenClass,!0))},a.ignore.split(",").forEach(function(t){g(z,t.trim(),Yt)}),u(l,"dragover",Pt),u(l,"mousemove",Pt),u(l,"touchmove",Pt),u(l,"mouseup",i._onDrop),u(l,"touchend",i._onDrop),u(l,"touchcancel",i._onDrop),c&&this.nativeDraggable&&(this.options.touchStartThreshold=4,z.draggable=!0),K("delayStart",this,{evt:t}),!a.delay||a.delayOnTouchOnly&&!e||this.nativeDraggable&&(E||w))o();else{if(Rt.eventCanceled)return void this._onDrop();u(l,"mouseup",i._disableDelayedDrag),u(l,"touchend",i._disableDelayedDrag),u(l,"touchcancel",i._disableDelayedDrag),u(l,"mousemove",i._delayedDragTouchMoveHandler),u(l,"touchmove",i._delayedDragTouchMoveHandler),a.supportPointer&&u(l,"pointermove",i._delayedDragTouchMoveHandler),i._dragStartTimer=setTimeout(o,a.delay)}}},_delayedDragTouchMoveHandler:function(t){var e=t.touches?t.touches[0]:t;Math.max(Math.abs(e.clientX-this._lastX),Math.abs(e.clientY-this._lastY))>=Math.floor(this.options.touchStartThreshold/(this.nativeDraggable&&window.devicePixelRatio||1))&&this._disableDelayedDrag()},_disableDelayedDrag:function(){z&&Yt(z),clearTimeout(this._dragStartTimer),this._disableDelayedDragEvents()},_disableDelayedDragEvents:function(){var t=this.el.ownerDocument;d(t,"mouseup",this._disableDelayedDrag),d(t,"touchend",this._disableDelayedDrag),d(t,"touchcancel",this._disableDelayedDrag),d(t,"mousemove",this._delayedDragTouchMoveHandler),d(t,"touchmove",this._delayedDragTouchMoveHandler),d(t,"pointermove",this._delayedDragTouchMoveHandler)},_triggerDragStart:function(t,e){e=e||"touch"==t.pointerType&&t,!this.nativeDraggable||e?this.options.supportPointer?u(document,"pointermove",this._onTouchMove):u(document,e?"touchmove":"mousemove",this._onTouchMove):(u(z,"dragend",this),u(q,"dragstart",this._onDragStart));try{document.selection?Ht(function(){document.selection.empty()}):window.getSelection().removeAllRanges()}catch(t){}},_dragStarted:function(t,e){if(vt=!1,q&&z){K("dragStarted",this,{evt:e}),this.nativeDraggable&&u(document,"dragover",kt);var n=this.options;t||k(z,n.dragClass,!1),k(z,n.ghostClass,!0),Rt.active=this,t&&this._appendGhost(),W({sortable:this,name:"start",originalEvent:e})}else this._nulling()},_emulateDragOver:function(){if(at){this._lastX=at.clientX,this._lastY=at.clientY,Nt();for(var t=document.elementFromPoint(at.clientX,at.clientY),e=t;t&&t.shadowRoot&&(t=t.shadowRoot.elementFromPoint(at.clientX,at.clientY))!==e;)e=t;if(z.parentNode[j]._isOutsideThisEl(t),e)do{if(e[j]){if(e[j]._onDragOver({clientX:at.clientX,clientY:at.clientY,target:t,rootEl:e})&&!this.options.dragoverBubble)break}t=e}while(e=e.parentNode);It()}},_onTouchMove:function(t){if(rt){var e=this.options,n=e.fallbackTolerance,o=e.fallbackOffset,i=t.touches?t.touches[0]:t,r=U&&v(U,!0),a=U&&r&&r.a,l=U&&r&&r.d,s=Ct&>&&b(gt),c=(i.clientX-rt.clientX+o.x)/(a||1)+(s?s[0]-Et[0]:0)/(a||1),u=(i.clientY-rt.clientY+o.y)/(l||1)+(s?s[1]-Et[1]:0)/(l||1);if(!Rt.active&&!vt){if(n&&Math.max(Math.abs(i.clientX-this._lastX),Math.abs(i.clientY-this._lastY))o.right+10||t.clientX<=o.right&&t.clientY>o.bottom&&t.clientX>=o.left:t.clientX>o.right&&t.clientY>o.top||t.clientX<=o.right&&t.clientY>o.bottom+10}(n,a,this)&&!g.animated){if(g===z)return A(!1);if(g&&l===n.target&&(s=g),s&&(i=X(s)),!1!==Xt(q,l,z,o,s,i,n,!!s))return O(),l.appendChild(z),G=l,N(),A(!0)}else if(s.parentNode===l){i=X(s);var v,m,b,y=z.parentNode!==l,w=!function(t,e,n){var o=n?t.left:t.top,i=n?t.right:t.bottom,r=n?t.width:t.height,a=n?e.left:e.top,l=n?e.right:e.bottom,s=n?e.width:e.height;return o===a||i===l||o+r/2===a+s/2}(z.animated&&z.toRect||o,s.animated&&s.toRect||i,a),E=a?"top":"left",D=Y(s,"top","top")||Y(z,"top","top"),S=D?D.scrollTop:void 0;if(ht!==s&&(m=i[E],yt=!1,wt=!w&&e.invertSwap||y),0!==(v=function(t,e,n,o,i,r,a,l){var s=o?t.clientY:t.clientX,c=o?n.height:n.width,u=o?n.top:n.left,d=o?n.bottom:n.right,h=!1;if(!a)if(l&&pt 0) + { + let imageFile = changeevent.target.files[0]; + let size = imageFile.size / 1024 / 1024; + + if (!imageFile.type.match('image.*')) + { + // publishController.errors.message = "Only images are allowed."; + } + else if (size > this.maxsize) + { + // publishController.errors.message = "The maximal size of images is " + this.maxsize + " MB"; + } + else + { + let reader = new FileReader(); + reader.readAsDataURL(imageFile); + reader.onload = function(fileevent) + { + var imgUploadField = changeevent.target.closest(".img-upload"); + var imgSrc = imgUploadField.getElementsByClassName("function-img-src")[0]; + imgSrc.src = fileevent.target.result; + var imgUrl = imgUploadField.getElementsByClassName("function-img-url")[0]; + imgUrl.value = imageFile.name; + } + } + } + } + + }, true); + }, + + start: function(){ + this.setYoutubeItems(); + this.addYoutubePlayButtons(); + this.listenToClick(); + this.listenToChange(); + }, +}; + +typemillUtilities.start(); \ No newline at end of file diff --git a/system/typemill/author/js/vue-blox-components.js b/system/typemill/author/js/vue-blox-components.js new file mode 100644 index 0000000..c68aef3 --- /dev/null +++ b/system/typemill/author/js/vue-blox-components.js @@ -0,0 +1,3249 @@ +bloxeditor.component('title-component', { + props: ['markdown', 'disabled', 'index'], + template: `
          + +
          `, + mounted: function(){ + this.$refs.markdown.focus(); + + autosize(document.querySelectorAll('textarea')); + + eventBus.$on('beforeSave', this.beforeSave ); + }, + methods: { + beforeSave() + { + /* You can do something here before save. Check image and file component */ + this.$emit('saveBlockEvent'); + }, + updatemarkdown(content) + { + this.$emit('updateMarkdownEvent', content); + }, + }, +}) + +bloxeditor.component('markdown-component', { + props: ['markdown', 'disabled', 'index'], + template: `
          +
          + + + +
          + + + +
          `, + mounted: function(){ + this.$refs.markdown.focus(); + + this.$nextTick(function () { + autosize(this.$refs.markdown); // Using $refs directly + }.bind(this)); + + /* + this.$nextTick(function () { + autosize(document.querySelectorAll('textarea')); + }); +*/ + eventBus.$on('beforeSave', this.beforeSave ); + }, + methods: { + beforeSave() + { + this.$emit('saveBlockEvent'); + }, + updatemarkdown(content) + { + var emptyline = /^\s*$(?:\r\n?|\n)/gm; + + if(content.search(emptyline) > -1) + { + this.$emit('updateMarkdownEvent', content.trim()); + this.$emit('saveBlockEvent'); + } + else + { + this.$emit('updateMarkdownEvent', content); + } + } + }, +}) + +bloxeditor.component('headline-component', { + props: ['markdown', 'disabled', 'index'], + template: `
          +
          + + + +
          + + +
          `, + data: function(){ + return { + level: '', + hlevel: '', + compmarkdown: '' + } + }, + mounted: function(){ + + eventBus.$on('beforeSave', this.beforeSave ); + + this.$refs.markdown.focus(); + + this.compmarkdown = this.markdown; + + if(!this.compmarkdown) + { + this.compmarkdown = '## '; + this.level = '2'; + this.hlevel = 'h2'; + } + else + { + this.level = this.getHeadlineLevel(this.markdown); + this.hlevel = 'h' + this.level; + } + }, + methods: { + beforeSave() + { + this.$emit('saveBlockEvent'); + }, + updatemarkdown(event) + { + this.level = this.getHeadlineLevel(this.compmarkdown); + if(this.level > 6) + { + this.compmarkdown = '######' + this.compmarkdown.substr(this.level); + this.level = 6; + } + else if(this.level < 2) + { + this.compmarkdown = '##' + this.compmarkdown.substr(this.level); + this.level = 2; + } + this.hlevel = 'h' + this.level; + + this.$emit('updateMarkdownEvent', this.compmarkdown); + }, + headlinedown() + { + this.level = this.getHeadlineLevel(this.compmarkdown); + if(this.level < 6) + { + this.compmarkdown = this.compmarkdown.substr(0, this.level) + '#' + this.compmarkdown.substr(this.level); + this.level = this.level+1; + this.hlevel = 'h' + this.level; + } + else + { + this.compmarkdown = '##' + this.compmarkdown.substr(this.level); + this.level = 2; + this.hlevel = 'h2'; + } + + this.$emit('updateMarkdownEvent', this.compmarkdown); + }, + getHeadlineLevel(str) + { + var count = 0; + for(var i = 0; i < str.length; i++){ + if(str[i] != '#'){ return count } + count++; + } + return count; + }, + }, +}) + +bloxeditor.component('ulist-component', { + props: ['markdown', 'disabled', 'index'], + template: `
          +
          + + + +
          + + + +
          `, + data: function(){ + return { + compmarkdown: '' + } + }, + mounted: function(){ + + eventBus.$on('beforeSave', this.beforeSave ); + + this.compmarkdown = this.markdown; + + if(this.compmarkdown == '') + { + this.compmarkdown = '* '; + } + else + { + var lines = this.compmarkdown.split("\n"); + var length = lines.length + var md = ''; + + for(i = 0; i < length; i++) + { + var clean = lines[i]; + clean = clean.replace(/^- /, '* '); + clean = clean.replace(/^\+ /, '* '); + if(i == length-1) + { + md += clean; + } + else + { + md += clean + '\n'; + } + } + this.compmarkdown = md; + } + + this.$emit('updateMarkdownEvent', this.compmarkdown); + + this.$nextTick(function () { + autosize(document.querySelectorAll('textarea')); + }); + + this.$refs.markdown.focus(); + }, + methods: { + beforeSave() + { + this.$emit('saveBlockEvent'); + }, + updatemarkdown(value) + { + this.$emit('updateMarkdownEvent', value); + }, + newLine(event) + { + this.compmarkdown = this.markdown; + + let listend = '* \n'; // '1. \n'; + let liststyle = '* '; // '1. '; + + if(this.compmarkdown.endsWith(listend)) + { + this.compmarkdown = this.compmarkdown.replace(listend, ''); + this.$emit('updateMarkdownEvent', this.compmarkdown); + this.$emit('saveBlockEvent'); + } + else + { + let mdtextarea = document.getElementsByTagName('textarea'); + let start = mdtextarea[0].selectionStart; + let end = mdtextarea[0].selectionEnd; + + this.compmarkdown = this.compmarkdown.substr(0, end) + liststyle + this.compmarkdown.substr(end); + + this.$emit('updateMarkdownEvent', this.compmarkdown); + + mdtextarea[0].focus(); + if(mdtextarea[0].setSelectionRange) + { + setTimeout(function(){ + // var spacer = (this.componentType == "ulist-component") ? 2 : 3; + mdtextarea[0].setSelectionRange(end+2, end+2); + }, 1); + } + } + }, + } +}) + +bloxeditor.component('olist-component', { + props: ['markdown', 'disabled', 'index'], + template: `
          +
          + + + +
          + + + +
          `, + data: function(){ + return { + compmarkdown: '' + } + }, + mounted: function(){ + + eventBus.$on('beforeSave', this.beforeSave ); + + this.compmarkdown = this.markdown; + + if(this.compmarkdown == '') + { + this.compmarkdown = '1. '; + } + + this.$emit('updateMarkdownEvent', this.compmarkdown); + + this.$nextTick(function () { + autosize(document.querySelectorAll('textarea')); + }); + + this.$refs.markdown.focus(); + }, + methods: { + beforeSave() + { + this.$emit('saveBlockEvent'); + }, + updatemarkdown(value) + { + this.$emit('updateMarkdownEvent', value); + }, + newLine(event) + { + this.compmarkdown = this.markdown; + + let listend = '1. \n'; + let liststyle = '1. '; + + if(this.compmarkdown.endsWith(listend)) + { + this.compmarkdown = this.compmarkdown.replace(listend, ''); + this.$emit('updateMarkdownEvent', this.compmarkdown); + this.$emit('saveBlockEvent'); + } + else + { + let mdtextarea = document.getElementsByTagName('textarea'); + let start = mdtextarea[0].selectionStart; + let end = mdtextarea[0].selectionEnd; + + this.compmarkdown = this.compmarkdown.substr(0, end) + liststyle + this.compmarkdown.substr(end); + + this.$emit('updateMarkdownEvent', this.compmarkdown); + + mdtextarea[0].focus(); + if(mdtextarea[0].setSelectionRange) + { + setTimeout(function(){ + mdtextarea[0].setSelectionRange(end+3, end+3); + }, 1); + } + } + }, + }, +}) + +bloxeditor.component('code-component', { + props: ['markdown', 'disabled', 'index'], + template: `
          +
          + + + +
          +
          + + +
          + +
          `, + data: function(){ + return { + prefix: '```', + language: '', + codeblock: '', + } + }, + mounted: function(){ + + eventBus.$on('beforeSave', this.beforeSave ); + + this.$refs.markdown.focus(); + + if(this.markdown) + { + var codelines = this.markdown.split(/\r\n|\n\r|\n|\r/); + var linelength = codelines.length; + var codeblock = ''; + + for(i=0;i +
          + + + +
          + + `, + mounted: function(){ + + eventBus.$on('beforeSave', this.beforeSave ); + + this.$refs.markdown.focus(); + + autosize(document.querySelectorAll('textarea')); + + this.$emit('updateMarkdownEvent', '---'); + }, + methods: { + beforeSave() + { + this.$emit('saveBlockEvent'); + }, + updatemarkdown(event) + { + var emptyline = /^\s*$(?:\r\n?|\n)/gm; + + if(event.target.value.search(emptyline) > -1) + { + this.$emit('updateMarkdownEvent', event.target.value.trim()); + this.$emit('saveBlockEvent'); + } + + this.$emit('updateMarkdownEvent', event.target.value); + }, + }, +}) + +bloxeditor.component('toc-component', { + props: ['markdown', 'disabled', 'index'], + template: `
          +
          + + + +
          + +
          `, + mounted: function(){ + + eventBus.$on('beforeSave', this.beforeSave ); + + this.$refs.markdown.focus(); + + autosize(document.querySelectorAll('textarea')); + + this.$emit('updateMarkdownEvent', '[TOC]'); + }, + methods: { + beforeSave() + { + this.$emit('saveBlockEvent'); + }, + updatemarkdown(event) + { + var emptyline = /^\s*$(?:\r\n?|\n)/gm; + + if(event.target.value.search(emptyline) > -1) + { + this.$emit('updateMarkdownEvent', event.target.value.trim()); + this.$emit('saveBlockEvent'); + } + + this.$emit('updateMarkdownEvent', event.target.value); + }, + }, +}) + + +bloxeditor.component('quote-component', { + props: ['markdown', 'disabled', 'index'], + template: `
          +
          + + + +
          + + + +
          `, + data: function(){ + return { + compmarkdown: '' + } + }, + mounted: function(){ + + eventBus.$on('beforeSave', this.beforeSave ); + + this.compmarkdown = this.markdown; + + if(this.compmarkdown == '') + { + this.compmarkdown = '> '; + } + + this.$emit('updateMarkdownEvent', this.compmarkdown); + + this.$nextTick(function () { + autosize(document.querySelectorAll('textarea')); + }); + + this.$refs.markdown.focus(); + }, + methods: { + beforeSave() + { + this.$emit('saveBlockEvent'); + }, + updatemarkdown(value) + { + this.$emit('updateMarkdownEvent', value); + }, + newLine(event) + { + this.compmarkdown = this.markdown; + + let listend = '> \n'; + let liststyle = '> '; + + if(this.compmarkdown.endsWith(listend)) + { + this.compmarkdown = this.compmarkdown.replace(listend, ''); + this.$emit('updateMarkdownEvent', this.compmarkdown); + this.$emit('saveBlockEvent'); + } + else + { + let mdtextarea = document.getElementsByTagName('textarea'); + let start = mdtextarea[0].selectionStart; + let end = mdtextarea[0].selectionEnd; + + this.compmarkdown = this.compmarkdown.substr(0, end) + liststyle + this.compmarkdown.substr(end); + + this.$emit('updateMarkdownEvent', this.compmarkdown); + + mdtextarea[0].focus(); + if(mdtextarea[0].setSelectionRange) + { + setTimeout(function(){ + mdtextarea[0].setSelectionRange(end+3, end+3); + }, 1); + } + } + } + } +}) + +bloxeditor.component('notice-component', { + props: ['markdown', 'disabled', 'index'], + template: `
          +
          + + + +
          + + +
          `, + data: function(){ + return { + prefix: '!', + notice: '', + noteclass: 'note1' + } + }, + mounted: function(){ + + eventBus.$on('beforeSave', this.beforeSave ); + + this.$refs.markdown.focus(); + + if(this.markdown) + { + this.prefix = this.getNoticePrefix(this.markdown); + + var lines = this.markdown.match(/^.*([\n\r]+|$)/gm); + for (var i = 0; i < lines.length; i++) + { + lines[i] = lines[i].replace(/(^[\! ]+(?!\[))/mg, ''); + } + + this.notice = lines.join(''); + this.noteclass = 'note'+this.prefix.length; + } + this.$nextTick(function () { + autosize(document.querySelectorAll('textarea')); + }); + }, + methods: { + beforeSave() + { + this.$emit('saveBlockEvent'); + }, + noticedown() + { + this.prefix = this.getNoticePrefix(this.markdown); + + /* initially it is empty string, so we add it here if user clicks downgrade button */ + if(this.prefix == '') + { + this.prefix = '!'; + } + + this.prefix = this.prefix + '!'; + if(this.prefix.length > 4) + { + this.prefix = '!'; + } + this.noteclass = 'note' + (this.prefix.length); + this.updatemarkdown(this.notice); + }, + getNoticePrefix(str) + { + var prefix = ''; + if(str === undefined) + { + return prefix; + } + for(var i = 0; i < str.length; i++) + { + if(str[i] != '!'){ return prefix } + prefix += '!'; + } + return prefix; + }, + updatemarkdown(value) + { + this.notice = value; + + var lines = value.match(/^.*([\n\r]|$)/gm); + + var notice = this.prefix + ' ' + lines.join(this.prefix+' '); + + this.$emit('updateMarkdownEvent', notice); + } + }, +}) + +bloxeditor.component('table-component', { + props: ['markdown', 'disabled', 'index'], + data: function(){ + return { + table: [ + ['0', '1', '2'], + ['1', 'Head', 'Head'], + ['2', 'cell', 'cell'], + ['3', 'cell', 'cell'], + ], + aligns: ['0', 'left', 'left'], + editable: 'editable', + noteditable: 'noteditable', + cellcontent: '', + columnbar: false, + rowbar: false, + tablekey: 1, + } + }, + template: `
          +
          + + + +
          + + + + + + + + + + + +
          {{value}} +
          +
          +
          :---
          +
          :---:
          +
          ---:
          +
          +
          {{ $filters.translate('add left column') }}
          +
          {{ $filters.translate('add right column') }}
          +
          {{ $filters.translate('delete column') }}
          +
          +
          {{ value }} + +
          +
          {{ $filters.translate('add row above') }}
          +
          {{ $filters.translate('add row below') }}
          +
          {{ $filters.translate('delete row') }}
          +
          + {{ value }} +
          +
          `, + mounted: function(){ + + eventBus.$on('beforeSave', this.beforeSave ); + + this.$refs.markdown.focus(); + + if(this.markdown) + { + this.generateTable(this.markdown); + } + }, + methods: { + beforeSave() + { + this.$emit('saveBlockEvent'); + }, + generateTable(markdown) + { + var newtable = []; + var lines = markdown.split("\n"); + var length = lines.length + var c = 1; + + for(i = 0; i < length; i++) + { + if(i == 1) + { + this.aligns = [0]; + var line = lines[i].trim(); + var columns = line.split("|"); + for(x = 0; x < columns.length; x++) + { + switch(columns[x].trim()) + { + case "": + break; + case ":---:": + this.aligns[x] = "center"; + break; + case "---:": + this.aligns[x] = "right"; + break; + default: + this.aligns[x] = "left"; + } + } + continue; + } + + var line = lines[i].trim(); + var row = line.split("|").map(function(cell) + { + return cell.trim(); + }); + if(row[0] == ''){ row.shift() } + if(row[row.length-1] == ''){ row.pop() } + if(i == 0) + { + var rlength = row.length; + var row0 = []; + for(y = 0; y <= rlength; y++) { row0.push(y) } + newtable.push(row0); + } + row.splice(0,0,c); + c++; + newtable.push(row); + } + this.table = newtable; + + this.$forceUpdate(); + }, + enter() + { + return false; + }, + updatedata(event,col,row) + { + const currentContent = this.table[row][col].trim().replace(/\u00A0/g, ' ').normalize(); + + const newContent = event.target.innerText.trim().replace(/\u00A0/g, ' ').normalize(); + + if (newContent !== currentContent) + { + this.table[row][col] = newContent; + this.markdowntable(); + } + }, + updatedataPaste(event,col,row) + { + return; + }, + switchcolumnbar(event, value) + { + this.rowbar = false; + (this.columnbar == value || value == 0) ? this.columnbar = false : this.columnbar = value; + }, + switchrowbar(event, value) + { + this.columnbar = false; + (this.rowbar == value || value == 0 || value == 1 ) ? this.rowbar = false : this.rowbar = value; + }, + addaboverow(event, index) + { + var row = []; + var cols = this.table[0].length; + for(var i = 0; i < cols; i++){ row.push("new"); } + this.table.splice(index,0,row); + this.markdowntable(); + }, + addbelowrow(event, index) + { + var row = []; + var cols = this.table[0].length; + for(var i = 0; i < cols; i++){ row.push("new"); } + this.table.splice(index+1,0,row); + this.markdowntable(); + }, + deleterow(event, index) + { + this.table.splice(index,1); + this.markdowntable(); + }, + addrightcolumn(event, index) + { + var tableLength = this.table.length; + for (var i = 0; i < tableLength; i++) + { + this.table[i].splice(index+1,0,"new"); + } + this.markdowntable(); + }, + aligncolumn(event, index, align) + { + this.aligns.splice(index,1,align); + this.markdowntable(); + }, + addleftcolumn(event, index) + { + var tableLength = this.table.length; + for (var i = 0; i < tableLength; i++) + { + this.table[i].splice(index,0,"new"); + } + this.markdowntable(); + }, + deletecolumn(event, index) + { + var tableLength = this.table.length; + for (var i = 0; i < tableLength; i++) + { + this.table[i].splice(index,1); + } + this.markdowntable(); + }, + markdowntable() + { + var compmarkdown = ''; + var separator = '\n|'; + var rows = this.table.length; + var cols = this.table[0].length; + + for(var i = 0; i < cols; i++) + { + if(i == 0){ continue; } + + switch(this.aligns[i]) + { + case "left": + separator += ':---|'; + break + case "center": + separator += ':---:|'; + break + case "right": + separator += '---:|'; + break + default: + separator += '---|'; + } + } + + for(var i = 0; i < rows; i++) + { + var row = this.table[i]; + + if(i == 0){ continue; } + + for(var y = 0; y < cols; y++) + { + if(y == 0){ continue; } + + var value = row[y].trim(); + + if(y == 1) + { + compmarkdown += '\n| ' + value + ' | '; + } + else + { + compmarkdown += value + ' | '; + } + } + if(i == 1) { compmarkdown = compmarkdown + separator; } + } + + compmarkdown = compmarkdown.trim(); + + this.$emit('updateMarkdownEvent', compmarkdown); + + this.generateTable(compmarkdown); + }, + }, +}) + +bloxeditor.component('definition-component', { + props: ['markdown', 'disabled', 'index', 'load'], + data: function(){ + return { + definitionList: [], + } + }, + template: `
          +
          + + + +
          + + + +
          + + {{ $filters.translate('Add definition') }} +
          +
          +
          `, + mounted: function(){ + + eventBus.$on('beforeSave', this.beforeSave ); + + if(this.markdown) + { + var definitionList = this.markdown.replace("\r\n", "\n"); + definitionList = definitionList.replace("\r", "\n"); + definitionList = definitionList.split("\n\n"); + + for(var i=0; i < definitionList.length; i++) + { + var definitionBlock = definitionList[i].split("\n"); + var term = definitionBlock[0]; + var descriptions = []; + var description = false; + + if(term.trim() == '') + { + continue; + } + + /* parse one or more descriptions */ + for(var y=0; y < definitionBlock.length; y++) + { + if(y == 0) + { + continue; + } + + if(definitionBlock[y].substring(0, 2) == ": ") + { + /* if there is another description in the loop, then push that first then start a new one */ + if(description) + { + descriptions.push(description); + } + var cleandefinition = definitionBlock[y].substr(1).trim(); + var description = cleandefinition; + } + else + { + description += "\n" + definitionBlock[y]; + } + } + + if(description) + { + descriptions.push(description); + } + this.definitionList.push({'term': term ,'descriptions': descriptions, 'id': i}); + } + } + else + { + this.addDefinition(); + } + }, + methods: { + beforeSave() + { + this.$emit('saveBlockEvent'); + }, + enter() + { + return false; + }, + updateterm(event, dtindex) + { + let content = event.target.value.trim(); + if(content != '') + { + this.definitionList[dtindex].term = content; + } + }, + updatedescription(event, dtindex, ddindex) + { + let content = event.target.value.trim(); + if(content != '') + { + this.definitionList[dtindex].descriptions[ddindex] = content; + } + }, + addDefinition() + { + var id = this.definitionList.length; + this.definitionList.push({'term': '', 'descriptions': [''], 'id': id}); + }, + deleteDefinition(event,dtindex) + { + this.definitionList.splice(dtindex,1); + this.updateMarkdown(); + }, + addItem(event,dtindex) + { + this.definitionList[dtindex].descriptions.push(''); + }, + deleteItem(event,dtindex,ddindex) + { + if(this.definitionList[dtindex].descriptions.length == 1) + { + this.deleteDefinition(false,dtindex); + } + else + { + this.definitionList[dtindex].descriptions.splice(ddindex,1); + this.updateMarkdown(); + } + }, + moveDefinition(evt) + { + this.updateMarkdown(); + }, + updateMarkdown() + { + var dllength = this.definitionList.length; + var markdown = ''; + + for(i = 0; i < dllength; i++) + { + let term = this.definitionList[i].term; + if(term != '') + { + markdown = markdown + term; + var ddlength = this.definitionList[i].descriptions.length; + for(y = 0; y < ddlength; y++) + { + let description = this.definitionList[i].descriptions[y]; + if(description != '') + { + markdown = markdown + "\n: " + description; + } + } + markdown = markdown + "\n\n"; + } + } + this.$emit('updateMarkdownEvent', markdown); + }, + }, +}) + +bloxeditor.component('inline-formats', { + template: `
          +
          + +
          + + + + + + + + + + + + + + + + + + + + + + + + + +
          +
          + +
          `, + data: function(){ + return { + formatBar: false, + formatElements: 0, + startX: 0, + startY: 0, + x: 0, + y: 0, + z: 150, + textComponent: '', + selectedText: '', + startPos: false, + endPos: false, + showInlineFormat: false, + link: false, + stopNext: false, + url: '', + code: (formatConfig.indexOf("code") > -1) ? true : false, + math: (formatConfig.indexOf("math") > -1) ? true : false, + } + }, + mounted: function() { + this.formatBar = document.getElementById('formatBar'); + window.addEventListener('mouseup', this.onMouseup), + window.addEventListener('mousedown', this.onMousedown) + }, + beforeDestroy: function() { + window.removeEventListener('mouseup', this.onMouseup), + window.removeEventListener('mousedown', this.onMousedown) + }, + computed: { + styleObject() { + return { + 'left': this.x + 'px', + 'top': this.y + 'px', + 'width': this.z + 'px' + } + }, + highlightableEl: function () { + return this.$slots.default[0].elm + } + }, + methods: { + onMousedown(event) { + this.startX = event.offsetX; + this.startY = event.offsetY; + }, + onMouseup(event) { + + /* if click is on format popup */ + if(this.formatBar.contains(event.target) || this.stopNext) + { + this.stopNext = false; + return; + } + + /* if click is outside the textarea * + if(!this.highlightableEl.contains(event.target)) + { + this.showInlineFormat = false; + this.link = false; + return; + } + */ + + this.textComponent = document.getElementsByClassName("iformat")[0]; + if(typeof this.textComponent == "undefined") + { + return; + } + + /* grab the selected text */ + if (document.selection != undefined) + { + this.textComponent.focus(); + var sel = document.selection.createRange(); + selectedText = sel.text; + } + /* Mozilla version */ + else if (this.textComponent.selectionStart != undefined) + { + this.startPos = this.textComponent.selectionStart; + this.endPos = this.textComponent.selectionEnd; + selectedText = this.textComponent.value.substring(this.startPos, this.endPos) + } + + var trimmedSelection = selectedText.replace(/\s/g, ''); + + if(trimmedSelection.length == 0) + { + this.showInlineFormat = false; + this.link = false; + return; + } + + /* determine the width of selection to position the format bar */ + if(event.offsetX > this.startX) + { + var width = event.offsetX - this.startX; + this.x = event.offsetX - (width/2); + } + else + { + var width = this.startX - event.offsetX; + this.x = event.offsetX + (width/2); + } + + this.y = event.offsetY - 15; + + /* calculate the width of the format bar */ + this.formatElements = document.getElementsByClassName('inlineFormatItem').length; + this.z = this.formatElements * 30; + + this.showInlineFormat = true; + this.selectedText = selectedText; + }, + formatBold() + { + content = this.textComponent.value; + content = content.substring(0, this.startPos) + '**' + this.selectedText + '**' + content.substring(this.endPos, content.length); + eventBus.$emit('inlineFormat', content); + this.showInlineFormat = false; + this.$nextTick(function () { + autosize.update(document.querySelectorAll('textarea')); + }); + }, + formatItalic() + { + content = this.textComponent.value; + content = content.substring(0, this.startPos) + '_' + this.selectedText + '_' + content.substring(this.endPos, content.length); + eventBus.$emit('inlineFormat', content); + this.showInlineFormat = false; + this.$nextTick(function () { + autosize.update(document.querySelectorAll('textarea')); + }); + }, + formatCode() + { + content = this.textComponent.value; + content = content.substring(0, this.startPos) + '`' + this.selectedText + '`' + content.substring(this.endPos, content.length); + eventBus.$emit('inlineFormat', content); + this.showInlineFormat = false; + this.$nextTick(function () { + autosize.update(document.querySelectorAll('textarea')); + }); + }, + formatMath() + { + content = this.textComponent.value; + content = content.substring(0, this.startPos) + '$' + this.selectedText + '$' + content.substring(this.endPos, content.length); + eventBus.$emit('inlineFormat', content); + this.showInlineFormat = false; + this.$nextTick(function () { + autosize.update(document.querySelectorAll('textarea')); + }); + }, + formatLink() + { + if(this.url == "") + { + this.stopNext = true; + this.link = false; + this.showInlineFormat = false; + return; + } + content = this.textComponent.value; + content = content.substring(0, this.startPos) + '[' + this.selectedText + '](' + this.url + ')' + content.substring(this.endPos, content.length); + eventBus.$emit('inlineFormat', content); + this.showInlineFormat = false; + this.link = false; + this.$nextTick(function () { + autosize.update(document.querySelectorAll('textarea')); + }); + }, + openLink() + { + this.link = true; + this.url = ''; + this.z = 200; + this.$nextTick(() => this.$refs.urlinput.focus()); + }, + closeLink() + { + this.stopNext = true; + this.link = false; + this.url = ''; + this.showInlineFormat = false; + } + } +}) + +bloxeditor.component('image-component', { + props: ['markdown', 'disabled', 'index'], + components: { + medialib: medialib + }, + template: `
          + +
          +
          + +

          {{ $filters.translate('drag a picture or click to select') }}

          +
          + +
          + + +
          + + +
          +
          + +
          + + + +
          +
          + +
          +
          +
          +
          + + +
          +
          + + +
          +
          + + +
          +
          + + +
          +
          + + +
          +
          + + + +
          +
          + +
          + +
          `, + data: function(){ + return { + compmarkdown: '', + saveimage: false, + maxsize: 5, // megabyte + imgpreview: '', + showmedialib: false, + load: false, + imgmeta: false, + imgalt: '', + imgtitle: '', + imgcaption: '', + imglink: '', + imgclass: 'center', + imgid: '', + imgwidth: 0, + imgheight: 0, + originalwidth: 0, + originalheight: 0, + imgloading: 'lazy', + imgattr: '', + imgfile: '', + showresize: true, + noresize: false, + newblock: true, + } + }, + mounted: function(){ + + eventBus.$on('beforeSave', this.beforeSave ); + + this.$refs.markdown.focus(); + + if(this.markdown) + { + this.newblock = false; + + this.showresize = false; + + this.imgmeta = true; + + var imgmarkdown = this.markdown; + + var imgcaption = imgmarkdown.match(/\*.*?\*/); + if(imgcaption) + { + this.imgcaption = imgcaption[0].slice(1,-1); + + imgmarkdown = imgmarkdown.replace(this.imgcaption,''); + imgmarkdown = imgmarkdown.replace(/\r?\n|\r/g,''); + } + + if(this.markdown[0] == '[') + { + var imglink = this.markdown.match(/\(.*?\)/g); + if(imglink[1]) + { + this.imglink = imglink[1].slice(1,-1); + + imgmarkdown = imgmarkdown.replace(imglink[1],''); + imgmarkdown = imgmarkdown.slice(1, -1); + } + } + + var imgalt = imgmarkdown.match(/\[.*?\]/); + if(imgalt) + { + this.imgalt = imgalt[0].slice(1,-1); + } + + var imgattr = imgmarkdown.match(/\{.*?\}/); + if(imgattr) + { + imgattr = imgattr[0].slice(1,-1); + imgattr = imgattr.trim(); + imgattr = imgattr.split(' '); + + var widthpattern = /width=\"?([0-9]*)[a-zA-Z%]*\"?/; + var heightpattern = /height=\"?([0-9]*)[a-zA-Z%]*\"?/; + var lazypattern = /loading=\"?([0-9a-zA-Z]*)\"?/; + + for (var i = 0; i < imgattr.length; i++) + { + var widthattr = imgattr[i].match(widthpattern); + var heightattr = imgattr[i].match(heightpattern); + var lazyattr = imgattr[i].match(lazypattern); + + if(imgattr[i].charAt(0) == '.') + { + this.imgclass = imgattr[i].slice(1); + } + else if(imgattr[i].charAt(0) == '#') + { + this.imgid = imgattr[i].slice(1); + } + else if(widthattr) + { + this.imgwidth = parseInt(widthattr[1]); + } + else if(heightattr) + { + this.imgheight = parseInt(heightattr[1]); + } + else if(lazyattr && lazyattr[1] != '') + { + this.imgloading = lazyattr[1]; + } + else + { + this.imgattr += ' ' + imgattr[i]; + } + } + } + + var imgfile = imgmarkdown.match(/\(.*?\)/); + if(imgfile) + { + imgfilestring = imgfile[0]; + var imgtitle = imgfilestring.match(/\".*?\"/); + if(imgtitle) + { + this.imgtitle = imgtitle[0].slice(1,-1); + imgfilestring = imgfilestring.replace(imgtitle[0], ''); + } + + this.imgfile = imgfilestring.slice(1,-1).trim(); + this.imgpreview = data.urlinfo.baseurl + '/' + this.imgfile; + } + + this.createmarkdown(); + } + }, + methods: { + closemedialib() + { + this.showmedialib = false; + }, + addFromMedialibFunction(value) + { + this.imgfile = value; + this.imgpreview = data.urlinfo.baseurl + '/' + value; + this.showmedialib = false; + this.saveimage = false; + this.imgmeta = true; + + this.createmarkdown(); + }, + updatemarkdown(event) + { + this.$emit('updateMarkdownEvent', event.target.value); + }, + createmarkdown() + { + if(this.imgpreview) + { + var img = new Image(); + img.src = this.imgpreview; + + var self = this; + + img.onload = function(){ + + self.originalwidth = img.width; + self.originalheight = img.height; + self.originalratio = self.originalwidth / self.originalheight; + + self.calculatewidth(); + self.calculateheight(); + self.createmarkdownimageloaded(); + } + } + else + { + this.createmarkdownimageloaded(); + } + }, + createmarkdownimageloaded() + { + var errors = false; + + var imgmarkdown = ''; + + if(this.imgalt.length < 101) + { + imgmarkdown = '![' + this.imgalt + ']'; + } + else + { + errors = this.$filters.translate('Maximum size of image alt-text is 100 characters'); + imgmarkdown = '![]'; + } + + if(this.imgtitle != '') + { + if(this.imgtitle.length < 101) + { + imgmarkdown = imgmarkdown + '(' + this.imgfile + ' "' + this.imgtitle + '")'; + } + else + { + errors = this.$filters.translate('Maximum size of image title is 100 characters'); + } + } + else + { + imgmarkdown = imgmarkdown + '(' + this.imgfile + ')'; + } + + var imgattr = ''; + + if(this.imgid != '') + { + if(this.imgid.length < 100) + { + imgattr = imgattr + ' #' + this.imgid; + } + else + { + errors = this.$filters.translate('Maximum size of image id is 100 characters'); + } + } + if(this.imgclass != '') + { + if(this.imgclass.length < 100) + { + imgattr = imgattr + ' .' + this.imgclass; + } + else + { + errors = this.$filters.translate('Maximum size of image class is 100 characters'); + } + } + if(this.imgloading != '') + { + imgattr = imgattr + ' loading="' + this.imgloading + '"'; + } + if(this.imgwidth != '') + { + imgattr = imgattr + ' width="' + this.imgwidth + '"'; + } + if(this.imgheight != '') + { + imgattr = imgattr + ' height="' + this.imgheight + '"'; + } + if(this.imgattr != '') + { + imgattr += this.imgattr; + } + if(imgattr != '') + { + imgmarkdown = imgmarkdown + '{' + imgattr.trim() + '}'; + } + if(this.imglink != '') + { + if(this.imglink.length < 101) + { + imgmarkdown = '[' + imgmarkdown + '](' + this.imglink + ')'; + } + else + { + errors = this.$filters.translate('Maximum size of image link is 100 characters'); + } + } + if(this.imgcaption != '') + { + if(this.imgcaption.length < 140) + { + imgmarkdown = imgmarkdown + '\n*' + this.imgcaption + '*'; + } + else + { + errors = this.$filters.translate('Maximum size of image caption is 140 characters'); + } + } + if(errors) + { + eventBus.$emit('publishermessage', errors); + } + else + { + this.compmarkdown = imgmarkdown; + + this.$emit('updateMarkdownEvent', imgmarkdown); + } + }, + calculatewidth() + { + this.setdefaultsize(); + if(this.imgheight && this.imgheight > 0) + { + this.imgwidth = Math.round(this.imgheight * this.originalratio); + } + else + { + this.imgwidth = ''; + } + }, + calculateheight() + { + this.setdefaultsize(); + if(this.imgwidth && this.imgwidth > 0) + { + this.imgheight = Math.round(this.imgwidth / this.originalratio); + } + else + { + this.imgheight = ''; + } + }, + setdefaultsize() + { + if( + (this.imgheight == 0 && this.imgwidth == 0) || + (this.imgheight > this.originalheight) || + (this.imgwidth > this.originalwidth) + ) + { + this.imgwidth = this.originalwidth; + this.imgheight = this.originalheight; + } + }, + changewidth() + { + this.calculateheight(); + this.createmarkdownimageloaded(); + }, + changeheight() + { + this.calculatewidth(); + this.createmarkdownimageloaded(); + }, + openmedialib() + { + this.showresize = false; + this.noresize = false; + this.showmedialib = true; + }, + isChecked(classname) + { + if(this.imgclass == classname) + { + return ' checked'; + } + }, + onFileChange( e ) + { + if(e.target.files.length > 0) + { + let imageFile = e.target.files[0]; + let size = imageFile.size / 1024 / 1024; + + if (!imageFile.type.match('image.*')) + { + let message = this.$filters.translate('Only images are allowed'); + eventBus.$emit('publishermessage', message); + } + else if (size > this.maxsize) + { + let message = "The maximal size of images is " + this.maxsize + " MB"; + message = this.$filters.translate(message); + eventBus.$emit('publishermessage', message); + } + else + { + self = this; + + self.load = true; + self.showresize = true; + self.noresize = false; + self.imgwidth = 0; + self.imgheight = 0; + + let reader = new FileReader(); + reader.readAsDataURL(imageFile); + reader.onload = function(e) { + + self.imgpreview = e.target.result; + + self.createmarkdown(); + + tmaxios.post('/api/v1/image',{ + 'url': data.urlinfo.route, + 'image': e.target.result, + 'name': imageFile.name, + }) + .then(function (response) { + + self.load = false; + self.saveimage = true; + + self.imgmeta = true; + self.imgfile = response.data.name; + + if(self.imgwidth > 820) + { + self.imgwidth = 820; + self.calculateheight(); + } + }) + .catch(function (error) + { + if(error.response) + { + let message = error.response.data.message; + message = self.$filters.translate(message); + eventBus.$emit('publishermessage', message); + } + }); + } + } + } + }, + beforeSave() + { + /* publish the image before you save the block */ + + if(!this.imgfile) + { + let message = this.$filters.translate("Imagefile is missing."); + eventBus.$emit('publishermessage', message); + return; + } + if(!this.saveimage) + { + this.$emit('saveBlockEvent'); + } + else + { + var self = this; + + tmaxios.put('/api/v1/image',{ + 'url': data.urlinfo.route, + 'imgfile': this.imgfile, + 'noresize': this.noresize + }) + .then(function (response) + { + self.saveimage = false; + self.imgfile = response.data.path; + + self.createmarkdownimageloaded(); + + self.$emit('saveBlockEvent'); + }) + .catch(function (error) + { + if(error.response) + { + let message = self.$filters.translate(error.response.data.message); + eventBus.$emit('publishermessage', message); + } + }); + } + }, + } +}) + +bloxeditor.component('file-component', { + props: ['markdown', 'disabled', 'index'], + components: { + medialib: medialib + }, + template: `
          + +
          +
          + +

          + + + + {{ $filters.translate('upload file') }} +

          +
          + +
          + + +
          + + +
          +
          + +
          + + + +
          +
          +
          + +
          + + +
          +
          + + +
          +
          +
          `, + data: function(){ + return { + maxsize: 20, // megabyte + showmedialib: false, + load: false, + filemeta: false, + filetitle: '', + fileextension: '', + fileurl: '', + fileid: '', + userroles: ['all'], + selectedrole: '', + savefile: false, + } + }, + mounted: function(){ + + eventBus.$on('beforeSave', this.beforeSave ); + + this.$refs.markdown.focus(); + + if(this.markdown) + { + this.filemeta = true; + + var filemarkdown = this.markdown; + + var filetitle = filemarkdown.match(/\[.*?\]/); + if(filetitle) + { + filemarkdown = filemarkdown.replace(filetitle[0],''); + this.filetitle = filetitle[0].slice(1,-1); + } + + var fileattr = filemarkdown.match(/\{.*?\}/); + var fileextension = filemarkdown.match(/file-(.*)?\}/); + if(fileattr && fileextension) + { + filemarkdown = filemarkdown.replace(fileattr[0],''); + this.fileextension = fileextension[1].trim(); + } + + var fileurl = filemarkdown.match(/\(.*?\)/g); + if(fileurl) + { + filemarkdown = filemarkdown.replace(fileurl[0],''); + this.fileurl = fileurl[0].slice(1,-1); + } + } + + this.getrestriction(); + }, + methods: { + addFromMedialibFunction(file) + { + this.showmedialib = false; + this.savefile = false; + this.fileurl = file.url; + this.filemeta = true; + this.filetitle = file.name; + this.fileextension = file.info.extension; + + this.createmarkdown(); + this.getrestriction(file.url); + }, + openmedialib() + { + this.showmedialib = true; + }, + isChecked(classname) + { + if(this.fileclass == classname) + { + return ' checked'; + } + }, + updatemarkdown(event, url) + { + this.$emit('updateMarkdownEvent', event.target.value); + this.getrestriction(url); + }, + createmarkdown() + { + var errors = false; + + if(this.filetitle.length < 101) + { + filemarkdown = '[' + this.filetitle + ']'; + } + else + { + errors = this.$filters.translate('Maximum size of file-text is 100 characters'); + filemarkdown = '[]'; + } + if(this.fileurl != '') + { + if(this.fileurl.length < 101) + { + filemarkdown = '[' + this.filetitle + '](' + this.fileurl + ')'; + } + else + { + errors = this.$filters.translate('Maximum size of file link is 100 characters'); + } + } + if(this.fileextension != '') + { + filemarkdown = filemarkdown + '{.tm-download file-' + this.fileextension + '}'; + } + if(errors) + { + eventBus.$emit('publishermessage', this.$filters.translate(errors)); + } + else + { + this.$emit('updateMarkdownEvent', filemarkdown); + this.compmarkdown = filemarkdown; + } + }, + getrestriction(url) + { + var fileurl = this.fileurl; + if(url) + { + fileurl = url; + } + + var myself = this; + + tmaxios.get('/api/v1/filerestrictions',{ + params: { + 'url': data.urlinfo.route, + 'filename': fileurl, + } + }) + .then(function (response) { + myself.userroles = ['all']; + myself.userroles = myself.userroles.concat(response.data.userroles); + myself.selectedrole = response.data.restriction; + }) + .catch(function (error) + { + if(error.response.data.message) + { + let message = myself.$filters.translate(error.response.data.message); + eventBus.$emit('publishermessage', message); + } + }); + }, + updaterestriction() + { + tmaxios.post('/api/v1/filerestrictions',{ + 'url': data.urlinfo.route, + 'filename': this.fileurl, + 'role': this.selectedrole, + }) + .then(function (response) {}) + .catch(function (error){ alert("reponse error")}); + }, + onFileChange( e ) + { + if(e.target.files.length > 0) + { + let uploadedFile = e.target.files[0]; + let size = uploadedFile.size / 1024 / 1024; + + if (size > this.maxsize) + { + let message = "The maximal size of a file is " + this.maxsize + " MB"; + eventBus.$emit('publishermessage', message); + } + else + { + self = this; + + self.load = true; + + let reader = new FileReader(); + reader.readAsDataURL(uploadedFile); + reader.onload = function(e) { + + tmaxios.post('/api/v1/file',{ + 'url': data.urlinfo.route, + 'file': e.target.result, + 'name': uploadedFile.name, + }) + .then(function (response) { + + self.load = false; + + self.filemeta = true; + self.savefile = true; + self.filetitle = response.data.fileinfo.title; + self.fileextension = response.data.fileinfo.extension; + self.fileurl = response.data.filepath; + self.selectedrole = ''; + + self.createmarkdown(); + }) + .catch(function (error) + { + self.load = false; + if(error.response) + { + let message = self.$filters.translate(error.response.data.message); + eventBus.$emit('publishermessage', message); + } + }); + } + } + } + }, + beforeSave() + { + /* publish file before you save markdown */ + + if(!this.fileurl) + { + let message = this.$filters.translate('file is missing.'); + eventBus.$emit('publishermessage', message); + + return; + } + + if(!this.savefile) + { + this.createmarkdown(); + this.$emit('saveBlockEvent'); + } + else + { + var self = this; + + tmaxios.put('/api/v1/file',{ + 'url': data.urlinfo.route, + 'file': this.fileurl, + }) + .then(function (response) + { + self.fileurl = response.data.path; + + self.createmarkdown(); + + self.$emit('saveBlockEvent'); + }) + .catch(function (error) + { + if(error.response) + { + let message = self.$filters.translate(error.response.data.message); + eventBus.$emit('publishermessage', message); + } + }); + } + }, + } +}) + +bloxeditor.component('video-component', { + props: ['markdown', 'disabled', 'index'], + components: { + medialib: medialib + }, + template: `
          + +
          +
          + +

          + + + + {{ $filters.translate('upload video') }} +

          +
          + +
          + + +
          + + +
          +
          + +
          + + +
          +
          + +
          + + + +
          +
          +
          + +
          + + +
          +
          + + +
          +
          + + +
          +
          + +
          + + + +
          +
          +
          +
          `, + data: function(){ + return { + maxsize: 100, // megabyte + showmedialib: false, + load: false, + filemeta: false, + fileextension: '', + allowedImageExtensions: ['webp', 'png', 'svg', 'jpg', 'jpeg'], + allowedExtensions: ['mp4', 'webm', 'ogg'], + fileurl: '', + width: '500', + fileid: '', + imageurl: '', + savefile: false, + mediatypes: 'files', + preload: 'none', + } + }, + mounted: function() { + eventBus.$on('beforeSave', this.beforeSave); + + this.$refs.markdown.focus(); + + if (this.markdown) + { + this.filemeta = true; + + var fileurl = this.markdown.match(/path="(.*?)"/); + if (fileurl && fileurl[1]) + { + this.fileurl = fileurl[1]; + } + + var width = this.markdown.match(/width="(.*?)"/); + if (width && width[1]) + { + this.width = width[1]; + } + + var preload = this.markdown.match(/preload="(.*?)"/); + if (preload && preload[1]) + { + this.preload = preload[1]; + } + + var poster = this.markdown.match(/poster="(.*?)"/); + if (poster && poster[1]) + { + this.imageurl = poster[1]; + } + } + }, + methods: { + addFromMedialibFunction(file) + { + this.showmedialib = false; + this.savefile = false; + this.filemeta = true; + + if (typeof file === 'string') + { + let fileExtension = file.split('.').pop().toLowerCase(); + + if (this.allowedImageExtensions.includes(fileExtension)) + { + this.imageurl = file; + } + else + { + let message = "Unsupported file type. Please select an image with format webp, png, jpg, jpeg. svg."; + eventBus.$emit('publishermessage', message); + return; + } + } + else if (this.allowedExtensions.includes(file.info.extension.toLowerCase())) + { + this.filetitle = file.name; + this.fileextension = file.info.extension.toLowerCase(); + this.fileurl = file.url; + } + else + { + let message = "Unsupported file type. Please select a valid video file (webm, mp4, ogg)."; + eventBus.$emit('publishermessage', message); + return; + } + + this.createmarkdown(); + }, + openmedialib(type) + { + this.showmedialib = type; + }, + deleteImage() + { + this.imageurl = ''; + }, + isChecked(classname) + { + if(this.fileclass == classname) + { + return ' checked'; + } + }, + updatemarkdown(event, url) + { + this.$emit('updateMarkdownEvent', event.target.value); + }, + createmarkdown() + { + var errors = false; + var filemarkdown = false; + + if (this.fileurl !== '') + { + if (this.fileurl.length < 101) + { + var width = this.width ? ' width="' + this.width + '"' : ''; + var preload = this.preload ? ' preload="' + this.preload + '"' : ' preload="none"'; + var poster = this.imageurl ? ' poster="' + this.imageurl + '"' : ''; + + filemarkdown = '[:video path="' + this.fileurl + '"' + width + preload + poster + ' :]'; + } + else + { + errors = this.$filters.translate('Maximum size of file link is 100 characters'); + } + } + + if (errors) + { + eventBus.$emit('publishermessage', this.$filters.translate(errors)); + } + else if (filemarkdown) + { + this.$emit('updateMarkdownEvent', filemarkdown); + this.compmarkdown = filemarkdown; + } + }, + onFileChange( e ) + { + if(e.target.files.length > 0) + { + let uploadedFile = e.target.files[0]; + + let allowedVideoTypes = ['video/mp4', 'video/webm', 'video/ogg']; + if (!allowedVideoTypes.includes(uploadedFile.type)) { + let message = "Unsupported file type. Please select a video file (mp4, webm, ogg)."; + eventBus.$emit('publishermessage', message); + return; + } + + let size = uploadedFile.size / 1024 / 1024; + + if (size > this.maxsize) + { + let message = "The maximal size of a file is " + this.maxsize + " MB"; + eventBus.$emit('publishermessage', message); + return; + } + else + { + self = this; + + self.load = true; + + let reader = new FileReader(); + reader.readAsDataURL(uploadedFile); + reader.onload = function(e) { + + tmaxios.post('/api/v1/file',{ + 'url': data.urlinfo.route, + 'file': e.target.result, + 'name': uploadedFile.name, + }) + .then(function (response) { + + self.load = false; + + self.filemeta = true; + self.savefile = true; + self.filetitle = response.data.fileinfo.title; + self.fileextension = response.data.fileinfo.extension; + self.fileurl = response.data.filepath; + self.selectedrole = ''; + + self.createmarkdown(); + }) + .catch(function (error) + { + self.load = false; + if(error.response) + { + let message = self.$filters.translate(error.response.data.message); + eventBus.$emit('publishermessage', message); + } + }); + } + } + } + }, + beforeSave() + { + /* publish file before you save markdown */ + + if(!this.fileurl) + { + let message = this.$filters.translate('file is missing.'); + eventBus.$emit('publishermessage', message); + + return; + } + + const fileExtension = this.fileurl.split('.').pop().toLowerCase(); + + if (!this.allowedExtensions.includes(fileExtension)) + { + let message = this.$filters.translate('Unsupported file format. Only MP4, WebM, and OGG files are allowed.'); + eventBus.$emit('publishermessage', message); + + return; + } + + if(!this.savefile) + { + this.createmarkdown(); + this.$emit('saveBlockEvent'); + } + else + { + var self = this; + + tmaxios.put('/api/v1/file',{ + 'url': data.urlinfo.route, + 'file': this.fileurl, + }) + .then(function (response) + { + self.fileurl = response.data.path; + + self.createmarkdown(); + + self.$emit('saveBlockEvent'); + }) + .catch(function (error) + { + if(error.response) + { + let message = self.$filters.translate(error.response.data.message); + eventBus.$emit('publishermessage', message); + } + }); + } + }, + } +}) + +bloxeditor.component('audio-component', { + props: ['markdown', 'disabled', 'index'], + components: { + medialib: medialib + }, + template: `
          + +
          +
          + +

          + + + + {{ $filters.translate('upload audio') }} +

          +
          + +
          + + +
          + + +
          +
          + +
          + + + +
          +
          +
          + +
          + + +
          +
          + + +
          +
          + + +
          +
          +
          `, + data: function(){ + return { + maxsize: 100, // megabyte + showmedialib: false, + load: false, + filemeta: false, + fileextension: '', + allowedExtensions: ['mp3', 'ogg'], + fileurl: '', + width: '500px', + fileid: '', + savefile: false, + preload: 'none', + } + }, + mounted: function() { + eventBus.$on('beforeSave', this.beforeSave); + + this.$refs.markdown.focus(); + + if (this.markdown) + { + this.filemeta = true; + + var fileurl = this.markdown.match(/path="(.*?)"/); + if (fileurl && fileurl[1]) + { + this.fileurl = fileurl[1]; + } + + var width = this.markdown.match(/width="(.*?)"/); + if (width && width[1]) + { + this.width = width[1]; + } + + var preload = this.markdown.match(/preload="(.*?)"/); + if (preload && preload[1]) + { + this.preload = preload[1]; + } + } + }, + methods: { + addFromMedialibFunction(file) + { + this.showmedialib = false; + this.savefile = false; + this.filemeta = true; + + if (this.allowedExtensions.includes(file.info.extension.toLowerCase())) + { + this.filetitle = file.name; + this.fileextension = file.info.extension.toLowerCase(); + this.fileurl = file.url; + } + else + { + let message = "Unsupported file type. Please select a valid audio file (mp3, ogg)."; + eventBus.$emit('publishermessage', message); + return; + } + + this.createmarkdown(); + }, + openmedialib() + { + this.showmedialib = true; + }, + isChecked(classname) + { + if(this.fileclass == classname) + { + return ' checked'; + } + }, + updatemarkdown(event, url) + { + this.$emit('updateMarkdownEvent', event.target.value); + }, + createmarkdown() + { + var errors = false; + var filemarkdown = false; + + if (this.fileurl !== '') + { + if (this.fileurl.length < 101) + { + var width = this.width ? ' width="' + this.width + '"' : ''; + var preload = this.preload ? ' preload="' + this.preload + '"' : ' preload="none"'; + + filemarkdown = '[:audio path="' + this.fileurl + '"' + width + preload + ' :]'; + } + else + { + errors = this.$filters.translate('Maximum size of file link is 100 characters'); + } + } + + if (errors) + { + eventBus.$emit('publishermessage', this.$filters.translate(errors)); + } + else if (filemarkdown) + { + this.$emit('updateMarkdownEvent', filemarkdown); + this.compmarkdown = filemarkdown; + } + }, + onFileChange( e ) + { + if(e.target.files.length > 0) + { + let uploadedFile = e.target.files[0]; + + let allowedAudioTypes = ['audio/mpeg', 'audio/ogg']; + if (!allowedAudioTypes.includes(uploadedFile.type)) { + let message = "Unsupported file type. Please select an audio file (mp3, ogg)."; + eventBus.$emit('publishermessage', message); + return; + } + + let size = uploadedFile.size / 1024 / 1024; + + if (size > this.maxsize) + { + let message = "The maximal size of a file is " + this.maxsize + " MB"; + eventBus.$emit('publishermessage', message); + return; + } + else + { + self = this; + + self.load = true; + + let reader = new FileReader(); + reader.readAsDataURL(uploadedFile); + reader.onload = function(e) { + + tmaxios.post('/api/v1/file',{ + 'url': data.urlinfo.route, + 'file': e.target.result, + 'name': uploadedFile.name, + }) + .then(function (response) { + + self.load = false; + + self.filemeta = true; + self.savefile = true; + self.filetitle = response.data.fileinfo.title; + self.fileextension = response.data.fileinfo.extension; + self.fileurl = response.data.filepath; + self.selectedrole = ''; + + self.createmarkdown(); + }) + .catch(function (error) + { + self.load = false; + if(error.response) + { + let message = self.$filters.translate(error.response.data.message); + eventBus.$emit('publishermessage', message); + } + }); + } + } + } + }, + beforeSave() + { + /* publish file before you save markdown */ + + if(!this.fileurl) + { + let message = this.$filters.translate('file is missing.'); + eventBus.$emit('publishermessage', message); + + return; + } + + const fileExtension = this.fileurl.split('.').pop().toLowerCase(); + + if (!this.allowedExtensions.includes(fileExtension)) + { + let message = this.$filters.translate('Unsupported file format. Only MP3, and OGG files are allowed.'); + eventBus.$emit('publishermessage', message); + + return; + } + + if(!this.savefile) + { + this.createmarkdown(); + this.$emit('saveBlockEvent'); + } + else + { + var self = this; + + tmaxios.put('/api/v1/file',{ + 'url': data.urlinfo.route, + 'file': this.fileurl, + }) + .then(function (response) + { + self.fileurl = response.data.path; + + self.createmarkdown(); + + self.$emit('saveBlockEvent'); + }) + .catch(function (error) + { + if(error.response) + { + let message = self.$filters.translate(error.response.data.message); + eventBus.$emit('publishermessage', message); + } + }); + } + }, + } +}) + +bloxeditor.component('shortcode-component', { + props: ['markdown', 'disabled', 'index'], + data: function(){ + return { + shortcodedata: false, + shortcodename: '', + compmarkdown: '', + } + }, + template: `
          +
          + + + +
          +
          +
          + + +
          +
          + +
          + +
          + + + +
          + + + +
          + +
          +
          + +
          `, + mounted: function(){ + + eventBus.$on('beforeSave', this.beforeSave ); + + var myself = this; + + tmaxios.get('/api/v1/shortcodedata', + { + params: + { + 'url': data.urlinfo.route, + } + }) + .then(function (response) + { + if (response.data.shortcodedata !== false) + { + let cleanedShortcodes = {}; + + for (let key in response.data.shortcodedata) + { + let shortcode = response.data.shortcodedata[key]; + + /* Skip if the shortcode data is an empty array */ + if (Array.isArray(shortcode) && shortcode.length === 0) + { + continue; + } + + /* Remove the "showInVisualEditor" key */ + if (typeof shortcode === 'object' && shortcode !== null) + { + delete shortcode.showInVisualEditor; + } + + // Add the cleaned shortcode back if not empty + cleanedShortcodes[key] = shortcode; + } + + // Assign the cleaned shortcodedata + myself.shortcodedata = Object.keys(cleanedShortcodes).length > 0 ? cleanedShortcodes : false; + myself.parseshortcode(); + } + }) + .catch(function (error) + { + if(error.response) + { + let message = self.$filters.translate(error.response.data.message); + eventBus.$emit('publishermessage', message); + } + }); + }, + methods: { + beforeSave() + { + this.$emit('saveBlockEvent'); + }, + parseshortcode() + { + if(this.markdown) + { + var shortcodestring = this.markdown.trim(); + shortcodestring = shortcodestring.slice(2,-2); + this.shortcodename = shortcodestring.substr(0,shortcodestring.indexOf(' ')); + + var regexp = /(\w+)\s*=\s*("[^"]*"|\'[^\']*\'|[^"\'\\s>]*)/g; + var matches = shortcodestring.matchAll(regexp); + matches = Array.from(matches); + matchlength = matches.length; + + if(matchlength > 0) + { + for(var i=0;i +
          + + + +
          +
          + + +
          + `, + data: function(){ + return { + edited: false, + url: false, + videoid: false, + param: false, + path: false, + provider: false, + providerurl: false, + compmarkdown: '', + } + }, + mounted: function(){ + + eventBus.$on('beforeSave', this.beforeSave ); + + this.$refs.markdown.focus(); + + if(this.markdown) + { + this.parseImageMarkdown(this.markdown); + } + }, + methods: { + generateMarkdown() + { + this.compmarkdown = '![' + this.provider + '-video](' + this.path + ' "click to load video"){#' + this.videoid + ' .' + this.provider + '}'; + }, + parseImageMarkdown(imageMarkdown) + { + let regexpurl = /\((.*)(".*")\)/; + let match = imageMarkdown.match(regexpurl); + let imageUrl = match[1]; + + let regexprov = /live\/(.*?)-/; + let matchprov = imageUrl.match(regexprov); + this.provider = matchprov[1]; + + if(this.provider == 'youtube') + { + this.providerurl = "https://www.youtube.com/watch"; + this.param = "v="; + } + + let videoid = imageMarkdown.match(/#.*? /); + if(videoid) + { + this.videoid = videoid[0].trim().substring(1); + } + + this.updatemarkdown(this.providerurl + "?" + this.param + this.videoid); + }, + parseUrl(url) + { + let urlparts = url.split('?'); + let urlParams = new URLSearchParams(urlparts[1]); + + this.providerurl = urlparts[0]; + + if(urlParams.has("v")) + { + this.param = "v="; + this.videoid = urlParams.get("v"); + this.provider = "youtube"; + } + if(this.provider != "youtube") + { + this.updatemarkdown(""); + let message = this.$filters.translate("We only support youtube right now."); + eventBus.$emit('publishermessage', message); + } + }, + updatemarkdown(url) + { + this.edited = true; + this.url = url; + this.parseUrl(url); + this.generateMarkdown(); + this.$emit('updateMarkdownEvent', url); + }, + beforeSave() + { + if(!this.edited) + { + eventBus.$emit('closeComponents'); + return; + } + var self = this; + + tmaxios.post('/api/v1/video',{ + 'url': data.urlinfo.route, + 'videourl': this.url, + 'provider': this.provider, + 'providerurl': this.providerurl, + 'videoid': this.videoid, + }) + .then(function (response) + { + self.path = response.data.path; + self.$emit('saveBlockEvent'); + }) + .catch(function (error) + { + if(error.response) + { + let message = self.$filters.translate(error.response.data.message); + eventBus.$emit('publishermessage', message); + } + }); + }, + }, +}) diff --git a/system/typemill/author/js/vue-blox-config.js b/system/typemill/author/js/vue-blox-config.js new file mode 100644 index 0000000..684936b --- /dev/null +++ b/system/typemill/author/js/vue-blox-config.js @@ -0,0 +1,143 @@ +const determiner = { + + hr: function(block,lines,firstChar,secondChar,thirdChar){ + if(lines[0] == '---') + { + return "hr-component"; + } + return false; + }, + toc: function(block,lines,firstChar,secondChar,thirdChar){ + if(lines[0] == '[TOC]') + { + return "toc-component"; + } + return false; + }, + olist: function(block,lines,firstChar,secondChar,thirdChar){ + if(block.match(/^\d+\./)) + { + return "olist-component"; + } + return false; + }, + definition: function(block,lines,firstChar,secondChar,thirdChar){ + if(lines.length > 1 && lines[1].substr(0,2) == ': ') + { + return "definition-component"; + } + return false; + }, + table: function(block,lines,firstChar,secondChar,thirdChar){ + if(lines.length > 2 && lines[0].indexOf('|') != -1 && /[\-\|: ]{3,}$/.test(lines[1])) + { + return "table-component"; + } + return false; + }, + quote: function(block,lines,firstChar,secondChar,thirdChar){ + if(firstChar == '>') + { + return "quote-component"; + } + return false; + }, + headline: function(block,lines,firstChar,secondChar,thirdChar){ + if(firstChar == '#') + { + return "headline-component"; + } + return false; + }, + image: function(block,lines,firstChar,secondChar,thirdChar){ + if( (firstChar == '!' && secondChar == '[' ) || (firstChar == '[' && secondChar == '!' && thirdChar == '[') ) + { + if(block.indexOf("-video") != -1) + { + return "youtube-component"; + } + return "image-component"; + } + return false; + }, + file: function(block,lines,firstChar,secondChar,thirdChar){ + if( (firstChar == '[' && lines[0].indexOf('{.tm-download') != -1) ) + { + return "file-component"; + } + return false; + }, + video: function(block,lines,firstChar,secondChar,thirdChar){ + if (lines[0].startsWith('[:video')) + { + return "video-component"; + } + return false; + }, + audio: function(block,lines,firstChar,secondChar,thirdChar){ + if (lines[0].startsWith('[:audio')) + { + return "audio-component"; + } + return false; + }, + code: function(block,lines,firstChar,secondChar,thirdChar){ + if( firstChar == '`' && secondChar == '`' && thirdChar == '`') + { + return "code-component"; + } + return false; + }, + shortcode: function(block,lines,firstChar,secondChar,thirdChar){ + if( firstChar == '[' && secondChar == ':') + { + return "shortcode-component"; + } + return false; + }, + notice: function(block,lines,firstChar,secondChar,thirdChar){ + if( firstChar == '!' && ( secondChar == '!' || secondChar == ' ') ) + { + return "notice-component"; + } + return false; + }, + ulist: function(block,lines,firstChar,secondChar,thirdChar){ + if( (firstChar == '*' || firstChar == '-' || firstChar == '+') && secondChar == ' ') + { + return "ulist-component"; + } + return false; + } +} + +const bloxFormats = { + markdown: { label: '', title: 'Paragraph', component: 'markdown-component' }, + headline: { label: '', title: 'Headline', component: 'headline-component' }, + ulist: { label: '', title: 'Bullet List', component: 'ulist-component' }, + olist: { label: '', title: 'Numbered List', component: 'olist-component' }, + table: { label: '', title: 'Table', component: 'table-component' }, + quote: { label: '', title: 'Quote', component: 'quote-component' }, + notice: { label: '', title: 'Notice', component: 'notice-component' }, + image: { label: '', title: 'Image', component: 'image-component' }, + video: { label: '', title: 'Video', component: 'video-component' }, + audio: { label: '', title: 'Audio', component: 'audio-component' }, + file: { label: '', title: 'File', component: 'file-component' }, + toc: { label: '', title: 'Table of Contents', component: 'toc-component' }, + hr: { label: '', title: 'Horizontal Line', component: 'hr-component' }, + definition: { label: '', title: 'Definition List', component: 'definition-component' }, + code: { label: '', title: 'Code', component: 'code-component' }, + shortcode: { label: '', title: 'Shortcode', component: 'shortcode-component' }, + youtube: { label: '', title: 'YouTube', component: 'youtube-component' }, +}; + +const formatConfig = data.settings.formats; +const activeFormats = {}; + +for (const format in bloxFormats) +{ + if (formatConfig.includes(format)) + { + activeFormats[format] = bloxFormats[format]; + } +} \ No newline at end of file diff --git a/system/typemill/author/js/vue-blox.js b/system/typemill/author/js/vue-blox.js new file mode 100644 index 0000000..00e30a1 --- /dev/null +++ b/system/typemill/author/js/vue-blox.js @@ -0,0 +1,561 @@ +const bloxeditor = Vue.createApp({ + template: `
          + + + + +
          + `, + data() { + return { + content: data.content, + editorVisible: true, + dragDisabled: false, + } + }, + computed: + { + dragOptions() + { + return { + animation: 150, + disabled: this.dragDisabled, + ghostClass: "ghost", + delay: 150, + delayOnTouchOnly: true + }; + }, + }, + mounted() { + + document.getElementById("initial-content").remove(); + + eventBus.$on('content', (content) => { + this.content = content; + }); + eventBus.$on('showEditor', (value) => { + this.editorVisible = value; + }); + eventBus.$on('disableDrag', (value) => { + this.dragDisabled = value; + }); + }, + methods: { + checkMove(event) + { + if(event.draggedContext.index == 0 || event.draggedContext.futureIndex == 0) + { + return false; + } + }, + onStart(event) + { + }, + onEnd(evt) + { + self = this; + + tmaxios.put('/api/v1/block/move',{ + 'url': data.urlinfo.route, + 'index_old': evt.oldIndex, + 'index_new': evt.newIndex, + }) + .then(function (response) + { + self.content = response.data.content; + if(response.data.navigation) + { + eventBus.$emit('navigation', response.data.navigation); + } + if(response.data.item) + { + eventBus.$emit('item', response.data.item); + } + this.$nextTick(function () { + eventBus.$emit('renderblox'); + }); + }) + .catch(function (error) + { + if(error.response) + { + let message = handleErrorMessage(error); + if(message) + { + eventBus.$emit('publishermessage', message); + } + } + }); + }, + }, +}) + +bloxeditor.component('draggable', vuedraggable); + +bloxeditor.component('content-block', { + props: ['element', 'index'], + template: ` +
          +
          +
          +

          Choose a content type

          + +
          + +
          +
          + +
          +
          +
          + + + + +
          + +
          + + +
          +
          +
          +
          + `, + data: function () { + return { + edit: false, + disabled: false, + newblock: false, + load: false, + + componentType: false, + updatedmarkdown: false, + formats: activeFormats, + hasUnsafedContent: false, + countUpdates: 0 + } + }, + mounted: function() + { + this.updatedmarkdown = this.element.markdown; + + eventBus.$on('closeComponents', this.closeEditor); + + eventBus.$on('closeNewBlock', this.closeNewBlock); + + eventBus.$on('inlineFormat', (content) => { + this.updatedmarkdown = content; + }); + + eventBus.$on('unsafedContent', (value) => { + this.hasUnsafedContent = value; + }); + }, + methods: { + openNewBlock() + { + if(this.hasUnsafedContent) + { + eventBus.$emit('publishermessage', 'Save or cancel your changes first.'); + } + else + { + this.newblock = true; + this.edit = false; + + eventBus.$emit('closeComponents'); + eventBus.$emit('disableDrag', true); + } + }, + closeNewBlock() + { + this.newblock = false; + this.componentType = false; + this.updatedmarkdown = false; + + eventBus.$emit('publisherclear'); + eventBus.$emit('unsafedContent', false); + eventBus.$emit('disableDrag', false); + + this.$nextTick(function () { + eventBus.$emit('renderblox'); + }); + }, + closeEditor() + { + if(this.edit) + { + this.edit = false; + this.newblock = false; + this.componentType = false; + this.updatedmarkdown = false; + + eventBus.$emit('publisherclear'); + eventBus.$emit('unsafedContent', false); + eventBus.$emit('disableDrag', false); + + this.$nextTick(function () { + eventBus.$emit('renderblox'); + }); + } + }, + showEditor() + { + if(this.hasUnsafedContent) + { + eventBus.$emit('publishermessage', 'Save or cancel your changes first.'); + } + else + { + eventBus.$emit('closeComponents'); + eventBus.$emit('disableDrag', true); + + this.edit = true; + this.componentType = this.determineBlockType(); + this.updatedmarkdown = this.element.markdown; + } + }, + determineBlockType() + { + this.countUpdates = 0; + + if(this.index == 0) + { + return 'title-component'; + } + + let markdown = this.element.markdown; + let lines = markdown.split("\n"); + + for (var method in determiner) + { + var specialBlock = determiner[method](markdown,lines,markdown[0],markdown[1],markdown[2]); + + if(specialBlock) + { + return specialBlock; + } + } + + return 'markdown-component'; + }, + updateMarkdownFunction(value) + { + this.updatedmarkdown = value; + + if(this.countUpdates > 0) + { + eventBus.$emit('unsafedContent', true); + } + + this.countUpdates++; + }, + disableSort() + { + eventBus.$emit('disableDrag', true); + }, + deleteBlock() + { + eventBus.$emit('closeComponents'); + + this.load = true; + var self = this; + + eventBus.$emit('publisherclear'); + + tmaxios.delete('/api/v1/block',{ + data: { + 'url': data.urlinfo.route, + 'block_id': this.index, + } + }) + .then(function (response) + { + eventBus.$emit('unsafedContent', false); + self.load = false; + self.$root.$data.content = response.data.content; + if(response.data.navigation) + { + eventBus.$emit('navigation', response.data.navigation); + } + if(response.data.item) + { + eventBus.$emit('item', response.data.item); + } + }) + .catch(function (error) + { + self.load = false; + if(error.response) + { + let message = handleErrorMessage(error); + if(message) + { + eventBus.$emit('publishermessage', message); + } + } + }); + }, + getHtml(html) + { + /* fix for empty html of abbreviations */ + if(html == '') + { + return '

          Invisible: ' + this.element.markdown + '

          '; + } + return html; + }, + beforeSave() + { + eventBus.$emit('beforeSave'); + }, + saveBlock() + { + if( + this.updatedmarkdown == undefined || + this.updatedmarkdown == this.element.markdown || + this.updatedmarkdown.replace(/(\r\n|\n|\r|\s)/gm,"") == '' + ) + { + this.closeEditor(); + return; + } + + var self = this; + this.load = true; + + eventBus.$emit('publisherclear'); + + tmaxios.put('/api/v1/block',{ + 'url': data.urlinfo.route, + 'block_id': this.index, + 'markdown': this.updatedmarkdown.trim(), + }) + .then(function (response) + { + eventBus.$emit('unsafedContent', false); + self.load = false; + self.newblock = false; + eventBus.$emit('closeComponents'); + + + self.$root.$data.content = response.data.content; + if(response.data.navigation) + { + eventBus.$emit('navigation', response.data.navigation); + } + if(response.data.item) + { + eventBus.$emit('item', response.data.item); + } + self.closeEditor(); + }) + .catch(function (error) + { + self.load = false; + if(error.response) + { + let message = handleErrorMessage(error); + if(message) + { + eventBus.$emit('publishermessage', message); + } + } + }); + }, + }, +}) + +bloxeditor.component('new-block',{ + props: ['markdown', 'index'], + template: ` +
          +
          + +
          +
          + +
          + + +
          +
          +
          + `, + data: function () { + return { + formats: activeFormats, + componentType: false, + disabled: false, + newblockmarkdown: '', + hasUnsafedContent: false, + } + }, + mounted: function() + { + eventBus.$on('closeComponents', this.closeComponent); + + eventBus.$on('inlineFormat', content => { + this.newblockmarkdown = content; + }); + + eventBus.$on('unsafedContent', (value) => { + this.hasUnsafedContent = value; + }); + }, + methods: { + setComponentType(event, componenttype) + { + if(this.hasUnsafedContent) + { + eventBus.$emit('publishermessage', 'Save or cancel your changes first.'); + } + else + { + /* if it is a new block at the end of the page, close other open blocks first */ + if(this.index == 999999) + { + eventBus.$emit('closeComponents'); + } + + eventBus.$emit('disableDrag', true); + + this.componentType = componenttype; + } + }, + closeComponent() + { + this.componentType = false; + this.newblockmarkdown = ''; + + eventBus.$emit('publisherclear'); + eventBus.$emit('unsafedContent', false); + eventBus.$emit('disableDrag', false); + + this.$nextTick(function () { + eventBus.$emit('renderblox', 0); + }); + + }, + updateMarkdownFunction(value) + { + eventBus.$emit('unsafedContent', true); + this.newblockmarkdown = value; + }, + beforeSaveNew() + { + eventBus.$emit('beforeSave'); + }, + saveNewBlock() + { + if( + this.newblockmarkdown == undefined || + this.newblockmarkdown.replace(/(\r\n|\n|\r|\s)/gm,"") == '' + ) + { + this.closeComponent(); + return; + } + + if(typeof this.$refs.activeComponent.saveBlock === "function") + { + this.$refs.activeComponent.saveBlock(this.updatedmarkdown); + return; + } + + var self = this; + + eventBus.$emit('publisherclear'); + + tmaxios.post('/api/v1/block',{ + 'url': data.urlinfo.route, + 'block_id': this.index, + 'markdown': this.newblockmarkdown.trim(), + }) + .then(function (response) + { + eventBus.$emit('unsafedContent', false); + self.$root.$data.content = response.data.content; + self.closeComponent(); + eventBus.$emit('closeComponents'); + eventBus.$emit('closeNewBlock'); + + if(response.data.navigation) + { + eventBus.$emit('navigation', response.data.navigation); + } + if(response.data.item) + { + eventBus.$emit('item', response.data.item); + } + + if(self.index == 999999) + { + self.setComponentType(false, 'markdown-component'); + } + + }) + .catch(function (error) + { + if(error.response) + { + let message = handleErrorMessage(error); + if(message) + { + eventBus.$emit('publishermessage', message); + } + } + }); + }, + } +}); diff --git a/system/typemill/author/js/vue-contentnavi.js b/system/typemill/author/js/vue-contentnavi.js new file mode 100644 index 0000000..c9aa529 --- /dev/null +++ b/system/typemill/author/js/vue-contentnavi.js @@ -0,0 +1,489 @@ +const navigation = Vue.createApp({ + template: ` +
          + + + +
          `, + data: function () { + return { + navigation: data.navigation, + home: data.home, + backup: false, + isExpended: false, + expanded: [], + menuvisible: false, + } + }, + mounted: function(){ + var expanded = localStorage.getItem('expanded'); + if(expanded !== null) + { + var expandedArray = expanded.split(','); + var expandedLength = expandedArray.length; + var cleanExpandedArray = []; + for(var i = 0; i < expandedLength; i++) + { + if(typeof expandedArray[i] === 'string' && expandedArray[i] != '') + { + cleanExpandedArray.push(expandedArray[i]); + } + } + this.expanded = expanded.split(','); + } + + eventBus.$on('toggleFolder', this.toggleFolder); + eventBus.$on('backupNavigation', this.backupNavigation); + eventBus.$on('revertNavigation', this.revertNavigation); + eventBus.$on('navigation', navigation => { + this.navigation = navigation; + }); + eventBus.$on('item', item => { + if(item.originalName == 'home') + { + this.home = item; + this.home.active = true; + } + }); + }, + methods: { + togglemenue() + { + if(this.menuvisible) + { + this.menuvisible = false; + } + else + { + this.menuvisible = true; + } + }, + getStatusClass(status) + { + if(status == 'published') + { + return "border-teal-500"; + } + else if(status == 'modified') + { + return "border-yellow-400"; + } + }, + getUrl() + { + return tmaxios.defaults.baseURL + '/tm/content/' + data.settings.editor; + }, + toggleFolder(url) + { + var index = this.expanded.indexOf(url); + if (index > -1) + { + this.expanded.splice(index, 1); + } + else + { + this.expanded.push(url); + } + localStorage.setItem("expanded", this.expanded.toString()); + }, + expandNavigation() + { + this.expanded = this.getFolderUrls(this.navigation, []); + localStorage.setItem("expanded", this.expanded.toString()); + }, + collapseNavigation() + { + this.expanded = this.getActiveUrls(this.navigation, []); + localStorage.setItem("expanded", this.expanded.toString()); + }, + getActiveUrls(navigation, expanded) + { + for (const item of navigation) + { + if(item.activeParent || item.active) + { + expanded.push(item.urlRelWoF); + } + + if (item.elementType == 'folder') + { + this.getActiveUrls(item.folderContent, expanded); + } + } + return expanded; + }, + getFolderUrls(navigation, result) + { + for (const item of navigation) + { + if (item.elementType == 'folder') + { + result.push(item.urlRelWoF); + this.getFolderUrls(item.folderContent, result); + } + } + return result; + }, + backupNavigation() + { + this.backup = this.navigation; + }, + revertNavigation() + { + this.navigation = this.backup; + } + } +}); + +navigation.component('draggable', vuedraggable); + +navigation.component('navilevel',{ + template: ` + + + + `, + props: { + navigation: { + type: Array, + required: true + }, + parentId: { + default: 'root' + }, + expanded: { + type: Array, + required: false + } + }, + data: function () { + return { + navilevel: '', + load: '?', + freeze: false, + newItem: '', +/* format: /[@#*()=\[\]{};:"\\|,.<>\/]/, */ + format: /(^\.)|(\.$)|[\/\\?%*:|"<>]/, + } + }, + computed: + { + dragOptions() + { + return { + animation: 150, + group: "file", + disabled: this.freeze, + ghostClass: "ghost", + delay: 150, + delayOnTouchOnly: true + }; + }, + + // this.value when input = v-model + // this.list when input != v-model + realValue() + { + return this.value ? this.value : this.list; + } + }, + methods: + { + getStatusClass(status, keyPathArray) + { + var level = 3; + if(keyPathArray.length > 1) + { + var level = keyPathArray.length * 3; // 3, 6, 9, 12, 15 + } + let naviclass = 'pl-' + level; + this.navilevel = naviclass; + + if(status == 'published') + { + return naviclass += " border-teal-500"; + } + else if(status == 'unpublished') + { + return naviclass += " border-rose-500"; + } + else if(status == 'modified') + { + return naviclass += " border-yellow-400"; + } + }, + getNaviClass(active, activeParent, type) + { + let fontweight = 'font-normal'; + if(type == 'folder') + { + fontweight = 'font-bold' + } + + if(activeParent) + { + return fontweight += " activeParent"; + } + else if(active) + { + return fontweight += " text-stone-50 bg-teal-500 dark:text-stone-900 dark:bg-stone-200"; + } + + return fontweight; + }, + getNaviInputLevel(keyPathArray) + { + var level = 3; + var levelString = String(keyPathArray); + if(levelString != "root") + { + var levelArray = levelString.split("."); + var level = (levelArray.length + 1) * 3; // 3, 6, 9, 12, 15 + } + return 'pl-' + level; + }, + getUrl(segment) + { + return tmaxios.defaults.baseURL + '/tm/content/' + data.settings.editor + segment; + }, + callToggle(url) + { + eventBus.$emit('toggleFolder', url); + }, + isExpanded(url) + { + if(this.expanded.indexOf(url) > -1) + { + return true; + } + return false; + }, + isActiveFolder(element) + { + if(this.expanded.indexOf(element.urlRelWoF) > -1 ) + { + return true; + } + return false; + +/* if you want active folders always expanded + if(element.active || element.activeParent || (this.expanded.indexOf(element.urlRelWoF) > -1) ) + { + return true; + } + return false; +*/ + }, + onStart(evt) + { + eventBus.$emit('backupNavigation'); + /* delete error messages if exist */ + // publishController.errors.message = false; + }, + checkMove(evt) + { + /* do we want to keep that restriction, no folder into folders? */ + if(evt.dragged.classList.contains('folder') && evt.from.parentNode.id != evt.to.parentNode.id) + { + console.info("moved folder to another folder"); + return false; + } + if(evt.dragged.dataset.active == 'active' && !editor.draftDisabled) + { + console.info("moved page is active, save your changes first"); + // publishController.errors.message = "Please save your changes before you move the file"; + return false; + } + return true; + }, + onEnd(evt) + { + if(evt.from.parentNode.id == evt.to.parentNode.id && evt.oldIndex == evt.newIndex) + { + return + } + this.freeze = true; + this.load = evt.item.id; + + var self = this; + +// self.errors = {title: false, content: false, message: false}; + + tmaxios.post('/api/v1/article/sort',{ + 'item_id': evt.item.id, + 'parent_id_from': evt.from.parentNode.id, + 'parent_id_to': evt.to.parentNode.id, + 'index_old': evt.oldIndex, + 'index_new': evt.newIndex, + 'active': evt.item.dataset.active === 'true' ? 'active' : '', + 'url': evt.item.dataset.url, + }) + .then(function (response) + { + self.load = '?'; + self.freeze = false; + + if(response.data.url) + { + window.location.replace(response.data.url); + } + if(response.data.navigation) + { + self.$root.$data.navigation = response.data.navigation; + } + }) + .catch(function (error) + { + if(error.response) + { + eventBus.$emit('revertNavigation'); + + let message = handleErrorMessage(error); + if(message) + { + eventBus.$emit('publishermessage', message); + } + } + }); + }, + addItem(type, parent) + { + eventBus.$emit('publisherclear'); + + if( + this.format.test(this.newItem) || + !this.newItem || + this.newItem.length > 60 + ) + { + let message = this.$filters.translate('Special characters or invalid patterns are not allowed. Length between 1 and 60.'); + eventBus.$emit('publishermessage', message); + return; + } + + self = this; + + self.freeze = true; + // self.errors = {title: false, content: false, message: false}; + + tmaxios.post('/api/v1/article',{ + 'item_name': this.newItem, + 'folder_id': parent, + 'type': type + }) + .then(function (response) { + + self.freeze = false; + + if(response.data.url) + { + window.location.replace(response.data.url); + } + if(response.data.navigation) + { + self.$root.$data.navigation = response.data.navigation; + self.newItem = ''; + } + }) + .catch(function (error) + { + if(error.response) + { + let message = handleErrorMessage(error); + if(message) + { + eventBus.$emit('publishermessage', message); + } + } + }); + }, + emitter(value) { + this.$emit("input", value); + }, + }, +}); \ No newline at end of file diff --git a/system/typemill/author/js/vue-eventbus.js b/system/typemill/author/js/vue-eventbus.js new file mode 100644 index 0000000..d06566d --- /dev/null +++ b/system/typemill/author/js/vue-eventbus.js @@ -0,0 +1,31 @@ +class Event{ + constructor(){ + this.events = {}; + } + + $on(eventName, fn) { + this.events[eventName] = this.events[eventName] || []; + this.events[eventName].push(fn); + } + + $off(eventName, fn) { + if (this.events[eventName]) { + for (var i = 0; i < this.events[eventName].length; i++) { + if (this.events[eventName][i] === fn) { + this.events[eventName].splice(i, 1); + break; + } + }; + } + } + + $emit(eventName, data) { + if (this.events[eventName]) { + this.events[eventName].forEach(function(fn) { + fn(data); + }); + } + } +}; + +const eventBus = new Event(); \ No newline at end of file diff --git a/system/typemill/author/js/vue-forms.js b/system/typemill/author/js/vue-forms.js new file mode 100644 index 0000000..6a66d7f --- /dev/null +++ b/system/typemill/author/js/vue-forms.js @@ -0,0 +1,1177 @@ +app.component('component-paragraph', { + props: ['id', 'description', 'maxlength', 'hidden', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'value', 'css', 'errors'], + template: `
          +

          {{ $filters.translate(label) }}

          +

          +
          `, +}) + + +app.component('component-text', { + props: ['id', 'description', 'maxlength', 'hidden', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'value', 'css', 'errors'], + template: `
          + + +

          {{ errors[name] }}

          +

          +
          `, + methods: { + update: function($event, name) + { + eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); + }, + }, +}) + +app.component('component-textarea', { + props: ['id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'css', 'value', 'errors'], + template: `
          + + +

          {{ errors[name] }}

          +

          +
          `, + methods: { + update: function($event, name) + { + eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); + }, + formatValue: function(value) + { + /* + if(value !== null && typeof value === 'object') + { + this.textareaclass = 'codearea'; + return JSON.stringify(value, undefined, 4); + } + return value; + */ + }, + }, +}) + +app.component('component-codearea', { + props: ['id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'css', 'value', 'errors'], + data: function() { + return { + highlighted: '', + } + }, + template: `
          + +
          + + +
          +

          {{ errors[name] }}

          +

          +
          `, + mounted: function() + { + this.initialize() + + eventBus.$on('codeareaupdate', this.initialize ); + }, + methods: { + initialize() + { + this.$nextTick(() => { + this.highlight(this.value); + this.resizeCodearea(); + }); + }, + update($event, name) + { + this.highlight($event.target.value); + eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); + + this.$nextTick(() => { + this.resizeCodearea(); + }); + }, + resizeCodearea() { + let codeeditor = this.$refs.editor; + const scrollPosition = codeeditor.scrollTop; // Store the current scroll position + + window.requestAnimationFrame(() => { + if (codeeditor.scrollHeight !== codeeditor.clientHeight) + { + codeeditor.style.height = `${codeeditor.scrollHeight + 2}px`; + } + codeeditor.scrollTop = scrollPosition; // Restore the scroll position + }); + }, + highlight(code) + { + if(code === undefined) + { + return; + } + + window.requestAnimationFrame(() => { + highlighted = hljs.highlightAuto(code, ['xml','css','yaml','markdown']).value; + this.highlighted = highlighted; + }); + }, + }, +}) + +app.component('component-select', { + props: ['id', 'description', 'readonly', 'required', 'disabled', 'label', 'name', 'type', 'css', 'options', 'value', 'errors', 'dataset', 'userroles'], + template: `
          + + +

          {{ errors[name] }}

          +

          +
          `, + methods: { + update: function($event, name) + { + eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); + }, + }, +}) + +app.component('component-checkbox', { + props: ['id', 'description', 'readonly', 'required', 'disabled', 'label', 'checkboxlabel', 'name', 'type', 'css', 'value', 'errors'], + data() { + return { + checked: false + } + }, + template: `
          +
          {{ $filters.translate(label) }}
          + +

          {{ errors[name] }}

          +

          +
          `, + mounted: function() + { + if(this.value === true || this.value == 'on') + { + this.checked = true; + } + }, + methods: { + update: function(checked, name) + { + eventBus.$emit('forminput', {'name': name, 'value': checked}); + }, + }, +}) + +app.component('component-checkboxlist', { + props: ['description', 'readonly', 'required', 'disabled', 'label', 'checkboxlabel', 'options', 'name', 'type', 'css', 'value', 'errors'], + data() { + return { + checkedoptions: [] + } + }, + template: `
          +
          {{ $filters.translate(label) }}
          + +

          {{ errors[name] }}

          +

          +
          `, + mounted: function() + { + if(this.value && typeof this.value === 'object') + { + this.checkedoptions = this.value; + } + }, + methods: { + update: function(checkedoptions, name) + { + eventBus.$emit('forminput', {'name': name, 'value': checkedoptions}); + }, + }, +}) + +app.component('component-radio', { + props: ['id', 'description', 'readonly', 'required', 'disabled', 'options', 'label', 'name', 'type', 'css', 'value', 'errors'], + data() { + return { + picked: this.value + } + }, + template: `
          +
          {{ $filters.translate(label) }}
          + +

          {{ errors[name] }}

          +

          +
          `, + methods: { + update: function(picked, name) + { + eventBus.$emit('forminput', {'name': name, 'value': picked}); + }, + }, +}) + +app.component('component-number', { + props: ['id', 'description', 'min', 'max', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'css', 'value', 'errors'], + template: `
          + + +

          {{ errors[name] }}

          +

          +
          `, + methods: { + update: function($event, name) + { + eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); + }, + }, +}) + +app.component('component-date', { + props: ['id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'css', 'value', 'errors'], + template: `
          + +
          +
          + +
          + +
          +

          {{ errors[name] }}

          +

          +
          `, + methods: { + update: function($event, name) + { + eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); + }, + }, +}) + +app.component('component-email', { + props: ['id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'css', 'value', 'errors'], + template: `
          + +
          +
          + +
          + +
          +

          {{ errors[name] }}

          +

          +
          `, + methods: { + update: function($event, name) + { + eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); + }, + }, +}) + +app.component('component-tel', { + props: ['id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'css', 'value', 'errors'], + template: `
          + +
          +
          + +
          + +
          +

          {{ errors[name] }}

          +

          +
          `, + methods: { + update: function($event, name) + { + eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); + }, + }, +}) + +app.component('component-url', { + props: ['id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'css', 'value', 'errors'], + template: `
          + +
          +
          + +
          + +
          +

          {{ errors[name] }}

          +

          +
          `, + methods: { + update: function($event, name) + { + eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); + }, + }, +}) + +app.component('component-color', { + props: ['id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'css', 'value', 'errors'], + template: `
          + +
          +
          + +
          + +
          +

          {{ errors[name] }}

          +

          +
          `, + methods: { + update: function($event, name) + { + eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); + }, + }, +}) + +app.component('component-password', { + props: ['id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'autocomplete', 'generator', 'css', 'value', 'errors'], + data() { + return { + fieldType: "password" + }; + }, + template: `
          + +
          +
          + +
          + +
          + + +
          +
          +
          +
          +

          {{ errors[name] }}

          +

          +
          +
          + +
          +
          +
          `, + methods: { + update: function(newvalue, name) + { + eventBus.$emit('forminput', {'name': name, 'value': newvalue}); + }, + toggleFieldType: function() + { + if (this.fieldType === "password") + { + this.fieldType = "text"; + } + else + { + this.fieldType = "password"; + } + }, + generatePassword: function() + { + const digits = '0123456789'; + const upper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + const lower = upper.toLowerCase(); + const characters = digits + upper + lower; + const length = 40; + + const randomCharacters = Array.from({ length }, (_) => + this.getRandomCharacter(characters), + ).join('') + + const passwordLength = this.getRandomInt(30,40); + + const password = randomCharacters.substring(0,passwordLength); + + this.update(password, this.name); + }, + getRandomInt: function(min,max) + { + return Math.floor(Math.random() * (max - min + 1) + min); + }, + getRandomCharacter: function(characters) + { + let randomNumber + + do{ + randomNumber = crypto.getRandomValues(new Uint8Array(1))[0] + } while (randomNumber >= 256 - (256 % characters.length)) + + return characters[randomNumber % characters.length] + } + }, +}) + +app.component('component-hidden', { + props: ['id', 'maxlength', 'required', 'disabled', 'name', 'type', 'css', 'value', 'errors'], + template: ``, + methods: { + update: function($event, name) + { + eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); + }, + }, +}) + +app.component('component-customfields', { + props: ['id', 'description', 'readonly', 'required', 'disabled', 'options', 'label', 'name', 'type', 'css', 'value', 'errors'], + data: function () { + return { + fielderrors: false, + fielddetails: {}, + disableaddbutton: false, + cfvalue: [{}] + } + }, + template: `
          + +

          {{ fielderrors }}

          +

          {{ $filters.translate(description) }}

          + +
          +
          + +
          +
          +
          + + + + + +
          +
          +
          +
          + + {{ $filters.translate('add entry') }} +
          `, + mounted: function(){ + if(typeof this.value === 'undefined' || this.value === null || this.value.length == 0) + { + // this.cfvalue = [{}]; + // this.update(this.cfvalue, this.name); + this.disableaddbutton = 'disabled'; + } + else + { + /* turn object { key:value, key:value } into array [[key,value][key,value]] */ + this.cfvalue = Object.entries(this.value); + /* and back into array of objects [ {key: key, value: value}{key:key, value: value }] */ + this.cfvalue = this.cfvalue.map(function(item){ return { 'key': item[0], 'value': item[1] } }); + } + }, + methods: { + update: function(value, name) + { + this.fielderrors = false; + this.mainerror = false; + + /* transform array of objects [{key:mykey, value:myvalue}] into array [[mykey,myvalue]] */ + var storedvalue = value.map(function(item){ return [item.key, item.value]; }); + + /* transform array [[mykey,myvalue]] into object { mykey:myvalue } */ + storedvalue = Object.fromEntries(storedvalue); + + eventBus.$emit('forminput', {'name': name, 'value': storedvalue}); + }, + updatePairKey: function(index,event) + { + this.cfvalue[index].key = event.target.value; + + var regex = /^[a-z0-9]+$/i; + + if(!this.keyIsUnique(event.target.value,index)) + { + this.cfvalue[index].keyerror = 'red'; + this.fielderrors = 'Error: The key already exists'; + this.disableaddbutton = 'disabled'; + return; + } + else if(!regex.test(event.target.value)) + { + this.cfvalue[index].keyerror = 'red'; + this.fielderrors = 'Error: Only alphanumeric for keys allowed'; + this.disableaddbutton = 'disabled'; + return; + } + + delete this.cfvalue[index].keyerror; + this.disableaddbutton = false; + this.update(this.cfvalue,this.name); + }, + keyIsUnique: function(keystring, index) + { + for(obj in this.cfvalue) + { + if( (obj != index) && (this.cfvalue[obj].key == keystring) ) + { + return false; + } + } + return true; + }, + updatePairValue: function(index, event) + { + this.cfvalue[index].value = event.target.value; + + var regex = /<.*(?=>)/gm; + if(event.target.value == '' || regex.test(event.target.value)) + { + this.cfvalue[index].valueerror = 'red'; + this.fielderrors = 'Error: No empty values or html tags are allowed'; + } + else + { + delete this.cfvalue[index].valueerror; + this.update(this.cfvalue,this.name); + } + }, + addField: function() + { + for(object in this.cfvalue) + { + if(Object.keys(this.cfvalue[object]).length === 0) + { + return; + } + } + this.cfvalue.push({}); + this.disableaddbutton = 'disabled'; + }, + deleteField: function(index) + { + this.cfvalue.splice(index,1); + this.disableaddbutton = false; + this.update(this.cfvalue,this.name); + }, + }, +}) + + +app.component('component-image', { + props: ['id', 'description', 'maxlength', 'hidden', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'value', 'css', 'errors', 'keepformat'], + components: { + medialib: medialib + }, + data: function(){ + return { + maxsize: 10, // megabyte + imagepreview: '', + showmedialib: false, + quality: false, + qualitylabel: false, + } + }, + template: `
          + +
          +
          +
          + +
          +
          +
          +
          +

          {{ $filters.translate('upload an image') }}

          + +
          +
          + +
          +
          + +
          + + +
          +
          + +
          +
          +

          {{ errors[name] }}

          +

          +
          +
          + + +
          + + +
          +
          + +
          `, + mounted: function() { + if(this.hasValue(this.value)) + { + this.imagepreview = tmaxios.defaults.baseURL + '/' + this.value; + + /* switcher for quality */ + if(this.value.indexOf("/live/") > -1 ) + { + this.quality = 'optimized'; + this.qualitylabel = 'switch size to: maximum'; + } + else if(this.value.indexOf("/original/") > -1) + { + this.quality = 'maximum'; + this.qualitylabel = 'switch size to: optimized'; + } + } + }, + methods: { + addFromMedialibFunction(value) + { + // this.imgfile = value; + this.imagepreview = data.urlinfo.baseurl + '/' + value; + this.showmedialib = false; + + this.update(value); + }, + hasValue: function(value) + { + if(typeof this.value !== "undefined" && this.value !== false && this.value !== null && this.value !== '') + { + return true; + } + return false; + }, + switchQuality: function(value) + { + if(this.hasValue(value)) + { + if(this.quality == 'optimized') + { + this.quality = 'maximum'; + this.qualitylabel = 'switch size to: optimized'; + var newUrl = value.replace("/live/", "/original/"); + this.update(newUrl); + } + else + { + this.quality = 'optimized'; + this.qualitylabel = 'switch quality to: maximum'; + var newUrl = value.replace("/original/", "/live/"); + this.update(newUrl); + } + } + }, + update: function(filepath) + { + eventBus.$emit('forminput', {'name': this.name, 'value': filepath}); + }, + /* + updatemarkdown: function(markdown, url) + { + /* is called from child component medialib + this.update(url); + }, + createmarkdown: function(url) + { + /* is called from child component medialib + this.update(url); + }, + */ + deleteImage: function() + { + this.imagepreview = ''; + this.update(''); + }, + onFileChange: function( e ) + { + if(e.target.files.length > 0) + { + let imageFile = e.target.files[0]; + let size = imageFile.size / 1024 / 1024; + + if (!imageFile.type.match('image.*')) + { + alert('only images allowed'); +/* publishController.errors.message = "Only images are allowed."; */ + } + else if (size > this.maxsize) + { + alert('too big'); +/* publishController.errors.message = "The maximal size of images is " + this.maxsize + " MB"; */ + } + else + { + sharedself = this; + + let keepformat = this.keepformat ? true : false; + let reader = new FileReader(); + reader.readAsDataURL(imageFile); + reader.onload = function(e) + { + sharedself.imagepreview = e.target.result; + + tmaxios.post('/api/v1/image',{ + 'image': e.target.result, + 'name': imageFile.name, + 'publish': true, + 'keepformat': keepformat + }) + .then(function (response) { + sharedself.update(response.data.name); + }) + .catch(function (error) + { + sharedself.load = false; + if(error.response) + { + console.info(error.response); +/* publishController.errors.message = error.response.data.errors; */ + } + }); + } + } + } + } + }, +}) + +app.component('component-file', { + props: ['id', 'description', 'maxlength', 'hidden', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'value', 'css', 'errors'], + components: { + medialib: medialib + }, + data: function(){ + return { + maxsize: 20, // megabyte + showmedialib: false, +// fileid: '', +// load: false, + error: false, + userroles: ['all'], + selectedrole: '', + } + }, + template: `
          + + + +
          + +
          + +
          + + +
          + + +
          +
          + +
          + + +
          + +
          + +
          + +
          +

          + + + + {{ $filters.translate('upload a file') }} +

          + +
          +
          + +
          +
          +
          +

          {{ error }}

          +

          + + +
          + + +
          +
          +
          `, + + mounted: function(){ + this.getrestriction(); + }, + methods: { + addFromMedialibFunction(file) + { + this.error = false; + this.showmedialib = false; + + this.update(file.url); + this.getrestriction(file.url); + }, + update: function(filepath) + { + eventBus.$emit('forminput', {'name': this.name, 'value': filepath}); + }, + deleteFile: function() + { + this.error = false; + + this.update(''); + this.selectedrole = 'all'; + }, + getrestriction(newfilepath) + { + this.error = false; + + var filepath = this.value; + if(newfilepath) + { + filepath = newfilepath; + } + + var myself = this; + + tmaxios.get('/api/v1/filerestrictions',{ + params: { + 'url': data.urlinfo.route, + 'filename': filepath, + } + }) + .then(function (response) { + myself.userroles = ['all']; + myself.userroles = myself.userroles.concat(response.data.userroles); + myself.selectedrole = response.data.restriction; + }) + .catch(function (error) + { + if(error.response.data.message) + { + myself.error = myself.$filters.translate(error.response.data.message); + myself.error +// eventBus.$emit('publishermessage', message); + } + }); + }, + updaterestriction() + { + this.error = false; + + var filepath = this.value; + if(!filepath) + { + this.error = 'File is missing for a restriction.'; + } + + var resself = this; + + tmaxios.post('/api/v1/filerestrictions',{ + 'url': data.urlinfo.route, + 'filename': filepath, + 'role': this.selectedrole, + }) + .then(function (response) { + + }) + .catch(function (error){ + resself.error = 'some error update file restrictions'; + }); + }, + onFileChange( e ) + { + this.error = false; + + if(e.target.files.length > 0) + { + let uploadedFile = e.target.files[0]; + let size = uploadedFile.size / 1024 / 1024; + + if (size > this.maxsize) + { + let message = "The maximal size of a file is " + this.maxsize + " MB"; + + // show error in component + eventBus.$emit('publishermessage', message); + } + else + { + self.load = true; + + self = this; + + let reader = new FileReader(); + reader.readAsDataURL(uploadedFile); + reader.onload = function(e) + { + tmaxios.post('/api/v1/file',{ + 'file': e.target.result, + 'name': uploadedFile.name + }) + .then(function (response) { + self.filetitle = response.data.fileinfo.title; + self.selectedrole = 'all'; + self.update(response.data.filepath); + }) + .catch(function (error) + { + if(error.response) + { + self.error = 'error update file'; + console.info(error.response); + /* publishController.errors.message = error.response.data.errors; */ + } + }); + } + } + } + } + } +}) \ No newline at end of file diff --git a/system/typemill/author/js/vue-kixote.js b/system/typemill/author/js/vue-kixote.js new file mode 100644 index 0000000..4bc1b02 --- /dev/null +++ b/system/typemill/author/js/vue-kixote.js @@ -0,0 +1,1531 @@ +const getKixoteError = function(error) +{ + if(error.response.data.error != undefined) + { + if(Array.isArray(error.response.data.error)) + { + return error.response.data.error; + } + if(typeof error.response.data.error === 'string') + { + return [error.response.data.error]; + } + } + + return ['something went wrong']; +} + +/* ++ If you change page and open kixote, then the old version without changes is loaded ++ store kixoteSettings +*/ + +const kixote = Vue.createApp({ + template: `
          + +
          +
          +
          + +
          + + + + +
          + +
          +
          +
          +
          + + + + {{ tab }} +
          +
          +
          +
          + + + + + + Usage +
          +
          + 0 + Token +
          +
          + +
          + +
          + +
          + +
          + +
          +
          +
          + + + + + + +
          +

          Generating, please be patient ...

          +
          +
          +
          `, + data() { + return { + showKixote: false, + currentTab: "Admin", + tabs: [ + "Admin", + "Generate", +/* "Automate", + "Translate", + "SEO", + "RAG" */ + ], + icons: { + Admin: "M11.366 22.564l1.291-1.807-1.414-1.414-1.807 1.291c-0.335-0.187-0.694-0.337-1.071-0.444l-0.365-2.19h-2l-0.365 2.19c-0.377 0.107-0.736 0.256-1.071 0.444l-1.807-1.291-1.414 1.414 1.291 1.807c-0.187 0.335-0.337 0.694-0.443 1.071l-2.19 0.365v2l2.19 0.365c0.107 0.377 0.256 0.736 0.444 1.071l-1.291 1.807 1.414 1.414 1.807-1.291c0.335 0.187 0.694 0.337 1.071 0.444l0.365 2.19h2l0.365-2.19c0.377-0.107 0.736-0.256 1.071-0.444l1.807 1.291 1.414-1.414-1.291-1.807c0.187-0.335 0.337-0.694 0.444-1.071l2.19-0.365v-2l-2.19-0.365c-0.107-0.377-0.256-0.736-0.444-1.071zM7 27c-1.105 0-2-0.895-2-2s0.895-2 2-2 2 0.895 2 2-0.895 2-2 2zM32 12v-2l-2.106-0.383c-0.039-0.251-0.088-0.499-0.148-0.743l1.799-1.159-0.765-1.848-2.092 0.452c-0.132-0.216-0.273-0.426-0.422-0.629l1.219-1.761-1.414-1.414-1.761 1.219c-0.203-0.149-0.413-0.29-0.629-0.422l0.452-2.092-1.848-0.765-1.159 1.799c-0.244-0.059-0.492-0.109-0.743-0.148l-0.383-2.106h-2l-0.383 2.106c-0.251 0.039-0.499 0.088-0.743 0.148l-1.159-1.799-1.848 0.765 0.452 2.092c-0.216 0.132-0.426 0.273-0.629 0.422l-1.761-1.219-1.414 1.414 1.219 1.761c-0.149 0.203-0.29 0.413-0.422 0.629l-2.092-0.452-0.765 1.848 1.799 1.159c-0.059 0.244-0.109 0.492-0.148 0.743l-2.106 0.383v2l2.106 0.383c0.039 0.251 0.088 0.499 0.148 0.743l-1.799 1.159 0.765 1.848 2.092-0.452c0.132 0.216 0.273 0.426 0.422 0.629l-1.219 1.761 1.414 1.414 1.761-1.219c0.203 0.149 0.413 0.29 0.629 0.422l-0.452 2.092 1.848 0.765 1.159-1.799c0.244 0.059 0.492 0.109 0.743 0.148l0.383 2.106h2l0.383-2.106c0.251-0.039 0.499-0.088 0.743-0.148l1.159 1.799 1.848-0.765-0.452-2.092c0.216-0.132 0.426-0.273 0.629-0.422l1.761 1.219 1.414-1.414-1.219-1.761c0.149-0.203 0.29-0.413 0.422-0.629l2.092 0.452 0.765-1.848-1.799-1.159c0.059-0.244 0.109-0.492 0.148-0.743l2.106-0.383zM21 15.35c-2.402 0-4.35-1.948-4.35-4.35s1.948-4.35 4.35-4.35 4.35 1.948 4.35 4.35c0 2.402-1.948 4.35-4.35 4.35z", + Generate: "M8 6l-4-4h-2v2l4 4zM10 0h2v4h-2zM18 10h4v2h-4zM20 4v-2h-2l-4 4 2 2zM0 10h4v2h-4zM10 18h2v4h-2zM2 18v2h2l4-4-2-2zM31.563 27.563l-19.879-19.879c-0.583-0.583-1.538-0.583-2.121 0l-1.879 1.879c-0.583 0.583-0.583 1.538 0 2.121l19.879 19.879c0.583 0.583 1.538 0.583 2.121 0l1.879-1.879c0.583-0.583 0.583-1.538 0-2.121zM15 17l-6-6 2-2 6 6-2 2z", + Automate: "M0.001 16.051l-0.001 0c0 0 0 0.003 0.001 0.007 0.003 0.121 0.017 0.24 0.041 0.355 0.006 0.055 0.013 0.114 0.021 0.18 0.007 0.059 0.014 0.122 0.022 0.19 0.012 0.080 0.024 0.165 0.037 0.256 0.027 0.18 0.056 0.379 0.091 0.592 0.042 0.201 0.088 0.419 0.136 0.652 0.022 0.116 0.055 0.235 0.087 0.356s0.065 0.247 0.099 0.375c0.018 0.064 0.032 0.129 0.053 0.194s0.041 0.131 0.062 0.197 0.085 0.268 0.129 0.406c0.011 0.035 0.022 0.069 0.033 0.104 0.013 0.034 0.025 0.069 0.038 0.104 0.026 0.069 0.052 0.139 0.078 0.21 0.053 0.14 0.107 0.284 0.162 0.429 0.061 0.143 0.124 0.288 0.188 0.435 0.032 0.073 0.064 0.147 0.096 0.222s0.071 0.147 0.107 0.221c0.073 0.147 0.146 0.297 0.221 0.448 0.077 0.15 0.163 0.297 0.245 0.448 0.042 0.075 0.084 0.15 0.126 0.226s0.091 0.148 0.136 0.223c0.092 0.148 0.185 0.298 0.279 0.448 0.395 0.59 0.834 1.174 1.319 1.727 0.491 0.549 1.023 1.070 1.584 1.55 0.568 0.473 1.165 0.903 1.773 1.285 0.613 0.376 1.239 0.697 1.856 0.973 0.156 0.064 0.311 0.127 0.465 0.19 0.077 0.030 0.152 0.064 0.229 0.091s0.154 0.054 0.23 0.081 0.302 0.108 0.453 0.156c0.151 0.045 0.3 0.089 0.447 0.133 0.074 0.021 0.146 0.045 0.219 0.063s0.146 0.036 0.218 0.053c0.144 0.035 0.286 0.069 0.425 0.103 0.141 0.027 0.279 0.054 0.415 0.080 0.068 0.013 0.135 0.026 0.201 0.038 0.033 0.006 0.066 0.012 0.099 0.019 0.033 0.005 0.066 0.009 0.099 0.014 0.131 0.018 0.259 0.036 0.384 0.053 0.062 0.009 0.124 0.017 0.185 0.026s0.122 0.012 0.182 0.018c0.119 0.011 0.236 0.021 0.349 0.031s0.222 0.021 0.329 0.023c0.007 0 0.014 0 0.021 0.001 0.019 1.088 0.906 1.964 1.999 1.964 0.017 0 0.034-0.001 0.051-0.001v0.001c0 0 0.003-0 0.007-0.001 0.121-0.003 0.24-0.017 0.355-0.041 0.055-0.006 0.114-0.013 0.18-0.021 0.059-0.007 0.122-0.014 0.19-0.022 0.080-0.012 0.165-0.024 0.256-0.037 0.18-0.027 0.379-0.056 0.592-0.091 0.201-0.042 0.419-0.088 0.652-0.136 0.116-0.022 0.235-0.056 0.356-0.087s0.247-0.065 0.375-0.099c0.064-0.018 0.129-0.032 0.194-0.053s0.13-0.041 0.197-0.062 0.268-0.085 0.406-0.129c0.035-0.011 0.069-0.022 0.104-0.033 0.034-0.013 0.069-0.025 0.104-0.038 0.069-0.026 0.139-0.052 0.21-0.078 0.14-0.053 0.284-0.107 0.429-0.162 0.143-0.061 0.288-0.124 0.436-0.188 0.073-0.032 0.147-0.064 0.222-0.096s0.147-0.071 0.221-0.107c0.147-0.073 0.297-0.146 0.448-0.221 0.15-0.077 0.297-0.163 0.448-0.245 0.075-0.042 0.15-0.084 0.226-0.126s0.148-0.091 0.223-0.136c0.148-0.092 0.298-0.185 0.448-0.279 0.59-0.395 1.174-0.834 1.727-1.319 0.549-0.491 1.070-1.023 1.55-1.584 0.473-0.568 0.903-1.165 1.285-1.773 0.376-0.613 0.697-1.239 0.973-1.855 0.064-0.156 0.127-0.311 0.19-0.465 0.030-0.077 0.064-0.152 0.091-0.229s0.054-0.154 0.081-0.23 0.108-0.302 0.156-0.453c0.045-0.151 0.089-0.3 0.133-0.447 0.021-0.074 0.045-0.146 0.063-0.219s0.036-0.146 0.053-0.218c0.035-0.144 0.069-0.286 0.103-0.425 0.027-0.141 0.054-0.279 0.080-0.415 0.013-0.068 0.026-0.135 0.038-0.201 0.006-0.033 0.012-0.066 0.019-0.099 0.005-0.033 0.009-0.066 0.014-0.099 0.018-0.131 0.036-0.259 0.053-0.384 0.009-0.062 0.017-0.124 0.026-0.185s0.012-0.122 0.018-0.182c0.011-0.119 0.021-0.236 0.031-0.349s0.021-0.222 0.023-0.329c0.001-0.017 0.001-0.033 0.002-0.049 1.101-0.005 1.992-0.898 1.992-2 0-0.017-0.001-0.034-0.001-0.051h0.001c0 0-0-0.003-0.001-0.007-0.003-0.121-0.017-0.24-0.041-0.355-0.006-0.055-0.013-0.114-0.021-0.181-0.007-0.059-0.014-0.122-0.022-0.19-0.012-0.080-0.024-0.165-0.037-0.255-0.027-0.18-0.056-0.379-0.091-0.592-0.042-0.201-0.088-0.419-0.136-0.652-0.022-0.116-0.055-0.235-0.087-0.357s-0.065-0.247-0.099-0.375c-0.018-0.064-0.032-0.129-0.053-0.194s-0.041-0.13-0.062-0.197-0.085-0.268-0.129-0.406c-0.011-0.034-0.022-0.069-0.033-0.104-0.013-0.034-0.025-0.069-0.038-0.104-0.026-0.069-0.052-0.139-0.078-0.21-0.053-0.141-0.107-0.284-0.162-0.429-0.061-0.143-0.124-0.288-0.188-0.435-0.032-0.073-0.064-0.147-0.096-0.222s-0.071-0.147-0.107-0.221c-0.073-0.147-0.146-0.297-0.221-0.448-0.077-0.15-0.163-0.297-0.245-0.448-0.042-0.075-0.084-0.15-0.126-0.226s-0.091-0.148-0.136-0.223c-0.092-0.148-0.185-0.298-0.279-0.448-0.395-0.59-0.834-1.174-1.319-1.727-0.491-0.549-1.023-1.070-1.584-1.55-0.568-0.473-1.165-0.903-1.773-1.285-0.613-0.376-1.239-0.697-1.855-0.973-0.156-0.064-0.311-0.127-0.465-0.19-0.077-0.030-0.152-0.063-0.229-0.091s-0.154-0.054-0.23-0.081-0.302-0.108-0.453-0.156c-0.151-0.045-0.3-0.089-0.447-0.133-0.074-0.021-0.146-0.045-0.219-0.063s-0.146-0.036-0.218-0.053c-0.144-0.035-0.286-0.069-0.425-0.103-0.141-0.027-0.279-0.054-0.415-0.080-0.068-0.013-0.135-0.026-0.201-0.038-0.033-0.006-0.066-0.012-0.099-0.019-0.033-0.005-0.066-0.009-0.099-0.014-0.131-0.018-0.259-0.036-0.384-0.053-0.062-0.009-0.124-0.017-0.185-0.026s-0.122-0.012-0.182-0.018c-0.119-0.010-0.236-0.021-0.349-0.031s-0.222-0.021-0.329-0.023c-0.027-0.001-0.052-0.002-0.078-0.003-0.020-1.087-0.907-1.962-1.999-1.962-0.017 0-0.034 0.001-0.051 0.001l-0-0.001c0 0-0.003 0-0.007 0.001-0.121 0.003-0.24 0.017-0.355 0.041-0.055 0.006-0.114 0.013-0.181 0.021-0.059 0.007-0.122 0.014-0.19 0.022-0.080 0.012-0.165 0.024-0.255 0.037-0.18 0.027-0.379 0.056-0.592 0.091-0.201 0.042-0.419 0.088-0.652 0.136-0.116 0.022-0.235 0.056-0.356 0.087s-0.247 0.065-0.375 0.099c-0.064 0.018-0.129 0.032-0.194 0.053s-0.13 0.041-0.197 0.062-0.268 0.085-0.406 0.129c-0.034 0.011-0.069 0.022-0.104 0.033-0.034 0.013-0.069 0.025-0.104 0.038-0.069 0.026-0.139 0.052-0.21 0.078-0.14 0.053-0.284 0.107-0.429 0.162-0.143 0.061-0.288 0.124-0.435 0.188-0.073 0.032-0.147 0.064-0.222 0.096s-0.147 0.071-0.221 0.107c-0.147 0.073-0.297 0.146-0.448 0.221-0.15 0.077-0.297 0.163-0.448 0.245-0.075 0.042-0.15 0.084-0.226 0.126s-0.148 0.091-0.223 0.136c-0.148 0.092-0.298 0.185-0.448 0.279-0.59 0.395-1.174 0.834-1.727 1.319-0.549 0.491-1.070 1.023-1.55 1.584-0.473 0.568-0.903 1.165-1.285 1.773-0.376 0.613-0.697 1.239-0.973 1.855-0.064 0.156-0.127 0.311-0.19 0.465-0.030 0.077-0.063 0.152-0.091 0.229s-0.054 0.154-0.081 0.23-0.108 0.302-0.156 0.453c-0.045 0.151-0.089 0.3-0.132 0.447-0.021 0.074-0.045 0.146-0.063 0.219s-0.036 0.146-0.053 0.218c-0.035 0.144-0.069 0.286-0.103 0.425-0.027 0.141-0.054 0.279-0.080 0.415-0.013 0.068-0.026 0.135-0.038 0.201-0.006 0.033-0.012 0.066-0.019 0.099-0.005 0.033-0.009 0.066-0.014 0.099-0.018 0.131-0.036 0.259-0.053 0.384-0.009 0.062-0.017 0.124-0.026 0.185s-0.012 0.122-0.018 0.182c-0.010 0.119-0.021 0.236-0.031 0.349s-0.021 0.222-0.023 0.329c-0.001 0.017-0.001 0.034-0.002 0.051-1.074 0.035-1.934 0.916-1.934 1.998 0 0.017 0.001 0.034 0.001 0.051zM2.297 14.022c0.001-0.006 0.003-0.012 0.004-0.018 0.020-0.101 0.051-0.204 0.080-0.311s0.059-0.215 0.090-0.327c0.016-0.056 0.029-0.113 0.048-0.169s0.038-0.113 0.057-0.171 0.077-0.233 0.117-0.353c0.010-0.030 0.020-0.060 0.030-0.090 0.012-0.030 0.023-0.060 0.035-0.090 0.023-0.060 0.047-0.121 0.071-0.182 0.047-0.122 0.096-0.246 0.145-0.373 0.055-0.124 0.111-0.25 0.168-0.377 0.028-0.064 0.057-0.128 0.086-0.192s0.064-0.127 0.095-0.191c0.065-0.128 0.13-0.257 0.197-0.388 0.069-0.129 0.145-0.257 0.219-0.387 0.037-0.065 0.074-0.13 0.112-0.195s0.081-0.128 0.121-0.193c0.082-0.128 0.164-0.257 0.247-0.388 0.351-0.509 0.739-1.012 1.167-1.489 0.434-0.472 0.901-0.919 1.394-1.33 0.499-0.404 1.021-0.77 1.552-1.094 0.535-0.319 1.081-0.589 1.617-0.821 0.136-0.053 0.271-0.106 0.404-0.158 0.067-0.025 0.132-0.053 0.199-0.076s0.134-0.045 0.2-0.067 0.262-0.090 0.392-0.129c0.131-0.037 0.26-0.073 0.387-0.109 0.064-0.017 0.126-0.037 0.189-0.052s0.126-0.029 0.189-0.043c0.124-0.028 0.247-0.056 0.367-0.084 0.121-0.021 0.241-0.043 0.358-0.063 0.058-0.010 0.116-0.021 0.173-0.031 0.029-0.005 0.057-0.010 0.085-0.015 0.029-0.003 0.057-0.007 0.085-0.010 0.113-0.014 0.223-0.028 0.331-0.041 0.054-0.007 0.107-0.013 0.159-0.020s0.105-0.008 0.157-0.013c0.103-0.007 0.203-0.015 0.3-0.022s0.191-0.016 0.283-0.016c0.183-0.004 0.354-0.008 0.512-0.012 0.146 0.005 0.28 0.010 0.401 0.014 0.060 0.002 0.116 0.003 0.17 0.005 0.066 0.004 0.128 0.008 0.186 0.012 0.067 0.004 0.127 0.008 0.182 0.012 0.102 0.016 0.206 0.024 0.312 0.024 0.015 0 0.029-0.001 0.044-0.001 0.004 0 0.007 0 0.007 0v-0.001c0.973-0.024 1.773-0.743 1.924-1.68 0.017 0.004 0.033 0.007 0.050 0.011 0.101 0.020 0.204 0.051 0.311 0.080s0.215 0.059 0.327 0.090c0.056 0.016 0.113 0.029 0.169 0.048s0.113 0.038 0.171 0.057 0.233 0.077 0.353 0.117c0.030 0.010 0.060 0.020 0.090 0.030 0.030 0.012 0.060 0.023 0.090 0.035 0.060 0.023 0.121 0.047 0.182 0.071 0.122 0.047 0.246 0.096 0.373 0.145 0.124 0.055 0.25 0.111 0.378 0.168 0.064 0.028 0.128 0.057 0.192 0.086s0.127 0.064 0.191 0.095c0.128 0.065 0.257 0.13 0.388 0.197 0.13 0.069 0.257 0.145 0.387 0.219 0.065 0.037 0.13 0.074 0.195 0.112s0.128 0.081 0.193 0.121c0.128 0.082 0.257 0.164 0.388 0.247 0.509 0.351 1.012 0.739 1.489 1.167 0.472 0.434 0.919 0.901 1.33 1.394 0.404 0.499 0.77 1.021 1.094 1.552 0.319 0.535 0.589 1.081 0.821 1.617 0.053 0.136 0.106 0.271 0.158 0.404 0.025 0.067 0.053 0.132 0.076 0.199s0.045 0.134 0.067 0.2 0.090 0.262 0.129 0.392c0.037 0.131 0.073 0.26 0.109 0.387 0.017 0.064 0.037 0.126 0.052 0.189s0.029 0.126 0.043 0.189c0.028 0.124 0.056 0.247 0.084 0.367 0.021 0.121 0.043 0.241 0.063 0.358 0.010 0.058 0.020 0.116 0.031 0.173 0.005 0.029 0.010 0.057 0.015 0.085 0.003 0.029 0.007 0.057 0.010 0.085 0.014 0.113 0.028 0.223 0.041 0.331 0.007 0.054 0.014 0.107 0.020 0.159s0.008 0.105 0.013 0.157c0.007 0.103 0.015 0.203 0.022 0.3s0.016 0.191 0.016 0.283c0.004 0.183 0.008 0.354 0.012 0.512-0.005 0.146-0.010 0.28-0.014 0.401-0.002 0.060-0.003 0.116-0.005 0.17-0.004 0.066-0.008 0.128-0.012 0.186-0.004 0.067-0.008 0.127-0.012 0.182-0.016 0.102-0.024 0.206-0.024 0.312 0 0.015 0.001 0.029 0.001 0.044-0 0.004-0 0.007-0 0.007h0.001c0.024 0.961 0.726 1.754 1.646 1.918-0.002 0.009-0.004 0.018-0.006 0.028-0.020 0.102-0.051 0.204-0.080 0.311s-0.059 0.215-0.090 0.327c-0.016 0.056-0.029 0.113-0.048 0.169s-0.038 0.113-0.057 0.171-0.077 0.233-0.117 0.353c-0.010 0.030-0.020 0.060-0.030 0.090-0.012 0.030-0.023 0.060-0.035 0.090-0.023 0.060-0.047 0.121-0.071 0.182-0.047 0.122-0.096 0.246-0.145 0.373-0.055 0.124-0.111 0.25-0.169 0.378-0.028 0.064-0.057 0.128-0.086 0.192s-0.064 0.127-0.095 0.191c-0.065 0.128-0.13 0.257-0.197 0.388-0.069 0.129-0.145 0.257-0.219 0.387-0.037 0.065-0.074 0.13-0.112 0.195s-0.081 0.128-0.121 0.193c-0.082 0.128-0.164 0.257-0.247 0.388-0.351 0.509-0.738 1.012-1.167 1.489-0.434 0.472-0.901 0.919-1.394 1.33-0.499 0.404-1.021 0.77-1.552 1.094-0.535 0.319-1.081 0.589-1.617 0.821-0.136 0.053-0.271 0.106-0.404 0.158-0.067 0.025-0.132 0.053-0.199 0.076s-0.134 0.045-0.2 0.067-0.262 0.090-0.392 0.129c-0.131 0.037-0.26 0.073-0.387 0.109-0.064 0.017-0.126 0.037-0.189 0.052s-0.126 0.029-0.189 0.043c-0.124 0.028-0.247 0.056-0.367 0.084-0.122 0.021-0.241 0.043-0.358 0.063-0.058 0.010-0.116 0.021-0.173 0.031-0.029 0.005-0.057 0.010-0.085 0.015-0.029 0.003-0.057 0.007-0.085 0.010-0.113 0.014-0.223 0.028-0.331 0.041-0.054 0.007-0.107 0.014-0.159 0.020s-0.105 0.008-0.157 0.013c-0.103 0.007-0.203 0.015-0.3 0.022s-0.191 0.016-0.283 0.016c-0.183 0.004-0.354 0.008-0.512 0.012-0.146-0.005-0.28-0.010-0.401-0.014-0.060-0.002-0.116-0.003-0.17-0.005-0.066-0.004-0.128-0.008-0.186-0.012-0.067-0.004-0.127-0.008-0.182-0.012-0.102-0.016-0.206-0.024-0.312-0.024-0.015 0-0.029 0.001-0.044 0.001-0.004-0-0.007-0-0.007-0v0.001c-0.969 0.024-1.766 0.737-1.921 1.668-0.1-0.020-0.201-0.050-0.306-0.079-0.106-0.029-0.215-0.059-0.327-0.090-0.056-0.016-0.113-0.029-0.169-0.048s-0.113-0.038-0.171-0.057-0.233-0.077-0.353-0.117c-0.030-0.010-0.060-0.020-0.090-0.030-0.030-0.012-0.060-0.023-0.090-0.035-0.060-0.023-0.121-0.047-0.182-0.071-0.122-0.048-0.246-0.096-0.373-0.145-0.124-0.055-0.25-0.111-0.377-0.168-0.064-0.028-0.128-0.057-0.192-0.086s-0.127-0.064-0.191-0.095c-0.128-0.065-0.257-0.13-0.388-0.197-0.13-0.069-0.257-0.145-0.387-0.219-0.065-0.037-0.13-0.074-0.195-0.112s-0.128-0.081-0.193-0.121c-0.128-0.082-0.257-0.164-0.388-0.247-0.509-0.351-1.012-0.738-1.489-1.166-0.472-0.434-0.919-0.901-1.33-1.394-0.404-0.499-0.77-1.021-1.094-1.552-0.319-0.535-0.589-1.081-0.821-1.617-0.053-0.136-0.106-0.271-0.158-0.404-0.025-0.067-0.053-0.132-0.076-0.199s-0.045-0.134-0.067-0.2-0.090-0.262-0.129-0.392c-0.037-0.131-0.073-0.26-0.109-0.387-0.017-0.064-0.037-0.126-0.052-0.189s-0.029-0.126-0.043-0.189c-0.028-0.124-0.056-0.247-0.084-0.367-0.021-0.121-0.043-0.241-0.063-0.358-0.010-0.058-0.021-0.116-0.031-0.173-0.005-0.029-0.010-0.057-0.015-0.085-0.003-0.029-0.007-0.057-0.010-0.085-0.014-0.113-0.028-0.223-0.041-0.331-0.007-0.054-0.013-0.107-0.020-0.159s-0.008-0.105-0.013-0.157c-0.007-0.103-0.015-0.203-0.022-0.3s-0.016-0.191-0.016-0.283c-0.004-0.183-0.008-0.354-0.012-0.512 0.005-0.146 0.010-0.28 0.014-0.401 0.002-0.060 0.003-0.116 0.005-0.17 0.004-0.066 0.008-0.128 0.012-0.186 0.004-0.067 0.008-0.127 0.012-0.182 0.015-0.102 0.024-0.206 0.024-0.312 0-0.015-0.001-0.029-0.001-0.044 0-0.004 0.001-0.007 0.001-0.007h-0.001c-0.024-0.981-0.754-1.786-1.701-1.927z", + Translate: "M16 0c-8.837 0-16 7.163-16 16s7.163 16 16 16 16-7.163 16-16-7.163-16-16-16zM16 30c-1.967 0-3.84-0.407-5.538-1.139l7.286-8.197c0.163-0.183 0.253-0.419 0.253-0.664v-3c0-0.552-0.448-1-1-1-3.531 0-7.256-3.671-7.293-3.707-0.188-0.188-0.442-0.293-0.707-0.293h-4c-0.552 0-1 0.448-1 1v6c0 0.379 0.214 0.725 0.553 0.894l3.447 1.724v5.871c-3.627-2.53-6-6.732-6-11.489 0-2.147 0.484-4.181 1.348-6h3.652c0.265 0 0.52-0.105 0.707-0.293l4-4c0.188-0.188 0.293-0.442 0.293-0.707v-2.419c1.268-0.377 2.61-0.581 4-0.581 2.2 0 4.281 0.508 6.134 1.412-0.13 0.109-0.256 0.224-0.376 0.345-1.133 1.133-1.757 2.64-1.757 4.243s0.624 3.109 1.757 4.243c1.139 1.139 2.663 1.758 4.239 1.758 0.099 0 0.198-0.002 0.297-0.007 0.432 1.619 1.211 5.833-0.263 11.635-0.014 0.055-0.022 0.109-0.026 0.163-2.541 2.596-6.084 4.208-10.004 4.208z", + SEO: "M16 2c8.837 0 16 7.163 16 16 0 6.025-3.331 11.271-8.25 14h-15.499c-4.92-2.729-8.25-7.975-8.25-14 0-8.837 7.163-16 16-16zM25.060 27.060c2.42-2.42 3.753-5.637 3.753-9.060h-2.813v-2h2.657c-0.219-1.406-0.668-2.755-1.33-4h-3.327v-2h2.009c-0.295-0.368-0.611-0.722-0.949-1.060-1.444-1.444-3.173-2.501-5.060-3.119v2.178h-2v-2.658c-0.656-0.102-1.324-0.155-2-0.155s-1.344 0.053-2 0.155v2.658h-2v-2.178c-1.887 0.617-3.615 1.674-5.060 3.119-0.338 0.338-0.654 0.692-0.949 1.060h2.009v2h-3.327c-0.662 1.245-1.111 2.594-1.33 4h2.657v2h-2.813c0 3.422 1.333 6.64 3.753 9.060 0.335 0.335 0.685 0.648 1.049 0.94h6.011l1.143-16h1.714l1.143 16h6.011c0.364-0.292 0.714-0.606 1.049-0.94z", + RAG: "M32 10l-16-8-16 8 16 8 16-8zM16 4.655l10.689 5.345-10.689 5.345-10.689-5.345 10.689-5.345zM28.795 14.398l3.205 1.602-16 8-16-8 3.205-1.602 12.795 6.398zM28.795 20.398l3.205 1.602-16 8-16-8 3.205-1.602 12.795 6.398z", + }, + aiservice: false, + tokenstats: 0, + useragreement: false, + loading: false, + item: data.item, + content: data.content, + urlinfo: data.urlinfo, + labels: data.labels, + settings: data.settings, + kixoteSettings: {}, + settingsSaved: false, + command: '', + } + }, + mounted() { + + eventBus.$on('kiExit', this.stopKixote); + + eventBus.$on('kiScrollBottom', this.scrollToBottom); + + eventBus.$on('switchLoading', this.switchLoading); + + eventBus.$on('updateKixoteSettings', this.updateKixoteSettings); + + eventBus.$on('storeKixoteSettings', this.storeKixoteSettings); + + eventBus.$on('agreetoservice', this.switchAgreement); + }, + watch: { + showKixote(newValue) { + if (newValue) { + this.loadKixoteSettings(); + this.loadContent(); + this.loadTokenStats(); + } + } + }, + computed: { + currentTabComponent: function () + { + return 'tab-' + this.currentTab.toLowerCase() + } + }, + methods: { + loadKixoteSettings() + { + self = this; + + tmaxios.get('/api/v1/kixotesettings',{ + params: { + 'url': data.urlinfo.route + } + }) + .then(function (response) + { + if (response.data.settings) + { + self.kixoteSettings = response.data.settings; + } + }) + .catch(function (error) + { + if(error.response) + { + } + }); + }, + loadContent() + { + self = this; + + tmaxios.get('/api/v1/article/content',{ + params: { + 'url': data.urlinfo.route, + 'draft': true + } + }) + .then(function (response) + { + if (response.data.content) + { + self.content = response.data.content; + } + }) + .catch(function (error) + { + if(error.response) + { + } + }); + }, + loadTokenStats() + { + self = this; + + tmaxios.get('/api/v1/tokenstats',{ + params: { + 'url': data.urlinfo.route, + } + }) + .then(function (response) + { + if (response.data) + { + self.aiservice = response.data.aiservice; + self.tokenstats = response.data.tokenstats; + self.useragreement = response.data.useragreement; + } + }) + .catch(function (error) + { + if(error.response) + { + console.info(response); + } + }); + }, + startKixote() + { + this.showKixote = true; + }, + stopKixote() + { + this.showKixote = false; + this.currentTab = 'Admin'; + this.content = ''; + }, + switchLoading() + { + this.loading = !this.loading; + }, + switchAgreement() + { + this.useragreement = true; + }, + selectTab(tab) + { + alert("Select Tab") + }, + scrollToBottom() + { + this.$nextTick(() => { + const displayRef = this.$refs.kdisplay; + displayRef.scrollTop = displayRef.scrollHeight; + }); + }, + updateKixoteSettings(newSettings) + { + this.settingsSaved = false; + this.kixoteSettings = { ...this.kixoteSettings, ...newSettings }; // ✅ Merge settings + }, + storeKixoteSettings() + { + self = this; + + tmaxios.put('/api/v1/kixotesettings',{ + 'url': data.urlinfo.route, + 'kixotesettings': this.kixoteSettings + }) + .then(function (response) + { + self.settingsSaved = true; + self.kixoteSettings = response.data.kixotesettings; + }) + .catch(function (error) + { + if(error.response) + { + self.kixoteSettings = error.response.data.kixotesettings; + } + }); + } + }, +}) + +kixote.component('tab-generate', { + props: ['content', 'item', 'labels', 'urlinfo', 'settings', 'kixoteSettings', 'settingsSaved', 'aiservice', 'useragreement', 'tokenstats'], + data: function () { + return { + tabs: [ + { value: 'article', name: 'Article' }, + { value: 'prompts', name: 'Prompts' }, +/* { value: 'tone', name: 'Tone' }, */ + ], + currentTab: 'article', + originalmd: '', + activeversion: 0, + versions: [], + prompt: '', + showFocusButton: false, + buttonPosition: { top: 0, left: 0 }, + selection: { start: 0, end: 0, text: '' }, + editPrompt: '', + addNewPrompt: false, + newPrompt: { + title: '', + content: '', + active: true, + system: false + }, + titleError: false, + bodyError: false, + currentFilter: 'all', + article: '', + index: '', + }; + }, + template: `
          + +
          +
          +

          Your AI Assistant for Typemill

          +

          + To get started with AI-powered assistance, go to System Settings, open the AI tab, and follow these steps: +

          +
            +
          1. Select an AI service.
          2. +
          3. Choose a model.
          4. +
          5. Enter your API key.
          6. +
          +

          Once set up, you can start using AI assistance right away!

          +
          +
          + +
          + +
          +
          + +
          +
          Activate {{aiservice}}
          + +
          + +
          +
          + +
          + +
            +
          • + {{ action.name }} +
          • +
          + +
          + + +
          + + + + + + +
          +
            +
          • + +
          • +
          +
          + +
          +
          +
          + + +
          +
          + Ki> + + + | + +
          +
          + + +
          +
            +
          • + +
          • +
          +
          + +
          + +
          +
          +
          + +
          + Filter: + + +
          + +
          + +
          +
          +
          + +
          + {{ titleError }} + + {{ bodyError }} + +
          +
          +
          +
          +
          +
          + +
          +
          + + + ✔ saved + + +
          +
          + + +
          +
          +
          + {{ prompttemplate.errors.title }} + + {{ prompttemplate.errors.body }} +
          +
          +
          +
          + +
          +

          Mimic your tone tab content here.

          +
          + +
          +
          + +
          +

          Content Generation only works on content pages. You are currently in the settings area.

          +
          + +
          `, + mounted: function() + { + this.initAutosize(); + + if(this.versions.length == 0) + { + this.initializeContent() + } + }, + computed: { + promptlistactive() + { + return Object.values(this.kixoteSettings?.promptlist || {}).filter(prompt => prompt.active); + }, + promptlistsystem() + { + return Object.fromEntries( + Object.entries(this.kixoteSettings?.promptlist || {}).filter(([key, prompt]) => prompt.system) + ); + }, + promptlistuser() + { + return Object.fromEntries( + Object.entries(this.kixoteSettings?.promptlist || {}).filter(([key, prompt]) => !prompt.system) + ); + }, + filteredPrompts() + { + if(this.currentFilter === 'system') + { + return this.promptlistsystem; + } + else if (this.currentFilter === 'user') + { + return this.promptlistuser; + } + else + { + return this.kixoteSettings.promptlist; + } + } + + }, + methods: { + initAutosize() + { + let kieditor = this.$refs["kieditor"]; + let prompteditor = this.$refs["prompteditor"]; + + if (kieditor) + { + autosize(kieditor); + } + if (prompteditor) + { + autosize(prompteditor); + } + }, + agreeTo(aiservice) + { + var self = this; + + tmaxios.post('/api/v1/agreetoaiservice',{ + 'aiservice': aiservice + }) + .then(function (response) + { + eventBus.$emit('agreetoservice'); + self.$nextTick(() => { + self.resizeAiEditor(); + }); + }) + }, + setCurrentTab(tabValue) + { + this.currentTab = tabValue; + + if(tabValue == 'article') + { + this.resizeAiEditor(); + } + }, + initializeContent() + { + let markdown = ''; + + for(block in this.content) + { + markdown += this.content[block].markdown + '\n\n'; + } + this.originalmd = markdown; + this.versions.push(markdown); + this.resizeAiEditor(); + }, + resizeAiEditor() + { + this.$nextTick(() => { + let kieditor = this.$refs["kieditor"]; + if (kieditor) + { + autosize.update(kieditor); + } + }); + }, + resizePromptEditor() + { + this.$nextTick(() => { + let prompteditor = this.$refs["prompteditor"]; + if (prompteditor) + { + autosize.update(prompteditor); + } + }); + }, + usePrompt(index) + { + this.prompt = this.promptlistactive[index].content; + this.resizePromptEditor(); + }, + switchVersion(index) + { + this.activeversion = index; + this.resizeAiEditor(); + }, + submitPrompt() + { + var self = this; + eventBus.$emit('switchLoading'); + + tmaxios.post('/api/v1/chatgpt',{ + 'prompt': this.prompt, + 'article': this.versions[this.activeversion] + }) + .then(function (response) + { + eventBus.$emit('switchLoading'); + if (response.data.message === 'Success') + { + let answer = response.data.data.choices[0].message.content; + answer = answer.replace(/<\/?focus>/g, ''); + self.versions.push(answer); + self.activeversion = self.versions.length-1; + self.prompt = ''; + self.resizePromptEditor(); + self.resizeAiEditor(); + } + }) + .catch(function (error) + { + eventBus.$emit('switchLoading'); + if(error.response) + { + self.disabled = false; + self.message = handleErrorMessage(error); + self.messageClass = 'bg-rose-500'; + self.licensemessage = error.response.data.message; + if(error.response.data.errors !== undefined) + { + self.errors = error.response.data.errors; + } + } + }); + }, + handleKeydown(event) + { + if (event.key === 'Enter' && !event.shiftKey) + { + event.preventDefault(); + this.submitPrompt(); + } + else if (event.key === 'Enter' && event.shiftKey) + { + // Allow line break + const textarea = event.target; + const start = textarea.selectionStart; + const end = textarea.selectionEnd; + textarea.value = textarea.value.slice(0, start) + '\n' + textarea.value.slice(end); + textarea.selectionStart = textarea.selectionEnd = start + 1; + + let prompteditor = this.$refs["prompteditor"]; + autosize.update(prompteditor); + + event.preventDefault(); + } + }, + detectSelection(event) + { + const textarea = this.$refs.kieditor; + const start = textarea.selectionStart; + const end = textarea.selectionEnd; + const text = textarea.value.substring(start, end); + + if (text.length > 0) + { + this.selection = { start, end, text }; + this.showFocusButton = true; + this.buttonPosition = { + top: event.offsetY-20, + left: event.offsetX, + }; + } + else + { + this.showFocusButton = false; + } + }, + wrapInFocus() + { + const textarea = this.$refs.kieditor; + const { start, end, text } = this.selection; + + if (!text) return; + + const newText = + textarea.value.substring(0, start) + + `${text}` + + textarea.value.substring(end); + + this.versions[this.activeversion] = newText; + this.showFocusButton = false; + }, + storeArticle(append = false) + { + var self = this; + + var content = this.versions[this.activeversion]; + var title = 'Title missing'; + + var regex = /^#(?!#)([^#\n]+)/m; + var match = content.match(regex); + if (match) + { + var title = '# ' + match[1]; + var content = content.replace(regex, ''); + } + + tmaxios.put('/api/v1/draft',{ + 'url': data.urlinfo.route, + 'item_id': this.item.keyPath, + 'title': title.trim(), + 'body': content.trim() + }) + .then(function (response) + { + location.reload(); + }) + .catch(function (error) + { + if(error.response) + { + let message = handleErrorMessage(error); + if(message) + { + eventBus.$emit('publishermessage', message); + } + } + }); + }, + slugify(text) + { + return text + .toLowerCase() // Convert to lowercase + .replace(/[^a-z0-9]/g, '-') // Replace non-alphanumeric characters with '-' + .replace(/-+/g, '-') // Remove multiple dashes + .trim(); // Trim leading/trailing dashes + }, + updateSettings(newSettings) + { + eventBus.$emit('updateKixoteSettings', newSettings); // Emit event + }, + validateTitle(title) + { + const titleRegex = /^[a-zA-Z0-9 ]{0,20}$/; + if (!titleRegex.test(title)) + { + this.titleError = "Title can only contain letters, numbers, and spaces (max 20 chars)."; + } + else + { + this.titleError = false; + } + }, + validateBody(body) + { + const bodyRegex = /<\/?[^>]+(>|$)/g; + if (bodyRegex.test(body)) + { + this.bodyError = "HTML and script tags are not allowed."; + } + else + { + this.bodyError = false; + } + }, + savePrompts() + { + if (!this.titleError && !this.bodyError) + { + eventBus.$emit('storeKixoteSettings'); + } + }, + updatePrompt(kixoteSettings, promptname) + { + if(kixoteSettings.promptlist[promptname] != undefined) + { + kixoteSettings.promptlist[promptname].errors = {}; + + this.validateTitle(kixoteSettings.promptlist[promptname].title); + kixoteSettings.promptlist[promptname].errors.title = this.titleError; + + this.validateBody(kixoteSettings.promptlist[promptname].content); + kixoteSettings.promptlist[promptname].errors.body = this.bodyError; + + this.updateSettings(kixoteSettings); + } + }, + deletePrompt(name) + { + var promptlist = this.kixoteSettings.promptlist; + + delete promptlist[name]; + + this.updateSettings(promptlist); + + eventBus.$emit('storeKixoteSettings'); + + }, + saveNewPrompt() + { + if (this.titleError || this.bodyError) + { + return false; + } + + var promptlist = this.kixoteSettings.promptlist; + var promptkey = this.slugify(this.newPrompt.title); + promptlist[promptkey] = { + title: this.newPrompt.title, + content: this.newPrompt.content, + active: this.newPrompt.active, + system: this.newPrompt.system + }; + + this.newPrompt = { + title: '', + content: '', + active: true, + system: false + }; + + this.addNewPrompt = false; + this.updateSettings(promptlist); + eventBus.$emit('storeKixoteSettings'); + }, + exit() + { + eventBus.$emit('kiExit'); + }, + } +}) + +// publish tree +// unpublish tree +// load page +// save page +// translate page +// translate tree + + +const kixoteCommands = [ + { + name: 'help', + description: 'List all available commands with a short description.', + method: function() + { + let result = ['
            ']; + kixoteCommands.forEach((command) => + { + let block = '
          • ' + command.name + ': ' + command.description + '
          • '; + result.push(block); + }) + result.push('
          '); + + eventBus.$emit('answer', result); + }, + answer: '

          You can use the following commands:

          ', + }, + { + name: 'exit', + description: 'Exit Kixote and close the Kixote window.', + }, + { + name: 'clear navigation', + description: 'Clear the cached navigation.', + method: function() + { + var self = this; + + tmaxios.delete('/api/v1/clearnavigation',{ + }) + .then(function (response) + { + eventBus.$emit('answer', ['navigation has been cleared']); + }) + .catch(function (error) + { + eventBus.$emit('answer', getKixoteError(error)); + }); + }, + answer: ['Asking server ...'], + }, + { + name: 'clear cache', + description: 'Clear the cache-folder and delete cached files.', + method: function() + { + var self = this; + + tmaxios.delete('/api/v1/cache',{ + }) + .then(function (response) + { + eventBus.$emit('answer', ['cache has been cleared']); + }) + .catch(function (error) + { + eventBus.$emit('answer', getKixoteError(error)); + }); + }, + answer: ['Asking server ...'], + }, + { + name: 'show security log', + description: 'Show the security log that you can activate in the security tab of the system settings.', + method: function() + { + var self = this; + + tmaxios.get('/api/v1/securitylog',{ + }) + .then(function (response) + { + eventBus.$emit('answer', response.data.lines); + eventBus.$emit('nextCommands', ['clear security log']); + }) + .catch(function (error) + { + eventBus.$emit('answer', getKixoteError(error)); + }); + }, + answer: ['Asking server ...'], + }, + { + name: 'clear security log', + description: 'Clear the security log.', + method: function() + { + var self = this; + + tmaxios.delete('/api/v1/securitylog',{ + }) + .then(function (response) + { + eventBus.$emit('answer', ['Security log has been cleared.']); + }) + .catch(function (error) + { + eventBus.$emit('answer', getKixoteError(error)); + }); + }, + answer: ['Asking server ...'], + }, +/* + { + name: 'skip', + description: 'Skip the current task and start a new command.', + answer: ['We skipped the current task. Waiting for your next command.'], + }, + { + name: 'create content', + description: 'Create content with artificial intelligence.', + params: [ + { + name: 'topic', + value: false, + question: 'Please describe a topic in few words:', + required: true, + regex: false, + }, + { + name: 'length', + value: false, + question: 'How many words should the text have?', + required: true, + regex: false, + }, + ], + method: function(params) + { + eventBus.$emit('storable', ['Lorem ipsum in markdown.']); + eventBus.$emit('nextCommands', ['transform', 'translate', 'save to page']); + eventBus.$emit('answer', ['This is the answer from the server. The server can ask an AI service with the collected parameters and return any kind of answer in HTML and preferably in markdown, so that typemill can process the content again (e.g. store, translate, and more).']); + }, + answer: ['Creating content...'], + }, + { + name: 'save to page', + description: 'Save markdown to current page.', + method: function(params) + { + console.info(params[0]); + eventBus.$emit('answer', ['saved content to page']); + }, + answer: ['Save content...'], + }, +*/ + ]; + + + +kixote.component('tab-admin', { + props: [], + data: function () { + return { + messenger: [], + messengerIndex: false, + command: '', + params: false, + } + }, + template: `
          +
          +

          Hello, I am Kixote from Typemill. How can I help?

          +
          +
          +
          +
          +
          +
          +
          + +
          +
          +
          +
          +
          +
          +

          + Ki> + + +

          +
          +
          +

          Enter "help" to see a list of commands

          +
          +
          +
          `, + mounted: function() + { +// this.clear(); + + eventBus.$on('answer', messages => { + let lastKey = this.messenger.length - 1; + messages.forEach((message) => + { + this.messenger[lastKey].answer.push(message); + }); + }); + + eventBus.$on('nextCommands', nextcommands => { + let lastKey = this.messenger.length - 1; + nextcommands.forEach((nextcommand) => + { + this.messenger[lastKey].nextCommands.push(nextcommand); + }); + }); + + eventBus.$on('storable', data => { + let lastKey = this.messenger.length - 1; + this.messenger[lastKey].storable = data; + }); + + this.focusOnInput(); + }, + methods: { + exit() + { + eventBus.$emit('kiExit'); + }, + clear() + { + this.messenger = []; + this.params = false; + this.command = ''; + }, + focusOnInput() + { + this.$nextTick(() => { + const inputRef = this.$refs.kinput; + inputRef.focus(); + }); + }, + finishCommand() + { + this.command = ''; + this.focusOnInput(); + eventBus.$emit('kiScrollBottom'); + }, + submitInlineCommand(command, index) + { + this.command = command; + this.messengerIndex = index; + // should we submit this.messenger[index].storable as params? + let storable = this.messenger[index].storable; + this.submitCommand(false, storable); + }, + submitCommand(event, params = false) + { + if(this.command.trim() == '') + { + return; + } + + let currentCommand = 'Ki> ' + this.command; + + let message = { 'command' : currentCommand, 'answer' : [], 'storable' : false, 'nextCommands' : [] } + + if(this.command == 'exit') + { + this.exit(); + + return; + } + + if(this.command == 'skip') + { + message.answer.push('We skipped the current task. Start with a new command.'); + + this.messenger.push(message); + + this.params = false; + + this.finishCommand(); + + return; + } + + if(this.params) + { + let question = this.getNextQuestion(this.params); + + if(question) + { + message.answer.push(question); + + this.messenger.push(message); + + this.finishCommand(); + + return; + } + + // if no further question submit inital command with params + let params = this.params; + + this.params = false; + + this.command = params[0].value; + + this.submitCommand(false, params); + + return; + } + + let commandObject = this.getCommandObject(this.command); + + if(!commandObject) + { + message.answer.push('Command not found. Type "help" to see a list of available commands.'); + + this.messenger.push(message); + + this.finishCommand(); + + return; + } + + if(params) + { + message.answer.push('Working ...'); + + this.messenger.push(message); + + commandObject.method(params); + + this.finishCommand(); + + return; + } + + let initialParams = this.getCommandParams(commandObject); + + if(initialParams) + { + this.params = initialParams; + + let question = this.getFirstQuestion(initialParams); + + if(question) + { + message.answer.push(question); + + this.messenger.push(message); + + this.finishCommand(); + + return; + } + + console.info("no questions found"); + } + + if(commandObject.answer) + { + message.answer.push(commandObject.answer); + } + + this.messenger.push(message); + + commandObject.method(); + + this.finishCommand(); + }, + getCommandObject(command) + { + let result = false; + + kixoteCommands.forEach((commandObject) => + { + if(commandObject.name == command) + { + result = commandObject; + } + }); + + return result; + }, + getCommandParams(commandObject) + { + if(commandObject.params) + { + let params = [ + { + name: 'submitWithCommand', + value: commandObject.name + } + ]; + + commandObject.params.forEach((param) => + { + param.value = false; + params.push(param); + }); + + return params; + } + + return false; + }, + getFirstQuestion(params) + { + if(typeof params[1].question != "undefined") + { + return params[1].question; + } + + return false; + }, + getNextQuestion(params) + { + let length = params.length; + + for (var index = 0; index < length; index++) + { + if(!params[index].value) + { + // set param if valid + this.params[index].value = this.command; + + // go to the next param if exists + let next = index + 1; + if(typeof params[next] != "undefined") + { + return params[next].question; + } + } + } + + return false; + } + } +}) + + +kixote.component('tab-translate', { + props: [], + data: function () { + return { + } + }, + template: `
          +

          Translation Component

          +
          `, + mounted: function() + { + }, + methods: { + selectComponent: function(type) + { + } + } +}) + +kixote.component('tab-automate', { + props: [], + data: function () { + return { + } + }, + template: `
          +

          Automation Component

          +
          `, + mounted: function() + { + }, + methods: { + selectComponent: function(type) + { + } + } +}) + +kixote.component('tab-seo', { + props: [], + data: function () { + return { + } + }, + template: `
          +

          SEO Component

          +
          `, + mounted: function() + { + }, + methods: { + selectComponent: function(type) + { + } + } +}) + +kixote.component('tab-rag', { + props: [], + data: function () { + return { + } + }, + template: `
          +

          Retrieval Augmented Generation

          +
          `, + mounted: function() + { + }, + methods: { + selectComponent: function(type) + { + } + } +}) + +kixote.component('tab-token', { + props: [], + data: function () { + return { + } + }, + template: `
          +

          Token Overview Component

          +
          `, + mounted: function() + { + }, + methods: { + selectComponent: function(type) + { + } + } +}) \ No newline at end of file diff --git a/system/typemill/author/js/vue-license.js b/system/typemill/author/js/vue-license.js new file mode 100644 index 0000000..9294924 --- /dev/null +++ b/system/typemill/author/js/vue-license.js @@ -0,0 +1,165 @@ +const app = Vue.createApp({ + template: ` +
          +
          +

          {{ licensemessage }}

          +

          Congratulations! Your license is active and you can enjoy all features until you cancel your subscription. To manage your subscription, visit the Paddle Customer Portal. Simply enter the email associated with your license to receive a temporary login link.

          +
          +
          +
          +
          +

          MAKER License

          +

          22 € + VAT/Year. Ideal for personal projects and side hustles.

          +
            +
          • Access to all MAKER-level products.
          • +
          • Valid for one domain.
          • +
          • Annual subscription, cancel anytime.
          • +
          +
          +
          +

          BUSINESS License

          +

          122 € + VAT/Year. Designed for small to medium businesses.

          +
            +
          • Includes all MAKER benefits plus BUSINESS-exclusive products.
          • +
          • Valid for one domain.
          • +
          • Annual subscription, cancel anytime.
          • +
          +
          +
          +
          +

          License-key:

          +

          {{ licenseData.license }}

          +

          Domain:

          +

          {{ licenseData.domain }}

          +

          E-Mail:

          +

          {{ licenseData.email }}

          +

          Payed until:

          +

          {{ licenseData.payed_until }}

          +
          +
          +

          The subscription extends automatically for 12 month every time until you cancel your subscription. For testing, you can also use the domains 'localhost', '127.0.0.1', and the subdomain 'typemilltest.'.

          +
          +
          + +
          +

          Activate your Typemill-License below and enjoy a flatrate-subscription for plugins, themes, and services. You do not have a License yet? Read all about it on the Typemill website.

          +
          + +
          +
          + {{ fieldDefinition.legend }} + + +
          + + +
          +
          +
          {{ message }}
          + +
          +
          +
          `, + data() { + return { + licenseData: data.licensedata, + formDefinitions: data.licensefields, + licensemessage: data.message, + licensefound: data.licensefound, + formData: {}, + message: '', + messageClass: '', + errors: {}, + disabled: false, + src: data.urlinfo.baseurl + "/system/typemill/author/img/typemill-icon.png" + } + }, + mounted() { + eventBus.$on('forminput', formdata => { + this.formData[formdata.name] = formdata.value; + }); + + /* test if the license server is reachable and all settings are ok */ + this.disabled = true; + var self = this; + + tmaxios.post('/api/v1/licensetestcall',{ + 'license': 'test' + }) + .then(function (response) + { + self.disabled = false; + }) + .catch(function (error) + { + if(error.response) + { + self.disabled = false; + self.message = handleErrorMessage(error); + self.messageClass = 'bg-rose-500'; + self.licensemessage = error.response.data.message; + if(error.response.data.errors !== undefined) + { + self.errors = error.response.data.errors; + } + } + }); + }, + methods: { + selectComponent: function(type) + { + return 'component-'+type; + }, + save: function() + { + this.reset(); + this.disabled = true; + var self = this; + + tmaxios.post('/api/v1/license',{ + 'license': this.formData + }) + .then(function (response) + { + self.disabled = false; + self.messageClass = 'bg-teal-500'; + self.message = response.data.message; + self.licenseData = response.data.licensedata; + }) + .catch(function (error) + { + if(error.response) + { + self.disabled = false; + self.message = handleErrorMessage(error); + self.messageClass = 'bg-rose-500'; + if(error.response.data.errors !== undefined) + { + self.errors = error.response.data.errors; + } + } + }); + }, + reset: function() + { + this.errors = {}; + this.message = ''; + this.messageClass = ''; + this.disabled = false; + } + }, +}) \ No newline at end of file diff --git a/system/typemill/author/js/vue-medialib.js b/system/typemill/author/js/vue-medialib.js new file mode 100644 index 0000000..c49c2c0 --- /dev/null +++ b/system/typemill/author/js/vue-medialib.js @@ -0,0 +1,618 @@ +const medialib = { + props: ['parentcomponent'], + template: `
          +
          +
          +
          +
          + +
          + + + +
          +
          +
          +
          +

          Images

          +
          + + +
          +
          +
          +

          Files

          +
          + + +
          +
          + +
          +

          Pagination

          +
            +
          • + +
          • +
          +
          + +
          +
          +
          {{errors}}
          +
          + +
          + + + + + click to select + + +
          +
          {{ image.name }}
          +
          + + +
          +
          +
          +
          +
          + +
          +
          +
          +
          + +
          +
          +
          +
          Name
          +
          {{ imagedetaildata.name}}
          +
          URL
          +
          {{ getImageUrl(imagedetaildata.src_live)}}
          +
          +
          +
          Size
          +
          {{ getSize(imagedetaildata.bytes) }}
          +
          +
          +
          Dimensions
          +
          {{ imagedetaildata.width }}x{{ imagedetaildata.height }} px
          +
          +
          +
          Type
          +
          {{ imagedetaildata.type }}
          +
          +
          +
          Date
          +
          {{ getDate(imagedetaildata.timestamp) }}
          +
          +
          +
          + + +
          +
          + +
          +
          +
          +
          + +
          + +
          {{ file.info.extension }}
          + + + + click to select + +
          +
          +
          {{ file.name }}
          +
          + + +
          +
          +
          +
          +
          + +
          +
          +
          +
          +
          {{ filedetaildata.info.extension }}
          +
          +
          +
          +
          Name
          +
          {{ filedetaildata.name}}
          +
          URL
          +
          {{ filedetaildata.url }}
          +
          +
          +
          Size
          +
          {{ getSize(filedetaildata.bytes) }}
          +
          +
          +
          Type
          +
          {{ filedetaildata.info.extension }}
          +
          +
          +
          Date
          +
          {{ getDate(filedetaildata.timestamp) }}
          +
          +
          +
          + + +
          +
          + +
          +
          +
          +
          +
          +
          `, + data: function(){ + return { + active: false, + imagedata: false, + pagemedia: false, + showimages: true, + imagedetaildata: false, + showimagedetails: false, + filedata: false, + showfiles: false, + filedetaildata: false, + showfiledetails: false, + detailindex: false, + load: false, + adminurl: false, + baseurl: data.urlinfo.baseurl, + search: '', + errors: false, + currentPage: 1, + totalPages: 0, + itemsPerPage: 15, + } + }, + mounted: function(){ + + this.errors = false; + + var self = this; + + var itempath = false; + if(typeof data.item !== "undefined") + { + itempath = data.item.pathWithoutType; + } + + tmaxios.get('/api/v1/pagemedia',{ + params: { + 'url': data.urlinfo.route, + 'path': itempath + } + }) + .then(function (response) + { + self.pagemedia = response.data.pagemedia; + }) + .catch(function (error) + { + if(error.response) + { + self.errors = error.response.data.errors; + } + }); + + if(this.parentcomponent == 'files') + { + this.showFiles(); +/* this.active = 'pageFiles'; */ + } + if(this.parentcomponent == 'images') + { + this.showImages(); +/* this.active = 'pageImages'; */ + } + }, + computed: { + filteredImages() { + + var searchimages = this.search; + var filteredImages = {}; + var images = this.imagedata; + var pagemedia = this.pagemedia; + var active = this.active; + + if(images) + { + if(active == 'pageImages') + { + Object.keys(images).forEach(function(key) { + var imagename = images[key].name; + if(pagemedia.indexOf(imagename) !== -1) + { + filteredImages[key] = images[key]; + } + }); + } + else if(searchimages != '') + { + Object.keys(images).forEach(function(key) { + var searchindex = key + ' ' + images[key].name; + if(searchindex.toLowerCase().indexOf(searchimages.toLowerCase()) !== -1) + { + filteredImages[key] = images[key]; + } + }); + } + else + { + const startIndex = (this.currentPage - 1) * this.itemsPerPage; + const endIndex = this.currentPage * this.itemsPerPage; + filteredImages = this.imagedata.slice(startIndex, endIndex); + } + } + + return filteredImages; + }, + filteredFiles() { + + var searchfiles = this.search; + var filteredFiles = {}; + var files = this.filedata; + var pagemedia = this.pagemedia; + var active = this.active; + + if(files) + { + if(active == 'pageFiles') + { + Object.keys(files).forEach(function(key) { + var filename = files[key].name; + if(pagemedia.indexOf(filename) !== -1) + { + filteredFiles[key] = files[key]; + } + }); + } + else + { + Object.keys(files).forEach(function(key) { + var searchindex = key + ' ' + files[key].name; + if(searchindex.toLowerCase().indexOf(searchfiles.toLowerCase()) !== -1) + { + filteredFiles[key] = files[key]; + } + }); + } + } + return filteredFiles; + }, + }, + methods: { + setCurrentImages() + { + const startIndex = (this.currentPage - 1) * this.itemsPerPage; + const endIndex = this.currentPage * this.itemsPerPage; + this.currentImages = this.imagedata.slice(startIndex, endIndex); + }, + calculateTotalPages() + { + this.totalPages = Math.ceil(this.imagedata.length / this.itemsPerPage); + }, + goToPage(num) + { + this.currentPage = num; + }, + isActive(activestring) + { + if(this.active == activestring) + { + return 'bg-stone-700 dark:bg-stone-900 text-stone-50'; + } + return 'bg-stone-200 dark:bg-stone-600'; + }, + getBackgroundImage(image) + { + return 'background-image: url(' + this.baseurl + '/' + image.src_thumb + ');width:250px'; + }, + getImageUrl(relativeUrl) + { + return this.baseurl + '/' + relativeUrl; + }, + showImages(pagesOrAll) + { + this.active = pagesOrAll; + this.errors = false; + this.showimages = true; + this.showfiles = false; + this.showimagedetails = false; + this.showfiledetails = false; + this.imagedetaildata = false; + this.detailindex = false; + + if(!this.imagedata) + { + this.errors = false; + + var imageself = this; + + var itempath = false; + if(typeof data.item !== "undefined") + { + itempath = data.item.pathWithoutType; + } + tmaxios.get('/api/v1/images',{ + params: { + 'url': data.urlinfo.route, + 'path': itempath, + } + }) + .then(function (response) + { + imageself.imagedata = response.data.images; + imageself.calculateTotalPages(); + imageself.setCurrentImages(); + }) + .catch(function (error) + { + if(error.response) + { + let message = handleErrorMessage(error); + if(message) + { + eventBus.$emit('publishermessage', message); + } + + imageself.errors = error.response.data.errors; + + } + }); + } + }, + showFiles(pagesOrAll) + { + this.active = pagesOrAll; + this.showimages = false; + this.showfiles = true; + this.showimagedetails = false; + this.showfiledetails = false; + this.imagedetaildata = false; + this.filedetaildata = false; + this.detailindex = false; + + if(!this.filedata) + { + this.errors = false; + var filesself = this; + + tmaxios.get('/api/v1/files',{ + params: { + 'url': data.urlinfo.route, + } + }) + .then(function (response) + { + filesself.filedata = response.data.files; + }) + .catch(function (error) + { + if(error.response) + { + let message = handleErrorMessage(error); + if(message) + { + eventBus.$emit('publishermessage', message); + } + + fileself.errors = error.response.data.errors; + } + }); + } + }, + showImageDetails(image,index) + { + this.errors = false; + this.showimages = false; + this.showfiles = false; + this.showimagedetails = true; + this.showfiledetails = false; + this.detailindex = index; + this.adminurl = this.baseurl + '/tm/content/visual'; + + var imageself = this; + + tmaxios.get('/api/v1/image',{ + params: { + 'url': data.urlinfo.route, + 'name': image.name, + } + }) + .then(function (response) + { + imageself.imagedetaildata = response.data.image; + }) + .catch(function (error) + { + if(error.response) + { + let message = handleErrorMessage(error); + if(message) + { + eventBus.$emit('publishermessage', message); + } + + imageself.errors = error.response.data.errors; + + } + }); + }, + showFileDetails(file,index) + { + this.errors = false; + this.showimages = false; + this.showfiles = false; + this.showimagedetails = false; + this.showfiledetails = true; + this.filedetaildata = file; + this.detailindex = index; + this.adminurl = this.baseurl + '/tm/content/visual'; + }, + selectImage(image) + { + this.$emit('addFromMedialibEvent', image.src_live); + }, + selectFile(file) + { + let extension = file.info.extension.toUpperCase(); + let size = this.getSize(file.bytes); + file.name = file.name + ' (' + extension + ', ' + size + ')'; + + this.$emit('addFromMedialibEvent', file); + }, + removeImage(index) + { + this.imagedata.splice(index,1); + }, + removeFile(index) + { + this.filedata.splice(index,1); + }, + deleteImage(image, index) + { + imageself = this; + + tmaxios.delete('/api/v1/image',{ + data: { + 'url': data.urlinfo.route, + 'name': image.name, + 'index': index, + } + }) + .then(function (response) + { + imageself.showImages(); + imageself.removeImage(index); + }) + .catch(function (error) + { + if(error.response) + { + let message = handleErrorMessage(error); + if(message) + { + eventBus.$emit('publishermessage', message); + } + + imageself.errors = error.response.data.errors; + + } + }); + }, + deleteFile(file, index) + { + fileself = this; + + tmaxios.delete('/api/v1/file',{ + data: { + 'url': data.urlinfo.route, + 'name': file.name, + 'index': index, + } + }) + .then(function (response) + { + fileself.showFiles(); + fileself.removeFile(index); + }) + .catch(function (error) + { + if(error.response) + { + let message = handleErrorMessage(error); + if(message) + { + eventBus.$emit('publishermessage', message); + } + + fileself.errors = error.response.data.errors; + + } + }); + }, + getDate(timestamp) + { + date = new Date(timestamp * 1000); + + datevalues = { + 'year': date.getFullYear(), + 'month': date.getMonth()+1, + 'day': date.getDate(), + 'hour': date.getHours(), + 'minute': date.getMinutes(), + 'second': date.getSeconds(), + }; + return datevalues.year + '-' + datevalues.month + '-' + datevalues.day; + }, + getSize(bytes) + { + var i = Math.floor(Math.log(bytes) / Math.log(1024)), + sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + + return (bytes / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + sizes[i]; + }, + isChecked(classname) + { + if(this.imgclass == classname) + { + return ' checked'; + } + }, + }, +} \ No newline at end of file diff --git a/system/typemill/author/js/vue-meta.js b/system/typemill/author/js/vue-meta.js new file mode 100644 index 0000000..0a08881 --- /dev/null +++ b/system/typemill/author/js/vue-meta.js @@ -0,0 +1,312 @@ +const app = Vue.createApp({ + template: `
          + + + + + + +
          `, + data: function () { + return { + item: data.item, + currentTab: 'Content', + tabs: ['Content'], + formDefinitions: [], + formData: [], + formErrors: {}, + formErrorsReset: {}, + message: false, + messageClass: false, + css: "lg:px-16 px-8 lg:py-16 py-8 bg-stone-50 shadow-md mb-16", + saved: false, + } + }, + computed: { + currentTabComponent: function () + { + if(this.currentTab == 'Content') + { + eventBus.$emit("showEditor", true); + } + else + { + eventBus.$emit("showEditor", false); + return 'tab-' + this.currentTab.toLowerCase() + } + } + }, + mounted: function(){ + + var self = this; + + tmaxios.get('/api/v1/meta',{ + params: { + 'url': data.urlinfo.route, + } + }) + .then(function (response){ + + var formdefinitions = response.data.metadefinitions; + + for (var key in formdefinitions) + { + if (formdefinitions.hasOwnProperty(key)) + { + self.tabs.push(key); + self.formErrors[key] = false; + } + } + + self.formErrorsReset = self.formErrors; + self.formDefinitions = formdefinitions; + + self.formData = response.data.metadata; + +/* + self.userroles = response.data.userroles; + self.item = response.data.item; + if(self.item.elementType == "folder" && self.item.contains == "posts") + { + posts.posts = self.item.folderContent; + posts.folderid = self.item.keyPath; + } + else + { + posts.posts = false; + } +*/ + }) + .catch(function (error) + { + if(error.response) + { + let message = handleErrorMessage(error); + if(message) + { + eventBus.$emit('publishermessage', message); + } + } + }); + + eventBus.$on('forminput', formdata => { + this.formData[this.currentTab][formdata.name] = formdata.value; + }); + + eventBus.$on('meta', metadata => { + this.formData.meta = metadata.meta; + }); + }, + methods: { + saveForm: function() + { + this.saved = false; + self.message = false; + self.messageClass = 'bg-stone-50'; + + self = this; + tmaxios.post('/api/v1/meta',{ + 'url': data.urlinfo.route, + 'tab': self.currentTab, + 'data': self.formData[self.currentTab] + }) + .then(function (response){ + + self.saved = true; + self.message = 'saved successfully'; + self.messageClass = 'bg-teal-500'; + self.formErrors = self.formErrorsReset; + + if(response.data.navigation) + { + eventBus.$emit('navigation', response.data.navigation); + } + if(response.data.item) + { + eventBus.$emit('item', response.data.item); + } + }) + .catch(function (error) + { + if(error.response) + { + self.messageClass = 'bg-rose-500'; + self.message = 'please correct your input.'; + + if(typeof error.response.data.message != "undefined") + { + self.message = error.response.data.message; + } + if(typeof error.response.data.errors != "undefined") + { + self.formErrors = error.response.data.errors; + } + } + }); + }, + } +}); + +app.component('tab-meta', { + props: ['item', 'formData', 'formDefinitions', 'saved', 'errors', 'message', 'messageClass'], + data: function () { + return { + slug: false, + originalSlug: false, + slugerror: false, + disabled: true, + } + }, + template: `
          +
          +
          +
          + +
          + + +
          +
          {{ slugerror }}
          +
          +
          +
          +
          + {{ fieldDefinition.legend }} + + +
          + + +
          +
          +
          + +
          {{ $filters.translate(message) }}
          +
          +
          + +
          +
          +
          `, + mounted: function() + { + if(this.item.slug != '') + + { + this.slug = this.item.slug; + this.originalSlug = this.item.slug; + } + }, + methods: { + selectComponent: function(type) + { + return 'component-' + type; + }, + saveInput: function() + { + this.$emit('saveform'); + }, + changeSlug: function() + { + if(this.slug == this.originalSlug) + { + this.slugerror = false; + this.disabled = true; + return; + } + if(this.slug == '') + { + this.slugerror = 'empty slugs are not allowed'; + this.disabled = true; + return; + } + + this.slug = this.slug.replace(/ /g, '-'); + this.slug = this.slug.toLowerCase(); + + if(this.slug.match(/^[a-z0-9\-]*$/)) + { + this.slugerror = false; + this.disabled = false; + } + else + { + this.slugerror = 'Only lowercase a-z and 0-9 and "-" is allowed for slugs.'; + this.disabled = true; + } + }, + storeSlug: function() + { + if(this.slug.match(/^[a-z0-9\-]*$/) && this.slug != this.originalSlug) + { + var self = this; + + tmaxios.post('/api/v1/article/rename',{ + 'url': data.urlinfo.route, + 'slug': this.slug, + 'oldslug': this.originalSlug, + }) + .then(function (response) + { + window.location.replace(response.data.url); + }) + .catch(function (error) + { + if(error.response) + { + let message = handleErrorMessage(error); + + if(message) + { + eventBus.$emit('publishermessage', message); + } + } + }); + } + } + } +}) \ No newline at end of file diff --git a/system/typemill/author/js/vue-plugins.js b/system/typemill/author/js/vue-plugins.js new file mode 100644 index 0000000..5f302d8 --- /dev/null +++ b/system/typemill/author/js/vue-plugins.js @@ -0,0 +1,250 @@ +const app = Vue.createApp({ + template: ` +
          + +
          + + + + + +
          +
          +
          `, + data() { + return { + current: '', + formDefinitions: data.definitions, + formData: data.settings, + license: data.license, + message: '', + messageClass: '', + errors: {}, + userroles: false, + showModal: false, + modalMessage: 'default', + versions: false, + } + }, + components: { + 'modal': modal + }, + mounted() { + eventBus.$on('forminput', formdata => { + this.formData[this.current][formdata.name] = formdata.value; + }); + + var self = this; + + var plugins = {}; + for (var key in this.formDefinitions) + { + if (this.formDefinitions.hasOwnProperty(key)) + { + plugins[key] = this.formDefinitions[key].version; + } + } + + tmaxios.post('/api/v1/versioncheck',{ + 'url': data.urlinfo.route, + 'type': 'plugins', + 'data': plugins + }) + .then(function (response) + { + if(response.data.plugins) + { + self.versions = response.data.plugins; + } + }) + .catch(function (error) + { + if(error.response) + { + self.message = handleErrorMessage(error); + self.messageClass = 'bg-rose-500'; + } + }); + + }, + methods: { + getActiveClass(pluginname) + { + if(typeof this.formData[pluginname] == "undefined") + { + console.info(pluginname); + return; + } + if(this.formData[pluginname]['active']) + { + return 'bg-stone-200 dark:bg-stone-900'; + } + }, + getLinkToLicense() + { + return tmaxios.defaults.baseURL + "/tm/license"; + }, + checkLicense(haystack, needle) + { + if(needle == 'MAKER' || needle == 'BUSINESS') + { + if(haystack.indexOf(needle) == -1) + { + return false; + } + } + return true; + }, + activate(pluginname) + { + var self = this; + + tmaxios.post('/api/v1/extensions',{ + 'type': 'plugins', + 'name': pluginname, + 'checked': this.formData[pluginname]['active'] + }) + .then(function (response) + { + + }) + .catch(function (error) + { + if(error.response) + { + self.formData[pluginname]['active'] = false; + self.modalMessage = handleErrorMessage(error); + self.showModal = true; + } + }); + }, + hasSettings(pluginname) + { + if(this.formDefinitions[pluginname].forms !== undefined) + { + return true; + } + return false; + }, + setCurrent(name) + { + if(this.current == name) + { + this.current = ''; + } + else + { + this.current = name; + } + }, + selectComponent(type) + { + return 'component-'+type; + }, + save() + { + this.reset(); + var self = this; + + tmaxios.post('/api/v1/plugin',{ + 'plugin': this.current, + 'settings': this.formData[this.current] + }) + .then(function (response) + { + self.messageClass = 'bg-teal-500'; + self.message = response.data.message; + }) + .catch(function (error) + { + if(error.response) + { + self.message = handleErrorMessage(error); + self.messageClass = 'bg-rose-500'; + if(error.response.data.errors !== undefined) + { + self.errors = error.response.data.errors; + } + } + }); + }, + reset() + { + this.errors = {}; + this.message = ''; + this.messageClass = ''; + } + }, +}) \ No newline at end of file diff --git a/system/typemill/author/js/vue-posts.js b/system/typemill/author/js/vue-posts.js new file mode 100644 index 0000000..536d791 --- /dev/null +++ b/system/typemill/author/js/vue-posts.js @@ -0,0 +1,145 @@ +const posts = Vue.createApp({ + template: `
          +
          + +
          + + +
          +
          {{ error }}
          +
          +
          + +
          +
          `, + data: function () { + return { + active: true, + item: data.item, + posts: false, + posttitle: '', + format: /[@#*()=\[\]{};:"\\|,.<>\/]/, + baseurl: data.urlinfo.baseurl, + editormode: data.settings.editor, + error: false + } + }, + mounted() { + /* fix this */ + eventBus.$on('showEditor', (value) => { + this.active = value; + }); + eventBus.$on('item', item => { + this.item = item; + }); + + if(this.item.elementType == "folder" && this.item.contains == "posts") + { + this.posts = this.item.folderContent; + } + }, + computed: { + showPosts() + { + if(this.item.elementType == "folder" && this.item.contains == "posts" && this.active) + { + return true; + } + return false; + } + }, + methods: { + createPost(evt) + { + eventBus.$emit('publisherclear'); + + if(this.format.test(this.posttitle) || this.posttitle == '' || this.posttitle.length > 60) + { + eventBus.$emit('publishermessage', 'Special Characters are not allowed. Length between 1 and 60.'); + return; + } + + var self = this; + + tmaxios.post('/api/v1/post',{ + 'folder_id': this.item.keyPath, + 'item_name': this.posttitle, + 'type': 'file', + }) + .then(function (response) + { + if(response.data.item) + { + self.posts = response.data.item.folderContent; + self.posttitle = ''; + } + }) + .catch(function (error) + { + if(error.response) + { + let message = handleErrorMessage(error); + + if(message) + { + eventBus.$emit('publishermessage', message); + } + } + }); + } + } +}) + +posts.component('single-post',{ + props: ['post', 'baseurl', 'editormode'], + template: ``, + methods: { + getBorderStyle(status) + { + if(status == 'published') + { + return "border-teal-500"; + } + if(status == 'modified') + { + return "border-yellow-400"; + } + if(status == 'unpublished') + { + return "border-rose-500"; + } + }, + getUrl(posturl) + { + return this.baseurl + '/tm/content/' + this.editormode + this.post.urlRelWoF; + }, + getDate(str) + { + var cleandate = [str.slice(0,4), str.slice(4,6), str.slice(6,8)]; + return cleandate.join("-"); + } + } +}) \ No newline at end of file diff --git a/system/typemill/author/js/vue-publisher.js b/system/typemill/author/js/vue-publisher.js new file mode 100644 index 0000000..4ca4020 --- /dev/null +++ b/system/typemill/author/js/vue-publisher.js @@ -0,0 +1,453 @@ +const publisher = Vue.createApp({ + template: ` +
          +
          {{ message }}
          +
          +
          +
          + +
          + + + + + + +
          + +
          + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
          `, + data() { + return { + item: data.item, + visual: false, + raw: false, + showModal: false, + message: false, + messageClass: '', + nochanges: true, /* for raw editor */ + unsafedcontent: false, /* for visual editor */ + } + }, + components: { + 'modal': modal + }, + mounted() { + if(window.location.href.indexOf('/content/raw') > -1) + { + this.raw = true; + } + if(window.location.href.indexOf('/content/visual') > -1) + { + this.visual = true; + } + + eventBus.$on('item', item => { + this.item = item; + }); + + eventBus.$on('publishermessage', message => { + this.message = message; + this.messageClass = 'bg-rose-500'; + }); + + eventBus.$on('publisherclear', this.clearPublisher); + + eventBus.$on('editdraft', this.markChanges); + + eventBus.$on('cleardraft', this.unmarkChanges); + + eventBus.$on('lockcontent', content => { + this.unsafedcontent = true; + }); + + eventBus.$on('unlockcontent', content => { + this.unsafedcontent = false; + }); + }, + computed: { + isPublished() + { + return this.item.status == 'published' ? true : false; + }, + isModified() + { + return this.item.status == 'modified' ? true : false; + }, + isUnpublished() + { + return this.item.status == 'unpublished' ? true : false; + }, + publishClass() + { + if(this.item.status == 'unpublished') + { + return 'bg-teal-500 hover:bg-teal-600'; + } + else + { + return 'bg-yellow-500 hover:bg-yellow-600'; + } + /* + if(this.item.status == 'modified') + { + return 'bg-yellow-500 hover:bg-yellow-600'; + }*/ + }, + nopublish() + { + if(this.item.status != 'published') + { + return false; + } + return this.nochanges; + }, + rawUrl() + { + return data.urlinfo.baseurl + '/tm/content/raw' + this.item.urlRelWoF; + }, + visualUrl() + { + return data.urlinfo.baseurl + '/tm/content/visual' + this.item.urlRelWoF; + }, + }, + methods: { + clearPublisher() + { + this.message = false; + this.messageClass = false; + this.showModal = false; + }, + markChanges() + { + this.nochanges = false; + }, + unmarkChanges() + { + this.nochanges = true; + }, + getStatusClass(status) + { + if(status == 'published') + { + return "border-teal-500"; + } + else if(status == 'unpublished') + { + return "border-rose-500"; + } + else if(status == 'modified') + { + return "border-yellow-500"; + } + }, + publishArticle() + { + var self = this; + + tmaxios.post('/api/v1/article/publish',{ + 'url': data.urlinfo.route, + 'item_id': this.item.keyPath, + }) + .then(function (response) + { + self.clearPublisher(); + eventBus.$emit('item', response.data.item); + eventBus.$emit('navigation', response.data.navigation); + eventBus.$emit('meta', response.data.metadata); + }) + .catch(function (error) + { + if(error.response) + { + let message = handleErrorMessage(error); + + if(message) + { + self.message = message; + self.messageClass = "bg-rose-500"; + } + } + }); + }, + checkUnpublish() + { + if(this.item.status == 'modified') + { + this.showModal = 'unpublish'; + } + else + { + this.clearPublisher(); + this.unpublishArticle(); + } + }, + unpublishArticle() + { + self = this; + + tmaxios.delete('/api/v1/article/unpublish',{ + data: { + 'url': data.urlinfo.route, + 'item_id': this.item.keyPath, + } + }) + .then(function (response) + { + self.clearPublisher(); + eventBus.$emit('item', response.data.item); + eventBus.$emit('navigation', response.data.navigation); + }) + .catch(function (error) + { + self.showModal = false; + + if(error.response) + { + let message = handleErrorMessage(error); + + if(message) + { + self.message = message; + self.messageClass = "bg-rose-500"; + } + } + }); + }, + discardChanges() + { + self = this; + + tmaxios.delete('/api/v1/article/discard',{ + data: { + 'url': data.urlinfo.route, + 'item_id': this.item.keyPath, + } + }) + .then(function (response) + { + self.clearPublisher(); + eventBus.$emit('item', response.data.item); + eventBus.$emit('navigation', response.data.navigation); + eventBus.$emit('content', response.data.content); + }) + .catch(function (error) + { + self.showModal = false; + + if(error.response) + { + let message = handleErrorMessage(error); + + if(message) + { + self.message = message; + self.messageClass = "bg-rose-500"; + } + } + }); + }, + saveDraft() + { + eventBus.$emit('savedraft'); + }, + publishDraft() + { + eventBus.$emit('publishdraft'); + }, + deleteArticle() + { + var self = this; + + tmaxios.delete('/api/v1/article',{ + data: { + 'url': data.urlinfo.route, + 'item_id': this.item.keyPath, + } + }) + .then(function (response) + { + window.location.replace(response.data.url); + }) + .catch(function (error) + { + self.showModal = false; + + if(error.response) + { + let message = handleErrorMessage(error); + + if(message) + { + self.message = message; + self.messageClass = "bg-rose-500"; + } + } + }); + }, + checkUnsafedContent(url) + { + if(this.unsafedcontent) + { + this.message = 'please save your changes before you switch the editor.'; + this.messageClass = "bg-rose-500"; + } + else + { + window.location.href = url; + } + }, + checkChanges(url) + { + if(!this.nochanges) + { + this.showModal = 'unsaved'; + } + else + { + window.location.href = url; + } + }, + switchToVisual(url) + { + window.location.href = url; + } + }, +}) \ No newline at end of file diff --git a/system/typemill/author/js/vue-raw.js b/system/typemill/author/js/vue-raw.js new file mode 100644 index 0000000..b5402c7 --- /dev/null +++ b/system/typemill/author/js/vue-raw.js @@ -0,0 +1,187 @@ +const raweditor = Vue.createApp({ + template: ` +
          +
          + + + {{ errors.title }} +
          +
          + +
          + + +
          +
          +
          + `, + data() { + return { + title: 'loading', + content: 'loading', + item: data.item, + highlighted: '', + errors: false, + freeze: false, + showraw: true, + editorsize: false, + } + }, + mounted() { + this.initializeContent(data.content) + + eventBus.$on('savedraft', this.saveDraft); + eventBus.$on('publishdraft', this.publishDraft); + eventBus.$on('showEditor', (value) => { + this.showEditor(value); + }); + eventBus.$on('content', content => { + this.initializeContent(content); + }); + + }, + methods: { + showEditor(value) + { + this.showraw = value; + if(value) + { + this.$nextTick(() => { + this.resizeCodearea(); + }) + } + }, + initializeContent(contentArray) + { + let markdown = ''; + let title = contentArray.shift(); + + for(item in contentArray) + { + markdown += contentArray[item].markdown + '\n\n'; + } + this.title = title.markdown; + this.content = markdown; + + this.highlight(this.content); + this.resizeCodearea(); + }, + updateTitle() + { + eventBus.$emit('editdraft'); + }, + updateBody() + { + this.highlight(this.content); + this.resizeCodearea(); + + eventBus.$emit('editdraft'); + }, + resizeCodearea() + { + let codeeditor = this.$refs["raweditor"]; + + window.requestAnimationFrame(() => { + + autosize(codeeditor); + + if(codeeditor.style.height > this.editorsize) + { + window.scrollBy({ + top: 18, + left: 0, + behavior: "smooth", + }); + } + + this.editorsize = codeeditor.style.height; + }); + }, + highlight(code) + { + if(code === undefined) + { + return; + } + + window.requestAnimationFrame(() => { + highlighted = hljs.highlightAuto(code, ['markdown']).value; + this.highlighted = highlighted; + }); + }, + saveDraft() + { + eventBus.$emit('publisherclear'); + + var self = this; + tmaxios.put('/api/v1/draft',{ + 'url': data.urlinfo.route, + 'item_id': this.item.keyPath, + 'title': this.title, + 'body': this.content + }) + .then(function (response) { + self.item = response.data.item; + eventBus.$emit('cleardraft'); + eventBus.$emit('item', response.data.item); + eventBus.$emit('navigation', response.data.navigation); + }) + .catch(function (error) + { + if(error.response) + { + let message = handleErrorMessage(error); + if(message) + { + eventBus.$emit('publishermessage', message); + } + } + }); + }, + publishDraft() + { + eventBus.$emit('publisherclear'); + + var self = this; + tmaxios.post('/api/v1/draft/publish',{ + 'url': data.urlinfo.route, + 'item_id': this.item.keyPath, + 'title': this.title, + 'body': this.content + }) + .then(function (response) { + self.item = response.data.item; + eventBus.$emit('cleardraft'); + eventBus.$emit('item', response.data.item); + eventBus.$emit('navigation', response.data.navigation); + }) + .catch(function (error) + { + if(error.response) + { + let message = handleErrorMessage(error); + if(message) + { + eventBus.$emit('publishermessage', message); + } + } + }); + }, + }, +}) \ No newline at end of file diff --git a/system/typemill/author/js/vue-shared.js b/system/typemill/author/js/vue-shared.js new file mode 100644 index 0000000..ac2c501 --- /dev/null +++ b/system/typemill/author/js/vue-shared.js @@ -0,0 +1,141 @@ +const modal = { + props: ['labelconfirm', 'labelcancel'], + template: ` +
          +
          +
          +
          + + default header + +
          +
          + + default body + +
          +
          + + + default button + +
          +
          +
          +
          +
          `, + methods: { + update: function($event, name) + { + eventBus.$emit('forminput', {'name': name, 'value': $event.target.value}); + }, + } +} + +const translatefilter = { + translate(value) + { + if(typeof data.labels === 'undefined') + { + return value; + } + if (!value) + { + return ''; + } + + /* corresponding rules in translationScanner and translationFiller */ + let translation_key = value + .replace(/[ \-+*#,.:;?!\"&()\\[\]\/]/g, "_") // Adjusted regex + .replace(/__+/g, "_") // Replace multiple underscores with a single underscore + .replace(/^_+|_+$/g, "") // Trim underscores from the start and end of the string + .toUpperCase(); // Convert to uppercase + + + let translation_value = data.labels[translation_key]; + + if(!translation_value || translation_value.length === 0) + { + translation_value = value; + } + + /* process markdown links */ + translation_value = translation_value.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); + + return translation_value; + } +} + +function handleErrorMessage(error) +{ + if(error.response) + { + if(error.response.status == 401) + { + eventBus.$emit('loginform', false); + } + else if(error.response.data.message) + { + return error.response.data.message; + } + } + + return false; +} + +const loginform = Vue.createApp({ + template: ` +
          +
          +
          +

          You are logged out

          +
          +

          You can visit the login page and authenticate again. Or you can close this window but you cannot perform any actions.

          +
          +
          + login page + +
          +
          +
          +
          +
          `, + data() { + return { + show: false, + errors: {}, + username: '', + password: '', + loginurl: data.urlinfo.baseurl + '/tm/login' + } + }, + mounted() { + eventBus.$on('loginform', content => { + this.show = true; + }); + }, + methods: { + login: function() + { + var self = this; + + tmaxios.post('/api/v1/authenticate',{ + 'username': this.username, + 'password': this.password + }) + .then(function (response) + { + self.show = false; + }) + .catch(function (error) + { + self.messageClass = 'bg-rose-500'; + self.message = error.response.data.message; + if(error.response.data.errors !== undefined) + { + self.errors = error.response.data.errors; + } + }); + }, + }, +}) \ No newline at end of file diff --git a/system/typemill/author/js/vue-system.js b/system/typemill/author/js/vue-system.js new file mode 100644 index 0000000..5024dea --- /dev/null +++ b/system/typemill/author/js/vue-system.js @@ -0,0 +1,153 @@ +const app = Vue.createApp({ + template: ` +
          +

          Please update typemill to version {{ version.system }}

          +
            +
          • + +
          • +
          +
          +
          + + + + + +
          +
          +
          +
          {{ $filters.translate(message) }}
          + +
          +
          +
          `, + data() { + return { + currentTab: 'System', + tabs: [], + formDefinitions: data.system, + formData: data.settings, + message: '', + messageClass: '', + errors: {}, + version: false, + } + }, + mounted() { + + eventBus.$on('forminput', formdata => { + this.formData[formdata.name] = formdata.value; + }); + + for (var key in this.formDefinitions) + { + if (this.formDefinitions.hasOwnProperty(key)) + { + this.tabs.push(this.formDefinitions[key].legend); + this.errors[key] = false; + } + } + + var self = this; + + tmaxios.post('/api/v1/versioncheck',{ + 'url': data.urlinfo.route, + 'type': 'system', + 'data': this.formData.version + }) + .then(function (response) + { + if(response.data.system) + { + self.version = response.data.system; + } + }) + .catch(function (error) + { + if(error.response) + { + self.message = handleErrorMessage(error); + self.messageClass = 'bg-rose-500'; + } + }); + }, + methods: { + selectComponent: function(type) + { + return 'component-'+type; + }, + activateTab: function(tab){ + this.currentTab = tab; + this.reset(); + }, + testmail: function() + { + this.reset(); + var self = this; + + tmaxios.post('/api/v1/testmail',{ + 'url': data.urlinfo.route, + }) + .then(function (response) + { + self.messageClass = 'bg-teal-500'; + self.message = response.data.message; + }) + .catch(function (error) + { + if(error.response) + { + self.message = handleErrorMessage(error); + self.messageClass = 'bg-rose-500'; + if(error.response.data.errors !== undefined) + { + self.errors = error.response.data.errors; + } + } + }); + }, + save: function() + { + this.reset(); + var self = this; + + tmaxios.post('/api/v1/settings',{ + 'settings': this.formData + }) + .then(function (response) + { + self.messageClass = 'bg-teal-500'; + self.message = response.data.message; + }) + .catch(function (error) + { + if(error.response) + { + self.message = handleErrorMessage(error); + self.messageClass = 'bg-rose-500'; + if(error.response.data.errors !== undefined) + { + self.errors = error.response.data.errors; + } + } + }); + }, + reset: function() + { + this.errors = {}; + this.message = ''; + this.messageClass = ''; + } + }, +}) \ No newline at end of file diff --git a/system/typemill/author/js/vue-systemnavi.js b/system/typemill/author/js/vue-systemnavi.js new file mode 100644 index 0000000..5a4a48d --- /dev/null +++ b/system/typemill/author/js/vue-systemnavi.js @@ -0,0 +1,37 @@ +const systemnavi = Vue.createApp({ + template: ``, + data() { + return { + systemnavi: data.systemnavi, + baseurl: data.urlinfo.baseurl, + expanded: false, + } + }, + mounted() { + }, + methods: { + toggle() + { + if(this.expanded) + { + this.expanded = false; + } + else + { + this.expanded = true; + } + } + }, +}) \ No newline at end of file diff --git a/system/typemill/author/js/vue-themes.js b/system/typemill/author/js/vue-themes.js new file mode 100644 index 0000000..5c2bb8f --- /dev/null +++ b/system/typemill/author/js/vue-themes.js @@ -0,0 +1,456 @@ +const app = Vue.createApp({ + template: ` +
          +
            +
          • +

            Please update to version {{ versions[themename].version }}

            +
            +

            License: {{ theme.license }}

            +
            + + +
            +
            +
            +
            +
            +

            {{theme.name}}

            +
            author: {{theme.author}} | version: {{theme.version}}
            +

            {{theme.description}}

            +
            +
            + +
            +
            +
            + + Buy a license + Donate {{theme.amount}},- +
            +
            +
            +
            +
            +
            +

            Readymades

            + +
            + +
            + +

            Readymades are predefined settings. Store your own readymades or load readymades to quickly setup your theme.

            +
              + +
            • +
              +
              {{ readysetup.name }}
              +
              +

              {{ readysetup.description }}

              +
              +
              + + +
              +
              + +
              +
              +
            • +
            • +
              + + + +
              +
            • +
              +
            +
            {{ readymadeError }}
            +
            +
            +
            +
            +
            +
            +
            +

            {{ fieldDefinition.legend }}

            + +
            + +
            + + +
            +
            +
            + + +
            +
            +
            {{ message }}
            + +
            +
            +
          • +
          +
          + + + + + +
          +
          +
          `, + data() { + return { + current: '', + formDefinitions: data.definitions, + formData: data.settings, + readymadeTitle: '', + readymadeDescription: '', + readymadeError: false, + readymadeCurrent: false, + theme: data.theme, + license: data.license, + message: '', + messageClass: '', + errors: {}, + versions: false, + userroles: false, + showModal: false, + modalMessage: 'default', + accordionState: {}, + } + }, + components: { + 'modal': modal + }, + mounted() { + eventBus.$on('forminput', formdata => { + this.formData[this.current][formdata.name] = formdata.value; + }); + this.deactivateThemes(); + this.formData[this.theme].active = true; + + var self = this; + + var themes = {}; + for (var key in this.formDefinitions) + { + if (this.formDefinitions.hasOwnProperty(key)) + { + themes[key] = this.formDefinitions[key].version; + } + } + + tmaxios.post('/api/v1/versioncheck',{ + 'url': data.urlinfo.route, + 'type': 'themes', + 'data': themes + }) + .then(function (response) + { + if(response.data.themes) + { + self.versions = response.data.themes; + } + }) + .catch(function (error) + { + if(error.response) + { + self.messageClass = 'bg-rose-500'; + self.message = handleErrorMessage(error); + } + }); + }, + methods: { + themeurl(name) + { + return 'https://themes.typemill.net/' + name; + }, + deactivateThemes() + { + for (const theme in this.formData) { + delete this.formData[theme].active; + } + }, + getActiveClass(themename) + { + if(this.formData[themename]['active']) + { + return 'bg-stone-200 dark:bg-stone-900'; + } + }, + getSrc(preview) + { + return data.urlinfo.baseurl + preview; + }, + getLinkToLicense() + { + return tmaxios.defaults.baseURL + "/tm/license"; + }, + checkLicense(haystack, needle) + { + if(needle == 'MAKER' || needle == 'BUSINESS') + { + if(haystack.indexOf(needle) == -1) + { + return false; + } + } + return true; + }, + activate(themename) + { + var self = this; + + tmaxios.post('/api/v1/extensions',{ + 'type': 'themes', + 'name': themename, + 'checked': this.formData[themename]['active'] + }) + .then(function (response) + { + var status = self.formData[themename]['active']; + self.deactivateThemes(); + self.formData[themename]['active'] = status; + }) + .catch(function (error) + { + if(error.response) + { + self.formData[themename]['active'] = false; + self.modalMessage = handleErrorMessage(error); + self.showModal = true; + } + }); + }, + setCurrent(name) + { + if(this.current == name) + { + this.current = ''; + } + else + { + this.current = name; + } + }, + selectComponent(type) + { + return 'component-'+type; + }, + loadReadymade(name) + { + this.readymadeError = false; + + if(this.formDefinitions[this.current].readymades[name] !== undefined) + { + this.readymadeCurrent = name; + + this.formData[this.current] = this.formDefinitions[this.current].readymades[name].settings; + eventBus.$emit('codeareaupdate'); + } + }, + checkTitle() + { + if(this.readymadeTitle.length > 20) + { + this.readymadeTitle = this.readymadeTitle.substring(0, 20); + } + if(this.readymadeTitle.match(/^[a-zA-Z0-9\- ]*$/)) + { + this.readymadeError = false; + } + else + { + this.readymadeError = "Only characters [a-zA-Z0-9- ] are allowed." + } + }, + checkDescription() + { + if(this.readymadeDescription.length > 100) + { + this.readymadeDescription = this.readymadeDescription.substring(0, 100); + } + }, + storeReadymade() + { + this.readymadeError = false; + + var rself = this; + + tmaxios.post('/api/v1/treadymade',{ + 'theme': this.current, + 'settings': this.formData[this.current], + 'readymadetitle': this.readymadeTitle, + 'readymadedesc': this.readymadeDescription + }) + .then(function (response) + { + if(response.data.readymade !== undefined) + { + rself.formDefinitions[rself.current].readymades = Object.assign(rself.formDefinitions[rself.current].readymades, response.data.readymade); + + rself.readymadeTitle = ''; + rself.readymadeDescription = ''; + } + }) + .catch(function (error) + { + if(error.response) + { + if(error.response.data.message !== undefined) + { + rself.readymadeError = error.response.data.message; + } + } + }); + }, + deleteReadymade(name) + { + this.readymadeError = false; + + var rself = this; + + tmaxios.delete('/api/v1/treadymade',{ + data: { + 'theme': this.current, + 'readymadeslug': name + } + }) + .then(function (response) + { + delete rself.formDefinitions[rself.current].readymades[name]; + }) + .catch(function (error) + { + if(error.response) + { + if(error.response.data.message !== undefined) + { + rself.readymadeError = error.response.data.message; + } + } + }); + }, + save() + { + this.reset(); + + var self = this; + + tmaxios.post('/api/v1/theme',{ + 'theme': this.current, + 'settings': this.formData[this.current] + }) + .then(function (response) + { + self.messageClass = 'bg-teal-500'; + self.message = response.data.message; + + self.updateCSS(); + }) + .catch(function (error) + { + if(error.response) + { + self.message = handleErrorMessage(error); + self.messageClass = 'bg-rose-500'; + if(error.response.data.errors !== undefined) + { + self.errors = error.response.data.errors; + } + } + }); + }, + updateCSS() + { + var selfcss = this; + + tmaxios.post('/api/v1/themecss',{ + 'theme': this.current, + 'css': this.formData[this.current].customcss + }) + .then(function (response) + { + self.messageClass = 'bg-teal-500'; + self.message = response.data.message; + }) + .catch(function (error) + { + if(error.response) + { + self.message = handleErrorMessage(error); + self.messageClass = 'bg-rose-500'; + if(error.response.data.errors !== undefined) + { + self.errors = error.response.data.errors; + } + } + }); + }, + reset() + { + this.readymadeError = false; + this.errors = {}; + this.message = ''; + this.messageClass = ''; + }, + toggleAccordion: function(fieldname){ + this.accordionState[fieldname] = !this.accordionState[fieldname]; + }, + isOpen: function(fieldname){ + return !!this.accordionState[fieldname]; + } + }, +}) \ No newline at end of file diff --git a/system/typemill/author/js/vue-user.js b/system/typemill/author/js/vue-user.js new file mode 100644 index 0000000..eabcffd --- /dev/null +++ b/system/typemill/author/js/vue-user.js @@ -0,0 +1,149 @@ +const app = Vue.createApp({ + template: ` +
          +
          + {{formdata}} +
          +
          + {{ $filters.translate(fieldDefinition.legend) }} + + +
          + + +
          +
          +
          {{ $filters.translate(message) }}
          + +
          +
          +
          + + + + + + +
          +
          +
          `, + data() { + return { + formDefinitions: data.userfields, + formData: data.userdata, + userroles: data.userroles, + message: '', + messageClass: '', + errors: {}, + showModal: false, + } + }, + components: { + 'modal': modal + }, + mounted() { + eventBus.$on('forminput', formdata => { + this.formData[formdata.name] = formdata.value; + }); + }, + methods: { + selectComponent: function(type) + { + return 'component-'+type; + }, + changeForm: function() + { + /* change input form if user role changed */ + }, + save: function() + { + this.reset(); + var self = this; + + tmaxios.put('/api/v1/user',{ + 'userdata': this.formData + }) + .then(function (response) + { + self.messageClass = 'bg-teal-500'; + self.message = response.data.message; + }) + .catch(function (error) + { + if(error.response) + { + self.messageClass = 'bg-rose-500'; + self.message = handleErrorMessage(error); + if(error.response.data.errors !== undefined) + { + self.errors = error.response.data.errors; + } + } + }); + }, + deleteuser: function() + { + this.reset(); + var self = this; + + tmaxios.delete('/api/v1/user',{ + data: { + 'username': this.formData.username + } + }) + .then(function (response) + { + self.showModal = false; + self.messageClass = 'bg-teal-500'; + self.message = response.data.message; + if(response.data.logout == true) + { + window.location.replace(data.urlinfo.baseurl + '/tm/logout'); + } + else + { + window.location.replace(data.urlinfo.baseurl + '/tm/users'); + } + }) + .catch(function (error) + { + if(error.response) + { + self.showModal = false; + self.messageClass = 'bg-rose-500'; + self.message = handleErrorMessage(error); + if(error.response.data.errors !== undefined) + { + self.errors = error.response.data.errors; + } + } + }); + }, + reset: function() + { + this.errors = {}; + this.message = ''; + this.messageClass = ''; + } + }, +}) \ No newline at end of file diff --git a/system/typemill/author/js/vue-usernew.js b/system/typemill/author/js/vue-usernew.js new file mode 100644 index 0000000..fe42837 --- /dev/null +++ b/system/typemill/author/js/vue-usernew.js @@ -0,0 +1,128 @@ +const app = Vue.createApp({ + template: ` +
          +
          + + +
          +
          +
          +
          + {{ fieldDefinition.legend }} + + +
          + + +
          +
          +
          {{ $filters.translate(message) }}
          + +
          +
          +
          +
          `, + data() { + return { + selectedrole: false, + formDefinitions: false, + formData: {}, + userroles: data.userroles, + message: '', + messageClass: '', + errors: {}, + } + }, + mounted() { + eventBus.$on('forminput', formdata => { + this.formData[formdata.name] = formdata.value; + }); + }, + methods: { + selectComponent: function(type) + { + return 'component-'+type; + }, + generateForm: function() + { + this.reset(); + var self = this; + + tmaxios.get('/api/v1/userform',{ + params: { + 'userrole': this.selectedrole + } + }) + .then(function (response) + { + self.formDefinitions = response.data.userform; + self.formData.userrole = self.selectedrole; + }) + .catch(function (error) + { + if(error.response) + { + self.messageClass = 'bg-rose-500'; + self.message = handleErrorMessage(error); + if(error.response.data.errors !== undefined) + { + self.errors = error.response.data.errors; + } + } + }); + }, + save: function() + { + this.reset(); + var self = this; + + tmaxios.post('/api/v1/user',{ + 'userdata': this.formData + }) + .then(function (response) + { + self.messageClass = 'bg-teal-500'; + self.message = response.data.message; + + window.location = tmaxios.defaults.baseURL + '/tm/user/' + self.formData.username; + }) + .catch(function (error) + { + if(error.response) + { + self.messageClass = 'bg-rose-500'; + self.message = handleErrorMessage(error); + if(error.response.data.errors !== undefined) + { + self.errors = error.response.data.errors; + } + } + }); + }, + reset: function() + { + this.errors = {}; + this.message = ''; + this.messageClass = ''; + } + }, +}) \ No newline at end of file diff --git a/system/typemill/author/js/vue-users.js b/system/typemill/author/js/vue-users.js new file mode 100644 index 0000000..2cfb12b --- /dev/null +++ b/system/typemill/author/js/vue-users.js @@ -0,0 +1,295 @@ +const app = Vue.createApp({ + template: `
          + + + +
          +
          + + + +
          +
            + +
          `, + data: function () { + return { + usernames: data.usernames, + holdusernames: data.usernames, + userdata: data.userdata, + holduserdata: data.userdata, + userroles: data.userroles, + pagenumber: 1, + pagesize: 10, + pages: 0, + error: false, + } + }, + mounted: function(){ + this.calculatepages(); + }, + computed: { + showpagination: function () { + return this.pages != 1; + } + }, + methods: { + clear: function(filter) + { + this.usernames = this.holdusernames; + this.userdata = this.holduserdata; + this.calculatepages(); + if(this.pages == 1) + { + this.showpagination = false; + } + }, + calculatepages: function() + { + this.pages = Math.ceil(this.usernames.length / this.pagesize); + this.pagenumber = 1; + }, + getusernamesforpage: function() { + // human-readable page numbers usually start with 1, so we reduce 1 in the first argument + return this.usernames.slice((this.pagenumber - 1) * this.pagesize, this.pagenumber * this.pagesize); + }, + getuserdata: function(usernames) + { + var self = this; + + tmaxios.get('/api/v1/users/getbynames',{ + params: { + 'usernames': usernames, + } + }) + .then(function (response) { + self.userdata = response.data.userdata; + }) + .catch(function (error) + { + if(error.response) + { + self.messageClass = 'bg-rose-500'; + self.message = handleErrorMessage(error); + } + }); + }, + search: function(term,filter) + { + if(filter == 'username') + { + this.usernames = this.filterItems(this.holdusernames, term); + this.userdata = []; + this.calculatepages(); + + if(this.usernames.length > 0) + { + let usernames = this.getusernamesforpage(); + + this.getuserdata(usernames); + } + } + else if(filter == 'usermail') + { + this.usernames = []; + this.userdata = []; + this.calculatepages(); + + var self = this; + + tmaxios.get('/api/v1/users/getbyemail',{ + params: { + 'email': term, + } + }) + .then(function (response) + { + self.userdata = response.data.userdata; + if(self.userdata.length > 0) + { + for(var x = 0; x <= self.userdata.length; x++) + { + self.usernames.push(self.userdata[x].username); + } + self.calculatepages(); + } + }) + .catch(function (error) + { + if(error.response) + { + self.messageClass = 'bg-rose-500'; + self.message = handleErrorMessage(error); + } + }); + } + else if(filter == 'userrole') + { + this.usernames = []; + this.userdata = []; + this.calculatepages(); + + var self = this; + + tmaxios.get('/api/v1/users/getbyrole',{ + params: { + 'role': term, + } + }) + .then(function (response) + { + self.userdata = response.data.userdata; + for(var x = 0; x <= self.userdata.length; x++) + { + self.usernames.push(self.userdata[x].username); + } + self.calculatepages(); + }) + .catch(function (error) + { + if(error.response) + { + self.messageClass = 'bg-rose-500'; + self.message = handleErrorMessage(error); + } + }); + } + }, + filterItems: function(arr, query) + { + return arr.filter(function(el){ + return el.toLowerCase().indexOf(query.toLowerCase()) !== -1 + }) + }, + } +}) + +app.component('searchbox', { + props: ['usernames', 'error'], + data: function () { + return { + filter: 'username', + searchterm: '', + userroles: data.userroles, + } + }, + template: `
          +
          + + + +
          +
          + + +
          + + +
          +
          +
          {{error}}
          +
          {{ $filters.translate('You can use the asterisk (*) wildcard to search for name@* or *@domain.com') }}.
          +
          `, + methods: { + startSearch: function() + { + this.$root.error = false; + + if(this.searchterm.trim() != '') + { + if(this.searchterm.trim().length < 3) + { + this.$root.error = 'Please enter at least 3 characters'; + return; + } + this.$root.search(this.searchterm, this.filter); + } + }, + clearSearch: function() + { + this.$root.error = false; + this.searchterm = ''; + this.$root.clear(this.filter); + }, + setFilter: function(filter) + { + this.searchterm = ''; + this.filter = filter; + if(filter == 'userrole') + { + this.searchterm = this.userroles[0]; + } + }, + checkActive: function(filter) + { + if(this.filter == filter) + { + return 'border-stone-700 bg-stone-200 dark:text-stone-900'; + } + return 'border-stone-100 bg-stone-100 dark:bg-stone-600 hover:dark:bg-stone-200 hover:dark:text-stone-900 dark:border-stone-700'; + } + } +}) + +app.component('usertable', { + props: ['userdata'], + template: ` + + + + + + + + + + + + + + + + + + + + + + +
          {{ $filters.translate('Username') }}{{ $filters.translate('Userrole') }}{{ $filters.translate('E-Mail') }}{{ $filters.translate('Edit') }}
          {{ user.username }}{{ user.userrole }}{{ user.email }}{{ $filters.translate('edit') }}
          {{ $filters.translate('New user') }}{{ $filters.translate('add') }}
          `, + methods: { + getEditLink: function(username){ + return tmaxios.defaults.baseURL + '/tm/user/' + username; + }, + getNewUserLink: function(){ + return tmaxios.defaults.baseURL + '/tm/user/new'; + }, + } +}) + +app.component('pagination', { + props: ['page'], + template: '
        • ', + methods: { + goto: function(page){ + + this.$root.$data.pagenumber = page; + let usernames = this.$root.getusernamesforpage(); + this.$root.getuserdata(usernames); + }, + checkActive: function() + { + if(this.page == this.$root.$data.pagenumber) + { + return 'bg-stone-200'; + } + return 'bg-stone-100'; + } + } +}) \ No newline at end of file diff --git a/system/typemill/author/js/vue.global.prod.js b/system/typemill/author/js/vue.global.prod.js new file mode 100644 index 0000000..b2cd5c3 --- /dev/null +++ b/system/typemill/author/js/vue.global.prod.js @@ -0,0 +1,9 @@ +/** +* vue v3.5.13 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/var Vue=function(e){"use strict";var t,n;let r,i,l,s,o,a,c,u,d,p,f,h,m;function g(e){let t=Object.create(null);for(let n of e.split(","))t[n]=1;return e=>e in t}let y={},b=[],_=()=>{},S=()=>!1,x=e=>111===e.charCodeAt(0)&&110===e.charCodeAt(1)&&(e.charCodeAt(2)>122||97>e.charCodeAt(2)),C=e=>e.startsWith("onUpdate:"),k=Object.assign,T=(e,t)=>{let n=e.indexOf(t);n>-1&&e.splice(n,1)},N=Object.prototype.hasOwnProperty,w=(e,t)=>N.call(e,t),A=Array.isArray,E=e=>"[object Map]"===V(e),I=e=>"[object Set]"===V(e),R=e=>"[object Date]"===V(e),O=e=>"[object RegExp]"===V(e),P=e=>"function"==typeof e,M=e=>"string"==typeof e,L=e=>"symbol"==typeof e,$=e=>null!==e&&"object"==typeof e,D=e=>($(e)||P(e))&&P(e.then)&&P(e.catch),F=Object.prototype.toString,V=e=>F.call(e),B=e=>V(e).slice(8,-1),U=e=>"[object Object]"===V(e),j=e=>M(e)&&"NaN"!==e&&"-"!==e[0]&&""+parseInt(e,10)===e,H=g(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),q=g("bind,cloak,else-if,else,for,html,if,model,on,once,pre,show,slot,text,memo"),W=e=>{let t=Object.create(null);return n=>t[n]||(t[n]=e(n))},K=/-(\w)/g,z=W(e=>e.replace(K,(e,t)=>t?t.toUpperCase():"")),J=/\B([A-Z])/g,G=W(e=>e.replace(J,"-$1").toLowerCase()),X=W(e=>e.charAt(0).toUpperCase()+e.slice(1)),Q=W(e=>e?`on${X(e)}`:""),Z=(e,t)=>!Object.is(e,t),Y=(e,...t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:r,value:n})},et=e=>{let t=parseFloat(e);return isNaN(t)?e:t},en=e=>{let t=M(e)?Number(e):NaN;return isNaN(t)?e:t},er=()=>r||(r="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{}),ei=g("Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt,console,Error,Symbol");function el(e){if(A(e)){let t={};for(let n=0;n{if(e){let n=e.split(eo);n.length>1&&(t[n[0].trim()]=n[1].trim())}}),t}function eu(e){let t="";if(M(e))t=e;else if(A(e))for(let n=0;neg(e,t))}let ev=e=>!!(e&&!0===e.__v_isRef),eb=e=>M(e)?e:null==e?"":A(e)||$(e)&&(e.toString===F||!P(e.toString))?ev(e)?eb(e.value):JSON.stringify(e,e_,2):String(e),e_=(e,t)=>ev(t)?e_(e,t.value):E(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((e,[t,n],r)=>(e[eS(t,r)+" =>"]=n,e),{})}:I(t)?{[`Set(${t.size})`]:[...t.values()].map(e=>eS(e))}:L(t)?eS(t):!$(t)||A(t)||U(t)?t:String(t),eS=(e,t="")=>{var n;return L(e)?`Symbol(${null!=(n=e.description)?n:t})`:e};class ex{constructor(e=!1){this.detached=e,this._active=!0,this.effects=[],this.cleanups=[],this._isPaused=!1,this.parent=i,!e&&i&&(this.index=(i.scopes||(i.scopes=[])).push(this)-1)}get active(){return this._active}pause(){if(this._active){let e,t;if(this._isPaused=!0,this.scopes)for(e=0,t=this.scopes.length;e0)){if(o){let e=o;for(o=void 0;e;){let t=e.next;e.next=void 0,e.flags&=-9,e=t}}for(;s;){let t=s;for(s=void 0;t;){let n=t.next;if(t.next=void 0,t.flags&=-9,1&t.flags)try{t.trigger()}catch(t){e||(e=t)}t=n}}if(e)throw e}}function eA(e){for(let t=e.deps;t;t=t.nextDep)t.version=-1,t.prevActiveLink=t.dep.activeLink,t.dep.activeLink=t}function eE(e){let t;let n=e.depsTail,r=n;for(;r;){let e=r.prevDep;-1===r.version?(r===n&&(n=e),eO(r),function(e){let{prevDep:t,nextDep:n}=e;t&&(t.nextDep=n,e.prevDep=void 0),n&&(n.prevDep=t,e.nextDep=void 0)}(r)):t=r,r.dep.activeLink=r.prevActiveLink,r.prevActiveLink=void 0,r=e}e.deps=t,e.depsTail=n}function eI(e){for(let t=e.deps;t;t=t.nextDep)if(t.dep.version!==t.version||t.dep.computed&&(eR(t.dep.computed)||t.dep.version!==t.version))return!0;return!!e._dirty}function eR(e){if(4&e.flags&&!(16&e.flags)||(e.flags&=-17,e.globalVersion===eF))return;e.globalVersion=eF;let t=e.dep;if(e.flags|=2,t.version>0&&!e.isSSR&&e.deps&&!eI(e)){e.flags&=-3;return}let n=l,r=eP;l=e,eP=!0;try{eA(e);let n=e.fn(e._value);(0===t.version||Z(n,e._value))&&(e._value=n,t.version++)}catch(e){throw t.version++,e}finally{l=n,eP=r,eE(e),e.flags&=-3}}function eO(e,t=!1){let{dep:n,prevSub:r,nextSub:i}=e;if(r&&(r.nextSub=i,e.prevSub=void 0),i&&(i.prevSub=r,e.nextSub=void 0),n.subs===e&&(n.subs=r,!r&&n.computed)){n.computed.flags&=-5;for(let e=n.computed.deps;e;e=e.nextDep)eO(e,!0)}t||--n.sc||!n.map||n.map.delete(n.key)}let eP=!0,eM=[];function eL(){eM.push(eP),eP=!1}function e$(){let e=eM.pop();eP=void 0===e||e}function eD(e){let{cleanup:t}=e;if(e.cleanup=void 0,t){let e=l;l=void 0;try{t()}finally{l=e}}}let eF=0;class eV{constructor(e,t){this.sub=e,this.dep=t,this.version=t.version,this.nextDep=this.prevDep=this.nextSub=this.prevSub=this.prevActiveLink=void 0}}class eB{constructor(e){this.computed=e,this.version=0,this.activeLink=void 0,this.subs=void 0,this.map=void 0,this.key=void 0,this.sc=0}track(e){if(!l||!eP||l===this.computed)return;let t=this.activeLink;if(void 0===t||t.sub!==l)t=this.activeLink=new eV(l,this),l.deps?(t.prevDep=l.depsTail,l.depsTail.nextDep=t,l.depsTail=t):l.deps=l.depsTail=t,function e(t){if(t.dep.sc++,4&t.sub.flags){let n=t.dep.computed;if(n&&!t.dep.subs){n.flags|=20;for(let t=n.deps;t;t=t.nextDep)e(t)}let r=t.dep.subs;r!==t&&(t.prevSub=r,r&&(r.nextSub=t)),t.dep.subs=t}}(t);else if(-1===t.version&&(t.version=this.version,t.nextDep)){let e=t.nextDep;e.prevDep=t.prevDep,t.prevDep&&(t.prevDep.nextDep=e),t.prevDep=l.depsTail,t.nextDep=void 0,l.depsTail.nextDep=t,l.depsTail=t,l.deps===t&&(l.deps=e)}return t}trigger(e){this.version++,eF++,this.notify(e)}notify(e){eT++;try{for(let e=this.subs;e;e=e.prevSub)e.sub.notify()&&e.sub.dep.notify()}finally{ew()}}}let eU=new WeakMap,ej=Symbol(""),eH=Symbol(""),eq=Symbol("");function eW(e,t,n){if(eP&&l){let t=eU.get(e);t||eU.set(e,t=new Map);let r=t.get(n);r||(t.set(n,r=new eB),r.map=t,r.key=n),r.track()}}function eK(e,t,n,r,i,l){let s=eU.get(e);if(!s){eF++;return}let o=e=>{e&&e.trigger()};if(eT++,"clear"===t)s.forEach(o);else{let i=A(e),l=i&&j(n);if(i&&"length"===n){let e=Number(r);s.forEach((t,n)=>{("length"===n||n===eq||!L(n)&&n>=e)&&o(t)})}else switch((void 0!==n||s.has(void 0))&&o(s.get(n)),l&&o(s.get(eq)),t){case"add":i?l&&o(s.get("length")):(o(s.get(ej)),E(e)&&o(s.get(eH)));break;case"delete":!i&&(o(s.get(ej)),E(e)&&o(s.get(eH)));break;case"set":E(e)&&o(s.get(ej))}}ew()}function ez(e){let t=tx(e);return t===e?t:(eW(t,"iterate",eq),t_(e)?t:t.map(tk))}function eJ(e){return eW(e=tx(e),"iterate",eq),e}let eG={__proto__:null,[Symbol.iterator](){return eX(this,Symbol.iterator,tk)},concat(...e){return ez(this).concat(...e.map(e=>A(e)?ez(e):e))},entries(){return eX(this,"entries",e=>(e[1]=tk(e[1]),e))},every(e,t){return eZ(this,"every",e,t,void 0,arguments)},filter(e,t){return eZ(this,"filter",e,t,e=>e.map(tk),arguments)},find(e,t){return eZ(this,"find",e,t,tk,arguments)},findIndex(e,t){return eZ(this,"findIndex",e,t,void 0,arguments)},findLast(e,t){return eZ(this,"findLast",e,t,tk,arguments)},findLastIndex(e,t){return eZ(this,"findLastIndex",e,t,void 0,arguments)},forEach(e,t){return eZ(this,"forEach",e,t,void 0,arguments)},includes(...e){return e0(this,"includes",e)},indexOf(...e){return e0(this,"indexOf",e)},join(e){return ez(this).join(e)},lastIndexOf(...e){return e0(this,"lastIndexOf",e)},map(e,t){return eZ(this,"map",e,t,void 0,arguments)},pop(){return e1(this,"pop")},push(...e){return e1(this,"push",e)},reduce(e,...t){return eY(this,"reduce",e,t)},reduceRight(e,...t){return eY(this,"reduceRight",e,t)},shift(){return e1(this,"shift")},some(e,t){return eZ(this,"some",e,t,void 0,arguments)},splice(...e){return e1(this,"splice",e)},toReversed(){return ez(this).toReversed()},toSorted(e){return ez(this).toSorted(e)},toSpliced(...e){return ez(this).toSpliced(...e)},unshift(...e){return e1(this,"unshift",e)},values(){return eX(this,"values",tk)}};function eX(e,t,n){let r=eJ(e),i=r[t]();return r===e||t_(e)||(i._next=i.next,i.next=()=>{let e=i._next();return e.value&&(e.value=n(e.value)),e}),i}let eQ=Array.prototype;function eZ(e,t,n,r,i,l){let s=eJ(e),o=s!==e&&!t_(e),a=s[t];if(a!==eQ[t]){let t=a.apply(e,l);return o?tk(t):t}let c=n;s!==e&&(o?c=function(t,r){return n.call(this,tk(t),r,e)}:n.length>2&&(c=function(t,r){return n.call(this,t,r,e)}));let u=a.call(s,c,r);return o&&i?i(u):u}function eY(e,t,n,r){let i=eJ(e),l=n;return i!==e&&(t_(e)?n.length>3&&(l=function(t,r,i){return n.call(this,t,r,i,e)}):l=function(t,r,i){return n.call(this,t,tk(r),i,e)}),i[t](l,...r)}function e0(e,t,n){let r=tx(e);eW(r,"iterate",eq);let i=r[t](...n);return(-1===i||!1===i)&&tS(n[0])?(n[0]=tx(n[0]),r[t](...n)):i}function e1(e,t,n=[]){eL(),eT++;let r=tx(e)[t].apply(e,n);return ew(),e$(),r}let e2=g("__proto__,__v_isRef,__isVue"),e3=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>"arguments"!==e&&"caller"!==e).map(e=>Symbol[e]).filter(L));function e6(e){L(e)||(e=String(e));let t=tx(this);return eW(t,"has",e),t.hasOwnProperty(e)}class e4{constructor(e=!1,t=!1){this._isReadonly=e,this._isShallow=t}get(e,t,n){if("__v_skip"===t)return e.__v_skip;let r=this._isReadonly,i=this._isShallow;if("__v_isReactive"===t)return!r;if("__v_isReadonly"===t)return r;if("__v_isShallow"===t)return i;if("__v_raw"===t)return n===(r?i?tf:tp:i?td:tu).get(e)||Object.getPrototypeOf(e)===Object.getPrototypeOf(n)?e:void 0;let l=A(e);if(!r){let e;if(l&&(e=eG[t]))return e;if("hasOwnProperty"===t)return e6}let s=Reflect.get(e,t,tN(e)?e:n);return(L(t)?e3.has(t):e2(t))?s:(r||eW(e,"get",t),i)?s:tN(s)?l&&j(t)?s:s.value:$(s)?r?tg(s):th(s):s}}class e8 extends e4{constructor(e=!1){super(!1,e)}set(e,t,n,r){let i=e[t];if(!this._isShallow){let t=tb(i);if(t_(n)||tb(n)||(i=tx(i),n=tx(n)),!A(e)&&tN(i)&&!tN(n))return!t&&(i.value=n,!0)}let l=A(e)&&j(t)?Number(t)e,tr=e=>Reflect.getPrototypeOf(e);function ti(e){return function(...t){return"delete"!==e&&("clear"===e?void 0:this)}}function tl(e,t){let n=function(e,t){let n={get(n){let r=this.__v_raw,i=tx(r),l=tx(n);e||(Z(n,l)&&eW(i,"get",n),eW(i,"get",l));let{has:s}=tr(i),o=t?tn:e?tT:tk;return s.call(i,n)?o(r.get(n)):s.call(i,l)?o(r.get(l)):void(r!==i&&r.get(n))},get size(){let t=this.__v_raw;return e||eW(tx(t),"iterate",ej),Reflect.get(t,"size",t)},has(t){let n=this.__v_raw,r=tx(n),i=tx(t);return e||(Z(t,i)&&eW(r,"has",t),eW(r,"has",i)),t===i?n.has(t):n.has(t)||n.has(i)},forEach(n,r){let i=this,l=i.__v_raw,s=tx(l),o=t?tn:e?tT:tk;return e||eW(s,"iterate",ej),l.forEach((e,t)=>n.call(r,o(e),o(t),i))}};return k(n,e?{add:ti("add"),set:ti("set"),delete:ti("delete"),clear:ti("clear")}:{add(e){t||t_(e)||tb(e)||(e=tx(e));let n=tx(this);return tr(n).has.call(n,e)||(n.add(e),eK(n,"add",e,e)),this},set(e,n){t||t_(n)||tb(n)||(n=tx(n));let r=tx(this),{has:i,get:l}=tr(r),s=i.call(r,e);s||(e=tx(e),s=i.call(r,e));let o=l.call(r,e);return r.set(e,n),s?Z(n,o)&&eK(r,"set",e,n):eK(r,"add",e,n),this},delete(e){let t=tx(this),{has:n,get:r}=tr(t),i=n.call(t,e);i||(e=tx(e),i=n.call(t,e)),r&&r.call(t,e);let l=t.delete(e);return i&&eK(t,"delete",e,void 0),l},clear(){let e=tx(this),t=0!==e.size,n=e.clear();return t&&eK(e,"clear",void 0,void 0),n}}),["keys","values","entries",Symbol.iterator].forEach(r=>{n[r]=function(...n){let i=this.__v_raw,l=tx(i),s=E(l),o="entries"===r||r===Symbol.iterator&&s,a=i[r](...n),c=t?tn:e?tT:tk;return e||eW(l,"iterate","keys"===r&&s?eH:ej),{next(){let{value:e,done:t}=a.next();return t?{value:e,done:t}:{value:o?[c(e[0]),c(e[1])]:c(e),done:t}},[Symbol.iterator](){return this}}}}),n}(e,t);return(t,r,i)=>"__v_isReactive"===r?!e:"__v_isReadonly"===r?e:"__v_raw"===r?t:Reflect.get(w(n,r)&&r in t?n:t,r,i)}let ts={get:tl(!1,!1)},to={get:tl(!1,!0)},ta={get:tl(!0,!1)},tc={get:tl(!0,!0)},tu=new WeakMap,td=new WeakMap,tp=new WeakMap,tf=new WeakMap;function th(e){return tb(e)?e:ty(e,!1,e9,ts,tu)}function tm(e){return ty(e,!1,te,to,td)}function tg(e){return ty(e,!0,e7,ta,tp)}function ty(e,t,n,r,i){if(!$(e)||e.__v_raw&&!(t&&e.__v_isReactive))return e;let l=i.get(e);if(l)return l;let s=e.__v_skip||!Object.isExtensible(e)?0:function(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}(B(e));if(0===s)return e;let o=new Proxy(e,2===s?r:n);return i.set(e,o),o}function tv(e){return tb(e)?tv(e.__v_raw):!!(e&&e.__v_isReactive)}function tb(e){return!!(e&&e.__v_isReadonly)}function t_(e){return!!(e&&e.__v_isShallow)}function tS(e){return!!e&&!!e.__v_raw}function tx(e){let t=e&&e.__v_raw;return t?tx(t):e}function tC(e){return!w(e,"__v_skip")&&Object.isExtensible(e)&&ee(e,"__v_skip",!0),e}let tk=e=>$(e)?th(e):e,tT=e=>$(e)?tg(e):e;function tN(e){return!!e&&!0===e.__v_isRef}function tw(e){return tE(e,!1)}function tA(e){return tE(e,!0)}function tE(e,t){return tN(e)?e:new tI(e,t)}class tI{constructor(e,t){this.dep=new eB,this.__v_isRef=!0,this.__v_isShallow=!1,this._rawValue=t?e:tx(e),this._value=t?e:tk(e),this.__v_isShallow=t}get value(){return this.dep.track(),this._value}set value(e){let t=this._rawValue,n=this.__v_isShallow||t_(e)||tb(e);Z(e=n?e:tx(e),t)&&(this._rawValue=e,this._value=n?e:tk(e),this.dep.trigger())}}function tR(e){return tN(e)?e.value:e}let tO={get:(e,t,n)=>"__v_raw"===t?e:tR(Reflect.get(e,t,n)),set:(e,t,n,r)=>{let i=e[t];return tN(i)&&!tN(n)?(i.value=n,!0):Reflect.set(e,t,n,r)}};function tP(e){return tv(e)?e:new Proxy(e,tO)}class tM{constructor(e){this.__v_isRef=!0,this._value=void 0;let t=this.dep=new eB,{get:n,set:r}=e(t.track.bind(t),t.trigger.bind(t));this._get=n,this._set=r}get value(){return this._value=this._get()}set value(e){this._set(e)}}function tL(e){return new tM(e)}class t${constructor(e,t,n){this._object=e,this._key=t,this._defaultValue=n,this.__v_isRef=!0,this._value=void 0}get value(){let e=this._object[this._key];return this._value=void 0===e?this._defaultValue:e}set value(e){this._object[this._key]=e}get dep(){return function(e,t){let n=eU.get(e);return n&&n.get(t)}(tx(this._object),this._key)}}class tD{constructor(e){this._getter=e,this.__v_isRef=!0,this.__v_isReadonly=!0,this._value=void 0}get value(){return this._value=this._getter()}}function tF(e,t,n){let r=e[t];return tN(r)?r:new t$(e,t,n)}class tV{constructor(e,t,n){this.fn=e,this.setter=t,this._value=void 0,this.dep=new eB(this),this.__v_isRef=!0,this.deps=void 0,this.depsTail=void 0,this.flags=16,this.globalVersion=eF-1,this.next=void 0,this.effect=this,this.__v_isReadonly=!t,this.isSSR=n}notify(){if(this.flags|=16,!(8&this.flags)&&l!==this)return eN(this,!0),!0}get value(){let e=this.dep.track();return eR(this),e&&(e.version=this.dep.version),this._value}set value(e){this.setter&&this.setter(e)}}let tB={},tU=new WeakMap;function tj(e,t=!1,n=h){if(n){let t=tU.get(n);t||tU.set(n,t=[]),t.push(e)}}function tH(e,t=1/0,n){if(t<=0||!$(e)||e.__v_skip||(n=n||new Set).has(e))return e;if(n.add(e),t--,tN(e))tH(e.value,t,n);else if(A(e))for(let r=0;r{tH(e,t,n)});else if(U(e)){for(let r in e)tH(e[r],t,n);for(let r of Object.getOwnPropertySymbols(e))Object.prototype.propertyIsEnumerable.call(e,r)&&tH(e[r],t,n)}return e}function tq(e,t,n,r){try{return r?e(...r):e()}catch(e){tK(e,t,n)}}function tW(e,t,n,r){if(P(e)){let i=tq(e,t,n,r);return i&&D(i)&&i.catch(e=>{tK(e,t,n)}),i}if(A(e)){let i=[];for(let l=0;l=t8(n)?tz.push(e):tz.splice(function(e){let t=tJ+1,n=tz.length;for(;t>>1,i=tz[r],l=t8(i);lt8(e)-t8(t));if(tG.length=0,tX){tX.push(...e);return}for(tQ=0,tX=e;tQnull==e.id?2&e.flags?-1:1/0:e.id,t5=null,t9=null;function t7(e){let t=t5;return t5=e,t9=e&&e.type.__scopeId||null,t}function ne(e,t=t5,n){if(!t||e._n)return e;let r=(...n)=>{let i;r._d&&im(-1);let l=t7(t);try{i=e(...n)}finally{t7(l),r._d&&im(1)}return i};return r._n=!0,r._c=!0,r._d=!0,r}function nt(e,t,n,r){let i=e.dirs,l=t&&t.dirs;for(let s=0;se.__isTeleport,ni=e=>e&&(e.disabled||""===e.disabled),nl=e=>e&&(e.defer||""===e.defer),ns=e=>"undefined"!=typeof SVGElement&&e instanceof SVGElement,no=e=>"function"==typeof MathMLElement&&e instanceof MathMLElement,na=(e,t)=>{let n=e&&e.to;return M(n)?t?t(n):null:n},nc={name:"Teleport",__isTeleport:!0,process(e,t,n,r,i,l,s,o,a,c){let{mc:u,pc:d,pbc:p,o:{insert:f,querySelector:h,createText:m,createComment:g}}=c,y=ni(t.props),{shapeFlag:b,children:_,dynamicChildren:S}=t;if(null==e){let e=t.el=m(""),c=t.anchor=m("");f(e,n,r),f(c,n,r);let d=(e,t)=>{16&b&&(i&&i.isCE&&(i.ce._teleportTarget=e),u(_,e,t,i,l,s,o,a))},p=()=>{let e=t.target=na(t.props,h),n=np(e,t,m,f);e&&("svg"!==s&&ns(e)?s="svg":"mathml"!==s&&no(e)&&(s="mathml"),y||(d(e,n),nd(t,!1)))};y&&(d(n,c),nd(t,!0)),nl(t.props)?rB(()=>{p(),t.el.__isMounted=!0},l):p()}else{if(nl(t.props)&&!e.el.__isMounted){rB(()=>{nc.process(e,t,n,r,i,l,s,o,a,c),delete e.el.__isMounted},l);return}t.el=e.el,t.targetStart=e.targetStart;let u=t.anchor=e.anchor,f=t.target=e.target,m=t.targetAnchor=e.targetAnchor,g=ni(e.props),b=g?n:f;if("svg"===s||ns(f)?s="svg":("mathml"===s||no(f))&&(s="mathml"),S?(p(e.dynamicChildren,S,b,i,l,s,o),rK(e,t,!0)):a||d(e,t,b,g?u:m,i,l,s,o,!1),y)g?t.props&&e.props&&t.props.to!==e.props.to&&(t.props.to=e.props.to):nu(t,n,u,c,1);else if((t.props&&t.props.to)!==(e.props&&e.props.to)){let e=t.target=na(t.props,h);e&&nu(t,e,null,c,0)}else g&&nu(t,f,m,c,1);nd(t,y)}},remove(e,t,n,{um:r,o:{remove:i}},l){let{shapeFlag:s,children:o,anchor:a,targetStart:c,targetAnchor:u,target:d,props:p}=e;if(d&&(i(c),i(u)),l&&i(a),16&s){let e=l||!ni(p);for(let i=0;i{e.isMounted=!0}),n3(()=>{e.isUnmounting=!0}),e}let ng=[Function,Array],ny={mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:ng,onEnter:ng,onAfterEnter:ng,onEnterCancelled:ng,onBeforeLeave:ng,onLeave:ng,onAfterLeave:ng,onLeaveCancelled:ng,onBeforeAppear:ng,onAppear:ng,onAfterAppear:ng,onAppearCancelled:ng},nv=e=>{let t=e.subTree;return t.component?nv(t.component):t};function nb(e){let t=e[0];if(e.length>1){for(let n of e)if(n.type!==io){t=n;break}}return t}let n_={name:"BaseTransition",props:ny,setup(e,{slots:t}){let n=iL(),r=nm();return()=>{let i=t.default&&nN(t.default(),!0);if(!i||!i.length)return;let l=nb(i),s=tx(e),{mode:o}=s;if(r.isLeaving)return nC(l);let a=nk(l);if(!a)return nC(l);let c=nx(a,s,r,n,e=>c=e);a.type!==io&&nT(a,c);let u=n.subTree&&nk(n.subTree);if(u&&u.type!==io&&!ib(a,u)&&nv(n).type!==io){let e=nx(u,s,r,n);if(nT(u,e),"out-in"===o&&a.type!==io)return r.isLeaving=!0,e.afterLeave=()=>{r.isLeaving=!1,8&n.job.flags||n.update(),delete e.afterLeave,u=void 0},nC(l);"in-out"===o&&a.type!==io?e.delayLeave=(e,t,n)=>{nS(r,u)[String(u.key)]=u,e[nf]=()=>{t(),e[nf]=void 0,delete c.delayedLeave,u=void 0},c.delayedLeave=()=>{n(),delete c.delayedLeave,u=void 0}}:u=void 0}else u&&(u=void 0);return l}}};function nS(e,t){let{leavingVNodes:n}=e,r=n.get(t.type);return r||(r=Object.create(null),n.set(t.type,r)),r}function nx(e,t,n,r,i){let{appear:l,mode:s,persisted:o=!1,onBeforeEnter:a,onEnter:c,onAfterEnter:u,onEnterCancelled:d,onBeforeLeave:p,onLeave:f,onAfterLeave:h,onLeaveCancelled:m,onBeforeAppear:g,onAppear:y,onAfterAppear:b,onAppearCancelled:_}=t,S=String(e.key),x=nS(n,e),C=(e,t)=>{e&&tW(e,r,9,t)},k=(e,t)=>{let n=t[1];C(e,t),A(e)?e.every(e=>e.length<=1)&&n():e.length<=1&&n()},T={mode:s,persisted:o,beforeEnter(t){let r=a;if(!n.isMounted){if(!l)return;r=g||a}t[nf]&&t[nf](!0);let i=x[S];i&&ib(e,i)&&i.el[nf]&&i.el[nf](),C(r,[t])},enter(e){let t=c,r=u,i=d;if(!n.isMounted){if(!l)return;t=y||c,r=b||u,i=_||d}let s=!1,o=e[nh]=t=>{s||(s=!0,t?C(i,[e]):C(r,[e]),T.delayedLeave&&T.delayedLeave(),e[nh]=void 0)};t?k(t,[e,o]):o()},leave(t,r){let i=String(e.key);if(t[nh]&&t[nh](!0),n.isUnmounting)return r();C(p,[t]);let l=!1,s=t[nf]=n=>{l||(l=!0,r(),n?C(m,[t]):C(h,[t]),t[nf]=void 0,x[i]!==e||delete x[i])};x[i]=e,f?k(f,[t,s]):s()},clone(e){let l=nx(e,t,n,r,i);return i&&i(l),l}};return T}function nC(e){if(nq(e))return(e=iT(e)).children=null,e}function nk(e){if(!nq(e))return nr(e.type)&&e.children?nb(e.children):e;let{shapeFlag:t,children:n}=e;if(n){if(16&t)return n[0];if(32&t&&P(n.default))return n.default()}}function nT(e,t){6&e.shapeFlag&&e.component?(e.transition=t,nT(e.component.subTree,t)):128&e.shapeFlag?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function nN(e,t=!1,n){let r=[],i=0;for(let l=0;l1)for(let e=0;enE(e,t&&(A(t)?t[l]:t),n,r,i));return}if(nj(r)&&!i){512&r.shapeFlag&&r.type.__asyncResolved&&r.component.subTree.component&&nE(e,t,n,r.component.subTree);return}let l=4&r.shapeFlag?iW(r.component):r.el,s=i?null:l,{i:o,r:a}=e,c=t&&t.r,u=o.refs===y?o.refs={}:o.refs,d=o.setupState,p=tx(d),f=d===y?()=>!1:e=>w(p,e);if(null!=c&&c!==a&&(M(c)?(u[c]=null,f(c)&&(d[c]=null)):tN(c)&&(c.value=null)),P(a))tq(a,o,12,[s,u]);else{let t=M(a),r=tN(a);if(t||r){let o=()=>{if(e.f){let n=t?f(a)?d[a]:u[a]:a.value;i?A(n)&&T(n,l):A(n)?n.includes(l)||n.push(l):t?(u[a]=[l],f(a)&&(d[a]=u[a])):(a.value=[l],e.k&&(u[e.k]=a.value))}else t?(u[a]=s,f(a)&&(d[a]=s)):r&&(a.value=s,e.k&&(u[e.k]=s))};s?(o.id=-1,rB(o,n)):o()}}}let nI=!1,nR=()=>{nI||(console.error("Hydration completed but contains mismatches."),nI=!0)},nO=e=>e.namespaceURI.includes("svg")&&"foreignObject"!==e.tagName,nP=e=>e.namespaceURI.includes("MathML"),nM=e=>{if(1===e.nodeType){if(nO(e))return"svg";if(nP(e))return"mathml"}},nL=e=>8===e.nodeType;function n$(e){let{mt:t,p:n,o:{patchProp:r,createText:i,nextSibling:l,parentNode:s,remove:o,insert:a,createComment:c}}=e,u=(n,r,o,c,b,_=!1)=>{_=_||!!r.dynamicChildren;let S=nL(n)&&"["===n.data,x=()=>h(n,r,o,c,b,S),{type:C,ref:k,shapeFlag:T,patchFlag:N}=r,w=n.nodeType;r.el=n,-2===N&&(_=!1,r.dynamicChildren=null);let A=null;switch(C){case is:3!==w?""===r.children?(a(r.el=i(""),s(n),n),A=n):A=x():(n.data!==r.children&&(nR(),n.data=r.children),A=l(n));break;case io:y(n)?(A=l(n),g(r.el=n.content.firstChild,n,o)):A=8!==w||S?x():l(n);break;case ia:if(S&&(w=(n=l(n)).nodeType),1===w||3===w){A=n;let e=!r.children.length;for(let t=0;t{s=s||!!t.dynamicChildren;let{type:a,props:c,patchFlag:u,shapeFlag:d,dirs:f,transition:h}=t,m="input"===a||"option"===a;if(m||-1!==u){let a;f&&nt(t,null,n,"created");let b=!1;if(y(e)){b=rW(null,h)&&n&&n.vnode.props&&n.vnode.props.appear;let r=e.content.firstChild;b&&h.beforeEnter(r),g(r,e,n),t.el=e=r}if(16&d&&!(c&&(c.innerHTML||c.textContent))){let r=p(e.firstChild,t,e,n,i,l,s);for(;r;){nV(e,1)||nR();let t=r;r=r.nextSibling,o(t)}}else if(8&d){let n=t.children;"\n"===n[0]&&("PRE"===e.tagName||"TEXTAREA"===e.tagName)&&(n=n.slice(1)),e.textContent!==n&&(nV(e,0)||nR(),e.textContent=t.children)}if(c){if(m||!s||48&u){let t=e.tagName.includes("-");for(let i in c)(m&&(i.endsWith("value")||"indeterminate"===i)||x(i)&&!H(i)||"."===i[0]||t)&&r(e,i,null,c[i],void 0,n)}else if(c.onClick)r(e,"onClick",null,c.onClick,void 0,n);else if(4&u&&tv(c.style))for(let e in c.style)c.style[e]}(a=c&&c.onVnodeBeforeMount)&&iR(a,n,t),f&&nt(t,null,n,"beforeMount"),((a=c&&c.onVnodeMounted)||f||b)&&ir(()=>{a&&iR(a,n,t),b&&h.enter(e),f&&nt(t,null,n,"mounted")},i)}return e.nextSibling},p=(e,t,r,s,o,c,d)=>{d=d||!!t.dynamicChildren;let p=t.children,f=p.length;for(let t=0;t{let{slotScopeIds:u}=t;u&&(i=i?i.concat(u):u);let d=s(e),f=p(l(e),t,d,n,r,i,o);return f&&nL(f)&&"]"===f.data?l(t.anchor=f):(nR(),a(t.anchor=c("]"),d,f),f)},h=(e,t,r,i,a,c)=>{if(nV(e.parentElement,1)||nR(),t.el=null,c){let t=m(e);for(;;){let n=l(e);if(n&&n!==t)o(n);else break}}let u=l(e),d=s(e);return o(e),n(null,t,d,u,r,i,nM(d),a),r&&(r.vnode.el=t.el,r8(r,t.el)),u},m=(e,t="[",n="]")=>{let r=0;for(;e;)if((e=l(e))&&nL(e)&&(e.data===t&&r++,e.data===n)){if(0===r)return l(e);r--}return e},g=(e,t,n)=>{let r=t.parentNode;r&&r.replaceChild(e,t);let i=n;for(;i;)i.vnode.el===t&&(i.vnode.el=i.subTree.el=e),i=i.parent},y=e=>1===e.nodeType&&"TEMPLATE"===e.tagName;return[(e,t)=>{if(!t.hasChildNodes()){n(null,e,t),t4(),t._vnode=e;return}u(t.firstChild,e,null,null,null),t4(),t._vnode=e},u]}let nD="data-allow-mismatch",nF={0:"text",1:"children",2:"class",3:"style",4:"attribute"};function nV(e,t){if(0===t||1===t)for(;e&&!e.hasAttribute(nD);)e=e.parentElement;let n=e&&e.getAttribute(nD);if(null==n)return!1;if(""===n)return!0;{let e=n.split(",");return!!(0===t&&e.includes("children"))||n.split(",").includes(nF[t])}}let nB=er().requestIdleCallback||(e=>setTimeout(e,1)),nU=er().cancelIdleCallback||(e=>clearTimeout(e)),nj=e=>!!e.type.__asyncLoader;function nH(e,t){let{ref:n,props:r,children:i,ce:l}=t.vnode,s=iC(e,r,i);return s.ref=n,s.ce=l,delete t.vnode.ce,s}let nq=e=>e.type.__isKeepAlive;function nW(e,t){return A(e)?e.some(e=>nW(e,t)):M(e)?e.split(",").includes(t):!!O(e)&&(e.lastIndex=0,e.test(t))}function nK(e,t){nJ(e,"a",t)}function nz(e,t){nJ(e,"da",t)}function nJ(e,t,n=iM){let r=e.__wdc||(e.__wdc=()=>{let t=n;for(;t;){if(t.isDeactivated)return;t=t.parent}return e()});if(nQ(t,r,n),n){let e=n.parent;for(;e&&e.parent;)nq(e.parent.vnode)&&function(e,t,n,r){let i=nQ(t,e,r,!0);n6(()=>{T(r[t],i)},n)}(r,t,n,e),e=e.parent}}function nG(e){e.shapeFlag&=-257,e.shapeFlag&=-513}function nX(e){return 128&e.shapeFlag?e.ssContent:e}function nQ(e,t,n=iM,r=!1){if(n){let i=n[e]||(n[e]=[]),l=t.__weh||(t.__weh=(...r)=>{eL();let i=i$(n),l=tW(t,n,e,r);return i(),e$(),l});return r?i.unshift(l):i.push(l),l}}let nZ=e=>(t,n=iM)=>{iV&&"sp"!==e||nQ(e,(...e)=>t(...e),n)},nY=nZ("bm"),n0=nZ("m"),n1=nZ("bu"),n2=nZ("u"),n3=nZ("bum"),n6=nZ("um"),n4=nZ("sp"),n8=nZ("rtg"),n5=nZ("rtc");function n9(e,t=iM){nQ("ec",e,t)}let n7="components",re=Symbol.for("v-ndc");function rt(e,t,n=!0,r=!1){let i=t5||iM;if(i){let n=i.type;if(e===n7){let e=iK(n,!1);if(e&&(e===t||e===z(t)||e===X(z(t))))return n}let l=rn(i[e]||n[e],t)||rn(i.appContext[e],t);return!l&&r?n:l}}function rn(e,t){return e&&(e[t]||e[z(t)]||e[X(z(t))])}let rr=e=>e?iF(e)?iW(e):rr(e.parent):null,ri=k(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>rr(e.parent),$root:e=>rr(e.root),$host:e=>e.ce,$emit:e=>e.emit,$options:e=>rp(e),$forceUpdate:e=>e.f||(e.f=()=>{t1(e.update)}),$nextTick:e=>e.n||(e.n=t0.bind(e.proxy)),$watch:e=>rQ.bind(e)}),rl=(e,t)=>e!==y&&!e.__isScriptSetup&&w(e,t),rs={get({_:e},t){let n,r,i;if("__v_skip"===t)return!0;let{ctx:l,setupState:s,data:o,props:a,accessCache:c,type:u,appContext:d}=e;if("$"!==t[0]){let r=c[t];if(void 0!==r)switch(r){case 1:return s[t];case 2:return o[t];case 4:return l[t];case 3:return a[t]}else{if(rl(s,t))return c[t]=1,s[t];if(o!==y&&w(o,t))return c[t]=2,o[t];if((n=e.propsOptions[0])&&w(n,t))return c[t]=3,a[t];if(l!==y&&w(l,t))return c[t]=4,l[t];ru&&(c[t]=0)}}let p=ri[t];return p?("$attrs"===t&&eW(e.attrs,"get",""),p(e)):(r=u.__cssModules)&&(r=r[t])?r:l!==y&&w(l,t)?(c[t]=4,l[t]):w(i=d.config.globalProperties,t)?i[t]:void 0},set({_:e},t,n){let{data:r,setupState:i,ctx:l}=e;return rl(i,t)?(i[t]=n,!0):r!==y&&w(r,t)?(r[t]=n,!0):!w(e.props,t)&&!("$"===t[0]&&t.slice(1)in e)&&(l[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:r,appContext:i,propsOptions:l}},s){let o;return!!n[s]||e!==y&&w(e,s)||rl(t,s)||(o=l[0])&&w(o,s)||w(r,s)||w(ri,s)||w(i.config.globalProperties,s)},defineProperty(e,t,n){return null!=n.get?e._.accessCache[t]=0:w(n,"value")&&this.set(e,t,n.value,null),Reflect.defineProperty(e,t,n)}},ro=k({},rs,{get(e,t){if(t!==Symbol.unscopables)return rs.get(e,t,e)},has:(e,t)=>"_"!==t[0]&&!ei(t)});function ra(){let e=iL();return e.setupContext||(e.setupContext=iq(e))}function rc(e){return A(e)?e.reduce((e,t)=>(e[t]=null,e),{}):e}let ru=!0;function rd(e,t,n){tW(A(e)?e.map(e=>e.bind(t.proxy)):e.bind(t.proxy),t,n)}function rp(e){let t;let n=e.type,{mixins:r,extends:i}=n,{mixins:l,optionsCache:s,config:{optionMergeStrategies:o}}=e.appContext,a=s.get(n);return a?t=a:l.length||r||i?(t={},l.length&&l.forEach(e=>rf(t,e,o,!0)),rf(t,n,o)):t=n,$(n)&&s.set(n,t),t}function rf(e,t,n,r=!1){let{mixins:i,extends:l}=t;for(let s in l&&rf(e,l,n,!0),i&&i.forEach(t=>rf(e,t,n,!0)),t)if(r&&"expose"===s);else{let r=rh[s]||n&&n[s];e[s]=r?r(e[s],t[s]):t[s]}return e}let rh={data:rm,props:rb,emits:rb,methods:rv,computed:rv,beforeCreate:ry,created:ry,beforeMount:ry,mounted:ry,beforeUpdate:ry,updated:ry,beforeDestroy:ry,beforeUnmount:ry,destroyed:ry,unmounted:ry,activated:ry,deactivated:ry,errorCaptured:ry,serverPrefetch:ry,components:rv,directives:rv,watch:function(e,t){if(!e)return t;if(!t)return e;let n=k(Object.create(null),e);for(let r in t)n[r]=ry(e[r],t[r]);return n},provide:rm,inject:function(e,t){return rv(rg(e),rg(t))}};function rm(e,t){return t?e?function(){return k(P(e)?e.call(this,this):e,P(t)?t.call(this,this):t)}:t:e}function rg(e){if(A(e)){let t={};for(let n=0;n1)return n&&P(t)?t.call(r&&r.proxy):t}}let rT={},rN=()=>Object.create(rT),rw=e=>Object.getPrototypeOf(e)===rT;function rA(e,t,n,r){let i;let[l,s]=e.propsOptions,o=!1;if(t)for(let a in t){let c;if(H(a))continue;let u=t[a];l&&w(l,c=z(a))?s&&s.includes(c)?(i||(i={}))[c]=u:n[c]=u:r1(e.emitsOptions,a)||a in r&&u===r[a]||(r[a]=u,o=!0)}if(s){let t=tx(n),r=i||y;for(let i=0;i"_"===e[0]||"$stable"===e,rP=e=>A(e)?e.map(iw):[iw(e)],rM=(e,t,n)=>{if(t._n)return t;let r=ne((...e)=>rP(t(...e)),n);return r._c=!1,r},rL=(e,t,n)=>{let r=e._ctx;for(let n in e){if(rO(n))continue;let i=e[n];if(P(i))t[n]=rM(n,i,r);else if(null!=i){let e=rP(i);t[n]=()=>e}}},r$=(e,t)=>{let n=rP(t);e.slots.default=()=>n},rD=(e,t,n)=>{for(let r in t)(n||"_"!==r)&&(e[r]=t[r])},rF=(e,t,n)=>{let r=e.slots=rN();if(32&e.vnode.shapeFlag){let e=t._;e?(rD(r,t,n),n&&ee(r,"_",e,!0)):rL(t,r)}else t&&r$(e,t)},rV=(e,t,n)=>{let{vnode:r,slots:i}=e,l=!0,s=y;if(32&r.shapeFlag){let e=t._;e?n&&1===e?l=!1:rD(i,t,n):(l=!t.$stable,rL(t,i)),s=t}else t&&(r$(e,t),s={default:1});if(l)for(let e in i)rO(e)||null!=s[e]||delete i[e]},rB=ir;function rU(e){return rj(e,n$)}function rj(e,t){var n;let r,i;er().__VUE__=!0;let{insert:l,remove:s,patchProp:o,createElement:a,createText:u,createComment:d,setText:p,setElementText:f,parentNode:h,nextSibling:m,setScopeId:g=_,insertStaticContent:S}=e,x=(e,t,n,r=null,i=null,l=null,s,o=null,a=!!t.dynamicChildren)=>{if(e===t)return;e&&!ib(e,t)&&(r=eo(e),et(e,i,l,!0),e=null),-2===t.patchFlag&&(a=!1,t.dynamicChildren=null);let{type:c,ref:u,shapeFlag:d}=t;switch(c){case is:C(e,t,n,r);break;case io:T(e,t,n,r);break;case ia:null==e&&N(t,n,r,s);break;case il:U(e,t,n,r,i,l,s,o,a);break;default:1&d?R(e,t,n,r,i,l,s,o,a):6&d?j(e,t,n,r,i,l,s,o,a):64&d?c.process(e,t,n,r,i,l,s,o,a,eu):128&d&&c.process(e,t,n,r,i,l,s,o,a,eu)}null!=u&&i&&nE(u,e&&e.ref,l,t||e,!t)},C=(e,t,n,r)=>{if(null==e)l(t.el=u(t.children),n,r);else{let n=t.el=e.el;t.children!==e.children&&p(n,t.children)}},T=(e,t,n,r)=>{null==e?l(t.el=d(t.children||""),n,r):t.el=e.el},N=(e,t,n,r)=>{[e.el,e.anchor]=S(e.children,t,n,r,e.el,e.anchor)},E=({el:e,anchor:t},n,r)=>{let i;for(;e&&e!==t;)i=m(e),l(e,n,r),e=i;l(t,n,r)},I=({el:e,anchor:t})=>{let n;for(;e&&e!==t;)n=m(e),s(e),e=n;s(t)},R=(e,t,n,r,i,l,s,o,a)=>{"svg"===t.type?s="svg":"math"===t.type&&(s="mathml"),null==e?O(t,n,r,i,l,s,o,a):F(e,t,i,l,s,o,a)},O=(e,t,n,r,i,s,c,u)=>{let d,p;let{props:h,shapeFlag:m,transition:g,dirs:y}=e;if(d=e.el=a(e.type,s,h&&h.is,h),8&m?f(d,e.children):16&m&&L(e.children,d,null,r,i,rH(e,s),c,u),y&&nt(e,null,r,"created"),M(d,e,e.scopeId,c,r),h){for(let e in h)"value"===e||H(e)||o(d,e,null,h[e],s,r);"value"in h&&o(d,"value",null,h.value,s),(p=h.onVnodeBeforeMount)&&iR(p,r,e)}y&&nt(e,null,r,"beforeMount");let b=rW(i,g);b&&g.beforeEnter(d),l(d,t,n),((p=h&&h.onVnodeMounted)||b||y)&&rB(()=>{p&&iR(p,r,e),b&&g.enter(d),y&&nt(e,null,r,"mounted")},i)},M=(e,t,n,r,i)=>{if(n&&g(e,n),r)for(let t=0;t{for(let c=a;c{let a;let c=t.el=e.el,{patchFlag:u,dynamicChildren:d,dirs:p}=t;u|=16&e.patchFlag;let h=e.props||y,m=t.props||y;if(n&&rq(n,!1),(a=m.onVnodeBeforeUpdate)&&iR(a,n,t,e),p&&nt(t,e,n,"beforeUpdate"),n&&rq(n,!0),(h.innerHTML&&null==m.innerHTML||h.textContent&&null==m.textContent)&&f(c,""),d?V(e.dynamicChildren,d,c,n,r,rH(t,i),l):s||X(e,t,c,null,n,r,rH(t,i),l,!1),u>0){if(16&u)B(c,h,m,n,i);else if(2&u&&h.class!==m.class&&o(c,"class",null,m.class,i),4&u&&o(c,"style",h.style,m.style,i),8&u){let e=t.dynamicProps;for(let t=0;t{a&&iR(a,n,t,e),p&&nt(t,e,n,"updated")},r)},V=(e,t,n,r,i,l,s)=>{for(let o=0;o{if(t!==n){if(t!==y)for(let l in t)H(l)||l in n||o(e,l,t[l],null,i,r);for(let l in n){if(H(l))continue;let s=n[l],a=t[l];s!==a&&"value"!==l&&o(e,l,a,s,i,r)}"value"in n&&o(e,"value",t.value,n.value,i)}},U=(e,t,n,r,i,s,o,a,c)=>{let d=t.el=e?e.el:u(""),p=t.anchor=e?e.anchor:u(""),{patchFlag:f,dynamicChildren:h,slotScopeIds:m}=t;m&&(a=a?a.concat(m):m),null==e?(l(d,n,r),l(p,n,r),L(t.children||[],n,p,i,s,o,a,c)):f>0&&64&f&&h&&e.dynamicChildren?(V(e.dynamicChildren,h,n,i,s,o,a),(null!=t.key||i&&t===i.subTree)&&rK(e,t,!0)):X(e,t,n,p,i,s,o,a,c)},j=(e,t,n,r,i,l,s,o,a)=>{t.slotScopeIds=o,null==e?512&t.shapeFlag?i.ctx.activate(t,n,r,s,a):q(t,n,r,i,l,s,a):W(e,t,a)},q=(e,t,n,r,i,l,s)=>{let o=e.component=function(e,t,n){let r=e.type,i=(t?t.appContext:e.appContext)||iO,l={uid:iP++,vnode:e,type:r,parent:t,appContext:i,root:null,next:null,subTree:null,effect:null,update:null,job:null,scope:new ex(!0),render:null,proxy:null,exposed:null,exposeProxy:null,withProxy:null,provides:t?t.provides:Object.create(i.provides),ids:t?t.ids:["",0,0],accessCache:null,renderCache:[],components:null,directives:null,propsOptions:function e(t,n,r=!1){let i=r?rI:n.propsCache,l=i.get(t);if(l)return l;let s=t.props,o={},a=[],c=!1;if(!P(t)){let i=t=>{c=!0;let[r,i]=e(t,n,!0);k(o,r),i&&a.push(...i)};!r&&n.mixins.length&&n.mixins.forEach(i),t.extends&&i(t.extends),t.mixins&&t.mixins.forEach(i)}if(!s&&!c)return $(t)&&i.set(t,b),b;if(A(s))for(let e=0;e{let r=e(t,n,!0);r&&(a=!0,k(o,r))};!r&&n.mixins.length&&n.mixins.forEach(i),t.extends&&i(t.extends),t.mixins&&t.mixins.forEach(i)}return s||a?(A(s)?s.forEach(e=>o[e]=null):k(o,s),$(t)&&i.set(t,o),o):($(t)&&i.set(t,null),null)}(r,i),emit:null,emitted:null,propsDefaults:y,inheritAttrs:r.inheritAttrs,ctx:y,data:y,props:y,attrs:y,slots:y,refs:y,setupState:y,setupContext:null,suspense:n,suspenseId:n?n.pendingId:0,asyncDep:null,asyncResolved:!1,isMounted:!1,isUnmounted:!1,isDeactivated:!1,bc:null,c:null,bm:null,m:null,bu:null,u:null,um:null,bum:null,da:null,a:null,rtg:null,rtc:null,ec:null,sp:null};return l.ctx={_:l},l.root=t?t.root:l,l.emit=r0.bind(null,l),e.ce&&e.ce(l),l}(e,r,i);nq(e)&&(o.ctx.renderer=eu),function(e,t=!1,n=!1){t&&c(t);let{props:r,children:i}=e.vnode,l=iF(e);(function(e,t,n,r=!1){let i={},l=rN();for(let n in e.propsDefaults=Object.create(null),rA(e,t,i,l),e.propsOptions[0])n in i||(i[n]=void 0);n?e.props=r?i:tm(i):e.type.props?e.props=i:e.props=l,e.attrs=l})(e,r,l,t),rF(e,i,n),l&&function(e,t){let n=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,rs);let{setup:r}=n;if(r){eL();let n=e.setupContext=r.length>1?iq(e):null,i=i$(e),l=tq(r,e,0,[e.props,n]),s=D(l);if(e$(),i(),(s||e.sp)&&!nj(e)&&nA(e),s){if(l.then(iD,iD),t)return l.then(n=>{iB(e,n,t)}).catch(t=>{tK(t,e,0)});e.asyncDep=l}else iB(e,l,t)}else ij(e,t)}(e,t),t&&c(!1)}(o,!1,s),o.asyncDep?(i&&i.registerDep(o,K,s),e.el||T(null,o.subTree=iC(io),t,n)):K(o,e,t,n,i,l,s)},W=(e,t,n)=>{let r=t.component=e.component;if(function(e,t,n){let{props:r,children:i,component:l}=e,{props:s,children:o,patchFlag:a}=t,c=l.emitsOptions;if(t.dirs||t.transition)return!0;if(!n||!(a>=0))return(!!i||!!o)&&(!o||!o.$stable)||r!==s&&(r?!s||r4(r,s,c):!!s);if(1024&a)return!0;if(16&a)return r?r4(r,s,c):!!s;if(8&a){let e=t.dynamicProps;for(let t=0;t{let a=()=>{if(e.isMounted){let t,{next:n,bu:r,u:i,parent:c,vnode:u}=e;{let t=function e(t){let n=t.subTree.component;if(n)return n.asyncDep&&!n.asyncResolved?n:e(n)}(e);if(t){n&&(n.el=u.el,J(e,n,o)),t.asyncDep.then(()=>{e.isUnmounted||a()});return}}let d=n;rq(e,!1),n?(n.el=u.el,J(e,n,o)):n=u,r&&Y(r),(t=n.props&&n.props.onVnodeBeforeUpdate)&&iR(t,c,n,u),rq(e,!0);let p=r2(e),f=e.subTree;e.subTree=p,x(f,p,h(f.el),eo(f),e,l,s),n.el=p.el,null===d&&r8(e,p.el),i&&rB(i,l),(t=n.props&&n.props.onVnodeUpdated)&&rB(()=>iR(t,c,n,u),l)}else{let o;let{el:a,props:c}=t,{bm:u,m:d,parent:p,root:f,type:h}=e,m=nj(t);if(rq(e,!1),u&&Y(u),!m&&(o=c&&c.onVnodeBeforeMount)&&iR(o,p,t),rq(e,!0),a&&i){let t=()=>{e.subTree=r2(e),i(a,e.subTree,e,l,null)};m&&h.__asyncHydrate?h.__asyncHydrate(a,e,t):t()}else{f.ce&&f.ce._injectChildStyle(h);let i=e.subTree=r2(e);x(null,i,n,r,e,l,s),t.el=i.el}if(d&&rB(d,l),!m&&(o=c&&c.onVnodeMounted)){let e=t;rB(()=>iR(o,p,e),l)}(256&t.shapeFlag||p&&nj(p.vnode)&&256&p.vnode.shapeFlag)&&e.a&&rB(e.a,l),e.isMounted=!0,t=n=r=null}};e.scope.on();let c=e.effect=new ek(a);e.scope.off();let u=e.update=c.run.bind(c),d=e.job=c.runIfDirty.bind(c);d.i=e,d.id=e.uid,c.scheduler=()=>t1(d),rq(e,!0),u()},J=(e,t,n)=>{t.component=e;let r=e.vnode.props;e.vnode=t,e.next=null,function(e,t,n,r){let{props:i,attrs:l,vnode:{patchFlag:s}}=e,o=tx(i),[a]=e.propsOptions,c=!1;if((r||s>0)&&!(16&s)){if(8&s){let n=e.vnode.dynamicProps;for(let r=0;r{let c=e&&e.children,u=e?e.shapeFlag:0,d=t.children,{patchFlag:p,shapeFlag:h}=t;if(p>0){if(128&p){Z(c,d,n,r,i,l,s,o,a);return}if(256&p){Q(c,d,n,r,i,l,s,o,a);return}}8&h?(16&u&&es(c,i,l),d!==c&&f(n,d)):16&u?16&h?Z(c,d,n,r,i,l,s,o,a):es(c,i,l,!0):(8&u&&f(n,""),16&h&&L(d,n,r,i,l,s,o,a))},Q=(e,t,n,r,i,l,s,o,a)=>{let c;e=e||b,t=t||b;let u=e.length,d=t.length,p=Math.min(u,d);for(c=0;cd?es(e,i,l,!0,!1,p):L(t,n,r,i,l,s,o,a,p)},Z=(e,t,n,r,i,l,s,o,a)=>{let c=0,u=t.length,d=e.length-1,p=u-1;for(;c<=d&&c<=p;){let r=e[c],u=t[c]=a?iA(t[c]):iw(t[c]);if(ib(r,u))x(r,u,n,null,i,l,s,o,a);else break;c++}for(;c<=d&&c<=p;){let r=e[d],c=t[p]=a?iA(t[p]):iw(t[p]);if(ib(r,c))x(r,c,n,null,i,l,s,o,a);else break;d--,p--}if(c>d){if(c<=p){let e=p+1,d=ep)for(;c<=d;)et(e[c],i,l,!0),c++;else{let f;let h=c,m=c,g=new Map;for(c=m;c<=p;c++){let e=t[c]=a?iA(t[c]):iw(t[c]);null!=e.key&&g.set(e.key,c)}let y=0,_=p-m+1,S=!1,C=0,k=Array(_);for(c=0;c<_;c++)k[c]=0;for(c=h;c<=d;c++){let r;let u=e[c];if(y>=_){et(u,i,l,!0);continue}if(null!=u.key)r=g.get(u.key);else for(f=m;f<=p;f++)if(0===k[f-m]&&ib(u,t[f])){r=f;break}void 0===r?et(u,i,l,!0):(k[r-m]=c+1,r>=C?C=r:S=!0,x(u,t[r],n,null,i,l,s,o,a),y++)}let T=S?function(e){let t,n,r,i,l;let s=e.slice(),o=[0],a=e.length;for(t=0;t>1]]0&&(s[t]=o[r-1]),o[r]=t)}}for(r=o.length,i=o[r-1];r-- >0;)o[r]=i,i=s[i];return o}(k):b;for(f=T.length-1,c=_-1;c>=0;c--){let e=m+c,d=t[e],p=e+1{let{el:s,type:o,transition:a,children:c,shapeFlag:u}=e;if(6&u){ee(e.component.subTree,t,n,r);return}if(128&u){e.suspense.move(t,n,r);return}if(64&u){o.move(e,t,n,eu);return}if(o===il){l(s,t,n);for(let e=0;ea.enter(s),i);else{let{leave:e,delayLeave:r,afterLeave:i}=a,o=()=>l(s,t,n),c=()=>{e(s,()=>{o(),i&&i()})};r?r(s,o,c):c()}}else l(s,t,n)},et=(e,t,n,r=!1,i=!1)=>{let l;let{type:s,props:o,ref:a,children:c,dynamicChildren:u,shapeFlag:d,patchFlag:p,dirs:f,cacheIndex:h}=e;if(-2===p&&(i=!1),null!=a&&nE(a,null,n,e,!0),null!=h&&(t.renderCache[h]=void 0),256&d){t.ctx.deactivate(e);return}let m=1&d&&f,g=!nj(e);if(g&&(l=o&&o.onVnodeBeforeUnmount)&&iR(l,t,e),6&d)el(e.component,n,r);else{if(128&d){e.suspense.unmount(n,r);return}m&&nt(e,null,t,"beforeUnmount"),64&d?e.type.remove(e,t,n,eu,r):u&&!u.hasOnce&&(s!==il||p>0&&64&p)?es(u,t,n,!1,!0):(s===il&&384&p||!i&&16&d)&&es(c,t,n),r&&en(e)}(g&&(l=o&&o.onVnodeUnmounted)||m)&&rB(()=>{l&&iR(l,t,e),m&&nt(e,null,t,"unmounted")},n)},en=e=>{let{type:t,el:n,anchor:r,transition:i}=e;if(t===il){ei(n,r);return}if(t===ia){I(e);return}let l=()=>{s(n),i&&!i.persisted&&i.afterLeave&&i.afterLeave()};if(1&e.shapeFlag&&i&&!i.persisted){let{leave:t,delayLeave:r}=i,s=()=>t(n,l);r?r(e.el,l,s):s()}else l()},ei=(e,t)=>{let n;for(;e!==t;)n=m(e),s(e),e=n;s(t)},el=(e,t,n)=>{let{bum:r,scope:i,job:l,subTree:s,um:o,m:a,a:c}=e;rz(a),rz(c),r&&Y(r),i.stop(),l&&(l.flags|=8,et(s,e,t,n)),o&&rB(o,t),rB(()=>{e.isUnmounted=!0},t),t&&t.pendingBranch&&!t.isUnmounted&&e.asyncDep&&!e.asyncResolved&&e.suspenseId===t.pendingId&&(t.deps--,0===t.deps&&t.resolve())},es=(e,t,n,r=!1,i=!1,l=0)=>{for(let s=l;s{if(6&e.shapeFlag)return eo(e.component.subTree);if(128&e.shapeFlag)return e.suspense.next();let t=m(e.anchor||e.el),n=t&&t[nn];return n?m(n):t},ea=!1,ec=(e,t,n)=>{null==e?t._vnode&&et(t._vnode,null,null,!0):x(t._vnode||null,e,t,null,null,null,n),t._vnode=e,ea||(ea=!0,t6(),t4(),ea=!1)},eu={p:x,um:et,m:ee,r:en,mt:q,mc:L,pc:X,pbc:V,n:eo,o:e};return t&&([r,i]=t(eu)),{render:ec,hydrate:r,createApp:(n=r,function(e,t=null){P(e)||(e=k({},e)),null==t||$(t)||(t=null);let r=r_(),i=new WeakSet,l=[],s=!1,o=r.app={_uid:rS++,_component:e,_props:t,_container:null,_context:r,_instance:null,version:iX,get config(){return r.config},set config(v){},use:(e,...t)=>(i.has(e)||(e&&P(e.install)?(i.add(e),e.install(o,...t)):P(e)&&(i.add(e),e(o,...t))),o),mixin:e=>(r.mixins.includes(e)||r.mixins.push(e),o),component:(e,t)=>t?(r.components[e]=t,o):r.components[e],directive:(e,t)=>t?(r.directives[e]=t,o):r.directives[e],mount(i,l,a){if(!s){let c=o._ceVNode||iC(e,t);return c.appContext=r,!0===a?a="svg":!1===a&&(a=void 0),l&&n?n(c,i):ec(c,i,a),s=!0,o._container=i,i.__vue_app__=o,iW(c.component)}},onUnmount(e){l.push(e)},unmount(){s&&(tW(l,o._instance,16),ec(null,o._container),delete o._container.__vue_app__)},provide:(e,t)=>(r.provides[e]=t,o),runWithContext(e){let t=rx;rx=o;try{return e()}finally{rx=t}}};return o})}}function rH({type:e,props:t},n){return"svg"===n&&"foreignObject"===e||"mathml"===n&&"annotation-xml"===e&&t&&t.encoding&&t.encoding.includes("html")?void 0:n}function rq({effect:e,job:t},n){n?(e.flags|=32,t.flags|=4):(e.flags&=-33,t.flags&=-5)}function rW(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}function rK(e,t,n=!1){let r=e.children,i=t.children;if(A(r)&&A(i))for(let e=0;etW(e,c,t,n);let u=!1;return"post"===s?a.scheduler=e=>{rB(e,c&&c.suspense)}:"sync"!==s&&(u=!0,a.scheduler=(e,t)=>{t?e():t1(e)}),a.augmentJob=e=>{t&&(e.flags|=4),u&&(e.flags|=2,c&&(e.id=c.uid,e.i=c))},function(e,t,n=y){let r,l,s,o;let{immediate:a,deep:c,once:u,scheduler:d,augmentJob:p,call:f}=n,m=e=>c?e:t_(e)||!1===c||0===c?tH(e,1):tH(e),g=!1,b=!1;if(tN(e)?(l=()=>e.value,g=t_(e)):tv(e)?(l=()=>m(e),g=!0):A(e)?(b=!0,g=e.some(e=>tv(e)||t_(e)),l=()=>e.map(e=>tN(e)?e.value:tv(e)?m(e):P(e)?f?f(e,2):e():void 0)):l=P(e)?t?f?()=>f(e,2):e:()=>{if(s){eL();try{s()}finally{e$()}}let t=h;h=r;try{return f?f(e,3,[o]):e(o)}finally{h=t}}:_,t&&c){let e=l,t=!0===c?1/0:c;l=()=>tH(e(),t)}let S=i,x=()=>{r.stop(),S&&S.active&&T(S.effects,r)};if(u&&t){let e=t;t=(...t)=>{e(...t),x()}}let C=b?Array(e.length).fill(tB):tB,k=e=>{if(1&r.flags&&(r.dirty||e)){if(t){let e=r.run();if(c||g||(b?e.some((e,t)=>Z(e,C[t])):Z(e,C))){s&&s();let n=h;h=r;try{let n=[e,C===tB?void 0:b&&C[0]===tB?[]:C,o];f?f(t,3,n):t(...n),C=e}finally{h=n}}}else r.run()}};return p&&p(k),(r=new ek(l)).scheduler=d?()=>d(k,!1):k,o=e=>tj(e,!1,r),s=r.onStop=()=>{let e=tU.get(r);if(e){if(f)f(e,4);else for(let t of e)t();tU.delete(r)}},t?a?k(!0):C=r.run():d?d(k.bind(null,!0),!0):r.run(),x.pause=r.pause.bind(r),x.resume=r.resume.bind(r),x.stop=x,x}(e,t,a)}function rQ(e,t,n){let r;let i=this.proxy,l=M(e)?e.includes(".")?rZ(i,e):()=>i[e]:e.bind(i,i);P(t)?r=t:(r=t.handler,n=t);let s=i$(this),o=rX(l,r.bind(i),n);return s(),o}function rZ(e,t){let n=t.split(".");return()=>{let t=e;for(let e=0;e"modelValue"===t||"model-value"===t?e.modelModifiers:e[`${t}Modifiers`]||e[`${z(t)}Modifiers`]||e[`${G(t)}Modifiers`];function r0(e,t,...n){let r;if(e.isUnmounted)return;let i=e.vnode.props||y,l=n,s=t.startsWith("update:"),o=s&&rY(i,t.slice(7));o&&(o.trim&&(l=n.map(e=>M(e)?e.trim():e)),o.number&&(l=n.map(et)));let a=i[r=Q(t)]||i[r=Q(z(t))];!a&&s&&(a=i[r=Q(G(t))]),a&&tW(a,e,6,l);let c=i[r+"Once"];if(c){if(e.emitted){if(e.emitted[r])return}else e.emitted={};e.emitted[r]=!0,tW(c,e,6,l)}}function r1(e,t){return!!(e&&x(t))&&(w(e,(t=t.slice(2).replace(/Once$/,""))[0].toLowerCase()+t.slice(1))||w(e,G(t))||w(e,t))}function r2(e){let t,n;let{type:r,vnode:i,proxy:l,withProxy:s,propsOptions:[o],slots:a,attrs:c,emit:u,render:d,renderCache:p,props:f,data:h,setupState:m,ctx:g,inheritAttrs:y}=e,b=t7(e);try{if(4&i.shapeFlag){let e=s||l;t=iw(d.call(e,e,p,f,m,h,g)),n=c}else t=iw(r.length>1?r(f,{attrs:c,slots:a,emit:u}):r(f,null)),n=r.props?c:r3(c)}catch(n){ic.length=0,tK(n,e,1),t=iC(io)}let _=t;if(n&&!1!==y){let e=Object.keys(n),{shapeFlag:t}=_;e.length&&7&t&&(o&&e.some(C)&&(n=r6(n,o)),_=iT(_,n,!1,!0))}return i.dirs&&((_=iT(_,null,!1,!0)).dirs=_.dirs?_.dirs.concat(i.dirs):i.dirs),i.transition&&nT(_,i.transition),t=_,t7(b),t}let r3=e=>{let t;for(let n in e)("class"===n||"style"===n||x(n))&&((t||(t={}))[n]=e[n]);return t},r6=(e,t)=>{let n={};for(let r in e)C(r)&&r.slice(9)in t||(n[r]=e[r]);return n};function r4(e,t,n){let r=Object.keys(t);if(r.length!==Object.keys(e).length)return!0;for(let i=0;ie.__isSuspense,r9=0;function r7(e,t){let n=e.props&&e.props[t];P(n)&&n()}function ie(e,t,n,r,i,l,s,o,a,c,u=!1){let d;let{p:p,m:f,um:h,n:m,o:{parentNode:g,remove:y}}=c,b=function(e){let t=e.props&&e.props.suspensible;return null!=t&&!1!==t}(e);b&&t&&t.pendingBranch&&(d=t.pendingId,t.deps++);let _=e.props?en(e.props.timeout):void 0,S=l,x={vnode:e,parent:t,parentComponent:n,namespace:s,container:r,hiddenContainer:i,deps:0,pendingId:r9++,timeout:"number"==typeof _?_:-1,activeBranch:null,pendingBranch:null,isInFallback:!u,isHydrating:u,isUnmounted:!1,effects:[],resolve(e=!1,n=!1){let{vnode:r,activeBranch:i,pendingBranch:s,pendingId:o,effects:a,parentComponent:c,container:u}=x,p=!1;x.isHydrating?x.isHydrating=!1:e||((p=i&&s.transition&&"out-in"===s.transition.mode)&&(i.transition.afterLeave=()=>{o===x.pendingId&&(f(s,u,l===S?m(i):l,0),t3(a))}),i&&(g(i.el)===u&&(l=m(i)),h(i,c,x,!0)),p||f(s,u,l,0)),ii(x,s),x.pendingBranch=null,x.isInFallback=!1;let y=x.parent,_=!1;for(;y;){if(y.pendingBranch){y.effects.push(...a),_=!0;break}y=y.parent}_||p||t3(a),x.effects=[],b&&t&&t.pendingBranch&&d===t.pendingId&&(t.deps--,0!==t.deps||n||t.resolve()),r7(r,"onResolve")},fallback(e){if(!x.pendingBranch)return;let{vnode:t,activeBranch:n,parentComponent:r,container:i,namespace:l}=x;r7(t,"onFallback");let s=m(n),c=()=>{x.isInFallback&&(p(null,e,i,s,r,null,l,o,a),ii(x,e))},u=e.transition&&"out-in"===e.transition.mode;u&&(n.transition.afterLeave=c),x.isInFallback=!0,h(n,r,null,!0),u||c()},move(e,t,n){x.activeBranch&&f(x.activeBranch,e,t,n),x.container=e},next:()=>x.activeBranch&&m(x.activeBranch),registerDep(e,t,n){let r=!!x.pendingBranch;r&&x.deps++;let i=e.vnode.el;e.asyncDep.catch(t=>{tK(t,e,0)}).then(l=>{if(e.isUnmounted||x.isUnmounted||x.pendingId!==e.suspenseId)return;e.asyncResolved=!0;let{vnode:o}=e;iB(e,l,!1),i&&(o.el=i);let a=!i&&e.subTree.el;t(e,o,g(i||e.subTree.el),i?null:m(e.subTree),x,s,n),a&&y(a),r8(e,o.el),r&&0==--x.deps&&x.resolve()})},unmount(e,t){x.isUnmounted=!0,x.activeBranch&&h(x.activeBranch,n,e,t),x.pendingBranch&&h(x.pendingBranch,n,e,t)}};return x}function it(e){let t;if(P(e)){let n=ih&&e._c;n&&(e._d=!1,id()),e=e(),n&&(e._d=!0,t=iu,ip())}return A(e)&&(e=function(e,t=!0){let n;for(let t=0;tt!==e)),e}function ir(e,t){t&&t.pendingBranch?A(e)?t.effects.push(...e):t.effects.push(e):t3(e)}function ii(e,t){e.activeBranch=t;let{vnode:n,parentComponent:r}=e,i=t.el;for(;!i&&t.component;)i=(t=t.component.subTree).el;n.el=i,r&&r.subTree===n&&(r.vnode.el=i,r8(r,i))}let il=Symbol.for("v-fgt"),is=Symbol.for("v-txt"),io=Symbol.for("v-cmt"),ia=Symbol.for("v-stc"),ic=[],iu=null;function id(e=!1){ic.push(iu=e?null:[])}function ip(){ic.pop(),iu=ic[ic.length-1]||null}let ih=1;function im(e,t=!1){ih+=e,e<0&&iu&&t&&(iu.hasOnce=!0)}function ig(e){return e.dynamicChildren=ih>0?iu||b:null,ip(),ih>0&&iu&&iu.push(e),e}function iy(e,t,n,r,i){return ig(iC(e,t,n,r,i,!0))}function iv(e){return!!e&&!0===e.__v_isVNode}function ib(e,t){return e.type===t.type&&e.key===t.key}let i_=({key:e})=>null!=e?e:null,iS=({ref:e,ref_key:t,ref_for:n})=>("number"==typeof e&&(e=""+e),null!=e?M(e)||tN(e)||P(e)?{i:t5,r:e,k:t,f:!!n}:e:null);function ix(e,t=null,n=null,r=0,i=null,l=e===il?0:1,s=!1,o=!1){let a={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&i_(t),ref:t&&iS(t),scopeId:t9,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetStart:null,targetAnchor:null,staticCount:0,shapeFlag:l,patchFlag:r,dynamicProps:i,dynamicChildren:null,appContext:null,ctx:t5};return o?(iE(a,n),128&l&&e.normalize(a)):n&&(a.shapeFlag|=M(n)?8:16),ih>0&&!s&&iu&&(a.patchFlag>0||6&l)&&32!==a.patchFlag&&iu.push(a),a}let iC=function(e,t=null,n=null,r=0,i=null,l=!1){var s;if(e&&e!==re||(e=io),iv(e)){let r=iT(e,t,!0);return n&&iE(r,n),ih>0&&!l&&iu&&(6&r.shapeFlag?iu[iu.indexOf(e)]=r:iu.push(r)),r.patchFlag=-2,r}if(P(s=e)&&"__vccOpts"in s&&(e=e.__vccOpts),t){let{class:e,style:n}=t=ik(t);e&&!M(e)&&(t.class=eu(e)),$(n)&&(tS(n)&&!A(n)&&(n=k({},n)),t.style=el(n))}let o=M(e)?1:r5(e)?128:nr(e)?64:$(e)?4:P(e)?2:0;return ix(e,t,n,r,i,o,l,!0)};function ik(e){return e?tS(e)||rw(e)?k({},e):e:null}function iT(e,t,n=!1,r=!1){let{props:i,ref:l,patchFlag:s,children:o,transition:a}=e,c=t?iI(i||{},t):i,u={__v_isVNode:!0,__v_skip:!0,type:e.type,props:c,key:c&&i_(c),ref:t&&t.ref?n&&l?A(l)?l.concat(iS(t)):[l,iS(t)]:iS(t):l,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:o,target:e.target,targetStart:e.targetStart,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==il?-1===s?16:16|s:s,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:a,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&iT(e.ssContent),ssFallback:e.ssFallback&&iT(e.ssFallback),el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce};return a&&r&&nT(u,a.clone(u)),u}function iN(e=" ",t=0){return iC(is,null,e,t)}function iw(e){return null==e||"boolean"==typeof e?iC(io):A(e)?iC(il,null,e.slice()):iv(e)?iA(e):iC(is,null,String(e))}function iA(e){return null===e.el&&-1!==e.patchFlag||e.memo?e:iT(e)}function iE(e,t){let n=0,{shapeFlag:r}=e;if(null==t)t=null;else if(A(t))n=16;else if("object"==typeof t){if(65&r){let n=t.default;n&&(n._c&&(n._d=!1),iE(e,n()),n._c&&(n._d=!0));return}{n=32;let r=t._;r||rw(t)?3===r&&t5&&(1===t5.slots._?t._=1:(t._=2,e.patchFlag|=1024)):t._ctx=t5}}else P(t)?(t={default:t,_ctx:t5},n=32):(t=String(t),64&r?(n=16,t=[iN(t)]):n=8);e.children=t,e.shapeFlag|=n}function iI(...e){let t={};for(let n=0;niM||t5;a=e=>{iM=e},c=e=>{iV=e};let i$=e=>{let t=iM;return a(e),e.scope.on(),()=>{e.scope.off(),a(t)}},iD=()=>{iM&&iM.scope.off(),a(null)};function iF(e){return 4&e.vnode.shapeFlag}let iV=!1;function iB(e,t,n){P(t)?e.render=t:$(t)&&(e.setupState=tP(t)),ij(e,n)}function iU(e){u=e,d=e=>{e.render._rc&&(e.withProxy=new Proxy(e.ctx,ro))}}function ij(e,t,n){let r=e.type;if(!e.render){if(!t&&u&&!r.render){let t=r.template||rp(e).template;if(t){let{isCustomElement:n,compilerOptions:i}=e.appContext.config,{delimiters:l,compilerOptions:s}=r,o=k(k({isCustomElement:n,delimiters:l},i),s);r.render=u(t,o)}}e.render=r.render||_,d&&d(e)}{let t=i$(e);eL();try{!function(e){let t=rp(e),n=e.proxy,r=e.ctx;ru=!1,t.beforeCreate&&rd(t.beforeCreate,e,"bc");let{data:i,computed:l,methods:s,watch:o,provide:a,inject:c,created:u,beforeMount:d,mounted:p,beforeUpdate:f,updated:h,activated:m,deactivated:g,beforeDestroy:y,beforeUnmount:b,destroyed:S,unmounted:x,render:C,renderTracked:k,renderTriggered:T,errorCaptured:N,serverPrefetch:w,expose:E,inheritAttrs:I,components:R,directives:O,filters:L}=t;if(c&&function(e,t,n=_){for(let n in A(e)&&(e=rg(e)),e){let r;let i=e[n];tN(r=$(i)?"default"in i?rk(i.from||n,i.default,!0):rk(i.from||n):rk(i))?Object.defineProperty(t,n,{enumerable:!0,configurable:!0,get:()=>r.value,set:e=>r.value=e}):t[n]=r}}(c,r,null),s)for(let e in s){let t=s[e];P(t)&&(r[e]=t.bind(n))}if(i){let t=i.call(n,n);$(t)&&(e.data=th(t))}if(ru=!0,l)for(let e in l){let t=l[e],i=P(t)?t.bind(n,n):P(t.get)?t.get.bind(n,n):_,s=iz({get:i,set:!P(t)&&P(t.set)?t.set.bind(n):_});Object.defineProperty(r,e,{enumerable:!0,configurable:!0,get:()=>s.value,set:e=>s.value=e})}if(o)for(let e in o)!function e(t,n,r,i){let l=i.includes(".")?rZ(r,i):()=>r[i];if(M(t)){let e=n[t];P(e)&&rX(l,e,void 0)}else if(P(t)){var s;s=t.bind(r),rX(l,s,void 0)}else if($(t)){if(A(t))t.forEach(t=>e(t,n,r,i));else{let e=P(t.handler)?t.handler.bind(r):n[t.handler];P(e)&&rX(l,e,t)}}}(o[e],r,n,e);if(a){let e=P(a)?a.call(n):a;Reflect.ownKeys(e).forEach(t=>{rC(t,e[t])})}function D(e,t){A(t)?t.forEach(t=>e(t.bind(n))):t&&e(t.bind(n))}if(u&&rd(u,e,"c"),D(nY,d),D(n0,p),D(n1,f),D(n2,h),D(nK,m),D(nz,g),D(n9,N),D(n5,k),D(n8,T),D(n3,b),D(n6,x),D(n4,w),A(E)){if(E.length){let t=e.exposed||(e.exposed={});E.forEach(e=>{Object.defineProperty(t,e,{get:()=>n[e],set:t=>n[e]=t})})}else e.exposed||(e.exposed={})}C&&e.render===_&&(e.render=C),null!=I&&(e.inheritAttrs=I),R&&(e.components=R),O&&(e.directives=O)}(e)}finally{e$(),t()}}}let iH={get:(e,t)=>(eW(e,"get",""),e[t])};function iq(e){return{attrs:new Proxy(e.attrs,iH),slots:e.slots,emit:e.emit,expose:t=>{e.exposed=t||{}}}}function iW(e){return e.exposed?e.exposeProxy||(e.exposeProxy=new Proxy(tP(tC(e.exposed)),{get:(t,n)=>n in t?t[n]:n in ri?ri[n](e):void 0,has:(e,t)=>t in e||t in ri})):e.proxy}function iK(e,t=!0){return P(e)?e.displayName||e.name:e.name||t&&e.__name}let iz=(e,t)=>(function(e,t,n=!1){let r,i;return P(e)?r=e:(r=e.get,i=e.set),new tV(r,i,n)})(e,0,iV);function iJ(e,t,n){let r=arguments.length;return 2!==r?(r>3?n=Array.prototype.slice.call(arguments,2):3===r&&iv(n)&&(n=[n]),iC(e,t,n)):!$(t)||A(t)?iC(e,null,t):iv(t)?iC(e,null,[t]):iC(e,t)}function iG(e,t){let n=e.memo;if(n.length!=t.length)return!1;for(let e=0;e0&&iu&&iu.push(e),!0}let iX="3.5.13",iQ="undefined"!=typeof window&&window.trustedTypes;if(iQ)try{m=iQ.createPolicy("vue",{createHTML:e=>e})}catch(e){}let iZ=m?e=>m.createHTML(e):e=>e,iY="undefined"!=typeof document?document:null,i0=iY&&iY.createElement("template"),i1="transition",i2="animation",i3=Symbol("_vtc"),i6={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String},i4=k({},ny,i6),i8=((t=(e,{slots:t})=>iJ(n_,i7(e),t)).displayName="Transition",t.props=i4,t),i5=(e,t=[])=>{A(e)?e.forEach(e=>e(...t)):e&&e(...t)},i9=e=>!!e&&(A(e)?e.some(e=>e.length>1):e.length>1);function i7(e){let t={};for(let n in e)n in i6||(t[n]=e[n]);if(!1===e.css)return t;let{name:n="v",type:r,duration:i,enterFromClass:l=`${n}-enter-from`,enterActiveClass:s=`${n}-enter-active`,enterToClass:o=`${n}-enter-to`,appearFromClass:a=l,appearActiveClass:c=s,appearToClass:u=o,leaveFromClass:d=`${n}-leave-from`,leaveActiveClass:p=`${n}-leave-active`,leaveToClass:f=`${n}-leave-to`}=e,h=function(e){if(null==e)return null;if($(e))return[en(e.enter),en(e.leave)];{let t=en(e);return[t,t]}}(i),m=h&&h[0],g=h&&h[1],{onBeforeEnter:y,onEnter:b,onEnterCancelled:_,onLeave:S,onLeaveCancelled:x,onBeforeAppear:C=y,onAppear:T=b,onAppearCancelled:N=_}=t,w=(e,t,n,r)=>{e._enterCancelled=r,lt(e,t?u:o),lt(e,t?c:s),n&&n()},A=(e,t)=>{e._isLeaving=!1,lt(e,d),lt(e,f),lt(e,p),t&&t()},E=e=>(t,n)=>{let i=e?T:b,s=()=>w(t,e,n);i5(i,[t,s]),ln(()=>{lt(t,e?a:l),le(t,e?u:o),i9(i)||li(t,r,m,s)})};return k(t,{onBeforeEnter(e){i5(y,[e]),le(e,l),le(e,s)},onBeforeAppear(e){i5(C,[e]),le(e,a),le(e,c)},onEnter:E(!1),onAppear:E(!0),onLeave(e,t){e._isLeaving=!0;let n=()=>A(e,t);le(e,d),e._enterCancelled?(le(e,p),la()):(la(),le(e,p)),ln(()=>{e._isLeaving&&(lt(e,d),le(e,f),i9(S)||li(e,r,g,n))}),i5(S,[e,n])},onEnterCancelled(e){w(e,!1,void 0,!0),i5(_,[e])},onAppearCancelled(e){w(e,!0,void 0,!0),i5(N,[e])},onLeaveCancelled(e){A(e),i5(x,[e])}})}function le(e,t){t.split(/\s+/).forEach(t=>t&&e.classList.add(t)),(e[i3]||(e[i3]=new Set)).add(t)}function lt(e,t){t.split(/\s+/).forEach(t=>t&&e.classList.remove(t));let n=e[i3];n&&(n.delete(t),n.size||(e[i3]=void 0))}function ln(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}let lr=0;function li(e,t,n,r){let i=e._endId=++lr,l=()=>{i===e._endId&&r()};if(null!=n)return setTimeout(l,n);let{type:s,timeout:o,propCount:a}=ll(e,t);if(!s)return r();let c=s+"end",u=0,d=()=>{e.removeEventListener(c,p),l()},p=t=>{t.target===e&&++u>=a&&d()};setTimeout(()=>{u(n[e]||"").split(", "),i=r(`${i1}Delay`),l=r(`${i1}Duration`),s=ls(i,l),o=r(`${i2}Delay`),a=r(`${i2}Duration`),c=ls(o,a),u=null,d=0,p=0;t===i1?s>0&&(u=i1,d=s,p=l.length):t===i2?c>0&&(u=i2,d=c,p=a.length):p=(u=(d=Math.max(s,c))>0?s>c?i1:i2:null)?u===i1?l.length:a.length:0;let f=u===i1&&/\b(transform|all)(,|$)/.test(r(`${i1}Property`).toString());return{type:u,timeout:d,propCount:p,hasTransform:f}}function ls(e,t){for(;e.lengthlo(t)+lo(e[n])))}function lo(e){return"auto"===e?0:1e3*Number(e.slice(0,-1).replace(",","."))}function la(){return document.body.offsetHeight}let lc=Symbol("_vod"),lu=Symbol("_vsh");function ld(e,t){e.style.display=t?e[lc]:"none",e[lu]=!t}let lp=Symbol("");function lf(e,t){if(1===e.nodeType){let n=e.style,r="";for(let e in t)n.setProperty(`--${e}`,t[e]),r+=`--${e}: ${t[e]};`;n[lp]=r}}let lh=/(^|;)\s*display\s*:/,lm=/\s*!important$/;function lg(e,t,n){if(A(n))n.forEach(n=>lg(e,t,n));else if(null==n&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{let r=function(e,t){let n=lv[t];if(n)return n;let r=z(t);if("filter"!==r&&r in e)return lv[t]=r;r=X(r);for(let n=0;nlT||(lN.then(()=>lT=0),lT=Date.now()),lA=e=>111===e.charCodeAt(0)&&110===e.charCodeAt(1)&&e.charCodeAt(2)>96&&123>e.charCodeAt(2),lE={};function lI(e,t,n){let r=nw(e,t);U(r)&&k(r,t);class i extends lO{constructor(e){super(r,e,n)}}return i.def=r,i}let lR="undefined"!=typeof HTMLElement?HTMLElement:class{};class lO extends lR{constructor(e,t={},n=l9){super(),this._def=e,this._props=t,this._createApp=n,this._isVueCE=!0,this._instance=null,this._app=null,this._nonce=this._def.nonce,this._connected=!1,this._resolved=!1,this._numberProps=null,this._styleChildren=new WeakSet,this._ob=null,this.shadowRoot&&n!==l9?this._root=this.shadowRoot:!1!==e.shadowRoot?(this.attachShadow({mode:"open"}),this._root=this.shadowRoot):this._root=this,this._def.__asyncLoader||this._resolveProps(this._def)}connectedCallback(){if(!this.isConnected)return;this.shadowRoot||this._parseSlots(),this._connected=!0;let e=this;for(;e=e&&(e.parentNode||e.host);)if(e instanceof lO){this._parent=e;break}this._instance||(this._resolved?(this._setParent(),this._update()):e&&e._pendingResolve?this._pendingResolve=e._pendingResolve.then(()=>{this._pendingResolve=void 0,this._resolveDef()}):this._resolveDef())}_setParent(e=this._parent){e&&(this._instance.parent=e._instance,this._instance.provides=e._instance.provides)}disconnectedCallback(){this._connected=!1,t0(()=>{this._connected||(this._ob&&(this._ob.disconnect(),this._ob=null),this._app&&this._app.unmount(),this._instance&&(this._instance.ce=void 0),this._app=this._instance=null)})}_resolveDef(){if(this._pendingResolve)return;for(let e=0;e{for(let t of e)this._setAttr(t.attributeName)}),this._ob.observe(this,{attributes:!0});let e=(e,t=!1)=>{let n;this._resolved=!0,this._pendingResolve=void 0;let{props:r,styles:i}=e;if(r&&!A(r))for(let e in r){let t=r[e];(t===Number||t&&t.type===Number)&&(e in this._props&&(this._props[e]=en(this._props[e])),(n||(n=Object.create(null)))[z(e)]=!0)}this._numberProps=n,t&&this._resolveProps(e),this.shadowRoot&&this._applyStyles(i),this._mount(e)},t=this._def.__asyncLoader;t?this._pendingResolve=t().then(t=>e(this._def=t,!0)):e(this._def)}_mount(e){this._app=this._createApp(e),e.configureApp&&e.configureApp(this._app),this._app._ceVNode=this._createVNode(),this._app.mount(this._root);let t=this._instance&&this._instance.exposed;if(t)for(let e in t)w(this,e)||Object.defineProperty(this,e,{get:()=>tR(t[e])})}_resolveProps(e){let{props:t}=e,n=A(t)?t:Object.keys(t||{});for(let e of Object.keys(this))"_"!==e[0]&&n.includes(e)&&this._setProp(e,this[e]);for(let e of n.map(z))Object.defineProperty(this,e,{get(){return this._getProp(e)},set(t){this._setProp(e,t,!0,!0)}})}_setAttr(e){if(e.startsWith("data-v-"))return;let t=this.hasAttribute(e),n=t?this.getAttribute(e):lE,r=z(e);t&&this._numberProps&&this._numberProps[r]&&(n=en(n)),this._setProp(r,n,!1,!0)}_getProp(e){return this._props[e]}_setProp(e,t,n=!0,r=!1){if(t!==this._props[e]&&(t===lE?delete this._props[e]:(this._props[e]=t,"key"===e&&this._app&&(this._app._ceVNode.key=t)),r&&this._instance&&this._update(),n)){let n=this._ob;n&&n.disconnect(),!0===t?this.setAttribute(G(e),""):"string"==typeof t||"number"==typeof t?this.setAttribute(G(e),t+""):t||this.removeAttribute(G(e)),n&&n.observe(this,{attributes:!0})}}_update(){l5(this._createVNode(),this._root)}_createVNode(){let e={};this.shadowRoot||(e.onVnodeMounted=e.onVnodeUpdated=this._renderSlots.bind(this));let t=iC(this._def,k(e,this._props));return this._instance||(t.ce=e=>{this._instance=e,e.ce=this,e.isCE=!0;let t=(e,t)=>{this.dispatchEvent(new CustomEvent(e,U(t[0])?k({detail:t},t[0]):{detail:t}))};e.emit=(e,...n)=>{t(e,n),G(e)!==e&&t(G(e),n)},this._setParent()}),t}_applyStyles(e,t){if(!e)return;if(t){if(t===this._def||this._styleChildren.has(t))return;this._styleChildren.add(t)}let n=this._nonce;for(let t=e.length-1;t>=0;t--){let r=document.createElement("style");n&&r.setAttribute("nonce",n),r.textContent=e[t],this.shadowRoot.prepend(r)}}_parseSlots(){let e;let t=this._slots={};for(;e=this.firstChild;){let n=1===e.nodeType&&e.getAttribute("slot")||"default";(t[n]||(t[n]=[])).push(e),this.removeChild(e)}}_renderSlots(){let e=(this._teleportTarget||this).querySelectorAll("slot"),t=this._instance.type.__scopeId;for(let n=0;n{if(!n.length)return;let t=e.moveClass||`${e.name||"v"}-move`;if(!function(e,t,n){let r=e.cloneNode(),i=e[i3];i&&i.forEach(e=>{e.split(/\s+/).forEach(e=>e&&r.classList.remove(e))}),n.split(/\s+/).forEach(e=>e&&r.classList.add(e)),r.style.display="none";let l=1===t.nodeType?t:t.parentNode;l.appendChild(r);let{hasTransform:s}=ll(r);return l.removeChild(r),s}(n[0].el,i.vnode.el,t))return;n.forEach(lV),n.forEach(lB);let r=n.filter(lU);la(),r.forEach(e=>{let n=e.el,r=n.style;le(n,t),r.transform=r.webkitTransform=r.transitionDuration="";let i=n[l$]=e=>{(!e||e.target===n)&&(!e||/transform$/.test(e.propertyName))&&(n.removeEventListener("transitionend",i),n[l$]=null,lt(n,t))};n.addEventListener("transitionend",i)})}),()=>{let s=tx(e),o=i7(s),a=s.tag||il;if(n=[],r)for(let e=0;e{let t=e.props["onUpdate:modelValue"]||!1;return A(t)?e=>Y(t,e):t};function lH(e){e.target.composing=!0}function lq(e){let t=e.target;t.composing&&(t.composing=!1,t.dispatchEvent(new Event("input")))}let lW=Symbol("_assign"),lK={created(e,{modifiers:{lazy:t,trim:n,number:r}},i){e[lW]=lj(i);let l=r||i.props&&"number"===i.props.type;lx(e,t?"change":"input",t=>{if(t.target.composing)return;let r=e.value;n&&(r=r.trim()),l&&(r=et(r)),e[lW](r)}),n&&lx(e,"change",()=>{e.value=e.value.trim()}),t||(lx(e,"compositionstart",lH),lx(e,"compositionend",lq),lx(e,"change",lq))},mounted(e,{value:t}){e.value=null==t?"":t},beforeUpdate(e,{value:t,oldValue:n,modifiers:{lazy:r,trim:i,number:l}},s){if(e[lW]=lj(s),e.composing)return;let o=(l||"number"===e.type)&&!/^0\d/.test(e.value)?et(e.value):e.value,a=null==t?"":t;o===a||document.activeElement===e&&"range"!==e.type&&(r&&t===n||i&&e.value.trim()===a)||(e.value=a)}},lz={deep:!0,created(e,t,n){e[lW]=lj(n),lx(e,"change",()=>{let t=e._modelValue,n=lZ(e),r=e.checked,i=e[lW];if(A(t)){let e=ey(t,n),l=-1!==e;if(r&&!l)i(t.concat(n));else if(!r&&l){let n=[...t];n.splice(e,1),i(n)}}else if(I(t)){let e=new Set(t);r?e.add(n):e.delete(n),i(e)}else i(lY(e,r))})},mounted:lJ,beforeUpdate(e,t,n){e[lW]=lj(n),lJ(e,t,n)}};function lJ(e,{value:t,oldValue:n},r){let i;if(e._modelValue=t,A(t))i=ey(t,r.props.value)>-1;else if(I(t))i=t.has(r.props.value);else{if(t===n)return;i=eg(t,lY(e,!0))}e.checked!==i&&(e.checked=i)}let lG={created(e,{value:t},n){e.checked=eg(t,n.props.value),e[lW]=lj(n),lx(e,"change",()=>{e[lW](lZ(e))})},beforeUpdate(e,{value:t,oldValue:n},r){e[lW]=lj(r),t!==n&&(e.checked=eg(t,r.props.value))}},lX={deep:!0,created(e,{value:t,modifiers:{number:n}},r){let i=I(t);lx(e,"change",()=>{let t=Array.prototype.filter.call(e.options,e=>e.selected).map(e=>n?et(lZ(e)):lZ(e));e[lW](e.multiple?i?new Set(t):t:t[0]),e._assigning=!0,t0(()=>{e._assigning=!1})}),e[lW]=lj(r)},mounted(e,{value:t}){lQ(e,t)},beforeUpdate(e,t,n){e[lW]=lj(n)},updated(e,{value:t}){e._assigning||lQ(e,t)}};function lQ(e,t){let n=e.multiple,r=A(t);if(!n||r||I(t)){for(let i=0,l=e.options.length;iString(e)===String(s)):l.selected=ey(t,s)>-1}else l.selected=t.has(s)}else if(eg(lZ(l),t)){e.selectedIndex!==i&&(e.selectedIndex=i);return}}n||-1===e.selectedIndex||(e.selectedIndex=-1)}}function lZ(e){return"_value"in e?e._value:e.value}function lY(e,t){let n=t?"_trueValue":"_falseValue";return n in e?e[n]:t}function l0(e,t,n,r,i){let l=function(e,t){switch(e){case"SELECT":return lX;case"TEXTAREA":return lK;default:switch(t){case"checkbox":return lz;case"radio":return lG;default:return lK}}}(e.tagName,n.props&&n.props.type)[i];l&&l(e,t,n,r)}let l1=["ctrl","shift","alt","meta"],l2={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&0!==e.button,middle:e=>"button"in e&&1!==e.button,right:e=>"button"in e&&2!==e.button,exact:(e,t)=>l1.some(n=>e[`${n}Key`]&&!t.includes(n))},l3={esc:"escape",space:" ",up:"arrow-up",left:"arrow-left",right:"arrow-right",down:"arrow-down",delete:"backspace"},l6=k({patchProp:(e,t,n,r,i,l)=>{let s="svg"===i;"class"===t?function(e,t,n){let r=e[i3];r&&(t=(t?[t,...r]:[...r]).join(" ")),null==t?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}(e,r,s):"style"===t?function(e,t,n){let r=e.style,i=M(n),l=!1;if(n&&!i){if(t){if(M(t))for(let e of t.split(";")){let t=e.slice(0,e.indexOf(":")).trim();null==n[t]&&lg(r,t,"")}else for(let e in t)null==n[e]&&lg(r,e,"")}for(let e in n)"display"===e&&(l=!0),lg(r,e,n[e])}else if(i){if(t!==n){let e=r[lp];e&&(n+=";"+e),r.cssText=n,l=lh.test(n)}}else t&&e.removeAttribute("style");lc in e&&(e[lc]=l?r.display:"",e[lu]&&(r.display="none"))}(e,n,r):x(t)?C(t)||function(e,t,n,r,i=null){let l=e[lC]||(e[lC]={}),s=l[t];if(r&&s)s.value=r;else{let[n,o]=function(e){let t;if(lk.test(e)){let n;for(t={};n=e.match(lk);)e=e.slice(0,e.length-n[0].length),t[n[0].toLowerCase()]=!0}return[":"===e[2]?e.slice(3):G(e.slice(2)),t]}(t);r?lx(e,n,l[t]=function(e,t){let n=e=>{if(e._vts){if(e._vts<=n.attached)return}else e._vts=Date.now();tW(function(e,t){if(!A(t))return t;{let n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(e=>t=>!t._stopped&&e&&e(t))}}(e,n.value),t,5,[e])};return n.value=e,n.attached=lw(),n}(r,i),o):s&&(!function(e,t,n,r){e.removeEventListener(t,n,r)}(e,n,s,o),l[t]=void 0)}}(e,t,0,r,l):("."===t[0]?(t=t.slice(1),0):"^"===t[0]?(t=t.slice(1),1):!function(e,t,n,r){if(r)return!!("innerHTML"===t||"textContent"===t||t in e&&lA(t)&&P(n));if("spellcheck"===t||"draggable"===t||"translate"===t||"form"===t||"list"===t&&"INPUT"===e.tagName||"type"===t&&"TEXTAREA"===e.tagName)return!1;if("width"===t||"height"===t){let t=e.tagName;if("IMG"===t||"VIDEO"===t||"CANVAS"===t||"SOURCE"===t)return!1}return!(lA(t)&&M(n))&&t in e}(e,t,r,s))?e._isVueCE&&(/[A-Z]/.test(t)||!M(r))?lS(e,z(t),r,l,t):("true-value"===t?e._trueValue=r:"false-value"===t&&(e._falseValue=r),l_(e,t,r,s)):(lS(e,t,r),e.tagName.includes("-")||"value"!==t&&"checked"!==t&&"selected"!==t||l_(e,t,r,s,l,"value"!==t))}},{insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{let t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,r)=>{let i="svg"===t?iY.createElementNS("http://www.w3.org/2000/svg",e):"mathml"===t?iY.createElementNS("http://www.w3.org/1998/Math/MathML",e):n?iY.createElement(e,{is:n}):iY.createElement(e);return"select"===e&&r&&null!=r.multiple&&i.setAttribute("multiple",r.multiple),i},createText:e=>iY.createTextNode(e),createComment:e=>iY.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>iY.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,r,i,l){let s=n?n.previousSibling:t.lastChild;if(i&&(i===l||i.nextSibling))for(;t.insertBefore(i.cloneNode(!0),n),i!==l&&(i=i.nextSibling););else{i0.innerHTML=iZ("svg"===r?`${e}`:"mathml"===r?`${e}`:e);let i=i0.content;if("svg"===r||"mathml"===r){let e=i.firstChild;for(;e.firstChild;)i.appendChild(e.firstChild);i.removeChild(e)}t.insertBefore(i,n)}return[s?s.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}}),l4=!1;function l8(){return p=l4?p:rU(l6),l4=!0,p}let l5=(...e)=>{(p||(p=rj(l6))).render(...e)},l9=(...e)=>{let t=(p||(p=rj(l6))).createApp(...e),{mount:n}=t;return t.mount=e=>{let r=st(e);if(!r)return;let i=t._component;P(i)||i.render||i.template||(i.template=r.innerHTML),1===r.nodeType&&(r.textContent="");let l=n(r,!1,se(r));return r instanceof Element&&(r.removeAttribute("v-cloak"),r.setAttribute("data-v-app","")),l},t},l7=(...e)=>{let t=l8().createApp(...e),{mount:n}=t;return t.mount=e=>{let t=st(e);if(t)return n(t,!0,se(t))},t};function se(e){return e instanceof SVGElement?"svg":"function"==typeof MathMLElement&&e instanceof MathMLElement?"mathml":void 0}function st(e){return M(e)?document.querySelector(e):e}let sn=Symbol(""),sr=Symbol(""),si=Symbol(""),sl=Symbol(""),ss=Symbol(""),so=Symbol(""),sa=Symbol(""),sc=Symbol(""),su=Symbol(""),sd=Symbol(""),sp=Symbol(""),sf=Symbol(""),sh=Symbol(""),sm=Symbol(""),sg=Symbol(""),sy=Symbol(""),sv=Symbol(""),sb=Symbol(""),s_=Symbol(""),sS=Symbol(""),sx=Symbol(""),sC=Symbol(""),sk=Symbol(""),sT=Symbol(""),sN=Symbol(""),sw=Symbol(""),sA=Symbol(""),sE=Symbol(""),sI=Symbol(""),sR=Symbol(""),sO=Symbol(""),sP=Symbol(""),sM=Symbol(""),sL=Symbol(""),s$=Symbol(""),sD=Symbol(""),sF=Symbol(""),sV=Symbol(""),sB=Symbol(""),sU={[sn]:"Fragment",[sr]:"Teleport",[si]:"Suspense",[sl]:"KeepAlive",[ss]:"BaseTransition",[so]:"openBlock",[sa]:"createBlock",[sc]:"createElementBlock",[su]:"createVNode",[sd]:"createElementVNode",[sp]:"createCommentVNode",[sf]:"createTextVNode",[sh]:"createStaticVNode",[sm]:"resolveComponent",[sg]:"resolveDynamicComponent",[sy]:"resolveDirective",[sv]:"resolveFilter",[sb]:"withDirectives",[s_]:"renderList",[sS]:"renderSlot",[sx]:"createSlots",[sC]:"toDisplayString",[sk]:"mergeProps",[sT]:"normalizeClass",[sN]:"normalizeStyle",[sw]:"normalizeProps",[sA]:"guardReactiveProps",[sE]:"toHandlers",[sI]:"camelize",[sR]:"capitalize",[sO]:"toHandlerKey",[sP]:"setBlockTracking",[sM]:"pushScopeId",[sL]:"popScopeId",[s$]:"withCtx",[sD]:"unref",[sF]:"isRef",[sV]:"withMemo",[sB]:"isMemoSame"},sj={start:{line:1,column:1,offset:0},end:{line:1,column:1,offset:0},source:""};function sH(e,t,n,r,i,l,s,o=!1,a=!1,c=!1,u=sj){return e&&(o?(e.helper(so),e.helper(e.inSSR||c?sa:sc)):e.helper(e.inSSR||c?su:sd),s&&e.helper(sb)),{type:13,tag:t,props:n,children:r,patchFlag:i,dynamicProps:l,directives:s,isBlock:o,disableTracking:a,isComponent:c,loc:u}}function sq(e,t=sj){return{type:17,loc:t,elements:e}}function sW(e,t=sj){return{type:15,loc:t,properties:e}}function sK(e,t){return{type:16,loc:sj,key:M(e)?sz(e,!0):e,value:t}}function sz(e,t=!1,n=sj,r=0){return{type:4,loc:n,content:e,isStatic:t,constType:t?3:r}}function sJ(e,t=sj){return{type:8,loc:t,children:e}}function sG(e,t=[],n=sj){return{type:14,loc:n,callee:e,arguments:t}}function sX(e,t,n=!1,r=!1,i=sj){return{type:18,params:e,returns:t,newline:n,isSlot:r,loc:i}}function sQ(e,t,n,r=!0){return{type:19,test:e,consequent:t,alternate:n,newline:r,loc:sj}}function sZ(e,{helper:t,removeHelper:n,inSSR:r}){if(!e.isBlock){var i,l;e.isBlock=!0,n((i=e.isComponent,r||i?su:sd)),t(so),t((l=e.isComponent,r||l?sa:sc))}}let sY=new Uint8Array([123,123]),s0=new Uint8Array([125,125]);function s1(e){return e>=97&&e<=122||e>=65&&e<=90}function s2(e){return 32===e||10===e||9===e||12===e||13===e}function s3(e){return 47===e||62===e||s2(e)}function s6(e){let t=new Uint8Array(e.length);for(let n=0;n4===e.type&&e.isStatic;function oe(e){switch(e){case"Teleport":case"teleport":return sr;case"Suspense":case"suspense":return si;case"KeepAlive":case"keep-alive":return sl;case"BaseTransition":case"base-transition":return ss}}let ot=/^\d|[^\$\w\xA0-\uFFFF]/,on=e=>!ot.test(e),or=/[A-Za-z_$\xA0-\uFFFF]/,oi=/[\.\?\w$\xA0-\uFFFF]/,ol=/\s+[.[]\s*|\s*[.[]\s+/g,os=e=>4===e.type?e.content:e.loc.source,oo=e=>{let t=os(e).trim().replace(ol,e=>e.trim()),n=0,r=[],i=0,l=0,s=null;for(let e=0;e|^\s*(async\s+)?function(?:\s+[\w$]+)?\s*\(/,oc=e=>oa.test(os(e));function ou(e,t,n=!1){for(let r=0;r4===e.key.type&&e.key.content===r)}return n}function o_(e,t){return`_${t}_${e.replace(/[^\w]/g,(t,n)=>"-"===t?"_":e.charCodeAt(n).toString())}`}let oS=/([\s\S]*?)\s+(?:in|of)\s+(\S[\s\S]*)/,ox={parseMode:"base",ns:0,delimiters:["{{","}}"],getNamespace:()=>0,isVoidTag:S,isPreTag:S,isIgnoreNewlineTag:S,isCustomElement:S,onError:s8,onWarn:s5,comments:!1,prefixIdentifiers:!1},oC=ox,ok=null,oT="",oN=null,ow=null,oA="",oE=-1,oI=-1,oR=0,oO=!1,oP=null,oM=[],oL=new class{constructor(e,t){this.stack=e,this.cbs=t,this.state=1,this.buffer="",this.sectionStart=0,this.index=0,this.entityStart=0,this.baseState=1,this.inRCDATA=!1,this.inXML=!1,this.inVPre=!1,this.newlines=[],this.mode=0,this.delimiterOpen=sY,this.delimiterClose=s0,this.delimiterIndex=-1,this.currentSequence=void 0,this.sequenceIndex=0}get inSFCRoot(){return 2===this.mode&&0===this.stack.length}reset(){this.state=1,this.mode=0,this.buffer="",this.sectionStart=0,this.index=0,this.baseState=1,this.inRCDATA=!1,this.currentSequence=void 0,this.newlines.length=0,this.delimiterOpen=sY,this.delimiterClose=s0}getPos(e){let t=1,n=e+1;for(let r=this.newlines.length-1;r>=0;r--){let i=this.newlines[r];if(e>i){t=r+2,n=e-i;break}}return{column:n,line:t,offset:e}}peek(){return this.buffer.charCodeAt(this.index+1)}stateText(e){60===e?(this.index>this.sectionStart&&this.cbs.ontext(this.sectionStart,this.index),this.state=5,this.sectionStart=this.index):this.inVPre||e!==this.delimiterOpen[0]||(this.state=2,this.delimiterIndex=0,this.stateInterpolationOpen(e))}stateInterpolationOpen(e){if(e===this.delimiterOpen[this.delimiterIndex]){if(this.delimiterIndex===this.delimiterOpen.length-1){let e=this.index+1-this.delimiterOpen.length;e>this.sectionStart&&this.cbs.ontext(this.sectionStart,e),this.state=3,this.sectionStart=e}else this.delimiterIndex++}else this.inRCDATA?(this.state=32,this.stateInRCDATA(e)):(this.state=1,this.stateText(e))}stateInterpolation(e){e===this.delimiterClose[0]&&(this.state=4,this.delimiterIndex=0,this.stateInterpolationClose(e))}stateInterpolationClose(e){e===this.delimiterClose[this.delimiterIndex]?this.delimiterIndex===this.delimiterClose.length-1?(this.cbs.oninterpolation(this.sectionStart,this.index+1),this.inRCDATA?this.state=32:this.state=1,this.sectionStart=this.index+1):this.delimiterIndex++:(this.state=3,this.stateInterpolation(e))}stateSpecialStartSequence(e){let t=this.sequenceIndex===this.currentSequence.length;if(t?s3(e):(32|e)===this.currentSequence[this.sequenceIndex]){if(!t){this.sequenceIndex++;return}}else this.inRCDATA=!1;this.sequenceIndex=0,this.state=6,this.stateInTagName(e)}stateInRCDATA(e){if(this.sequenceIndex===this.currentSequence.length){if(62===e||s2(e)){let t=this.index-this.currentSequence.length;if(this.sectionStart=e||(28===this.state?this.currentSequence===s4.CdataEnd?this.cbs.oncdata(this.sectionStart,e):this.cbs.oncomment(this.sectionStart,e):6===this.state||11===this.state||18===this.state||17===this.state||12===this.state||13===this.state||14===this.state||15===this.state||16===this.state||20===this.state||19===this.state||21===this.state||9===this.state||this.cbs.ontext(this.sectionStart,e))}emitCodePoint(e,t){}}(oM,{onerr:oQ,ontext(e,t){oB(oF(e,t),e,t)},ontextentity(e,t,n){oB(e,t,n)},oninterpolation(e,t){if(oO)return oB(oF(e,t),e,t);let n=e+oL.delimiterOpen.length,r=t-oL.delimiterClose.length;for(;s2(oT.charCodeAt(n));)n++;for(;s2(oT.charCodeAt(r-1));)r--;let i=oF(n,r);i.includes("&")&&(i=oC.decodeEntities(i,!1)),oz({type:5,content:oX(i,!1,oJ(n,r)),loc:oJ(e,t)})},onopentagname(e,t){let n=oF(e,t);oN={type:1,tag:n,ns:oC.getNamespace(n,oM[0],oC.ns),tagType:0,props:[],children:[],loc:oJ(e-1,t),codegenNode:void 0}},onopentagend(e){oV(e)},onclosetag(e,t){let n=oF(e,t);if(!oC.isVoidTag(n)){let r=!1;for(let e=0;e0&&oM[0].loc.start.offset;for(let n=0;n<=e;n++)oU(oM.shift(),t,n(7===e.type?e.rawName:e.name)===t)},onattribend(e,t){oN&&ow&&(oG(ow.loc,t),0!==e&&(oA.includes("&")&&(oA=oC.decodeEntities(oA,!0)),6===ow.type?("class"===ow.name&&(oA=oK(oA).trim()),ow.value={type:2,content:oA,loc:1===e?oJ(oE,oI):oJ(oE-1,oI+1)},oL.inSFCRoot&&"template"===oN.tag&&"lang"===ow.name&&oA&&"html"!==oA&&oL.enterRCDATA(s6("{let i=t.start.offset+n,l=i+e.length;return oX(e,!1,oJ(i,l),0,r?1:0)},o={source:s(l.trim(),n.indexOf(l,i.length)),value:void 0,key:void 0,index:void 0,finalized:!1},a=i.trim().replace(oD,"").trim(),c=i.indexOf(a),u=a.match(o$);if(u){let e;a=a.replace(o$,"").trim();let t=u[1].trim();if(t&&(e=n.indexOf(t,c+a.length),o.key=s(t,e,!0)),u[2]){let r=u[2].trim();r&&(o.index=s(r,n.indexOf(r,o.key?e+t.length:c+a.length),!0))}}return a&&(o.value=s(a,c,!0)),o}(ow.exp)))),(7!==ow.type||"pre"!==ow.name)&&oN.props.push(ow)),oA="",oE=oI=-1},oncomment(e,t){oC.comments&&oz({type:3,content:oF(e,t),loc:oJ(e-4,t+3)})},onend(){let e=oT.length;for(let t=0;t64&&n<91||oe(e)||oC.isBuiltInComponent&&oC.isBuiltInComponent(e)||oC.isNativeTag&&!oC.isNativeTag(e))return!0;for(let e=0;e=0;)n--;return n}let oH=new Set(["if","else","else-if","for","slot"]),oq=/\r\n/g;function oW(e,t){let n="preserve"!==oC.whitespace,r=!1;for(let t=0;t1)for(let i=0;i{n--};for(;nt===e:t=>e.test(t);return(e,r)=>{if(1===e.type){let{props:i}=e;if(3===e.tagType&&i.some(oh))return;let l=[];for(let s=0;s`${sU[e]}: _${sU[e]}`;function o5(e,t,{helper:n,push:r,newline:i,isTS:l}){let s=n("component"===t?sm:sy);for(let n=0;n3;t.push("["),n&&t.indent(),o7(e,t,n),n&&t.deindent(),t.push("]")}function o7(e,t,n=!1,r=!0){let{push:i,newline:l}=t;for(let s=0;se||"null")}([s,o,a,n,u]),t),r(")"),p&&r(")"),d&&(r(", "),ae(d,t),r(")"))}(e,t);break;case 14:!function(e,t){let{push:n,helper:r,pure:i}=t,l=M(e.callee)?e.callee:r(e.callee);i&&n(o4),n(l+"(",-2,e),o7(e.arguments,t),n(")")}(e,t);break;case 15:!function(e,t){let{push:n,indent:r,deindent:i,newline:l}=t,{properties:s}=e;if(!s.length){n("{}",-2,e);return}let o=s.length>1;n(o?"{":"{ "),o&&r();for(let e=0;e "),(a||o)&&(n("{"),r()),s?(a&&n("return "),A(s)?o9(s,t):ae(s,t)):o&&ae(o,t),(a||o)&&(i(),n("}")),c&&n(")")}(e,t);break;case 19:!function(e,t){let{test:n,consequent:r,alternate:i,newline:l}=e,{push:s,indent:o,deindent:a,newline:c}=t;if(4===n.type){let e=!on(n.content);e&&s("("),at(n,t),e&&s(")")}else s("("),ae(n,t),s(")");l&&o(),t.indentLevel++,l||s(" "),s("? "),ae(r,t),t.indentLevel--,l&&c(),l||s(" "),s(": ");let u=19===i.type;!u&&t.indentLevel++,ae(i,t),!u&&t.indentLevel--,l&&a(!0)}(e,t);break;case 20:!function(e,t){let{push:n,helper:r,indent:i,deindent:l,newline:s}=t,{needPauseTracking:o,needArraySpread:a}=e;a&&n("[...("),n(`_cache[${e.index}] || (`),o&&(i(),n(`${r(sP)}(-1`),e.inVOnce&&n(", true"),n("),"),s(),n("(")),n(`_cache[${e.index}] = `),ae(e.value,t),o&&(n(`).cacheIndex = ${e.index},`),s(),n(`${r(sP)}(1),`),s(),n(`_cache[${e.index}]`),l()),n(")"),a&&n(")]")}(e,t);break;case 21:o7(e.body,t,!0,!1)}}function at(e,t){let{content:n,isStatic:r}=e;t.push(r?JSON.stringify(n):n,-3,e)}function an(e,t){for(let n=0;n(function(e,t,n,r){if("else"!==t.name&&(!t.exp||!t.exp.content.trim())){let r=t.exp?t.exp.loc:e.loc;n.onError(s9(28,t.loc)),t.exp=sz("true",!1,r)}if("if"===t.name){var i;let l=ai(e,t),s={type:9,loc:oJ((i=e.loc).start.offset,i.end.offset),branches:[l]};if(n.replaceNode(s),r)return r(s,l,!0)}else{let i=n.parent.children,l=i.indexOf(e);for(;l-- >=-1;){let s=i[l];if(s&&3===s.type||s&&2===s.type&&!s.content.trim().length){n.removeNode(s);continue}if(s&&9===s.type){"else-if"===t.name&&void 0===s.branches[s.branches.length-1].condition&&n.onError(s9(30,e.loc)),n.removeNode();let i=ai(e,t);s.branches.push(i);let l=r&&r(s,i,!1);o3(i,n),l&&l(),n.currentNode=null}else n.onError(s9(30,e.loc));break}}})(e,t,n,(e,t,r)=>{let i=n.parent.children,l=i.indexOf(e),s=0;for(;l-- >=0;){let e=i[l];e&&9===e.type&&(s+=e.branches.length)}return()=>{r?e.codegenNode=al(t,s,n):function(e){for(;;)if(19===e.type){if(19!==e.alternate.type)return e;e=e.alternate}else 20===e.type&&(e=e.value)}(e.codegenNode).alternate=al(t,s+e.branches.length-1,n)}}));function ai(e,t){let n=3===e.tagType;return{type:10,loc:e.loc,condition:"else"===t.name?void 0:t.exp,children:n&&!ou(e,"for")?e.children:[e],userKey:od(e,"key"),isTemplateIf:n}}function al(e,t,n){return e.condition?sQ(e.condition,as(e,t,n),sG(n.helper(sp),['""',"true"])):as(e,t,n)}function as(e,t,n){let{helper:r}=n,i=sK("key",sz(`${t}`,!1,sj,2)),{children:l}=e,s=l[0];if(1!==l.length||1!==s.type){if(1!==l.length||11!==s.type)return sH(n,r(sn),sW([i]),l,64,void 0,void 0,!0,!1,!1,e.loc);{let e=s.codegenNode;return ov(e,i,n),e}}{let e=s.codegenNode,t=14===e.type&&e.callee===sV?e.arguments[1].returns:e;return 13===t.type&&sZ(t,n),ov(t,i,n),e}}let ao=(e,t,n)=>{let{modifiers:r,loc:i}=e,l=e.arg,{exp:s}=e;if(s&&4===s.type&&!s.content.trim()&&(s=void 0),!s){if(4!==l.type||!l.isStatic)return n.onError(s9(52,l.loc)),{props:[sK(l,sz("",!0,i))]};aa(e),s=e.exp}return 4!==l.type?(l.children.unshift("("),l.children.push(') || ""')):l.isStatic||(l.content=`${l.content} || ""`),r.some(e=>"camel"===e.content)&&(4===l.type?l.isStatic?l.content=z(l.content):l.content=`${n.helperString(sI)}(${l.content})`:(l.children.unshift(`${n.helperString(sI)}(`),l.children.push(")"))),!n.inSSR&&(r.some(e=>"prop"===e.content)&&ac(l,"."),r.some(e=>"attr"===e.content)&&ac(l,"^")),{props:[sK(l,s)]}},aa=(e,t)=>{let n=e.arg,r=z(n.content);e.exp=sz(r,!1,n.loc)},ac=(e,t)=>{4===e.type?e.isStatic?e.content=t+e.content:e.content=`\`${t}\${${e.content}}\``:(e.children.unshift(`'${t}' + (`),e.children.push(")"))},au=o6("for",(e,t,n)=>{let{helper:r,removeHelper:i}=n;return function(e,t,n,r){if(!t.exp){n.onError(s9(31,t.loc));return}let i=t.forParseResult;if(!i){n.onError(s9(32,t.loc));return}ad(i);let{addIdentifiers:l,removeIdentifiers:s,scopes:o}=n,{source:a,value:c,key:u,index:d}=i,p={type:11,loc:t.loc,source:a,valueAlias:c,keyAlias:u,objectIndexAlias:d,parseResult:i,children:om(e)?e.children:[e]};n.replaceNode(p),o.vFor++;let f=r&&r(p);return()=>{o.vFor--,f&&f()}}(e,t,n,t=>{let l=sG(r(s_),[t.source]),s=om(e),o=ou(e,"memo"),a=od(e,"key",!1,!0);a&&7===a.type&&!a.exp&&aa(a);let c=a&&(6===a.type?a.value?sz(a.value.content,!0):void 0:a.exp),u=a&&c?sK("key",c):null,d=4===t.source.type&&t.source.constType>0,p=d?64:a?128:256;return t.codegenNode=sH(n,r(sn),void 0,l,p,void 0,void 0,!0,!d,!1,e.loc),()=>{let a;let{children:p}=t,f=1!==p.length||1!==p[0].type,h=og(e)?e:s&&1===e.children.length&&og(e.children[0])?e.children[0]:null;if(h)a=h.codegenNode,s&&u&&ov(a,u,n);else if(f)a=sH(n,r(sn),u?sW([u]):void 0,e.children,64,void 0,void 0,!0,void 0,!1);else{var m,g,y,b,_,S,x,C;a=p[0].codegenNode,s&&u&&ov(a,u,n),!d!==a.isBlock&&(a.isBlock?(i(so),i((m=n.inSSR,g=a.isComponent,m||g?sa:sc))):i((y=n.inSSR,b=a.isComponent,y||b?su:sd))),(a.isBlock=!d,a.isBlock)?(r(so),r((_=n.inSSR,S=a.isComponent,_||S?sa:sc))):r((x=n.inSSR,C=a.isComponent,x||C?su:sd))}if(o){let e=sX(ap(t.parseResult,[sz("_cached")]));e.body={type:21,body:[sJ(["const _memo = (",o.exp,")"]),sJ(["if (_cached",...c?[" && _cached.key === ",c]:[],` && ${n.helperString(sB)}(_cached, _memo)) return _cached`]),sJ(["const _item = ",a]),sz("_item.memo = _memo"),sz("return _item")],loc:sj},l.arguments.push(e,sz("_cache"),sz(String(n.cached.length))),n.cached.push(null)}else l.arguments.push(sX(ap(t.parseResult),a,!0))}})});function ad(e,t){e.finalized||(e.finalized=!0)}function ap({value:e,key:t,index:n},r=[]){return function(e){let t=e.length;for(;t--&&!e[t];);return e.slice(0,t+1).map((e,t)=>e||sz("_".repeat(t+1),!1))}([e,t,n,...r])}let af=sz("undefined",!1),ah=(e,t)=>{if(1===e.type&&(1===e.tagType||3===e.tagType)){let n=ou(e,"slot");if(n)return n.exp,t.scopes.vSlot++,()=>{t.scopes.vSlot--}}},am=(e,t,n,r)=>sX(e,n,!1,!0,n.length?n[0].loc:r);function ag(e,t,n){let r=[sK("name",e),sK("fn",t)];return null!=n&&r.push(sK("key",sz(String(n),!0))),sW(r)}let ay=new WeakMap,av=(e,t)=>function(){let n,r,i,l,s;if(!(1===(e=t.currentNode).type&&(0===e.tagType||1===e.tagType)))return;let{tag:o,props:a}=e,c=1===e.tagType,u=c?function(e,t,n=!1){let{tag:r}=e,i=aS(r),l=od(e,"is",!1,!0);if(l){if(i){let e;if(6===l.type?e=l.value&&sz(l.value.content,!0):(e=l.exp)||(e=sz("is",!1,l.arg.loc)),e)return sG(t.helper(sg),[e])}else 6===l.type&&l.value.content.startsWith("vue:")&&(r=l.value.content.slice(4))}let s=oe(r)||t.isBuiltInComponent(r);return s?(n||t.helper(s),s):(t.helper(sm),t.components.add(r),o_(r,"component"))}(e,t):`"${o}"`,d=$(u)&&u.callee===sg,p=0,f=d||u===sr||u===si||!c&&("svg"===o||"foreignObject"===o||"math"===o);if(a.length>0){let r=ab(e,t,void 0,c,d);n=r.props,p=r.patchFlag,l=r.dynamicPropNames;let i=r.directives;s=i&&i.length?sq(i.map(e=>(function(e,t){let n=[],r=ay.get(e);r?n.push(t.helperString(r)):(t.helper(sy),t.directives.add(e.name),n.push(o_(e.name,"directive")));let{loc:i}=e;if(e.exp&&n.push(e.exp),e.arg&&(e.exp||n.push("void 0"),n.push(e.arg)),Object.keys(e.modifiers).length){e.arg||(e.exp||n.push("void 0"),n.push("void 0"));let t=sz("true",!1,i);n.push(sW(e.modifiers.map(e=>sK(e,t)),i))}return sq(n,e.loc)})(e,t))):void 0,r.shouldUseBlock&&(f=!0)}if(e.children.length>0){if(u===sl&&(f=!0,p|=1024),c&&u!==sr&&u!==sl){let{slots:n,hasDynamicSlots:i}=function(e,t,n=am){t.helper(s$);let{children:r,loc:i}=e,l=[],s=[],o=t.scopes.vSlot>0||t.scopes.vFor>0,a=ou(e,"slot",!0);if(a){let{arg:e,exp:t}=a;e&&!s7(e)&&(o=!0),l.push(sK(e||sz("default",!0),n(t,void 0,r,i)))}let c=!1,u=!1,d=[],p=new Set,f=0;for(let e=0;esK("default",n(e,void 0,t,i));c?d.length&&d.some(e=>(function e(t){return 2!==t.type&&12!==t.type||(2===t.type?!!t.content.trim():e(t.content))})(e))&&(u?t.onError(s9(39,d[0].loc)):l.push(e(void 0,d))):l.push(e(void 0,r))}let h=o?2:!function e(t){for(let n=0;n0,h=!1,m=0,g=!1,y=!1,b=!1,_=!1,S=!1,C=!1,k=[],T=e=>{u.length&&(d.push(sW(a_(u),a)),u=[]),e&&d.push(e)},N=()=>{t.scopes.vFor>0&&u.push(sK(sz("ref_for",!0),sz("true")))},w=({key:e,value:n})=>{if(s7(e)){let l=e.content,s=x(l);s&&(!r||i)&&"onclick"!==l.toLowerCase()&&"onUpdate:modelValue"!==l&&!H(l)&&(_=!0),s&&H(l)&&(C=!0),s&&14===n.type&&(n=n.arguments[0]),20===n.type||(4===n.type||8===n.type)&&oY(n,t)>0||("ref"===l?g=!0:"class"===l?y=!0:"style"===l?b=!0:"key"===l||k.includes(l)||k.push(l),r&&("class"===l||"style"===l)&&!k.includes(l)&&k.push(l))}else S=!0};for(let i=0;i"prop"===e.content)&&(m|=32);let x=t.directiveTransforms[n];if(x){let{props:n,needRuntime:r}=x(s,e,t);l||n.forEach(w),_&&i&&!s7(i)?T(sW(n,a)):u.push(...n),r&&(p.push(s),L(r)&&ay.set(s,r))}else!q(n)&&(p.push(s),f&&(h=!0))}}if(d.length?(T(),s=d.length>1?sG(t.helper(sk),d,a):d[0]):u.length&&(s=sW(a_(u),a)),S?m|=16:(y&&!r&&(m|=2),b&&!r&&(m|=4),k.length&&(m|=8),_&&(m|=32)),!h&&(0===m||32===m)&&(g||C||p.length>0)&&(m|=512),!t.inSSR&&s)switch(s.type){case 15:let A=-1,E=-1,I=!1;for(let e=0;e{if(og(e)){let{children:n,loc:r}=e,{slotName:i,slotProps:l}=function(e,t){let n,r='"default"',i=[];for(let t=0;t0){let{props:r,directives:l}=ab(e,t,i,!1,!1);n=r,l.length&&t.onError(s9(36,l[0].loc))}return{slotName:r,slotProps:n}}(e,t),s=[t.prefixIdentifiers?"_ctx.$slots":"$slots",i,"{}","undefined","true"],o=2;l&&(s[2]=l,o=3),n.length&&(s[3]=sX([],n,!1,!1,r),o=4),t.scopeId&&!t.slotted&&(o=5),s.splice(o),e.codegenNode=sG(t.helper(sS),s,r)}},aC=(e,t,n,r)=>{let i;let{loc:l,modifiers:s,arg:o}=e;if(e.exp||s.length,4===o.type){if(o.isStatic){let e=o.content;e.startsWith("vue:")&&(e=`vnode-${e.slice(4)}`),i=sz(0!==t.tagType||e.startsWith("vnode")||!/[A-Z]/.test(e)?Q(z(e)):`on:${e}`,!0,o.loc)}else i=sJ([`${n.helperString(sO)}(`,o,")"])}else(i=o).children.unshift(`${n.helperString(sO)}(`),i.children.push(")");let a=e.exp;a&&!a.content.trim()&&(a=void 0);let c=n.cacheHandlers&&!a&&!n.inVOnce;if(a){let e=oo(a),t=!(e||oc(a)),n=a.content.includes(";");(t||c&&e)&&(a=sJ([`${t?"$event":"(...args)"} => ${n?"{":"("}`,a,n?"}":")"]))}let u={props:[sK(i,a||sz("() => {}",!1,l))]};return r&&(u=r(u)),c&&(u.props[0].value=n.cache(u.props[0].value)),u.props.forEach(e=>e.key.isHandlerKey=!0),u},ak=(e,t)=>{if(0===e.type||1===e.type||11===e.type||10===e.type)return()=>{let n;let r=e.children,i=!1;for(let e=0;e7===e.type&&!t.directiveTransforms[e.name]))))for(let e=0;e{if(1===e.type&&ou(e,"once",!0)&&!aT.has(e)&&!t.inVOnce&&!t.inSSR)return aT.add(e),t.inVOnce=!0,t.helper(sP),()=>{t.inVOnce=!1;let e=t.currentNode;e.codegenNode&&(e.codegenNode=t.cache(e.codegenNode,!0,!0))}},aw=(e,t,n)=>{let r;let{exp:i,arg:l}=e;if(!i)return n.onError(s9(41,e.loc)),aA();let s=i.loc.source.trim(),o=4===i.type?i.content:s,a=n.bindingMetadata[s];if("props"===a||"props-aliased"===a)return i.loc,aA();if(!o.trim()||!oo(i))return n.onError(s9(42,i.loc)),aA();let c=l||sz("modelValue",!0),u=l?s7(l)?`onUpdate:${z(l.content)}`:sJ(['"onUpdate:" + ',l]):"onUpdate:modelValue",d=n.isTS?"($event: any)":"$event";r=sJ([`${d} => ((`,i,") = $event)"]);let p=[sK(c,e.exp),sK(u,r)];if(e.modifiers.length&&1===t.tagType){let t=e.modifiers.map(e=>e.content).map(e=>(on(e)?e:JSON.stringify(e))+": true").join(", "),n=l?s7(l)?`${l.content}Modifiers`:sJ([l,' + "Modifiers"']):"modelModifiers";p.push(sK(n,sz(`{ ${t} }`,!1,e.loc,2)))}return aA(p)};function aA(e=[]){return{props:e}}let aE=new WeakSet,aI=(e,t)=>{if(1===e.type){let n=ou(e,"memo");if(!(!n||aE.has(e)))return aE.add(e),()=>{let r=e.codegenNode||t.currentNode.codegenNode;r&&13===r.type&&(1!==e.tagType&&sZ(r,t),e.codegenNode=sG(t.helper(sV),[n.exp,sX(void 0,r),"_cache",String(t.cached.length)]),t.cached.push(null))}}},aR=Symbol(""),aO=Symbol(""),aP=Symbol(""),aM=Symbol(""),aL=Symbol(""),a$=Symbol(""),aD=Symbol(""),aF=Symbol(""),aV=Symbol(""),aB=Symbol("");!function(e){Object.getOwnPropertySymbols(e).forEach(t=>{sU[t]=e[t]})}({[aR]:"vModelRadio",[aO]:"vModelCheckbox",[aP]:"vModelText",[aM]:"vModelSelect",[aL]:"vModelDynamic",[a$]:"withModifiers",[aD]:"withKeys",[aF]:"vShow",[aV]:"Transition",[aB]:"TransitionGroup"});let aU={parseMode:"html",isVoidTag:eh,isNativeTag:e=>ed(e)||ep(e)||ef(e),isPreTag:e=>"pre"===e,isIgnoreNewlineTag:e=>"pre"===e||"textarea"===e,decodeEntities:function(e,t=!1){return(f||(f=document.createElement("div")),t)?(f.innerHTML=`
          `,f.children[0].getAttribute("foo")):(f.innerHTML=e,f.textContent)},isBuiltInComponent:e=>"Transition"===e||"transition"===e?aV:"TransitionGroup"===e||"transition-group"===e?aB:void 0,getNamespace(e,t,n){let r=t?t.ns:n;if(t&&2===r){if("annotation-xml"===t.tag){if("svg"===e)return 1;t.props.some(e=>6===e.type&&"encoding"===e.name&&null!=e.value&&("text/html"===e.value.content||"application/xhtml+xml"===e.value.content))&&(r=0)}else/^m(?:[ions]|text)$/.test(t.tag)&&"mglyph"!==e&&"malignmark"!==e&&(r=0)}else t&&1===r&&("foreignObject"===t.tag||"desc"===t.tag||"title"===t.tag)&&(r=0);if(0===r){if("svg"===e)return 1;if("math"===e)return 2}return r}},aj=(e,t)=>sz(JSON.stringify(ec(e)),!1,t,3),aH=g("passive,once,capture"),aq=g("stop,prevent,self,ctrl,shift,alt,meta,exact,middle"),aW=g("left,right"),aK=g("onkeyup,onkeydown,onkeypress"),az=(e,t,n,r)=>{let i=[],l=[],s=[];for(let n=0;ns7(e)&&"onclick"===e.content.toLowerCase()?sz(t,!0):4!==e.type?sJ(["(",e,`) === "onClick" ? "${t}" : (`,e,")"]):e,aG=(e,t)=>{1===e.type&&0===e.tagType&&("script"===e.tag||"style"===e.tag)&&t.removeNode()},aX=[e=>{1===e.type&&e.props.forEach((t,n)=>{6===t.type&&"style"===t.name&&t.value&&(e.props[n]={type:7,name:"bind",arg:sz("style",!0,t.loc),exp:aj(t.value.content,t.loc),modifiers:[],loc:t.loc})})}],aQ={cloak:()=>({props:[]}),html:(e,t,n)=>{let{exp:r,loc:i}=e;return r||n.onError(s9(53,i)),t.children.length&&(n.onError(s9(54,i)),t.children.length=0),{props:[sK(sz("innerHTML",!0,i),r||sz("",!0))]}},text:(e,t,n)=>{let{exp:r,loc:i}=e;return r||n.onError(s9(55,i)),t.children.length&&(n.onError(s9(56,i)),t.children.length=0),{props:[sK(sz("textContent",!0),r?oY(r,n)>0?r:sG(n.helperString(sC),[r],i):sz("",!0))]}},model:(e,t,n)=>{let r=aw(e,t,n);if(!r.props.length||1===t.tagType)return r;e.arg&&n.onError(s9(58,e.arg.loc));let{tag:i}=t,l=n.isCustomElement(i);if("input"===i||"textarea"===i||"select"===i||l){let s=aP,o=!1;if("input"===i||l){let r=od(t,"type");if(r){if(7===r.type)s=aL;else if(r.value)switch(r.value.content){case"radio":s=aR;break;case"checkbox":s=aO;break;case"file":o=!0,n.onError(s9(59,e.loc))}}else t.props.some(e=>7===e.type&&"bind"===e.name&&(!e.arg||4!==e.arg.type||!e.arg.isStatic))&&(s=aL)}else"select"===i&&(s=aM);o||(r.needRuntime=n.helper(s))}else n.onError(s9(57,e.loc));return r.props=r.props.filter(e=>!(4===e.key.type&&"modelValue"===e.key.content)),r},on:(e,t,n)=>aC(e,t,n,t=>{let{modifiers:r}=e;if(!r.length)return t;let{key:i,value:l}=t.props[0],{keyModifiers:s,nonKeyModifiers:o,eventOptionModifiers:a}=az(i,r,n,e.loc);if(o.includes("right")&&(i=aJ(i,"onContextmenu")),o.includes("middle")&&(i=aJ(i,"onMouseup")),o.length&&(l=sG(n.helper(a$),[l,JSON.stringify(o)])),s.length&&(!s7(i)||aK(i.content.toLowerCase()))&&(l=sG(n.helper(aD),[l,JSON.stringify(s)])),a.length){let e=a.map(X).join("");i=s7(i)?sz(`${i.content}${e}`,!0):sJ(["(",i,`) + "${e}"`])}return{props:[sK(i,l)]}}),show:(e,t,n)=>{let{exp:r,loc:i}=e;return!r&&n.onError(s9(61,i)),{props:[],needRuntime:n.helper(aF)}}},aZ=Object.create(null);function aY(e,t){if(!M(e)){if(!e.nodeType)return _;e=e.innerHTML}let n=e+JSON.stringify(t,(e,t)=>"function"==typeof t?t.toString():t),r=aZ[n];if(r)return r;if("#"===e[0]){let t=document.querySelector(e);e=t?t.innerHTML:""}let i=k({hoistStatic:!0,onError:void 0,onWarn:_},t);i.isCustomElement||"undefined"==typeof customElements||(i.isCustomElement=e=>!!customElements.get(e));let{code:l}=function(e,t={}){return function(e,t={}){let n=t.onError||s8,r="module"===t.mode;!0===t.prefixIdentifiers?n(s9(47)):r&&n(s9(48)),t.cacheHandlers&&n(s9(49)),t.scopeId&&!r&&n(s9(50));let i=k({},t,{prefixIdentifiers:!1}),l=M(e)?function(e,t){if(oL.reset(),oN=null,ow=null,oA="",oE=-1,oI=-1,oM.length=0,oT=e,oC=k({},ox),t){let e;for(e in t)null!=t[e]&&(oC[e]=t[e])}oL.mode="html"===oC.parseMode?1:"sfc"===oC.parseMode?2:0,oL.inXML=1===oC.ns||2===oC.ns;let n=t&&t.delimiters;n&&(oL.delimiterOpen=s6(n[0]),oL.delimiterClose=s6(n[1]));let r=ok=function(e,t=""){return{type:0,source:t,children:e,helpers:new Set,components:[],directives:[],hoists:[],imports:[],cached:[],temps:0,codegenNode:void 0,loc:sj}}([],e);return oL.parse(oT),r.loc=oJ(0,e.length),r.children=oW(r.children),ok=null,r}(e,i):e,[s,o]=[[aN,ar,aI,au,ax,av,ah,ak],{on:aC,bind:ao,model:aw}];return!function(e,t){let n=function(e,{filename:t="",prefixIdentifiers:n=!1,hoistStatic:r=!1,hmr:i=!1,cacheHandlers:l=!1,nodeTransforms:s=[],directiveTransforms:o={},transformHoist:a=null,isBuiltInComponent:c=_,isCustomElement:u=_,expressionPlugins:d=[],scopeId:p=null,slotted:f=!0,ssr:h=!1,inSSR:m=!1,ssrCssVars:g="",bindingMetadata:b=y,inline:S=!1,isTS:x=!1,onError:C=s8,onWarn:k=s5,compatConfig:T}){let N=t.replace(/\?.*$/,"").match(/([^/\\]+)\.\w+$/),w={filename:t,selfName:N&&X(z(N[1])),prefixIdentifiers:n,hoistStatic:r,hmr:i,cacheHandlers:l,nodeTransforms:s,directiveTransforms:o,transformHoist:a,isBuiltInComponent:c,isCustomElement:u,expressionPlugins:d,scopeId:p,slotted:f,ssr:h,inSSR:m,ssrCssVars:g,bindingMetadata:b,inline:S,isTS:x,onError:C,onWarn:k,compatConfig:T,root:e,helpers:new Map,components:new Set,directives:new Set,hoists:[],imports:[],cached:[],constantCache:new WeakMap,temps:0,identifiers:Object.create(null),scopes:{vFor:0,vSlot:0,vPre:0,vOnce:0},parent:null,grandParent:null,currentNode:e,childIndex:0,inVOnce:!1,helper(e){let t=w.helpers.get(e)||0;return w.helpers.set(e,t+1),e},removeHelper(e){let t=w.helpers.get(e);if(t){let n=t-1;n?w.helpers.set(e,n):w.helpers.delete(e)}},helperString:e=>`_${sU[w.helper(e)]}`,replaceNode(e){w.parent.children[w.childIndex]=w.currentNode=e},removeNode(e){let t=w.parent.children,n=e?t.indexOf(e):w.currentNode?w.childIndex:-1;e&&e!==w.currentNode?w.childIndex>n&&(w.childIndex--,w.onNodeRemoved()):(w.currentNode=null,w.onNodeRemoved()),w.parent.children.splice(n,1)},onNodeRemoved:_,addIdentifiers(e){},removeIdentifiers(e){},hoist(e){M(e)&&(e=sz(e)),w.hoists.push(e);let t=sz(`_hoisted_${w.hoists.length}`,!1,e.loc,2);return t.hoisted=e,t},cache(e,t=!1,n=!1){let r=function(e,t,n=!1,r=!1){return{type:20,index:e,value:t,needPauseTracking:n,inVOnce:r,needArraySpread:!1,loc:sj}}(w.cached.length,e,t,n);return w.cached.push(r),r}};return w}(e,t);o3(e,n),t.hoistStatic&&function e(t,n,r,i=!1,l=!1){let{children:s}=t,o=[];for(let n=0;n0){if(e>=2){a.codegenNode.patchFlag=-1,o.push(a);continue}}else{let e=a.codegenNode;if(13===e.type){let t=e.patchFlag;if((void 0===t||512===t||1===t)&&o1(a,r)>=2){let t=o2(a);t&&(e.props=r.hoist(t))}e.dynamicProps&&(e.dynamicProps=r.hoist(e.dynamicProps))}}}else if(12===a.type&&(i?0:oY(a,r))>=2){o.push(a);continue}if(1===a.type){let n=1===a.tagType;n&&r.scopes.vSlot++,e(a,t,r,!1,l),n&&r.scopes.vSlot--}else if(11===a.type)e(a,t,r,1===a.children.length,!0);else if(9===a.type)for(let n=0;ne.key===t||e.key.content===t);return n&&n.value}}o.length&&r.transformHoist&&r.transformHoist(s,r,t)}(e,void 0,n,oZ(e,e.children[0])),t.ssr||function(e,t){let{helper:n}=t,{children:r}=e;if(1===r.length){let n=r[0];if(oZ(e,n)&&n.codegenNode){let r=n.codegenNode;13===r.type&&sZ(r,t),e.codegenNode=r}else e.codegenNode=n}else r.length>1&&(e.codegenNode=sH(t,n(sn),void 0,e.children,64,void 0,void 0,!0,void 0,!1))}(e,n),e.helpers=new Set([...n.helpers.keys()]),e.components=[...n.components],e.directives=[...n.directives],e.imports=n.imports,e.hoists=n.hoists,e.temps=n.temps,e.cached=n.cached,e.transformed=!0}(l,k({},i,{nodeTransforms:[...s,...t.nodeTransforms||[]],directiveTransforms:k({},o,t.directiveTransforms||{})})),function(e,t={}){let n=function(e,{mode:t="function",prefixIdentifiers:n="module"===t,sourceMap:r=!1,filename:i="template.vue.html",scopeId:l=null,optimizeImports:s=!1,runtimeGlobalName:o="Vue",runtimeModuleName:a="vue",ssrRuntimeModuleName:c="vue/server-renderer",ssr:u=!1,isTS:d=!1,inSSR:p=!1}){let f={mode:t,prefixIdentifiers:n,sourceMap:r,filename:i,scopeId:l,optimizeImports:s,runtimeGlobalName:o,runtimeModuleName:a,ssrRuntimeModuleName:c,ssr:u,isTS:d,inSSR:p,source:e.source,code:"",column:1,line:1,offset:0,indentLevel:0,pure:!1,map:void 0,helper:e=>`_${sU[e]}`,push(e,t=-2,n){f.code+=e},indent(){h(++f.indentLevel)},deindent(e=!1){e?--f.indentLevel:h(--f.indentLevel)},newline(){h(f.indentLevel)}};function h(e){f.push("\n"+" ".repeat(e),0)}return f}(e,t);t.onContextCreated&&t.onContextCreated(n);let{mode:r,push:i,prefixIdentifiers:l,indent:s,deindent:o,newline:a,scopeId:c,ssr:u}=n,d=Array.from(e.helpers),p=d.length>0,f=!l&&"module"!==r;(function(e,t){let{ssr:n,prefixIdentifiers:r,push:i,newline:l,runtimeModuleName:s,runtimeGlobalName:o,ssrRuntimeModuleName:a}=t,c=Array.from(e.helpers);if(c.length>0&&(i(`const _Vue = ${o} +`,-1),e.hoists.length)){let e=[su,sd,sp,sf,sh].filter(e=>c.includes(e)).map(o8).join(", ");i(`const { ${e} } = _Vue +`,-1)}(function(e,t){if(!e.length)return;t.pure=!0;let{push:n,newline:r}=t;r();for(let i=0;i0)&&a()),e.directives.length&&(o5(e.directives,"directive",n),e.temps>0&&a()),e.temps>0){i("let ");for(let t=0;t0?", ":""}_temp${t}`)}return(e.components.length||e.directives.length||e.temps)&&(i(` +`,0),a()),u||i("return "),e.codegenNode?ae(e.codegenNode,n):i("null"),f&&(o(),i("}")),o(),i("}"),{ast:e,code:n.code,preamble:"",map:n.map?n.map.toJSON():void 0}}(l,i)}(e,k({},aU,t,{nodeTransforms:[aG,...aX,...t.nodeTransforms||[]],directiveTransforms:k({},aQ,t.directiveTransforms||{}),transformHoist:null}))}(e,i),s=Function(l)();return s._rc=!0,aZ[n]=s}return iU(aY),e.BaseTransition=n_,e.BaseTransitionPropsValidators=ny,e.Comment=io,e.DeprecationTypes=null,e.EffectScope=ex,e.ErrorCodes={SETUP_FUNCTION:0,0:"SETUP_FUNCTION",RENDER_FUNCTION:1,1:"RENDER_FUNCTION",NATIVE_EVENT_HANDLER:5,5:"NATIVE_EVENT_HANDLER",COMPONENT_EVENT_HANDLER:6,6:"COMPONENT_EVENT_HANDLER",VNODE_HOOK:7,7:"VNODE_HOOK",DIRECTIVE_HOOK:8,8:"DIRECTIVE_HOOK",TRANSITION_HOOK:9,9:"TRANSITION_HOOK",APP_ERROR_HANDLER:10,10:"APP_ERROR_HANDLER",APP_WARN_HANDLER:11,11:"APP_WARN_HANDLER",FUNCTION_REF:12,12:"FUNCTION_REF",ASYNC_COMPONENT_LOADER:13,13:"ASYNC_COMPONENT_LOADER",SCHEDULER:14,14:"SCHEDULER",COMPONENT_UPDATE:15,15:"COMPONENT_UPDATE",APP_UNMOUNT_CLEANUP:16,16:"APP_UNMOUNT_CLEANUP"},e.ErrorTypeStrings=null,e.Fragment=il,e.KeepAlive={name:"KeepAlive",__isKeepAlive:!0,props:{include:[String,RegExp,Array],exclude:[String,RegExp,Array],max:[String,Number]},setup(e,{slots:t}){let n=iL(),r=n.ctx,i=new Map,l=new Set,s=null,o=n.suspense,{renderer:{p:a,m:c,um:u,o:{createElement:d}}}=r,p=d("div");function f(e){nG(e),u(e,n,o,!0)}function h(e){i.forEach((t,n)=>{let r=iK(t.type);r&&!e(r)&&m(n)})}function m(e){let t=i.get(e);!t||s&&ib(t,s)?s&&nG(s):f(t),i.delete(e),l.delete(e)}r.activate=(e,t,n,r,i)=>{let l=e.component;c(e,t,n,0,o),a(l.vnode,e,t,n,l,o,r,e.slotScopeIds,i),rB(()=>{l.isDeactivated=!1,l.a&&Y(l.a);let t=e.props&&e.props.onVnodeMounted;t&&iR(t,l.parent,e)},o)},r.deactivate=e=>{let t=e.component;rz(t.m),rz(t.a),c(e,p,null,1,o),rB(()=>{t.da&&Y(t.da);let n=e.props&&e.props.onVnodeUnmounted;n&&iR(n,t.parent,e),t.isDeactivated=!0},o)},rX(()=>[e.include,e.exclude],([e,t])=>{e&&h(t=>nW(e,t)),t&&h(e=>!nW(t,e))},{flush:"post",deep:!0});let g=null,y=()=>{null!=g&&(r5(n.subTree.type)?rB(()=>{i.set(g,nX(n.subTree))},n.subTree.suspense):i.set(g,nX(n.subTree)))};return n0(y),n2(y),n3(()=>{i.forEach(e=>{let{subTree:t,suspense:r}=n,i=nX(t);if(e.type===i.type&&e.key===i.key){nG(i);let e=i.component.da;e&&rB(e,r);return}f(e)})}),()=>{if(g=null,!t.default)return s=null;let n=t.default(),r=n[0];if(n.length>1)return s=null,n;if(!iv(r)||!(4&r.shapeFlag)&&!(128&r.shapeFlag))return s=null,r;let o=nX(r);if(o.type===io)return s=null,o;let a=o.type,c=iK(nj(o)?o.type.__asyncResolved||{}:a),{include:u,exclude:d,max:p}=e;if(u&&(!c||!nW(u,c))||d&&c&&nW(d,c))return o.shapeFlag&=-257,s=o,r;let f=null==o.key?a:o.key,h=i.get(f);return o.el&&(o=iT(o),128&r.shapeFlag&&(r.ssContent=o)),g=f,h?(o.el=h.el,o.component=h.component,o.transition&&nT(o,o.transition),o.shapeFlag|=512,l.delete(f),l.add(f)):(l.add(f),p&&l.size>parseInt(p,10)&&m(l.values().next().value)),o.shapeFlag|=256,s=o,r5(r.type)?r:o}}},e.ReactiveEffect=ek,e.Static=ia,e.Suspense={name:"Suspense",__isSuspense:!0,process(e,t,n,r,i,l,s,o,a,c){if(null==e)(function(e,t,n,r,i,l,s,o,a){let{p:c,o:{createElement:u}}=a,d=u("div"),p=e.suspense=ie(e,i,r,t,d,n,l,s,o,a);c(null,p.pendingBranch=e.ssContent,d,null,r,p,l,s),p.deps>0?(r7(e,"onPending"),r7(e,"onFallback"),c(null,e.ssFallback,t,n,r,null,l,s),ii(p,e.ssFallback)):p.resolve(!1,!0)})(t,n,r,i,l,s,o,a,c);else{if(l&&l.deps>0&&!e.suspense.isInFallback){t.suspense=e.suspense,t.suspense.vnode=t,t.el=e.el;return}(function(e,t,n,r,i,l,s,o,{p:a,um:c,o:{createElement:u}}){let d=t.suspense=e.suspense;d.vnode=t,t.el=e.el;let p=t.ssContent,f=t.ssFallback,{activeBranch:h,pendingBranch:m,isInFallback:g,isHydrating:y}=d;if(m)d.pendingBranch=p,ib(p,m)?(a(m,p,d.hiddenContainer,null,i,d,l,s,o),d.deps<=0?d.resolve():g&&!y&&(a(h,f,n,r,i,null,l,s,o),ii(d,f))):(d.pendingId=r9++,y?(d.isHydrating=!1,d.activeBranch=m):c(m,i,d),d.deps=0,d.effects.length=0,d.hiddenContainer=u("div"),g?(a(null,p,d.hiddenContainer,null,i,d,l,s,o),d.deps<=0?d.resolve():(a(h,f,n,r,i,null,l,s,o),ii(d,f))):h&&ib(p,h)?(a(h,p,n,r,i,d,l,s,o),d.resolve(!0)):(a(null,p,d.hiddenContainer,null,i,d,l,s,o),d.deps<=0&&d.resolve()));else if(h&&ib(p,h))a(h,p,n,r,i,d,l,s,o),ii(d,p);else if(r7(t,"onPending"),d.pendingBranch=p,512&p.shapeFlag?d.pendingId=p.component.suspenseId:d.pendingId=r9++,a(null,p,d.hiddenContainer,null,i,d,l,s,o),d.deps<=0)d.resolve();else{let{timeout:e,pendingId:t}=d;e>0?setTimeout(()=>{d.pendingId===t&&d.fallback(f)},e):0===e&&d.fallback(f)}})(e,t,n,r,i,s,o,a,c)}},hydrate:function(e,t,n,r,i,l,s,o,a){let c=t.suspense=ie(t,r,n,e.parentNode,document.createElement("div"),null,i,l,s,o,!0),u=a(e,c.pendingBranch=t.ssContent,n,c,l,s);return 0===c.deps&&c.resolve(!1,!0),u},normalize:function(e){let{shapeFlag:t,children:n}=e,r=32&t;e.ssContent=it(r?n.default:n),e.ssFallback=r?it(n.fallback):iC(io)}},e.Teleport=nc,e.Text=is,e.TrackOpTypes={GET:"get",HAS:"has",ITERATE:"iterate"},e.Transition=i8,e.TransitionGroup=lF,e.TriggerOpTypes={SET:"set",ADD:"add",DELETE:"delete",CLEAR:"clear"},e.VueElement=lO,e.assertNumber=function(e,t){},e.callWithAsyncErrorHandling=tW,e.callWithErrorHandling=tq,e.camelize=z,e.capitalize=X,e.cloneVNode=iT,e.compatUtils=null,e.compile=aY,e.computed=iz,e.createApp=l9,e.createBlock=iy,e.createCommentVNode=function(e="",t=!1){return t?(id(),iy(io,null,e)):iC(io,null,e)},e.createElementBlock=function(e,t,n,r,i,l){return ig(ix(e,t,n,r,i,l,!0))},e.createElementVNode=ix,e.createHydrationRenderer=rU,e.createPropsRestProxy=function(e,t){let n={};for(let r in e)t.includes(r)||Object.defineProperty(n,r,{enumerable:!0,get:()=>e[r]});return n},e.createRenderer=function(e){return rj(e)},e.createSSRApp=l7,e.createSlots=function(e,t){for(let n=0;n{let t=r.fn(...e);return t&&(t.key=r.key),t}:r.fn)}return e},e.createStaticVNode=function(e,t){let n=iC(ia,null,e);return n.staticCount=t,n},e.createTextVNode=iN,e.createVNode=iC,e.customRef=tL,e.defineAsyncComponent=function(e){let t;P(e)&&(e={loader:e});let{loader:n,loadingComponent:r,errorComponent:i,delay:l=200,hydrate:s,timeout:o,suspensible:a=!0,onError:c}=e,u=null,d=0,p=()=>(d++,u=null,f()),f=()=>{let e;return u||(e=u=n().catch(e=>{if(e=e instanceof Error?e:Error(String(e)),c)return new Promise((t,n)=>{c(e,()=>t(p()),()=>n(e),d+1)});throw e}).then(n=>e!==u&&u?u:(n&&(n.__esModule||"Module"===n[Symbol.toStringTag])&&(n=n.default),t=n,n)))};return nw({name:"AsyncComponentWrapper",__asyncLoader:f,__asyncHydrate(e,n,r){let i=s?()=>{let t=s(r,t=>(function(e,t){if(nL(e)&&"["===e.data){let n=1,r=e.nextSibling;for(;r;){if(1===r.nodeType){if(!1===t(r))break}else if(nL(r)){if("]"===r.data){if(0==--n)break}else"["===r.data&&n++}r=r.nextSibling}}else t(e)})(e,t));t&&(n.bum||(n.bum=[])).push(t)}:r;t?i():f().then(()=>!n.isUnmounted&&i())},get __asyncResolved(){return t},setup(){let e=iM;if(nA(e),t)return()=>nH(t,e);let n=t=>{u=null,tK(t,e,13,!i)};if(a&&e.suspense)return f().then(t=>()=>nH(t,e)).catch(e=>(n(e),()=>i?iC(i,{error:e}):null));let s=tw(!1),c=tw(),d=tw(!!l);return l&&setTimeout(()=>{d.value=!1},l),null!=o&&setTimeout(()=>{if(!s.value&&!c.value){let e=Error(`Async component timed out after ${o}ms.`);n(e),c.value=e}},o),f().then(()=>{s.value=!0,e.parent&&nq(e.parent.vnode)&&e.parent.update()}).catch(e=>{n(e),c.value=e}),()=>s.value&&t?nH(t,e):c.value&&i?iC(i,{error:c.value}):r&&!d.value?iC(r):void 0}})},e.defineComponent=nw,e.defineCustomElement=lI,e.defineEmits=function(){return null},e.defineExpose=function(e){},e.defineModel=function(){},e.defineOptions=function(e){},e.defineProps=function(){return null},e.defineSSRCustomElement=(e,t)=>lI(e,t,l7),e.defineSlots=function(){return null},e.devtools=void 0,e.effect=function(e,t){e.effect instanceof ek&&(e=e.effect.fn);let n=new ek(e);t&&k(n,t);try{n.run()}catch(e){throw n.stop(),e}let r=n.run.bind(n);return r.effect=n,r},e.effectScope=function(e){return new ex(e)},e.getCurrentInstance=iL,e.getCurrentScope=function(){return i},e.getCurrentWatcher=function(){return h},e.getTransitionRawChildren=nN,e.guardReactiveProps=ik,e.h=iJ,e.handleError=tK,e.hasInjectionContext=function(){return!!(iM||t5||rx)},e.hydrate=(...e)=>{l8().hydrate(...e)},e.hydrateOnIdle=(e=1e4)=>t=>{let n=nB(t,{timeout:e});return()=>nU(n)},e.hydrateOnInteraction=(e=[])=>(t,n)=>{M(e)&&(e=[e]);let r=!1,i=e=>{r||(r=!0,l(),t(),e.target.dispatchEvent(new e.constructor(e.type,e)))},l=()=>{n(t=>{for(let n of e)t.removeEventListener(n,i)})};return n(t=>{for(let n of e)t.addEventListener(n,i,{once:!0})}),l},e.hydrateOnMediaQuery=e=>t=>{if(e){let n=matchMedia(e);if(!n.matches)return n.addEventListener("change",t,{once:!0}),()=>n.removeEventListener("change",t);t()}},e.hydrateOnVisible=e=>(t,n)=>{let r=new IntersectionObserver(e=>{for(let n of e)if(n.isIntersecting){r.disconnect(),t();break}},e);return n(e=>{if(e instanceof Element){if(function(e){let{top:t,left:n,bottom:r,right:i}=e.getBoundingClientRect(),{innerHeight:l,innerWidth:s}=window;return(t>0&&t0&&r0&&n0&&ir.disconnect()},e.initCustomFormatter=function(){},e.initDirectivesForSSR=_,e.inject=rk,e.isMemoSame=iG,e.isProxy=tS,e.isReactive=tv,e.isReadonly=tb,e.isRef=tN,e.isRuntimeOnly=()=>!u,e.isShallow=t_,e.isVNode=iv,e.markRaw=tC,e.mergeDefaults=function(e,t){let n=rc(e);for(let e in t){if(e.startsWith("__skip"))continue;let r=n[e];r?A(r)||P(r)?r=n[e]={type:r,default:t[e]}:r.default=t[e]:null===r&&(r=n[e]={default:t[e]}),r&&t[`__skip_${e}`]&&(r.skipFactory=!0)}return n},e.mergeModels=function(e,t){return e&&t?A(e)&&A(t)?e.concat(t):k({},rc(e),rc(t)):e||t},e.mergeProps=iI,e.nextTick=t0,e.normalizeClass=eu,e.normalizeProps=function(e){if(!e)return null;let{class:t,style:n}=e;return t&&!M(t)&&(e.class=eu(t)),n&&(e.style=el(n)),e},e.normalizeStyle=el,e.onActivated=nK,e.onBeforeMount=nY,e.onBeforeUnmount=n3,e.onBeforeUpdate=n1,e.onDeactivated=nz,e.onErrorCaptured=n9,e.onMounted=n0,e.onRenderTracked=n5,e.onRenderTriggered=n8,e.onScopeDispose=function(e,t=!1){i&&i.cleanups.push(e)},e.onServerPrefetch=n4,e.onUnmounted=n6,e.onUpdated=n2,e.onWatcherCleanup=tj,e.openBlock=id,e.popScopeId=function(){t9=null},e.provide=rC,e.proxyRefs=tP,e.pushScopeId=function(e){t9=e},e.queuePostFlushCb=t3,e.reactive=th,e.readonly=tg,e.ref=tw,e.registerRuntimeCompiler=iU,e.render=l5,e.renderList=function(e,t,n,r){let i;let l=n&&n[r],s=A(e);if(s||M(e)){let n=s&&tv(e),r=!1;n&&(r=!t_(e),e=eJ(e)),i=Array(e.length);for(let n=0,s=e.length;nt(e,n,void 0,l&&l[n]));else{let n=Object.keys(e);i=Array(n.length);for(let r=0,s=n.length;r!iv(t)||!!(t.type!==io&&(t.type!==il||e(t.children))))?t:null}(l(n)),o=n.key||s&&s.key,a=iy(il,{key:(o&&!L(o)?o:`_${t}`)+(!s&&r?"_fb":"")},s||(r?r():[]),s&&1===e._?64:-2);return!i&&a.scopeId&&(a.slotScopeIds=[a.scopeId+"-s"]),l&&l._c&&(l._d=!0),a},e.resolveComponent=function(e,t){return rt(n7,e,!0,t)||e},e.resolveDirective=function(e){return rt("directives",e)},e.resolveDynamicComponent=function(e){return M(e)?rt(n7,e,!1)||e:e||re},e.resolveFilter=null,e.resolveTransitionHooks=nx,e.setBlockTracking=im,e.setDevtoolsHook=_,e.setTransitionHooks=nT,e.shallowReactive=tm,e.shallowReadonly=function(e){return ty(e,!0,tt,tc,tf)},e.shallowRef=tA,e.ssrContextKey=rJ,e.ssrUtils=null,e.stop=function(e){e.effect.stop()},e.toDisplayString=eb,e.toHandlerKey=Q,e.toHandlers=function(e,t){let n={};for(let r in e)n[t&&/[A-Z]/.test(r)?`on:${r}`:Q(r)]=e[r];return n},e.toRaw=tx,e.toRef=function(e,t,n){return tN(e)?e:P(e)?new tD(e):$(e)&&arguments.length>1?tF(e,t,n):tw(e)},e.toRefs=function(e){let t=A(e)?Array(e.length):{};for(let n in e)t[n]=tF(e,n);return t},e.toValue=function(e){return P(e)?e():tR(e)},e.transformVNodeArgs=function(e){},e.triggerRef=function(e){e.dep&&e.dep.trigger()},e.unref=tR,e.useAttrs=function(){return ra().attrs},e.useCssModule=function(e="$style"){return y},e.useCssVars=function(e){let t=iL();if(!t)return;let n=t.ut=(n=e(t.proxy))=>{Array.from(document.querySelectorAll(`[data-v-owner="${t.uid}"]`)).forEach(e=>lf(e,n))},r=()=>{let r=e(t.proxy);t.ce?lf(t.ce,r):function e(t,n){if(128&t.shapeFlag){let r=t.suspense;t=r.activeBranch,r.pendingBranch&&!r.isHydrating&&r.effects.push(()=>{e(r.activeBranch,n)})}for(;t.component;)t=t.component.subTree;if(1&t.shapeFlag&&t.el)lf(t.el,n);else if(t.type===il)t.children.forEach(t=>e(t,n));else if(t.type===ia){let{el:e,anchor:r}=t;for(;e&&(lf(e,n),e!==r);)e=e.nextSibling}}(t.subTree,r),n(r)};n1(()=>{t3(r)}),n0(()=>{rX(r,_,{flush:"post"});let e=new MutationObserver(r);e.observe(t.subTree.el.parentNode,{childList:!0}),n6(()=>e.disconnect())})},e.useHost=lP,e.useId=function(){let e=iL();return e?(e.appContext.config.idPrefix||"v")+"-"+e.ids[0]+e.ids[1]++:""},e.useModel=function(e,t,n=y){let r=iL(),i=z(t),l=G(t),s=rY(e,i),o=tL((s,o)=>{let a,c;let u=y;return rG(()=>{let t=e[i];Z(a,t)&&(a=t,o())}),{get:()=>(s(),n.get?n.get(a):a),set(e){let s=n.set?n.set(e):e;if(!Z(s,a)&&!(u!==y&&Z(e,u)))return;let d=r.vnode.props;d&&(t in d||i in d||l in d)&&(`onUpdate:${t}`in d||`onUpdate:${i}`in d||`onUpdate:${l}`in d)||(a=e,o()),r.emit(`update:${t}`,s),Z(e,s)&&Z(e,u)&&!Z(s,c)&&o(),u=e,c=s}}});return o[Symbol.iterator]=()=>{let e=0;return{next:()=>e<2?{value:e++?s||y:o,done:!1}:{done:!0}}},o},e.useSSRContext=()=>{},e.useShadowRoot=function(){let e=lP();return e&&e.shadowRoot},e.useSlots=function(){return ra().slots},e.useTemplateRef=function(e){let t=iL(),n=tA(null);return t&&Object.defineProperty(t.refs===y?t.refs={}:t.refs,e,{enumerable:!0,get:()=>n.value,set:e=>n.value=e}),n},e.useTransitionState=nm,e.vModelCheckbox=lz,e.vModelDynamic={created(e,t,n){l0(e,t,n,null,"created")},mounted(e,t,n){l0(e,t,n,null,"mounted")},beforeUpdate(e,t,n,r){l0(e,t,n,r,"beforeUpdate")},updated(e,t,n,r){l0(e,t,n,r,"updated")}},e.vModelRadio=lG,e.vModelSelect=lX,e.vModelText=lK,e.vShow={beforeMount(e,{value:t},{transition:n}){e[lc]="none"===e.style.display?"":e.style.display,n&&t?n.beforeEnter(e):ld(e,t)},mounted(e,{value:t},{transition:n}){n&&t&&n.enter(e)},updated(e,{value:t,oldValue:n},{transition:r}){!t!=!n&&(r?t?(r.beforeEnter(e),ld(e,!0),r.enter(e)):r.leave(e,()=>{ld(e,!1)}):ld(e,t))},beforeUnmount(e,{value:t}){ld(e,t)}},e.version=iX,e.warn=_,e.watch=function(e,t,n){return rX(e,t,n)},e.watchEffect=function(e,t){return rX(e,null,t)},e.watchPostEffect=function(e,t){return rX(e,null,{flush:"post"})},e.watchSyncEffect=rG,e.withAsyncContext=function(e){let t=iL(),n=e();return iD(),D(n)&&(n=n.catch(e=>{throw i$(t),e})),[n,()=>i$(t)]},e.withCtx=ne,e.withDefaults=function(e,t){return null},e.withDirectives=function(e,t){if(null===t5)return e;let n=iW(t5),r=e.dirs||(e.dirs=[]);for(let e=0;e{let n=e._withKeys||(e._withKeys={}),r=t.join(".");return n[r]||(n[r]=n=>{if(!("key"in n))return;let r=G(n.key);if(t.some(e=>e===r||l3[e]===r))return e(n)})},e.withMemo=function(e,t,n,r){let i=n[r];if(i&&iG(i,e))return i;let l=t();return l.memo=e.slice(),l.cacheIndex=r,n[r]=l},e.withModifiers=(e,t)=>{let n=e._withMods||(e._withMods={}),r=t.join(".");return n[r]||(n[r]=(n,...r)=>{for(let e=0;ene,e}({}); diff --git a/system/typemill/author/js/vue.js b/system/typemill/author/js/vue.js new file mode 100644 index 0000000..ba3fbfb --- /dev/null +++ b/system/typemill/author/js/vue.js @@ -0,0 +1,18096 @@ +/** +* vue v3.5.13 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/ +var Vue = (function (exports) { + 'use strict'; + + /*! #__NO_SIDE_EFFECTS__ */ + // @__NO_SIDE_EFFECTS__ + function makeMap(str) { + const map = /* @__PURE__ */ Object.create(null); + for (const key of str.split(",")) map[key] = 1; + return (val) => val in map; + } + + const EMPTY_OBJ = Object.freeze({}) ; + const EMPTY_ARR = Object.freeze([]) ; + const NOOP = () => { + }; + const NO = () => false; + const isOn = (key) => key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && // uppercase letter + (key.charCodeAt(2) > 122 || key.charCodeAt(2) < 97); + const isModelListener = (key) => key.startsWith("onUpdate:"); + const extend = Object.assign; + const remove = (arr, el) => { + const i = arr.indexOf(el); + if (i > -1) { + arr.splice(i, 1); + } + }; + const hasOwnProperty$1 = Object.prototype.hasOwnProperty; + const hasOwn = (val, key) => hasOwnProperty$1.call(val, key); + const isArray = Array.isArray; + const isMap = (val) => toTypeString(val) === "[object Map]"; + const isSet = (val) => toTypeString(val) === "[object Set]"; + const isDate = (val) => toTypeString(val) === "[object Date]"; + const isRegExp = (val) => toTypeString(val) === "[object RegExp]"; + const isFunction = (val) => typeof val === "function"; + const isString = (val) => typeof val === "string"; + const isSymbol = (val) => typeof val === "symbol"; + const isObject = (val) => val !== null && typeof val === "object"; + const isPromise = (val) => { + return (isObject(val) || isFunction(val)) && isFunction(val.then) && isFunction(val.catch); + }; + const objectToString = Object.prototype.toString; + const toTypeString = (value) => objectToString.call(value); + const toRawType = (value) => { + return toTypeString(value).slice(8, -1); + }; + const isPlainObject = (val) => toTypeString(val) === "[object Object]"; + const isIntegerKey = (key) => isString(key) && key !== "NaN" && key[0] !== "-" && "" + parseInt(key, 10) === key; + const isReservedProp = /* @__PURE__ */ makeMap( + // the leading comma is intentional so empty string "" is also included + ",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted" + ); + const isBuiltInDirective = /* @__PURE__ */ makeMap( + "bind,cloak,else-if,else,for,html,if,model,on,once,pre,show,slot,text,memo" + ); + const cacheStringFunction = (fn) => { + const cache = /* @__PURE__ */ Object.create(null); + return (str) => { + const hit = cache[str]; + return hit || (cache[str] = fn(str)); + }; + }; + const camelizeRE = /-(\w)/g; + const camelize = cacheStringFunction( + (str) => { + return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : ""); + } + ); + const hyphenateRE = /\B([A-Z])/g; + const hyphenate = cacheStringFunction( + (str) => str.replace(hyphenateRE, "-$1").toLowerCase() + ); + const capitalize = cacheStringFunction((str) => { + return str.charAt(0).toUpperCase() + str.slice(1); + }); + const toHandlerKey = cacheStringFunction( + (str) => { + const s = str ? `on${capitalize(str)}` : ``; + return s; + } + ); + const hasChanged = (value, oldValue) => !Object.is(value, oldValue); + const invokeArrayFns = (fns, ...arg) => { + for (let i = 0; i < fns.length; i++) { + fns[i](...arg); + } + }; + const def = (obj, key, value, writable = false) => { + Object.defineProperty(obj, key, { + configurable: true, + enumerable: false, + writable, + value + }); + }; + const looseToNumber = (val) => { + const n = parseFloat(val); + return isNaN(n) ? val : n; + }; + const toNumber = (val) => { + const n = isString(val) ? Number(val) : NaN; + return isNaN(n) ? val : n; + }; + let _globalThis; + const getGlobalThis = () => { + return _globalThis || (_globalThis = typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : {}); + }; + function genCacheKey(source, options) { + return source + JSON.stringify( + options, + (_, val) => typeof val === "function" ? val.toString() : val + ); + } + + const PatchFlagNames = { + [1]: `TEXT`, + [2]: `CLASS`, + [4]: `STYLE`, + [8]: `PROPS`, + [16]: `FULL_PROPS`, + [32]: `NEED_HYDRATION`, + [64]: `STABLE_FRAGMENT`, + [128]: `KEYED_FRAGMENT`, + [256]: `UNKEYED_FRAGMENT`, + [512]: `NEED_PATCH`, + [1024]: `DYNAMIC_SLOTS`, + [2048]: `DEV_ROOT_FRAGMENT`, + [-1]: `HOISTED`, + [-2]: `BAIL` + }; + + const slotFlagsText = { + [1]: "STABLE", + [2]: "DYNAMIC", + [3]: "FORWARDED" + }; + + const GLOBALS_ALLOWED = "Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt,console,Error,Symbol"; + const isGloballyAllowed = /* @__PURE__ */ makeMap(GLOBALS_ALLOWED); + + const range = 2; + function generateCodeFrame(source, start = 0, end = source.length) { + start = Math.max(0, Math.min(start, source.length)); + end = Math.max(0, Math.min(end, source.length)); + if (start > end) return ""; + let lines = source.split(/(\r?\n)/); + const newlineSequences = lines.filter((_, idx) => idx % 2 === 1); + lines = lines.filter((_, idx) => idx % 2 === 0); + let count = 0; + const res = []; + for (let i = 0; i < lines.length; i++) { + count += lines[i].length + (newlineSequences[i] && newlineSequences[i].length || 0); + if (count >= start) { + for (let j = i - range; j <= i + range || end > count; j++) { + if (j < 0 || j >= lines.length) continue; + const line = j + 1; + res.push( + `${line}${" ".repeat(Math.max(3 - String(line).length, 0))}| ${lines[j]}` + ); + const lineLength = lines[j].length; + const newLineSeqLength = newlineSequences[j] && newlineSequences[j].length || 0; + if (j === i) { + const pad = start - (count - (lineLength + newLineSeqLength)); + const length = Math.max( + 1, + end > count ? lineLength - pad : end - start + ); + res.push(` | ` + " ".repeat(pad) + "^".repeat(length)); + } else if (j > i) { + if (end > count) { + const length = Math.max(Math.min(end - count, lineLength), 1); + res.push(` | ` + "^".repeat(length)); + } + count += lineLength + newLineSeqLength; + } + } + break; + } + } + return res.join("\n"); + } + + function normalizeStyle(value) { + if (isArray(value)) { + const res = {}; + for (let i = 0; i < value.length; i++) { + const item = value[i]; + const normalized = isString(item) ? parseStringStyle(item) : normalizeStyle(item); + if (normalized) { + for (const key in normalized) { + res[key] = normalized[key]; + } + } + } + return res; + } else if (isString(value) || isObject(value)) { + return value; + } + } + const listDelimiterRE = /;(?![^(]*\))/g; + const propertyDelimiterRE = /:([^]+)/; + const styleCommentRE = /\/\*[^]*?\*\//g; + function parseStringStyle(cssText) { + const ret = {}; + cssText.replace(styleCommentRE, "").split(listDelimiterRE).forEach((item) => { + if (item) { + const tmp = item.split(propertyDelimiterRE); + tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim()); + } + }); + return ret; + } + function stringifyStyle(styles) { + if (!styles) return ""; + if (isString(styles)) return styles; + let ret = ""; + for (const key in styles) { + const value = styles[key]; + if (isString(value) || typeof value === "number") { + const normalizedKey = key.startsWith(`--`) ? key : hyphenate(key); + ret += `${normalizedKey}:${value};`; + } + } + return ret; + } + function normalizeClass(value) { + let res = ""; + if (isString(value)) { + res = value; + } else if (isArray(value)) { + for (let i = 0; i < value.length; i++) { + const normalized = normalizeClass(value[i]); + if (normalized) { + res += normalized + " "; + } + } + } else if (isObject(value)) { + for (const name in value) { + if (value[name]) { + res += name + " "; + } + } + } + return res.trim(); + } + function normalizeProps(props) { + if (!props) return null; + let { class: klass, style } = props; + if (klass && !isString(klass)) { + props.class = normalizeClass(klass); + } + if (style) { + props.style = normalizeStyle(style); + } + return props; + } + + const HTML_TAGS = "html,body,base,head,link,meta,style,title,address,article,aside,footer,header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,output,progress,select,textarea,details,dialog,menu,summary,template,blockquote,iframe,tfoot"; + const SVG_TAGS = "svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,text,textPath,title,tspan,unknown,use,view"; + const MATH_TAGS = "annotation,annotation-xml,maction,maligngroup,malignmark,math,menclose,merror,mfenced,mfrac,mfraction,mglyph,mi,mlabeledtr,mlongdiv,mmultiscripts,mn,mo,mover,mpadded,mphantom,mprescripts,mroot,mrow,ms,mscarries,mscarry,msgroup,msline,mspace,msqrt,msrow,mstack,mstyle,msub,msubsup,msup,mtable,mtd,mtext,mtr,munder,munderover,none,semantics"; + const VOID_TAGS = "area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr"; + const isHTMLTag = /* @__PURE__ */ makeMap(HTML_TAGS); + const isSVGTag = /* @__PURE__ */ makeMap(SVG_TAGS); + const isMathMLTag = /* @__PURE__ */ makeMap(MATH_TAGS); + const isVoidTag = /* @__PURE__ */ makeMap(VOID_TAGS); + + const specialBooleanAttrs = `itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly`; + const isSpecialBooleanAttr = /* @__PURE__ */ makeMap(specialBooleanAttrs); + const isBooleanAttr = /* @__PURE__ */ makeMap( + specialBooleanAttrs + `,async,autofocus,autoplay,controls,default,defer,disabled,hidden,inert,loop,open,required,reversed,scoped,seamless,checked,muted,multiple,selected` + ); + function includeBooleanAttr(value) { + return !!value || value === ""; + } + const isKnownHtmlAttr = /* @__PURE__ */ makeMap( + `accept,accept-charset,accesskey,action,align,allow,alt,async,autocapitalize,autocomplete,autofocus,autoplay,background,bgcolor,border,buffered,capture,challenge,charset,checked,cite,class,code,codebase,color,cols,colspan,content,contenteditable,contextmenu,controls,coords,crossorigin,csp,data,datetime,decoding,default,defer,dir,dirname,disabled,download,draggable,dropzone,enctype,enterkeyhint,for,form,formaction,formenctype,formmethod,formnovalidate,formtarget,headers,height,hidden,high,href,hreflang,http-equiv,icon,id,importance,inert,integrity,ismap,itemprop,keytype,kind,label,lang,language,loading,list,loop,low,manifest,max,maxlength,minlength,media,min,multiple,muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,preload,radiogroup,readonly,referrerpolicy,rel,required,reversed,rows,rowspan,sandbox,scope,scoped,selected,shape,size,sizes,slot,span,spellcheck,src,srcdoc,srclang,srcset,start,step,style,summary,tabindex,target,title,translate,type,usemap,value,width,wrap` + ); + const isKnownSvgAttr = /* @__PURE__ */ makeMap( + `xmlns,accent-height,accumulate,additive,alignment-baseline,alphabetic,amplitude,arabic-form,ascent,attributeName,attributeType,azimuth,baseFrequency,baseline-shift,baseProfile,bbox,begin,bias,by,calcMode,cap-height,class,clip,clipPathUnits,clip-path,clip-rule,color,color-interpolation,color-interpolation-filters,color-profile,color-rendering,contentScriptType,contentStyleType,crossorigin,cursor,cx,cy,d,decelerate,descent,diffuseConstant,direction,display,divisor,dominant-baseline,dur,dx,dy,edgeMode,elevation,enable-background,end,exponent,fill,fill-opacity,fill-rule,filter,filterRes,filterUnits,flood-color,flood-opacity,font-family,font-size,font-size-adjust,font-stretch,font-style,font-variant,font-weight,format,from,fr,fx,fy,g1,g2,glyph-name,glyph-orientation-horizontal,glyph-orientation-vertical,glyphRef,gradientTransform,gradientUnits,hanging,height,href,hreflang,horiz-adv-x,horiz-origin-x,id,ideographic,image-rendering,in,in2,intercept,k,k1,k2,k3,k4,kernelMatrix,kernelUnitLength,kerning,keyPoints,keySplines,keyTimes,lang,lengthAdjust,letter-spacing,lighting-color,limitingConeAngle,local,marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mask,maskContentUnits,maskUnits,mathematical,max,media,method,min,mode,name,numOctaves,offset,opacity,operator,order,orient,orientation,origin,overflow,overline-position,overline-thickness,panose-1,paint-order,path,pathLength,patternContentUnits,patternTransform,patternUnits,ping,pointer-events,points,pointsAtX,pointsAtY,pointsAtZ,preserveAlpha,preserveAspectRatio,primitiveUnits,r,radius,referrerPolicy,refX,refY,rel,rendering-intent,repeatCount,repeatDur,requiredExtensions,requiredFeatures,restart,result,rotate,rx,ry,scale,seed,shape-rendering,slope,spacing,specularConstant,specularExponent,speed,spreadMethod,startOffset,stdDeviation,stemh,stemv,stitchTiles,stop-color,stop-opacity,strikethrough-position,strikethrough-thickness,string,stroke,stroke-dasharray,stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,stroke-width,style,surfaceScale,systemLanguage,tabindex,tableValues,target,targetX,targetY,text-anchor,text-decoration,text-rendering,textLength,to,transform,transform-origin,type,u1,u2,underline-position,underline-thickness,unicode,unicode-bidi,unicode-range,units-per-em,v-alphabetic,v-hanging,v-ideographic,v-mathematical,values,vector-effect,version,vert-adv-y,vert-origin-x,vert-origin-y,viewBox,viewTarget,visibility,width,widths,word-spacing,writing-mode,x,x-height,x1,x2,xChannelSelector,xlink:actuate,xlink:arcrole,xlink:href,xlink:role,xlink:show,xlink:title,xlink:type,xmlns:xlink,xml:base,xml:lang,xml:space,y,y1,y2,yChannelSelector,z,zoomAndPan` + ); + function isRenderableAttrValue(value) { + if (value == null) { + return false; + } + const type = typeof value; + return type === "string" || type === "number" || type === "boolean"; + } + + const cssVarNameEscapeSymbolsRE = /[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g; + function getEscapedCssVarName(key, doubleEscape) { + return key.replace( + cssVarNameEscapeSymbolsRE, + (s) => `\\${s}` + ); + } + + function looseCompareArrays(a, b) { + if (a.length !== b.length) return false; + let equal = true; + for (let i = 0; equal && i < a.length; i++) { + equal = looseEqual(a[i], b[i]); + } + return equal; + } + function looseEqual(a, b) { + if (a === b) return true; + let aValidType = isDate(a); + let bValidType = isDate(b); + if (aValidType || bValidType) { + return aValidType && bValidType ? a.getTime() === b.getTime() : false; + } + aValidType = isSymbol(a); + bValidType = isSymbol(b); + if (aValidType || bValidType) { + return a === b; + } + aValidType = isArray(a); + bValidType = isArray(b); + if (aValidType || bValidType) { + return aValidType && bValidType ? looseCompareArrays(a, b) : false; + } + aValidType = isObject(a); + bValidType = isObject(b); + if (aValidType || bValidType) { + if (!aValidType || !bValidType) { + return false; + } + const aKeysCount = Object.keys(a).length; + const bKeysCount = Object.keys(b).length; + if (aKeysCount !== bKeysCount) { + return false; + } + for (const key in a) { + const aHasKey = a.hasOwnProperty(key); + const bHasKey = b.hasOwnProperty(key); + if (aHasKey && !bHasKey || !aHasKey && bHasKey || !looseEqual(a[key], b[key])) { + return false; + } + } + } + return String(a) === String(b); + } + function looseIndexOf(arr, val) { + return arr.findIndex((item) => looseEqual(item, val)); + } + + const isRef$1 = (val) => { + return !!(val && val["__v_isRef"] === true); + }; + const toDisplayString = (val) => { + return isString(val) ? val : val == null ? "" : isArray(val) || isObject(val) && (val.toString === objectToString || !isFunction(val.toString)) ? isRef$1(val) ? toDisplayString(val.value) : JSON.stringify(val, replacer, 2) : String(val); + }; + const replacer = (_key, val) => { + if (isRef$1(val)) { + return replacer(_key, val.value); + } else if (isMap(val)) { + return { + [`Map(${val.size})`]: [...val.entries()].reduce( + (entries, [key, val2], i) => { + entries[stringifySymbol(key, i) + " =>"] = val2; + return entries; + }, + {} + ) + }; + } else if (isSet(val)) { + return { + [`Set(${val.size})`]: [...val.values()].map((v) => stringifySymbol(v)) + }; + } else if (isSymbol(val)) { + return stringifySymbol(val); + } else if (isObject(val) && !isArray(val) && !isPlainObject(val)) { + return String(val); + } + return val; + }; + const stringifySymbol = (v, i = "") => { + var _a; + return ( + // Symbol.description in es2019+ so we need to cast here to pass + // the lib: es2016 check + isSymbol(v) ? `Symbol(${(_a = v.description) != null ? _a : i})` : v + ); + }; + + function warn$2(msg, ...args) { + console.warn(`[Vue warn] ${msg}`, ...args); + } + + let activeEffectScope; + class EffectScope { + constructor(detached = false) { + this.detached = detached; + /** + * @internal + */ + this._active = true; + /** + * @internal + */ + this.effects = []; + /** + * @internal + */ + this.cleanups = []; + this._isPaused = false; + this.parent = activeEffectScope; + if (!detached && activeEffectScope) { + this.index = (activeEffectScope.scopes || (activeEffectScope.scopes = [])).push( + this + ) - 1; + } + } + get active() { + return this._active; + } + pause() { + if (this._active) { + this._isPaused = true; + let i, l; + if (this.scopes) { + for (i = 0, l = this.scopes.length; i < l; i++) { + this.scopes[i].pause(); + } + } + for (i = 0, l = this.effects.length; i < l; i++) { + this.effects[i].pause(); + } + } + } + /** + * Resumes the effect scope, including all child scopes and effects. + */ + resume() { + if (this._active) { + if (this._isPaused) { + this._isPaused = false; + let i, l; + if (this.scopes) { + for (i = 0, l = this.scopes.length; i < l; i++) { + this.scopes[i].resume(); + } + } + for (i = 0, l = this.effects.length; i < l; i++) { + this.effects[i].resume(); + } + } + } + } + run(fn) { + if (this._active) { + const currentEffectScope = activeEffectScope; + try { + activeEffectScope = this; + return fn(); + } finally { + activeEffectScope = currentEffectScope; + } + } else { + warn$2(`cannot run an inactive effect scope.`); + } + } + /** + * This should only be called on non-detached scopes + * @internal + */ + on() { + activeEffectScope = this; + } + /** + * This should only be called on non-detached scopes + * @internal + */ + off() { + activeEffectScope = this.parent; + } + stop(fromParent) { + if (this._active) { + this._active = false; + let i, l; + for (i = 0, l = this.effects.length; i < l; i++) { + this.effects[i].stop(); + } + this.effects.length = 0; + for (i = 0, l = this.cleanups.length; i < l; i++) { + this.cleanups[i](); + } + this.cleanups.length = 0; + if (this.scopes) { + for (i = 0, l = this.scopes.length; i < l; i++) { + this.scopes[i].stop(true); + } + this.scopes.length = 0; + } + if (!this.detached && this.parent && !fromParent) { + const last = this.parent.scopes.pop(); + if (last && last !== this) { + this.parent.scopes[this.index] = last; + last.index = this.index; + } + } + this.parent = void 0; + } + } + } + function effectScope(detached) { + return new EffectScope(detached); + } + function getCurrentScope() { + return activeEffectScope; + } + function onScopeDispose(fn, failSilently = false) { + if (activeEffectScope) { + activeEffectScope.cleanups.push(fn); + } else if (!failSilently) { + warn$2( + `onScopeDispose() is called when there is no active effect scope to be associated with.` + ); + } + } + + let activeSub; + const pausedQueueEffects = /* @__PURE__ */ new WeakSet(); + class ReactiveEffect { + constructor(fn) { + this.fn = fn; + /** + * @internal + */ + this.deps = void 0; + /** + * @internal + */ + this.depsTail = void 0; + /** + * @internal + */ + this.flags = 1 | 4; + /** + * @internal + */ + this.next = void 0; + /** + * @internal + */ + this.cleanup = void 0; + this.scheduler = void 0; + if (activeEffectScope && activeEffectScope.active) { + activeEffectScope.effects.push(this); + } + } + pause() { + this.flags |= 64; + } + resume() { + if (this.flags & 64) { + this.flags &= ~64; + if (pausedQueueEffects.has(this)) { + pausedQueueEffects.delete(this); + this.trigger(); + } + } + } + /** + * @internal + */ + notify() { + if (this.flags & 2 && !(this.flags & 32)) { + return; + } + if (!(this.flags & 8)) { + batch(this); + } + } + run() { + if (!(this.flags & 1)) { + return this.fn(); + } + this.flags |= 2; + cleanupEffect(this); + prepareDeps(this); + const prevEffect = activeSub; + const prevShouldTrack = shouldTrack; + activeSub = this; + shouldTrack = true; + try { + return this.fn(); + } finally { + if (activeSub !== this) { + warn$2( + "Active effect was not restored correctly - this is likely a Vue internal bug." + ); + } + cleanupDeps(this); + activeSub = prevEffect; + shouldTrack = prevShouldTrack; + this.flags &= ~2; + } + } + stop() { + if (this.flags & 1) { + for (let link = this.deps; link; link = link.nextDep) { + removeSub(link); + } + this.deps = this.depsTail = void 0; + cleanupEffect(this); + this.onStop && this.onStop(); + this.flags &= ~1; + } + } + trigger() { + if (this.flags & 64) { + pausedQueueEffects.add(this); + } else if (this.scheduler) { + this.scheduler(); + } else { + this.runIfDirty(); + } + } + /** + * @internal + */ + runIfDirty() { + if (isDirty(this)) { + this.run(); + } + } + get dirty() { + return isDirty(this); + } + } + let batchDepth = 0; + let batchedSub; + let batchedComputed; + function batch(sub, isComputed = false) { + sub.flags |= 8; + if (isComputed) { + sub.next = batchedComputed; + batchedComputed = sub; + return; + } + sub.next = batchedSub; + batchedSub = sub; + } + function startBatch() { + batchDepth++; + } + function endBatch() { + if (--batchDepth > 0) { + return; + } + if (batchedComputed) { + let e = batchedComputed; + batchedComputed = void 0; + while (e) { + const next = e.next; + e.next = void 0; + e.flags &= ~8; + e = next; + } + } + let error; + while (batchedSub) { + let e = batchedSub; + batchedSub = void 0; + while (e) { + const next = e.next; + e.next = void 0; + e.flags &= ~8; + if (e.flags & 1) { + try { + ; + e.trigger(); + } catch (err) { + if (!error) error = err; + } + } + e = next; + } + } + if (error) throw error; + } + function prepareDeps(sub) { + for (let link = sub.deps; link; link = link.nextDep) { + link.version = -1; + link.prevActiveLink = link.dep.activeLink; + link.dep.activeLink = link; + } + } + function cleanupDeps(sub) { + let head; + let tail = sub.depsTail; + let link = tail; + while (link) { + const prev = link.prevDep; + if (link.version === -1) { + if (link === tail) tail = prev; + removeSub(link); + removeDep(link); + } else { + head = link; + } + link.dep.activeLink = link.prevActiveLink; + link.prevActiveLink = void 0; + link = prev; + } + sub.deps = head; + sub.depsTail = tail; + } + function isDirty(sub) { + for (let link = sub.deps; link; link = link.nextDep) { + if (link.dep.version !== link.version || link.dep.computed && (refreshComputed(link.dep.computed) || link.dep.version !== link.version)) { + return true; + } + } + if (sub._dirty) { + return true; + } + return false; + } + function refreshComputed(computed) { + if (computed.flags & 4 && !(computed.flags & 16)) { + return; + } + computed.flags &= ~16; + if (computed.globalVersion === globalVersion) { + return; + } + computed.globalVersion = globalVersion; + const dep = computed.dep; + computed.flags |= 2; + if (dep.version > 0 && !computed.isSSR && computed.deps && !isDirty(computed)) { + computed.flags &= ~2; + return; + } + const prevSub = activeSub; + const prevShouldTrack = shouldTrack; + activeSub = computed; + shouldTrack = true; + try { + prepareDeps(computed); + const value = computed.fn(computed._value); + if (dep.version === 0 || hasChanged(value, computed._value)) { + computed._value = value; + dep.version++; + } + } catch (err) { + dep.version++; + throw err; + } finally { + activeSub = prevSub; + shouldTrack = prevShouldTrack; + cleanupDeps(computed); + computed.flags &= ~2; + } + } + function removeSub(link, soft = false) { + const { dep, prevSub, nextSub } = link; + if (prevSub) { + prevSub.nextSub = nextSub; + link.prevSub = void 0; + } + if (nextSub) { + nextSub.prevSub = prevSub; + link.nextSub = void 0; + } + if (dep.subsHead === link) { + dep.subsHead = nextSub; + } + if (dep.subs === link) { + dep.subs = prevSub; + if (!prevSub && dep.computed) { + dep.computed.flags &= ~4; + for (let l = dep.computed.deps; l; l = l.nextDep) { + removeSub(l, true); + } + } + } + if (!soft && !--dep.sc && dep.map) { + dep.map.delete(dep.key); + } + } + function removeDep(link) { + const { prevDep, nextDep } = link; + if (prevDep) { + prevDep.nextDep = nextDep; + link.prevDep = void 0; + } + if (nextDep) { + nextDep.prevDep = prevDep; + link.nextDep = void 0; + } + } + function effect(fn, options) { + if (fn.effect instanceof ReactiveEffect) { + fn = fn.effect.fn; + } + const e = new ReactiveEffect(fn); + if (options) { + extend(e, options); + } + try { + e.run(); + } catch (err) { + e.stop(); + throw err; + } + const runner = e.run.bind(e); + runner.effect = e; + return runner; + } + function stop(runner) { + runner.effect.stop(); + } + let shouldTrack = true; + const trackStack = []; + function pauseTracking() { + trackStack.push(shouldTrack); + shouldTrack = false; + } + function resetTracking() { + const last = trackStack.pop(); + shouldTrack = last === void 0 ? true : last; + } + function cleanupEffect(e) { + const { cleanup } = e; + e.cleanup = void 0; + if (cleanup) { + const prevSub = activeSub; + activeSub = void 0; + try { + cleanup(); + } finally { + activeSub = prevSub; + } + } + } + + let globalVersion = 0; + class Link { + constructor(sub, dep) { + this.sub = sub; + this.dep = dep; + this.version = dep.version; + this.nextDep = this.prevDep = this.nextSub = this.prevSub = this.prevActiveLink = void 0; + } + } + class Dep { + constructor(computed) { + this.computed = computed; + this.version = 0; + /** + * Link between this dep and the current active effect + */ + this.activeLink = void 0; + /** + * Doubly linked list representing the subscribing effects (tail) + */ + this.subs = void 0; + /** + * For object property deps cleanup + */ + this.map = void 0; + this.key = void 0; + /** + * Subscriber counter + */ + this.sc = 0; + { + this.subsHead = void 0; + } + } + track(debugInfo) { + if (!activeSub || !shouldTrack || activeSub === this.computed) { + return; + } + let link = this.activeLink; + if (link === void 0 || link.sub !== activeSub) { + link = this.activeLink = new Link(activeSub, this); + if (!activeSub.deps) { + activeSub.deps = activeSub.depsTail = link; + } else { + link.prevDep = activeSub.depsTail; + activeSub.depsTail.nextDep = link; + activeSub.depsTail = link; + } + addSub(link); + } else if (link.version === -1) { + link.version = this.version; + if (link.nextDep) { + const next = link.nextDep; + next.prevDep = link.prevDep; + if (link.prevDep) { + link.prevDep.nextDep = next; + } + link.prevDep = activeSub.depsTail; + link.nextDep = void 0; + activeSub.depsTail.nextDep = link; + activeSub.depsTail = link; + if (activeSub.deps === link) { + activeSub.deps = next; + } + } + } + if (activeSub.onTrack) { + activeSub.onTrack( + extend( + { + effect: activeSub + }, + debugInfo + ) + ); + } + return link; + } + trigger(debugInfo) { + this.version++; + globalVersion++; + this.notify(debugInfo); + } + notify(debugInfo) { + startBatch(); + try { + if (true) { + for (let head = this.subsHead; head; head = head.nextSub) { + if (head.sub.onTrigger && !(head.sub.flags & 8)) { + head.sub.onTrigger( + extend( + { + effect: head.sub + }, + debugInfo + ) + ); + } + } + } + for (let link = this.subs; link; link = link.prevSub) { + if (link.sub.notify()) { + ; + link.sub.dep.notify(); + } + } + } finally { + endBatch(); + } + } + } + function addSub(link) { + link.dep.sc++; + if (link.sub.flags & 4) { + const computed = link.dep.computed; + if (computed && !link.dep.subs) { + computed.flags |= 4 | 16; + for (let l = computed.deps; l; l = l.nextDep) { + addSub(l); + } + } + const currentTail = link.dep.subs; + if (currentTail !== link) { + link.prevSub = currentTail; + if (currentTail) currentTail.nextSub = link; + } + if (link.dep.subsHead === void 0) { + link.dep.subsHead = link; + } + link.dep.subs = link; + } + } + const targetMap = /* @__PURE__ */ new WeakMap(); + const ITERATE_KEY = Symbol( + "Object iterate" + ); + const MAP_KEY_ITERATE_KEY = Symbol( + "Map keys iterate" + ); + const ARRAY_ITERATE_KEY = Symbol( + "Array iterate" + ); + function track(target, type, key) { + if (shouldTrack && activeSub) { + let depsMap = targetMap.get(target); + if (!depsMap) { + targetMap.set(target, depsMap = /* @__PURE__ */ new Map()); + } + let dep = depsMap.get(key); + if (!dep) { + depsMap.set(key, dep = new Dep()); + dep.map = depsMap; + dep.key = key; + } + { + dep.track({ + target, + type, + key + }); + } + } + } + function trigger(target, type, key, newValue, oldValue, oldTarget) { + const depsMap = targetMap.get(target); + if (!depsMap) { + globalVersion++; + return; + } + const run = (dep) => { + if (dep) { + { + dep.trigger({ + target, + type, + key, + newValue, + oldValue, + oldTarget + }); + } + } + }; + startBatch(); + if (type === "clear") { + depsMap.forEach(run); + } else { + const targetIsArray = isArray(target); + const isArrayIndex = targetIsArray && isIntegerKey(key); + if (targetIsArray && key === "length") { + const newLength = Number(newValue); + depsMap.forEach((dep, key2) => { + if (key2 === "length" || key2 === ARRAY_ITERATE_KEY || !isSymbol(key2) && key2 >= newLength) { + run(dep); + } + }); + } else { + if (key !== void 0 || depsMap.has(void 0)) { + run(depsMap.get(key)); + } + if (isArrayIndex) { + run(depsMap.get(ARRAY_ITERATE_KEY)); + } + switch (type) { + case "add": + if (!targetIsArray) { + run(depsMap.get(ITERATE_KEY)); + if (isMap(target)) { + run(depsMap.get(MAP_KEY_ITERATE_KEY)); + } + } else if (isArrayIndex) { + run(depsMap.get("length")); + } + break; + case "delete": + if (!targetIsArray) { + run(depsMap.get(ITERATE_KEY)); + if (isMap(target)) { + run(depsMap.get(MAP_KEY_ITERATE_KEY)); + } + } + break; + case "set": + if (isMap(target)) { + run(depsMap.get(ITERATE_KEY)); + } + break; + } + } + } + endBatch(); + } + function getDepFromReactive(object, key) { + const depMap = targetMap.get(object); + return depMap && depMap.get(key); + } + + function reactiveReadArray(array) { + const raw = toRaw(array); + if (raw === array) return raw; + track(raw, "iterate", ARRAY_ITERATE_KEY); + return isShallow(array) ? raw : raw.map(toReactive); + } + function shallowReadArray(arr) { + track(arr = toRaw(arr), "iterate", ARRAY_ITERATE_KEY); + return arr; + } + const arrayInstrumentations = { + __proto__: null, + [Symbol.iterator]() { + return iterator(this, Symbol.iterator, toReactive); + }, + concat(...args) { + return reactiveReadArray(this).concat( + ...args.map((x) => isArray(x) ? reactiveReadArray(x) : x) + ); + }, + entries() { + return iterator(this, "entries", (value) => { + value[1] = toReactive(value[1]); + return value; + }); + }, + every(fn, thisArg) { + return apply(this, "every", fn, thisArg, void 0, arguments); + }, + filter(fn, thisArg) { + return apply(this, "filter", fn, thisArg, (v) => v.map(toReactive), arguments); + }, + find(fn, thisArg) { + return apply(this, "find", fn, thisArg, toReactive, arguments); + }, + findIndex(fn, thisArg) { + return apply(this, "findIndex", fn, thisArg, void 0, arguments); + }, + findLast(fn, thisArg) { + return apply(this, "findLast", fn, thisArg, toReactive, arguments); + }, + findLastIndex(fn, thisArg) { + return apply(this, "findLastIndex", fn, thisArg, void 0, arguments); + }, + // flat, flatMap could benefit from ARRAY_ITERATE but are not straight-forward to implement + forEach(fn, thisArg) { + return apply(this, "forEach", fn, thisArg, void 0, arguments); + }, + includes(...args) { + return searchProxy(this, "includes", args); + }, + indexOf(...args) { + return searchProxy(this, "indexOf", args); + }, + join(separator) { + return reactiveReadArray(this).join(separator); + }, + // keys() iterator only reads `length`, no optimisation required + lastIndexOf(...args) { + return searchProxy(this, "lastIndexOf", args); + }, + map(fn, thisArg) { + return apply(this, "map", fn, thisArg, void 0, arguments); + }, + pop() { + return noTracking(this, "pop"); + }, + push(...args) { + return noTracking(this, "push", args); + }, + reduce(fn, ...args) { + return reduce(this, "reduce", fn, args); + }, + reduceRight(fn, ...args) { + return reduce(this, "reduceRight", fn, args); + }, + shift() { + return noTracking(this, "shift"); + }, + // slice could use ARRAY_ITERATE but also seems to beg for range tracking + some(fn, thisArg) { + return apply(this, "some", fn, thisArg, void 0, arguments); + }, + splice(...args) { + return noTracking(this, "splice", args); + }, + toReversed() { + return reactiveReadArray(this).toReversed(); + }, + toSorted(comparer) { + return reactiveReadArray(this).toSorted(comparer); + }, + toSpliced(...args) { + return reactiveReadArray(this).toSpliced(...args); + }, + unshift(...args) { + return noTracking(this, "unshift", args); + }, + values() { + return iterator(this, "values", toReactive); + } + }; + function iterator(self, method, wrapValue) { + const arr = shallowReadArray(self); + const iter = arr[method](); + if (arr !== self && !isShallow(self)) { + iter._next = iter.next; + iter.next = () => { + const result = iter._next(); + if (result.value) { + result.value = wrapValue(result.value); + } + return result; + }; + } + return iter; + } + const arrayProto = Array.prototype; + function apply(self, method, fn, thisArg, wrappedRetFn, args) { + const arr = shallowReadArray(self); + const needsWrap = arr !== self && !isShallow(self); + const methodFn = arr[method]; + if (methodFn !== arrayProto[method]) { + const result2 = methodFn.apply(self, args); + return needsWrap ? toReactive(result2) : result2; + } + let wrappedFn = fn; + if (arr !== self) { + if (needsWrap) { + wrappedFn = function(item, index) { + return fn.call(this, toReactive(item), index, self); + }; + } else if (fn.length > 2) { + wrappedFn = function(item, index) { + return fn.call(this, item, index, self); + }; + } + } + const result = methodFn.call(arr, wrappedFn, thisArg); + return needsWrap && wrappedRetFn ? wrappedRetFn(result) : result; + } + function reduce(self, method, fn, args) { + const arr = shallowReadArray(self); + let wrappedFn = fn; + if (arr !== self) { + if (!isShallow(self)) { + wrappedFn = function(acc, item, index) { + return fn.call(this, acc, toReactive(item), index, self); + }; + } else if (fn.length > 3) { + wrappedFn = function(acc, item, index) { + return fn.call(this, acc, item, index, self); + }; + } + } + return arr[method](wrappedFn, ...args); + } + function searchProxy(self, method, args) { + const arr = toRaw(self); + track(arr, "iterate", ARRAY_ITERATE_KEY); + const res = arr[method](...args); + if ((res === -1 || res === false) && isProxy(args[0])) { + args[0] = toRaw(args[0]); + return arr[method](...args); + } + return res; + } + function noTracking(self, method, args = []) { + pauseTracking(); + startBatch(); + const res = toRaw(self)[method].apply(self, args); + endBatch(); + resetTracking(); + return res; + } + + const isNonTrackableKeys = /* @__PURE__ */ makeMap(`__proto__,__v_isRef,__isVue`); + const builtInSymbols = new Set( + /* @__PURE__ */ Object.getOwnPropertyNames(Symbol).filter((key) => key !== "arguments" && key !== "caller").map((key) => Symbol[key]).filter(isSymbol) + ); + function hasOwnProperty(key) { + if (!isSymbol(key)) key = String(key); + const obj = toRaw(this); + track(obj, "has", key); + return obj.hasOwnProperty(key); + } + class BaseReactiveHandler { + constructor(_isReadonly = false, _isShallow = false) { + this._isReadonly = _isReadonly; + this._isShallow = _isShallow; + } + get(target, key, receiver) { + if (key === "__v_skip") return target["__v_skip"]; + const isReadonly2 = this._isReadonly, isShallow2 = this._isShallow; + if (key === "__v_isReactive") { + return !isReadonly2; + } else if (key === "__v_isReadonly") { + return isReadonly2; + } else if (key === "__v_isShallow") { + return isShallow2; + } else if (key === "__v_raw") { + if (receiver === (isReadonly2 ? isShallow2 ? shallowReadonlyMap : readonlyMap : isShallow2 ? shallowReactiveMap : reactiveMap).get(target) || // receiver is not the reactive proxy, but has the same prototype + // this means the receiver is a user proxy of the reactive proxy + Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)) { + return target; + } + return; + } + const targetIsArray = isArray(target); + if (!isReadonly2) { + let fn; + if (targetIsArray && (fn = arrayInstrumentations[key])) { + return fn; + } + if (key === "hasOwnProperty") { + return hasOwnProperty; + } + } + const res = Reflect.get( + target, + key, + // if this is a proxy wrapping a ref, return methods using the raw ref + // as receiver so that we don't have to call `toRaw` on the ref in all + // its class methods + isRef(target) ? target : receiver + ); + if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) { + return res; + } + if (!isReadonly2) { + track(target, "get", key); + } + if (isShallow2) { + return res; + } + if (isRef(res)) { + return targetIsArray && isIntegerKey(key) ? res : res.value; + } + if (isObject(res)) { + return isReadonly2 ? readonly(res) : reactive(res); + } + return res; + } + } + class MutableReactiveHandler extends BaseReactiveHandler { + constructor(isShallow2 = false) { + super(false, isShallow2); + } + set(target, key, value, receiver) { + let oldValue = target[key]; + if (!this._isShallow) { + const isOldValueReadonly = isReadonly(oldValue); + if (!isShallow(value) && !isReadonly(value)) { + oldValue = toRaw(oldValue); + value = toRaw(value); + } + if (!isArray(target) && isRef(oldValue) && !isRef(value)) { + if (isOldValueReadonly) { + return false; + } else { + oldValue.value = value; + return true; + } + } + } + const hadKey = isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target, key); + const result = Reflect.set( + target, + key, + value, + isRef(target) ? target : receiver + ); + if (target === toRaw(receiver)) { + if (!hadKey) { + trigger(target, "add", key, value); + } else if (hasChanged(value, oldValue)) { + trigger(target, "set", key, value, oldValue); + } + } + return result; + } + deleteProperty(target, key) { + const hadKey = hasOwn(target, key); + const oldValue = target[key]; + const result = Reflect.deleteProperty(target, key); + if (result && hadKey) { + trigger(target, "delete", key, void 0, oldValue); + } + return result; + } + has(target, key) { + const result = Reflect.has(target, key); + if (!isSymbol(key) || !builtInSymbols.has(key)) { + track(target, "has", key); + } + return result; + } + ownKeys(target) { + track( + target, + "iterate", + isArray(target) ? "length" : ITERATE_KEY + ); + return Reflect.ownKeys(target); + } + } + class ReadonlyReactiveHandler extends BaseReactiveHandler { + constructor(isShallow2 = false) { + super(true, isShallow2); + } + set(target, key) { + { + warn$2( + `Set operation on key "${String(key)}" failed: target is readonly.`, + target + ); + } + return true; + } + deleteProperty(target, key) { + { + warn$2( + `Delete operation on key "${String(key)}" failed: target is readonly.`, + target + ); + } + return true; + } + } + const mutableHandlers = /* @__PURE__ */ new MutableReactiveHandler(); + const readonlyHandlers = /* @__PURE__ */ new ReadonlyReactiveHandler(); + const shallowReactiveHandlers = /* @__PURE__ */ new MutableReactiveHandler(true); + const shallowReadonlyHandlers = /* @__PURE__ */ new ReadonlyReactiveHandler(true); + + const toShallow = (value) => value; + const getProto = (v) => Reflect.getPrototypeOf(v); + function createIterableMethod(method, isReadonly2, isShallow2) { + return function(...args) { + const target = this["__v_raw"]; + const rawTarget = toRaw(target); + const targetIsMap = isMap(rawTarget); + const isPair = method === "entries" || method === Symbol.iterator && targetIsMap; + const isKeyOnly = method === "keys" && targetIsMap; + const innerIterator = target[method](...args); + const wrap = isShallow2 ? toShallow : isReadonly2 ? toReadonly : toReactive; + !isReadonly2 && track( + rawTarget, + "iterate", + isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY + ); + return { + // iterator protocol + next() { + const { value, done } = innerIterator.next(); + return done ? { value, done } : { + value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value), + done + }; + }, + // iterable protocol + [Symbol.iterator]() { + return this; + } + }; + }; + } + function createReadonlyMethod(type) { + return function(...args) { + { + const key = args[0] ? `on key "${args[0]}" ` : ``; + warn$2( + `${capitalize(type)} operation ${key}failed: target is readonly.`, + toRaw(this) + ); + } + return type === "delete" ? false : type === "clear" ? void 0 : this; + }; + } + function createInstrumentations(readonly, shallow) { + const instrumentations = { + get(key) { + const target = this["__v_raw"]; + const rawTarget = toRaw(target); + const rawKey = toRaw(key); + if (!readonly) { + if (hasChanged(key, rawKey)) { + track(rawTarget, "get", key); + } + track(rawTarget, "get", rawKey); + } + const { has } = getProto(rawTarget); + const wrap = shallow ? toShallow : readonly ? toReadonly : toReactive; + if (has.call(rawTarget, key)) { + return wrap(target.get(key)); + } else if (has.call(rawTarget, rawKey)) { + return wrap(target.get(rawKey)); + } else if (target !== rawTarget) { + target.get(key); + } + }, + get size() { + const target = this["__v_raw"]; + !readonly && track(toRaw(target), "iterate", ITERATE_KEY); + return Reflect.get(target, "size", target); + }, + has(key) { + const target = this["__v_raw"]; + const rawTarget = toRaw(target); + const rawKey = toRaw(key); + if (!readonly) { + if (hasChanged(key, rawKey)) { + track(rawTarget, "has", key); + } + track(rawTarget, "has", rawKey); + } + return key === rawKey ? target.has(key) : target.has(key) || target.has(rawKey); + }, + forEach(callback, thisArg) { + const observed = this; + const target = observed["__v_raw"]; + const rawTarget = toRaw(target); + const wrap = shallow ? toShallow : readonly ? toReadonly : toReactive; + !readonly && track(rawTarget, "iterate", ITERATE_KEY); + return target.forEach((value, key) => { + return callback.call(thisArg, wrap(value), wrap(key), observed); + }); + } + }; + extend( + instrumentations, + readonly ? { + add: createReadonlyMethod("add"), + set: createReadonlyMethod("set"), + delete: createReadonlyMethod("delete"), + clear: createReadonlyMethod("clear") + } : { + add(value) { + if (!shallow && !isShallow(value) && !isReadonly(value)) { + value = toRaw(value); + } + const target = toRaw(this); + const proto = getProto(target); + const hadKey = proto.has.call(target, value); + if (!hadKey) { + target.add(value); + trigger(target, "add", value, value); + } + return this; + }, + set(key, value) { + if (!shallow && !isShallow(value) && !isReadonly(value)) { + value = toRaw(value); + } + const target = toRaw(this); + const { has, get } = getProto(target); + let hadKey = has.call(target, key); + if (!hadKey) { + key = toRaw(key); + hadKey = has.call(target, key); + } else { + checkIdentityKeys(target, has, key); + } + const oldValue = get.call(target, key); + target.set(key, value); + if (!hadKey) { + trigger(target, "add", key, value); + } else if (hasChanged(value, oldValue)) { + trigger(target, "set", key, value, oldValue); + } + return this; + }, + delete(key) { + const target = toRaw(this); + const { has, get } = getProto(target); + let hadKey = has.call(target, key); + if (!hadKey) { + key = toRaw(key); + hadKey = has.call(target, key); + } else { + checkIdentityKeys(target, has, key); + } + const oldValue = get ? get.call(target, key) : void 0; + const result = target.delete(key); + if (hadKey) { + trigger(target, "delete", key, void 0, oldValue); + } + return result; + }, + clear() { + const target = toRaw(this); + const hadItems = target.size !== 0; + const oldTarget = isMap(target) ? new Map(target) : new Set(target) ; + const result = target.clear(); + if (hadItems) { + trigger( + target, + "clear", + void 0, + void 0, + oldTarget + ); + } + return result; + } + } + ); + const iteratorMethods = [ + "keys", + "values", + "entries", + Symbol.iterator + ]; + iteratorMethods.forEach((method) => { + instrumentations[method] = createIterableMethod(method, readonly, shallow); + }); + return instrumentations; + } + function createInstrumentationGetter(isReadonly2, shallow) { + const instrumentations = createInstrumentations(isReadonly2, shallow); + return (target, key, receiver) => { + if (key === "__v_isReactive") { + return !isReadonly2; + } else if (key === "__v_isReadonly") { + return isReadonly2; + } else if (key === "__v_raw") { + return target; + } + return Reflect.get( + hasOwn(instrumentations, key) && key in target ? instrumentations : target, + key, + receiver + ); + }; + } + const mutableCollectionHandlers = { + get: /* @__PURE__ */ createInstrumentationGetter(false, false) + }; + const shallowCollectionHandlers = { + get: /* @__PURE__ */ createInstrumentationGetter(false, true) + }; + const readonlyCollectionHandlers = { + get: /* @__PURE__ */ createInstrumentationGetter(true, false) + }; + const shallowReadonlyCollectionHandlers = { + get: /* @__PURE__ */ createInstrumentationGetter(true, true) + }; + function checkIdentityKeys(target, has, key) { + const rawKey = toRaw(key); + if (rawKey !== key && has.call(target, rawKey)) { + const type = toRawType(target); + warn$2( + `Reactive ${type} contains both the raw and reactive versions of the same object${type === `Map` ? ` as keys` : ``}, which can lead to inconsistencies. Avoid differentiating between the raw and reactive versions of an object and only use the reactive version if possible.` + ); + } + } + + const reactiveMap = /* @__PURE__ */ new WeakMap(); + const shallowReactiveMap = /* @__PURE__ */ new WeakMap(); + const readonlyMap = /* @__PURE__ */ new WeakMap(); + const shallowReadonlyMap = /* @__PURE__ */ new WeakMap(); + function targetTypeMap(rawType) { + switch (rawType) { + case "Object": + case "Array": + return 1 /* COMMON */; + case "Map": + case "Set": + case "WeakMap": + case "WeakSet": + return 2 /* COLLECTION */; + default: + return 0 /* INVALID */; + } + } + function getTargetType(value) { + return value["__v_skip"] || !Object.isExtensible(value) ? 0 /* INVALID */ : targetTypeMap(toRawType(value)); + } + function reactive(target) { + if (isReadonly(target)) { + return target; + } + return createReactiveObject( + target, + false, + mutableHandlers, + mutableCollectionHandlers, + reactiveMap + ); + } + function shallowReactive(target) { + return createReactiveObject( + target, + false, + shallowReactiveHandlers, + shallowCollectionHandlers, + shallowReactiveMap + ); + } + function readonly(target) { + return createReactiveObject( + target, + true, + readonlyHandlers, + readonlyCollectionHandlers, + readonlyMap + ); + } + function shallowReadonly(target) { + return createReactiveObject( + target, + true, + shallowReadonlyHandlers, + shallowReadonlyCollectionHandlers, + shallowReadonlyMap + ); + } + function createReactiveObject(target, isReadonly2, baseHandlers, collectionHandlers, proxyMap) { + if (!isObject(target)) { + { + warn$2( + `value cannot be made ${isReadonly2 ? "readonly" : "reactive"}: ${String( + target + )}` + ); + } + return target; + } + if (target["__v_raw"] && !(isReadonly2 && target["__v_isReactive"])) { + return target; + } + const existingProxy = proxyMap.get(target); + if (existingProxy) { + return existingProxy; + } + const targetType = getTargetType(target); + if (targetType === 0 /* INVALID */) { + return target; + } + const proxy = new Proxy( + target, + targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers + ); + proxyMap.set(target, proxy); + return proxy; + } + function isReactive(value) { + if (isReadonly(value)) { + return isReactive(value["__v_raw"]); + } + return !!(value && value["__v_isReactive"]); + } + function isReadonly(value) { + return !!(value && value["__v_isReadonly"]); + } + function isShallow(value) { + return !!(value && value["__v_isShallow"]); + } + function isProxy(value) { + return value ? !!value["__v_raw"] : false; + } + function toRaw(observed) { + const raw = observed && observed["__v_raw"]; + return raw ? toRaw(raw) : observed; + } + function markRaw(value) { + if (!hasOwn(value, "__v_skip") && Object.isExtensible(value)) { + def(value, "__v_skip", true); + } + return value; + } + const toReactive = (value) => isObject(value) ? reactive(value) : value; + const toReadonly = (value) => isObject(value) ? readonly(value) : value; + + function isRef(r) { + return r ? r["__v_isRef"] === true : false; + } + function ref(value) { + return createRef(value, false); + } + function shallowRef(value) { + return createRef(value, true); + } + function createRef(rawValue, shallow) { + if (isRef(rawValue)) { + return rawValue; + } + return new RefImpl(rawValue, shallow); + } + class RefImpl { + constructor(value, isShallow2) { + this.dep = new Dep(); + this["__v_isRef"] = true; + this["__v_isShallow"] = false; + this._rawValue = isShallow2 ? value : toRaw(value); + this._value = isShallow2 ? value : toReactive(value); + this["__v_isShallow"] = isShallow2; + } + get value() { + { + this.dep.track({ + target: this, + type: "get", + key: "value" + }); + } + return this._value; + } + set value(newValue) { + const oldValue = this._rawValue; + const useDirectValue = this["__v_isShallow"] || isShallow(newValue) || isReadonly(newValue); + newValue = useDirectValue ? newValue : toRaw(newValue); + if (hasChanged(newValue, oldValue)) { + this._rawValue = newValue; + this._value = useDirectValue ? newValue : toReactive(newValue); + { + this.dep.trigger({ + target: this, + type: "set", + key: "value", + newValue, + oldValue + }); + } + } + } + } + function triggerRef(ref2) { + if (ref2.dep) { + { + ref2.dep.trigger({ + target: ref2, + type: "set", + key: "value", + newValue: ref2._value + }); + } + } + } + function unref(ref2) { + return isRef(ref2) ? ref2.value : ref2; + } + function toValue(source) { + return isFunction(source) ? source() : unref(source); + } + const shallowUnwrapHandlers = { + get: (target, key, receiver) => key === "__v_raw" ? target : unref(Reflect.get(target, key, receiver)), + set: (target, key, value, receiver) => { + const oldValue = target[key]; + if (isRef(oldValue) && !isRef(value)) { + oldValue.value = value; + return true; + } else { + return Reflect.set(target, key, value, receiver); + } + } + }; + function proxyRefs(objectWithRefs) { + return isReactive(objectWithRefs) ? objectWithRefs : new Proxy(objectWithRefs, shallowUnwrapHandlers); + } + class CustomRefImpl { + constructor(factory) { + this["__v_isRef"] = true; + this._value = void 0; + const dep = this.dep = new Dep(); + const { get, set } = factory(dep.track.bind(dep), dep.trigger.bind(dep)); + this._get = get; + this._set = set; + } + get value() { + return this._value = this._get(); + } + set value(newVal) { + this._set(newVal); + } + } + function customRef(factory) { + return new CustomRefImpl(factory); + } + function toRefs(object) { + if (!isProxy(object)) { + warn$2(`toRefs() expects a reactive object but received a plain one.`); + } + const ret = isArray(object) ? new Array(object.length) : {}; + for (const key in object) { + ret[key] = propertyToRef(object, key); + } + return ret; + } + class ObjectRefImpl { + constructor(_object, _key, _defaultValue) { + this._object = _object; + this._key = _key; + this._defaultValue = _defaultValue; + this["__v_isRef"] = true; + this._value = void 0; + } + get value() { + const val = this._object[this._key]; + return this._value = val === void 0 ? this._defaultValue : val; + } + set value(newVal) { + this._object[this._key] = newVal; + } + get dep() { + return getDepFromReactive(toRaw(this._object), this._key); + } + } + class GetterRefImpl { + constructor(_getter) { + this._getter = _getter; + this["__v_isRef"] = true; + this["__v_isReadonly"] = true; + this._value = void 0; + } + get value() { + return this._value = this._getter(); + } + } + function toRef(source, key, defaultValue) { + if (isRef(source)) { + return source; + } else if (isFunction(source)) { + return new GetterRefImpl(source); + } else if (isObject(source) && arguments.length > 1) { + return propertyToRef(source, key, defaultValue); + } else { + return ref(source); + } + } + function propertyToRef(source, key, defaultValue) { + const val = source[key]; + return isRef(val) ? val : new ObjectRefImpl(source, key, defaultValue); + } + + class ComputedRefImpl { + constructor(fn, setter, isSSR) { + this.fn = fn; + this.setter = setter; + /** + * @internal + */ + this._value = void 0; + /** + * @internal + */ + this.dep = new Dep(this); + /** + * @internal + */ + this.__v_isRef = true; + // TODO isolatedDeclarations "__v_isReadonly" + // A computed is also a subscriber that tracks other deps + /** + * @internal + */ + this.deps = void 0; + /** + * @internal + */ + this.depsTail = void 0; + /** + * @internal + */ + this.flags = 16; + /** + * @internal + */ + this.globalVersion = globalVersion - 1; + /** + * @internal + */ + this.next = void 0; + // for backwards compat + this.effect = this; + this["__v_isReadonly"] = !setter; + this.isSSR = isSSR; + } + /** + * @internal + */ + notify() { + this.flags |= 16; + if (!(this.flags & 8) && // avoid infinite self recursion + activeSub !== this) { + batch(this, true); + return true; + } + } + get value() { + const link = this.dep.track({ + target: this, + type: "get", + key: "value" + }) ; + refreshComputed(this); + if (link) { + link.version = this.dep.version; + } + return this._value; + } + set value(newValue) { + if (this.setter) { + this.setter(newValue); + } else { + warn$2("Write operation failed: computed value is readonly"); + } + } + } + function computed$1(getterOrOptions, debugOptions, isSSR = false) { + let getter; + let setter; + if (isFunction(getterOrOptions)) { + getter = getterOrOptions; + } else { + getter = getterOrOptions.get; + setter = getterOrOptions.set; + } + const cRef = new ComputedRefImpl(getter, setter, isSSR); + if (debugOptions && !isSSR) { + cRef.onTrack = debugOptions.onTrack; + cRef.onTrigger = debugOptions.onTrigger; + } + return cRef; + } + + const TrackOpTypes = { + "GET": "get", + "HAS": "has", + "ITERATE": "iterate" + }; + const TriggerOpTypes = { + "SET": "set", + "ADD": "add", + "DELETE": "delete", + "CLEAR": "clear" + }; + + const INITIAL_WATCHER_VALUE = {}; + const cleanupMap = /* @__PURE__ */ new WeakMap(); + let activeWatcher = void 0; + function getCurrentWatcher() { + return activeWatcher; + } + function onWatcherCleanup(cleanupFn, failSilently = false, owner = activeWatcher) { + if (owner) { + let cleanups = cleanupMap.get(owner); + if (!cleanups) cleanupMap.set(owner, cleanups = []); + cleanups.push(cleanupFn); + } else if (!failSilently) { + warn$2( + `onWatcherCleanup() was called when there was no active watcher to associate with.` + ); + } + } + function watch$1(source, cb, options = EMPTY_OBJ) { + const { immediate, deep, once, scheduler, augmentJob, call } = options; + const warnInvalidSource = (s) => { + (options.onWarn || warn$2)( + `Invalid watch source: `, + s, + `A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types.` + ); + }; + const reactiveGetter = (source2) => { + if (deep) return source2; + if (isShallow(source2) || deep === false || deep === 0) + return traverse(source2, 1); + return traverse(source2); + }; + let effect; + let getter; + let cleanup; + let boundCleanup; + let forceTrigger = false; + let isMultiSource = false; + if (isRef(source)) { + getter = () => source.value; + forceTrigger = isShallow(source); + } else if (isReactive(source)) { + getter = () => reactiveGetter(source); + forceTrigger = true; + } else if (isArray(source)) { + isMultiSource = true; + forceTrigger = source.some((s) => isReactive(s) || isShallow(s)); + getter = () => source.map((s) => { + if (isRef(s)) { + return s.value; + } else if (isReactive(s)) { + return reactiveGetter(s); + } else if (isFunction(s)) { + return call ? call(s, 2) : s(); + } else { + warnInvalidSource(s); + } + }); + } else if (isFunction(source)) { + if (cb) { + getter = call ? () => call(source, 2) : source; + } else { + getter = () => { + if (cleanup) { + pauseTracking(); + try { + cleanup(); + } finally { + resetTracking(); + } + } + const currentEffect = activeWatcher; + activeWatcher = effect; + try { + return call ? call(source, 3, [boundCleanup]) : source(boundCleanup); + } finally { + activeWatcher = currentEffect; + } + }; + } + } else { + getter = NOOP; + warnInvalidSource(source); + } + if (cb && deep) { + const baseGetter = getter; + const depth = deep === true ? Infinity : deep; + getter = () => traverse(baseGetter(), depth); + } + const scope = getCurrentScope(); + const watchHandle = () => { + effect.stop(); + if (scope && scope.active) { + remove(scope.effects, effect); + } + }; + if (once && cb) { + const _cb = cb; + cb = (...args) => { + _cb(...args); + watchHandle(); + }; + } + let oldValue = isMultiSource ? new Array(source.length).fill(INITIAL_WATCHER_VALUE) : INITIAL_WATCHER_VALUE; + const job = (immediateFirstRun) => { + if (!(effect.flags & 1) || !effect.dirty && !immediateFirstRun) { + return; + } + if (cb) { + const newValue = effect.run(); + if (deep || forceTrigger || (isMultiSource ? newValue.some((v, i) => hasChanged(v, oldValue[i])) : hasChanged(newValue, oldValue))) { + if (cleanup) { + cleanup(); + } + const currentWatcher = activeWatcher; + activeWatcher = effect; + try { + const args = [ + newValue, + // pass undefined as the old value when it's changed for the first time + oldValue === INITIAL_WATCHER_VALUE ? void 0 : isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE ? [] : oldValue, + boundCleanup + ]; + call ? call(cb, 3, args) : ( + // @ts-expect-error + cb(...args) + ); + oldValue = newValue; + } finally { + activeWatcher = currentWatcher; + } + } + } else { + effect.run(); + } + }; + if (augmentJob) { + augmentJob(job); + } + effect = new ReactiveEffect(getter); + effect.scheduler = scheduler ? () => scheduler(job, false) : job; + boundCleanup = (fn) => onWatcherCleanup(fn, false, effect); + cleanup = effect.onStop = () => { + const cleanups = cleanupMap.get(effect); + if (cleanups) { + if (call) { + call(cleanups, 4); + } else { + for (const cleanup2 of cleanups) cleanup2(); + } + cleanupMap.delete(effect); + } + }; + { + effect.onTrack = options.onTrack; + effect.onTrigger = options.onTrigger; + } + if (cb) { + if (immediate) { + job(true); + } else { + oldValue = effect.run(); + } + } else if (scheduler) { + scheduler(job.bind(null, true), true); + } else { + effect.run(); + } + watchHandle.pause = effect.pause.bind(effect); + watchHandle.resume = effect.resume.bind(effect); + watchHandle.stop = watchHandle; + return watchHandle; + } + function traverse(value, depth = Infinity, seen) { + if (depth <= 0 || !isObject(value) || value["__v_skip"]) { + return value; + } + seen = seen || /* @__PURE__ */ new Set(); + if (seen.has(value)) { + return value; + } + seen.add(value); + depth--; + if (isRef(value)) { + traverse(value.value, depth, seen); + } else if (isArray(value)) { + for (let i = 0; i < value.length; i++) { + traverse(value[i], depth, seen); + } + } else if (isSet(value) || isMap(value)) { + value.forEach((v) => { + traverse(v, depth, seen); + }); + } else if (isPlainObject(value)) { + for (const key in value) { + traverse(value[key], depth, seen); + } + for (const key of Object.getOwnPropertySymbols(value)) { + if (Object.prototype.propertyIsEnumerable.call(value, key)) { + traverse(value[key], depth, seen); + } + } + } + return value; + } + + const stack$1 = []; + function pushWarningContext(vnode) { + stack$1.push(vnode); + } + function popWarningContext() { + stack$1.pop(); + } + let isWarning = false; + function warn$1(msg, ...args) { + if (isWarning) return; + isWarning = true; + pauseTracking(); + const instance = stack$1.length ? stack$1[stack$1.length - 1].component : null; + const appWarnHandler = instance && instance.appContext.config.warnHandler; + const trace = getComponentTrace(); + if (appWarnHandler) { + callWithErrorHandling( + appWarnHandler, + instance, + 11, + [ + // eslint-disable-next-line no-restricted-syntax + msg + args.map((a) => { + var _a, _b; + return (_b = (_a = a.toString) == null ? void 0 : _a.call(a)) != null ? _b : JSON.stringify(a); + }).join(""), + instance && instance.proxy, + trace.map( + ({ vnode }) => `at <${formatComponentName(instance, vnode.type)}>` + ).join("\n"), + trace + ] + ); + } else { + const warnArgs = [`[Vue warn]: ${msg}`, ...args]; + if (trace.length && // avoid spamming console during tests + true) { + warnArgs.push(` +`, ...formatTrace(trace)); + } + console.warn(...warnArgs); + } + resetTracking(); + isWarning = false; + } + function getComponentTrace() { + let currentVNode = stack$1[stack$1.length - 1]; + if (!currentVNode) { + return []; + } + const normalizedStack = []; + while (currentVNode) { + const last = normalizedStack[0]; + if (last && last.vnode === currentVNode) { + last.recurseCount++; + } else { + normalizedStack.push({ + vnode: currentVNode, + recurseCount: 0 + }); + } + const parentInstance = currentVNode.component && currentVNode.component.parent; + currentVNode = parentInstance && parentInstance.vnode; + } + return normalizedStack; + } + function formatTrace(trace) { + const logs = []; + trace.forEach((entry, i) => { + logs.push(...i === 0 ? [] : [` +`], ...formatTraceEntry(entry)); + }); + return logs; + } + function formatTraceEntry({ vnode, recurseCount }) { + const postfix = recurseCount > 0 ? `... (${recurseCount} recursive calls)` : ``; + const isRoot = vnode.component ? vnode.component.parent == null : false; + const open = ` at <${formatComponentName( + vnode.component, + vnode.type, + isRoot + )}`; + const close = `>` + postfix; + return vnode.props ? [open, ...formatProps(vnode.props), close] : [open + close]; + } + function formatProps(props) { + const res = []; + const keys = Object.keys(props); + keys.slice(0, 3).forEach((key) => { + res.push(...formatProp(key, props[key])); + }); + if (keys.length > 3) { + res.push(` ...`); + } + return res; + } + function formatProp(key, value, raw) { + if (isString(value)) { + value = JSON.stringify(value); + return raw ? value : [`${key}=${value}`]; + } else if (typeof value === "number" || typeof value === "boolean" || value == null) { + return raw ? value : [`${key}=${value}`]; + } else if (isRef(value)) { + value = formatProp(key, toRaw(value.value), true); + return raw ? value : [`${key}=Ref<`, value, `>`]; + } else if (isFunction(value)) { + return [`${key}=fn${value.name ? `<${value.name}>` : ``}`]; + } else { + value = toRaw(value); + return raw ? value : [`${key}=`, value]; + } + } + function assertNumber(val, type) { + if (val === void 0) { + return; + } else if (typeof val !== "number") { + warn$1(`${type} is not a valid number - got ${JSON.stringify(val)}.`); + } else if (isNaN(val)) { + warn$1(`${type} is NaN - the duration expression might be incorrect.`); + } + } + + const ErrorCodes = { + "SETUP_FUNCTION": 0, + "0": "SETUP_FUNCTION", + "RENDER_FUNCTION": 1, + "1": "RENDER_FUNCTION", + "NATIVE_EVENT_HANDLER": 5, + "5": "NATIVE_EVENT_HANDLER", + "COMPONENT_EVENT_HANDLER": 6, + "6": "COMPONENT_EVENT_HANDLER", + "VNODE_HOOK": 7, + "7": "VNODE_HOOK", + "DIRECTIVE_HOOK": 8, + "8": "DIRECTIVE_HOOK", + "TRANSITION_HOOK": 9, + "9": "TRANSITION_HOOK", + "APP_ERROR_HANDLER": 10, + "10": "APP_ERROR_HANDLER", + "APP_WARN_HANDLER": 11, + "11": "APP_WARN_HANDLER", + "FUNCTION_REF": 12, + "12": "FUNCTION_REF", + "ASYNC_COMPONENT_LOADER": 13, + "13": "ASYNC_COMPONENT_LOADER", + "SCHEDULER": 14, + "14": "SCHEDULER", + "COMPONENT_UPDATE": 15, + "15": "COMPONENT_UPDATE", + "APP_UNMOUNT_CLEANUP": 16, + "16": "APP_UNMOUNT_CLEANUP" + }; + const ErrorTypeStrings$1 = { + ["sp"]: "serverPrefetch hook", + ["bc"]: "beforeCreate hook", + ["c"]: "created hook", + ["bm"]: "beforeMount hook", + ["m"]: "mounted hook", + ["bu"]: "beforeUpdate hook", + ["u"]: "updated", + ["bum"]: "beforeUnmount hook", + ["um"]: "unmounted hook", + ["a"]: "activated hook", + ["da"]: "deactivated hook", + ["ec"]: "errorCaptured hook", + ["rtc"]: "renderTracked hook", + ["rtg"]: "renderTriggered hook", + [0]: "setup function", + [1]: "render function", + [2]: "watcher getter", + [3]: "watcher callback", + [4]: "watcher cleanup function", + [5]: "native event handler", + [6]: "component event handler", + [7]: "vnode hook", + [8]: "directive hook", + [9]: "transition hook", + [10]: "app errorHandler", + [11]: "app warnHandler", + [12]: "ref function", + [13]: "async component loader", + [14]: "scheduler flush", + [15]: "component update", + [16]: "app unmount cleanup function" + }; + function callWithErrorHandling(fn, instance, type, args) { + try { + return args ? fn(...args) : fn(); + } catch (err) { + handleError(err, instance, type); + } + } + function callWithAsyncErrorHandling(fn, instance, type, args) { + if (isFunction(fn)) { + const res = callWithErrorHandling(fn, instance, type, args); + if (res && isPromise(res)) { + res.catch((err) => { + handleError(err, instance, type); + }); + } + return res; + } + if (isArray(fn)) { + const values = []; + for (let i = 0; i < fn.length; i++) { + values.push(callWithAsyncErrorHandling(fn[i], instance, type, args)); + } + return values; + } else { + warn$1( + `Invalid value type passed to callWithAsyncErrorHandling(): ${typeof fn}` + ); + } + } + function handleError(err, instance, type, throwInDev = true) { + const contextVNode = instance ? instance.vnode : null; + const { errorHandler, throwUnhandledErrorInProduction } = instance && instance.appContext.config || EMPTY_OBJ; + if (instance) { + let cur = instance.parent; + const exposedInstance = instance.proxy; + const errorInfo = ErrorTypeStrings$1[type] ; + while (cur) { + const errorCapturedHooks = cur.ec; + if (errorCapturedHooks) { + for (let i = 0; i < errorCapturedHooks.length; i++) { + if (errorCapturedHooks[i](err, exposedInstance, errorInfo) === false) { + return; + } + } + } + cur = cur.parent; + } + if (errorHandler) { + pauseTracking(); + callWithErrorHandling(errorHandler, null, 10, [ + err, + exposedInstance, + errorInfo + ]); + resetTracking(); + return; + } + } + logError(err, type, contextVNode, throwInDev, throwUnhandledErrorInProduction); + } + function logError(err, type, contextVNode, throwInDev = true, throwInProd = false) { + { + const info = ErrorTypeStrings$1[type]; + if (contextVNode) { + pushWarningContext(contextVNode); + } + warn$1(`Unhandled error${info ? ` during execution of ${info}` : ``}`); + if (contextVNode) { + popWarningContext(); + } + if (throwInDev) { + throw err; + } else { + console.error(err); + } + } + } + + const queue = []; + let flushIndex = -1; + const pendingPostFlushCbs = []; + let activePostFlushCbs = null; + let postFlushIndex = 0; + const resolvedPromise = /* @__PURE__ */ Promise.resolve(); + let currentFlushPromise = null; + const RECURSION_LIMIT = 100; + function nextTick(fn) { + const p = currentFlushPromise || resolvedPromise; + return fn ? p.then(this ? fn.bind(this) : fn) : p; + } + function findInsertionIndex(id) { + let start = flushIndex + 1; + let end = queue.length; + while (start < end) { + const middle = start + end >>> 1; + const middleJob = queue[middle]; + const middleJobId = getId(middleJob); + if (middleJobId < id || middleJobId === id && middleJob.flags & 2) { + start = middle + 1; + } else { + end = middle; + } + } + return start; + } + function queueJob(job) { + if (!(job.flags & 1)) { + const jobId = getId(job); + const lastJob = queue[queue.length - 1]; + if (!lastJob || // fast path when the job id is larger than the tail + !(job.flags & 2) && jobId >= getId(lastJob)) { + queue.push(job); + } else { + queue.splice(findInsertionIndex(jobId), 0, job); + } + job.flags |= 1; + queueFlush(); + } + } + function queueFlush() { + if (!currentFlushPromise) { + currentFlushPromise = resolvedPromise.then(flushJobs); + } + } + function queuePostFlushCb(cb) { + if (!isArray(cb)) { + if (activePostFlushCbs && cb.id === -1) { + activePostFlushCbs.splice(postFlushIndex + 1, 0, cb); + } else if (!(cb.flags & 1)) { + pendingPostFlushCbs.push(cb); + cb.flags |= 1; + } + } else { + pendingPostFlushCbs.push(...cb); + } + queueFlush(); + } + function flushPreFlushCbs(instance, seen, i = flushIndex + 1) { + { + seen = seen || /* @__PURE__ */ new Map(); + } + for (; i < queue.length; i++) { + const cb = queue[i]; + if (cb && cb.flags & 2) { + if (instance && cb.id !== instance.uid) { + continue; + } + if (checkRecursiveUpdates(seen, cb)) { + continue; + } + queue.splice(i, 1); + i--; + if (cb.flags & 4) { + cb.flags &= ~1; + } + cb(); + if (!(cb.flags & 4)) { + cb.flags &= ~1; + } + } + } + } + function flushPostFlushCbs(seen) { + if (pendingPostFlushCbs.length) { + const deduped = [...new Set(pendingPostFlushCbs)].sort( + (a, b) => getId(a) - getId(b) + ); + pendingPostFlushCbs.length = 0; + if (activePostFlushCbs) { + activePostFlushCbs.push(...deduped); + return; + } + activePostFlushCbs = deduped; + { + seen = seen || /* @__PURE__ */ new Map(); + } + for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) { + const cb = activePostFlushCbs[postFlushIndex]; + if (checkRecursiveUpdates(seen, cb)) { + continue; + } + if (cb.flags & 4) { + cb.flags &= ~1; + } + if (!(cb.flags & 8)) cb(); + cb.flags &= ~1; + } + activePostFlushCbs = null; + postFlushIndex = 0; + } + } + const getId = (job) => job.id == null ? job.flags & 2 ? -1 : Infinity : job.id; + function flushJobs(seen) { + { + seen = seen || /* @__PURE__ */ new Map(); + } + const check = (job) => checkRecursiveUpdates(seen, job) ; + try { + for (flushIndex = 0; flushIndex < queue.length; flushIndex++) { + const job = queue[flushIndex]; + if (job && !(job.flags & 8)) { + if (check(job)) { + continue; + } + if (job.flags & 4) { + job.flags &= ~1; + } + callWithErrorHandling( + job, + job.i, + job.i ? 15 : 14 + ); + if (!(job.flags & 4)) { + job.flags &= ~1; + } + } + } + } finally { + for (; flushIndex < queue.length; flushIndex++) { + const job = queue[flushIndex]; + if (job) { + job.flags &= ~1; + } + } + flushIndex = -1; + queue.length = 0; + flushPostFlushCbs(seen); + currentFlushPromise = null; + if (queue.length || pendingPostFlushCbs.length) { + flushJobs(seen); + } + } + } + function checkRecursiveUpdates(seen, fn) { + const count = seen.get(fn) || 0; + if (count > RECURSION_LIMIT) { + const instance = fn.i; + const componentName = instance && getComponentName(instance.type); + handleError( + `Maximum recursive updates exceeded${componentName ? ` in component <${componentName}>` : ``}. This means you have a reactive effect that is mutating its own dependencies and thus recursively triggering itself. Possible sources include component template, render function, updated hook or watcher source function.`, + null, + 10 + ); + return true; + } + seen.set(fn, count + 1); + return false; + } + + let isHmrUpdating = false; + const hmrDirtyComponents = /* @__PURE__ */ new Map(); + { + getGlobalThis().__VUE_HMR_RUNTIME__ = { + createRecord: tryWrap(createRecord), + rerender: tryWrap(rerender), + reload: tryWrap(reload) + }; + } + const map = /* @__PURE__ */ new Map(); + function registerHMR(instance) { + const id = instance.type.__hmrId; + let record = map.get(id); + if (!record) { + createRecord(id, instance.type); + record = map.get(id); + } + record.instances.add(instance); + } + function unregisterHMR(instance) { + map.get(instance.type.__hmrId).instances.delete(instance); + } + function createRecord(id, initialDef) { + if (map.has(id)) { + return false; + } + map.set(id, { + initialDef: normalizeClassComponent(initialDef), + instances: /* @__PURE__ */ new Set() + }); + return true; + } + function normalizeClassComponent(component) { + return isClassComponent(component) ? component.__vccOpts : component; + } + function rerender(id, newRender) { + const record = map.get(id); + if (!record) { + return; + } + record.initialDef.render = newRender; + [...record.instances].forEach((instance) => { + if (newRender) { + instance.render = newRender; + normalizeClassComponent(instance.type).render = newRender; + } + instance.renderCache = []; + isHmrUpdating = true; + instance.update(); + isHmrUpdating = false; + }); + } + function reload(id, newComp) { + const record = map.get(id); + if (!record) return; + newComp = normalizeClassComponent(newComp); + updateComponentDef(record.initialDef, newComp); + const instances = [...record.instances]; + for (let i = 0; i < instances.length; i++) { + const instance = instances[i]; + const oldComp = normalizeClassComponent(instance.type); + let dirtyInstances = hmrDirtyComponents.get(oldComp); + if (!dirtyInstances) { + if (oldComp !== record.initialDef) { + updateComponentDef(oldComp, newComp); + } + hmrDirtyComponents.set(oldComp, dirtyInstances = /* @__PURE__ */ new Set()); + } + dirtyInstances.add(instance); + instance.appContext.propsCache.delete(instance.type); + instance.appContext.emitsCache.delete(instance.type); + instance.appContext.optionsCache.delete(instance.type); + if (instance.ceReload) { + dirtyInstances.add(instance); + instance.ceReload(newComp.styles); + dirtyInstances.delete(instance); + } else if (instance.parent) { + queueJob(() => { + isHmrUpdating = true; + instance.parent.update(); + isHmrUpdating = false; + dirtyInstances.delete(instance); + }); + } else if (instance.appContext.reload) { + instance.appContext.reload(); + } else if (typeof window !== "undefined") { + window.location.reload(); + } else { + console.warn( + "[HMR] Root or manually mounted instance modified. Full reload required." + ); + } + if (instance.root.ce && instance !== instance.root) { + instance.root.ce._removeChildStyle(oldComp); + } + } + queuePostFlushCb(() => { + hmrDirtyComponents.clear(); + }); + } + function updateComponentDef(oldComp, newComp) { + extend(oldComp, newComp); + for (const key in oldComp) { + if (key !== "__file" && !(key in newComp)) { + delete oldComp[key]; + } + } + } + function tryWrap(fn) { + return (id, arg) => { + try { + return fn(id, arg); + } catch (e) { + console.error(e); + console.warn( + `[HMR] Something went wrong during Vue component hot-reload. Full reload required.` + ); + } + }; + } + + let devtools$1; + let buffer = []; + let devtoolsNotInstalled = false; + function emit$1(event, ...args) { + if (devtools$1) { + devtools$1.emit(event, ...args); + } else if (!devtoolsNotInstalled) { + buffer.push({ event, args }); + } + } + function setDevtoolsHook$1(hook, target) { + var _a, _b; + devtools$1 = hook; + if (devtools$1) { + devtools$1.enabled = true; + buffer.forEach(({ event, args }) => devtools$1.emit(event, ...args)); + buffer = []; + } else if ( + // handle late devtools injection - only do this if we are in an actual + // browser environment to avoid the timer handle stalling test runner exit + // (#4815) + typeof window !== "undefined" && // some envs mock window but not fully + window.HTMLElement && // also exclude jsdom + // eslint-disable-next-line no-restricted-syntax + !((_b = (_a = window.navigator) == null ? void 0 : _a.userAgent) == null ? void 0 : _b.includes("jsdom")) + ) { + const replay = target.__VUE_DEVTOOLS_HOOK_REPLAY__ = target.__VUE_DEVTOOLS_HOOK_REPLAY__ || []; + replay.push((newHook) => { + setDevtoolsHook$1(newHook, target); + }); + setTimeout(() => { + if (!devtools$1) { + target.__VUE_DEVTOOLS_HOOK_REPLAY__ = null; + devtoolsNotInstalled = true; + buffer = []; + } + }, 3e3); + } else { + devtoolsNotInstalled = true; + buffer = []; + } + } + function devtoolsInitApp(app, version) { + emit$1("app:init" /* APP_INIT */, app, version, { + Fragment, + Text, + Comment, + Static + }); + } + function devtoolsUnmountApp(app) { + emit$1("app:unmount" /* APP_UNMOUNT */, app); + } + const devtoolsComponentAdded = /* @__PURE__ */ createDevtoolsComponentHook("component:added" /* COMPONENT_ADDED */); + const devtoolsComponentUpdated = /* @__PURE__ */ createDevtoolsComponentHook("component:updated" /* COMPONENT_UPDATED */); + const _devtoolsComponentRemoved = /* @__PURE__ */ createDevtoolsComponentHook( + "component:removed" /* COMPONENT_REMOVED */ + ); + const devtoolsComponentRemoved = (component) => { + if (devtools$1 && typeof devtools$1.cleanupBuffer === "function" && // remove the component if it wasn't buffered + !devtools$1.cleanupBuffer(component)) { + _devtoolsComponentRemoved(component); + } + }; + /*! #__NO_SIDE_EFFECTS__ */ + // @__NO_SIDE_EFFECTS__ + function createDevtoolsComponentHook(hook) { + return (component) => { + emit$1( + hook, + component.appContext.app, + component.uid, + component.parent ? component.parent.uid : void 0, + component + ); + }; + } + const devtoolsPerfStart = /* @__PURE__ */ createDevtoolsPerformanceHook("perf:start" /* PERFORMANCE_START */); + const devtoolsPerfEnd = /* @__PURE__ */ createDevtoolsPerformanceHook("perf:end" /* PERFORMANCE_END */); + function createDevtoolsPerformanceHook(hook) { + return (component, type, time) => { + emit$1(hook, component.appContext.app, component.uid, component, type, time); + }; + } + function devtoolsComponentEmit(component, event, params) { + emit$1( + "component:emit" /* COMPONENT_EMIT */, + component.appContext.app, + component, + event, + params + ); + } + + let currentRenderingInstance = null; + let currentScopeId = null; + function setCurrentRenderingInstance(instance) { + const prev = currentRenderingInstance; + currentRenderingInstance = instance; + currentScopeId = instance && instance.type.__scopeId || null; + return prev; + } + function pushScopeId(id) { + currentScopeId = id; + } + function popScopeId() { + currentScopeId = null; + } + const withScopeId = (_id) => withCtx; + function withCtx(fn, ctx = currentRenderingInstance, isNonScopedSlot) { + if (!ctx) return fn; + if (fn._n) { + return fn; + } + const renderFnWithContext = (...args) => { + if (renderFnWithContext._d) { + setBlockTracking(-1); + } + const prevInstance = setCurrentRenderingInstance(ctx); + let res; + try { + res = fn(...args); + } finally { + setCurrentRenderingInstance(prevInstance); + if (renderFnWithContext._d) { + setBlockTracking(1); + } + } + { + devtoolsComponentUpdated(ctx); + } + return res; + }; + renderFnWithContext._n = true; + renderFnWithContext._c = true; + renderFnWithContext._d = true; + return renderFnWithContext; + } + + function validateDirectiveName(name) { + if (isBuiltInDirective(name)) { + warn$1("Do not use built-in directive ids as custom directive id: " + name); + } + } + function withDirectives(vnode, directives) { + if (currentRenderingInstance === null) { + warn$1(`withDirectives can only be used inside render functions.`); + return vnode; + } + const instance = getComponentPublicInstance(currentRenderingInstance); + const bindings = vnode.dirs || (vnode.dirs = []); + for (let i = 0; i < directives.length; i++) { + let [dir, value, arg, modifiers = EMPTY_OBJ] = directives[i]; + if (dir) { + if (isFunction(dir)) { + dir = { + mounted: dir, + updated: dir + }; + } + if (dir.deep) { + traverse(value); + } + bindings.push({ + dir, + instance, + value, + oldValue: void 0, + arg, + modifiers + }); + } + } + return vnode; + } + function invokeDirectiveHook(vnode, prevVNode, instance, name) { + const bindings = vnode.dirs; + const oldBindings = prevVNode && prevVNode.dirs; + for (let i = 0; i < bindings.length; i++) { + const binding = bindings[i]; + if (oldBindings) { + binding.oldValue = oldBindings[i].value; + } + let hook = binding.dir[name]; + if (hook) { + pauseTracking(); + callWithAsyncErrorHandling(hook, instance, 8, [ + vnode.el, + binding, + vnode, + prevVNode + ]); + resetTracking(); + } + } + } + + const TeleportEndKey = Symbol("_vte"); + const isTeleport = (type) => type.__isTeleport; + const isTeleportDisabled = (props) => props && (props.disabled || props.disabled === ""); + const isTeleportDeferred = (props) => props && (props.defer || props.defer === ""); + const isTargetSVG = (target) => typeof SVGElement !== "undefined" && target instanceof SVGElement; + const isTargetMathML = (target) => typeof MathMLElement === "function" && target instanceof MathMLElement; + const resolveTarget = (props, select) => { + const targetSelector = props && props.to; + if (isString(targetSelector)) { + if (!select) { + warn$1( + `Current renderer does not support string target for Teleports. (missing querySelector renderer option)` + ); + return null; + } else { + const target = select(targetSelector); + if (!target && !isTeleportDisabled(props)) { + warn$1( + `Failed to locate Teleport target with selector "${targetSelector}". Note the target element must exist before the component is mounted - i.e. the target cannot be rendered by the component itself, and ideally should be outside of the entire Vue component tree.` + ); + } + return target; + } + } else { + if (!targetSelector && !isTeleportDisabled(props)) { + warn$1(`Invalid Teleport target: ${targetSelector}`); + } + return targetSelector; + } + }; + const TeleportImpl = { + name: "Teleport", + __isTeleport: true, + process(n1, n2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized, internals) { + const { + mc: mountChildren, + pc: patchChildren, + pbc: patchBlockChildren, + o: { insert, querySelector, createText, createComment } + } = internals; + const disabled = isTeleportDisabled(n2.props); + let { shapeFlag, children, dynamicChildren } = n2; + if (isHmrUpdating) { + optimized = false; + dynamicChildren = null; + } + if (n1 == null) { + const placeholder = n2.el = createComment("teleport start") ; + const mainAnchor = n2.anchor = createComment("teleport end") ; + insert(placeholder, container, anchor); + insert(mainAnchor, container, anchor); + const mount = (container2, anchor2) => { + if (shapeFlag & 16) { + if (parentComponent && parentComponent.isCE) { + parentComponent.ce._teleportTarget = container2; + } + mountChildren( + children, + container2, + anchor2, + parentComponent, + parentSuspense, + namespace, + slotScopeIds, + optimized + ); + } + }; + const mountToTarget = () => { + const target = n2.target = resolveTarget(n2.props, querySelector); + const targetAnchor = prepareAnchor(target, n2, createText, insert); + if (target) { + if (namespace !== "svg" && isTargetSVG(target)) { + namespace = "svg"; + } else if (namespace !== "mathml" && isTargetMathML(target)) { + namespace = "mathml"; + } + if (!disabled) { + mount(target, targetAnchor); + updateCssVars(n2, false); + } + } else if (!disabled) { + warn$1( + "Invalid Teleport target on mount:", + target, + `(${typeof target})` + ); + } + }; + if (disabled) { + mount(container, mainAnchor); + updateCssVars(n2, true); + } + if (isTeleportDeferred(n2.props)) { + queuePostRenderEffect(() => { + mountToTarget(); + n2.el.__isMounted = true; + }, parentSuspense); + } else { + mountToTarget(); + } + } else { + if (isTeleportDeferred(n2.props) && !n1.el.__isMounted) { + queuePostRenderEffect(() => { + TeleportImpl.process( + n1, + n2, + container, + anchor, + parentComponent, + parentSuspense, + namespace, + slotScopeIds, + optimized, + internals + ); + delete n1.el.__isMounted; + }, parentSuspense); + return; + } + n2.el = n1.el; + n2.targetStart = n1.targetStart; + const mainAnchor = n2.anchor = n1.anchor; + const target = n2.target = n1.target; + const targetAnchor = n2.targetAnchor = n1.targetAnchor; + const wasDisabled = isTeleportDisabled(n1.props); + const currentContainer = wasDisabled ? container : target; + const currentAnchor = wasDisabled ? mainAnchor : targetAnchor; + if (namespace === "svg" || isTargetSVG(target)) { + namespace = "svg"; + } else if (namespace === "mathml" || isTargetMathML(target)) { + namespace = "mathml"; + } + if (dynamicChildren) { + patchBlockChildren( + n1.dynamicChildren, + dynamicChildren, + currentContainer, + parentComponent, + parentSuspense, + namespace, + slotScopeIds + ); + traverseStaticChildren(n1, n2, true); + } else if (!optimized) { + patchChildren( + n1, + n2, + currentContainer, + currentAnchor, + parentComponent, + parentSuspense, + namespace, + slotScopeIds, + false + ); + } + if (disabled) { + if (!wasDisabled) { + moveTeleport( + n2, + container, + mainAnchor, + internals, + 1 + ); + } else { + if (n2.props && n1.props && n2.props.to !== n1.props.to) { + n2.props.to = n1.props.to; + } + } + } else { + if ((n2.props && n2.props.to) !== (n1.props && n1.props.to)) { + const nextTarget = n2.target = resolveTarget( + n2.props, + querySelector + ); + if (nextTarget) { + moveTeleport( + n2, + nextTarget, + null, + internals, + 0 + ); + } else { + warn$1( + "Invalid Teleport target on update:", + target, + `(${typeof target})` + ); + } + } else if (wasDisabled) { + moveTeleport( + n2, + target, + targetAnchor, + internals, + 1 + ); + } + } + updateCssVars(n2, disabled); + } + }, + remove(vnode, parentComponent, parentSuspense, { um: unmount, o: { remove: hostRemove } }, doRemove) { + const { + shapeFlag, + children, + anchor, + targetStart, + targetAnchor, + target, + props + } = vnode; + if (target) { + hostRemove(targetStart); + hostRemove(targetAnchor); + } + doRemove && hostRemove(anchor); + if (shapeFlag & 16) { + const shouldRemove = doRemove || !isTeleportDisabled(props); + for (let i = 0; i < children.length; i++) { + const child = children[i]; + unmount( + child, + parentComponent, + parentSuspense, + shouldRemove, + !!child.dynamicChildren + ); + } + } + }, + move: moveTeleport, + hydrate: hydrateTeleport + }; + function moveTeleport(vnode, container, parentAnchor, { o: { insert }, m: move }, moveType = 2) { + if (moveType === 0) { + insert(vnode.targetAnchor, container, parentAnchor); + } + const { el, anchor, shapeFlag, children, props } = vnode; + const isReorder = moveType === 2; + if (isReorder) { + insert(el, container, parentAnchor); + } + if (!isReorder || isTeleportDisabled(props)) { + if (shapeFlag & 16) { + for (let i = 0; i < children.length; i++) { + move( + children[i], + container, + parentAnchor, + 2 + ); + } + } + } + if (isReorder) { + insert(anchor, container, parentAnchor); + } + } + function hydrateTeleport(node, vnode, parentComponent, parentSuspense, slotScopeIds, optimized, { + o: { nextSibling, parentNode, querySelector, insert, createText } + }, hydrateChildren) { + const target = vnode.target = resolveTarget( + vnode.props, + querySelector + ); + if (target) { + const disabled = isTeleportDisabled(vnode.props); + const targetNode = target._lpa || target.firstChild; + if (vnode.shapeFlag & 16) { + if (disabled) { + vnode.anchor = hydrateChildren( + nextSibling(node), + vnode, + parentNode(node), + parentComponent, + parentSuspense, + slotScopeIds, + optimized + ); + vnode.targetStart = targetNode; + vnode.targetAnchor = targetNode && nextSibling(targetNode); + } else { + vnode.anchor = nextSibling(node); + let targetAnchor = targetNode; + while (targetAnchor) { + if (targetAnchor && targetAnchor.nodeType === 8) { + if (targetAnchor.data === "teleport start anchor") { + vnode.targetStart = targetAnchor; + } else if (targetAnchor.data === "teleport anchor") { + vnode.targetAnchor = targetAnchor; + target._lpa = vnode.targetAnchor && nextSibling(vnode.targetAnchor); + break; + } + } + targetAnchor = nextSibling(targetAnchor); + } + if (!vnode.targetAnchor) { + prepareAnchor(target, vnode, createText, insert); + } + hydrateChildren( + targetNode && nextSibling(targetNode), + vnode, + target, + parentComponent, + parentSuspense, + slotScopeIds, + optimized + ); + } + } + updateCssVars(vnode, disabled); + } + return vnode.anchor && nextSibling(vnode.anchor); + } + const Teleport = TeleportImpl; + function updateCssVars(vnode, isDisabled) { + const ctx = vnode.ctx; + if (ctx && ctx.ut) { + let node, anchor; + if (isDisabled) { + node = vnode.el; + anchor = vnode.anchor; + } else { + node = vnode.targetStart; + anchor = vnode.targetAnchor; + } + while (node && node !== anchor) { + if (node.nodeType === 1) node.setAttribute("data-v-owner", ctx.uid); + node = node.nextSibling; + } + ctx.ut(); + } + } + function prepareAnchor(target, vnode, createText, insert) { + const targetStart = vnode.targetStart = createText(""); + const targetAnchor = vnode.targetAnchor = createText(""); + targetStart[TeleportEndKey] = targetAnchor; + if (target) { + insert(targetStart, target); + insert(targetAnchor, target); + } + return targetAnchor; + } + + const leaveCbKey = Symbol("_leaveCb"); + const enterCbKey$1 = Symbol("_enterCb"); + function useTransitionState() { + const state = { + isMounted: false, + isLeaving: false, + isUnmounting: false, + leavingVNodes: /* @__PURE__ */ new Map() + }; + onMounted(() => { + state.isMounted = true; + }); + onBeforeUnmount(() => { + state.isUnmounting = true; + }); + return state; + } + const TransitionHookValidator = [Function, Array]; + const BaseTransitionPropsValidators = { + mode: String, + appear: Boolean, + persisted: Boolean, + // enter + onBeforeEnter: TransitionHookValidator, + onEnter: TransitionHookValidator, + onAfterEnter: TransitionHookValidator, + onEnterCancelled: TransitionHookValidator, + // leave + onBeforeLeave: TransitionHookValidator, + onLeave: TransitionHookValidator, + onAfterLeave: TransitionHookValidator, + onLeaveCancelled: TransitionHookValidator, + // appear + onBeforeAppear: TransitionHookValidator, + onAppear: TransitionHookValidator, + onAfterAppear: TransitionHookValidator, + onAppearCancelled: TransitionHookValidator + }; + const recursiveGetSubtree = (instance) => { + const subTree = instance.subTree; + return subTree.component ? recursiveGetSubtree(subTree.component) : subTree; + }; + const BaseTransitionImpl = { + name: `BaseTransition`, + props: BaseTransitionPropsValidators, + setup(props, { slots }) { + const instance = getCurrentInstance(); + const state = useTransitionState(); + return () => { + const children = slots.default && getTransitionRawChildren(slots.default(), true); + if (!children || !children.length) { + return; + } + const child = findNonCommentChild(children); + const rawProps = toRaw(props); + const { mode } = rawProps; + if (mode && mode !== "in-out" && mode !== "out-in" && mode !== "default") { + warn$1(`invalid mode: ${mode}`); + } + if (state.isLeaving) { + return emptyPlaceholder(child); + } + const innerChild = getInnerChild$1(child); + if (!innerChild) { + return emptyPlaceholder(child); + } + let enterHooks = resolveTransitionHooks( + innerChild, + rawProps, + state, + instance, + // #11061, ensure enterHooks is fresh after clone + (hooks) => enterHooks = hooks + ); + if (innerChild.type !== Comment) { + setTransitionHooks(innerChild, enterHooks); + } + let oldInnerChild = instance.subTree && getInnerChild$1(instance.subTree); + if (oldInnerChild && oldInnerChild.type !== Comment && !isSameVNodeType(innerChild, oldInnerChild) && recursiveGetSubtree(instance).type !== Comment) { + let leavingHooks = resolveTransitionHooks( + oldInnerChild, + rawProps, + state, + instance + ); + setTransitionHooks(oldInnerChild, leavingHooks); + if (mode === "out-in" && innerChild.type !== Comment) { + state.isLeaving = true; + leavingHooks.afterLeave = () => { + state.isLeaving = false; + if (!(instance.job.flags & 8)) { + instance.update(); + } + delete leavingHooks.afterLeave; + oldInnerChild = void 0; + }; + return emptyPlaceholder(child); + } else if (mode === "in-out" && innerChild.type !== Comment) { + leavingHooks.delayLeave = (el, earlyRemove, delayedLeave) => { + const leavingVNodesCache = getLeavingNodesForType( + state, + oldInnerChild + ); + leavingVNodesCache[String(oldInnerChild.key)] = oldInnerChild; + el[leaveCbKey] = () => { + earlyRemove(); + el[leaveCbKey] = void 0; + delete enterHooks.delayedLeave; + oldInnerChild = void 0; + }; + enterHooks.delayedLeave = () => { + delayedLeave(); + delete enterHooks.delayedLeave; + oldInnerChild = void 0; + }; + }; + } else { + oldInnerChild = void 0; + } + } else if (oldInnerChild) { + oldInnerChild = void 0; + } + return child; + }; + } + }; + function findNonCommentChild(children) { + let child = children[0]; + if (children.length > 1) { + let hasFound = false; + for (const c of children) { + if (c.type !== Comment) { + if (hasFound) { + warn$1( + " can only be used on a single element or component. Use for lists." + ); + break; + } + child = c; + hasFound = true; + } + } + } + return child; + } + const BaseTransition = BaseTransitionImpl; + function getLeavingNodesForType(state, vnode) { + const { leavingVNodes } = state; + let leavingVNodesCache = leavingVNodes.get(vnode.type); + if (!leavingVNodesCache) { + leavingVNodesCache = /* @__PURE__ */ Object.create(null); + leavingVNodes.set(vnode.type, leavingVNodesCache); + } + return leavingVNodesCache; + } + function resolveTransitionHooks(vnode, props, state, instance, postClone) { + const { + appear, + mode, + persisted = false, + onBeforeEnter, + onEnter, + onAfterEnter, + onEnterCancelled, + onBeforeLeave, + onLeave, + onAfterLeave, + onLeaveCancelled, + onBeforeAppear, + onAppear, + onAfterAppear, + onAppearCancelled + } = props; + const key = String(vnode.key); + const leavingVNodesCache = getLeavingNodesForType(state, vnode); + const callHook = (hook, args) => { + hook && callWithAsyncErrorHandling( + hook, + instance, + 9, + args + ); + }; + const callAsyncHook = (hook, args) => { + const done = args[1]; + callHook(hook, args); + if (isArray(hook)) { + if (hook.every((hook2) => hook2.length <= 1)) done(); + } else if (hook.length <= 1) { + done(); + } + }; + const hooks = { + mode, + persisted, + beforeEnter(el) { + let hook = onBeforeEnter; + if (!state.isMounted) { + if (appear) { + hook = onBeforeAppear || onBeforeEnter; + } else { + return; + } + } + if (el[leaveCbKey]) { + el[leaveCbKey]( + true + /* cancelled */ + ); + } + const leavingVNode = leavingVNodesCache[key]; + if (leavingVNode && isSameVNodeType(vnode, leavingVNode) && leavingVNode.el[leaveCbKey]) { + leavingVNode.el[leaveCbKey](); + } + callHook(hook, [el]); + }, + enter(el) { + let hook = onEnter; + let afterHook = onAfterEnter; + let cancelHook = onEnterCancelled; + if (!state.isMounted) { + if (appear) { + hook = onAppear || onEnter; + afterHook = onAfterAppear || onAfterEnter; + cancelHook = onAppearCancelled || onEnterCancelled; + } else { + return; + } + } + let called = false; + const done = el[enterCbKey$1] = (cancelled) => { + if (called) return; + called = true; + if (cancelled) { + callHook(cancelHook, [el]); + } else { + callHook(afterHook, [el]); + } + if (hooks.delayedLeave) { + hooks.delayedLeave(); + } + el[enterCbKey$1] = void 0; + }; + if (hook) { + callAsyncHook(hook, [el, done]); + } else { + done(); + } + }, + leave(el, remove) { + const key2 = String(vnode.key); + if (el[enterCbKey$1]) { + el[enterCbKey$1]( + true + /* cancelled */ + ); + } + if (state.isUnmounting) { + return remove(); + } + callHook(onBeforeLeave, [el]); + let called = false; + const done = el[leaveCbKey] = (cancelled) => { + if (called) return; + called = true; + remove(); + if (cancelled) { + callHook(onLeaveCancelled, [el]); + } else { + callHook(onAfterLeave, [el]); + } + el[leaveCbKey] = void 0; + if (leavingVNodesCache[key2] === vnode) { + delete leavingVNodesCache[key2]; + } + }; + leavingVNodesCache[key2] = vnode; + if (onLeave) { + callAsyncHook(onLeave, [el, done]); + } else { + done(); + } + }, + clone(vnode2) { + const hooks2 = resolveTransitionHooks( + vnode2, + props, + state, + instance, + postClone + ); + if (postClone) postClone(hooks2); + return hooks2; + } + }; + return hooks; + } + function emptyPlaceholder(vnode) { + if (isKeepAlive(vnode)) { + vnode = cloneVNode(vnode); + vnode.children = null; + return vnode; + } + } + function getInnerChild$1(vnode) { + if (!isKeepAlive(vnode)) { + if (isTeleport(vnode.type) && vnode.children) { + return findNonCommentChild(vnode.children); + } + return vnode; + } + if (vnode.component) { + return vnode.component.subTree; + } + const { shapeFlag, children } = vnode; + if (children) { + if (shapeFlag & 16) { + return children[0]; + } + if (shapeFlag & 32 && isFunction(children.default)) { + return children.default(); + } + } + } + function setTransitionHooks(vnode, hooks) { + if (vnode.shapeFlag & 6 && vnode.component) { + vnode.transition = hooks; + setTransitionHooks(vnode.component.subTree, hooks); + } else if (vnode.shapeFlag & 128) { + vnode.ssContent.transition = hooks.clone(vnode.ssContent); + vnode.ssFallback.transition = hooks.clone(vnode.ssFallback); + } else { + vnode.transition = hooks; + } + } + function getTransitionRawChildren(children, keepComment = false, parentKey) { + let ret = []; + let keyedFragmentCount = 0; + for (let i = 0; i < children.length; i++) { + let child = children[i]; + const key = parentKey == null ? child.key : String(parentKey) + String(child.key != null ? child.key : i); + if (child.type === Fragment) { + if (child.patchFlag & 128) keyedFragmentCount++; + ret = ret.concat( + getTransitionRawChildren(child.children, keepComment, key) + ); + } else if (keepComment || child.type !== Comment) { + ret.push(key != null ? cloneVNode(child, { key }) : child); + } + } + if (keyedFragmentCount > 1) { + for (let i = 0; i < ret.length; i++) { + ret[i].patchFlag = -2; + } + } + return ret; + } + + /*! #__NO_SIDE_EFFECTS__ */ + // @__NO_SIDE_EFFECTS__ + function defineComponent(options, extraOptions) { + return isFunction(options) ? ( + // #8236: extend call and options.name access are considered side-effects + // by Rollup, so we have to wrap it in a pure-annotated IIFE. + /* @__PURE__ */ (() => extend({ name: options.name }, extraOptions, { setup: options }))() + ) : options; + } + + function useId() { + const i = getCurrentInstance(); + if (i) { + return (i.appContext.config.idPrefix || "v") + "-" + i.ids[0] + i.ids[1]++; + } else { + warn$1( + `useId() is called when there is no active component instance to be associated with.` + ); + } + return ""; + } + function markAsyncBoundary(instance) { + instance.ids = [instance.ids[0] + instance.ids[2]++ + "-", 0, 0]; + } + + const knownTemplateRefs = /* @__PURE__ */ new WeakSet(); + function useTemplateRef(key) { + const i = getCurrentInstance(); + const r = shallowRef(null); + if (i) { + const refs = i.refs === EMPTY_OBJ ? i.refs = {} : i.refs; + let desc; + if ((desc = Object.getOwnPropertyDescriptor(refs, key)) && !desc.configurable) { + warn$1(`useTemplateRef('${key}') already exists.`); + } else { + Object.defineProperty(refs, key, { + enumerable: true, + get: () => r.value, + set: (val) => r.value = val + }); + } + } else { + warn$1( + `useTemplateRef() is called when there is no active component instance to be associated with.` + ); + } + const ret = readonly(r) ; + { + knownTemplateRefs.add(ret); + } + return ret; + } + + function setRef(rawRef, oldRawRef, parentSuspense, vnode, isUnmount = false) { + if (isArray(rawRef)) { + rawRef.forEach( + (r, i) => setRef( + r, + oldRawRef && (isArray(oldRawRef) ? oldRawRef[i] : oldRawRef), + parentSuspense, + vnode, + isUnmount + ) + ); + return; + } + if (isAsyncWrapper(vnode) && !isUnmount) { + if (vnode.shapeFlag & 512 && vnode.type.__asyncResolved && vnode.component.subTree.component) { + setRef(rawRef, oldRawRef, parentSuspense, vnode.component.subTree); + } + return; + } + const refValue = vnode.shapeFlag & 4 ? getComponentPublicInstance(vnode.component) : vnode.el; + const value = isUnmount ? null : refValue; + const { i: owner, r: ref } = rawRef; + if (!owner) { + warn$1( + `Missing ref owner context. ref cannot be used on hoisted vnodes. A vnode with ref must be created inside the render function.` + ); + return; + } + const oldRef = oldRawRef && oldRawRef.r; + const refs = owner.refs === EMPTY_OBJ ? owner.refs = {} : owner.refs; + const setupState = owner.setupState; + const rawSetupState = toRaw(setupState); + const canSetSetupRef = setupState === EMPTY_OBJ ? () => false : (key) => { + { + if (hasOwn(rawSetupState, key) && !isRef(rawSetupState[key])) { + warn$1( + `Template ref "${key}" used on a non-ref value. It will not work in the production build.` + ); + } + if (knownTemplateRefs.has(rawSetupState[key])) { + return false; + } + } + return hasOwn(rawSetupState, key); + }; + if (oldRef != null && oldRef !== ref) { + if (isString(oldRef)) { + refs[oldRef] = null; + if (canSetSetupRef(oldRef)) { + setupState[oldRef] = null; + } + } else if (isRef(oldRef)) { + oldRef.value = null; + } + } + if (isFunction(ref)) { + callWithErrorHandling(ref, owner, 12, [value, refs]); + } else { + const _isString = isString(ref); + const _isRef = isRef(ref); + if (_isString || _isRef) { + const doSet = () => { + if (rawRef.f) { + const existing = _isString ? canSetSetupRef(ref) ? setupState[ref] : refs[ref] : ref.value; + if (isUnmount) { + isArray(existing) && remove(existing, refValue); + } else { + if (!isArray(existing)) { + if (_isString) { + refs[ref] = [refValue]; + if (canSetSetupRef(ref)) { + setupState[ref] = refs[ref]; + } + } else { + ref.value = [refValue]; + if (rawRef.k) refs[rawRef.k] = ref.value; + } + } else if (!existing.includes(refValue)) { + existing.push(refValue); + } + } + } else if (_isString) { + refs[ref] = value; + if (canSetSetupRef(ref)) { + setupState[ref] = value; + } + } else if (_isRef) { + ref.value = value; + if (rawRef.k) refs[rawRef.k] = value; + } else { + warn$1("Invalid template ref type:", ref, `(${typeof ref})`); + } + }; + if (value) { + doSet.id = -1; + queuePostRenderEffect(doSet, parentSuspense); + } else { + doSet(); + } + } else { + warn$1("Invalid template ref type:", ref, `(${typeof ref})`); + } + } + } + + let hasLoggedMismatchError = false; + const logMismatchError = () => { + if (hasLoggedMismatchError) { + return; + } + console.error("Hydration completed but contains mismatches."); + hasLoggedMismatchError = true; + }; + const isSVGContainer = (container) => container.namespaceURI.includes("svg") && container.tagName !== "foreignObject"; + const isMathMLContainer = (container) => container.namespaceURI.includes("MathML"); + const getContainerType = (container) => { + if (container.nodeType !== 1) return void 0; + if (isSVGContainer(container)) return "svg"; + if (isMathMLContainer(container)) return "mathml"; + return void 0; + }; + const isComment = (node) => node.nodeType === 8; + function createHydrationFunctions(rendererInternals) { + const { + mt: mountComponent, + p: patch, + o: { + patchProp, + createText, + nextSibling, + parentNode, + remove, + insert, + createComment + } + } = rendererInternals; + const hydrate = (vnode, container) => { + if (!container.hasChildNodes()) { + warn$1( + `Attempting to hydrate existing markup but container is empty. Performing full mount instead.` + ); + patch(null, vnode, container); + flushPostFlushCbs(); + container._vnode = vnode; + return; + } + hydrateNode(container.firstChild, vnode, null, null, null); + flushPostFlushCbs(); + container._vnode = vnode; + }; + const hydrateNode = (node, vnode, parentComponent, parentSuspense, slotScopeIds, optimized = false) => { + optimized = optimized || !!vnode.dynamicChildren; + const isFragmentStart = isComment(node) && node.data === "["; + const onMismatch = () => handleMismatch( + node, + vnode, + parentComponent, + parentSuspense, + slotScopeIds, + isFragmentStart + ); + const { type, ref, shapeFlag, patchFlag } = vnode; + let domType = node.nodeType; + vnode.el = node; + { + def(node, "__vnode", vnode, true); + def(node, "__vueParentComponent", parentComponent, true); + } + if (patchFlag === -2) { + optimized = false; + vnode.dynamicChildren = null; + } + let nextNode = null; + switch (type) { + case Text: + if (domType !== 3) { + if (vnode.children === "") { + insert(vnode.el = createText(""), parentNode(node), node); + nextNode = node; + } else { + nextNode = onMismatch(); + } + } else { + if (node.data !== vnode.children) { + warn$1( + `Hydration text mismatch in`, + node.parentNode, + ` + - rendered on server: ${JSON.stringify( + node.data + )} + - expected on client: ${JSON.stringify(vnode.children)}` + ); + logMismatchError(); + node.data = vnode.children; + } + nextNode = nextSibling(node); + } + break; + case Comment: + if (isTemplateNode(node)) { + nextNode = nextSibling(node); + replaceNode( + vnode.el = node.content.firstChild, + node, + parentComponent + ); + } else if (domType !== 8 || isFragmentStart) { + nextNode = onMismatch(); + } else { + nextNode = nextSibling(node); + } + break; + case Static: + if (isFragmentStart) { + node = nextSibling(node); + domType = node.nodeType; + } + if (domType === 1 || domType === 3) { + nextNode = node; + const needToAdoptContent = !vnode.children.length; + for (let i = 0; i < vnode.staticCount; i++) { + if (needToAdoptContent) + vnode.children += nextNode.nodeType === 1 ? nextNode.outerHTML : nextNode.data; + if (i === vnode.staticCount - 1) { + vnode.anchor = nextNode; + } + nextNode = nextSibling(nextNode); + } + return isFragmentStart ? nextSibling(nextNode) : nextNode; + } else { + onMismatch(); + } + break; + case Fragment: + if (!isFragmentStart) { + nextNode = onMismatch(); + } else { + nextNode = hydrateFragment( + node, + vnode, + parentComponent, + parentSuspense, + slotScopeIds, + optimized + ); + } + break; + default: + if (shapeFlag & 1) { + if ((domType !== 1 || vnode.type.toLowerCase() !== node.tagName.toLowerCase()) && !isTemplateNode(node)) { + nextNode = onMismatch(); + } else { + nextNode = hydrateElement( + node, + vnode, + parentComponent, + parentSuspense, + slotScopeIds, + optimized + ); + } + } else if (shapeFlag & 6) { + vnode.slotScopeIds = slotScopeIds; + const container = parentNode(node); + if (isFragmentStart) { + nextNode = locateClosingAnchor(node); + } else if (isComment(node) && node.data === "teleport start") { + nextNode = locateClosingAnchor(node, node.data, "teleport end"); + } else { + nextNode = nextSibling(node); + } + mountComponent( + vnode, + container, + null, + parentComponent, + parentSuspense, + getContainerType(container), + optimized + ); + if (isAsyncWrapper(vnode) && !vnode.type.__asyncResolved) { + let subTree; + if (isFragmentStart) { + subTree = createVNode(Fragment); + subTree.anchor = nextNode ? nextNode.previousSibling : container.lastChild; + } else { + subTree = node.nodeType === 3 ? createTextVNode("") : createVNode("div"); + } + subTree.el = node; + vnode.component.subTree = subTree; + } + } else if (shapeFlag & 64) { + if (domType !== 8) { + nextNode = onMismatch(); + } else { + nextNode = vnode.type.hydrate( + node, + vnode, + parentComponent, + parentSuspense, + slotScopeIds, + optimized, + rendererInternals, + hydrateChildren + ); + } + } else if (shapeFlag & 128) { + nextNode = vnode.type.hydrate( + node, + vnode, + parentComponent, + parentSuspense, + getContainerType(parentNode(node)), + slotScopeIds, + optimized, + rendererInternals, + hydrateNode + ); + } else { + warn$1("Invalid HostVNode type:", type, `(${typeof type})`); + } + } + if (ref != null) { + setRef(ref, null, parentSuspense, vnode); + } + return nextNode; + }; + const hydrateElement = (el, vnode, parentComponent, parentSuspense, slotScopeIds, optimized) => { + optimized = optimized || !!vnode.dynamicChildren; + const { type, props, patchFlag, shapeFlag, dirs, transition } = vnode; + const forcePatch = type === "input" || type === "option"; + { + if (dirs) { + invokeDirectiveHook(vnode, null, parentComponent, "created"); + } + let needCallTransitionHooks = false; + if (isTemplateNode(el)) { + needCallTransitionHooks = needTransition( + null, + // no need check parentSuspense in hydration + transition + ) && parentComponent && parentComponent.vnode.props && parentComponent.vnode.props.appear; + const content = el.content.firstChild; + if (needCallTransitionHooks) { + transition.beforeEnter(content); + } + replaceNode(content, el, parentComponent); + vnode.el = el = content; + } + if (shapeFlag & 16 && // skip if element has innerHTML / textContent + !(props && (props.innerHTML || props.textContent))) { + let next = hydrateChildren( + el.firstChild, + vnode, + el, + parentComponent, + parentSuspense, + slotScopeIds, + optimized + ); + let hasWarned = false; + while (next) { + if (!isMismatchAllowed(el, 1 /* CHILDREN */)) { + if (!hasWarned) { + warn$1( + `Hydration children mismatch on`, + el, + ` +Server rendered element contains more child nodes than client vdom.` + ); + hasWarned = true; + } + logMismatchError(); + } + const cur = next; + next = next.nextSibling; + remove(cur); + } + } else if (shapeFlag & 8) { + let clientText = vnode.children; + if (clientText[0] === "\n" && (el.tagName === "PRE" || el.tagName === "TEXTAREA")) { + clientText = clientText.slice(1); + } + if (el.textContent !== clientText) { + if (!isMismatchAllowed(el, 0 /* TEXT */)) { + warn$1( + `Hydration text content mismatch on`, + el, + ` + - rendered on server: ${el.textContent} + - expected on client: ${vnode.children}` + ); + logMismatchError(); + } + el.textContent = vnode.children; + } + } + if (props) { + { + const isCustomElement = el.tagName.includes("-"); + for (const key in props) { + if (// #11189 skip if this node has directives that have created hooks + // as it could have mutated the DOM in any possible way + !(dirs && dirs.some((d) => d.dir.created)) && propHasMismatch(el, key, props[key], vnode, parentComponent)) { + logMismatchError(); + } + if (forcePatch && (key.endsWith("value") || key === "indeterminate") || isOn(key) && !isReservedProp(key) || // force hydrate v-bind with .prop modifiers + key[0] === "." || isCustomElement) { + patchProp(el, key, null, props[key], void 0, parentComponent); + } + } + } + } + let vnodeHooks; + if (vnodeHooks = props && props.onVnodeBeforeMount) { + invokeVNodeHook(vnodeHooks, parentComponent, vnode); + } + if (dirs) { + invokeDirectiveHook(vnode, null, parentComponent, "beforeMount"); + } + if ((vnodeHooks = props && props.onVnodeMounted) || dirs || needCallTransitionHooks) { + queueEffectWithSuspense(() => { + vnodeHooks && invokeVNodeHook(vnodeHooks, parentComponent, vnode); + needCallTransitionHooks && transition.enter(el); + dirs && invokeDirectiveHook(vnode, null, parentComponent, "mounted"); + }, parentSuspense); + } + } + return el.nextSibling; + }; + const hydrateChildren = (node, parentVNode, container, parentComponent, parentSuspense, slotScopeIds, optimized) => { + optimized = optimized || !!parentVNode.dynamicChildren; + const children = parentVNode.children; + const l = children.length; + let hasWarned = false; + for (let i = 0; i < l; i++) { + const vnode = optimized ? children[i] : children[i] = normalizeVNode(children[i]); + const isText = vnode.type === Text; + if (node) { + if (isText && !optimized) { + if (i + 1 < l && normalizeVNode(children[i + 1]).type === Text) { + insert( + createText( + node.data.slice(vnode.children.length) + ), + container, + nextSibling(node) + ); + node.data = vnode.children; + } + } + node = hydrateNode( + node, + vnode, + parentComponent, + parentSuspense, + slotScopeIds, + optimized + ); + } else if (isText && !vnode.children) { + insert(vnode.el = createText(""), container); + } else { + if (!isMismatchAllowed(container, 1 /* CHILDREN */)) { + if (!hasWarned) { + warn$1( + `Hydration children mismatch on`, + container, + ` +Server rendered element contains fewer child nodes than client vdom.` + ); + hasWarned = true; + } + logMismatchError(); + } + patch( + null, + vnode, + container, + null, + parentComponent, + parentSuspense, + getContainerType(container), + slotScopeIds + ); + } + } + return node; + }; + const hydrateFragment = (node, vnode, parentComponent, parentSuspense, slotScopeIds, optimized) => { + const { slotScopeIds: fragmentSlotScopeIds } = vnode; + if (fragmentSlotScopeIds) { + slotScopeIds = slotScopeIds ? slotScopeIds.concat(fragmentSlotScopeIds) : fragmentSlotScopeIds; + } + const container = parentNode(node); + const next = hydrateChildren( + nextSibling(node), + vnode, + container, + parentComponent, + parentSuspense, + slotScopeIds, + optimized + ); + if (next && isComment(next) && next.data === "]") { + return nextSibling(vnode.anchor = next); + } else { + logMismatchError(); + insert(vnode.anchor = createComment(`]`), container, next); + return next; + } + }; + const handleMismatch = (node, vnode, parentComponent, parentSuspense, slotScopeIds, isFragment) => { + if (!isMismatchAllowed(node.parentElement, 1 /* CHILDREN */)) { + warn$1( + `Hydration node mismatch: +- rendered on server:`, + node, + node.nodeType === 3 ? `(text)` : isComment(node) && node.data === "[" ? `(start of fragment)` : ``, + ` +- expected on client:`, + vnode.type + ); + logMismatchError(); + } + vnode.el = null; + if (isFragment) { + const end = locateClosingAnchor(node); + while (true) { + const next2 = nextSibling(node); + if (next2 && next2 !== end) { + remove(next2); + } else { + break; + } + } + } + const next = nextSibling(node); + const container = parentNode(node); + remove(node); + patch( + null, + vnode, + container, + next, + parentComponent, + parentSuspense, + getContainerType(container), + slotScopeIds + ); + if (parentComponent) { + parentComponent.vnode.el = vnode.el; + updateHOCHostEl(parentComponent, vnode.el); + } + return next; + }; + const locateClosingAnchor = (node, open = "[", close = "]") => { + let match = 0; + while (node) { + node = nextSibling(node); + if (node && isComment(node)) { + if (node.data === open) match++; + if (node.data === close) { + if (match === 0) { + return nextSibling(node); + } else { + match--; + } + } + } + } + return node; + }; + const replaceNode = (newNode, oldNode, parentComponent) => { + const parentNode2 = oldNode.parentNode; + if (parentNode2) { + parentNode2.replaceChild(newNode, oldNode); + } + let parent = parentComponent; + while (parent) { + if (parent.vnode.el === oldNode) { + parent.vnode.el = parent.subTree.el = newNode; + } + parent = parent.parent; + } + }; + const isTemplateNode = (node) => { + return node.nodeType === 1 && node.tagName === "TEMPLATE"; + }; + return [hydrate, hydrateNode]; + } + function propHasMismatch(el, key, clientValue, vnode, instance) { + let mismatchType; + let mismatchKey; + let actual; + let expected; + if (key === "class") { + actual = el.getAttribute("class"); + expected = normalizeClass(clientValue); + if (!isSetEqual(toClassSet(actual || ""), toClassSet(expected))) { + mismatchType = 2 /* CLASS */; + mismatchKey = `class`; + } + } else if (key === "style") { + actual = el.getAttribute("style") || ""; + expected = isString(clientValue) ? clientValue : stringifyStyle(normalizeStyle(clientValue)); + const actualMap = toStyleMap(actual); + const expectedMap = toStyleMap(expected); + if (vnode.dirs) { + for (const { dir, value } of vnode.dirs) { + if (dir.name === "show" && !value) { + expectedMap.set("display", "none"); + } + } + } + if (instance) { + resolveCssVars(instance, vnode, expectedMap); + } + if (!isMapEqual(actualMap, expectedMap)) { + mismatchType = 3 /* STYLE */; + mismatchKey = "style"; + } + } else if (el instanceof SVGElement && isKnownSvgAttr(key) || el instanceof HTMLElement && (isBooleanAttr(key) || isKnownHtmlAttr(key))) { + if (isBooleanAttr(key)) { + actual = el.hasAttribute(key); + expected = includeBooleanAttr(clientValue); + } else if (clientValue == null) { + actual = el.hasAttribute(key); + expected = false; + } else { + if (el.hasAttribute(key)) { + actual = el.getAttribute(key); + } else if (key === "value" && el.tagName === "TEXTAREA") { + actual = el.value; + } else { + actual = false; + } + expected = isRenderableAttrValue(clientValue) ? String(clientValue) : false; + } + if (actual !== expected) { + mismatchType = 4 /* ATTRIBUTE */; + mismatchKey = key; + } + } + if (mismatchType != null && !isMismatchAllowed(el, mismatchType)) { + const format = (v) => v === false ? `(not rendered)` : `${mismatchKey}="${v}"`; + const preSegment = `Hydration ${MismatchTypeString[mismatchType]} mismatch on`; + const postSegment = ` + - rendered on server: ${format(actual)} + - expected on client: ${format(expected)} + Note: this mismatch is check-only. The DOM will not be rectified in production due to performance overhead. + You should fix the source of the mismatch.`; + { + warn$1(preSegment, el, postSegment); + } + return true; + } + return false; + } + function toClassSet(str) { + return new Set(str.trim().split(/\s+/)); + } + function isSetEqual(a, b) { + if (a.size !== b.size) { + return false; + } + for (const s of a) { + if (!b.has(s)) { + return false; + } + } + return true; + } + function toStyleMap(str) { + const styleMap = /* @__PURE__ */ new Map(); + for (const item of str.split(";")) { + let [key, value] = item.split(":"); + key = key.trim(); + value = value && value.trim(); + if (key && value) { + styleMap.set(key, value); + } + } + return styleMap; + } + function isMapEqual(a, b) { + if (a.size !== b.size) { + return false; + } + for (const [key, value] of a) { + if (value !== b.get(key)) { + return false; + } + } + return true; + } + function resolveCssVars(instance, vnode, expectedMap) { + const root = instance.subTree; + if (instance.getCssVars && (vnode === root || root && root.type === Fragment && root.children.includes(vnode))) { + const cssVars = instance.getCssVars(); + for (const key in cssVars) { + expectedMap.set( + `--${getEscapedCssVarName(key)}`, + String(cssVars[key]) + ); + } + } + if (vnode === root && instance.parent) { + resolveCssVars(instance.parent, instance.vnode, expectedMap); + } + } + const allowMismatchAttr = "data-allow-mismatch"; + const MismatchTypeString = { + [0 /* TEXT */]: "text", + [1 /* CHILDREN */]: "children", + [2 /* CLASS */]: "class", + [3 /* STYLE */]: "style", + [4 /* ATTRIBUTE */]: "attribute" + }; + function isMismatchAllowed(el, allowedType) { + if (allowedType === 0 /* TEXT */ || allowedType === 1 /* CHILDREN */) { + while (el && !el.hasAttribute(allowMismatchAttr)) { + el = el.parentElement; + } + } + const allowedAttr = el && el.getAttribute(allowMismatchAttr); + if (allowedAttr == null) { + return false; + } else if (allowedAttr === "") { + return true; + } else { + const list = allowedAttr.split(","); + if (allowedType === 0 /* TEXT */ && list.includes("children")) { + return true; + } + return allowedAttr.split(",").includes(MismatchTypeString[allowedType]); + } + } + + const requestIdleCallback = getGlobalThis().requestIdleCallback || ((cb) => setTimeout(cb, 1)); + const cancelIdleCallback = getGlobalThis().cancelIdleCallback || ((id) => clearTimeout(id)); + const hydrateOnIdle = (timeout = 1e4) => (hydrate) => { + const id = requestIdleCallback(hydrate, { timeout }); + return () => cancelIdleCallback(id); + }; + function elementIsVisibleInViewport(el) { + const { top, left, bottom, right } = el.getBoundingClientRect(); + const { innerHeight, innerWidth } = window; + return (top > 0 && top < innerHeight || bottom > 0 && bottom < innerHeight) && (left > 0 && left < innerWidth || right > 0 && right < innerWidth); + } + const hydrateOnVisible = (opts) => (hydrate, forEach) => { + const ob = new IntersectionObserver((entries) => { + for (const e of entries) { + if (!e.isIntersecting) continue; + ob.disconnect(); + hydrate(); + break; + } + }, opts); + forEach((el) => { + if (!(el instanceof Element)) return; + if (elementIsVisibleInViewport(el)) { + hydrate(); + ob.disconnect(); + return false; + } + ob.observe(el); + }); + return () => ob.disconnect(); + }; + const hydrateOnMediaQuery = (query) => (hydrate) => { + if (query) { + const mql = matchMedia(query); + if (mql.matches) { + hydrate(); + } else { + mql.addEventListener("change", hydrate, { once: true }); + return () => mql.removeEventListener("change", hydrate); + } + } + }; + const hydrateOnInteraction = (interactions = []) => (hydrate, forEach) => { + if (isString(interactions)) interactions = [interactions]; + let hasHydrated = false; + const doHydrate = (e) => { + if (!hasHydrated) { + hasHydrated = true; + teardown(); + hydrate(); + e.target.dispatchEvent(new e.constructor(e.type, e)); + } + }; + const teardown = () => { + forEach((el) => { + for (const i of interactions) { + el.removeEventListener(i, doHydrate); + } + }); + }; + forEach((el) => { + for (const i of interactions) { + el.addEventListener(i, doHydrate, { once: true }); + } + }); + return teardown; + }; + function forEachElement(node, cb) { + if (isComment(node) && node.data === "[") { + let depth = 1; + let next = node.nextSibling; + while (next) { + if (next.nodeType === 1) { + const result = cb(next); + if (result === false) { + break; + } + } else if (isComment(next)) { + if (next.data === "]") { + if (--depth === 0) break; + } else if (next.data === "[") { + depth++; + } + } + next = next.nextSibling; + } + } else { + cb(node); + } + } + + const isAsyncWrapper = (i) => !!i.type.__asyncLoader; + /*! #__NO_SIDE_EFFECTS__ */ + // @__NO_SIDE_EFFECTS__ + function defineAsyncComponent(source) { + if (isFunction(source)) { + source = { loader: source }; + } + const { + loader, + loadingComponent, + errorComponent, + delay = 200, + hydrate: hydrateStrategy, + timeout, + // undefined = never times out + suspensible = true, + onError: userOnError + } = source; + let pendingRequest = null; + let resolvedComp; + let retries = 0; + const retry = () => { + retries++; + pendingRequest = null; + return load(); + }; + const load = () => { + let thisRequest; + return pendingRequest || (thisRequest = pendingRequest = loader().catch((err) => { + err = err instanceof Error ? err : new Error(String(err)); + if (userOnError) { + return new Promise((resolve, reject) => { + const userRetry = () => resolve(retry()); + const userFail = () => reject(err); + userOnError(err, userRetry, userFail, retries + 1); + }); + } else { + throw err; + } + }).then((comp) => { + if (thisRequest !== pendingRequest && pendingRequest) { + return pendingRequest; + } + if (!comp) { + warn$1( + `Async component loader resolved to undefined. If you are using retry(), make sure to return its return value.` + ); + } + if (comp && (comp.__esModule || comp[Symbol.toStringTag] === "Module")) { + comp = comp.default; + } + if (comp && !isObject(comp) && !isFunction(comp)) { + throw new Error(`Invalid async component load result: ${comp}`); + } + resolvedComp = comp; + return comp; + })); + }; + return defineComponent({ + name: "AsyncComponentWrapper", + __asyncLoader: load, + __asyncHydrate(el, instance, hydrate) { + const doHydrate = hydrateStrategy ? () => { + const teardown = hydrateStrategy( + hydrate, + (cb) => forEachElement(el, cb) + ); + if (teardown) { + (instance.bum || (instance.bum = [])).push(teardown); + } + } : hydrate; + if (resolvedComp) { + doHydrate(); + } else { + load().then(() => !instance.isUnmounted && doHydrate()); + } + }, + get __asyncResolved() { + return resolvedComp; + }, + setup() { + const instance = currentInstance; + markAsyncBoundary(instance); + if (resolvedComp) { + return () => createInnerComp(resolvedComp, instance); + } + const onError = (err) => { + pendingRequest = null; + handleError( + err, + instance, + 13, + !errorComponent + ); + }; + if (suspensible && instance.suspense || false) { + return load().then((comp) => { + return () => createInnerComp(comp, instance); + }).catch((err) => { + onError(err); + return () => errorComponent ? createVNode(errorComponent, { + error: err + }) : null; + }); + } + const loaded = ref(false); + const error = ref(); + const delayed = ref(!!delay); + if (delay) { + setTimeout(() => { + delayed.value = false; + }, delay); + } + if (timeout != null) { + setTimeout(() => { + if (!loaded.value && !error.value) { + const err = new Error( + `Async component timed out after ${timeout}ms.` + ); + onError(err); + error.value = err; + } + }, timeout); + } + load().then(() => { + loaded.value = true; + if (instance.parent && isKeepAlive(instance.parent.vnode)) { + instance.parent.update(); + } + }).catch((err) => { + onError(err); + error.value = err; + }); + return () => { + if (loaded.value && resolvedComp) { + return createInnerComp(resolvedComp, instance); + } else if (error.value && errorComponent) { + return createVNode(errorComponent, { + error: error.value + }); + } else if (loadingComponent && !delayed.value) { + return createVNode(loadingComponent); + } + }; + } + }); + } + function createInnerComp(comp, parent) { + const { ref: ref2, props, children, ce } = parent.vnode; + const vnode = createVNode(comp, props, children); + vnode.ref = ref2; + vnode.ce = ce; + delete parent.vnode.ce; + return vnode; + } + + const isKeepAlive = (vnode) => vnode.type.__isKeepAlive; + const KeepAliveImpl = { + name: `KeepAlive`, + // Marker for special handling inside the renderer. We are not using a === + // check directly on KeepAlive in the renderer, because importing it directly + // would prevent it from being tree-shaken. + __isKeepAlive: true, + props: { + include: [String, RegExp, Array], + exclude: [String, RegExp, Array], + max: [String, Number] + }, + setup(props, { slots }) { + const instance = getCurrentInstance(); + const sharedContext = instance.ctx; + const cache = /* @__PURE__ */ new Map(); + const keys = /* @__PURE__ */ new Set(); + let current = null; + { + instance.__v_cache = cache; + } + const parentSuspense = instance.suspense; + const { + renderer: { + p: patch, + m: move, + um: _unmount, + o: { createElement } + } + } = sharedContext; + const storageContainer = createElement("div"); + sharedContext.activate = (vnode, container, anchor, namespace, optimized) => { + const instance2 = vnode.component; + move(vnode, container, anchor, 0, parentSuspense); + patch( + instance2.vnode, + vnode, + container, + anchor, + instance2, + parentSuspense, + namespace, + vnode.slotScopeIds, + optimized + ); + queuePostRenderEffect(() => { + instance2.isDeactivated = false; + if (instance2.a) { + invokeArrayFns(instance2.a); + } + const vnodeHook = vnode.props && vnode.props.onVnodeMounted; + if (vnodeHook) { + invokeVNodeHook(vnodeHook, instance2.parent, vnode); + } + }, parentSuspense); + { + devtoolsComponentAdded(instance2); + } + }; + sharedContext.deactivate = (vnode) => { + const instance2 = vnode.component; + invalidateMount(instance2.m); + invalidateMount(instance2.a); + move(vnode, storageContainer, null, 1, parentSuspense); + queuePostRenderEffect(() => { + if (instance2.da) { + invokeArrayFns(instance2.da); + } + const vnodeHook = vnode.props && vnode.props.onVnodeUnmounted; + if (vnodeHook) { + invokeVNodeHook(vnodeHook, instance2.parent, vnode); + } + instance2.isDeactivated = true; + }, parentSuspense); + { + devtoolsComponentAdded(instance2); + } + }; + function unmount(vnode) { + resetShapeFlag(vnode); + _unmount(vnode, instance, parentSuspense, true); + } + function pruneCache(filter) { + cache.forEach((vnode, key) => { + const name = getComponentName(vnode.type); + if (name && !filter(name)) { + pruneCacheEntry(key); + } + }); + } + function pruneCacheEntry(key) { + const cached = cache.get(key); + if (cached && (!current || !isSameVNodeType(cached, current))) { + unmount(cached); + } else if (current) { + resetShapeFlag(current); + } + cache.delete(key); + keys.delete(key); + } + watch( + () => [props.include, props.exclude], + ([include, exclude]) => { + include && pruneCache((name) => matches(include, name)); + exclude && pruneCache((name) => !matches(exclude, name)); + }, + // prune post-render after `current` has been updated + { flush: "post", deep: true } + ); + let pendingCacheKey = null; + const cacheSubtree = () => { + if (pendingCacheKey != null) { + if (isSuspense(instance.subTree.type)) { + queuePostRenderEffect(() => { + cache.set(pendingCacheKey, getInnerChild(instance.subTree)); + }, instance.subTree.suspense); + } else { + cache.set(pendingCacheKey, getInnerChild(instance.subTree)); + } + } + }; + onMounted(cacheSubtree); + onUpdated(cacheSubtree); + onBeforeUnmount(() => { + cache.forEach((cached) => { + const { subTree, suspense } = instance; + const vnode = getInnerChild(subTree); + if (cached.type === vnode.type && cached.key === vnode.key) { + resetShapeFlag(vnode); + const da = vnode.component.da; + da && queuePostRenderEffect(da, suspense); + return; + } + unmount(cached); + }); + }); + return () => { + pendingCacheKey = null; + if (!slots.default) { + return current = null; + } + const children = slots.default(); + const rawVNode = children[0]; + if (children.length > 1) { + { + warn$1(`KeepAlive should contain exactly one component child.`); + } + current = null; + return children; + } else if (!isVNode(rawVNode) || !(rawVNode.shapeFlag & 4) && !(rawVNode.shapeFlag & 128)) { + current = null; + return rawVNode; + } + let vnode = getInnerChild(rawVNode); + if (vnode.type === Comment) { + current = null; + return vnode; + } + const comp = vnode.type; + const name = getComponentName( + isAsyncWrapper(vnode) ? vnode.type.__asyncResolved || {} : comp + ); + const { include, exclude, max } = props; + if (include && (!name || !matches(include, name)) || exclude && name && matches(exclude, name)) { + vnode.shapeFlag &= ~256; + current = vnode; + return rawVNode; + } + const key = vnode.key == null ? comp : vnode.key; + const cachedVNode = cache.get(key); + if (vnode.el) { + vnode = cloneVNode(vnode); + if (rawVNode.shapeFlag & 128) { + rawVNode.ssContent = vnode; + } + } + pendingCacheKey = key; + if (cachedVNode) { + vnode.el = cachedVNode.el; + vnode.component = cachedVNode.component; + if (vnode.transition) { + setTransitionHooks(vnode, vnode.transition); + } + vnode.shapeFlag |= 512; + keys.delete(key); + keys.add(key); + } else { + keys.add(key); + if (max && keys.size > parseInt(max, 10)) { + pruneCacheEntry(keys.values().next().value); + } + } + vnode.shapeFlag |= 256; + current = vnode; + return isSuspense(rawVNode.type) ? rawVNode : vnode; + }; + } + }; + const KeepAlive = KeepAliveImpl; + function matches(pattern, name) { + if (isArray(pattern)) { + return pattern.some((p) => matches(p, name)); + } else if (isString(pattern)) { + return pattern.split(",").includes(name); + } else if (isRegExp(pattern)) { + pattern.lastIndex = 0; + return pattern.test(name); + } + return false; + } + function onActivated(hook, target) { + registerKeepAliveHook(hook, "a", target); + } + function onDeactivated(hook, target) { + registerKeepAliveHook(hook, "da", target); + } + function registerKeepAliveHook(hook, type, target = currentInstance) { + const wrappedHook = hook.__wdc || (hook.__wdc = () => { + let current = target; + while (current) { + if (current.isDeactivated) { + return; + } + current = current.parent; + } + return hook(); + }); + injectHook(type, wrappedHook, target); + if (target) { + let current = target.parent; + while (current && current.parent) { + if (isKeepAlive(current.parent.vnode)) { + injectToKeepAliveRoot(wrappedHook, type, target, current); + } + current = current.parent; + } + } + } + function injectToKeepAliveRoot(hook, type, target, keepAliveRoot) { + const injected = injectHook( + type, + hook, + keepAliveRoot, + true + /* prepend */ + ); + onUnmounted(() => { + remove(keepAliveRoot[type], injected); + }, target); + } + function resetShapeFlag(vnode) { + vnode.shapeFlag &= ~256; + vnode.shapeFlag &= ~512; + } + function getInnerChild(vnode) { + return vnode.shapeFlag & 128 ? vnode.ssContent : vnode; + } + + function injectHook(type, hook, target = currentInstance, prepend = false) { + if (target) { + const hooks = target[type] || (target[type] = []); + const wrappedHook = hook.__weh || (hook.__weh = (...args) => { + pauseTracking(); + const reset = setCurrentInstance(target); + const res = callWithAsyncErrorHandling(hook, target, type, args); + reset(); + resetTracking(); + return res; + }); + if (prepend) { + hooks.unshift(wrappedHook); + } else { + hooks.push(wrappedHook); + } + return wrappedHook; + } else { + const apiName = toHandlerKey(ErrorTypeStrings$1[type].replace(/ hook$/, "")); + warn$1( + `${apiName} is called when there is no active component instance to be associated with. Lifecycle injection APIs can only be used during execution of setup().` + (` If you are using async setup(), make sure to register lifecycle hooks before the first await statement.` ) + ); + } + } + const createHook = (lifecycle) => (hook, target = currentInstance) => { + if (!isInSSRComponentSetup || lifecycle === "sp") { + injectHook(lifecycle, (...args) => hook(...args), target); + } + }; + const onBeforeMount = createHook("bm"); + const onMounted = createHook("m"); + const onBeforeUpdate = createHook( + "bu" + ); + const onUpdated = createHook("u"); + const onBeforeUnmount = createHook( + "bum" + ); + const onUnmounted = createHook("um"); + const onServerPrefetch = createHook( + "sp" + ); + const onRenderTriggered = createHook("rtg"); + const onRenderTracked = createHook("rtc"); + function onErrorCaptured(hook, target = currentInstance) { + injectHook("ec", hook, target); + } + + const COMPONENTS = "components"; + const DIRECTIVES = "directives"; + function resolveComponent(name, maybeSelfReference) { + return resolveAsset(COMPONENTS, name, true, maybeSelfReference) || name; + } + const NULL_DYNAMIC_COMPONENT = Symbol.for("v-ndc"); + function resolveDynamicComponent(component) { + if (isString(component)) { + return resolveAsset(COMPONENTS, component, false) || component; + } else { + return component || NULL_DYNAMIC_COMPONENT; + } + } + function resolveDirective(name) { + return resolveAsset(DIRECTIVES, name); + } + function resolveAsset(type, name, warnMissing = true, maybeSelfReference = false) { + const instance = currentRenderingInstance || currentInstance; + if (instance) { + const Component = instance.type; + if (type === COMPONENTS) { + const selfName = getComponentName( + Component, + false + ); + if (selfName && (selfName === name || selfName === camelize(name) || selfName === capitalize(camelize(name)))) { + return Component; + } + } + const res = ( + // local registration + // check instance[type] first which is resolved for options API + resolve(instance[type] || Component[type], name) || // global registration + resolve(instance.appContext[type], name) + ); + if (!res && maybeSelfReference) { + return Component; + } + if (warnMissing && !res) { + const extra = type === COMPONENTS ? ` +If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement.` : ``; + warn$1(`Failed to resolve ${type.slice(0, -1)}: ${name}${extra}`); + } + return res; + } else { + warn$1( + `resolve${capitalize(type.slice(0, -1))} can only be used in render() or setup().` + ); + } + } + function resolve(registry, name) { + return registry && (registry[name] || registry[camelize(name)] || registry[capitalize(camelize(name))]); + } + + function renderList(source, renderItem, cache, index) { + let ret; + const cached = cache && cache[index]; + const sourceIsArray = isArray(source); + if (sourceIsArray || isString(source)) { + const sourceIsReactiveArray = sourceIsArray && isReactive(source); + let needsWrap = false; + if (sourceIsReactiveArray) { + needsWrap = !isShallow(source); + source = shallowReadArray(source); + } + ret = new Array(source.length); + for (let i = 0, l = source.length; i < l; i++) { + ret[i] = renderItem( + needsWrap ? toReactive(source[i]) : source[i], + i, + void 0, + cached && cached[i] + ); + } + } else if (typeof source === "number") { + if (!Number.isInteger(source)) { + warn$1(`The v-for range expect an integer value but got ${source}.`); + } + ret = new Array(source); + for (let i = 0; i < source; i++) { + ret[i] = renderItem(i + 1, i, void 0, cached && cached[i]); + } + } else if (isObject(source)) { + if (source[Symbol.iterator]) { + ret = Array.from( + source, + (item, i) => renderItem(item, i, void 0, cached && cached[i]) + ); + } else { + const keys = Object.keys(source); + ret = new Array(keys.length); + for (let i = 0, l = keys.length; i < l; i++) { + const key = keys[i]; + ret[i] = renderItem(source[key], key, i, cached && cached[i]); + } + } + } else { + ret = []; + } + if (cache) { + cache[index] = ret; + } + return ret; + } + + function createSlots(slots, dynamicSlots) { + for (let i = 0; i < dynamicSlots.length; i++) { + const slot = dynamicSlots[i]; + if (isArray(slot)) { + for (let j = 0; j < slot.length; j++) { + slots[slot[j].name] = slot[j].fn; + } + } else if (slot) { + slots[slot.name] = slot.key ? (...args) => { + const res = slot.fn(...args); + if (res) res.key = slot.key; + return res; + } : slot.fn; + } + } + return slots; + } + + function renderSlot(slots, name, props = {}, fallback, noSlotted) { + if (currentRenderingInstance.ce || currentRenderingInstance.parent && isAsyncWrapper(currentRenderingInstance.parent) && currentRenderingInstance.parent.ce) { + if (name !== "default") props.name = name; + return openBlock(), createBlock( + Fragment, + null, + [createVNode("slot", props, fallback && fallback())], + 64 + ); + } + let slot = slots[name]; + if (slot && slot.length > 1) { + warn$1( + `SSR-optimized slot function detected in a non-SSR-optimized render function. You need to mark this component with $dynamic-slots in the parent template.` + ); + slot = () => []; + } + if (slot && slot._c) { + slot._d = false; + } + openBlock(); + const validSlotContent = slot && ensureValidVNode(slot(props)); + const slotKey = props.key || // slot content array of a dynamic conditional slot may have a branch + // key attached in the `createSlots` helper, respect that + validSlotContent && validSlotContent.key; + const rendered = createBlock( + Fragment, + { + key: (slotKey && !isSymbol(slotKey) ? slotKey : `_${name}`) + // #7256 force differentiate fallback content from actual content + (!validSlotContent && fallback ? "_fb" : "") + }, + validSlotContent || (fallback ? fallback() : []), + validSlotContent && slots._ === 1 ? 64 : -2 + ); + if (!noSlotted && rendered.scopeId) { + rendered.slotScopeIds = [rendered.scopeId + "-s"]; + } + if (slot && slot._c) { + slot._d = true; + } + return rendered; + } + function ensureValidVNode(vnodes) { + return vnodes.some((child) => { + if (!isVNode(child)) return true; + if (child.type === Comment) return false; + if (child.type === Fragment && !ensureValidVNode(child.children)) + return false; + return true; + }) ? vnodes : null; + } + + function toHandlers(obj, preserveCaseIfNecessary) { + const ret = {}; + if (!isObject(obj)) { + warn$1(`v-on with no argument expects an object value.`); + return ret; + } + for (const key in obj) { + ret[preserveCaseIfNecessary && /[A-Z]/.test(key) ? `on:${key}` : toHandlerKey(key)] = obj[key]; + } + return ret; + } + + const getPublicInstance = (i) => { + if (!i) return null; + if (isStatefulComponent(i)) return getComponentPublicInstance(i); + return getPublicInstance(i.parent); + }; + const publicPropertiesMap = ( + // Move PURE marker to new line to workaround compiler discarding it + // due to type annotation + /* @__PURE__ */ extend(/* @__PURE__ */ Object.create(null), { + $: (i) => i, + $el: (i) => i.vnode.el, + $data: (i) => i.data, + $props: (i) => shallowReadonly(i.props) , + $attrs: (i) => shallowReadonly(i.attrs) , + $slots: (i) => shallowReadonly(i.slots) , + $refs: (i) => shallowReadonly(i.refs) , + $parent: (i) => getPublicInstance(i.parent), + $root: (i) => getPublicInstance(i.root), + $host: (i) => i.ce, + $emit: (i) => i.emit, + $options: (i) => resolveMergedOptions(i) , + $forceUpdate: (i) => i.f || (i.f = () => { + queueJob(i.update); + }), + $nextTick: (i) => i.n || (i.n = nextTick.bind(i.proxy)), + $watch: (i) => instanceWatch.bind(i) + }) + ); + const isReservedPrefix = (key) => key === "_" || key === "$"; + const hasSetupBinding = (state, key) => state !== EMPTY_OBJ && !state.__isScriptSetup && hasOwn(state, key); + const PublicInstanceProxyHandlers = { + get({ _: instance }, key) { + if (key === "__v_skip") { + return true; + } + const { ctx, setupState, data, props, accessCache, type, appContext } = instance; + if (key === "__isVue") { + return true; + } + let normalizedProps; + if (key[0] !== "$") { + const n = accessCache[key]; + if (n !== void 0) { + switch (n) { + case 1 /* SETUP */: + return setupState[key]; + case 2 /* DATA */: + return data[key]; + case 4 /* CONTEXT */: + return ctx[key]; + case 3 /* PROPS */: + return props[key]; + } + } else if (hasSetupBinding(setupState, key)) { + accessCache[key] = 1 /* SETUP */; + return setupState[key]; + } else if (data !== EMPTY_OBJ && hasOwn(data, key)) { + accessCache[key] = 2 /* DATA */; + return data[key]; + } else if ( + // only cache other properties when instance has declared (thus stable) + // props + (normalizedProps = instance.propsOptions[0]) && hasOwn(normalizedProps, key) + ) { + accessCache[key] = 3 /* PROPS */; + return props[key]; + } else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) { + accessCache[key] = 4 /* CONTEXT */; + return ctx[key]; + } else if (shouldCacheAccess) { + accessCache[key] = 0 /* OTHER */; + } + } + const publicGetter = publicPropertiesMap[key]; + let cssModule, globalProperties; + if (publicGetter) { + if (key === "$attrs") { + track(instance.attrs, "get", ""); + markAttrsAccessed(); + } else if (key === "$slots") { + track(instance, "get", key); + } + return publicGetter(instance); + } else if ( + // css module (injected by vue-loader) + (cssModule = type.__cssModules) && (cssModule = cssModule[key]) + ) { + return cssModule; + } else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) { + accessCache[key] = 4 /* CONTEXT */; + return ctx[key]; + } else if ( + // global properties + globalProperties = appContext.config.globalProperties, hasOwn(globalProperties, key) + ) { + { + return globalProperties[key]; + } + } else if (currentRenderingInstance && (!isString(key) || // #1091 avoid internal isRef/isVNode checks on component instance leading + // to infinite warning loop + key.indexOf("__v") !== 0)) { + if (data !== EMPTY_OBJ && isReservedPrefix(key[0]) && hasOwn(data, key)) { + warn$1( + `Property ${JSON.stringify( + key + )} must be accessed via $data because it starts with a reserved character ("$" or "_") and is not proxied on the render context.` + ); + } else if (instance === currentRenderingInstance) { + warn$1( + `Property ${JSON.stringify(key)} was accessed during render but is not defined on instance.` + ); + } + } + }, + set({ _: instance }, key, value) { + const { data, setupState, ctx } = instance; + if (hasSetupBinding(setupState, key)) { + setupState[key] = value; + return true; + } else if (setupState.__isScriptSetup && hasOwn(setupState, key)) { + warn$1(`Cannot mutate + + + + + + + + + + + + + + {% block javascript %}{% endblock %} + + {{ assets.renderJS()|raw }} + + + \ No newline at end of file diff --git a/system/typemill/author/layouts/layoutSystem.twig b/system/typemill/author/layouts/layoutSystem.twig new file mode 100644 index 0000000..f89eebc --- /dev/null +++ b/system/typemill/author/layouts/layoutSystem.twig @@ -0,0 +1,80 @@ + + + + + {% block title %}{% endblock %} + + + + + + + + + + + + + + + + + {% block stylesheet %}{% endblock %} + + {{ assets.renderCSS() }} + + + + + {% include 'partials/symbols.twig' %} + +
          + {% include 'partials/mainNavi.twig' %} +
          + +
          + +
          + {% block content %}{% endblock %} +
          +
          + +
          + + + + + + + + + + + + + {% block javascript %}{% endblock %} + + {{ assets.renderJS() }} + + + \ No newline at end of file diff --git a/system/typemill/author/layouts/layoutSystemBlank.twig b/system/typemill/author/layouts/layoutSystemBlank.twig new file mode 100644 index 0000000..524db22 --- /dev/null +++ b/system/typemill/author/layouts/layoutSystemBlank.twig @@ -0,0 +1,88 @@ + + + + + {% block title %}{% endblock %} + + + + + + + + + + + + + + + + + {% block stylesheet %}{% endblock %} + + {{ assets.renderCSS() }} + + + + +
          + {% include 'partials/symbols.twig' %} + +
          + {% include 'partials/mainNavi.twig' %} +
          + +
          + +
          +
          +
          +
          + +
          + + + + + + + + + + + + + {% block javascript %}{% endblock %} + + {{ assets.renderJS() }} + + + + + + + + \ No newline at end of file diff --git a/system/typemill/author/partials/contentNavi.twig b/system/typemill/author/partials/contentNavi.twig new file mode 100644 index 0000000..9ef5187 --- /dev/null +++ b/system/typemill/author/partials/contentNavi.twig @@ -0,0 +1,13 @@ + + +{% block javascript %} + + + +{% endblock %} diff --git a/system/typemill/author/partials/fields.twig b/system/typemill/author/partials/fields.twig new file mode 100644 index 0000000..87240f4 --- /dev/null +++ b/system/typemill/author/partials/fields.twig @@ -0,0 +1,103 @@ +
          + + + + {% if field.type == 'image' %} +
          +
          +
          + +
          +
          +
          +
          + +
          {{ translate('Upload an image') }}
          +
          +
          + +
          + + +
          +
          + {% if errors[field.name] %} +
          {{ errors[field.name] }}
          + {% endif %} + + {% if field.description %}
          {{ translate(field.description) }}
          {% endif %} + +
          +
          + + {% else %} + + {% if field.type == 'textarea' %} + + + + {% elseif (field.type == 'paragraph') and (field.getContent() != '') %} + + {{ markdown(field.getContent()) }} + + {% elseif field.type == 'checkbox' %} + + + + {% elseif field.type == 'checkboxlist' %} + + {% set options = field.getOptions() %} + + {% for value,label in options %} + + + + {% endfor %} + + {% elseif field.type == 'select' %} + + {% set options = field.getOptions() %} + + + + {% elseif field.type == 'radio' %} + + {% set options = field.getOptions() %} + + {% for value,label in options %} + + + + {% endfor %} + + {% else %} + + + + {% endif %} + + {% if field.description %}
          {{ translate(field.description) }}
          {% endif %} + + {% if errors[field.name] %} +
          {{ errors[field.name] }}
          + {% endif %} + + {% endif %} + +
          \ No newline at end of file diff --git a/system/typemill/author/partials/flash.twig b/system/typemill/author/partials/flash.twig new file mode 100644 index 0000000..992596d --- /dev/null +++ b/system/typemill/author/partials/flash.twig @@ -0,0 +1,15 @@ +{% if flash.info %} + +
          + {{ flash.info | first }} +
          + +{% endif %} + +{% if flash.error %} + +
          + {{ flash.error | first }} +
          + +{% endif %} \ No newline at end of file diff --git a/system/typemill/author/partials/form.twig b/system/typemill/author/partials/form.twig new file mode 100644 index 0000000..88adc05 --- /dev/null +++ b/system/typemill/author/partials/form.twig @@ -0,0 +1,62 @@ +{% if recaptcha_webkey %} + + + +{% endif %} +
          + +
          + + {% for field in fields %} + + {% if field.type == 'fieldset' %} + +
          + {{ translate(field.legend) }} + {% for field in field.fields %} + + {% include '/partials/fields.twig' with {'itemName' : itemName, 'object' : object } %} + + {% endfor %} +
          + + {% else %} + + {% include '/partials/fields.twig' with {'itemName' : itemName, 'object' : object} %} + + {% endif %} + + {% endfor %} + +
          + + +
          + + {% if captchaoptions == 'standard' %} + + {{ captcha(true) }} + + {% elseif captchaoptions == 'aftererror' %} + + {{ captcha(old) }} + + {% else %} + + {{ clearcaptcha() }} + + {% endif %} + + {% if recaptcha_webkey %} +

          + {% endif %} + + + + +
          +
          \ No newline at end of file diff --git a/system/typemill/author/partials/mainNavi.twig b/system/typemill/author/partials/mainNavi.twig new file mode 100644 index 0000000..1be59b4 --- /dev/null +++ b/system/typemill/author/partials/mainNavi.twig @@ -0,0 +1,29 @@ + \ No newline at end of file diff --git a/system/typemill/author/partials/symbols.twig b/system/typemill/author/partials/symbols.twig new file mode 100644 index 0000000..54d07d7 --- /dev/null +++ b/system/typemill/author/partials/symbols.twig @@ -0,0 +1,171 @@ + + + + + {{ translate('EDITOR') }} + + + + {{ translate('ACCOUNT') }} + + + + {{ translate('SYSTEM') }} + + + + {{ translate('LOGOUT') }} + + + + {{ translate('EXTERNAL_LINK') }} + + + + {{ translate('TEXT_FILE') }} + + + + {{ translate('DELETE') }} + + + + {{ translate('ADD') }} + + + + {{ translate('CLOSE') }} + + + + {{ translate('UPLOAD') }} + + + + {{ translate('IMAGE') }} + + + + + + {{ translate('NOTICE') }} + + + + {{ translate('FILE') }} + + + + {{ translate('VIDEO') }} + + + + {{ translate('VIDEO') }} + + + + {{ translate('AUDIO') }} + + + + {{ translate('QUOTES') }} + + + + {{ translate('NUMBERED_LIST') }} + + + + {{ translate('BULLET_LIST') }} + + + + {{ translate('LINK') }} + + + + + {{ translate('BOLD') }} + + + + {{ translate('ITALIC') }} + + + + {{ translate('HORIZONTAL_LINE') }} + + + + {{ translate('TABLE') }} + + + + {{ translate('PARAGRAPH') }} + + + + {{ translate('CODE') }} + + + + + {{ translate('HEADLINE') }} + + + + {{ translate('TABLE_OF_CONTENTS') }} + + + + {{ translate('DEFINITION') }} + + + + {{ translate('CHECK') }} + + + + {{ translate('CANCEL') }} + + + + {{ translate('TRASH') }} + + + + {{ translate('INFO') }} + + + + + + {{ translate('INVISIBLE') }} + + + + + + {{ translate('SEARCH') }} + + + + {{ translate('CANCEL') }} + + + + + {{ translate('SHORTCODES') }} + + + + {{ translate('OPEN') }} + + + + {{ translate('CLOSE') }} + + + {{ assets.renderSvg() }} + + diff --git a/system/typemill/author/partials/systemNavi.twig b/system/typemill/author/partials/systemNavi.twig new file mode 100644 index 0000000..fef9b5b --- /dev/null +++ b/system/typemill/author/partials/systemNavi.twig @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/system/typemill/author/system/account.twig b/system/typemill/author/system/account.twig new file mode 100644 index 0000000..0c8f60f --- /dev/null +++ b/system/typemill/author/system/account.twig @@ -0,0 +1,21 @@ +{% extends 'layouts/layoutSystem.twig' %} +{% block title %}{{ translate('Account') }}{% endblock %} + +{% block content %} + +

          {{ translate('Account') }}

          + +
          + +{% endblock %} + +{% block javascript %} + + + + + +{% endblock %} diff --git a/system/typemill/author/system/license.twig b/system/typemill/author/system/license.twig new file mode 100644 index 0000000..f92624d --- /dev/null +++ b/system/typemill/author/system/license.twig @@ -0,0 +1,20 @@ +{% extends 'layouts/layoutSystem.twig' %} +{% block title %}{{ translate('License') }}{% endblock %} + +{% block content %} + +

          {{ translate('License') }}

          + +
          + +{% endblock %} + +{% block javascript %} + + + + +{% endblock %} \ No newline at end of file diff --git a/system/typemill/author/system/plugins.twig b/system/typemill/author/system/plugins.twig new file mode 100644 index 0000000..abb4fa7 --- /dev/null +++ b/system/typemill/author/system/plugins.twig @@ -0,0 +1,28 @@ +{% extends 'layouts/layoutSystem.twig' %} +{% block title %}{{ translate('Plugin Settings') }}{% endblock %} + +{% block content %} + +
          +

          {{ translate('Plugins') }}

          + +
          + +
          + +{% endblock %} + +{% block javascript %} + + + + + + + +{% endblock %} diff --git a/system/typemill/author/system/system.twig b/system/typemill/author/system/system.twig new file mode 100644 index 0000000..fba7824 --- /dev/null +++ b/system/typemill/author/system/system.twig @@ -0,0 +1,22 @@ +{% extends 'layouts/layoutSystem.twig' %} +{% block title %}{{ translate('System Settings') }}{% endblock %} + +{% block content %} + +

          {{ translate('System') }} version {{settings.version}}

          + +
          + +{% endblock %} + +{% block javascript %} + + + + + + +{% endblock %} diff --git a/system/typemill/author/system/themes.twig b/system/typemill/author/system/themes.twig new file mode 100644 index 0000000..189aa49 --- /dev/null +++ b/system/typemill/author/system/themes.twig @@ -0,0 +1,27 @@ +{% extends 'layouts/layoutSystem.twig' %} +{% block title %}{{ translate('Theme Settings') }}{% endblock %} + +{% block content %} + +
          +

          {{ translate('Themes') }}

          + +
          + +
          + +{% endblock %} + +{% block javascript %} + + + + + + +{% endblock %} diff --git a/system/typemill/author/system/user.twig b/system/typemill/author/system/user.twig new file mode 100644 index 0000000..889f040 --- /dev/null +++ b/system/typemill/author/system/user.twig @@ -0,0 +1,21 @@ +{% extends 'layouts/layoutSystem.twig' %} +{% block title %}{{ translate('Account') }}{% endblock %} + +{% block content %} + +

          {{ translate('User') }}

          + +
          + +{% endblock %} + +{% block javascript %} + + + + + +{% endblock %} diff --git a/system/typemill/author/system/usernew.twig b/system/typemill/author/system/usernew.twig new file mode 100644 index 0000000..42a9823 --- /dev/null +++ b/system/typemill/author/system/usernew.twig @@ -0,0 +1,21 @@ +{% extends 'layouts/layoutSystem.twig' %} +{% block title %}{{ translate('Create user') }}{% endblock %} + +{% block content %} + +

          {{ translate('Create user') }}

          + +
          + +{% endblock %} + +{% block javascript %} + + + + + +{% endblock %} diff --git a/system/typemill/author/system/users.twig b/system/typemill/author/system/users.twig new file mode 100644 index 0000000..922c8c0 --- /dev/null +++ b/system/typemill/author/system/users.twig @@ -0,0 +1,21 @@ +{% extends 'layouts/layoutSystem.twig' %} +{% block title %}{{ translate('Users') }}{% endblock %} + +{% block content %} + +

          {{ translate('Users') }}

          + +
          + +{% endblock %} + +{% block javascript %} + + + + + +{% endblock %} diff --git a/system/typemill/author/translations/de.yaml b/system/typemill/author/translations/de.yaml new file mode 100644 index 0000000..500bb4f --- /dev/null +++ b/system/typemill/author/translations/de.yaml @@ -0,0 +1,481 @@ +# German (Deutsch) +# Author: Sebastian Schürmanns +ACCESS: Zugriff +ACCESS_FOR: 'Zugang für' +ACCESS_RIGHTS: 'Zugang & Rechte' +ACCESS_TO_BASEPATH_IS_NOT_ALLOWED: 'Ein Zugriff auf den Basis-Pfad ist nicht erlaubt.' +ACCOUNT: Account +ACCOUNT_CREATED_PLEASE_LOGIN_WITH_YOUR_USERNAME_AND_PASSWORD_NOW: 'Account erstellt. Melde dich mit dem Usernamen und Passwort an.' +ACTIVATE_API_ACCESS_FOR_THIS_USER_USE_USERNAME_AND_PASSWORD_FOR_API_CALLS: 'API-Zugriff für diesen Nutzer aktivierten. Verwende den Nutzernamen und das Passwort für den API-Zugriff.' +ACTIVATE_A_PASSWORD_RECOVERY_IN_THE_LOGIN_FORM: 'Aktiviere die Passwort-Widerherstellung auf der Login-Seite.' +ACTIVATE_INDIVIDUAL_RESTRICTIONS_FOR_PAGES_IN_THE_META_TAB_OF_EACH_PAGE: 'Individuelle Einschränkungen für Seiten können im Meta-Tab jeder Seite aktiviert werden.' +ACTIVATE_THE_CACHE_FOR_TWIG_TEMPLATES: 'Cache für Twig-Templates aktivieren' +ACTIVATE_THE_DARKMODE_FOR_ME: 'Dunkles Design für mich aktivieren' +ACTIVATE_YOUR_LICENSE: 'Aktiviere deine Lizenz.' +ACTIVATION_FAILED_BECAUSE_YOU_NEED_A_VALID: 'Für die Aktivierung fehlt eine gültige ' +ACTIVE: Aktiv +ACTUAL_PASSWORD: 'Aktuelles Passwort' +ADD: erstellen +ADD_A_FILE: 'neue Seite' +ADD_A_FOLDER: 'neuer Ordner' +ADD_DEFINITION: 'neue Definition' +ADD_DESCRIPTION: 'neue Beschreibung ' +ADD_ENTRY: 'Eintrag hinzufügen' +ADD_LEFT_COLUMN: 'neue linke Spalte' +ADD_MORE_URL_SCHEMES_FOR_EXTERNAL_LINKS_E_G_LIKE_DICT_COMMA_SEPARATED_LIST: 'Mehr URL-Schemata für externe Links definieren, z.B. DICT:// (Liste mit Kommas)' +ADD_NOINDEX_TAG_AND_EXCLUDE_FROM_SITEMAP: 'No-Index-Attribut hinzufügen und von Sitemap ausschließen' +ADD_ONE_OR_MORE_USERNAMES_SEPARATED_WITH_COMMA: 'Einen oder mehrere Nutzernamen separiert mit Komma' +ADD_RIGHT_COLUMN: 'neue rechte Spalte' +ADD_ROW_ABOVE: 'neue Reihe ober' +ADD_ROW_BELOW: 'neue Reihe unten' +ALLOWED_DOMAINS_FOR_API_ACCESS_CORS_HEADERS: 'Erlaubte Domains für den API-Zugriff (CORS-Header)' +ALLOWED_DOMAINS_FOR_CONTENT_ON_TYPEMILL_CSP_HEADERS: 'Erlaubte Domains für Inhalte auf Typemill (CSP-Headers)' +ALLOW_SVG: 'SVG erlauben' +ALLOW_THE_UPLOAD_OF_SVG_IMAGES: 'Upload von SVG-Bildern erlauben.' +ALL_PAGES: 'Alle Seiten' +ALTERNATIVE_TEXT_FOR_THE_HERO_IMAGE: 'Alternativ-Text für das Hero-Image' +ALT_TEXT: Alternativ-Text +ALWAYS_SHOW: 'Immer anzeigen' +AND_YOU_CANNOT: ' und du kannst nicht ' +AND_YOU_MIGHT_FIND_THE_FOLLOWING_TIPS_HELPFUL: 'Und die folgenden Hinweise sind vielleicht hilfreich' +ANSWER_FROM_LICENSE_SERVER: 'Antwort vom Lizenz-Server' +API_ACCESS: API-Zugang +ARTICLE_DATE: Artikel-Datum +AUTHOR: Autor +AUTHOR_DESCRIPTION_MARKDOWN: 'Autoren-Beschreibung (Markdown)' +BACK_TO_LOGIN: 'Zurück zum Login' +BLOCK_ID_NOT_FOUND: 'Block-ID nicht gefunden.' +BOLD: fett +BULLET_LIST: Auflistung +BUY_A_LICENSE: 'Kauf eine Lizenz' +CANCEL: abbrechen +CAN_BE_USED_FOR_AUTHOR_LINE_IN_FRONTEND: 'Kann für die Autoren-Zeile im Frontend genutzt werden.' +CAPTION: Bild-Unterschrift +CC_BY: '' +CC_BY_NC: '' +CC_BY_NC_ND: '' +CC_BY_NC_SA: '' +CC_BY_ND: '' +CC_BY_SA: '' +CENTER: Mitte +CHANGE_SLUG: 'Slug ändern' +CHECK: prüfen +CHECK_YOUR_INBOX: 'Prüfe deinen Posteingang' +CHECK_YOUR_LICENSE: 'Prüfe deine Lizenz' +CLASS: Class +CLEAR: Löschen +CLOSE: Schließen +CLOSE_LIBRARY: 'Medialib schließen' +CODE: Code +COLLAPSE_ALL: 'Alle einklappen' +CONFIGURE: Konfigurieren +CONFIRM: Bestätige +CONTENT_BREAK: Inhalts-Trennung +CONVERT_TO_WEBP: 'Zu WEBP konvertrieren' +COPYRIGHT: Copyright +COPY_THE_CONTENT_OF_THE_REFERENCED_INTERNAL_PAGE: 'Kopiere den Inhalt der referenzierten internen Seite' +COULD_NOT_CREATE_FOLDER: 'Ordner konnte nicht erstellt werden' +COULD_NOT_DECODE_IMAGE_OR_FILE_PROBABLY_NOT_A_BASE64_ENCODING: 'Datei oder Bild konnte nicht dekodiert werden, Base64-Kodierung erforderlich' +COULD_NOT_DELETE_FILE: 'Datei konnte nicht gelöscht werden' +COULD_NOT_DELETE_FOLDER: 'Ordner konnte nicht gelöscht werden' +COULD_NOT_GET_THE_VIDEO_IMAGE: 'Video-Bild konnte nicht geladen werden' +COULD_NOT_OPEN_AND_READ_THE_FILE: 'Datei konnte nicht geöffnet und gelesen werden' +COULD_NOT_READ_PATHINFO: 'PATHINFO konnte nicht gelesen werden' +COULD_NOT_STORE_THE_CUSTOM_SIZE_OF: 'Benutzerdefinierte Größe konnte nicht gespeichert werden' +COULD_NOT_STORE_THE_IMAGE_IN_THE_TEMPORARY_FOLDER: 'Bild konnte nicht im temporären Ordner gespeichert werden' +COULD_NOT_STORE_THE_RESIZED_VERSION: 'Die verkleinerte Version konnte nicht gespeichert werden' +COULD_NOT_WRITE_TO_THE_FILE: 'Datei konnte nicht beschrieben werden' +CREATED_AT_READ_ONLY: 'Erstellt am (nur lesend)' +CREATE_NEW_PASSWORD: 'Neues Passwort erstellen' +CREATE_POST: 'Post erstellen' +CREATE_USER: '+ Nutzer' +CUSTOM_CSS: 'Benutzerdefiniertes CSS' +CUT_RESTRICTED_CONTENT_AFTER_THE_FIRST_HR_ELEMENT_ON_A_PAGE_PER_DEFAULT_CONTENT_WILL_BE_CUT_AFTER_TITLE: 'Eingeschränkte Inhalte nach dem ersten Trennstrich abschneiden (andernfalls wird der Inhalt nach dem Titel abgeschnitten)' +DARKMODE: 'Dunkles Design' +DEAR: Lieber +DEAR_USER: 'Lieber Nutzer' +DEFAULT_WIDTH_OF_LIVE_IMAGES_IS_820PX_CHANGES_WILL_APPLY_TO_FUTURE_UPLOADS: 'Standard-Breite für Live-Bilder ist 820px. Änderungen werden nur für neue Bilder angewendet.' +DEFINITION: Definitions-Liste +DEFINITION_LIST: Definitions-Liste +DELETE: löschen +DELETE_COLUMN: 'Spalte löschen' +DELETE_DESCRIPTION: 'Beschreibung löschen' +DELETE_PAGE: 'Seite löschen' +DELETE_ROW: 'Reihe löschen' +DELETE_USER: 'Nutzer löschen' +DEVELOPER: Entwickler +DISABLE: Deaktivieren +DISABLE_ALL_CSP_HEADERS_CONTENT_SECURITY_POLICY_FOR_THIS_WEBSITE: 'Deaktiviere alle CSP-Header (Content Security Policy) für diese Webseite.' +DISABLE_ALL_CUSTOM_HEADERS_OF_TYPEMILL_EXCEPT_CORS_AND_SEND_YOUR_OWN_HEADERS_INSTEAD: 'Deaktiviere alle eigenen Header von Typemill (außer CORS) um meine eigenen Header zu senden.' +DISABLE_CSP_HEADERS: 'CSP-Header deaktivieren' +DISABLE_CUSTOM_HEADERS: 'Custom Header deaktivieren' +DISCARD: Verwerfen +DISCARD_CHANGES: 'Änderungen verwerfen' +DISPLAY_APPLICATION_ERRORS: 'Zeige Fehler-Reports der Applikation' +DOES_NOT_EXIST: 'Existiert nicht' +DOES_NOT_EXIST_OR_IS_NOT_WRITABLE: 'Existiert nicht oder ist nicht beschreibbar' +DOMAIN_FOR_LICENSE: 'Domain für die LIzenz' +DONATE: Spenden +DONE: Erledigt +DO_NOT_FORGET_TO_CHECK_YOUR_SPAM_FOLDER_IF_YOUR_INBOX_IS_EMPTY: 'Vergiss nicht den Spam-Folder zu prüfen, wenn der Posteingang leer ist.' +DO_NOT_RESIZE: 'Nicht verkleinern' +DO_YOU_REALLY_WANT_TO_DELETE_THIS_PAGE: 'Soll die Seite wirklich gelöscht werden?' +DO_YOU_REALLY_WANT_TO_DELETE_THIS_USER: 'Soll der Nutzer wirklich gelöscht werden?' +DO_YOU_WANT_TO_DISCARD_YOUR_CHANGES_AND_SET_THE_CONTENT_BACK_TO_THE_LIVE_VERSION: 'Sollen die Änderungen wirklich verworfen und der Inhalt auf den Live-Zustand zurückgesetzt werden?' +DRAFT: Entwurf +DRAG_A_PICTURE_OR_CLICK_TO_SELECT: 'Klicken oder Bild hierhinziehen' +DUTCH_FLEMISH: 'Dänsch, Flämisch' +EDIT: editieren +EMAIL: E-Mail +EMAIL_ADDRESS_IN_SYSTEM_SETTINGS_IS_MISSING: 'Die E-Mail-Adresse in den System-Einstellungen fehlt.' +EMAIL_SUBJECT: 'Betreff der E-Mail' +ENGLISH: Englisch +ENTER_AN_EMAIL_ADDRESS_THAT_SENDS_THE_E_MAILS_SENDER_THE_E_MAIL_FEATURE_WILL_BE_USED_FOR_RECOVERY_AND_VERIFICATION_E_MAILS_SEND_A_TESTMAIL_TO_YOUR_USER_ACCOUNT_TO_VERIFY_THAT_YOU_RECEIVE_THE_E_MAILS: 'Gib eine E-Mail-Adresse an, die die Mails versendet (Absender). Das E-Mail-Feature wird für die Password-Wiederherstellung und die Verifizierungs-Emails genutzt. Sende eine Test-Mail um zu prüfen, ob die Mail zugestellt wird.' +ENTER_THE_EMAIL_OF_YOUR_USER_ACCOUNT_CLICK_THE_RECOVER_BUTTON_AND_CHECK_YOUR_MAILBOX_FOR_FURTHER_INSTRUCTIONS: 'Gib die E-Mail-Adresse deines Nutzer-Accounts ein, drück den Wiederherstellungs-Button und prüfe dein Postfach, um Anweisungen für die Passwort-Wiederherstellung zu bekommen.' +ENTER_THE_FULL_DOMAIN_LIKE_HTTPS_WWWW_MYWEBSITE_DE: 'Gib die volle Domain wie https://meinewebseite.de ein.' +ENTER_THE_VERIFICATION_CODE_FROM_YOUR_EMAIL: 'Gib den Verifizierungs-Code aus der E-Mail ein.' +ENTER_YOUR_E_MAIL_ADDRESS_THAT_YOU_USED_FOR_YOUR_LICENSE_PURCHASE: 'Gib die E-Mail-Adresse ein, die du beim Erverb der Lizenz verwendet hast.' +ENTER_YOUR_LICENSE_KEY_THAT_YOU_GOT_AFTER_YOUR_PURCHASE_VIA_EMAIL: 'Gib den Lizenz-Schlüssel ein, den du nach dem Kauf per E-Mail erhalten hast.' +ERROR_REPORTING: Fehler-Reports +ERROR_SENDING_EMAIL: 'Fehler beim Versenden der E-Mail' +EXPAND_ALL: 'Alle ausklappen' +EXTENSION_OR_FILENAME_ARE_MISSING: 'Datei-Endung oder Dateiname fehlen' +EXTENSION_OR_NAME_FOR_IMAGE_IS_MISSING: 'Datei-Endung oder Name für Bild fehlen' +EXTERNAL_LINK: 'externer Link' +E_MAIL: E-Mail +FAVICON: Favicon +FILE: Datei +FILENAME_IS_MISSING: 'Dateiname fehlt' +FILENAME_OR_USERROLE_IS_MISSING: 'Dateiname oder Nutzerrolle fehlen' +FILETYPE_IS_NOT_ALLOWED: 'Dateityp ist nicht erlaubt' +FILE_DELETED_SUCCESSFULLY: 'Datei wurde erfolgreich gelöscht' +FILE_HAS_BEEN_STORED: 'Datei wurde gespeichert' +FILE_IS_BIGGER_THAN_20MB: 'Datei ist größer als 20MB' +FILE_IS_EMPTY: 'Datei ist leer' +FILE_IS_MISSING: 'Datei fehlt' +FILE_NOT_FOUND: 'Datei nicht gefunden' +FILE_SAVED_SUCCESSFULLY: 'Datei erfolgreich gespeichert' +FIRST_NAME: Vorname +FOLDER: Ordner +FORGOT_PASSWORD: 'Passwort vergessen' +FORGOT_YOUR_PASSWORD: 'Passwort vergessen' +FORMAT_OPTIONS_FOR_VISUAL_EDITOR: 'Formatierungs-Optionen für den visuellen Editor' +FRENCH: French +FROM_MAIL_IS_REQUIRED_FOR_THIS_FEATURE_SEND_A_TESTMAIL_BEFORE_YOU_USE_THIS_FEATURE: 'Eine Absender-Adresse ist für das Feature erforderlich. Sende eine Test-Mail, bevor du das Feature nutzt.' +FROM_MAIL_IS_REQUIRED_FOR_THIS_FEATURE_SEND_A_TESTMAIL_BEFORE_YOU_USE_THIS_FEATURE_MAKE_SURE_YOU_HAVE_FTP_ACCESS_TO_DISABLE_THE_FEATURE_IN_SETTINGS_YAML_ON_FAILURE_THE_VERIFICATION_CODE_WILL_BE_VALID_FOR_5_MINUTES_BE_AWARE_THAT_DEVICE_FINGERPRINTS_WILL_BE_STORED_IN_THE_USER_ACCOUNTS: 'Eine Absender-Adresse ist für das Feature erforderlich. Sende eine Test-Mail, bevor du das Feature nutzt. Stelle sicher, dass du FTP-Zugriff auf die Webseite hast um das Feature im Bedarfsfall zu deaktivieren. Der Verifizierungs-Code ist 5 Minuten lang gültig. Beachte, dass Device-Fingerprints im Nutzeraccount gespeichert werden.' +GERMAN: Deutsch +GET_INSPIRED_AND_ENJOY_YOUR_WRITING: 'Lass dich inspirieren und viel Freude beim Schreiben.' +GOOGLE_SITEMAP_READONLY: 'Google Sitemap (nur lesend)' +GO_TO_LOGIN: 'Zur Login-Seite gehen' +HAS_EDIT_RIGHTS_FOR_THIS_ARTICLE: 'Hat Editier-Rechte für diesen Artikel' +HEADLINE: Überschrift +HEADLINE_ANCHORS: Überschriften-Anker +HERO_IMAGE: 'Hero Image' +HEY_WRITER_AUTHOR_EDITOR_CONTENT_GURU_OR_WEBSITE_MANAGER: 'Hallo Author, Editor, Content-Guru oder Website-Manager.' +HIDE: Verbergen +HIDE_PAGE_FROM_NAVIGATION: 'Seite in Navigation verbergen' +HOMEPAGE: Homepage +HORIZONTAL_LINE: 'Horizontale Linie' +HR: 'Horizontale Linie' +IF_NOT_FILLED_THE_DESCRIPTION_IS_EXTRACTED_FROM_CONTENT: 'Wenn nicht gefüllt, wird die Beschreibung aus dem Inhalt extrahiert.' +IF_YOUR_PROXY_DOES_NOT_WORK_TRY_TO_ADD_THE_BASE_URL_OF_YOUR_PROXY_HERE_LIKE_HTTPS_MYWEBSITE_COM: 'Falls der Proxy nicht funktioniert, kann hier eine Basis-Url des Proxis wie https://meinproxy.de eingegeben werden.' +IF_YOU_ADD_A_VALUE_FOR_THE_HEIGHT_THEN_THE_IMAGE_WILL_BE_CROPPED: 'Wenn ein Wert für die Höhe angegeben ist, wird das Bild beschnitten.' +IF_YOU_DID_NOT_MAKE_THIS_LOGIN_ATTEMPT_PLEASE_RESET_YOUR_PASSWORD_IMMEDIATELY: 'Wenn dieser Anmeldeversuch nicht von dir stammt, dann ändere sofort dein Passwort.' +IF_YOU_DID_NOT_RECEIVE_AN_EMAIL_WITH_THE_VERIFICATION_CODE_THEN_THE_USERNAME_OR_PASSWORD_YOU_ENTERED_WAS_WRONG_PLEASE_TRY_AGAIN: 'Wenn du keine E-Mail mit einem Verifizierungs-Code erhalten hast, dann war das Passwort oder der Username falsch. Bitte versuche es erneut.' +IF_YOU_UNPUBLISH_THE_PAGE_THEN_WE_WILL_DELETE_THE_PUBLISHED_VERSION_AND_KEEP_THE_MODIFIED_VERSION: 'Wenn du die Seite depublizierst, bleibt der Entwurf enthalten und die Live-Version wird gelöscht.' +IF_YOU_WANT_TO_KEEP_YOUR_CHANGES_THEN_CLICK_ON_CANCEL_AND_SAVE_YOUR_CHANGES_BEFORE_YOU_SWITCH_TO_THE_VISUAL_EDITOR: 'Wenn die Änderungen beibehalten werden sollen, dann abbrechen, speichern und wieder zum Visual Editor wechseln.' +IMAGE: Bild +IMAGENAME_IS_MISSING: 'Bild-Name fehlt' +IMAGES_WITH_THIS_EXTENSION_ARE_NOT_ALLOWED: 'Bilder mit dieser Endung sind nicht erlaubt.' +IMAGE_DELETED_SUCCESSFULLY: 'Bild wurde erfolgreich gelöscht.' +IMAGE_OR_FILENAME_IS_MISSING: 'Bild- oder Dateiname fehlen.' +IMAGE_OR_NAME_IS_MISSING: 'Bild oder Datei fehlen.' +IMAGE_SAVED_SUCCESSFULLY: 'Bild erfolgreich gespeichert' +IMAGE_URL: 'Bild URL' +IMAGE_URL_READ_ONLY: 'Bild URL (nur lesend)' +INFO: Info +INVALID_VERIFICATION_CODE_FORMAT_PLEASE_TRY_AGAIN: 'Ungültiges Format des Verifizierungs-Codes. Bitte versuch es erneut.' +INVISIBLE: Unsichtbar +IS_NOT_A_DIRECTORY: 'ist kein Verzeichnis' +IS_NOT_A_FOLDER: 'ist kein Ordner' +ITALIAN: Italienisch +ITALIC: kursiv +IT_IS_NOT_ALLOWED_TO_WRITE_INTO: 'In den folgenden Ort konnte nicht geschrieben werden' +LANGUAGE: Sprache +LANGUAGE_ATTRIBUTE_WEBSITE: 'Sprach-Attribut (Webseite)' +LANGUAGE_AUTHOR_AREA: 'Sprache (Autorenumgebung)' +LAST_MODIFIED_LIVE_READONLY: 'Zuletzt live geändert (nur lesend)' +LAST_NAME: Zuname +LEFT: Links +LICENCE_HAS_BEEN_STORED: 'Lizenz wurde gespeichert' +LICENSE: Lizenz +LICENSE_AND_YOUR_WEBSITE_MUST_RUN_UNDER_THE_DOMAIN_OF_YOUR_LICENSE: '-Lizenz und die Webseite muss unter der Domain von der Lizenz laufen.' +LICENSE_DATA_ARE_INCOMPLETE: 'Die Linzenz-Angaben sind unvollständig.' +LICENSE_DATA_MISSING: 'Lizenz-Daten fehlen' +LICENSE_REQUIRED: 'Lizenz erforderlich' +LICENSE_VALIDATION_FAILED: 'Die Validierung der Lizenzdaten schlug fehl.' +LINK: Link +LINK_TO_AN_EXTERNAL_PAGE: 'Link zu einer externen Seite' +LINK_TO_YOUTUBE: 'Link zu YouTube' +LIST_ALL_DOMAINS_SEPARATED_BY_COMMAS_TO_ALLOW_CONTENT_INTEGRATION_SUCH_AS_IFRAMES_ON_YOUR_TYPEMILL_WEBSITE_DOMAINS_WILL_BE_ADDED_TO_THE_CSP_HEADER_USUALLY_DONE_WITH_PLUGINS_AND_THEMES_BUT_ADD_MANUALLY_IF_SOMETHING_IS_BLOCKED: 'Trage eine komma-separierte Liste aller Domains ein, von denen Inhalte in Form von Iframes oder Bildern integriert werden sollen. Wird normalerweise über Plugins und Themes gemacht. Falls eine Domain geblockt wird, kann sie hier manuell eingetragen werden.' +LIST_ALL_DOMAINS_SEPARATED_BY_COMMA_THAT_SHOULD_HAVE_ACCESS_TO_THE_TYPEMILL_API_DOMAINS_WILL_BE_ADDED_TO_THE_CORS_HEADER: 'Trage eine komma-separierte Liste aller Domains ein, die Zugriff auf die API haben sollen. Die Domains werden für den CORS-Header verwendet.' +LOGIN: Anmelden +LOGIN_TO_THE_AUTHOR_AREA_OR_GO_TO_THE: 'Logge dich in die Autorenumgebung ein oder gehe zur' +LOGIN_VERIFICATION_RECOMMENDED: 'Login-Verifizierung (empfohlen)' +LOGO: Logo +MAIL_FROM_NAME_OPTIONAL: 'Absender-Name (optional)' +MAIL_FROM_REQUIRED: 'Absender-Adresse (erforderlich)' +MANUAL_DATE: 'Manuelles Datum' +MARKDOWN: Markdown +MARKDOWN_IS_MISSING: 'Markdown fehlt' +MAXIMUM_SIZE_FOR_AN_IMAGE_IS_5_MB_HERO_IMAGES_ARE_NOT_SUPPORTED_BY_ALL_THEMES: 'Maximale Größe für ein Bild ist 5 MB. Hero-Bilder werden nicht von allen Themes unterstützt.' +MAXIMUM_SIZE_FOR_FILE_UPLOADS_IN_MB: 'Maximale Größe für Dateien in MB' +MAXIMUM_SIZE_FOR_IMAGE_UPLOADS_IN_MB: 'Maximale Größe für Bilder in MB' +MAXIMUM_SIZE_OF_FILE_LINK_IS_100_CHARACTERS: '' +MAXIMUM_SIZE_OF_FILE_TEXT_IS_100_CHARACTERS: '' +MAXIMUM_SIZE_OF_IMAGE_ALT_TEXT_IS_100_CHARACTERS: 'Maximal 100 Zeichen für Alternativ-Text' +MAXIMUM_SIZE_OF_IMAGE_CAPTION_IS_140_CHARACTERS: 'Maximal 140 Zeichen für Bild-Unterschrift' +MAXIMUM_SIZE_OF_IMAGE_CLASS_IS_100_CHARACTERS: 'Maximal 100 Zeichen für Bild-CSS-Klasse' +MAXIMUM_SIZE_OF_IMAGE_ID_IS_100_CHARACTERS: 'Maximal 100 Zeichen für Bild-ID' +MAXIMUM_SIZE_OF_IMAGE_LINK_IS_100_CHARACTERS: 'Maximal 100 Zeichen für Bild-Link' +MAXIMUM_SIZE_OF_IMAGE_TITLE_IS_100_CHARACTERS: 'Maximal 100 Zeichen für Bild-Titel' +MEDIA: Medien +MEDIALIB: Medien-Bibliothek +MENU: Menü +META_CONTENT: Meta-Inhalte +META_DESCRIPTION: Meta-Beschreibung +META_TITLE: Meta-Titel +MINIMUM_USER_ROLE_TO_ACCESS_THIS_PAGE: 'Minimale Nutzerrolle für den Seiten-Zugriff' +NAVIGATION_TITLE: Navigationstitel +NEW_PASSWORD: 'Neues Passwort' +NEW_USER: 'Neuer Nutzer' +NEW_USER_CREATED: 'Neuen Nutzer erstellt' +NOINDEX: Noindex +NOTICE: Hinweis +NO_FILE_FOUND: 'Keine Datei gefunden' +NO_IMAGE_FOUND: 'Kein Bild gefunden' +NO_PROBLEM_YOU_CAN_CREATE_A_NEW_ONE_HERE: 'Kein Problem, erstelle hier ein neues.' +NUMBERED_LIST: Aufzählung +OLIST: Auflistung +ONE_OR_MORE_FILES_COULD_NOT_BE_TRANSFORMED: 'Ein oder mehrere Dateien konnten nicht umgeschrieben werden.' +ONLY_IMAGES_ARE_ALLOWED: 'Nur Bilder sind erlaubt' +ONLY_PNG_FORMAT_WILL_WORK: 'Nur PNG-Formate funktionieren' +ONLY_THE_FOLLOWING_USERS_HAVE_ACCESS: 'Nur die folgenden Nutzer haben Zugriff' +OPEN: Öffnen +OPTIONALLY_ENTER_A_NAME_FOR_THE_SENDER_ADDRESS_IF_NOT_SET_THE_FROM_ADDRESS_WILL_BE_VISIBLE: 'Gib optional einen Namen für die Absender-Adresse an. Wenn nicht gefüllt wird die Absender-Adresse angezeigt.' +OPTIONALLY_ENTER_A_REPLY_TO_ADDRESS_FOR_ANSWERS_FROM_THE_RECEIVER_IF_NOT_SET_ANSWERS_WILL_GO_TO_THE_FROM_ADDRESS: 'Gib optional eine Antwort-Adresse für Antworten des Empfängers an. Wenn nicht befüllt, werden Antworten an die Absender-Adresse geschickt.' +OWNER_USERNAME: 'Besitzer (username)' +PAGES_SORT_IN_NAVIGATION_WITH_DRAG_DROP: 'Seiten (mit Drag&Drop in der Navigation verschieben)' +PAGE_NOT_FOUND: 'Seite nicht gefunden' +PAGE_RESTRICTION: Seiten-Einschränkungen +PARAGRAPH: Absatz +PASSWORD: Passwort +PATH_IS_MISSING: 'Pfad fehlt' +PERMANENT_REDIRECT_301_THE_USER_TO_THE_REFERENCED_INTERNAL_PAGE: 'Permanente Umleitung (301) zur referenzierten Seite' +PERMISSION_DENIED: 'Erlaubnis verweigert' +PLEASE_CHECK_IF_THERE_IS_A_READABLE_FILE_PUBLIC_KEY_PEM_IN_YOUR_SETTINGS_FOLDER: 'Prüfe, ob die Datei public_key.pem im Settings-Ordner vorhanden und lesbar ist.' +PLEASE_CHECK_THE_INBOX_OF_YOUR_EMAIL_ACCOUNT_FOR_MORE_INSTRUCTIONS: 'Wir haben weitere Informationen an deine E-Mail-Adresse geschickt.' +PLEASE_CONFIRM: 'Bitte bestätigen' +PLEASE_CORRECT_YOUR_INPUT: 'Bitte korrigiere deine Eingaben.' +PLEASE_ENTER_A_VALID_EMAIL: 'Bitte trage eine gültige E-Mail-Adresse ein.' +PLEASE_LOGIN_WITH_YOUR_NEW_PASSWORD: 'Bitte logge dich mit dem neuen Passwort ein.' +PLEASE_SELECT: 'Bitte auswählen' +PLEASE_USE_THE_FOLLOWING_LINK_TO_SET_A_NEW_PASSWORD: 'Bitte folge dem Link um ein neues Passwort zu vergeben' +PLUGINS: Plugins +PLUGIN_SETTINGS: Plugin-Einstellungen +POSTS_SORTED_BY_PUBLISH_DATE_FOR_NEWS_OR_BLOGS: 'Posts (sortiert nach Publikations-Datum, für News oder Blogs)' +PROFILE_IMAGE: Profil-Bild +PROXY: Proxy +PUBLISH: Publizieren +QUOTE: Zitat +QUOTES: Zitate +RAW: raw +RAW_EDITOR: Markdown-Editor +RECOVER_PASSWORD: 'Passwort wiederherstellen' +REFERENCE: Referenz +REFERENCE_TO_PAGE: 'Referenz zur Seite' +REPEAT_PASSWORD: 'Passwort wiederholen' +REPLY_TO_OPTIONAL: 'Antwort-Adresse (optional)' +REQUIRED: erforderlich +RESTRICTION_NOTICE_USE_MARKDOWN: 'Einschränkungs-Hinweis (Markdown verwendbar)' +RIGHT: Rechts +ROLE: Rolle +RUSSIAN: Russisch +SAVE: speichern +SEARCH: Suchen +SECURITY: Sicherheit +SECURITY_LOG: Sicherheits-Logbuch +SELECT_FROM_MEDIALIB: 'Aus Medien-Bibliothek wählen' +SELECT_THE_LOWEST_USERROLE_HIGHER_ROLES_WILL_HAVE_ACCESS_TOO: 'Wähle die niedrigste Nutzerrolle. Höhere Rollen haben ebenfalls Zugriff.' +SETTINGS_HAVE_BEEN_SAVED: 'Einstellungen wurden gespeichert' +SETUP: Setup +SHORTCODE: Shortcode +SHORTCODES: Shortcodes +SHORT_TITLE_FOR_POST: 'Kurztitel für Post' +SHOW_AFTER_FIRST_WRONG_INPUT: 'Nach dem ersten Fehl-Versuch anzeigen' +SHOW_ANCHORS_NEXT_TO_HEADLINE_IN_FRONTEND: 'Anker neben Überschriften im Frontend anzeigen' +SHOW_THE_WEBSITE_ONLY_TO_AUTHENTICATED_USERS_AND_REDIRECT_ALL_OTHER_USERS_TO_THE_LOGIN_PAGE: 'Gesamte Webseite nur authentifizierten Nutzern anzeigen und alle anderen zur Login-Seite umleiten.' +SLUG: Slug +SOMEONE_TRIED_TO_LOG_IN_TO_YOUR_TYPEMILL_WEBSITE_AND_WE_WANT_TO_MAKE_SURE_IT_IS_YOU_ENTER_THE_FOLLOWING_VERIFICATION_CODE_TO_FINISH_YOUR_LOGIN_THE_CODE_WILL_BE_VALID_FOR_5_MINUTES: 'Jemand hat versucht sich bei deiner Typemill-Webseite einzuloggen und wir möchten sicherstellen, dass du es warst. Gib den folgenden Verifizierungs-Code ein, um die Anmeldung abzuschließen. Der Code ist fünf Minuten lang gültig.' +SOMETHING_WENT_WRONG_PLEASE_REFRESH_THE_PAGE_AND_CHECK_IF_ALL_FOLDERS_AND_FILES_ARE_WRITABLE: 'Etwas ist schief gegangen, bitte lade die Seite neu und prüfe, ob die Ordner und Dateien beschreibbar sind.' +SOMETHING_WENT_WRONG_THE_INPUT_IS_NOT_VALID: 'Etwas ist schief gegangen, die Eingaben waren ungültig.' +SPECIAL_CHARACTERS_ARE_NOT_ALLOWED_LENGTH_BETWEEN_1_AND_40: 'Sonderzeichen sind nicht erlaubt, Länge zwischen 1 und 40 Zeichen.' +STANDARD_EDITOR_MODE: 'Standard Editor-Modus' +STANDARD_HEIGHT_FOR_LIVE_PICTURES: 'Standard-Höhe für Live-Bilder' +STANDARD_WIDTH_FOR_LIVE_PICTURES: 'Standard-Breite für Live-Bilder' +SUBMIT_THE_URL_ABOVE_IN_GOOGLE_SEARCH_CONSOLE_TO_SUPPORT_INDEXING: 'Trag die URL oben in der Google Search Console zur Unterstützung der Indexierung ein.' +SWITCH_TO_VISUAL: 'Zum Visual Editor wechseln' +SYSTEM: System +SYSTEMCHECK: Systemprüfung +SYSTEM_SETTINGS: System-Einstellungen +TABLE: Tabelle +TABLE_OF_CONTENTS: Inhaltsverzeichnis +TAB_NOT_FOUND: 'Tab nicht gefunden' +TEMPORARY_REDIRECT_302_THE_USER_TO_THE_REFERENCED_INTERNAL_PAGE: 'Temporäre Umleitung (302) zur referenzierten internen Seite' +TESTMAIL_FROM_TYPEMILL: 'Test-Email von Typemill' +TEXT_BEFORE_RECOVER_LINK_IN_EMAIL_MESSAGE: 'Text vor dem Link in der E-Mail-Nachricht' +TEXT_FILE: Text-Datei +THEMES: Themes +THEME_SETTINGS: Theme-Einstellungen +THERE_ARE_PUBLISHED_PAGES_WITHIN_THIS_FOLDER_THE_PAGES_ARE_NOT_VISIBLE_ON_YOUR_WEBSITE_ANYMORE: 'Der Ordner enthält publizierte Seiten. Die Seiten sind im Frontend nicht mehr zugänglich.' +THERE_IS_ALREADY_A_PAGE_WITH_THAT_SLUG: 'Es gibt bereits eine Seite mit diesem Slug' +THERE_IS_ALREADY_A_PAGE_WITH_THIS_NAME_PLEASE_CHOOSE_ANOTHER_NAME: 'Es gibt bereits eine Seite mit diesem Namen. Bitte wähle einen anderen Namen.' +THERE_WAS_AN_ERROR_CHECKING_THE_LICENSE_SIGNATURE: 'Fehler beim Prüfen der Lizenz-Signatur' +THE_BEST_OPTION_IS_A_SEPARATE_PASSWORD_MANAGER_LIKE_KEEPASS_AND_OTHERS: 'Die beste Lösung ist ein separater Passwort-Manager wie KeePass und andere.' +THE_CAPTCHA_IS_WRONG_PLEASE_TRY_AGAIN: 'Falsches Captcha, bitte noch einmal versuchen' +THE_FOLDER_CONTAINS_ANOTHER_FOLDER_SO_WE_CANNOT_TRANSFORM_IT_PLEASE_MAKE_SURE_THERE_ARE_ONLY_FILES_IN_THIS_FOLDER: 'Der Ordner enthält einen Unterordner-Ordner. Stelle sicher, dass der Ordner nur Seiten enthält, sonst können die Inhalte nicht in Posts umgewandelt werden.' +THE_FOLDER_CONTAINS_PUBLISHED_PAGES_PLEASE_UNPUBLISH_OR_DELETE_THEM_FIRST: 'Der Ordner enthält publizierte Seiten. Bitte depubliziere oder Lösche die Seiten zuerst.' +THE_FOLLOWING_REQUIREMENTS_FOR_THE_INSTALLATION_ARE_MISSING: 'Die folgenden Voraussetzungen für die Installation fehlen' +THE_FROM_MAIL_IS_MISSING_OR_IT_IS_NOT_A_VALID_E_MAIL_ADDRESS: 'Die Absender-Adresse fehlt oder es ist keine gültige E-Mail-Adresse.' +THE_ID_OF_THE_CONTENT_BLOCK_IS_WRONG: 'Die ID des Inhalts-Blocks ist falsch.' +THE_LICENSE_DATA_ARE_INVALID: 'Die Lizenzdaten sind ungültig.' +THE_LINK_TO_RECOVER_THE_PASSWORD_WAS_TOO_OLD_PLEASE_CREATE_A_NEW_ONE: 'Der Link zur Passwort-Vergabe ist abgelaufen. Bitte erstelle einen neuen Link.' +THE_MAXIMUM_FILE_SIZE_MIGHT_BE_LIMITED_BY_YOUR_SERVER_SETTINGS: 'Die maximale Datei-Größe könnte durch die Server-Einstellungen begrenzt sein.' +THE_MAXIMUM_IMAGE_SIZE_MIGHT_BE_LIMITED_BY_YOUR_SERVER_SETTINGS: 'Die maximale Bild-Größe könnte durch die Server-Einstellungen begrenzt sein.' +THE_MIME_TYPE_IS_MISSING_NOT_ALLOWED_OR_DOES_NOT_FIT_TO_THE_FILE_EXTENSION: 'Der Mime-Type fehlt, ist nicht erlaubt oder passt nicht zur Datei-Endung.' +THE_PLUGIN_OR_THEMES_WAS_NOT_FOUND: 'Das Plugin oder Theme wurde nicht gefunden.' +THE_RECOVER_LINK_WILL_BE_ACTIVE_FOR_24_HOURS: 'Der Wiederherstellungs-Link ist für 24 Stunden aktiv.' +THE_REQUESTED_FILETYPE_DOES_NOT_EXIST: 'Der angeforderte Datei-Typ existiert nicht.' +THE_REQUESTED_FILE_DOES_NOT_EXIST: 'Die angeforderte Datei existiert nicht.' +THE_SUBSCRIPTION_PERIOD_HAS_NOT_BEEN_PAID_YET_AND_WE_GOT_AN_ERROR: 'Die Abo-Periode wurde noch nicht bezahlt und wir haben einen Fehler erhalten.' +THE_SUBSCRIPTION_PERIOD_HAS_NOT_BEEN_PAID_YET_WE_WILL_CHECK_IT_EVERY_60_MINUTES: 'Die Abo-Periode wurde noch nicht bezahlt. Wir prüfen die Zahlung alle 60 Minuten.' +THE_TESTMAIL_HAS_BEEN_SEND_PLEASE_CHECK_YOUR_INBOX_AND_YOUR_SPAM_FOLDER_TO_VARIFY_THAT_YOU_RECEIVED_THE_MAIL: 'Die Test-Email wurde verschickt. Prüfe deinen Eingangs- und den Spamordner um sicherzustellen, dass die E-Mails zugestellt werden.' +THE_VERIFICATION_WAS_WRONG_OR_OUTDATED_PLEASE_START_AGAIN: 'Die Verifizierung war falsch oder ist abgelaufen. Bitte starte eine neue Anmeldung.' +THE_VERSION_CHECK_FAILED_BECAUSE_OF_INVALID_PARAMETERS: 'Die Versions-Prüfung ist wegen ungültiger Parameter fehlgeschlagen.' +THE_WEBSITE_IS_RUNNING_NOT_UNDER_THE_DOMAIN_OF_YOUR_LICENSE: 'Die Webseite läuft nicht unter der Domain, die in der Lizenz angegeben wurde.' +THIS: Dieses +THIS_FIELD_IS_NOT_DEFINED: 'Dieses Feld ist nicht definiert.' +THIS_FOLDER_CONTAINS: 'Dieser Ordner enthält: ' +THIS_IS_A_TESTMAIL_FROM_TYPEMILL_AND_IF_YOU_READ_THIS_E_MAIL_THEN_EVERYTHING_WORKS_FINE: 'Dies ist eine Test-Email von Typemill und wenn dich diese Zeilen erreichen, ist alles in Ordnung.' +THIS_PAGE: 'Diese Seite' +THIS_PAGE_HAS_BEEN_MODIFIED: 'Diese Seite wurde bearbeitet' +THIS_PAGE_HAS_UNSAVED_CHANGES: 'Die Seite enthält ungespeicherte Änderungen.' +TITLE: Titel +TOC: IHV +TO_DOWNLOAD_THIS_FILE_YOU_NEED_TO_BE_AUTHENTICATED_WITH_THE_ROLE: 'Der Datei-Download ist nur mit der folgenden Rolle möglich: ' +TRACK_SPAM_AND_SUSPICIOUS_ACTIONS_IN_A_LOGFILE: 'Notiere Spam und verdächtige Aktionen in einem Logbuch' +TRASH: Papierkorb +TRUSTED_IPS_FOR_PROXIES_COMMA_SEPARATED: 'Vertrauenswürdige IPs für Proxies (mit Komma separiert)' +TRY_TO_CONVERT_UPLOADED_IMAGES_INTO_THE_WEBP_FORMAT_FOR_BETTER_PERFORMANCE: 'Versuche, für eine bessere Performance die hochgelandenen Bilder in das WebP-Format zu konvertieren.' +TWIG_CACHE: 'Twig Cache' +TYPE_OF_REFERENCE: 'Art der Referenz' +ULIST: Auflistung +UNPUBLISH: Depubl. +UNPUBLISH_PAGE: 'Seite depublizieren' +UNSAVED_CHANGES: 'Ungespeicherte Änderungen' +UPLOAD: hochladen +UPLOAD_AN_IMAGE: 'Bild hochladen' +UPLOAD_FILE: 'Datei hochladen' +URL_SCHEMES: URL-Schemata +USED_AS_FALLBACK_WHEN_NO_MANUAL_DATE_IS_SET: 'Wird verwendet, wenn manuelles Datum fehlt' +USED_FOR_COPYRIGHT_AND_YEAR_IN_FOOTER: 'Wird für das Copyright und das Jahr im Footer verwendet.' +USED_FOR_FRONTEND_LANGUAGE_ATTRIBUTE_PLEASE_USE_ISO_639_1_CODES_LIKE_EN: 'Wird für das Sprach-Attribut im Frontend verwendet. Bitte nutze ISO 639-1 Codes wie "en"' +USED_FOR_TRANSLATIONS_IN_AUTHOR_AREA_THEMES_AND_PLUGINS: 'Wird für die Übersetzung der Authorenoberfläche, der Plugins und der Themes verwendet.' +USER: Nutzer +USERDATA_ARE_REQUIRED: 'Nutzerdaten erforderlich' +USERNAME: Nutzername +USERNAME_IS_REQUIRED: 'Nutzername ist erforderlich' +USERNAME_READ_ONLY: 'Nutzername (nur lesend)' +USERROLE: Nutzerrolle +USERROLE_IS_REQUIRED: 'Nutzerrolle ist erforderlich' +USERROLE_IS_UNKNOWN: 'Nutzerrolle ist unbekannt' +USERS: Nutzer +USER_DELETED: 'Nutzer gelöscht' +USER_HAS_BEEN_UPDATED: 'Nutzer wurde aktualisiert' +USER_NOT_FOUND: 'Nutzer nicht gefunden' +USE_A_STRONG_AND_INDIVIDUAL_PASSWORD_FOR_EVERY_ACCOUNT: 'Verwende ein starkes und individuelles Passwort für jeden Account' +USE_CAPTCHA_IN_AUTHENTICATION_FORMS: 'Aktiviere Captcha-Prüfungen bei Authentifizierungen.' +USE_X_FORWARDED_HEADER: 'Nutze X-Forwarded-Header' +VERIFICATION_CODE_MISSING: 'Fehlt der Verifizierungs-Code?' +VERIFY_YOUR_LOGIN_WITH_A_5_DIGIT_CODE_SEND_BY_EMAIL: 'Versende per E-Mail 5-stellige Codes zur Verifizierung von Anmeldungen.' +VERSION: Version +VIDEO: Video +VISIBILITY: Sichtbarkeit +VISIT_THE_PLUGIN_DIRECTORY: 'Zum Plugin-Verzeichnis' +VISIT_THE_THEME_DIRECTORY: 'Zum Theme-Verzeichnis' +VISUAL: visuell +VISUAL_EDITOR: 'Visual Editor' +WEBSITE_OWNER: Webseiten-Besitzer +WEBSITE_RESTRICTION: 'Website Beschränkungen' +WEBSITE_TITLE: Webseiten-Titel +WELCOME_BACK: Willkommen! +WELCOME_TO_TYPEMILL: 'Willkommen bei Typemill' +WE_CANNOT_CREATE_A_FOLDER_ONLY_FILES: 'Es können keine Ordner erstellt werden, nur Dateien.' +WE_COULD_NOT_CREATE_A_SANITIZED_VERSION_OF_THE_SVG_IT_PROBABLY_HAS_INVALID_CONTENT: 'Wir konnten die SVG-Datei nicht säubern, sie enthält vermutlich ungültige Inhalte.' +WE_COULD_NOT_CREATE_THE_FILE_PLEASE_REFRESH_THE_PAGE_AND_CHECK_IF_ALL_FOLDERS_AND_FILES_ARE_WRITABLE: 'Wir konnten keine Datei erstellen. Bitte lade die Seite neu und stelle sicher, dass die Ordner und Dateien beschreibbar sind.' +WE_COULD_NOT_CREATE_THE_FOLDER_PLEASE_REFRESH_THE_PAGE_AND_CHECK_IF_ALL_FOLDERS_AND_FILES_ARE_WRITABLE: 'Wir konnten keinen Ordner erstellen. Bitte lade die Seite neu und stelle sicher, dass die Ordner und Dateien beschreibbar sind.' +WE_COULD_NOT_CREATE_THE_USER_PLEASE_CHECK_IF_THE_SETTINGS_FOLDE_IS_WRITABLE: 'Wir konnten keinen Nutzer erstellen, bitte prüfe, ob der Settings-Ordner beschreibbar ist.' +WE_COULD_NOT_DELETE_A_CUSTOM_IMAGE_GRAYSCALE_OR_RESIZED: 'Wir konnten das benutzerdefinierte Bild nicht löschen (Graustufen oder benutzerdefinierte Größe)' +WE_COULD_NOT_DELETE_THE_LIVE_IMAGE: 'Wir konnten das Live-Bild nicht löschen' +WE_COULD_NOT_DELETE_THE_ORIGINAL_IMAGE_IN: 'Wir konnten das Original-Bild nicht löschen in' +WE_COULD_NOT_DELETE_THE_THUMB_IMAGE: 'Wir konnten das Vorschaubild nicht löschen.' +WE_COULD_NOT_DELETE_THE_USER: 'Wir konnten den Nutzer nicht löschen.' +WE_COULD_NOT_FIND_A_FOLDERPATH_FOR: 'Wir konnten den Ordnerpfad nicht finden für' +WE_COULD_NOT_FIND_OR_READ_THE_PUBLIC_KEY_PEM_IN_THE_SETTINGS_FOLDER: 'Wir konnten die Datei public_key_pem im Settings-Folder nicht finden oder lesen.' +WE_COULD_NOT_FIND_THE_USER: 'Wir konnten den Nutzer nicht finden' +WE_COULD_NOT_FIND_THIS_PAGE_PLEASE_REFRESH_AND_TRY_AGAIN: 'Wir konnten die Seite nicht finden. Bitte versuche es noch einmal und lade die Seite neu.' +WE_COULD_NOT_LOAD_THE_SVG_FILE_IT_IS_PROBABLY_CORRUPTED: 'Wir konnten die SVG-Datei nicht laden, vermutlich ist sie nicht gültig.' +WE_COULD_NOT_SEND_THE_EMAIL_WITH_THE_PASSWORD_INSTRUCTIONS_TO_YOUR_ADDRESS_REASON: 'Wir konnten die E-Mail mit den Password-Instruktionen nicht an deine Adresse senden. Grund: ' +WE_COULD_NOT_SEND_THE_EMAIL_WITH_THE_VERIFICATION_CODE_TO_YOUR_ADDRESS_REASON: 'Wir konnten den Verifieriungs-Code nicht an deine E-Mail-Adresse senden. Grund: ' +WE_COULD_NOT_SEND_THE_TESTMAIL_TO_YOUR_E_MAIL_ADDRESS_REASON: 'Wir konnten die Test-Mail nicht an deine Adresse senden. Grund: ' +WE_COULD_NOT_STORE_FILE_TO_TEMPORARY_FOLDER: 'Wir konnten die Datei nicht in dem temporären Ordner speichern.' +WE_COULD_NOT_STORE_THE_CONTENT: 'Wir konnten die Inhalte nicht speichern' +WE_COULD_NOT_STORE_THE_LIVE_IMAGE_TO_THE_LIVE_FOLDER: 'Wir konnten das Live-Bild nicht im LIve-Ordner speichern.' +WE_COULD_NOT_STORE_THE_NEW_USER: 'Wir konnten den neuen Nutzer nicht speichern.' +WE_COULD_NOT_STORE_THE_ORIGINAL_IMAGE: 'Wir konnten das Original-Bild nicht speichern.' +WE_COULD_NOT_STORE_THE_THUMB_TO_THE_THUMB_FOLDER: 'Wir konnten das Vorschau-Bild nicht im Vorschau-Ordner speichern.' +WE_DID_NOT_FIND_A_FILE_WITH_THAT_NAME: 'Wir haben keine Datei mit diesem Namen gefunden.' +WE_DID_NOT_FIND_THE_A_USER_OR_USERMAIL: 'Wir haben den Nutzer oder die Nutzermail nicht gefunden.' +WE_DID_NOT_FIND_THE_FILE_IN_THE_TMP_FOLDER_OR_COULD_NOT_READ_IT: 'Wir konnten die Datei nicht im temporären Ordner finden oder lesen.' +WE_DID_NOT_FIND_THE_IMAGE_IN_THE_TMP_FOLDER_OR_COULD_NOT_READ_IT: 'Wir konnten das Bild nicht im temporären Ordner finden oder lesen.' +WE_FOUND_THE_FILE_BUT_COULD_NOT_DELETE: 'Wir haben die Datei gefunden aber konnten sie nicht löschen.' +WE_FOUND_THE_FOLDER_BUT_COULD_NOT_DELETE: 'Wir haben den Ordner gefunden aber konnten ihn nicht löschen.' +WE_FOUND_THE_FOLDER_BUT_COULD_NOT_DELETE_IT: 'Wir haben den Ordner gefunden aber konnten ihn nicht löschen.' +WE_HOPE_YOU_LIKE_TYPEMILL_BECAUSE_WE_CODED_IT_JUST_FOR_YOU: 'Wir hoffen, dir gefällt Typemill, denn es ist ganz auf dich zugeschnitten' +WIDTH_HEIGHT: Breite/Höhe +WRAP_RESTRICTION_NOTICE: 'Formatierung für Beschränkungs-Hinweis' +WRAP_THE_RESTRICTION_NOTICE_ABOVE_INTO_A_NOTICE_4_ELEMENT_WHICH_CAN_BE_DESIGNED_AS_SPECIAL_BOX: 'Formatiere den Beschränkungshinweis von oben als Notiz der vierten Ebene (Darstellung abhängig vom Theme)' +WRITING: Schreiben +WRONG_PASSWORD_OR_USERNAME_PLEASE_TRY_AGAIN: 'Falsches Passwort oder Nutzername. Bitte versuche es erneut.' +YEAR: Jahr +YOUR_ARE_AN: 'Du bist ein' +YOUR_BROWSER_CAN_REMEMBER_ALL_OF_YOUR_PASSWORDS: 'Dein Browser kann sich alle deine Passwörter merken.' +YOUR_EMAIL: 'Deine E-Mail' +YOUR_LICENSE_KEY: 'Dein Lizenz-Schlüssel' +YOUR_REGISTRATION_IS_NOT_CONFIRMED_YET_PLEASE_CHECK_YOUR_E_MAILS_AND_USE_THE_CONFIRMATION_LINK: 'Deine Registrierung ist noch nicht bestätigt. Bitte prüfe deine E-Mails und nutze den Bestätigungs-Link.' +YOUR_TYPEMILL_VERIFICATION_CODE: 'Dein Verifizierungs-Code für Typemill' +YOU_ARE_NOT_ALLOWED_TO_DELETE_ANOTHER_USER: 'Du kannst keinen anderen Nutzer löschen.' +YOU_ARE_NOT_ALLOWED_TO_UPDATE_ANOTHER_USER: 'Du kannst keinen anderen Nutzer aktualisieren.' +YOU_CANNOT_CREATE_AN_ITEM_WITH_THE_SLUG_TM_IN_THE_ROOT_FOLDER_BECAUSE_THIS_IS_THE_SYSTEM_PATH: '' +YOU_CAN_OVERWRITE_THE_THEME_CSS_WITH_YOUR_OWN_CSS_HERE: 'Hier kannst du das Theme-CSS mit eigenen CSS-Regeln überschreiben.' +YOU_CAN_USE_THE_ASTERISK_WILDCARD_TO_SEARCH_FOR_NAME@_OR_@DOMAIN_COM: 'Du kannst den Stern (*) als Wildcard für die Suche nach Name@* oder *@domain.com nutzen.' +YOU_DO_NOT_HAVE_ENOUGH_RIGHTS: 'Du hast nicht genug Rechte.' +YOU_TRIED_TO_OPEN_THE_PASSWORD_RESET_PAGE_BUT_THE_LINK_WAS_INVALID: 'Du wolltest die Seite zum Zurücksetzen des Passworts öffnen aber der Link war ungültig.' +YOU_TRIED_TO_SET_A_NEW_PASSWORD_BUT_USERNAME_OR_TOKEN_WAS_INVALID: 'Du wolltest ein neues Passwort setzen aber der Nutzername oder der Token waren ungültig.' +©: '' +LICENSE_DATA_INCOMPLETE: 'Lizenz-Daten sind unvollständig' +LICENSE_DATA_INVALID: 'Lizenz-Daten sind ungültig' +NO_LICENSE_FOUND: 'Keine Lizenz gefunden' +THE_LICENSE_SERVER_RESPONDED_WITH: 'Der Lizenz-Server hat geantwortet mit: ' +THE_SUBSCRIPTION_PERIOD_IS_NOT_PAID_YET: 'Die Abo-Periode ist noch nicht bezahlt.' +VALIDATION_FAILED: 'Validierung fehlgeschlagen' diff --git a/system/typemill/author/translations/en.yaml b/system/typemill/author/translations/en.yaml new file mode 100644 index 0000000..f7766e7 --- /dev/null +++ b/system/typemill/author/translations/en.yaml @@ -0,0 +1,481 @@ +# English +# Author: Sebastian Schuermanns +ACCESS: Access +ACCESS_FOR: 'Access for' +ACCESS_RIGHTS: 'Access rights' +ACCESS_TO_BASEPATH_IS_NOT_ALLOWED: 'Access to basepath is not allowed' +ACCOUNT: Account +ACCOUNT_CREATED_PLEASE_LOGIN_WITH_YOUR_USERNAME_AND_PASSWORD_NOW: 'Account created. Please login with your username and password now.' +ACTIVATE_API_ACCESS_FOR_THIS_USER_USE_USERNAME_AND_PASSWORD_FOR_API_CALLS: 'Activate API access for this user. Use username and password for api calls.' +ACTIVATE_A_PASSWORD_RECOVERY_IN_THE_LOGIN_FORM: 'Activate a password recovery in the login form.' +ACTIVATE_INDIVIDUAL_RESTRICTIONS_FOR_PAGES_IN_THE_META_TAB_OF_EACH_PAGE: 'Activate individual restrictions for pages in the meta-tab of each page.' +ACTIVATE_THE_CACHE_FOR_TWIG_TEMPLATES: 'Activate the cache for twig-templates.' +ACTIVATE_THE_DARKMODE_FOR_ME: 'Activate the darkmode for me.' +ACTIVATE_YOUR_LICENSE: 'Activate your license.' +ACTIVATION_FAILED_BECAUSE_YOU_NEED_A_VALID: 'Activation failed because you need a valid ' +ACTIVE: Active +ACTUAL_PASSWORD: 'Actual Password' +ADD: Add +ADD_A_FILE: '' +ADD_A_FOLDER: '' +ADD_DEFINITION: 'Add definition' +ADD_DESCRIPTION: '' +ADD_ENTRY: 'Add entry' +ADD_LEFT_COLUMN: 'Add left column' +ADD_MORE_URL_SCHEMES_FOR_EXTERNAL_LINKS_E_G_LIKE_DICT_COMMA_SEPARATED_LIST: 'Add more url schemes for external links e.g. like dict:// (comma separated list)' +ADD_NOINDEX_TAG_AND_EXCLUDE_FROM_SITEMAP: 'Add noindex tag and exclude from sitemap.' +ADD_ONE_OR_MORE_USERNAMES_SEPARATED_WITH_COMMA: 'Add one or more usernames separated with comma.' +ADD_RIGHT_COLUMN: 'Add right column' +ADD_ROW_ABOVE: 'Add row above' +ADD_ROW_BELOW: 'Add row below' +ALLOWED_DOMAINS_FOR_API_ACCESS_CORS_HEADERS: 'Allowed domains for API access (CORS HEADERS)' +ALLOWED_DOMAINS_FOR_CONTENT_ON_TYPEMILL_CSP_HEADERS: 'Allowed domains for content integration on Typemill (CSP HEADERS)' +ALLOW_SVG: 'Allow svg' +ALLOW_THE_UPLOAD_OF_SVG_IMAGES: 'Allow the upload of svg-images.' +ALL_PAGES: 'All pages' +ALTERNATIVE_TEXT_FOR_THE_HERO_IMAGE: 'Alternative text for the hero image' +ALT_TEXT: Alt-text +ALWAYS_SHOW: 'Always show' +AND_YOU_CANNOT: ' and you cannot ' +AND_YOU_MIGHT_FIND_THE_FOLLOWING_TIPS_HELPFUL: ' and you might find the following tips helpful:' +ANSWER_FROM_LICENSE_SERVER: '' +API_ACCESS: 'API access' +ARTICLE_DATE: 'Article date' +AUTHOR: Author +AUTHOR_DESCRIPTION_MARKDOWN: 'Author-description (markdown)' +BACK_TO_LOGIN: 'Back to login' +BLOCK_ID_NOT_FOUND: 'Block-id not found.' +BOLD: Bold +BULLET_LIST: 'Bullet list' +BUY_A_LICENSE: 'Buy a license' +CANCEL: Cancel +CAN_BE_USED_FOR_AUTHOR_LINE_IN_FRONTEND: 'Can be used for author line in frontend.' +CAPTION: Caption +CC_BY: 'CC BY' +CC_BY_NC: 'CC BY-NC' +CC_BY_NC_ND: 'CC BY-NC-ND' +CC_BY_NC_SA: 'CC BY-NC-SA' +CC_BY_ND: 'CC BY-ND' +CC_BY_SA: 'CC BY-SA' +CENTER: Center +CHANGE_SLUG: 'Change slug' +CHECK: Check +CHECK_YOUR_INBOX: 'Check your inbox.' +CHECK_YOUR_LICENSE: 'Check your license' +CLASS: Class +CLEAR: Clear +CLOSE: Close +CLOSE_LIBRARY: 'Close library' +CODE: Code +COLLAPSE_ALL: 'Collapse all' +CONFIGURE: Configure +CONFIRM: Confirm +CONTENT_BREAK: 'Content break' +CONVERT_TO_WEBP: 'Convert to webp' +COPYRIGHT: Copyright +COPY_THE_CONTENT_OF_THE_REFERENCED_INTERNAL_PAGE: 'Copy the content of the referenced internal page.' +COULD_NOT_CREATE_FOLDER: 'Could not create folder' +COULD_NOT_DECODE_IMAGE_OR_FILE_PROBABLY_NOT_A_BASE64_ENCODING: 'Could not decode image or file, probably not a base64 encoding' +COULD_NOT_DELETE_FILE: 'Could not delete file' +COULD_NOT_DELETE_FOLDER: 'Could not delete folder' +COULD_NOT_GET_THE_VIDEO_IMAGE: 'Could not get the video image' +COULD_NOT_OPEN_AND_READ_THE_FILE: 'Could not open and read the file' +COULD_NOT_READ_PATHINFO: 'Could not read pathinfo' +COULD_NOT_STORE_THE_CUSTOM_SIZE_OF: 'Could not store the custom size of' +COULD_NOT_STORE_THE_IMAGE_IN_THE_TEMPORARY_FOLDER: 'Could not store the image in the temporary folder' +COULD_NOT_STORE_THE_RESIZED_VERSION: 'Could not store the resized version' +COULD_NOT_WRITE_TO_THE_FILE: 'Could not write to the file' +CREATED_AT_READ_ONLY: 'Created at (readonly)' +CREATE_NEW_PASSWORD: 'Create new password' +CREATE_POST: 'Create post' +CREATE_USER: 'Create user' +CUSTOM_CSS: 'Custom CSS' +CUT_RESTRICTED_CONTENT_AFTER_THE_FIRST_HR_ELEMENT_ON_A_PAGE_PER_DEFAULT_CONTENT_WILL_BE_CUT_AFTER_TITLE: 'Cut restricted content after the first hr-element on a page (per default content will be cut after title).' +DARKMODE: Darkmode +DEAR: Dear +DEAR_USER: 'Dear user' +DEFAULT_WIDTH_OF_LIVE_IMAGES_IS_820PX_CHANGES_WILL_APPLY_TO_FUTURE_UPLOADS: 'Default width of live images is 820px, changes will apply to future uploads' +DEFINITION: Definition +DEFINITION_LIST: 'Definition list' +DELETE: Delete +DELETE_COLUMN: 'Delete column' +DELETE_DESCRIPTION: '' +DELETE_PAGE: 'Delete page' +DELETE_ROW: 'Delete row' +DELETE_USER: 'Delete user' +DEVELOPER: Developer +DISABLE: Disable +DISABLE_ALL_CSP_HEADERS_CONTENT_SECURITY_POLICY_FOR_THIS_WEBSITE: 'Disable all CSP headers (Content Security Policy) for this website' +DISABLE_ALL_CUSTOM_HEADERS_OF_TYPEMILL_EXCEPT_CORS_AND_SEND_YOUR_OWN_HEADERS_INSTEAD: 'Disable all custom headers of Typemill except CORS and send your own headers instead' +DISABLE_CSP_HEADERS: 'Disable CSP headers' +DISABLE_CUSTOM_HEADERS: 'Disable custom headers' +DISCARD: Discard +DISCARD_CHANGES: 'Discard changes' +DISPLAY_APPLICATION_ERRORS: 'Display Application Errors' +DOES_NOT_EXIST: 'Does not exist' +DOES_NOT_EXIST_OR_IS_NOT_WRITABLE: 'Does not exist or is not writable' +DOMAIN_FOR_LICENSE: 'Domain for license' +DONATE: Donate +DONE: Done +DO_NOT_FORGET_TO_CHECK_YOUR_SPAM_FOLDER_IF_YOUR_INBOX_IS_EMPTY: 'Do not forget to check your spam folder if your inbox is empty' +DO_NOT_RESIZE: 'Do not resize' +DO_YOU_REALLY_WANT_TO_DELETE_THIS_PAGE: 'Do you really want to delete this page?' +DO_YOU_REALLY_WANT_TO_DELETE_THIS_USER: 'Do you really want to delete this user?' +DO_YOU_WANT_TO_DISCARD_YOUR_CHANGES_AND_SET_THE_CONTENT_BACK_TO_THE_LIVE_VERSION: 'Do you want to discard your changes and set the content back to the live version?' +DRAFT: Draft +DRAG_A_PICTURE_OR_CLICK_TO_SELECT: '' +DUTCH_FLEMISH: 'Dutch, Flemish' +EDIT: Edit +EMAIL: Email +EMAIL_ADDRESS_IN_SYSTEM_SETTINGS_IS_MISSING: 'Email address in system settings is missing' +EMAIL_SUBJECT: 'Email subject' +ENGLISH: English +ENTER_AN_EMAIL_ADDRESS_THAT_SENDS_THE_E_MAILS_SENDER_THE_E_MAIL_FEATURE_WILL_BE_USED_FOR_RECOVERY_AND_VERIFICATION_E_MAILS_SEND_A_TESTMAIL_TO_YOUR_USER_ACCOUNT_TO_VERIFY_THAT_YOU_RECEIVE_THE_E_MAILS: 'Enter an email address that sends the e-mails (sender). The e-mail feature will be used for recovery and verification e-mails. Send a testmail to your user account to verify that you receive the e-mails.' +ENTER_THE_EMAIL_OF_YOUR_USER_ACCOUNT_CLICK_THE_RECOVER_BUTTON_AND_CHECK_YOUR_MAILBOX_FOR_FURTHER_INSTRUCTIONS: 'Enter the email of your user account, click the recover button, and check your mailbox for further instructions.' +ENTER_THE_FULL_DOMAIN_LIKE_HTTPS_WWWW_MYWEBSITE_DE: 'Enter the full domain like https://www.mywebsite.de' +ENTER_THE_VERIFICATION_CODE_FROM_YOUR_EMAIL: 'Enter the verification code from your email' +ENTER_YOUR_E_MAIL_ADDRESS_THAT_YOU_USED_FOR_YOUR_LICENSE_PURCHASE: 'Enter your e-mail address that you used for your license purchase' +ENTER_YOUR_LICENSE_KEY_THAT_YOU_GOT_AFTER_YOUR_PURCHASE_VIA_EMAIL: 'Enter your license key that you got after your purchase via email' +ERROR_REPORTING: 'Error Reporting' +ERROR_SENDING_EMAIL: 'Error sending email' +EXPAND_ALL: 'Expand all' +EXTENSION_OR_FILENAME_ARE_MISSING: 'Extension or filename are missing' +EXTENSION_OR_NAME_FOR_IMAGE_IS_MISSING: 'Extension or name for image is missing' +EXTERNAL_LINK: 'External link' +E_MAIL: E-mail +FAVICON: Favicon +FILE: File +FILENAME_IS_MISSING: 'Filename is missing' +FILENAME_OR_USERROLE_IS_MISSING: 'Filename or userrole is missing' +FILETYPE_IS_NOT_ALLOWED: 'Filetype is not allowed' +FILE_DELETED_SUCCESSFULLY: 'File deleted successfully' +FILE_HAS_BEEN_STORED: 'File has been stored' +FILE_IS_BIGGER_THAN_20MB: 'File is bigger than 20MB' +FILE_IS_EMPTY: 'File is empty' +FILE_IS_MISSING: 'File is missing' +FILE_NOT_FOUND: 'File not found' +FILE_SAVED_SUCCESSFULLY: 'File saved successfully' +FIRST_NAME: 'First name' +FOLDER: Folder +FORGOT_PASSWORD: 'Forgot password' +FORGOT_YOUR_PASSWORD: 'Forgot your password' +FORMAT_OPTIONS_FOR_VISUAL_EDITOR: 'Format options for visual editor' +FRENCH: French +FROM_MAIL_IS_REQUIRED_FOR_THIS_FEATURE_SEND_A_TESTMAIL_BEFORE_YOU_USE_THIS_FEATURE: 'From mail is required for this feature. Send a testmail before you use this feature.' +FROM_MAIL_IS_REQUIRED_FOR_THIS_FEATURE_SEND_A_TESTMAIL_BEFORE_YOU_USE_THIS_FEATURE_MAKE_SURE_YOU_HAVE_FTP_ACCESS_TO_DISABLE_THE_FEATURE_IN_SETTINGS_YAML_ON_FAILURE_THE_VERIFICATION_CODE_WILL_BE_VALID_FOR_5_MINUTES_BE_AWARE_THAT_DEVICE_FINGERPRINTS_WILL_BE_STORED_IN_THE_USER_ACCOUNTS: 'From mail is required for this feature. Send a testmail before you use this feature. Make sure you have FTP access to disable the feature in settings.yaml on failure. The verification code will be valid for 5 minutes. Be aware that device fingerprints will be stored in the user accounts.' +GERMAN: German +GET_INSPIRED_AND_ENJOY_YOUR_WRITING: 'Get inspired and enjoy your writing' +GOOGLE_SITEMAP_READONLY: 'Google sitemap (readonly)' +GO_TO_LOGIN: 'Go to login' +HAS_EDIT_RIGHTS_FOR_THIS_ARTICLE: 'Has edit rights for this article.' +HEADLINE: Headline +HEADLINE_ANCHORS: 'Headline anchors' +HERO_IMAGE: 'Hero image' +HEY_WRITER_AUTHOR_EDITOR_CONTENT_GURU_OR_WEBSITE_MANAGER: 'Hey writer, author, editor, content guru, or website manager' +HIDE: Hide +HIDE_PAGE_FROM_NAVIGATION: 'Hide page from navigation' +HOMEPAGE: Homepage +HORIZONTAL_LINE: 'Horizontal Line' +HR: hr +IF_NOT_FILLED_THE_DESCRIPTION_IS_EXTRACTED_FROM_CONTENT: 'If not filled, the description is extracted from content.' +IF_YOUR_PROXY_DOES_NOT_WORK_TRY_TO_ADD_THE_BASE_URL_OF_YOUR_PROXY_HERE_LIKE_HTTPS_MYWEBSITE_COM: '' +IF_YOU_ADD_A_VALUE_FOR_THE_HEIGHT_THEN_THE_IMAGE_WILL_BE_CROPPED: 'If you add a value for the height, then the image will be cropped.' +IF_YOU_DID_NOT_MAKE_THIS_LOGIN_ATTEMPT_PLEASE_RESET_YOUR_PASSWORD_IMMEDIATELY: 'If you did not make this login attempt, please reset your password immediately.' +IF_YOU_DID_NOT_RECEIVE_AN_EMAIL_WITH_THE_VERIFICATION_CODE_THEN_THE_USERNAME_OR_PASSWORD_YOU_ENTERED_WAS_WRONG_PLEASE_TRY_AGAIN: 'If you did not receive an email with the verification code, then the username or password you entered was wrong. Please try again.' +IF_YOU_UNPUBLISH_THE_PAGE_THEN_WE_WILL_DELETE_THE_PUBLISHED_VERSION_AND_KEEP_THE_MODIFIED_VERSION: 'If you unpublish the page, then we will delete the published version and keep the modified version.' +IF_YOU_WANT_TO_KEEP_YOUR_CHANGES_THEN_CLICK_ON_CANCEL_AND_SAVE_YOUR_CHANGES_BEFORE_YOU_SWITCH_TO_THE_VISUAL_EDITOR: '' +IMAGE: Image +IMAGENAME_IS_MISSING: 'Imagename is missing' +IMAGES_WITH_THIS_EXTENSION_ARE_NOT_ALLOWED: 'Images with this extension are not allowed' +IMAGE_DELETED_SUCCESSFULLY: 'Image deleted successfully' +IMAGE_OR_FILENAME_IS_MISSING: 'Image or filename is missing' +IMAGE_OR_NAME_IS_MISSING: 'Image or name is missing' +IMAGE_SAVED_SUCCESSFULLY: 'Image saved successfully' +IMAGE_URL: 'Image URL' +IMAGE_URL_READ_ONLY: 'Image URL (read only)' +INFO: Info +INVALID_VERIFICATION_CODE_FORMAT_PLEASE_TRY_AGAIN: 'Invalid verification code format, please try again' +INVISIBLE: Invisible +IS_NOT_A_DIRECTORY: 'Is not a directory' +IS_NOT_A_FOLDER: 'Is not a folder' +ITALIAN: Italian +ITALIC: Italic +IT_IS_NOT_ALLOWED_TO_WRITE_INTO: 'It is not allowed to write into' +LANGUAGE: Language +LANGUAGE_ATTRIBUTE_WEBSITE: 'Language attribute (website)' +LANGUAGE_AUTHOR_AREA: 'Language (author area)' +LAST_MODIFIED_LIVE_READONLY: 'Last modified live (readonly)' +LAST_NAME: 'Last Name' +LEFT: Left +LICENCE_HAS_BEEN_STORED: 'Licence has been stored' +LICENSE: License +LICENSE_AND_YOUR_WEBSITE_MUST_RUN_UNDER_THE_DOMAIN_OF_YOUR_LICENSE: 'License and your website must run under the domain of your license' +LICENSE_DATA_ARE_INCOMPLETE: '' +LICENSE_DATA_MISSING: 'License data missing' +LICENSE_REQUIRED: 'License required' +LICENSE_VALIDATION_FAILED: '' +LINK: Link +LINK_TO_AN_EXTERNAL_PAGE: 'Link to an external page' +LINK_TO_YOUTUBE: 'Link to YouTube' +LIST_ALL_DOMAINS_SEPARATED_BY_COMMAS_TO_ALLOW_CONTENT_INTEGRATION_SUCH_AS_IFRAMES_ON_YOUR_TYPEMILL_WEBSITE_DOMAINS_WILL_BE_ADDED_TO_THE_CSP_HEADER_USUALLY_DONE_WITH_PLUGINS_AND_THEMES_BUT_ADD_MANUALLY_IF_SOMETHING_IS_BLOCKED: 'List all domains separated by commas to allow content integration such as iframes on your Typemill website. Domains will be added to the CSP header, usually done with plugins and themes, but add manually if something is blocked.' +LIST_ALL_DOMAINS_SEPARATED_BY_COMMA_THAT_SHOULD_HAVE_ACCESS_TO_THE_TYPEMILL_API_DOMAINS_WILL_BE_ADDED_TO_THE_CORS_HEADER: 'List all domains separated by comma that should have access to the Typemill API. Domains will be added to the CORS header.' +LOGIN: Login +LOGIN_TO_THE_AUTHOR_AREA_OR_GO_TO_THE: 'Login to the author area or go to the' +LOGIN_VERIFICATION_RECOMMENDED: 'Login verification recommended' +LOGO: Logo +MAIL_FROM_NAME_OPTIONAL: 'Mail from name (optional)' +MAIL_FROM_REQUIRED: 'Mail from required' +MANUAL_DATE: 'Manual date' +MARKDOWN: Markdown +MARKDOWN_IS_MISSING: 'Markdown is missing' +MAXIMUM_SIZE_FOR_AN_IMAGE_IS_5_MB_HERO_IMAGES_ARE_NOT_SUPPORTED_BY_ALL_THEMES: 'Maximum size for an image is 5 MB. Hero images are not supported by all themes.' +MAXIMUM_SIZE_FOR_FILE_UPLOADS_IN_MB: 'Maximum size for file uploads in MB' +MAXIMUM_SIZE_FOR_IMAGE_UPLOADS_IN_MB: 'Maximum size for image uploads in MB' +MAXIMUM_SIZE_OF_FILE_LINK_IS_100_CHARACTERS: '' +MAXIMUM_SIZE_OF_FILE_TEXT_IS_100_CHARACTERS: '' +MAXIMUM_SIZE_OF_IMAGE_ALT_TEXT_IS_100_CHARACTERS: 'Maximum size of image alt text is 100 characters' +MAXIMUM_SIZE_OF_IMAGE_CAPTION_IS_140_CHARACTERS: 'Maximum size of image caption is 140 characters' +MAXIMUM_SIZE_OF_IMAGE_CLASS_IS_100_CHARACTERS: 'Maximum size of image class is 100 characters' +MAXIMUM_SIZE_OF_IMAGE_ID_IS_100_CHARACTERS: 'Maximum size of image ID is 100 characters' +MAXIMUM_SIZE_OF_IMAGE_LINK_IS_100_CHARACTERS: 'Maximum size of image link is 100 characters' +MAXIMUM_SIZE_OF_IMAGE_TITLE_IS_100_CHARACTERS: 'Maximum size of image title is 100 characters' +MEDIA: Media +MEDIALIB: Medialib +MENU: Menu +META_CONTENT: 'Meta content' +META_DESCRIPTION: 'Meta description' +META_TITLE: 'Meta title' +MINIMUM_USER_ROLE_TO_ACCESS_THIS_PAGE: 'Minimum user role to access this page' +NAVIGATION_TITLE: 'Navigation Title' +NEW_PASSWORD: 'New Password' +NEW_USER: 'New user' +NEW_USER_CREATED: 'New user created' +NOINDEX: Noindex +NOTICE: Notice +NO_FILE_FOUND: 'No file found' +NO_IMAGE_FOUND: 'No image found' +NO_PROBLEM_YOU_CAN_CREATE_A_NEW_ONE_HERE: 'No problem, you can create a new one here' +NUMBERED_LIST: 'Numbered List' +OLIST: olist +ONE_OR_MORE_FILES_COULD_NOT_BE_TRANSFORMED: 'One or more files could not be transformed' +ONLY_IMAGES_ARE_ALLOWED: 'Only images are allowed' +ONLY_PNG_FORMAT_WILL_WORK: 'Only PNG format will work' +ONLY_THE_FOLLOWING_USERS_HAVE_ACCESS: 'Only the following users have access' +OPEN: Open +OPTIONALLY_ENTER_A_NAME_FOR_THE_SENDER_ADDRESS_IF_NOT_SET_THE_FROM_ADDRESS_WILL_BE_VISIBLE: 'Optionally enter a name for the sender address. If not set, the from address will be visible.' +OPTIONALLY_ENTER_A_REPLY_TO_ADDRESS_FOR_ANSWERS_FROM_THE_RECEIVER_IF_NOT_SET_ANSWERS_WILL_GO_TO_THE_FROM_ADDRESS: 'Optionally enter a reply-to address for answers from the receiver. If not set, answers will go to the from address.' +OWNER_USERNAME: 'Owner (username)' +PAGES_SORT_IN_NAVIGATION_WITH_DRAG_DROP: 'Pages sort in navigation with drag & drop' +PAGE_NOT_FOUND: 'Page not found' +PAGE_RESTRICTION: 'Page restriction' +PARAGRAPH: Paragraph +PASSWORD: Password +PATH_IS_MISSING: 'Path is missing' +PERMANENT_REDIRECT_301_THE_USER_TO_THE_REFERENCED_INTERNAL_PAGE: 'Permanent redirect (301) the user to the referenced internal page' +PERMISSION_DENIED: 'Permission denied' +PLEASE_CHECK_IF_THERE_IS_A_READABLE_FILE_PUBLIC_KEY_PEM_IN_YOUR_SETTINGS_FOLDER: 'Please check if there is a readable file public_key.pem in your settings folder' +PLEASE_CHECK_THE_INBOX_OF_YOUR_EMAIL_ACCOUNT_FOR_MORE_INSTRUCTIONS: 'Please check the inbox of your email account for more instructions' +PLEASE_CONFIRM: 'Please confirm' +PLEASE_CORRECT_YOUR_INPUT: 'Please correct your input' +PLEASE_ENTER_A_VALID_EMAIL: 'Please enter a valid email' +PLEASE_LOGIN_WITH_YOUR_NEW_PASSWORD: 'Please login with your new password' +PLEASE_SELECT: 'Please select' +PLEASE_USE_THE_FOLLOWING_LINK_TO_SET_A_NEW_PASSWORD: 'Please use the following link to set a new password' +PLUGINS: Plugins +PLUGIN_SETTINGS: 'Plugin settings' +POSTS_SORTED_BY_PUBLISH_DATE_FOR_NEWS_OR_BLOGS: 'Posts sorted by publish date for news or blogs' +PROFILE_IMAGE: 'Profile Image' +PROXY: Proxy +PUBLISH: Publish +QUOTE: Quote +QUOTES: Quotes +RAW: Raw +RAW_EDITOR: 'Raw editor' +RECOVER_PASSWORD: 'Recover password' +REFERENCE: Reference +REFERENCE_TO_PAGE: 'Reference to page' +REPEAT_PASSWORD: 'Repeat password' +REPLY_TO_OPTIONAL: 'Reply to (optional)' +REQUIRED: Required +RESTRICTION_NOTICE_USE_MARKDOWN: 'Restriction notice (use markdown)' +RIGHT: Right +ROLE: Role +RUSSIAN: Russian +SAVE: Save +SEARCH: Search +SECURITY: Security +SECURITY_LOG: 'Security log' +SELECT_FROM_MEDIALIB: 'Select from medialib' +SELECT_THE_LOWEST_USERROLE_HIGHER_ROLES_WILL_HAVE_ACCESS_TOO: 'Select the lowest userrole. Higher roles will have access too.' +SETTINGS_HAVE_BEEN_SAVED: 'Settings have been saved' +SETUP: Setup +SHORTCODE: Shortcode +SHORTCODES: Shortcodes +SHORT_TITLE_FOR_POST: 'Short title for post' +SHOW_AFTER_FIRST_WRONG_INPUT: 'Show after first wrong input' +SHOW_ANCHORS_NEXT_TO_HEADLINE_IN_FRONTEND: 'Show anchors next to headline in frontend' +SHOW_THE_WEBSITE_ONLY_TO_AUTHENTICATED_USERS_AND_REDIRECT_ALL_OTHER_USERS_TO_THE_LOGIN_PAGE: 'Show the website only to authenticated users and redirect all other users to the login page.' +SLUG: Slug +SOMEONE_TRIED_TO_LOG_IN_TO_YOUR_TYPEMILL_WEBSITE_AND_WE_WANT_TO_MAKE_SURE_IT_IS_YOU_ENTER_THE_FOLLOWING_VERIFICATION_CODE_TO_FINISH_YOUR_LOGIN_THE_CODE_WILL_BE_VALID_FOR_5_MINUTES: 'Someone tried to log in to your Typemill website, and we want to make sure it is you. Enter the following verification code to finish your login. The code will be valid for 5 minutes.' +SOMETHING_WENT_WRONG_PLEASE_REFRESH_THE_PAGE_AND_CHECK_IF_ALL_FOLDERS_AND_FILES_ARE_WRITABLE: 'Something went wrong, please refresh the page and check if all folders and files are writable' +SOMETHING_WENT_WRONG_THE_INPUT_IS_NOT_VALID: 'Something went wrong, the input is not valid' +SPECIAL_CHARACTERS_ARE_NOT_ALLOWED_LENGTH_BETWEEN_1_AND_40: '' +STANDARD_EDITOR_MODE: 'Standard Editor Mode' +STANDARD_HEIGHT_FOR_LIVE_PICTURES: 'Standard height for live pictures' +STANDARD_WIDTH_FOR_LIVE_PICTURES: 'Standard width for live pictures' +SUBMIT_THE_URL_ABOVE_IN_GOOGLE_SEARCH_CONSOLE_TO_SUPPORT_INDEXING: 'Submit the URL above in Google Search Console to support indexing' +SWITCH_TO_VISUAL: '' +SYSTEM: System +SYSTEMCHECK: Systemcheck +SYSTEM_SETTINGS: 'System settings' +TABLE: Table +TABLE_OF_CONTENTS: 'Table of Contents' +TAB_NOT_FOUND: 'Tab not found' +TEMPORARY_REDIRECT_302_THE_USER_TO_THE_REFERENCED_INTERNAL_PAGE: 'Temporary redirect (302) the user to the referenced internal page' +TESTMAIL_FROM_TYPEMILL: 'Testmail from Typemill' +TEXT_BEFORE_RECOVER_LINK_IN_EMAIL_MESSAGE: 'Text before recover link in email message' +TEXT_FILE: 'Text file' +THEMES: Themes +THEME_SETTINGS: 'Theme settings' +THERE_ARE_PUBLISHED_PAGES_WITHIN_THIS_FOLDER_THE_PAGES_ARE_NOT_VISIBLE_ON_YOUR_WEBSITE_ANYMORE: 'There are published pages within this folder. The pages are not visible on your website anymore.' +THERE_IS_ALREADY_A_PAGE_WITH_THAT_SLUG: 'There is already a page with that slug' +THERE_IS_ALREADY_A_PAGE_WITH_THIS_NAME_PLEASE_CHOOSE_ANOTHER_NAME: 'There is already a page with this name. Please choose another name.' +THERE_WAS_AN_ERROR_CHECKING_THE_LICENSE_SIGNATURE: 'There was an error checking the license signature' +THE_BEST_OPTION_IS_A_SEPARATE_PASSWORD_MANAGER_LIKE_KEEPASS_AND_OTHERS: 'The best option is a separate password manager like KeePass and others.' +THE_CAPTCHA_IS_WRONG_PLEASE_TRY_AGAIN: 'The captcha is wrong, please try again.' +THE_FOLDER_CONTAINS_ANOTHER_FOLDER_SO_WE_CANNOT_TRANSFORM_IT_PLEASE_MAKE_SURE_THERE_ARE_ONLY_FILES_IN_THIS_FOLDER: 'The folder contains another folder so we cannot transform it. Please make sure there are only files in this folder.' +THE_FOLDER_CONTAINS_PUBLISHED_PAGES_PLEASE_UNPUBLISH_OR_DELETE_THEM_FIRST: 'The folder contains published pages. Please unpublish or delete them first.' +THE_FOLLOWING_REQUIREMENTS_FOR_THE_INSTALLATION_ARE_MISSING: 'The following requirements for the installation are missing:' +THE_FROM_MAIL_IS_MISSING_OR_IT_IS_NOT_A_VALID_E_MAIL_ADDRESS: 'The from mail is missing or it is not a valid e-mail address.' +THE_ID_OF_THE_CONTENT_BLOCK_IS_WRONG: 'The ID of the content block is wrong.' +THE_LICENSE_DATA_ARE_INVALID: '' +THE_LINK_TO_RECOVER_THE_PASSWORD_WAS_TOO_OLD_PLEASE_CREATE_A_NEW_ONE: 'The link to recover the password was too old. Please create a new one.' +THE_MAXIMUM_FILE_SIZE_MIGHT_BE_LIMITED_BY_YOUR_SERVER_SETTINGS: 'The maximum file size might be limited by your server settings.' +THE_MAXIMUM_IMAGE_SIZE_MIGHT_BE_LIMITED_BY_YOUR_SERVER_SETTINGS: 'The maximum image size might be limited by your server settings.' +THE_MIME_TYPE_IS_MISSING_NOT_ALLOWED_OR_DOES_NOT_FIT_TO_THE_FILE_EXTENSION: 'The MIME type is missing, not allowed, or does not fit to the file extension.' +THE_PLUGIN_OR_THEMES_WAS_NOT_FOUND: 'The plugin or themes was not found.' +THE_RECOVER_LINK_WILL_BE_ACTIVE_FOR_24_HOURS: 'The recover link will be active for 24 hours.' +THE_REQUESTED_FILETYPE_DOES_NOT_EXIST: 'The requested filetype does not exist.' +THE_REQUESTED_FILE_DOES_NOT_EXIST: 'The requested file does not exist.' +THE_SUBSCRIPTION_PERIOD_HAS_NOT_BEEN_PAID_YET_AND_WE_GOT_AN_ERROR: '' +THE_SUBSCRIPTION_PERIOD_HAS_NOT_BEEN_PAID_YET_WE_WILL_CHECK_IT_EVERY_60_MINUTES: '' +THE_TESTMAIL_HAS_BEEN_SEND_PLEASE_CHECK_YOUR_INBOX_AND_YOUR_SPAM_FOLDER_TO_VARIFY_THAT_YOU_RECEIVED_THE_MAIL: 'The testmail has been send. Please check your inbox and your spam folder to verify that you received the mail.' +THE_VERIFICATION_WAS_WRONG_OR_OUTDATED_PLEASE_START_AGAIN: 'The verification was wrong or outdated. Please start again.' +THE_VERSION_CHECK_FAILED_BECAUSE_OF_INVALID_PARAMETERS: 'The version check failed because of invalid parameters.' +THE_WEBSITE_IS_RUNNING_NOT_UNDER_THE_DOMAIN_OF_YOUR_LICENSE: '' +THIS: This +THIS_FIELD_IS_NOT_DEFINED: 'This field is not defined' +THIS_FOLDER_CONTAINS: 'This folder contains' +THIS_IS_A_TESTMAIL_FROM_TYPEMILL_AND_IF_YOU_READ_THIS_E_MAIL_THEN_EVERYTHING_WORKS_FINE: 'This is a testmail from Typemill and if you read this e-mail then everything works fine.' +THIS_PAGE: 'This page' +THIS_PAGE_HAS_BEEN_MODIFIED: 'This page has been modified' +THIS_PAGE_HAS_UNSAVED_CHANGES: '' +TITLE: Title +TOC: Toc +TO_DOWNLOAD_THIS_FILE_YOU_NEED_TO_BE_AUTHENTICATED_WITH_THE_ROLE: 'To download this file you need to be authenticated with the role:' +TRACK_SPAM_AND_SUSPICIOUS_ACTIONS_IN_A_LOGFILE: 'Track spam and suspicious actions in a logfile' +TRASH: Trash +TRUSTED_IPS_FOR_PROXIES_COMMA_SEPARATED: 'Trusted IPs for proxies (comma separated)' +TRY_TO_CONVERT_UPLOADED_IMAGES_INTO_THE_WEBP_FORMAT_FOR_BETTER_PERFORMANCE: 'Try to convert uploaded images into the webp format for better performance' +TWIG_CACHE: 'Twig Cache' +TYPE_OF_REFERENCE: 'Type of reference' +ULIST: Ulist +UNPUBLISH: Unpublish +UNPUBLISH_PAGE: 'Unpublish page' +UNSAVED_CHANGES: '' +UPLOAD: Upload +UPLOAD_AN_IMAGE: 'Upload an image' +UPLOAD_FILE: 'Upload a file' +URL_SCHEMES: 'URL schemes' +USED_AS_FALLBACK_WHEN_NO_MANUAL_DATE_IS_SET: 'Used as fallback when no manual date is set.' +USED_FOR_COPYRIGHT_AND_YEAR_IN_FOOTER: 'Used for copyright and year in footer' +USED_FOR_FRONTEND_LANGUAGE_ATTRIBUTE_PLEASE_USE_ISO_639_1_CODES_LIKE_EN: 'Used for frontend language attribute. Please use ISO 639-1 codes like en.' +USED_FOR_TRANSLATIONS_IN_AUTHOR_AREA_THEMES_AND_PLUGINS: 'Used for translations in author area, themes, and plugins.' +USER: User +USERDATA_ARE_REQUIRED: 'Userdata are required' +USERNAME: Username +USERNAME_IS_REQUIRED: 'Username is required' +USERNAME_READ_ONLY: 'Username (read only)' +USERROLE: Userrole +USERROLE_IS_REQUIRED: 'Userrole is required' +USERROLE_IS_UNKNOWN: 'Userrole is unknown' +USERS: Users +USER_DELETED: 'User deleted' +USER_HAS_BEEN_UPDATED: 'User has been updated' +USER_NOT_FOUND: 'User not found' +USE_A_STRONG_AND_INDIVIDUAL_PASSWORD_FOR_EVERY_ACCOUNT: 'Use a strong and individual password for every account' +USE_CAPTCHA_IN_AUTHENTICATION_FORMS: 'Use captcha in authentication forms' +USE_X_FORWARDED_HEADER: 'Use X-Forwarded header' +VERIFICATION_CODE_MISSING: 'Verification code missing' +VERIFY_YOUR_LOGIN_WITH_A_5_DIGIT_CODE_SEND_BY_EMAIL: 'Verify your login with a 5 digit code send by email' +VERSION: Version +VIDEO: Video +VISIBILITY: Visibility +VISIT_THE_PLUGIN_DIRECTORY: '' +VISIT_THE_THEME_DIRECTORY: '' +VISUAL: Visual +VISUAL_EDITOR: 'Visual Editor' +WEBSITE_OWNER: 'Website owner' +WEBSITE_RESTRICTION: 'Website Restriction' +WEBSITE_TITLE: 'Website Title' +WELCOME_BACK: 'Welcome back' +WELCOME_TO_TYPEMILL: 'Welcome to Typemill' +WE_CANNOT_CREATE_A_FOLDER_ONLY_FILES: 'We cannot create a folder, only files' +WE_COULD_NOT_CREATE_A_SANITIZED_VERSION_OF_THE_SVG_IT_PROBABLY_HAS_INVALID_CONTENT: 'We could not create a sanitized version of the SVG. It probably has invalid content.' +WE_COULD_NOT_CREATE_THE_FILE_PLEASE_REFRESH_THE_PAGE_AND_CHECK_IF_ALL_FOLDERS_AND_FILES_ARE_WRITABLE: 'We could not create the file. Please refresh the page and check if all folders and files are writable.' +WE_COULD_NOT_CREATE_THE_FOLDER_PLEASE_REFRESH_THE_PAGE_AND_CHECK_IF_ALL_FOLDERS_AND_FILES_ARE_WRITABLE: 'We could not create the folder. Please refresh the page and check if all folders and files are writable.' +WE_COULD_NOT_CREATE_THE_USER_PLEASE_CHECK_IF_THE_SETTINGS_FOLDE_IS_WRITABLE: 'We could not create the user. Please check if the settings folder is writable.' +WE_COULD_NOT_DELETE_A_CUSTOM_IMAGE_GRAYSCALE_OR_RESIZED: 'We could not delete a custom image (grayscale or resized)' +WE_COULD_NOT_DELETE_THE_LIVE_IMAGE: 'We could not delete the live image' +WE_COULD_NOT_DELETE_THE_ORIGINAL_IMAGE_IN: 'We could not delete the original image in' +WE_COULD_NOT_DELETE_THE_THUMB_IMAGE: 'We could not delete the thumb image' +WE_COULD_NOT_DELETE_THE_USER: 'We could not delete the user' +WE_COULD_NOT_FIND_A_FOLDERPATH_FOR: 'We could not find a folderpath for' +WE_COULD_NOT_FIND_OR_READ_THE_PUBLIC_KEY_PEM_IN_THE_SETTINGS_FOLDER: '' +WE_COULD_NOT_FIND_THE_USER: 'We could not find the user' +WE_COULD_NOT_FIND_THIS_PAGE_PLEASE_REFRESH_AND_TRY_AGAIN: 'We could not find this page. Please refresh and try again.' +WE_COULD_NOT_LOAD_THE_SVG_FILE_IT_IS_PROBABLY_CORRUPTED: 'We could not load the SVG file. It is probably corrupted.' +WE_COULD_NOT_SEND_THE_EMAIL_WITH_THE_PASSWORD_INSTRUCTIONS_TO_YOUR_ADDRESS_REASON: 'We could not send the email with the password instructions to your address. Reason:' +WE_COULD_NOT_SEND_THE_EMAIL_WITH_THE_VERIFICATION_CODE_TO_YOUR_ADDRESS_REASON: 'We could not send the email with the verification code to your address. Reason:' +WE_COULD_NOT_SEND_THE_TESTMAIL_TO_YOUR_E_MAIL_ADDRESS_REASON: 'We could not send the testmail to your e-mail address. Reason:' +WE_COULD_NOT_STORE_FILE_TO_TEMPORARY_FOLDER: 'We could not store file to temporary folder' +WE_COULD_NOT_STORE_THE_CONTENT: 'We could not store the content' +WE_COULD_NOT_STORE_THE_LIVE_IMAGE_TO_THE_LIVE_FOLDER: 'We could not store the live image to the live folder' +WE_COULD_NOT_STORE_THE_NEW_USER: 'We could not store the new user' +WE_COULD_NOT_STORE_THE_ORIGINAL_IMAGE: 'We could not store the original image' +WE_COULD_NOT_STORE_THE_THUMB_TO_THE_THUMB_FOLDER: 'We could not store the thumb to the thumb folder' +WE_DID_NOT_FIND_A_FILE_WITH_THAT_NAME: 'We did not find a file with that name' +WE_DID_NOT_FIND_THE_A_USER_OR_USERMAIL: 'We did not find the a user or usermail' +WE_DID_NOT_FIND_THE_FILE_IN_THE_TMP_FOLDER_OR_COULD_NOT_READ_IT: 'We did not find the file in the tmp folder or could not read it' +WE_DID_NOT_FIND_THE_IMAGE_IN_THE_TMP_FOLDER_OR_COULD_NOT_READ_IT: 'We did not find the image in the tmp folder or could not read it' +WE_FOUND_THE_FILE_BUT_COULD_NOT_DELETE: 'We found the file but could not delete' +WE_FOUND_THE_FOLDER_BUT_COULD_NOT_DELETE: 'We found the folder but could not delete' +WE_FOUND_THE_FOLDER_BUT_COULD_NOT_DELETE_IT: 'We found the folder but could not delete it' +WE_HOPE_YOU_LIKE_TYPEMILL_BECAUSE_WE_CODED_IT_JUST_FOR_YOU: 'We hope you like Typemill because we coded it just for you.' +WIDTH_HEIGHT: Width/Height +WRAP_RESTRICTION_NOTICE: 'Wrap restriction notice' +WRAP_THE_RESTRICTION_NOTICE_ABOVE_INTO_A_NOTICE_4_ELEMENT_WHICH_CAN_BE_DESIGNED_AS_SPECIAL_BOX: 'Wrap the restriction notice above into a notice-4 element (which can be designed as special box)' +WRITING: Writing +WRONG_PASSWORD_OR_USERNAME_PLEASE_TRY_AGAIN: 'Wrong password or username, please try again.' +YEAR: Year +YOUR_ARE_AN: 'Your are an' +YOUR_BROWSER_CAN_REMEMBER_ALL_OF_YOUR_PASSWORDS: 'Your browser can remember all of your passwords.' +YOUR_EMAIL: 'Your email' +YOUR_LICENSE_KEY: 'Your license key' +YOUR_REGISTRATION_IS_NOT_CONFIRMED_YET_PLEASE_CHECK_YOUR_E_MAILS_AND_USE_THE_CONFIRMATION_LINK: 'Your registration is not confirmed yet. Please check your e-mails and use the confirmation link.' +YOUR_TYPEMILL_VERIFICATION_CODE: 'Your Typemill verification code' +YOU_ARE_NOT_ALLOWED_TO_DELETE_ANOTHER_USER: 'You are not allowed to delete another user' +YOU_ARE_NOT_ALLOWED_TO_UPDATE_ANOTHER_USER: 'You are not allowed to update another user' +YOU_CANNOT_CREATE_AN_ITEM_WITH_THE_SLUG_TM_IN_THE_ROOT_FOLDER_BECAUSE_THIS_IS_THE_SYSTEM_PATH: '' +YOU_CAN_OVERWRITE_THE_THEME_CSS_WITH_YOUR_OWN_CSS_HERE: 'You can overwrite the theme-css with your own css here.' +YOU_CAN_USE_THE_ASTERISK_WILDCARD_TO_SEARCH_FOR_NAME@_OR_@DOMAIN_COM: 'You can use the asterisk wildcard to search for name@ or @domain.com' +YOU_DO_NOT_HAVE_ENOUGH_RIGHTS: 'You do not have enough rights' +YOU_TRIED_TO_OPEN_THE_PASSWORD_RESET_PAGE_BUT_THE_LINK_WAS_INVALID: 'You tried to open the password reset page but the link was invalid' +YOU_TRIED_TO_SET_A_NEW_PASSWORD_BUT_USERNAME_OR_TOKEN_WAS_INVALID: 'You tried to set a new password but username or token was invalid' +©: © +LICENSE_DATA_INCOMPLETE: 'License data incomplete' +LICENSE_DATA_INVALID: 'License data invalid' +NO_LICENSE_FOUND: 'No license found' +THE_LICENSE_SERVER_RESPONDED_WITH: 'The license server responded with:' +THE_SUBSCRIPTION_PERIOD_IS_NOT_PAID_YET: 'The subscription period is not paid yet.' +VALIDATION_FAILED: 'Validation failed' diff --git a/system/typemill/author/translations/fr.yaml b/system/typemill/author/translations/fr.yaml new file mode 100644 index 0000000..6aad88d --- /dev/null +++ b/system/typemill/author/translations/fr.yaml @@ -0,0 +1,481 @@ +# French (Français) +# Author: oliviercrouzet (https://github.com/oliviercrouzet) +ACCESS: '' +ACCESS_FOR: '' +ACCESS_RIGHTS: '' +ACCESS_TO_BASEPATH_IS_NOT_ALLOWED: '' +ACCOUNT: Compte +ACCOUNT_CREATED_PLEASE_LOGIN_WITH_YOUR_USERNAME_AND_PASSWORD_NOW: '' +ACTIVATE_API_ACCESS_FOR_THIS_USER_USE_USERNAME_AND_PASSWORD_FOR_API_CALLS: '' +ACTIVATE_A_PASSWORD_RECOVERY_IN_THE_LOGIN_FORM: '' +ACTIVATE_INDIVIDUAL_RESTRICTIONS_FOR_PAGES_IN_THE_META_TAB_OF_EACH_PAGE: 'Activer les restrictions par page dans l''onglet Métadonnées de chaque page.' +ACTIVATE_THE_CACHE_FOR_TWIG_TEMPLATES: '' +ACTIVATE_THE_DARKMODE_FOR_ME: '' +ACTIVATE_YOUR_LICENSE: '' +ACTIVATION_FAILED_BECAUSE_YOU_NEED_A_VALID: '' +ACTIVE: Actif +ACTUAL_PASSWORD: 'Mot de passe actuel' +ADD: Ajouter +ADD_A_FILE: '' +ADD_A_FOLDER: '' +ADD_DEFINITION: 'ajouter une définition' +ADD_DESCRIPTION: '' +ADD_ENTRY: '' +ADD_LEFT_COLUMN: 'Ajouter une colonne à gauche' +ADD_MORE_URL_SCHEMES_FOR_EXTERNAL_LINKS_E_G_LIKE_DICT_COMMA_SEPARATED_LIST: 'Ajouter d''autres schémas d''url pour les liens externes. ex: dict:// (liste séparée par des virgules)' +ADD_NOINDEX_TAG_AND_EXCLUDE_FROM_SITEMAP: '' +ADD_ONE_OR_MORE_USERNAMES_SEPARATED_WITH_COMMA: 'Ajouter un ou plusieurs noms séparés par une virgule.' +ADD_RIGHT_COLUMN: 'Ajouter une colonne à droite' +ADD_ROW_ABOVE: 'Ajouter une rangée en haut' +ADD_ROW_BELOW: 'Ajouter une rangée en bas' +ALLOWED_DOMAINS_FOR_API_ACCESS_CORS_HEADERS: '' +ALLOWED_DOMAINS_FOR_CONTENT_ON_TYPEMILL_CSP_HEADERS: '' +ALLOW_SVG: '' +ALLOW_THE_UPLOAD_OF_SVG_IMAGES: '' +ALL_PAGES: '' +ALTERNATIVE_TEXT_FOR_THE_HERO_IMAGE: 'Texte alternatif pour la bannière d''accueil' +ALT_TEXT: 'Texte alternatif' +ALWAYS_SHOW: '' +AND_YOU_CANNOT: '' +AND_YOU_MIGHT_FIND_THE_FOLLOWING_TIPS_HELPFUL: '' +ANSWER_FROM_LICENSE_SERVER: '' +API_ACCESS: '' +ARTICLE_DATE: '' +AUTHOR: Auteur +AUTHOR_DESCRIPTION_MARKDOWN: 'Auteur-Description (Markdown)' +BACK_TO_LOGIN: '' +BLOCK_ID_NOT_FOUND: '' +BOLD: Gras +BULLET_LIST: 'Liste à puces' +BUY_A_LICENSE: '' +CANCEL: Annuler +CAN_BE_USED_FOR_AUTHOR_LINE_IN_FRONTEND: 'Sera affiché comme auteur sur le site web.' +CAPTION: Légende +CC_BY: '' +CC_BY_NC: '' +CC_BY_NC_ND: '' +CC_BY_NC_SA: '' +CC_BY_ND: '' +CC_BY_SA: '' +CENTER: Center +CHANGE_SLUG: '' +CHECK: Vérifier +CHECK_YOUR_INBOX: '' +CHECK_YOUR_LICENSE: '' +CLASS: Classe +CLEAR: '' +CLOSE: '' +CLOSE_LIBRARY: 'Fermer la bibliothèque' +CODE: Code +COLLAPSE_ALL: '' +CONFIGURE: '' +CONFIRM: '' +CONTENT_BREAK: '' +CONVERT_TO_WEBP: '' +COPYRIGHT: Copyright +COPY_THE_CONTENT_OF_THE_REFERENCED_INTERNAL_PAGE: '' +COULD_NOT_CREATE_FOLDER: '' +COULD_NOT_DECODE_IMAGE_OR_FILE_PROBABLY_NOT_A_BASE64_ENCODING: '' +COULD_NOT_DELETE_FILE: '' +COULD_NOT_DELETE_FOLDER: '' +COULD_NOT_GET_THE_VIDEO_IMAGE: '' +COULD_NOT_OPEN_AND_READ_THE_FILE: '' +COULD_NOT_READ_PATHINFO: '' +COULD_NOT_STORE_THE_CUSTOM_SIZE_OF: '' +COULD_NOT_STORE_THE_IMAGE_IN_THE_TEMPORARY_FOLDER: '' +COULD_NOT_STORE_THE_RESIZED_VERSION: '' +COULD_NOT_WRITE_TO_THE_FILE: '' +CREATED_AT_READ_ONLY: 'Date de création (lecture seule)' +CREATE_NEW_PASSWORD: '' +CREATE_POST: '' +CREATE_USER: 'Créer un utilisateur' +CUSTOM_CSS: 'CSS personnalisé' +CUT_RESTRICTED_CONTENT_AFTER_THE_FIRST_HR_ELEMENT_ON_A_PAGE_PER_DEFAULT_CONTENT_WILL_BE_CUT_AFTER_TITLE: 'Couper le contenu restreint après le premier élément hr de la page (par défaut, le contenu sera coupé après le titre).' +DARKMODE: '' +DEAR: '' +DEAR_USER: '' +DEFAULT_WIDTH_OF_LIVE_IMAGES_IS_820PX_CHANGES_WILL_APPLY_TO_FUTURE_UPLOADS: '' +DEFINITION: 'Liste de définition' +DEFINITION_LIST: 'Liste de définition' +DELETE: Supprimer +DELETE_COLUMN: 'Supprimer cette colonne' +DELETE_DESCRIPTION: '' +DELETE_PAGE: 'Supprimer la page' +DELETE_ROW: 'Supprimer une rangée' +DELETE_USER: 'Supprimer l''utilisateur' +DEVELOPER: Développeur +DISABLE: '' +DISABLE_ALL_CSP_HEADERS_CONTENT_SECURITY_POLICY_FOR_THIS_WEBSITE: '' +DISABLE_ALL_CUSTOM_HEADERS_OF_TYPEMILL_EXCEPT_CORS_AND_SEND_YOUR_OWN_HEADERS_INSTEAD: '' +DISABLE_CSP_HEADERS: '' +DISABLE_CUSTOM_HEADERS: '' +DISCARD: 'Annuler les modifications' +DISCARD_CHANGES: 'Ne pas sauvegarder les modifications' +DISPLAY_APPLICATION_ERRORS: 'Afficher les erreurs de l''application' +DOES_NOT_EXIST: '' +DOES_NOT_EXIST_OR_IS_NOT_WRITABLE: '' +DOMAIN_FOR_LICENSE: '' +DONATE: '' +DONE: '' +DO_NOT_FORGET_TO_CHECK_YOUR_SPAM_FOLDER_IF_YOUR_INBOX_IS_EMPTY: '' +DO_NOT_RESIZE: '' +DO_YOU_REALLY_WANT_TO_DELETE_THIS_PAGE: 'Voulez-vous vraiment supprimer cette page ?' +DO_YOU_REALLY_WANT_TO_DELETE_THIS_USER: '' +DO_YOU_WANT_TO_DISCARD_YOUR_CHANGES_AND_SET_THE_CONTENT_BACK_TO_THE_LIVE_VERSION: 'Voulez-vous annuler les modifications et revenir à la version précédente ?' +DRAFT: 'comme brouillon' +DRAG_A_PICTURE_OR_CLICK_TO_SELECT: '' +DUTCH_FLEMISH: 'Néerlandais, Flamand' +EDIT: Éditer +EMAIL: '' +EMAIL_ADDRESS_IN_SYSTEM_SETTINGS_IS_MISSING: '' +EMAIL_SUBJECT: '' +ENGLISH: Anglais +ENTER_AN_EMAIL_ADDRESS_THAT_SENDS_THE_E_MAILS_SENDER_THE_E_MAIL_FEATURE_WILL_BE_USED_FOR_RECOVERY_AND_VERIFICATION_E_MAILS_SEND_A_TESTMAIL_TO_YOUR_USER_ACCOUNT_TO_VERIFY_THAT_YOU_RECEIVE_THE_E_MAILS: '' +ENTER_THE_EMAIL_OF_YOUR_USER_ACCOUNT_CLICK_THE_RECOVER_BUTTON_AND_CHECK_YOUR_MAILBOX_FOR_FURTHER_INSTRUCTIONS: '' +ENTER_THE_FULL_DOMAIN_LIKE_HTTPS_WWWW_MYWEBSITE_DE: '' +ENTER_THE_VERIFICATION_CODE_FROM_YOUR_EMAIL: '' +ENTER_YOUR_E_MAIL_ADDRESS_THAT_YOU_USED_FOR_YOUR_LICENSE_PURCHASE: '' +ENTER_YOUR_LICENSE_KEY_THAT_YOU_GOT_AFTER_YOUR_PURCHASE_VIA_EMAIL: '' +ERROR_REPORTING: 'Rapport d''erreur' +ERROR_SENDING_EMAIL: '' +EXPAND_ALL: '' +EXTENSION_OR_FILENAME_ARE_MISSING: '' +EXTENSION_OR_NAME_FOR_IMAGE_IS_MISSING: '' +EXTERNAL_LINK: 'Lien externe' +E_MAIL: Courriel +FAVICON: Favicon +FILE: Fichier +FILENAME_IS_MISSING: '' +FILENAME_OR_USERROLE_IS_MISSING: '' +FILETYPE_IS_NOT_ALLOWED: '' +FILE_DELETED_SUCCESSFULLY: '' +FILE_HAS_BEEN_STORED: '' +FILE_IS_BIGGER_THAN_20MB: '' +FILE_IS_EMPTY: '' +FILE_IS_MISSING: '' +FILE_NOT_FOUND: '' +FILE_SAVED_SUCCESSFULLY: '' +FIRST_NAME: Nom +FOLDER: Dossier +FORGOT_PASSWORD: 'Mot de passe oublié' +FORGOT_YOUR_PASSWORD: '' +FORMAT_OPTIONS_FOR_VISUAL_EDITOR: '' +FRENCH: Français +FROM_MAIL_IS_REQUIRED_FOR_THIS_FEATURE_SEND_A_TESTMAIL_BEFORE_YOU_USE_THIS_FEATURE: '' +FROM_MAIL_IS_REQUIRED_FOR_THIS_FEATURE_SEND_A_TESTMAIL_BEFORE_YOU_USE_THIS_FEATURE_MAKE_SURE_YOU_HAVE_FTP_ACCESS_TO_DISABLE_THE_FEATURE_IN_SETTINGS_YAML_ON_FAILURE_THE_VERIFICATION_CODE_WILL_BE_VALID_FOR_5_MINUTES_BE_AWARE_THAT_DEVICE_FINGERPRINTS_WILL_BE_STORED_IN_THE_USER_ACCOUNTS: '' +GERMAN: Allemand +GET_INSPIRED_AND_ENJOY_YOUR_WRITING: '' +GOOGLE_SITEMAP_READONLY: '' +GO_TO_LOGIN: '' +HAS_EDIT_RIGHTS_FOR_THIS_ARTICLE: 'Possède les droits d''édition sur cette article.' +HEADLINE: Titre +HEADLINE_ANCHORS: 'Ancres de titre' +HERO_IMAGE: 'Bannière d''accueil (Hero image)' +HEY_WRITER_AUTHOR_EDITOR_CONTENT_GURU_OR_WEBSITE_MANAGER: '' +HIDE: Masquer +HIDE_PAGE_FROM_NAVIGATION: 'Masquer dans le menu de navigation du site' +HOMEPAGE: 'Page d''accueil' +HORIZONTAL_LINE: 'Ligne horizontale' +HR: 'Ligne horizontale' +IF_NOT_FILLED_THE_DESCRIPTION_IS_EXTRACTED_FROM_CONTENT: 'Si non renseignée ici, la description est extraite du contenu.' +IF_YOUR_PROXY_DOES_NOT_WORK_TRY_TO_ADD_THE_BASE_URL_OF_YOUR_PROXY_HERE_LIKE_HTTPS_MYWEBSITE_COM: '' +IF_YOU_ADD_A_VALUE_FOR_THE_HEIGHT_THEN_THE_IMAGE_WILL_BE_CROPPED: 'Si vous ajoutez une valeur pour la hauteur, l''image sera recadrée.' +IF_YOU_DID_NOT_MAKE_THIS_LOGIN_ATTEMPT_PLEASE_RESET_YOUR_PASSWORD_IMMEDIATELY: '' +IF_YOU_DID_NOT_RECEIVE_AN_EMAIL_WITH_THE_VERIFICATION_CODE_THEN_THE_USERNAME_OR_PASSWORD_YOU_ENTERED_WAS_WRONG_PLEASE_TRY_AGAIN: '' +IF_YOU_UNPUBLISH_THE_PAGE_THEN_WE_WILL_DELETE_THE_PUBLISHED_VERSION_AND_KEEP_THE_MODIFIED_VERSION: '' +IF_YOU_WANT_TO_KEEP_YOUR_CHANGES_THEN_CLICK_ON_CANCEL_AND_SAVE_YOUR_CHANGES_BEFORE_YOU_SWITCH_TO_THE_VISUAL_EDITOR: '' +IMAGE: Image +IMAGENAME_IS_MISSING: '' +IMAGES_WITH_THIS_EXTENSION_ARE_NOT_ALLOWED: '' +IMAGE_DELETED_SUCCESSFULLY: '' +IMAGE_OR_FILENAME_IS_MISSING: '' +IMAGE_OR_NAME_IS_MISSING: '' +IMAGE_SAVED_SUCCESSFULLY: '' +IMAGE_URL: 'URL de l''image' +IMAGE_URL_READ_ONLY: 'URL de l''image (lecture seule)' +INFO: '' +INVALID_VERIFICATION_CODE_FORMAT_PLEASE_TRY_AGAIN: '' +INVISIBLE: '' +IS_NOT_A_DIRECTORY: '' +IS_NOT_A_FOLDER: '' +ITALIAN: Italien +ITALIC: Italique +IT_IS_NOT_ALLOWED_TO_WRITE_INTO: '' +LANGUAGE: Langue +LANGUAGE_ATTRIBUTE_WEBSITE: '' +LANGUAGE_AUTHOR_AREA: '' +LAST_MODIFIED_LIVE_READONLY: 'Dernière modification (lecture seule)' +LAST_NAME: Prénom +LEFT: Gauche +LICENCE_HAS_BEEN_STORED: '' +LICENSE: '' +LICENSE_AND_YOUR_WEBSITE_MUST_RUN_UNDER_THE_DOMAIN_OF_YOUR_LICENSE: '' +LICENSE_DATA_ARE_INCOMPLETE: '' +LICENSE_DATA_MISSING: '' +LICENSE_REQUIRED: '' +LICENSE_VALIDATION_FAILED: '' +LINK: Lien +LINK_TO_AN_EXTERNAL_PAGE: '' +LINK_TO_YOUTUBE: '' +LIST_ALL_DOMAINS_SEPARATED_BY_COMMAS_TO_ALLOW_CONTENT_INTEGRATION_SUCH_AS_IFRAMES_ON_YOUR_TYPEMILL_WEBSITE_DOMAINS_WILL_BE_ADDED_TO_THE_CSP_HEADER_USUALLY_DONE_WITH_PLUGINS_AND_THEMES_BUT_ADD_MANUALLY_IF_SOMETHING_IS_BLOCKED: '' +LIST_ALL_DOMAINS_SEPARATED_BY_COMMA_THAT_SHOULD_HAVE_ACCESS_TO_THE_TYPEMILL_API_DOMAINS_WILL_BE_ADDED_TO_THE_CORS_HEADER: '' +LOGIN: Connexion +LOGIN_TO_THE_AUTHOR_AREA_OR_GO_TO_THE: '' +LOGIN_VERIFICATION_RECOMMENDED: '' +LOGO: Logo +MAIL_FROM_NAME_OPTIONAL: '' +MAIL_FROM_REQUIRED: '' +MANUAL_DATE: 'Date manuelle' +MARKDOWN: Markdown +MARKDOWN_IS_MISSING: '' +MAXIMUM_SIZE_FOR_AN_IMAGE_IS_5_MB_HERO_IMAGES_ARE_NOT_SUPPORTED_BY_ALL_THEMES: 'La taille maximale pour une image est 5 Mo. Les bannières d''accueil ne sont pas gérées dans tous les thèmes.' +MAXIMUM_SIZE_FOR_FILE_UPLOADS_IN_MB: '' +MAXIMUM_SIZE_FOR_IMAGE_UPLOADS_IN_MB: '' +MAXIMUM_SIZE_OF_FILE_LINK_IS_100_CHARACTERS: '' +MAXIMUM_SIZE_OF_FILE_TEXT_IS_100_CHARACTERS: '' +MAXIMUM_SIZE_OF_IMAGE_ALT_TEXT_IS_100_CHARACTERS: '' +MAXIMUM_SIZE_OF_IMAGE_CAPTION_IS_140_CHARACTERS: '' +MAXIMUM_SIZE_OF_IMAGE_CLASS_IS_100_CHARACTERS: '' +MAXIMUM_SIZE_OF_IMAGE_ID_IS_100_CHARACTERS: '' +MAXIMUM_SIZE_OF_IMAGE_LINK_IS_100_CHARACTERS: '' +MAXIMUM_SIZE_OF_IMAGE_TITLE_IS_100_CHARACTERS: '' +MEDIA: '' +MEDIALIB: '' +MENU: Menu +META_CONTENT: '' +META_DESCRIPTION: 'Meta description' +META_TITLE: 'Meta titre' +MINIMUM_USER_ROLE_TO_ACCESS_THIS_PAGE: '' +NAVIGATION_TITLE: 'Titre dans le menu de navigation' +NEW_PASSWORD: 'Nouveau mot de passe' +NEW_USER: '' +NEW_USER_CREATED: '' +NOINDEX: '' +NOTICE: Note +NO_FILE_FOUND: '' +NO_IMAGE_FOUND: '' +NO_PROBLEM_YOU_CAN_CREATE_A_NEW_ONE_HERE: '' +NUMBERED_LIST: 'Liste numérotée' +OLIST: 'Liste ordonnée' +ONE_OR_MORE_FILES_COULD_NOT_BE_TRANSFORMED: '' +ONLY_IMAGES_ARE_ALLOWED: '' +ONLY_PNG_FORMAT_WILL_WORK: '' +ONLY_THE_FOLLOWING_USERS_HAVE_ACCESS: 'Seuls les utilisateurs suivants ont accès' +OPEN: '' +OPTIONALLY_ENTER_A_NAME_FOR_THE_SENDER_ADDRESS_IF_NOT_SET_THE_FROM_ADDRESS_WILL_BE_VISIBLE: '' +OPTIONALLY_ENTER_A_REPLY_TO_ADDRESS_FOR_ANSWERS_FROM_THE_RECEIVER_IF_NOT_SET_ANSWERS_WILL_GO_TO_THE_FROM_ADDRESS: '' +OWNER_USERNAME: 'propriétaire (nom d''utilisateur)' +PAGES_SORT_IN_NAVIGATION_WITH_DRAG_DROP: '' +PAGE_NOT_FOUND: '' +PAGE_RESTRICTION: '' +PARAGRAPH: Paragraphe +PASSWORD: 'Mot de passe' +PATH_IS_MISSING: '' +PERMANENT_REDIRECT_301_THE_USER_TO_THE_REFERENCED_INTERNAL_PAGE: '' +PERMISSION_DENIED: '' +PLEASE_CHECK_IF_THERE_IS_A_READABLE_FILE_PUBLIC_KEY_PEM_IN_YOUR_SETTINGS_FOLDER: '' +PLEASE_CHECK_THE_INBOX_OF_YOUR_EMAIL_ACCOUNT_FOR_MORE_INSTRUCTIONS: '' +PLEASE_CONFIRM: Confirmer +PLEASE_CORRECT_YOUR_INPUT: '' +PLEASE_ENTER_A_VALID_EMAIL: '' +PLEASE_LOGIN_WITH_YOUR_NEW_PASSWORD: '' +PLEASE_SELECT: '' +PLEASE_USE_THE_FOLLOWING_LINK_TO_SET_A_NEW_PASSWORD: '' +PLUGINS: Plugins +PLUGIN_SETTINGS: '' +POSTS_SORTED_BY_PUBLISH_DATE_FOR_NEWS_OR_BLOGS: '' +PROFILE_IMAGE: 'Image du Profil' +PROXY: Proxy +PUBLISH: Publier +QUOTE: Citation +QUOTES: Citations +RAW: 'Texte brut' +RAW_EDITOR: '' +RECOVER_PASSWORD: '' +REFERENCE: '' +REFERENCE_TO_PAGE: '' +REPEAT_PASSWORD: '' +REPLY_TO_OPTIONAL: '' +REQUIRED: Requis +RESTRICTION_NOTICE_USE_MARKDOWN: '' +RIGHT: Droit +ROLE: Rôle +RUSSIAN: Russe +SAVE: Enregistrer +SEARCH: '' +SECURITY: '' +SECURITY_LOG: '' +SELECT_FROM_MEDIALIB: 'Sélectionner dans la bibliothèque de médias' +SELECT_THE_LOWEST_USERROLE_HIGHER_ROLES_WILL_HAVE_ACCESS_TOO: 'Selectionner le rôle minimal. Les rôles plus élevés accèderont également.' +SETTINGS_HAVE_BEEN_SAVED: '' +SETUP: Configuration +SHORTCODE: '' +SHORTCODES: '' +SHORT_TITLE_FOR_POST: '' +SHOW_AFTER_FIRST_WRONG_INPUT: '' +SHOW_ANCHORS_NEXT_TO_HEADLINE_IN_FRONTEND: '' +SHOW_THE_WEBSITE_ONLY_TO_AUTHENTICATED_USERS_AND_REDIRECT_ALL_OTHER_USERS_TO_THE_LOGIN_PAGE: 'Afficher le site uniquement pour les utilisateurs authentifiés et rediriger tous les autres vers la page de login.' +SLUG: '' +SOMEONE_TRIED_TO_LOG_IN_TO_YOUR_TYPEMILL_WEBSITE_AND_WE_WANT_TO_MAKE_SURE_IT_IS_YOU_ENTER_THE_FOLLOWING_VERIFICATION_CODE_TO_FINISH_YOUR_LOGIN_THE_CODE_WILL_BE_VALID_FOR_5_MINUTES: '' +SOMETHING_WENT_WRONG_PLEASE_REFRESH_THE_PAGE_AND_CHECK_IF_ALL_FOLDERS_AND_FILES_ARE_WRITABLE: '' +SOMETHING_WENT_WRONG_THE_INPUT_IS_NOT_VALID: '' +SPECIAL_CHARACTERS_ARE_NOT_ALLOWED_LENGTH_BETWEEN_1_AND_40: '' +STANDARD_EDITOR_MODE: 'Mode éditeur standard' +STANDARD_HEIGHT_FOR_LIVE_PICTURES: '' +STANDARD_WIDTH_FOR_LIVE_PICTURES: '' +SUBMIT_THE_URL_ABOVE_IN_GOOGLE_SEARCH_CONSOLE_TO_SUPPORT_INDEXING: '' +SWITCH_TO_VISUAL: '' +SYSTEM: Système +SYSTEMCHECK: '' +SYSTEM_SETTINGS: '' +TABLE: Tableau +TABLE_OF_CONTENTS: Sommaire +TAB_NOT_FOUND: '' +TEMPORARY_REDIRECT_302_THE_USER_TO_THE_REFERENCED_INTERNAL_PAGE: '' +TESTMAIL_FROM_TYPEMILL: '' +TEXT_BEFORE_RECOVER_LINK_IN_EMAIL_MESSAGE: '' +TEXT_FILE: 'Fichier texte' +THEMES: Thèmes +THEME_SETTINGS: '' +THERE_ARE_PUBLISHED_PAGES_WITHIN_THIS_FOLDER_THE_PAGES_ARE_NOT_VISIBLE_ON_YOUR_WEBSITE_ANYMORE: '' +THERE_IS_ALREADY_A_PAGE_WITH_THAT_SLUG: '' +THERE_IS_ALREADY_A_PAGE_WITH_THIS_NAME_PLEASE_CHOOSE_ANOTHER_NAME: '' +THERE_WAS_AN_ERROR_CHECKING_THE_LICENSE_SIGNATURE: '' +THE_BEST_OPTION_IS_A_SEPARATE_PASSWORD_MANAGER_LIKE_KEEPASS_AND_OTHERS: '' +THE_CAPTCHA_IS_WRONG_PLEASE_TRY_AGAIN: '' +THE_FOLDER_CONTAINS_ANOTHER_FOLDER_SO_WE_CANNOT_TRANSFORM_IT_PLEASE_MAKE_SURE_THERE_ARE_ONLY_FILES_IN_THIS_FOLDER: '' +THE_FOLDER_CONTAINS_PUBLISHED_PAGES_PLEASE_UNPUBLISH_OR_DELETE_THEM_FIRST: '' +THE_FOLLOWING_REQUIREMENTS_FOR_THE_INSTALLATION_ARE_MISSING: '' +THE_FROM_MAIL_IS_MISSING_OR_IT_IS_NOT_A_VALID_E_MAIL_ADDRESS: '' +THE_ID_OF_THE_CONTENT_BLOCK_IS_WRONG: '' +THE_LICENSE_DATA_ARE_INVALID: '' +THE_LINK_TO_RECOVER_THE_PASSWORD_WAS_TOO_OLD_PLEASE_CREATE_A_NEW_ONE: '' +THE_MAXIMUM_FILE_SIZE_MIGHT_BE_LIMITED_BY_YOUR_SERVER_SETTINGS: '' +THE_MAXIMUM_IMAGE_SIZE_MIGHT_BE_LIMITED_BY_YOUR_SERVER_SETTINGS: '' +THE_MIME_TYPE_IS_MISSING_NOT_ALLOWED_OR_DOES_NOT_FIT_TO_THE_FILE_EXTENSION: '' +THE_PLUGIN_OR_THEMES_WAS_NOT_FOUND: '' +THE_RECOVER_LINK_WILL_BE_ACTIVE_FOR_24_HOURS: '' +THE_REQUESTED_FILETYPE_DOES_NOT_EXIST: '' +THE_REQUESTED_FILE_DOES_NOT_EXIST: '' +THE_SUBSCRIPTION_PERIOD_HAS_NOT_BEEN_PAID_YET_AND_WE_GOT_AN_ERROR: '' +THE_SUBSCRIPTION_PERIOD_HAS_NOT_BEEN_PAID_YET_WE_WILL_CHECK_IT_EVERY_60_MINUTES: '' +THE_TESTMAIL_HAS_BEEN_SEND_PLEASE_CHECK_YOUR_INBOX_AND_YOUR_SPAM_FOLDER_TO_VARIFY_THAT_YOU_RECEIVED_THE_MAIL: '' +THE_VERIFICATION_WAS_WRONG_OR_OUTDATED_PLEASE_START_AGAIN: '' +THE_VERSION_CHECK_FAILED_BECAUSE_OF_INVALID_PARAMETERS: '' +THE_WEBSITE_IS_RUNNING_NOT_UNDER_THE_DOMAIN_OF_YOUR_LICENSE: '' +THIS: '' +THIS_FIELD_IS_NOT_DEFINED: '' +THIS_FOLDER_CONTAINS: '' +THIS_IS_A_TESTMAIL_FROM_TYPEMILL_AND_IF_YOU_READ_THIS_E_MAIL_THEN_EVERYTHING_WORKS_FINE: '' +THIS_PAGE: '' +THIS_PAGE_HAS_BEEN_MODIFIED: '' +THIS_PAGE_HAS_UNSAVED_CHANGES: '' +TITLE: Titre +TOC: Sommaire +TO_DOWNLOAD_THIS_FILE_YOU_NEED_TO_BE_AUTHENTICATED_WITH_THE_ROLE: '' +TRACK_SPAM_AND_SUSPICIOUS_ACTIONS_IN_A_LOGFILE: '' +TRASH: '' +TRUSTED_IPS_FOR_PROXIES_COMMA_SEPARATED: '' +TRY_TO_CONVERT_UPLOADED_IMAGES_INTO_THE_WEBP_FORMAT_FOR_BETTER_PERFORMANCE: '' +TWIG_CACHE: 'Cache Twig' +TYPE_OF_REFERENCE: '' +ULIST: 'Liste simple' +UNPUBLISH: '' +UNPUBLISH_PAGE: '' +UNSAVED_CHANGES: '' +UPLOAD: Télécharger +UPLOAD_AN_IMAGE: 'Télécharger une image' +UPLOAD_FILE: 'Télécharger un fichier' +URL_SCHEMES: '' +USED_AS_FALLBACK_WHEN_NO_MANUAL_DATE_IS_SET: 'Utilisée si aucune date manuelle n''est définie.' +USED_FOR_COPYRIGHT_AND_YEAR_IN_FOOTER: '' +USED_FOR_FRONTEND_LANGUAGE_ATTRIBUTE_PLEASE_USE_ISO_639_1_CODES_LIKE_EN: '' +USED_FOR_TRANSLATIONS_IN_AUTHOR_AREA_THEMES_AND_PLUGINS: '' +USER: Utilisateur +USERDATA_ARE_REQUIRED: '' +USERNAME: 'Nom de l''utilisateur' +USERNAME_IS_REQUIRED: '' +USERNAME_READ_ONLY: 'Nom de l''utilisateur (lecture seule)' +USERROLE: '' +USERROLE_IS_REQUIRED: '' +USERROLE_IS_UNKNOWN: '' +USERS: Utilisateurs +USER_DELETED: '' +USER_HAS_BEEN_UPDATED: '' +USER_NOT_FOUND: '' +USE_A_STRONG_AND_INDIVIDUAL_PASSWORD_FOR_EVERY_ACCOUNT: '' +USE_CAPTCHA_IN_AUTHENTICATION_FORMS: '' +USE_X_FORWARDED_HEADER: '' +VERIFICATION_CODE_MISSING: '' +VERIFY_YOUR_LOGIN_WITH_A_5_DIGIT_CODE_SEND_BY_EMAIL: '' +VERSION: '' +VIDEO: Vidéo +VISIBILITY: '' +VISIT_THE_PLUGIN_DIRECTORY: '' +VISIT_THE_THEME_DIRECTORY: '' +VISUAL: Visuel +VISUAL_EDITOR: 'Editeur visuel' +WEBSITE_OWNER: '' +WEBSITE_RESTRICTION: 'Restriction sur le site' +WEBSITE_TITLE: 'Titre du site' +WELCOME_BACK: '' +WELCOME_TO_TYPEMILL: '' +WE_CANNOT_CREATE_A_FOLDER_ONLY_FILES: '' +WE_COULD_NOT_CREATE_A_SANITIZED_VERSION_OF_THE_SVG_IT_PROBABLY_HAS_INVALID_CONTENT: '' +WE_COULD_NOT_CREATE_THE_FILE_PLEASE_REFRESH_THE_PAGE_AND_CHECK_IF_ALL_FOLDERS_AND_FILES_ARE_WRITABLE: '' +WE_COULD_NOT_CREATE_THE_FOLDER_PLEASE_REFRESH_THE_PAGE_AND_CHECK_IF_ALL_FOLDERS_AND_FILES_ARE_WRITABLE: '' +WE_COULD_NOT_CREATE_THE_USER_PLEASE_CHECK_IF_THE_SETTINGS_FOLDE_IS_WRITABLE: '' +WE_COULD_NOT_DELETE_A_CUSTOM_IMAGE_GRAYSCALE_OR_RESIZED: '' +WE_COULD_NOT_DELETE_THE_LIVE_IMAGE: '' +WE_COULD_NOT_DELETE_THE_ORIGINAL_IMAGE_IN: '' +WE_COULD_NOT_DELETE_THE_THUMB_IMAGE: '' +WE_COULD_NOT_DELETE_THE_USER: '' +WE_COULD_NOT_FIND_A_FOLDERPATH_FOR: '' +WE_COULD_NOT_FIND_OR_READ_THE_PUBLIC_KEY_PEM_IN_THE_SETTINGS_FOLDER: '' +WE_COULD_NOT_FIND_THE_USER: '' +WE_COULD_NOT_FIND_THIS_PAGE_PLEASE_REFRESH_AND_TRY_AGAIN: '' +WE_COULD_NOT_LOAD_THE_SVG_FILE_IT_IS_PROBABLY_CORRUPTED: '' +WE_COULD_NOT_SEND_THE_EMAIL_WITH_THE_PASSWORD_INSTRUCTIONS_TO_YOUR_ADDRESS_REASON: '' +WE_COULD_NOT_SEND_THE_EMAIL_WITH_THE_VERIFICATION_CODE_TO_YOUR_ADDRESS_REASON: '' +WE_COULD_NOT_SEND_THE_TESTMAIL_TO_YOUR_E_MAIL_ADDRESS_REASON: '' +WE_COULD_NOT_STORE_FILE_TO_TEMPORARY_FOLDER: '' +WE_COULD_NOT_STORE_THE_CONTENT: '' +WE_COULD_NOT_STORE_THE_LIVE_IMAGE_TO_THE_LIVE_FOLDER: '' +WE_COULD_NOT_STORE_THE_NEW_USER: '' +WE_COULD_NOT_STORE_THE_ORIGINAL_IMAGE: '' +WE_COULD_NOT_STORE_THE_THUMB_TO_THE_THUMB_FOLDER: '' +WE_DID_NOT_FIND_A_FILE_WITH_THAT_NAME: '' +WE_DID_NOT_FIND_THE_A_USER_OR_USERMAIL: '' +WE_DID_NOT_FIND_THE_FILE_IN_THE_TMP_FOLDER_OR_COULD_NOT_READ_IT: '' +WE_DID_NOT_FIND_THE_IMAGE_IN_THE_TMP_FOLDER_OR_COULD_NOT_READ_IT: '' +WE_FOUND_THE_FILE_BUT_COULD_NOT_DELETE: '' +WE_FOUND_THE_FOLDER_BUT_COULD_NOT_DELETE: '' +WE_FOUND_THE_FOLDER_BUT_COULD_NOT_DELETE_IT: '' +WE_HOPE_YOU_LIKE_TYPEMILL_BECAUSE_WE_CODED_IT_JUST_FOR_YOU: '' +WIDTH_HEIGHT: '' +WRAP_RESTRICTION_NOTICE: '' +WRAP_THE_RESTRICTION_NOTICE_ABOVE_INTO_A_NOTICE_4_ELEMENT_WHICH_CAN_BE_DESIGNED_AS_SPECIAL_BOX: 'Insérer l''avis de restriction ci-dessus dans un élément notice-4 (qui peut être conçu comme special box)' +WRITING: Rédaction +WRONG_PASSWORD_OR_USERNAME_PLEASE_TRY_AGAIN: '' +YEAR: Année +YOUR_ARE_AN: '' +YOUR_BROWSER_CAN_REMEMBER_ALL_OF_YOUR_PASSWORDS: '' +YOUR_EMAIL: '' +YOUR_LICENSE_KEY: '' +YOUR_REGISTRATION_IS_NOT_CONFIRMED_YET_PLEASE_CHECK_YOUR_E_MAILS_AND_USE_THE_CONFIRMATION_LINK: '' +YOUR_TYPEMILL_VERIFICATION_CODE: '' +YOU_ARE_NOT_ALLOWED_TO_DELETE_ANOTHER_USER: '' +YOU_ARE_NOT_ALLOWED_TO_UPDATE_ANOTHER_USER: '' +YOU_CANNOT_CREATE_AN_ITEM_WITH_THE_SLUG_TM_IN_THE_ROOT_FOLDER_BECAUSE_THIS_IS_THE_SYSTEM_PATH: '' +YOU_CAN_OVERWRITE_THE_THEME_CSS_WITH_YOUR_OWN_CSS_HERE: 'Vous pouvez surcharger ici le css du thème avec vos propres styles.' +YOU_CAN_USE_THE_ASTERISK_WILDCARD_TO_SEARCH_FOR_NAME@_OR_@DOMAIN_COM: '' +YOU_DO_NOT_HAVE_ENOUGH_RIGHTS: '' +YOU_TRIED_TO_OPEN_THE_PASSWORD_RESET_PAGE_BUT_THE_LINK_WAS_INVALID: '' +YOU_TRIED_TO_SET_A_NEW_PASSWORD_BUT_USERNAME_OR_TOKEN_WAS_INVALID: '' +©: '' +LICENSE_DATA_INCOMPLETE: '' +LICENSE_DATA_INVALID: '' +NO_LICENSE_FOUND: '' +THE_LICENSE_SERVER_RESPONDED_WITH: '' +THE_SUBSCRIPTION_PERIOD_IS_NOT_PAID_YET: '' +VALIDATION_FAILED: '' diff --git a/system/typemill/author/translations/it.yaml b/system/typemill/author/translations/it.yaml new file mode 100644 index 0000000..18a1485 --- /dev/null +++ b/system/typemill/author/translations/it.yaml @@ -0,0 +1,479 @@ +# Italian (Italiano) pls ignore autoupdates +# Author: Severo Iuliano (https://github.com/iusvar) +ACCESS: Accesso +ACCESS_FOR: 'Accesso per' +ACCESS_RIGHTS: 'Diritti di accesso' +ACCESS_TO_BASEPATH_IS_NOT_ALLOWED: 'L''accesso al percorso di base non è consentito' +ACCOUNT: '' +ACCOUNT_CREATED_PLEASE_LOGIN_WITH_YOUR_USERNAME_AND_PASSWORD_NOW: 'Account creato. Effettua il login con il tuo username e password ora.' +ACTIVATE_API_ACCESS_FOR_THIS_USER_USE_USERNAME_AND_PASSWORD_FOR_API_CALLS: 'Attiva l’accesso API per questo utente. Usa username e password per le chiamate API.' +ACTIVATE_A_PASSWORD_RECOVERY_IN_THE_LOGIN_FORM: 'Attiva il recupero password nel modulo di login.' +ACTIVATE_INDIVIDUAL_RESTRICTIONS_FOR_PAGES_IN_THE_META_TAB_OF_EACH_PAGE: 'Attiva restrizioni individuali per le pagine nel tab meta di ciascuna pagina.' +ACTIVATE_THE_CACHE_FOR_TWIG_TEMPLATES: 'Attiva la cache per i template Twig.' +ACTIVATE_THE_DARKMODE_FOR_ME: 'Attiva il dark mode per me.' +ACTIVATE_YOUR_LICENSE: 'Attiva la tua licenza.' +ACTIVATION_FAILED_BECAUSE_YOU_NEED_A_VALID: 'Attivazione fallita perché necessiti di un valido' +ACTIVE: Attivo +ACTUAL_PASSWORD: '' +ADD: '' +ADD_A_FILE: '' +ADD_A_FOLDER: '' +ADD_DEFINITION: '' +ADD_DESCRIPTION: '' +ADD_ENTRY: 'Aggiungi voce' +ADD_LEFT_COLUMN: '' +ADD_MORE_URL_SCHEMES_FOR_EXTERNAL_LINKS_E_G_LIKE_DICT_COMMA_SEPARATED_LIST: 'Aggiungi altri schemi URL per link esterni, es. come dict:// (lista separata da virgola)' +ADD_NOINDEX_TAG_AND_EXCLUDE_FROM_SITEMAP: 'Aggiungi tag noindex ed escludi dalla sitemap.' +ADD_ONE_OR_MORE_USERNAMES_SEPARATED_WITH_COMMA: 'Aggiungi uno o più nomi utente separati da virgola.' +ADD_RIGHT_COLUMN: '' +ADD_ROW_ABOVE: '' +ADD_ROW_BELOW: '' +ALLOWED_DOMAINS_FOR_API_ACCESS_CORS_HEADERS: 'Domini consentiti per l’accesso API (CORS HEADERS)' +ALLOWED_DOMAINS_FOR_CONTENT_ON_TYPEMILL_CSP_HEADERS: 'Domini consentiti per il contenuto su Typemill (CSP HEADERS)' +ALLOW_SVG: 'Consenti SVG' +ALLOW_THE_UPLOAD_OF_SVG_IMAGES: 'Consenti il caricamento di immagini SVG.' +ALL_PAGES: 'Tutte le pagine' +ALTERNATIVE_TEXT_FOR_THE_HERO_IMAGE: '' +ALT_TEXT: '' +ALWAYS_SHOW: 'Mostra sempre' +AND_YOU_CANNOT: 'e non puoi' +AND_YOU_MIGHT_FIND_THE_FOLLOWING_TIPS_HELPFUL: 'e potresti trovare utili i seguenti consigli:' +ANSWER_FROM_LICENSE_SERVER: '' +API_ACCESS: 'Accesso API' +ARTICLE_DATE: 'Data articolo' +AUTHOR: '' +AUTHOR_DESCRIPTION_MARKDOWN: 'Descrizione autore (Markdown)' +BACK_TO_LOGIN: 'Torna al login' +BLOCK_ID_NOT_FOUND: 'ID del blocco non trovato' +BOLD: '' +BULLET_LIST: '' +BUY_A_LICENSE: 'Acquista una licenza' +CANCEL: '' +CAN_BE_USED_FOR_AUTHOR_LINE_IN_FRONTEND: 'Può essere utilizzato per la linea dell’autore in frontend.' +CAPTION: '' +CC_BY: '' +CC_BY_NC: '' +CC_BY_NC_ND: '' +CC_BY_NC_SA: '' +CC_BY_ND: '' +CC_BY_SA: '' +CENTER: '' +CHANGE_SLUG: '' +CHECK: '' +CHECK_YOUR_INBOX: 'Controlla la tua casella di posta' +CHECK_YOUR_LICENSE: 'Verifica la tua licenza' +CLASS: '' +CLEAR: Pulisci +CLOSE: Chiudi +CLOSE_LIBRARY: '' +CODE: '' +COLLAPSE_ALL: '' +CONFIGURE: '' +CONFIRM: '' +CONTENT_BREAK: 'Interruzione di contenuto' +CONVERT_TO_WEBP: 'Converti in WebP' +COPYRIGHT: '' +COPY_THE_CONTENT_OF_THE_REFERENCED_INTERNAL_PAGE: 'Copia il contenuto della pagina interna di riferimento' +COULD_NOT_CREATE_FOLDER: 'Impossibile creare la cartella' +COULD_NOT_DECODE_IMAGE_OR_FILE_PROBABLY_NOT_A_BASE64_ENCODING: 'Impossibile decodificare immagine o file, probabilmente non è una codifica base64' +COULD_NOT_DELETE_FILE: 'Impossibile eliminare il file' +COULD_NOT_DELETE_FOLDER: 'Impossibile eliminare la cartella' +COULD_NOT_GET_THE_VIDEO_IMAGE: 'Impossibile ottenere l’immagine del video' +COULD_NOT_OPEN_AND_READ_THE_FILE: 'Impossibile aprire e leggere il file' +COULD_NOT_READ_PATHINFO: 'Impossibile leggere pathinfo' +COULD_NOT_STORE_THE_CUSTOM_SIZE_OF: 'Impossibile memorizzare la dimensione personalizzata di' +COULD_NOT_STORE_THE_IMAGE_IN_THE_TEMPORARY_FOLDER: 'Impossibile memorizzare l’immagine nella cartella temporanea' +COULD_NOT_STORE_THE_RESIZED_VERSION: 'Impossibile memorizzare la versione ridimensionata' +COULD_NOT_WRITE_TO_THE_FILE: 'Impossibile scrivere sul file' +CREATED_AT_READ_ONLY: 'Creato il (sola lettura)' +CREATE_NEW_PASSWORD: 'Crea nuova password' +CREATE_POST: 'Crea post' +CREATE_USER: '' +CUSTOM_CSS: '' +CUT_RESTRICTED_CONTENT_AFTER_THE_FIRST_HR_ELEMENT_ON_A_PAGE_PER_DEFAULT_CONTENT_WILL_BE_CUT_AFTER_TITLE: 'Taglia il contenuto riservato dopo il primo elemento hr in una pagina (per default il contenuto verrà tagliato dopo il titolo).' +DARKMODE: 'Modalità scura' +DEAR: Caro +DEAR_USER: 'Caro utente' +DEFAULT_WIDTH_OF_LIVE_IMAGES_IS_820PX_CHANGES_WILL_APPLY_TO_FUTURE_UPLOADS: 'La larghezza predefinita delle immagini live è 820px, le modifiche si applicheranno ai futuri caricamenti' +DEFINITION: '' +DEFINITION_LIST: '' +DELETE: '' +DELETE_COLUMN: '' +DELETE_DESCRIPTION: '' +DELETE_PAGE: '' +DELETE_ROW: '' +DELETE_USER: '' +DEVELOPER: '' +DISABLE: Disattiva +DISABLE_ALL_CSP_HEADERS_CONTENT_SECURITY_POLICY_FOR_THIS_WEBSITE: 'Disattiva tutti gli header CSP (Content Security Policy) per questo sito web' +DISABLE_ALL_CUSTOM_HEADERS_OF_TYPEMILL_EXCEPT_CORS_AND_SEND_YOUR_OWN_HEADERS_INSTEAD: 'Disattiva tutti gli header personalizzati di Typemill tranne CORS e invia invece i tuoi header' +DISABLE_CSP_HEADERS: 'Disattiva gli header CSP' +DISABLE_CUSTOM_HEADERS: 'Disattiva gli header personalizzati' +DISCARD: '' +DISCARD_CHANGES: '' +DISPLAY_APPLICATION_ERRORS: 'Mostra errori dell’applicazione' +DOES_NOT_EXIST: 'Non esiste' +DOES_NOT_EXIST_OR_IS_NOT_WRITABLE: 'Non esiste o non è scrivibile' +DOMAIN_FOR_LICENSE: 'Dominio per la licenza' +DONATE: Dona +DONE: Fatto +DO_NOT_FORGET_TO_CHECK_YOUR_SPAM_FOLDER_IF_YOUR_INBOX_IS_EMPTY: 'Non dimenticare di controllare la cartella spam se la tua casella di posta è vuota' +DO_NOT_RESIZE: 'Non ridimensionare' +DO_YOU_REALLY_WANT_TO_DELETE_THIS_PAGE: '' +DO_YOU_REALLY_WANT_TO_DELETE_THIS_USER: 'Vuoi davvero eliminare questo utente?' +DO_YOU_WANT_TO_DISCARD_YOUR_CHANGES_AND_SET_THE_CONTENT_BACK_TO_THE_LIVE_VERSION: '' +DRAFT: '' +DRAG_A_PICTURE_OR_CLICK_TO_SELECT: '' +DUTCH_FLEMISH: '' +EDIT: '' +EMAIL: Email +EMAIL_ADDRESS_IN_SYSTEM_SETTINGS_IS_MISSING: 'L’indirizzo email nelle impostazioni di sistema è mancante' +EMAIL_SUBJECT: 'Oggetto dell’email' +ENGLISH: '' +ENTER_AN_EMAIL_ADDRESS_THAT_SENDS_THE_E_MAILS_SENDER_THE_E_MAIL_FEATURE_WILL_BE_USED_FOR_RECOVERY_AND_VERIFICATION_E_MAILS_SEND_A_TESTMAIL_TO_YOUR_USER_ACCOUNT_TO_VERIFY_THAT_YOU_RECEIVE_THE_E_MAILS: 'Inserisci un indirizzo email che invia le email (mittente). La funzionalità email verrà utilizzata per email di recupero e verifica. Invia una email di prova al tuo account utente per verificare che tu riceva le email.' +ENTER_THE_EMAIL_OF_YOUR_USER_ACCOUNT_CLICK_THE_RECOVER_BUTTON_AND_CHECK_YOUR_MAILBOX_FOR_FURTHER_INSTRUCTIONS: 'Inserisci l’email del tuo account utente, clicca sul pulsante di recupero e controlla la tua casella di posta per ulteriori istruzioni.' +ENTER_THE_FULL_DOMAIN_LIKE_HTTPS_WWWW_MYWEBSITE_DE: 'Inserisci il dominio completo come https://www.miosito.it' +ENTER_THE_VERIFICATION_CODE_FROM_YOUR_EMAIL: 'Inserisci il codice di verifica dalla tua email' +ENTER_YOUR_E_MAIL_ADDRESS_THAT_YOU_USED_FOR_YOUR_LICENSE_PURCHASE: 'Inserisci l’indirizzo email che hai usato per l’acquisto della tua licenza' +ENTER_YOUR_LICENSE_KEY_THAT_YOU_GOT_AFTER_YOUR_PURCHASE_VIA_EMAIL: 'Inserisci la tua chiave di licenza che hai ottenuto dopo l’acquisto via email' +ERROR_REPORTING: '' +ERROR_SENDING_EMAIL: 'Errore nell’invio dell’email' +EXPAND_ALL: 'Espandi tutto' +EXTENSION_OR_FILENAME_ARE_MISSING: 'Manca l’estensione o il nome del file' +EXTENSION_OR_NAME_FOR_IMAGE_IS_MISSING: 'Manca l’estensione o il nome dell’immagine' +EXTERNAL_LINK: 'Link esterno' +E_MAIL: '' +FAVICON: '' +FILE: '' +FILENAME_IS_MISSING: 'Manca il nome del file' +FILENAME_OR_USERROLE_IS_MISSING: 'Manca il nome del file o il ruolo dell’utente' +FILETYPE_IS_NOT_ALLOWED: 'Tipo di file non consentito' +FILE_DELETED_SUCCESSFULLY: 'File eliminato con successo' +FILE_HAS_BEEN_STORED: 'Il file è stato memorizzato' +FILE_IS_BIGGER_THAN_20MB: 'Il file è più grande di 20MB' +FILE_IS_EMPTY: 'Il file è vuoto' +FILE_IS_MISSING: 'Il file manca' +FILE_NOT_FOUND: 'File non trovato' +FILE_SAVED_SUCCESSFULLY: 'File salvato con successo' +FIRST_NAME: '' +FOLDER: '' +FORGOT_PASSWORD: '' +FORGOT_YOUR_PASSWORD: 'Hai dimenticato la password?' +FORMAT_OPTIONS_FOR_VISUAL_EDITOR: 'Opzioni di formato per l’editor visuale' +FRENCH: Francese +FROM_MAIL_IS_REQUIRED_FOR_THIS_FEATURE_SEND_A_TESTMAIL_BEFORE_YOU_USE_THIS_FEATURE: 'È richiesta la mail del mittente per questa funzione. Invia una mail di prova prima di utilizzare questa funzione.' +FROM_MAIL_IS_REQUIRED_FOR_THIS_FEATURE_SEND_A_TESTMAIL_BEFORE_YOU_USE_THIS_FEATURE_MAKE_SURE_YOU_HAVE_FTP_ACCESS_TO_DISABLE_THE_FEATURE_IN_SETTINGS_YAML_ON_FAILURE_THE_VERIFICATION_CODE_WILL_BE_VALID_FOR_5_MINUTES_BE_AWARE_THAT_DEVICE_FINGERPRINTS_WILL_BE_STORED_IN_THE_USER_ACCOUNTS: 'È richiesta la mail del mittente per questa funzione. Invia una mail di prova prima di utilizzare questa funzione. Assicurati di avere accesso FTP per disabilitare la funzione in settings.yaml in caso di fallimento. Il codice di verifica sarà valido per 5 minuti. Tieni presente che le impronte digitali dei dispositivi verranno memorizzate negli account utente.' +GERMAN: '' +GET_INSPIRED_AND_ENJOY_YOUR_WRITING: 'Lasciati ispirare e goditi la scrittura' +GOOGLE_SITEMAP_READONLY: 'Sitemap di Google (sola lettura)' +GO_TO_LOGIN: 'Vai al login' +HAS_EDIT_RIGHTS_FOR_THIS_ARTICLE: 'Ha diritti di modifica per questo articolo.' +HEADLINE: '' +HEADLINE_ANCHORS: '' +HERO_IMAGE: '' +HEY_WRITER_AUTHOR_EDITOR_CONTENT_GURU_OR_WEBSITE_MANAGER: 'Ehi scrittore, autore, editor, guru dei contenuti o gestore del sito web' +HIDE: '' +HIDE_PAGE_FROM_NAVIGATION: '' +HOMEPAGE: '' +HORIZONTAL_LINE: '' +HR: '' +IF_NOT_FILLED_THE_DESCRIPTION_IS_EXTRACTED_FROM_CONTENT: '' +IF_YOUR_PROXY_DOES_NOT_WORK_TRY_TO_ADD_THE_BASE_URL_OF_YOUR_PROXY_HERE_LIKE_HTTPS_MYWEBSITE_COM: '' +IF_YOU_ADD_A_VALUE_FOR_THE_HEIGHT_THEN_THE_IMAGE_WILL_BE_CROPPED: '' +IF_YOU_DID_NOT_MAKE_THIS_LOGIN_ATTEMPT_PLEASE_RESET_YOUR_PASSWORD_IMMEDIATELY: 'Se non hai effettuato questo tentativo di accesso, ti preghiamo di reimpostare immediatamente la tua password.' +IF_YOU_DID_NOT_RECEIVE_AN_EMAIL_WITH_THE_VERIFICATION_CODE_THEN_THE_USERNAME_OR_PASSWORD_YOU_ENTERED_WAS_WRONG_PLEASE_TRY_AGAIN: 'Se non hai ricevuto un email con il codice di verifica, allora il nome utente o la password che hai inserito erano sbagliati. Per favore prova di nuovo.' +IF_YOU_UNPUBLISH_THE_PAGE_THEN_WE_WILL_DELETE_THE_PUBLISHED_VERSION_AND_KEEP_THE_MODIFIED_VERSION: 'Se annulli la pubblicazione della pagina, elimineremo la versione pubblicata e conserveremo la versione modificata.' +IF_YOU_WANT_TO_KEEP_YOUR_CHANGES_THEN_CLICK_ON_CANCEL_AND_SAVE_YOUR_CHANGES_BEFORE_YOU_SWITCH_TO_THE_VISUAL_EDITOR: '' +IMAGE: '' +IMAGENAME_IS_MISSING: 'Manca il nome dell’immagine' +IMAGES_WITH_THIS_EXTENSION_ARE_NOT_ALLOWED: 'Immagini con questa estensione non sono consentite' +IMAGE_DELETED_SUCCESSFULLY: 'Immagine eliminata con successo' +IMAGE_OR_FILENAME_IS_MISSING: 'Manca l’immagine o il nome del file' +IMAGE_OR_NAME_IS_MISSING: 'Manca l’immagine o il nome' +IMAGE_SAVED_SUCCESSFULLY: 'Immagine salvata con successo' +IMAGE_URL: '' +IMAGE_URL_READ_ONLY: '' +INFO: Informazioni +INVALID_VERIFICATION_CODE_FORMAT_PLEASE_TRY_AGAIN: 'Formato del codice di verifica non valido, per favore riprova' +INVISIBLE: Invisibile +IS_NOT_A_DIRECTORY: 'Non è una directory' +IS_NOT_A_FOLDER: 'Non è una cartella' +ITALIAN: '' +ITALIC: '' +IT_IS_NOT_ALLOWED_TO_WRITE_INTO: 'Non è permesso scrivere dentro' +LANGUAGE: '' +LANGUAGE_ATTRIBUTE_WEBSITE: 'Attributo lingua (sito web)' +LANGUAGE_AUTHOR_AREA: 'Lingua (area autore)' +LAST_MODIFIED_LIVE_READONLY: 'Ultima modifica live (sola lettura)' +LAST_NAME: '' +LEFT: '' +LICENCE_HAS_BEEN_STORED: '' +LICENSE: Licenza +LICENSE_AND_YOUR_WEBSITE_MUST_RUN_UNDER_THE_DOMAIN_OF_YOUR_LICENSE: '' +LICENSE_DATA_ARE_INCOMPLETE: '' +LICENSE_DATA_MISSING: '' +LICENSE_REQUIRED: '' +LICENSE_VALIDATION_FAILED: '' +LINK: '' +LINK_TO_AN_EXTERNAL_PAGE: '' +LINK_TO_YOUTUBE: '' +LIST_ALL_DOMAINS_SEPARATED_BY_COMMAS_TO_ALLOW_CONTENT_INTEGRATION_SUCH_AS_IFRAMES_ON_YOUR_TYPEMILL_WEBSITE_DOMAINS_WILL_BE_ADDED_TO_THE_CSP_HEADER_USUALLY_DONE_WITH_PLUGINS_AND_THEMES_BUT_ADD_MANUALLY_IF_SOMETHING_IS_BLOCKED: 'Elenca tutti i domini separati da virgole per consentire l’integrazione di contenuti come iframe sul tuo sito web Typemill. I domini verranno aggiunti all’header CSP, solitamente fatto con plugin e temi, ma aggiungi manualmente se qualcosa è bloccato.' +LIST_ALL_DOMAINS_SEPARATED_BY_COMMA_THAT_SHOULD_HAVE_ACCESS_TO_THE_TYPEMILL_API_DOMAINS_WILL_BE_ADDED_TO_THE_CORS_HEADER: 'Elenca tutti i domini separati da virgola che dovrebbero avere accesso all’API di Typemill. I domini verranno aggiunti all’header CORS.' +LOGIN: '' +LOGIN_TO_THE_AUTHOR_AREA_OR_GO_TO_THE: 'Accedi all’area autore o vai al' +LOGIN_VERIFICATION_RECOMMENDED: 'Verifica login raccomandata' +LOGO: '' +MAIL_FROM_NAME_OPTIONAL: '' +MAIL_FROM_REQUIRED: '' +MANUAL_DATE: 'Data manuale' +MARKDOWN: '' +MARKDOWN_IS_MISSING: '' +MAXIMUM_SIZE_FOR_AN_IMAGE_IS_5_MB_HERO_IMAGES_ARE_NOT_SUPPORTED_BY_ALL_THEMES: '' +MAXIMUM_SIZE_FOR_FILE_UPLOADS_IN_MB: '' +MAXIMUM_SIZE_FOR_IMAGE_UPLOADS_IN_MB: '' +MAXIMUM_SIZE_OF_FILE_LINK_IS_100_CHARACTERS: '' +MAXIMUM_SIZE_OF_FILE_TEXT_IS_100_CHARACTERS: '' +MAXIMUM_SIZE_OF_IMAGE_ALT_TEXT_IS_100_CHARACTERS: 'La dimensione massima del testo alternativo dell’immagine è di 100 caratteri' +MAXIMUM_SIZE_OF_IMAGE_CAPTION_IS_140_CHARACTERS: 'La dimensione massima della didascalia dell’immagine è di 140 caratteri' +MAXIMUM_SIZE_OF_IMAGE_CLASS_IS_100_CHARACTERS: 'La dimensione massima della classe dell’immagine è di 100 caratteri' +MAXIMUM_SIZE_OF_IMAGE_ID_IS_100_CHARACTERS: 'La dimensione massima dell’ID dell’immagine è di 100 caratteri' +MAXIMUM_SIZE_OF_IMAGE_LINK_IS_100_CHARACTERS: 'La dimensione massima del link dell’immagine è di 100 caratteri' +MAXIMUM_SIZE_OF_IMAGE_TITLE_IS_100_CHARACTERS: 'La dimensione massima del titolo dell’immagine è di 100 caratteri' +MEDIA: '' +MEDIALIB: '' +MENU: '' +META_CONTENT: '' +META_DESCRIPTION: '' +META_TITLE: '' +MINIMUM_USER_ROLE_TO_ACCESS_THIS_PAGE: 'Ruolo utente minimo per accedere a questa pagina' +NAVIGATION_TITLE: 'Titolo di navigazione' +NEW_PASSWORD: '' +NEW_USER: 'Nuovo utente' +NEW_USER_CREATED: 'Nuovo utente creato' +NOINDEX: Noindex +NOTICE: '' +NO_FILE_FOUND: 'Nessun file trovato' +NO_IMAGE_FOUND: 'Nessuna immagine trovata' +NO_PROBLEM_YOU_CAN_CREATE_A_NEW_ONE_HERE: 'Nessun problema, puoi crearne uno nuovo qui' +NUMBERED_LIST: 'Lista numerata' +OLIST: 'Elenco ordinato' +ONE_OR_MORE_FILES_COULD_NOT_BE_TRANSFORMED: 'Uno o più file non hanno potuto essere trasformati' +ONLY_IMAGES_ARE_ALLOWED: 'Sono consentite solo immagini' +ONLY_PNG_FORMAT_WILL_WORK: 'Funziona solo il formato PNG' +ONLY_THE_FOLLOWING_USERS_HAVE_ACCESS: 'Solo i seguenti utenti hanno accesso' +OPEN: '' +OPTIONALLY_ENTER_A_NAME_FOR_THE_SENDER_ADDRESS_IF_NOT_SET_THE_FROM_ADDRESS_WILL_BE_VISIBLE: 'Inserisci facoltativamente un nome per l’indirizzo del mittente. Se non impostato, sarà visibile l’indirizzo del mittente.' +OPTIONALLY_ENTER_A_REPLY_TO_ADDRESS_FOR_ANSWERS_FROM_THE_RECEIVER_IF_NOT_SET_ANSWERS_WILL_GO_TO_THE_FROM_ADDRESS: 'Inserisci facoltativamente un indirizzo di risposta per le risposte del destinatario. Se non impostato, le risposte andranno all’indirizzo del mittente.' +OWNER_USERNAME: 'Proprietario (nome utente)' +PAGES_SORT_IN_NAVIGATION_WITH_DRAG_DROP: 'Le pagine si ordinano nella navigazione con trascinamento e rilascio' +PAGE_NOT_FOUND: 'Pagina non trovata' +PAGE_RESTRICTION: 'Restrizione della pagina' +PARAGRAPH: Paragrafo +PASSWORD: '' +PATH_IS_MISSING: 'Manca il percorso' +PERMANENT_REDIRECT_301_THE_USER_TO_THE_REFERENCED_INTERNAL_PAGE: 'Reindirizzamento permanente (301) dell’utente alla pagina interna di riferimento' +PERMISSION_DENIED: 'Permesso negato' +PLEASE_CHECK_IF_THERE_IS_A_READABLE_FILE_PUBLIC_KEY_PEM_IN_YOUR_SETTINGS_FOLDER: 'Si prega di verificare se esiste un file leggibile public_key.pem nella cartella delle impostazioni' +PLEASE_CHECK_THE_INBOX_OF_YOUR_EMAIL_ACCOUNT_FOR_MORE_INSTRUCTIONS: 'Controlla la casella di posta del tuo account email per ulteriori istruzioni' +PLEASE_CONFIRM: 'Si prega di confermare' +PLEASE_CORRECT_YOUR_INPUT: 'Correggi il tuo input' +PLEASE_ENTER_A_VALID_EMAIL: 'Inserisci un’e-mail valida' +PLEASE_LOGIN_WITH_YOUR_NEW_PASSWORD: 'Effettua il login con la tua nuova password' +PLEASE_SELECT: 'Si prega di selezionare' +PLEASE_USE_THE_FOLLOWING_LINK_TO_SET_A_NEW_PASSWORD: 'Usa il seguente link per impostare una nuova password' +PLUGINS: '' +PLUGIN_SETTINGS: '' +POSTS_SORTED_BY_PUBLISH_DATE_FOR_NEWS_OR_BLOGS: 'Post ordinati per data di pubblicazione per notizie o blog' +PROFILE_IMAGE: 'Immagine del profilo' +PROXY: '' +PUBLISH: Pubblica +QUOTE: Citazione +QUOTES: '' +RAW: '' +RAW_EDITOR: 'Editor raw' +RECOVER_PASSWORD: 'Recupera password' +REFERENCE: Riferimento +REFERENCE_TO_PAGE: 'Riferimento alla pagina' +REPEAT_PASSWORD: 'Ripeti password' +REPLY_TO_OPTIONAL: 'Rispondi a (opzionale)' +REQUIRED: Richiesto +RESTRICTION_NOTICE_USE_MARKDOWN: 'Avviso di restrizione (usa Markdown)' +RIGHT: Destra +ROLE: Ruolo +RUSSIAN: Russo +SAVE: '' +SEARCH: Cerca +SECURITY: Sicurezza +SECURITY_LOG: 'Registro di sicurezza' +SELECT_FROM_MEDIALIB: 'Seleziona dalla medialib' +SELECT_THE_LOWEST_USERROLE_HIGHER_ROLES_WILL_HAVE_ACCESS_TOO: 'Seleziona il ruolo utente più basso. I ruoli più alti avranno accesso anche loro.' +SETTINGS_HAVE_BEEN_SAVED: 'Le impostazioni sono state salvate' +SETUP: Impostazione +SHORTCODE: Shortcode +SHORTCODES: Shortcodes +SHORT_TITLE_FOR_POST: 'Titolo breve per il post' +SHOW_AFTER_FIRST_WRONG_INPUT: 'Mostra dopo il primo input errato' +SHOW_ANCHORS_NEXT_TO_HEADLINE_IN_FRONTEND: 'Mostra ancoraggi accanto al titolo in frontend' +SHOW_THE_WEBSITE_ONLY_TO_AUTHENTICATED_USERS_AND_REDIRECT_ALL_OTHER_USERS_TO_THE_LOGIN_PAGE: 'Mostra il sito web solo agli utenti autenticati e reindirizza tutti gli altri utenti alla pagina di login' +SLUG: Slug +SOMEONE_TRIED_TO_LOG_IN_TO_YOUR_TYPEMILL_WEBSITE_AND_WE_WANT_TO_MAKE_SURE_IT_IS_YOU_ENTER_THE_FOLLOWING_VERIFICATION_CODE_TO_FINISH_YOUR_LOGIN_THE_CODE_WILL_BE_VALID_FOR_5_MINUTES: 'Qualcuno ha tentato di accedere al tuo sito web Typemill e vogliamo assicurarci che sia tu. Inserisci il seguente codice di verifica per completare il tuo accesso. Il codice sarà valido per 5 minuti.' +SOMETHING_WENT_WRONG_PLEASE_REFRESH_THE_PAGE_AND_CHECK_IF_ALL_FOLDERS_AND_FILES_ARE_WRITABLE: 'Qualcosa è andato storto. Aggiorna la pagina e verifica se tutte le cartelle e i file sono scrivibili' +SOMETHING_WENT_WRONG_THE_INPUT_IS_NOT_VALID: 'Qualcosa è andato storto. L’input non è valido' +SPECIAL_CHARACTERS_ARE_NOT_ALLOWED_LENGTH_BETWEEN_1_AND_40: '' +STANDARD_EDITOR_MODE: 'Modalità editor standard' +STANDARD_HEIGHT_FOR_LIVE_PICTURES: 'Altezza standard per le immagini live' +STANDARD_WIDTH_FOR_LIVE_PICTURES: 'Larghezza standard per le immagini live' +SUBMIT_THE_URL_ABOVE_IN_GOOGLE_SEARCH_CONSOLE_TO_SUPPORT_INDEXING: 'Invia l’URL sopra a Google Search Console per supportare l’indicizzazione' +SWITCH_TO_VISUAL: '' +SYSTEM: Sistema +SYSTEMCHECK: 'Controllo del sistema' +SYSTEM_SETTINGS: 'Impostazioni del sistema' +TABLE: Tabella +TABLE_OF_CONTENTS: 'Indice dei contenuti' +TAB_NOT_FOUND: 'Tab non trovato' +TEMPORARY_REDIRECT_302_THE_USER_TO_THE_REFERENCED_INTERNAL_PAGE: 'Reindirizzamento temporaneo (302) dell’utente alla pagina interna di riferimento' +TESTMAIL_FROM_TYPEMILL: 'Email di prova da Typemill' +TEXT_BEFORE_RECOVER_LINK_IN_EMAIL_MESSAGE: 'Testo prima del link di recupero nel messaggio email' +TEXT_FILE: 'File di testo' +THEMES: Temi +THEME_SETTINGS: 'Impostazioni del tema' +THERE_ARE_PUBLISHED_PAGES_WITHIN_THIS_FOLDER_THE_PAGES_ARE_NOT_VISIBLE_ON_YOUR_WEBSITE_ANYMORE: 'Ci sono pagine pubblicate all’interno di questa cartella. Le pagine non sono più visibili sul tuo sito web.' +THERE_IS_ALREADY_A_PAGE_WITH_THAT_SLUG: 'Esiste già una pagina con quel slug' +THERE_IS_ALREADY_A_PAGE_WITH_THIS_NAME_PLEASE_CHOOSE_ANOTHER_NAME: 'Esiste già una pagina con questo nome. Si prega di scegliere un altro nome.' +THERE_WAS_AN_ERROR_CHECKING_THE_LICENSE_SIGNATURE: 'Si è verificato un errore durante la verifica della firma della licenza' +THE_BEST_OPTION_IS_A_SEPARATE_PASSWORD_MANAGER_LIKE_KEEPASS_AND_OTHERS: 'La migliore opzione è un gestore di password separato come KeePass e altri.' +THE_CAPTCHA_IS_WRONG_PLEASE_TRY_AGAIN: 'Il captcha è errato, per favore riprova' +THE_FOLDER_CONTAINS_ANOTHER_FOLDER_SO_WE_CANNOT_TRANSFORM_IT_PLEASE_MAKE_SURE_THERE_ARE_ONLY_FILES_IN_THIS_FOLDER: 'La cartella contiene un’altra cartella, quindi non possiamo trasformarla. Assicurati che ci siano solo file in questa cartella.' +THE_FOLDER_CONTAINS_PUBLISHED_PAGES_PLEASE_UNPUBLISH_OR_DELETE_THEM_FIRST: 'La cartella contiene pagine pubblicate. Si prega di annullare la pubblicazione o eliminarle prima.' +THE_FOLLOWING_REQUIREMENTS_FOR_THE_INSTALLATION_ARE_MISSING: 'Mancano i seguenti requisiti per l’installazione' +THE_FROM_MAIL_IS_MISSING_OR_IT_IS_NOT_A_VALID_E_MAIL_ADDRESS: 'Manca la mail del mittente o non è un indirizzo email valido' +THE_ID_OF_THE_CONTENT_BLOCK_IS_WRONG: 'L’ID del blocco di contenuto è sbagliato' +THE_LICENSE_DATA_ARE_INVALID: '' +THE_LINK_TO_RECOVER_THE_PASSWORD_WAS_TOO_OLD_PLEASE_CREATE_A_NEW_ONE: 'Il link per recuperare la password era troppo vecchio. Si prega di crearne uno nuovo.' +THE_MAXIMUM_FILE_SIZE_MIGHT_BE_LIMITED_BY_YOUR_SERVER_SETTINGS: 'La dimensione massima del file potrebbe essere limitata dalle impostazioni del tuo server.' +THE_MAXIMUM_IMAGE_SIZE_MIGHT_BE_LIMITED_BY_YOUR_SERVER_SETTINGS: 'La dimensione massima dell’immagine potrebbe essere limitata dalle impostazioni del tuo server.' +THE_MIME_TYPE_IS_MISSING_NOT_ALLOWED_OR_DOES_NOT_FIT_TO_THE_FILE_EXTENSION: 'Manca il tipo MIME, non è consentito o non si adatta all’estensione del file' +THE_PLUGIN_OR_THEMES_WAS_NOT_FOUND: 'Il plugin o i temi non sono stati trovati' +THE_RECOVER_LINK_WILL_BE_ACTIVE_FOR_24_HOURS: 'Il link di recupero sarà attivo per 24 ore.' +THE_REQUESTED_FILETYPE_DOES_NOT_EXIST: 'Il tipo di file richiesto non esiste.' +THE_REQUESTED_FILE_DOES_NOT_EXIST: 'Il file richiesto non esiste.' +THE_SUBSCRIPTION_PERIOD_HAS_NOT_BEEN_PAID_YET_AND_WE_GOT_AN_ERROR: '' +THE_SUBSCRIPTION_PERIOD_HAS_NOT_BEEN_PAID_YET_WE_WILL_CHECK_IT_EVERY_60_MINUTES: '' +THE_TESTMAIL_HAS_BEEN_SEND_PLEASE_CHECK_YOUR_INBOX_AND_YOUR_SPAM_FOLDER_TO_VARIFY_THAT_YOU_RECEIVED_THE_MAIL: 'L’email di prova è stata inviata. Controlla la tua casella di posta e la cartella spam per verificare che tu abbia ricevuto la mail.' +THE_VERIFICATION_WAS_WRONG_OR_OUTDATED_PLEASE_START_AGAIN: 'La verifica era errata o obsoleta. Si prega di ricominciare.' +THE_VERSION_CHECK_FAILED_BECAUSE_OF_INVALID_PARAMETERS: 'Il controllo della versione è fallito a causa di parametri non validi.' +THE_WEBSITE_IS_RUNNING_NOT_UNDER_THE_DOMAIN_OF_YOUR_LICENSE: '' +THIS: Questo +THIS_FIELD_IS_NOT_DEFINED: 'Questo campo non è definito' +THIS_FOLDER_CONTAINS: 'Questa cartella contiene' +THIS_IS_A_TESTMAIL_FROM_TYPEMILL_AND_IF_YOU_READ_THIS_E_MAIL_THEN_EVERYTHING_WORKS_FINE: 'Questa è un’email di prova da Typemill e se stai leggendo questa email allora tutto funziona bene.' +THIS_PAGE: 'Questa pagina' +THIS_PAGE_HAS_BEEN_MODIFIED: 'Questa pagina è stata modificata' +THIS_PAGE_HAS_UNSAVED_CHANGES: '' +TITLE: '' +TOC: '' +TO_DOWNLOAD_THIS_FILE_YOU_NEED_TO_BE_AUTHENTICATED_WITH_THE_ROLE: 'Per scaricare questo file devi essere autenticato con il ruolo:' +TRACK_SPAM_AND_SUSPICIOUS_ACTIONS_IN_A_LOGFILE: 'Traccia spam e azioni sospette in un file di registro' +TRASH: Cestino +TRUSTED_IPS_FOR_PROXIES_COMMA_SEPARATED: 'IP fidati per proxy (separati da virgola)' +TRY_TO_CONVERT_UPLOADED_IMAGES_INTO_THE_WEBP_FORMAT_FOR_BETTER_PERFORMANCE: 'Prova a convertire le immagini caricate nel formato WebP per una migliore performance' +TWIG_CACHE: 'Cache di Twig' +TYPE_OF_REFERENCE: 'Tipo di riferimento' +ULIST: 'Elenco non ordinato' +UNPUBLISH: 'Annulla pubblicazione' +UNPUBLISH_PAGE: 'Annulla pubblicazione pagina' +UNSAVED_CHANGES: '' +UPLOAD: '' +UPLOAD_AN_IMAGE: 'Carica un’immagine' +UPLOAD_FILE: 'Carica un file' +URL_SCHEMES: 'Schemi URL' +USED_AS_FALLBACK_WHEN_NO_MANUAL_DATE_IS_SET: 'Utilizzato come fallback quando non è impostata nessuna data manuale.' +USED_FOR_COPYRIGHT_AND_YEAR_IN_FOOTER: 'Utilizzato per il copyright e l’anno nel piè di pagina' +USED_FOR_FRONTEND_LANGUAGE_ATTRIBUTE_PLEASE_USE_ISO_639_1_CODES_LIKE_EN: 'Utilizzato per l’attributo lingua frontend. Si prega di utilizzare codici ISO 639-1 come en.' +USED_FOR_TRANSLATIONS_IN_AUTHOR_AREA_THEMES_AND_PLUGINS: 'Utilizzato per le traduzioni nell’area autore, temi e plugin.' +USER: '' +USERDATA_ARE_REQUIRED: 'I dati utente sono richiesti' +USERNAME: '' +USERNAME_IS_REQUIRED: 'Il nome utente è richiesto' +USERNAME_READ_ONLY: '' +USERROLE: 'Ruolo utente' +USERROLE_IS_REQUIRED: 'Il ruolo utente è richiesto' +USERROLE_IS_UNKNOWN: 'Il ruolo utente è sconosciuto' +USERS: '' +USER_DELETED: 'Utente eliminato' +USER_HAS_BEEN_UPDATED: 'L’utente è stato aggiornato' +USER_NOT_FOUND: 'Utente non trovato' +USE_A_STRONG_AND_INDIVIDUAL_PASSWORD_FOR_EVERY_ACCOUNT: 'Usa una password forte e individuale per ogni account' +USE_CAPTCHA_IN_AUTHENTICATION_FORMS: 'Usa CAPTCHA nei moduli di autenticazione' +USE_X_FORWARDED_HEADER: 'Usa l’header X-Forwarded' +VERIFICATION_CODE_MISSING: 'Codice di verifica mancante' +VERIFY_YOUR_LOGIN_WITH_A_5_DIGIT_CODE_SEND_BY_EMAIL: 'Verifica il tuo accesso con un codice a 5 cifre inviato via email' +VERSION: '' +VIDEO: Video +VISIBILITY: Visibilità +VISIT_THE_PLUGIN_DIRECTORY: '' +VISIT_THE_THEME_DIRECTORY: '' +VISUAL: '' +VISUAL_EDITOR: 'Editor visuale' +WEBSITE_OWNER: 'Proprietario del sito web' +WEBSITE_RESTRICTION: 'Restrizione del sito web' +WEBSITE_TITLE: 'Titolo del sito web' +WELCOME_BACK: Bentornato +WELCOME_TO_TYPEMILL: 'Benvenuto in Typemill' +WE_CANNOT_CREATE_A_FOLDER_ONLY_FILES: 'Non possiamo creare una cartella, solo file' +WE_COULD_NOT_CREATE_A_SANITIZED_VERSION_OF_THE_SVG_IT_PROBABLY_HAS_INVALID_CONTENT: 'Non siamo riusciti a creare una versione sanificata dell’SVG. Probabilmente contiene contenuti non validi.' +WE_COULD_NOT_CREATE_THE_FILE_PLEASE_REFRESH_THE_PAGE_AND_CHECK_IF_ALL_FOLDERS_AND_FILES_ARE_WRITABLE: 'Non siamo riusciti a creare il file. Si prega di aggiornare la pagina e verificare se tutte le cartelle e i file sono scrivibili.' +WE_COULD_NOT_CREATE_THE_FOLDER_PLEASE_REFRESH_THE_PAGE_AND_CHECK_IF_ALL_FOLDERS_AND_FILES_ARE_WRITABLE: 'Non siamo riusciti a creare la cartella. Si prega di aggiornare la pagina e verificare se tutte le cartelle e i file sono scrivibili.' +WE_COULD_NOT_CREATE_THE_USER_PLEASE_CHECK_IF_THE_SETTINGS_FOLDE_IS_WRITABLE: 'Non siamo riusciti a creare l’utente. Si prega di verificare se la cartella delle impostazioni è scrivibile.' +WE_COULD_NOT_DELETE_A_CUSTOM_IMAGE_GRAYSCALE_OR_RESIZED: 'Non siamo riusciti a eliminare un’immagine personalizzata (in scala di grigi o ridimensionata)' +WE_COULD_NOT_DELETE_THE_LIVE_IMAGE: 'Non siamo riusciti a eliminare l’immagine live' +WE_COULD_NOT_DELETE_THE_ORIGINAL_IMAGE_IN: 'Non siamo riusciti a eliminare l’immagine originale in' +WE_COULD_NOT_DELETE_THE_THUMB_IMAGE: 'Non siamo riusciti a eliminare l’immagine in miniatura' +WE_COULD_NOT_DELETE_THE_USER: 'Non siamo riusciti a eliminare l’utente' +WE_COULD_NOT_FIND_A_FOLDERPATH_FOR: 'Non siamo riusciti a trovare un percorso per la cartella per' +WE_COULD_NOT_FIND_OR_READ_THE_PUBLIC_KEY_PEM_IN_THE_SETTINGS_FOLDER: '' +WE_COULD_NOT_FIND_THE_USER: 'Non siamo riusciti a trovare l’utente' +WE_COULD_NOT_FIND_THIS_PAGE_PLEASE_REFRESH_AND_TRY_AGAIN: 'Non siamo riusciti a trovare questa pagina. Si prega di aggiornare e riprovare.' +WE_COULD_NOT_LOAD_THE_SVG_FILE_IT_IS_PROBABLY_CORRUPTED: 'Non siamo riusciti a caricare il file SVG. Probabilmente è corrotto.' +WE_COULD_NOT_SEND_THE_EMAIL_WITH_THE_PASSWORD_INSTRUCTIONS_TO_YOUR_ADDRESS_REASON: 'Non siamo riusciti a inviare l’email con le istruzioni per la password al tuo indirizzo. Motivo:' +WE_COULD_NOT_SEND_THE_EMAIL_WITH_THE_VERIFICATION_CODE_TO_YOUR_ADDRESS_REASON: 'Non siamo riusciti a inviare l’email con il codice di verifica al tuo indirizzo. Motivo:' +WE_COULD_NOT_SEND_THE_TESTMAIL_TO_YOUR_E_MAIL_ADDRESS_REASON: 'Non siamo riusciti a inviare l’email di prova al tuo indirizzo email. Motivo:' +WE_COULD_NOT_STORE_FILE_TO_TEMPORARY_FOLDER: 'Non siamo riusciti a memorizzare il file nella cartella temporanea' +WE_COULD_NOT_STORE_THE_CONTENT: 'Non siamo riusciti a memorizzare il contenuto' +WE_COULD_NOT_STORE_THE_LIVE_IMAGE_TO_THE_LIVE_FOLDER: 'Non siamo riusciti a memorizzare l’immagine live nella cartella live' +WE_COULD_NOT_STORE_THE_NEW_USER: 'Non siamo riusciti a memorizzare il nuovo utente' +WE_COULD_NOT_STORE_THE_ORIGINAL_IMAGE: 'Non siamo riusciti a memorizzare l’immagine originale' +WE_COULD_NOT_STORE_THE_THUMB_TO_THE_THUMB_FOLDER: 'Non siamo riusciti a memorizzare la miniatura nella cartella delle miniature' +WE_DID_NOT_FIND_A_FILE_WITH_THAT_NAME: 'Non abbiamo trovato un file con quel nome' +WE_DID_NOT_FIND_THE_A_USER_OR_USERMAIL: 'Non abbiamo trovato l’utente o l’email dell’utente' +WE_DID_NOT_FIND_THE_FILE_IN_THE_TMP_FOLDER_OR_COULD_NOT_READ_IT: 'Non abbiamo trovato il file nella cartella tmp o non siamo riusciti a leggerlo' +WE_DID_NOT_FIND_THE_IMAGE_IN_THE_TMP_FOLDER_OR_COULD_NOT_READ_IT: 'Non abbiamo trovato l’immagine nella cartella tmp o non siamo riusciti a leggerla' +WE_FOUND_THE_FILE_BUT_COULD_NOT_DELETE: 'Abbiamo trovato il file ma non siamo riusciti a eliminarlo' +WE_FOUND_THE_FOLDER_BUT_COULD_NOT_DELETE: 'Abbiamo trovato la cartella ma non siamo riusciti a eliminarla' +WE_FOUND_THE_FOLDER_BUT_COULD_NOT_DELETE_IT: 'Abbiamo trovato la cartella ma non siamo riusciti a eliminarla' +WE_HOPE_YOU_LIKE_TYPEMILL_BECAUSE_WE_CODED_IT_JUST_FOR_YOU: 'Speriamo ti piaccia Typemill perché lo abbiamo programmato solo per te.' +WIDTH_HEIGHT: Larghezza/Altezza +WRAP_RESTRICTION_NOTICE: 'Avvolgi avviso di restrizione' +WRAP_THE_RESTRICTION_NOTICE_ABOVE_INTO_A_NOTICE_4_ELEMENT_WHICH_CAN_BE_DESIGNED_AS_SPECIAL_BOX: 'Inserisci l’avviso di restrizione sopra in un elemento notice-4 che può essere progettato come una scatola speciale' +WRITING: Scrittura +WRONG_PASSWORD_OR_USERNAME_PLEASE_TRY_AGAIN: 'Password o username errati, per favore riprova' +YEAR: Anno +YOUR_ARE_AN: 'Tu sei un' +YOUR_BROWSER_CAN_REMEMBER_ALL_OF_YOUR_PASSWORDS: 'Il tuo browser può ricordare tutte le tue password' +YOUR_EMAIL: 'La tua email' +YOUR_LICENSE_KEY: 'La tua chiave di licenza' +YOUR_REGISTRATION_IS_NOT_CONFIRMED_YET_PLEASE_CHECK_YOUR_E_MAILS_AND_USE_THE_CONFIRMATION_LINK: 'La tua registrazione non è ancora confermata. Per favore controlla le tue email e usa il link di conferma' +YOUR_TYPEMILL_VERIFICATION_CODE: 'Il tuo codice di verifica Typemill' +YOU_ARE_NOT_ALLOWED_TO_DELETE_ANOTHER_USER: 'Non ti è permesso eliminare un altro utente' +YOU_ARE_NOT_ALLOWED_TO_UPDATE_ANOTHER_USER: 'Non ti è permesso aggiornare un altro utente' +YOU_CANNOT_CREATE_AN_ITEM_WITH_THE_SLUG_TM_IN_THE_ROOT_FOLDER_BECAUSE_THIS_IS_THE_SYSTEM_PATH: '' +YOU_CAN_OVERWRITE_THE_THEME_CSS_WITH_YOUR_OWN_CSS_HERE: 'Puoi sovrascrivere il CSS del tema con il tuo CSS qui.' +YOU_CAN_USE_THE_ASTERISK_WILDCARD_TO_SEARCH_FOR_NAME@_OR_@DOMAIN_COM: 'Puoi usare il carattere jolly asterisco per cercare name@ o @domain.com' +YOU_DO_NOT_HAVE_ENOUGH_RIGHTS: 'Non hai diritti sufficienti' +YOU_TRIED_TO_OPEN_THE_PASSWORD_RESET_PAGE_BUT_THE_LINK_WAS_INVALID: 'Hai provato ad aprire la pagina di reimpostazione della password ma il link era invalido' +YOU_TRIED_TO_SET_A_NEW_PASSWORD_BUT_USERNAME_OR_TOKEN_WAS_INVALID: 'Hai tentato di impostare una nuova password ma il nome utente o il token erano invalidi' +©: © +NO_LICENSE_FOUND: 'Nessuna licenza trovata' +THE_LICENSE_SERVER_RESPONDED_WITH: 'Il server delle licenze ha risposto con:' +THE_SUBSCRIPTION_PERIOD_IS_NOT_PAID_YET: 'Il periodo di sottoscrizione non è ancora stato pagato.' +VALIDATION_FAILED: 'Validazione fallita' diff --git a/system/typemill/author/translations/nl.yaml b/system/typemill/author/translations/nl.yaml new file mode 100644 index 0000000..5e2141d --- /dev/null +++ b/system/typemill/author/translations/nl.yaml @@ -0,0 +1,481 @@ +# Dutch (Nederlands) +# Author: S. van Laere (https://github.com/svanlaere) +ACCESS: '' +ACCESS_FOR: '' +ACCESS_RIGHTS: '' +ACCESS_TO_BASEPATH_IS_NOT_ALLOWED: '' +ACCOUNT: Account +ACCOUNT_CREATED_PLEASE_LOGIN_WITH_YOUR_USERNAME_AND_PASSWORD_NOW: '' +ACTIVATE_API_ACCESS_FOR_THIS_USER_USE_USERNAME_AND_PASSWORD_FOR_API_CALLS: '' +ACTIVATE_A_PASSWORD_RECOVERY_IN_THE_LOGIN_FORM: '' +ACTIVATE_INDIVIDUAL_RESTRICTIONS_FOR_PAGES_IN_THE_META_TAB_OF_EACH_PAGE: 'Activeer individuele beperkingen voor pagina''s in de meta-tab van elke pagina.' +ACTIVATE_THE_CACHE_FOR_TWIG_TEMPLATES: '' +ACTIVATE_THE_DARKMODE_FOR_ME: '' +ACTIVATE_YOUR_LICENSE: '' +ACTIVATION_FAILED_BECAUSE_YOU_NEED_A_VALID: '' +ACTIVE: Actief +ACTUAL_PASSWORD: 'Actueel wachtwoord' +ADD: toevoegen +ADD_A_FILE: '' +ADD_A_FOLDER: '' +ADD_DEFINITION: 'definitie toevoegen' +ADD_DESCRIPTION: '' +ADD_ENTRY: '' +ADD_LEFT_COLUMN: 'linkerkolom toevoegen' +ADD_MORE_URL_SCHEMES_FOR_EXTERNAL_LINKS_E_G_LIKE_DICT_COMMA_SEPARATED_LIST: 'Voeg meer url-schema''s toe voor externe links, b.v. zoals dict:// (door komma''s gescheiden lijst)' +ADD_NOINDEX_TAG_AND_EXCLUDE_FROM_SITEMAP: '' +ADD_ONE_OR_MORE_USERNAMES_SEPARATED_WITH_COMMA: 'Voeg een of meer gebruikersnamen toe, gescheiden door komma''s.' +ADD_RIGHT_COLUMN: 'rechter kolom toevoegen' +ADD_ROW_ABOVE: 'rij boven toevoegen' +ADD_ROW_BELOW: 'rij hieronder toevoegen' +ALLOWED_DOMAINS_FOR_API_ACCESS_CORS_HEADERS: '' +ALLOWED_DOMAINS_FOR_CONTENT_ON_TYPEMILL_CSP_HEADERS: '' +ALLOW_SVG: '' +ALLOW_THE_UPLOAD_OF_SVG_IMAGES: '' +ALL_PAGES: '' +ALTERNATIVE_TEXT_FOR_THE_HERO_IMAGE: 'Alternative Text for the hero image' +ALT_TEXT: Alt-tekst +ALWAYS_SHOW: '' +AND_YOU_CANNOT: '' +AND_YOU_MIGHT_FIND_THE_FOLLOWING_TIPS_HELPFUL: '' +ANSWER_FROM_LICENSE_SERVER: '' +API_ACCESS: '' +ARTICLE_DATE: '' +AUTHOR: Auteur +AUTHOR_DESCRIPTION_MARKDOWN: 'Author-Description (Markdown)' +BACK_TO_LOGIN: '' +BLOCK_ID_NOT_FOUND: '' +BOLD: vetgedrukt +BULLET_LIST: 'Lijst met opsommingstekens' +BUY_A_LICENSE: '' +CANCEL: annuleren +CAN_BE_USED_FOR_AUTHOR_LINE_IN_FRONTEND: 'Can be used for author line in frontend.' +CAPTION: Bijschrift +CC_BY: '' +CC_BY_NC: '' +CC_BY_NC_ND: '' +CC_BY_NC_SA: '' +CC_BY_ND: '' +CC_BY_SA: '' +CENTER: Midden +CHANGE_SLUG: '' +CHECK: check +CHECK_YOUR_INBOX: '' +CHECK_YOUR_LICENSE: '' +CLASS: Class +CLEAR: '' +CLOSE: '' +CLOSE_LIBRARY: 'Close Library' +CODE: Code +COLLAPSE_ALL: '' +CONFIGURE: '' +CONFIRM: '' +CONTENT_BREAK: '' +CONVERT_TO_WEBP: '' +COPYRIGHT: Auteursrecht +COPY_THE_CONTENT_OF_THE_REFERENCED_INTERNAL_PAGE: '' +COULD_NOT_CREATE_FOLDER: '' +COULD_NOT_DECODE_IMAGE_OR_FILE_PROBABLY_NOT_A_BASE64_ENCODING: '' +COULD_NOT_DELETE_FILE: '' +COULD_NOT_DELETE_FOLDER: '' +COULD_NOT_GET_THE_VIDEO_IMAGE: '' +COULD_NOT_OPEN_AND_READ_THE_FILE: '' +COULD_NOT_READ_PATHINFO: '' +COULD_NOT_STORE_THE_CUSTOM_SIZE_OF: '' +COULD_NOT_STORE_THE_IMAGE_IN_THE_TEMPORARY_FOLDER: '' +COULD_NOT_STORE_THE_RESIZED_VERSION: '' +COULD_NOT_WRITE_TO_THE_FILE: '' +CREATED_AT_READ_ONLY: 'Created at (readonly)' +CREATE_NEW_PASSWORD: '' +CREATE_POST: '' +CREATE_USER: 'Gebruiker maken' +CUSTOM_CSS: 'Custom CSS' +CUT_RESTRICTED_CONTENT_AFTER_THE_FIRST_HR_ELEMENT_ON_A_PAGE_PER_DEFAULT_CONTENT_WILL_BE_CUT_AFTER_TITLE: 'Beperkte inhoud knippen na het eerste hr-element op een pagina (standaard wordt inhoud na titel geknipt).' +DARKMODE: '' +DEAR: '' +DEAR_USER: '' +DEFAULT_WIDTH_OF_LIVE_IMAGES_IS_820PX_CHANGES_WILL_APPLY_TO_FUTURE_UPLOADS: '' +DEFINITION: Definitielijst +DEFINITION_LIST: Definitielijst +DELETE: verwijderen +DELETE_COLUMN: 'kolom verwijderen' +DELETE_DESCRIPTION: '' +DELETE_PAGE: 'pagina verwijderen' +DELETE_ROW: 'rij verwijderen' +DELETE_USER: 'Gebruiker verwijderen' +DEVELOPER: Ontwikkelaar +DISABLE: '' +DISABLE_ALL_CSP_HEADERS_CONTENT_SECURITY_POLICY_FOR_THIS_WEBSITE: '' +DISABLE_ALL_CUSTOM_HEADERS_OF_TYPEMILL_EXCEPT_CORS_AND_SEND_YOUR_OWN_HEADERS_INSTEAD: '' +DISABLE_CSP_HEADERS: '' +DISABLE_CUSTOM_HEADERS: '' +DISCARD: weggooien +DISCARD_CHANGES: 'Wijzigingen negeren' +DISPLAY_APPLICATION_ERRORS: 'Applicatiefouten weergeven' +DOES_NOT_EXIST: '' +DOES_NOT_EXIST_OR_IS_NOT_WRITABLE: '' +DOMAIN_FOR_LICENSE: '' +DONATE: '' +DONE: '' +DO_NOT_FORGET_TO_CHECK_YOUR_SPAM_FOLDER_IF_YOUR_INBOX_IS_EMPTY: '' +DO_NOT_RESIZE: '' +DO_YOU_REALLY_WANT_TO_DELETE_THIS_PAGE: 'Wilt u deze pagina echt verwijderen?' +DO_YOU_REALLY_WANT_TO_DELETE_THIS_USER: '' +DO_YOU_WANT_TO_DISCARD_YOUR_CHANGES_AND_SET_THE_CONTENT_BACK_TO_THE_LIVE_VERSION: 'Wilt u uw wijzigingen annuleren en de inhoud terugzetten naar de live versie?' +DRAFT: Ontwerp +DRAG_A_PICTURE_OR_CLICK_TO_SELECT: '' +DUTCH_FLEMISH: 'Dutch, Flemish' +EDIT: bewerken +EMAIL: '' +EMAIL_ADDRESS_IN_SYSTEM_SETTINGS_IS_MISSING: '' +EMAIL_SUBJECT: '' +ENGLISH: English +ENTER_AN_EMAIL_ADDRESS_THAT_SENDS_THE_E_MAILS_SENDER_THE_E_MAIL_FEATURE_WILL_BE_USED_FOR_RECOVERY_AND_VERIFICATION_E_MAILS_SEND_A_TESTMAIL_TO_YOUR_USER_ACCOUNT_TO_VERIFY_THAT_YOU_RECEIVE_THE_E_MAILS: '' +ENTER_THE_EMAIL_OF_YOUR_USER_ACCOUNT_CLICK_THE_RECOVER_BUTTON_AND_CHECK_YOUR_MAILBOX_FOR_FURTHER_INSTRUCTIONS: '' +ENTER_THE_FULL_DOMAIN_LIKE_HTTPS_WWWW_MYWEBSITE_DE: '' +ENTER_THE_VERIFICATION_CODE_FROM_YOUR_EMAIL: '' +ENTER_YOUR_E_MAIL_ADDRESS_THAT_YOU_USED_FOR_YOUR_LICENSE_PURCHASE: '' +ENTER_YOUR_LICENSE_KEY_THAT_YOU_GOT_AFTER_YOUR_PURCHASE_VIA_EMAIL: '' +ERROR_REPORTING: Foutmelding +ERROR_SENDING_EMAIL: '' +EXPAND_ALL: '' +EXTENSION_OR_FILENAME_ARE_MISSING: '' +EXTENSION_OR_NAME_FOR_IMAGE_IS_MISSING: '' +EXTERNAL_LINK: 'externe link' +E_MAIL: e-mail +FAVICON: Favicon +FILE: File +FILENAME_IS_MISSING: '' +FILENAME_OR_USERROLE_IS_MISSING: '' +FILETYPE_IS_NOT_ALLOWED: '' +FILE_DELETED_SUCCESSFULLY: '' +FILE_HAS_BEEN_STORED: '' +FILE_IS_BIGGER_THAN_20MB: '' +FILE_IS_EMPTY: '' +FILE_IS_MISSING: '' +FILE_NOT_FOUND: '' +FILE_SAVED_SUCCESSFULLY: '' +FIRST_NAME: Voornaam +FOLDER: map +FORGOT_PASSWORD: 'Wachtwoord vergeten' +FORGOT_YOUR_PASSWORD: '' +FORMAT_OPTIONS_FOR_VISUAL_EDITOR: '' +FRENCH: French +FROM_MAIL_IS_REQUIRED_FOR_THIS_FEATURE_SEND_A_TESTMAIL_BEFORE_YOU_USE_THIS_FEATURE: '' +FROM_MAIL_IS_REQUIRED_FOR_THIS_FEATURE_SEND_A_TESTMAIL_BEFORE_YOU_USE_THIS_FEATURE_MAKE_SURE_YOU_HAVE_FTP_ACCESS_TO_DISABLE_THE_FEATURE_IN_SETTINGS_YAML_ON_FAILURE_THE_VERIFICATION_CODE_WILL_BE_VALID_FOR_5_MINUTES_BE_AWARE_THAT_DEVICE_FINGERPRINTS_WILL_BE_STORED_IN_THE_USER_ACCOUNTS: '' +GERMAN: German +GET_INSPIRED_AND_ENJOY_YOUR_WRITING: '' +GOOGLE_SITEMAP_READONLY: '' +GO_TO_LOGIN: '' +HAS_EDIT_RIGHTS_FOR_THIS_ARTICLE: 'Has edit rights for this article.' +HEADLINE: Kop +HEADLINE_ANCHORS: 'Headline Anchors' +HERO_IMAGE: 'Hero Image' +HEY_WRITER_AUTHOR_EDITOR_CONTENT_GURU_OR_WEBSITE_MANAGER: '' +HIDE: Hide +HIDE_PAGE_FROM_NAVIGATION: 'Hide page from navigation' +HOMEPAGE: Homepage +HORIZONTAL_LINE: 'Horizontale lijn' +HR: hr +IF_NOT_FILLED_THE_DESCRIPTION_IS_EXTRACTED_FROM_CONTENT: 'Indien niet ingevuld, wordt de beschrijving uit de inhoud gehaald.' +IF_YOUR_PROXY_DOES_NOT_WORK_TRY_TO_ADD_THE_BASE_URL_OF_YOUR_PROXY_HERE_LIKE_HTTPS_MYWEBSITE_COM: '' +IF_YOU_ADD_A_VALUE_FOR_THE_HEIGHT_THEN_THE_IMAGE_WILL_BE_CROPPED: 'Als u een waarde voor de hoogte toevoegt, wordt de afbeelding bijgesneden.' +IF_YOU_DID_NOT_MAKE_THIS_LOGIN_ATTEMPT_PLEASE_RESET_YOUR_PASSWORD_IMMEDIATELY: '' +IF_YOU_DID_NOT_RECEIVE_AN_EMAIL_WITH_THE_VERIFICATION_CODE_THEN_THE_USERNAME_OR_PASSWORD_YOU_ENTERED_WAS_WRONG_PLEASE_TRY_AGAIN: '' +IF_YOU_UNPUBLISH_THE_PAGE_THEN_WE_WILL_DELETE_THE_PUBLISHED_VERSION_AND_KEEP_THE_MODIFIED_VERSION: '' +IF_YOU_WANT_TO_KEEP_YOUR_CHANGES_THEN_CLICK_ON_CANCEL_AND_SAVE_YOUR_CHANGES_BEFORE_YOU_SWITCH_TO_THE_VISUAL_EDITOR: '' +IMAGE: Afbeelding +IMAGENAME_IS_MISSING: '' +IMAGES_WITH_THIS_EXTENSION_ARE_NOT_ALLOWED: '' +IMAGE_DELETED_SUCCESSFULLY: '' +IMAGE_OR_FILENAME_IS_MISSING: '' +IMAGE_OR_NAME_IS_MISSING: '' +IMAGE_SAVED_SUCCESSFULLY: '' +IMAGE_URL: 'Image URL' +IMAGE_URL_READ_ONLY: 'Image URL (read only)' +INFO: '' +INVALID_VERIFICATION_CODE_FORMAT_PLEASE_TRY_AGAIN: '' +INVISIBLE: '' +IS_NOT_A_DIRECTORY: '' +IS_NOT_A_FOLDER: '' +ITALIAN: Italian +ITALIC: cursief +IT_IS_NOT_ALLOWED_TO_WRITE_INTO: '' +LANGUAGE: Taal +LANGUAGE_ATTRIBUTE_WEBSITE: '' +LANGUAGE_AUTHOR_AREA: '' +LAST_MODIFIED_LIVE_READONLY: 'Last modified live (readonly)' +LAST_NAME: achternaam +LEFT: Links +LICENCE_HAS_BEEN_STORED: '' +LICENSE: '' +LICENSE_AND_YOUR_WEBSITE_MUST_RUN_UNDER_THE_DOMAIN_OF_YOUR_LICENSE: '' +LICENSE_DATA_ARE_INCOMPLETE: '' +LICENSE_DATA_MISSING: '' +LICENSE_REQUIRED: '' +LICENSE_VALIDATION_FAILED: '' +LINK: Link +LINK_TO_AN_EXTERNAL_PAGE: '' +LINK_TO_YOUTUBE: '' +LIST_ALL_DOMAINS_SEPARATED_BY_COMMAS_TO_ALLOW_CONTENT_INTEGRATION_SUCH_AS_IFRAMES_ON_YOUR_TYPEMILL_WEBSITE_DOMAINS_WILL_BE_ADDED_TO_THE_CSP_HEADER_USUALLY_DONE_WITH_PLUGINS_AND_THEMES_BUT_ADD_MANUALLY_IF_SOMETHING_IS_BLOCKED: '' +LIST_ALL_DOMAINS_SEPARATED_BY_COMMA_THAT_SHOULD_HAVE_ACCESS_TO_THE_TYPEMILL_API_DOMAINS_WILL_BE_ADDED_TO_THE_CORS_HEADER: '' +LOGIN: Inloggen +LOGIN_TO_THE_AUTHOR_AREA_OR_GO_TO_THE: '' +LOGIN_VERIFICATION_RECOMMENDED: '' +LOGO: Logo +MAIL_FROM_NAME_OPTIONAL: '' +MAIL_FROM_REQUIRED: '' +MANUAL_DATE: 'Handmatige datum' +MARKDOWN: Markdown +MARKDOWN_IS_MISSING: '' +MAXIMUM_SIZE_FOR_AN_IMAGE_IS_5_MB_HERO_IMAGES_ARE_NOT_SUPPORTED_BY_ALL_THEMES: 'Maximum size for an image is 5 MB. Hero images are not supported by all themes.' +MAXIMUM_SIZE_FOR_FILE_UPLOADS_IN_MB: '' +MAXIMUM_SIZE_FOR_IMAGE_UPLOADS_IN_MB: '' +MAXIMUM_SIZE_OF_FILE_LINK_IS_100_CHARACTERS: '' +MAXIMUM_SIZE_OF_FILE_TEXT_IS_100_CHARACTERS: '' +MAXIMUM_SIZE_OF_IMAGE_ALT_TEXT_IS_100_CHARACTERS: '' +MAXIMUM_SIZE_OF_IMAGE_CAPTION_IS_140_CHARACTERS: '' +MAXIMUM_SIZE_OF_IMAGE_CLASS_IS_100_CHARACTERS: '' +MAXIMUM_SIZE_OF_IMAGE_ID_IS_100_CHARACTERS: '' +MAXIMUM_SIZE_OF_IMAGE_LINK_IS_100_CHARACTERS: '' +MAXIMUM_SIZE_OF_IMAGE_TITLE_IS_100_CHARACTERS: '' +MEDIA: '' +MEDIALIB: '' +MENU: Menu +META_CONTENT: '' +META_DESCRIPTION: Metabeschrijving +META_TITLE: Metatitel +MINIMUM_USER_ROLE_TO_ACCESS_THIS_PAGE: '' +NAVIGATION_TITLE: 'Navigation Title' +NEW_PASSWORD: 'Nieuw wachtwoord' +NEW_USER: '' +NEW_USER_CREATED: '' +NOINDEX: '' +NOTICE: Opmerking +NO_FILE_FOUND: '' +NO_IMAGE_FOUND: '' +NO_PROBLEM_YOU_CAN_CREATE_A_NEW_ONE_HERE: '' +NUMBERED_LIST: 'Genummerde lijst' +OLIST: olist +ONE_OR_MORE_FILES_COULD_NOT_BE_TRANSFORMED: '' +ONLY_IMAGES_ARE_ALLOWED: '' +ONLY_PNG_FORMAT_WILL_WORK: '' +ONLY_THE_FOLLOWING_USERS_HAVE_ACCESS: 'Alleen de volgende gebruikers hebben toegang' +OPEN: '' +OPTIONALLY_ENTER_A_NAME_FOR_THE_SENDER_ADDRESS_IF_NOT_SET_THE_FROM_ADDRESS_WILL_BE_VISIBLE: '' +OPTIONALLY_ENTER_A_REPLY_TO_ADDRESS_FOR_ANSWERS_FROM_THE_RECEIVER_IF_NOT_SET_ANSWERS_WILL_GO_TO_THE_FROM_ADDRESS: '' +OWNER_USERNAME: 'owner (username)' +PAGES_SORT_IN_NAVIGATION_WITH_DRAG_DROP: '' +PAGE_NOT_FOUND: '' +PAGE_RESTRICTION: '' +PARAGRAPH: Paragraaf +PASSWORD: Wachtwoord +PATH_IS_MISSING: '' +PERMANENT_REDIRECT_301_THE_USER_TO_THE_REFERENCED_INTERNAL_PAGE: '' +PERMISSION_DENIED: '' +PLEASE_CHECK_IF_THERE_IS_A_READABLE_FILE_PUBLIC_KEY_PEM_IN_YOUR_SETTINGS_FOLDER: '' +PLEASE_CHECK_THE_INBOX_OF_YOUR_EMAIL_ACCOUNT_FOR_MORE_INSTRUCTIONS: '' +PLEASE_CONFIRM: 'bevestig alstublieft' +PLEASE_CORRECT_YOUR_INPUT: '' +PLEASE_ENTER_A_VALID_EMAIL: '' +PLEASE_LOGIN_WITH_YOUR_NEW_PASSWORD: '' +PLEASE_SELECT: '' +PLEASE_USE_THE_FOLLOWING_LINK_TO_SET_A_NEW_PASSWORD: '' +PLUGINS: plug-ins +PLUGIN_SETTINGS: '' +POSTS_SORTED_BY_PUBLISH_DATE_FOR_NEWS_OR_BLOGS: '' +PROFILE_IMAGE: 'Profiel afbeelding' +PROXY: Proxy +PUBLISH: Publiceren +QUOTE: Citeer +QUOTES: Citeren +RAW: rauw +RAW_EDITOR: '' +RECOVER_PASSWORD: '' +REFERENCE: '' +REFERENCE_TO_PAGE: '' +REPEAT_PASSWORD: '' +REPLY_TO_OPTIONAL: '' +REQUIRED: verplicht +RESTRICTION_NOTICE_USE_MARKDOWN: '' +RIGHT: Rechts +ROLE: rol +RUSSIAN: Russisch +SAVE: Opslaan +SEARCH: '' +SECURITY: '' +SECURITY_LOG: '' +SELECT_FROM_MEDIALIB: 'Selecteer uit medialib' +SELECT_THE_LOWEST_USERROLE_HIGHER_ROLES_WILL_HAVE_ACCESS_TOO: 'Selecteer de laagste gebruikersrol. Hogere rollen hebben ook toegang.' +SETTINGS_HAVE_BEEN_SAVED: '' +SETUP: Instellen +SHORTCODE: '' +SHORTCODES: '' +SHORT_TITLE_FOR_POST: '' +SHOW_AFTER_FIRST_WRONG_INPUT: '' +SHOW_ANCHORS_NEXT_TO_HEADLINE_IN_FRONTEND: '' +SHOW_THE_WEBSITE_ONLY_TO_AUTHENTICATED_USERS_AND_REDIRECT_ALL_OTHER_USERS_TO_THE_LOGIN_PAGE: 'Toon de website alleen aan geverifieerde gebruikers en stuur alle andere gebruikers door naar de inlogpagina.' +SLUG: '' +SOMEONE_TRIED_TO_LOG_IN_TO_YOUR_TYPEMILL_WEBSITE_AND_WE_WANT_TO_MAKE_SURE_IT_IS_YOU_ENTER_THE_FOLLOWING_VERIFICATION_CODE_TO_FINISH_YOUR_LOGIN_THE_CODE_WILL_BE_VALID_FOR_5_MINUTES: '' +SOMETHING_WENT_WRONG_PLEASE_REFRESH_THE_PAGE_AND_CHECK_IF_ALL_FOLDERS_AND_FILES_ARE_WRITABLE: '' +SOMETHING_WENT_WRONG_THE_INPUT_IS_NOT_VALID: '' +SPECIAL_CHARACTERS_ARE_NOT_ALLOWED_LENGTH_BETWEEN_1_AND_40: '' +STANDARD_EDITOR_MODE: Standaardeditormodus +STANDARD_HEIGHT_FOR_LIVE_PICTURES: '' +STANDARD_WIDTH_FOR_LIVE_PICTURES: '' +SUBMIT_THE_URL_ABOVE_IN_GOOGLE_SEARCH_CONSOLE_TO_SUPPORT_INDEXING: '' +SWITCH_TO_VISUAL: '' +SYSTEM: Systeem +SYSTEMCHECK: '' +SYSTEM_SETTINGS: '' +TABLE: Tabel +TABLE_OF_CONTENTS: Inhoudsopgave +TAB_NOT_FOUND: '' +TEMPORARY_REDIRECT_302_THE_USER_TO_THE_REFERENCED_INTERNAL_PAGE: '' +TESTMAIL_FROM_TYPEMILL: '' +TEXT_BEFORE_RECOVER_LINK_IN_EMAIL_MESSAGE: '' +TEXT_FILE: tekstbestand +THEMES: Themes +THEME_SETTINGS: '' +THERE_ARE_PUBLISHED_PAGES_WITHIN_THIS_FOLDER_THE_PAGES_ARE_NOT_VISIBLE_ON_YOUR_WEBSITE_ANYMORE: '' +THERE_IS_ALREADY_A_PAGE_WITH_THAT_SLUG: '' +THERE_IS_ALREADY_A_PAGE_WITH_THIS_NAME_PLEASE_CHOOSE_ANOTHER_NAME: '' +THERE_WAS_AN_ERROR_CHECKING_THE_LICENSE_SIGNATURE: '' +THE_BEST_OPTION_IS_A_SEPARATE_PASSWORD_MANAGER_LIKE_KEEPASS_AND_OTHERS: '' +THE_CAPTCHA_IS_WRONG_PLEASE_TRY_AGAIN: '' +THE_FOLDER_CONTAINS_ANOTHER_FOLDER_SO_WE_CANNOT_TRANSFORM_IT_PLEASE_MAKE_SURE_THERE_ARE_ONLY_FILES_IN_THIS_FOLDER: '' +THE_FOLDER_CONTAINS_PUBLISHED_PAGES_PLEASE_UNPUBLISH_OR_DELETE_THEM_FIRST: '' +THE_FOLLOWING_REQUIREMENTS_FOR_THE_INSTALLATION_ARE_MISSING: '' +THE_FROM_MAIL_IS_MISSING_OR_IT_IS_NOT_A_VALID_E_MAIL_ADDRESS: '' +THE_ID_OF_THE_CONTENT_BLOCK_IS_WRONG: '' +THE_LICENSE_DATA_ARE_INVALID: '' +THE_LINK_TO_RECOVER_THE_PASSWORD_WAS_TOO_OLD_PLEASE_CREATE_A_NEW_ONE: '' +THE_MAXIMUM_FILE_SIZE_MIGHT_BE_LIMITED_BY_YOUR_SERVER_SETTINGS: '' +THE_MAXIMUM_IMAGE_SIZE_MIGHT_BE_LIMITED_BY_YOUR_SERVER_SETTINGS: '' +THE_MIME_TYPE_IS_MISSING_NOT_ALLOWED_OR_DOES_NOT_FIT_TO_THE_FILE_EXTENSION: '' +THE_PLUGIN_OR_THEMES_WAS_NOT_FOUND: '' +THE_RECOVER_LINK_WILL_BE_ACTIVE_FOR_24_HOURS: '' +THE_REQUESTED_FILETYPE_DOES_NOT_EXIST: '' +THE_REQUESTED_FILE_DOES_NOT_EXIST: '' +THE_SUBSCRIPTION_PERIOD_HAS_NOT_BEEN_PAID_YET_AND_WE_GOT_AN_ERROR: '' +THE_SUBSCRIPTION_PERIOD_HAS_NOT_BEEN_PAID_YET_WE_WILL_CHECK_IT_EVERY_60_MINUTES: '' +THE_TESTMAIL_HAS_BEEN_SEND_PLEASE_CHECK_YOUR_INBOX_AND_YOUR_SPAM_FOLDER_TO_VARIFY_THAT_YOU_RECEIVED_THE_MAIL: '' +THE_VERIFICATION_WAS_WRONG_OR_OUTDATED_PLEASE_START_AGAIN: '' +THE_VERSION_CHECK_FAILED_BECAUSE_OF_INVALID_PARAMETERS: '' +THE_WEBSITE_IS_RUNNING_NOT_UNDER_THE_DOMAIN_OF_YOUR_LICENSE: '' +THIS: '' +THIS_FIELD_IS_NOT_DEFINED: '' +THIS_FOLDER_CONTAINS: '' +THIS_IS_A_TESTMAIL_FROM_TYPEMILL_AND_IF_YOU_READ_THIS_E_MAIL_THEN_EVERYTHING_WORKS_FINE: '' +THIS_PAGE: '' +THIS_PAGE_HAS_BEEN_MODIFIED: '' +THIS_PAGE_HAS_UNSAVED_CHANGES: '' +TITLE: Titel +TOC: Toc +TO_DOWNLOAD_THIS_FILE_YOU_NEED_TO_BE_AUTHENTICATED_WITH_THE_ROLE: '' +TRACK_SPAM_AND_SUSPICIOUS_ACTIONS_IN_A_LOGFILE: '' +TRASH: '' +TRUSTED_IPS_FOR_PROXIES_COMMA_SEPARATED: '' +TRY_TO_CONVERT_UPLOADED_IMAGES_INTO_THE_WEBP_FORMAT_FOR_BETTER_PERFORMANCE: '' +TWIG_CACHE: 'Twig Cache' +TYPE_OF_REFERENCE: '' +ULIST: ulist +UNPUBLISH: '' +UNPUBLISH_PAGE: '' +UNSAVED_CHANGES: '' +UPLOAD: Upload +UPLOAD_AN_IMAGE: 'Upload een afbeelding' +UPLOAD_FILE: 'Upload een bestand' +URL_SCHEMES: '' +USED_AS_FALLBACK_WHEN_NO_MANUAL_DATE_IS_SET: 'Gebruikt als fallback als er geen handmatige datum is ingesteld.' +USED_FOR_COPYRIGHT_AND_YEAR_IN_FOOTER: '' +USED_FOR_FRONTEND_LANGUAGE_ATTRIBUTE_PLEASE_USE_ISO_639_1_CODES_LIKE_EN: '' +USED_FOR_TRANSLATIONS_IN_AUTHOR_AREA_THEMES_AND_PLUGINS: '' +USER: Gebruiker +USERDATA_ARE_REQUIRED: '' +USERNAME: Gebruikersnaam +USERNAME_IS_REQUIRED: '' +USERNAME_READ_ONLY: 'Gebruikersnaam (alleen lezen)' +USERROLE: '' +USERROLE_IS_REQUIRED: '' +USERROLE_IS_UNKNOWN: '' +USERS: Gebruikers +USER_DELETED: '' +USER_HAS_BEEN_UPDATED: '' +USER_NOT_FOUND: '' +USE_A_STRONG_AND_INDIVIDUAL_PASSWORD_FOR_EVERY_ACCOUNT: '' +USE_CAPTCHA_IN_AUTHENTICATION_FORMS: '' +USE_X_FORWARDED_HEADER: '' +VERIFICATION_CODE_MISSING: '' +VERIFY_YOUR_LOGIN_WITH_A_5_DIGIT_CODE_SEND_BY_EMAIL: '' +VERSION: '' +VIDEO: Video +VISIBILITY: '' +VISIT_THE_PLUGIN_DIRECTORY: '' +VISIT_THE_THEME_DIRECTORY: '' +VISUAL: visual +VISUAL_EDITOR: 'visuele editor' +WEBSITE_OWNER: '' +WEBSITE_RESTRICTION: Websitebeperking +WEBSITE_TITLE: 'Titel van de website' +WELCOME_BACK: '' +WELCOME_TO_TYPEMILL: '' +WE_CANNOT_CREATE_A_FOLDER_ONLY_FILES: '' +WE_COULD_NOT_CREATE_A_SANITIZED_VERSION_OF_THE_SVG_IT_PROBABLY_HAS_INVALID_CONTENT: '' +WE_COULD_NOT_CREATE_THE_FILE_PLEASE_REFRESH_THE_PAGE_AND_CHECK_IF_ALL_FOLDERS_AND_FILES_ARE_WRITABLE: '' +WE_COULD_NOT_CREATE_THE_FOLDER_PLEASE_REFRESH_THE_PAGE_AND_CHECK_IF_ALL_FOLDERS_AND_FILES_ARE_WRITABLE: '' +WE_COULD_NOT_CREATE_THE_USER_PLEASE_CHECK_IF_THE_SETTINGS_FOLDE_IS_WRITABLE: '' +WE_COULD_NOT_DELETE_A_CUSTOM_IMAGE_GRAYSCALE_OR_RESIZED: '' +WE_COULD_NOT_DELETE_THE_LIVE_IMAGE: '' +WE_COULD_NOT_DELETE_THE_ORIGINAL_IMAGE_IN: '' +WE_COULD_NOT_DELETE_THE_THUMB_IMAGE: '' +WE_COULD_NOT_DELETE_THE_USER: '' +WE_COULD_NOT_FIND_A_FOLDERPATH_FOR: '' +WE_COULD_NOT_FIND_OR_READ_THE_PUBLIC_KEY_PEM_IN_THE_SETTINGS_FOLDER: '' +WE_COULD_NOT_FIND_THE_USER: '' +WE_COULD_NOT_FIND_THIS_PAGE_PLEASE_REFRESH_AND_TRY_AGAIN: '' +WE_COULD_NOT_LOAD_THE_SVG_FILE_IT_IS_PROBABLY_CORRUPTED: '' +WE_COULD_NOT_SEND_THE_EMAIL_WITH_THE_PASSWORD_INSTRUCTIONS_TO_YOUR_ADDRESS_REASON: '' +WE_COULD_NOT_SEND_THE_EMAIL_WITH_THE_VERIFICATION_CODE_TO_YOUR_ADDRESS_REASON: '' +WE_COULD_NOT_SEND_THE_TESTMAIL_TO_YOUR_E_MAIL_ADDRESS_REASON: '' +WE_COULD_NOT_STORE_FILE_TO_TEMPORARY_FOLDER: '' +WE_COULD_NOT_STORE_THE_CONTENT: '' +WE_COULD_NOT_STORE_THE_LIVE_IMAGE_TO_THE_LIVE_FOLDER: '' +WE_COULD_NOT_STORE_THE_NEW_USER: '' +WE_COULD_NOT_STORE_THE_ORIGINAL_IMAGE: '' +WE_COULD_NOT_STORE_THE_THUMB_TO_THE_THUMB_FOLDER: '' +WE_DID_NOT_FIND_A_FILE_WITH_THAT_NAME: '' +WE_DID_NOT_FIND_THE_A_USER_OR_USERMAIL: '' +WE_DID_NOT_FIND_THE_FILE_IN_THE_TMP_FOLDER_OR_COULD_NOT_READ_IT: '' +WE_DID_NOT_FIND_THE_IMAGE_IN_THE_TMP_FOLDER_OR_COULD_NOT_READ_IT: '' +WE_FOUND_THE_FILE_BUT_COULD_NOT_DELETE: '' +WE_FOUND_THE_FOLDER_BUT_COULD_NOT_DELETE: '' +WE_FOUND_THE_FOLDER_BUT_COULD_NOT_DELETE_IT: '' +WE_HOPE_YOU_LIKE_TYPEMILL_BECAUSE_WE_CODED_IT_JUST_FOR_YOU: '' +WIDTH_HEIGHT: '' +WRAP_RESTRICTION_NOTICE: '' +WRAP_THE_RESTRICTION_NOTICE_ABOVE_INTO_A_NOTICE_4_ELEMENT_WHICH_CAN_BE_DESIGNED_AS_SPECIAL_BOX: 'Wikkel de beperkingskennisgeving hierboven in een bericht-4-element (dat kan worden ontworpen als een speciaal vak)' +WRITING: Schrijven +WRONG_PASSWORD_OR_USERNAME_PLEASE_TRY_AGAIN: '' +YEAR: Jaar +YOUR_ARE_AN: '' +YOUR_BROWSER_CAN_REMEMBER_ALL_OF_YOUR_PASSWORDS: '' +YOUR_EMAIL: '' +YOUR_LICENSE_KEY: '' +YOUR_REGISTRATION_IS_NOT_CONFIRMED_YET_PLEASE_CHECK_YOUR_E_MAILS_AND_USE_THE_CONFIRMATION_LINK: '' +YOUR_TYPEMILL_VERIFICATION_CODE: '' +YOU_ARE_NOT_ALLOWED_TO_DELETE_ANOTHER_USER: '' +YOU_ARE_NOT_ALLOWED_TO_UPDATE_ANOTHER_USER: '' +YOU_CANNOT_CREATE_AN_ITEM_WITH_THE_SLUG_TM_IN_THE_ROOT_FOLDER_BECAUSE_THIS_IS_THE_SYSTEM_PATH: '' +YOU_CAN_OVERWRITE_THE_THEME_CSS_WITH_YOUR_OWN_CSS_HERE: 'Je kunt de thema-css hier overschrijven met je eigen css.' +YOU_CAN_USE_THE_ASTERISK_WILDCARD_TO_SEARCH_FOR_NAME@_OR_@DOMAIN_COM: '' +YOU_DO_NOT_HAVE_ENOUGH_RIGHTS: '' +YOU_TRIED_TO_OPEN_THE_PASSWORD_RESET_PAGE_BUT_THE_LINK_WAS_INVALID: '' +YOU_TRIED_TO_SET_A_NEW_PASSWORD_BUT_USERNAME_OR_TOKEN_WAS_INVALID: '' +©: '' +LICENSE_DATA_INCOMPLETE: '' +LICENSE_DATA_INVALID: '' +NO_LICENSE_FOUND: '' +THE_LICENSE_SERVER_RESPONDED_WITH: '' +THE_SUBSCRIPTION_PERIOD_IS_NOT_PAID_YET: '' +VALIDATION_FAILED: '' diff --git a/system/typemill/author/translations/ru.yaml b/system/typemill/author/translations/ru.yaml new file mode 100644 index 0000000..476a0e3 --- /dev/null +++ b/system/typemill/author/translations/ru.yaml @@ -0,0 +1,621 @@ +# Russian (Русский) | Author: Paul (https://paul.bid) paulbid@protonmail.com +ACCESS: '' +ACCESS_FOR: 'Доступ для' +ACCESS_RIGHTS: 'Доступ и Права' +ACCESS_TO_BASEPATH_IS_NOT_ALLOWED: 'Доступ к базовому пути не разрешён.' +ACCOUNT: Аккаунт +ACCOUNT_CREATED_PLEASE_LOGIN_WITH_YOUR_USERNAME_AND_PASSWORD_NOW: '' +ACTIVATE_API_ACCESS_FOR_THIS_USER_USE_USERNAME_AND_PASSWORD_FOR_API_CALLS: 'Включите доступ к API для этого пользователя. Для этого используйте имя пользователя и пароль для доступа к API.' +ACTIVATE_A_PASSWORD_RECOVERY_IN_THE_LOGIN_FORM: '' +ACTIVATE_INDIVIDUAL_RESTRICTIONS_FOR_PAGES_IN_THE_META_TAB_OF_EACH_PAGE: 'Индивидуальные ограничения для страниц могут быть включены на вкладке «Метаданные» каждой страницы.' +ACTIVATE_THE_CACHE_FOR_TWIG_TEMPLATES: 'Включить кэш для шаблонов Twig' +ACTIVATE_THE_DARKMODE_FOR_ME: 'Включить и использовать тёмный режим (тёмную тему оформления).' +ACTIVATE_YOUR_LICENSE: '' +ACTIVATION_FAILED_BECAUSE_YOU_NEED_A_VALID: 'Для активации не хватает верного ' +ACTIVE: Активна(о) +ACTUAL_PASSWORD: 'Текущий пароль' +ADD: Добавить +ADD_A_FILE: '' +ADD_A_FOLDER: '' +ADD_DEFINITION: 'добавить определение' +ADD_DESCRIPTION: '' +ADD_ENTRY: 'Добавить запись' +ADD_LEFT_COLUMN: 'добавить колонку слева' +ADD_MORE_URL_SCHEMES_FOR_EXTERNAL_LINKS_E_G_LIKE_DICT_COMMA_SEPARATED_LIST: 'Добавьте дополнительные схемы URL-адресов для внешних ссылок, например такие как dict:// (список, разделённый запятыми)' +ADD_NOINDEX_TAG_AND_EXCLUDE_FROM_SITEMAP: 'Добавить тег noindex и исключить эту страницу из карты сайта' +ADD_ONE_OR_MORE_USERNAMES_SEPARATED_WITH_COMMA: 'Добавьте одно или несколько имён пользователей через запятую.' +ADD_RIGHT_COLUMN: 'добавить колонку справа' +ADD_ROW_ABOVE: 'добавить строку выше' +ADD_ROW_BELOW: 'добавить строку ниже' +ALLOWED_DOMAINS_FOR_API_ACCESS_CORS_HEADERS: '' +ALLOWED_DOMAINS_FOR_CONTENT_ON_TYPEMILL_CSP_HEADERS: '' +ALLOW_SVG: 'Разрешить SVG' +ALLOW_THE_UPLOAD_OF_SVG_IMAGES: 'Разрешить загрузку SVG изображений' +ALL_PAGES: 'Все страницы' +ALTERNATIVE_TEXT_FOR_THE_HERO_IMAGE: 'Альтернативный текст-описание главного изображения для записи' +ALT_TEXT: 'Альтернативный текст' +ALWAYS_SHOW: 'Показывать всегда' +AND_YOU_CANNOT: ' и вы не можете ' +AND_YOU_MIGHT_FIND_THE_FOLLOWING_TIPS_HELPFUL: 'И, возможно, вам будут полезны следующие рекомендации' +ANSWER_FROM_LICENSE_SERVER: '' +API_ACCESS: 'Доступ к API' +ARTICLE_DATE: 'Дата записи' +AUTHOR: Автор +AUTHOR_DESCRIPTION_MARKDOWN: 'Об авторе (это поле поддерживает Markdown)' +BACK_TO_LOGIN: '' +BLOCK_ID_NOT_FOUND: 'ID блока не найден.' +BOLD: Полужирный +BULLET_LIST: 'Маркированный список' +BUY_A_LICENSE: 'Приобрести лицензию' +CANCEL: Отмена +CAN_BE_USED_FOR_AUTHOR_LINE_IN_FRONTEND: 'Может использоваться для информации об авторе на страницах сайта.' +CAPTION: Название +CC_BY: CC-BY +CC_BY_NC: CC-BY-NC +CC_BY_NC_ND: CC-BY-NC-ND +CC_BY_NC_SA: CC-BY-NC-SA +CC_BY_ND: CC-BY-ND +CC_BY_SA: CC-BY-SA +CENTER: 'По центру' +CHANGE_SLUG: 'Изменить URL страницы' +CHECK: проверить +CHECK_YOUR_INBOX: 'Проверьте свой почтовый ящик' +CHECK_YOUR_LICENSE: 'Проверьте свою лицензию' +CLASS: Класс +CLEAR: Очистить +CLOSE: Закрыть +CLOSE_LIBRARY: 'Закрыть библиотеку' +CODE: Код +COLLAPSE_ALL: 'Свернуть всё' +CONFIGURE: Настройка +CONFIRM: '' +CONTENT_BREAK: 'Разделитель содержимого' +CONVERT_TO_WEBP: 'Конвертировать в WEBP' +COPYRIGHT: 'Тип лицензии для контента на сайте' +COPY_THE_CONTENT_OF_THE_REFERENCED_INTERNAL_PAGE: '' +COULD_NOT_CREATE_FOLDER: 'Не удалось создать папку' +COULD_NOT_DECODE_IMAGE_OR_FILE_PROBABLY_NOT_A_BASE64_ENCODING: 'Не удалось расшифровать файл или изображение, требуется кодировка Base64' +COULD_NOT_DELETE_FILE: 'Не удалось удалить файл' +COULD_NOT_DELETE_FOLDER: 'Не удалось удалить папку' +COULD_NOT_GET_THE_VIDEO_IMAGE: 'Не удалось загрузить видеоизображение' +COULD_NOT_OPEN_AND_READ_THE_FILE: 'Не удалось открыть и прочитать файл' +COULD_NOT_READ_PATHINFO: 'Не удалось прочитать параметр PATHINFO' +COULD_NOT_STORE_THE_CUSTOM_SIZE_OF: 'Не удалось сохранить пользовательский размер' +COULD_NOT_STORE_THE_IMAGE_IN_THE_TEMPORARY_FOLDER: 'Не удалось сохранить изображение во временную папку' +COULD_NOT_STORE_THE_RESIZED_VERSION: 'Не удалось сохранить уменьшенную версию' +COULD_NOT_WRITE_TO_THE_FILE: 'Не удалось записать и сохранить файл' +CREATED_AT_READ_ONLY: 'Создано (только просмотр)' +CREATE_NEW_PASSWORD: 'Создать новый пароль' +CREATE_POST: 'Создать запись' +CREATE_USER: 'Создать пользователя' +CUSTOM_CSS: 'Дополнительный CSS' +CUT_RESTRICTED_CONTENT_AFTER_THE_FIRST_HR_ELEMENT_ON_A_PAGE_PER_DEFAULT_CONTENT_WILL_BE_CUT_AFTER_TITLE: 'Обреза́ть содержимое страницы после первого разделителя (в противном случае содержимое страницы будет обрезано после заголовка).' +DARKMODE: 'Тёмный режим' +DEAR: 'Дорогой ' +DEAR_USER: 'Дорогой пользователь' +DEFAULT_WIDTH_OF_LIVE_IMAGES_IS_820PX_CHANGES_WILL_APPLY_TO_FUTURE_UPLOADS: 'Стандартная ширина для живых изображений составляет 820px. Изменения применяются только к новым изображениям.' +DEFINITION: 'Список определений' +DEFINITION_LIST: 'Список определений' +DELETE: Удалить +DELETE_COLUMN: 'Удалить колонку' +DELETE_DESCRIPTION: '' +DELETE_PAGE: 'Удалить страницу' +DELETE_ROW: 'Удалить строку' +DELETE_USER: 'Удалить пользователя' +DEVELOPER: 'Опции разработчика' +DISABLE: Отключить +DISABLE_ALL_CSP_HEADERS_CONTENT_SECURITY_POLICY_FOR_THIS_WEBSITE: '' +DISABLE_ALL_CUSTOM_HEADERS_OF_TYPEMILL_EXCEPT_CORS_AND_SEND_YOUR_OWN_HEADERS_INSTEAD: '' +DISABLE_CSP_HEADERS: '' +DISABLE_CUSTOM_HEADERS: '' +DISCARD: Сбросить +DISCARD_CHANGES: 'Сбросить изменения' +DISPLAY_APPLICATION_ERRORS: 'Отображение ошибок' +DOES_NOT_EXIST: 'Не существует' +DOES_NOT_EXIST_OR_IS_NOT_WRITABLE: 'Не существует или недоступен для записи' +DOMAIN_FOR_LICENSE: 'Домен для лицензии' +DONATE: Пожертвования +DONE: Готово +DO_NOT_FORGET_TO_CHECK_YOUR_SPAM_FOLDER_IF_YOUR_INBOX_IS_EMPTY: '' +DO_NOT_RESIZE: 'Не уменьшать масштаб' +DO_YOU_REALLY_WANT_TO_DELETE_THIS_PAGE: 'Вы действительно хотите удалить эту страницу?' +DO_YOU_REALLY_WANT_TO_DELETE_THIS_USER: 'Вы действительно хотите удалить этого пользователя?' +DO_YOU_WANT_TO_DISCARD_YOUR_CHANGES_AND_SET_THE_CONTENT_BACK_TO_THE_LIVE_VERSION: 'Вы хотите сбросить изменения и вернуться к предыдущей версии?' +DRAFT: Черновик +DRAG_A_PICTURE_OR_CLICK_TO_SELECT: '➕ загрузить изображение' +DUTCH_FLEMISH: 'Dutch, Flemish (Голландский, Фламандский)' +EDIT: Редактировать +EMAIL: Email +EMAIL_ADDRESS_IN_SYSTEM_SETTINGS_IS_MISSING: '' +EMAIL_SUBJECT: 'Тема электронного письма' +ENGLISH: 'English (Английский)' +ENTER_AN_EMAIL_ADDRESS_THAT_SENDS_THE_E_MAILS_SENDER_THE_E_MAIL_FEATURE_WILL_BE_USED_FOR_RECOVERY_AND_VERIFICATION_E_MAILS_SEND_A_TESTMAIL_TO_YOUR_USER_ACCOUNT_TO_VERIFY_THAT_YOU_RECEIVE_THE_E_MAILS: '' +ENTER_THE_EMAIL_OF_YOUR_USER_ACCOUNT_CLICK_THE_RECOVER_BUTTON_AND_CHECK_YOUR_MAILBOX_FOR_FURTHER_INSTRUCTIONS: 'Введите адрес электронной почты вашей учётной записи пользователя, затем нажмите кнопку «Восстановить» и проверьте свою электронную почту для получения дальнейших инструкций.' +ENTER_THE_FULL_DOMAIN_LIKE_HTTPS_WWWW_MYWEBSITE_DE: '' +ENTER_THE_VERIFICATION_CODE_FROM_YOUR_EMAIL: '' +ENTER_YOUR_E_MAIL_ADDRESS_THAT_YOU_USED_FOR_YOUR_LICENSE_PURCHASE: '' +ENTER_YOUR_LICENSE_KEY_THAT_YOU_GOT_AFTER_YOUR_PURCHASE_VIA_EMAIL: '' +ERROR_REPORTING: 'Отчёты об ошибках' +ERROR_SENDING_EMAIL: 'Ошибка при отправке email' +EXPAND_ALL: 'Развернуть всё' +EXTENSION_OR_FILENAME_ARE_MISSING: 'Отсутствует имя или расширение файла' +EXTENSION_OR_NAME_FOR_IMAGE_IS_MISSING: 'Отсутствует имя или расширение изображения' +EXTERNAL_LINK: 'внешняя ссылка' +E_MAIL: Email +FAVICON: 'Иконка сайта (favicon)' +FILE: Файл +FILENAME_IS_MISSING: 'Отсутствует имя файла.' +FILENAME_OR_USERROLE_IS_MISSING: 'Отсутствуют имя файла или роль пользователя.' +FILETYPE_IS_NOT_ALLOWED: 'Тип файла не разрешён.' +FILE_DELETED_SUCCESSFULLY: 'Файл успешно удалён' +FILE_HAS_BEEN_STORED: 'Файл сохранён' +FILE_IS_BIGGER_THAN_20MB: 'Размер файла превышает 20 МБ.' +FILE_IS_EMPTY: 'Файл пуст.' +FILE_IS_MISSING: 'Файл отсутствует.' +FILE_NOT_FOUND: 'Файл не найден.' +FILE_SAVED_SUCCESSFULLY: 'Файл успешно сохранён' +FIRST_NAME: Имя +FOLDER: раздел +FORGOT_PASSWORD: 'Забыл пароль' +FORGOT_YOUR_PASSWORD: 'Забыл свой пароль' +FORMAT_OPTIONS_FOR_VISUAL_EDITOR: 'Параметры и опции визуального редактора' +FRENCH: 'French (Французский)' +FROM_MAIL_IS_REQUIRED_FOR_THIS_FEATURE_SEND_A_TESTMAIL_BEFORE_YOU_USE_THIS_FEATURE: '' +FROM_MAIL_IS_REQUIRED_FOR_THIS_FEATURE_SEND_A_TESTMAIL_BEFORE_YOU_USE_THIS_FEATURE_MAKE_SURE_YOU_HAVE_FTP_ACCESS_TO_DISABLE_THE_FEATURE_IN_SETTINGS_YAML_ON_FAILURE_THE_VERIFICATION_CODE_WILL_BE_VALID_FOR_5_MINUTES_BE_AWARE_THAT_DEVICE_FINGERPRINTS_WILL_BE_STORED_IN_THE_USER_ACCOUNTS: '' +GERMAN: 'German (Немецкий)' +GET_INSPIRED_AND_ENJOY_YOUR_WRITING: 'Получайте вдохновение и море радости от написания.' +GOOGLE_SITEMAP_READONLY: 'Google Sitemap (только просмотр)' +GO_TO_LOGIN: 'Перейти на страницу входа' +HAS_EDIT_RIGHTS_FOR_THIS_ARTICLE: 'Этот пользователь будет иметь права на редактирование этой записи.' +HEADLINE: Заголовок +HEADLINE_ANCHORS: 'Якорные ссылки в заголовках' +HERO_IMAGE: 'Главное изображение для записи' +HEY_WRITER_AUTHOR_EDITOR_CONTENT_GURU_OR_WEBSITE_MANAGER: 'Приветик автор, редактор, контент-гуру или администратор.' +HIDE: 'Скрыть страницу' +HIDE_PAGE_FROM_NAVIGATION: 'Скрывать страницу из меню навигации сайта' +HOMEPAGE: 'главную страницу' +HORIZONTAL_LINE: 'Горизонтальная линия' +HR: 'Горизонтальная линия' +IF_NOT_FILLED_THE_DESCRIPTION_IS_EXTRACTED_FROM_CONTENT: 'Если поле не заполнено, описание генерируется из содержимого.' +IF_YOUR_PROXY_DOES_NOT_WORK_TRY_TO_ADD_THE_BASE_URL_OF_YOUR_PROXY_HERE_LIKE_HTTPS_MYWEBSITE_COM: '' +IF_YOU_ADD_A_VALUE_FOR_THE_HEIGHT_THEN_THE_IMAGE_WILL_BE_CROPPED: 'Если вы добавите значение высоты, изображение будет обрезано.' +IF_YOU_DID_NOT_MAKE_THIS_LOGIN_ATTEMPT_PLEASE_RESET_YOUR_PASSWORD_IMMEDIATELY: '' +IF_YOU_DID_NOT_RECEIVE_AN_EMAIL_WITH_THE_VERIFICATION_CODE_THEN_THE_USERNAME_OR_PASSWORD_YOU_ENTERED_WAS_WRONG_PLEASE_TRY_AGAIN: '' +IF_YOU_UNPUBLISH_THE_PAGE_THEN_WE_WILL_DELETE_THE_PUBLISHED_VERSION_AND_KEEP_THE_MODIFIED_VERSION: 'Если вы уберёте статус опубликовано у записи, то опубликованная версия будет удалена, а изменённая версия — сохранена.' +IF_YOU_WANT_TO_KEEP_YOUR_CHANGES_THEN_CLICK_ON_CANCEL_AND_SAVE_YOUR_CHANGES_BEFORE_YOU_SWITCH_TO_THE_VISUAL_EDITOR: '' +IMAGE: Изображение +IMAGENAME_IS_MISSING: 'Имя изображения отсутствует' +IMAGES_WITH_THIS_EXTENSION_ARE_NOT_ALLOWED: 'Изображения с таким расширением не разрешены.' +IMAGE_DELETED_SUCCESSFULLY: 'Изображение успешно удалено' +IMAGE_OR_FILENAME_IS_MISSING: 'Изображение или имя файла не найдено' +IMAGE_OR_NAME_IS_MISSING: 'Изображение или имя не найдено' +IMAGE_SAVED_SUCCESSFULLY: 'Изображение успешно сохранено' +IMAGE_URL: 'URL изображения' +IMAGE_URL_READ_ONLY: 'URL изображения (только просмотр)' +INFO: Информация +INVALID_VERIFICATION_CODE_FORMAT_PLEASE_TRY_AGAIN: '' +INVISIBLE: '' +IS_NOT_A_DIRECTORY: 'Это не каталог' +IS_NOT_A_FOLDER: 'Это не папка' +ITALIAN: 'Italian (Итальянский)' +ITALIC: Наклонный +IT_IS_NOT_ALLOWED_TO_WRITE_INTO: 'Не удалось записать в' +LANGUAGE: Язык +LANGUAGE_ATTRIBUTE_WEBSITE: 'Языковой атрибут (сайт)' +LANGUAGE_AUTHOR_AREA: 'Язык (в интерфейсе автора)' +LAST_MODIFIED_LIVE_READONLY: 'Последняя редакция (нельзя изменить)' +LAST_NAME: Фамилия +LEFT: Слева +LICENCE_HAS_BEEN_STORED: '' +LICENSE: Лицензия +LICENSE_AND_YOUR_WEBSITE_MUST_RUN_UNDER_THE_DOMAIN_OF_YOUR_LICENSE: '-Лицензия и сайт должен работать под доменом, указанным в лицензии.' +LICENSE_DATA_ARE_INCOMPLETE: '' +LICENSE_DATA_MISSING: 'Данные о лицензии отсутствуют' +LICENSE_REQUIRED: 'Необходима лицензия' +LICENSE_VALIDATION_FAILED: '' +LINK: Ссылка +LINK_TO_AN_EXTERNAL_PAGE: '' +LINK_TO_YOUTUBE: 'Ссылка на Youtube' +LIST_ALL_DOMAINS_SEPARATED_BY_COMMAS_TO_ALLOW_CONTENT_INTEGRATION_SUCH_AS_IFRAMES_ON_YOUR_TYPEMILL_WEBSITE_DOMAINS_WILL_BE_ADDED_TO_THE_CSP_HEADER_USUALLY_DONE_WITH_PLUGINS_AND_THEMES_BUT_ADD_MANUALLY_IF_SOMETHING_IS_BLOCKED: '' +LIST_ALL_DOMAINS_SEPARATED_BY_COMMA_THAT_SHOULD_HAVE_ACCESS_TO_THE_TYPEMILL_API_DOMAINS_WILL_BE_ADDED_TO_THE_CORS_HEADER: '' +LOGIN: Войти +LOGIN_TO_THE_AUTHOR_AREA_OR_GO_TO_THE: 'Войдите в интерфейс авторов или перейдите на' +LOGIN_VERIFICATION_RECOMMENDED: '' +LOGO: Логотип +MAIL_FROM_NAME_OPTIONAL: '' +MAIL_FROM_REQUIRED: '' +MANUAL_DATE: 'Выбор даты вручную' +MARKDOWN: Markdown +MARKDOWN_IS_MISSING: 'Markdown отсутствует.' +MAXIMUM_SIZE_FOR_AN_IMAGE_IS_5_MB_HERO_IMAGES_ARE_NOT_SUPPORTED_BY_ALL_THEMES: 'Максимальный размер изображения — 5 МБ. Главные изображения для записей поддерживаются не всеми темами.' +MAXIMUM_SIZE_FOR_FILE_UPLOADS_IN_MB: 'Максимальный размер файлов в МБ' +MAXIMUM_SIZE_FOR_IMAGE_UPLOADS_IN_MB: 'Максимальный размер изображений в МБ' +MAXIMUM_SIZE_OF_FILE_LINK_IS_100_CHARACTERS: '' +MAXIMUM_SIZE_OF_FILE_TEXT_IS_100_CHARACTERS: '' +MAXIMUM_SIZE_OF_IMAGE_ALT_TEXT_IS_100_CHARACTERS: 'Максимум 100 символов для альтернативного текста к изображению' +MAXIMUM_SIZE_OF_IMAGE_CAPTION_IS_140_CHARACTERS: 'Максимум 140 символов для подписи к изображению' +MAXIMUM_SIZE_OF_IMAGE_CLASS_IS_100_CHARACTERS: 'Максимум 100 символов для имени CSS класса изображения' +MAXIMUM_SIZE_OF_IMAGE_ID_IS_100_CHARACTERS: 'Максимум 100 символов для ID атрибута изображения' +MAXIMUM_SIZE_OF_IMAGE_LINK_IS_100_CHARACTERS: 'Максимум 100 символов для ссылки на изображение' +MAXIMUM_SIZE_OF_IMAGE_TITLE_IS_100_CHARACTERS: 'Максимум 100 символов для заголовка изображения' +MEDIA: Медиа +MEDIALIB: Медиа-библиотека +MENU: Меню +META_CONTENT: Мета-контент +META_DESCRIPTION: Мета-описание +META_TITLE: Мета-заголовок +MINIMUM_USER_ROLE_TO_ACCESS_THIS_PAGE: 'Минимальная роль пользователя для доступа к этой странице' +NAVIGATION_TITLE: 'Название страницы в меню навигации' +NEW_PASSWORD: 'Новый пароль' +NEW_USER: '➕ Новый пользователь' +NEW_USER_CREATED: 'Новый пользователь успешно создан.' +NOINDEX: 'Тег noindex' +NOTICE: Заметка +NO_FILE_FOUND: 'Файл(ы) не найден(ы).' +NO_IMAGE_FOUND: 'Изображения не найдены.' +NO_PROBLEM_YOU_CAN_CREATE_A_NEW_ONE_HERE: 'Нет проблем, создайте здесь новый.' +NUMBERED_LIST: 'Нумерованный список' +OLIST: 'Упорядоченный список' +ONE_OR_MORE_FILES_COULD_NOT_BE_TRANSFORMED: 'Не удалось перезаписать один или несколько файлов.' +ONLY_IMAGES_ARE_ALLOWED: 'Разрешены только изображения' +ONLY_PNG_FORMAT_WILL_WORK: 'Будет работать только формат PNG' +ONLY_THE_FOLLOWING_USERS_HAVE_ACCESS: 'Только следующие пользователи имеют доступ к этой странице' +OPEN: '' +OPTIONALLY_ENTER_A_NAME_FOR_THE_SENDER_ADDRESS_IF_NOT_SET_THE_FROM_ADDRESS_WILL_BE_VISIBLE: '' +OPTIONALLY_ENTER_A_REPLY_TO_ADDRESS_FOR_ANSWERS_FROM_THE_RECEIVER_IF_NOT_SET_ANSWERS_WILL_GO_TO_THE_FROM_ADDRESS: '' +OWNER_USERNAME: 'Владелец (имя пользователя)' +PAGES_SORT_IN_NAVIGATION_WITH_DRAG_DROP: 'Перемещать страницы (с помощью перетаскивания в блоке навигации)' +PAGE_NOT_FOUND: 'К сожалению, запрашиваемая страница не найдена' +PAGE_RESTRICTION: 'Ограничения страницы' +PARAGRAPH: Параграф +PASSWORD: Пароль +PATH_IS_MISSING: 'Путь отсутствует' +PERMANENT_REDIRECT_301_THE_USER_TO_THE_REFERENCED_INTERNAL_PAGE: '' +PERMISSION_DENIED: 'В доступе отказано' +PLEASE_CHECK_IF_THERE_IS_A_READABLE_FILE_PUBLIC_KEY_PEM_IN_YOUR_SETTINGS_FOLDER: '' +PLEASE_CHECK_THE_INBOX_OF_YOUR_EMAIL_ACCOUNT_FOR_MORE_INSTRUCTIONS: 'Проверьте свою электронную почту для получения дополнительной информации.' +PLEASE_CONFIRM: 'Пожалуйста подтвердите' +PLEASE_CORRECT_YOUR_INPUT: 'Пожалуйста, исправьте введённые данные.' +PLEASE_ENTER_A_VALID_EMAIL: 'Пожалуйста, укажите корректный адрес электронной почты.' +PLEASE_LOGIN_WITH_YOUR_NEW_PASSWORD: 'Пожалуйста, войдите с вашим новым паролем.' +PLEASE_SELECT: 'Пожалуйста выберите' +PLEASE_USE_THE_FOLLOWING_LINK_TO_SET_A_NEW_PASSWORD: 'Пожалуйста, перейдите по ссылке, чтобы установить новый пароль' +PLUGINS: Плагины +PLUGIN_SETTINGS: 'Настройки плагинов' +POSTS_SORTED_BY_PUBLISH_DATE_FOR_NEWS_OR_BLOGS: 'Записи (отсортированные по дате публикации, для новостей или блогов)' +PROFILE_IMAGE: 'Изображение профиля (аватар)' +PROXY: Прокси +PUBLISH: Опубликовать +QUOTE: Цитата +QUOTES: Цитирует +RAW: 'исходный код' +RAW_EDITOR: 'Markdown редактор' +RECOVER_PASSWORD: 'Восстановление пароля' +REFERENCE: '' +REFERENCE_TO_PAGE: '' +REPEAT_PASSWORD: 'Повторите пароль' +REPLY_TO_OPTIONAL: '' +REQUIRED: Обязательно +RESTRICTION_NOTICE_USE_MARKDOWN: 'Уведомление об ограничениях (это поле поддерживает Markdown)' +RIGHT: Справа +ROLE: Роль +RUSSIAN: 'Russian (Русский)' +SAVE: Сохранить +SEARCH: Поиск +SECURITY: Безопасность +SECURITY_LOG: 'Журнал безопасности' +SELECT_FROM_MEDIALIB: 'Выбрать из библиотеки' +SELECT_THE_LOWEST_USERROLE_HIGHER_ROLES_WILL_HAVE_ACCESS_TOO: 'Выберите минимальную роль пользователя, который будет иметь доступ к этой странице. Пользователи с более важными ролями также будут иметь доступ к странице.' +SETTINGS_HAVE_BEEN_SAVED: 'Настройки успешно сохранены' +SETUP: Установка +SHORTCODE: Шотркод +SHORTCODES: '' +SHORT_TITLE_FOR_POST: 'Краткое название для записи' +SHOW_AFTER_FIRST_WRONG_INPUT: 'Показывать после первой неудачной попытки входа' +SHOW_ANCHORS_NEXT_TO_HEADLINE_IN_FRONTEND: 'Показывать якорные ссылки рядом с заголовками в интерфейсе' +SHOW_THE_WEBSITE_ONLY_TO_AUTHENTICATED_USERS_AND_REDIRECT_ALL_OTHER_USERS_TO_THE_LOGIN_PAGE: 'Отображать сайт только для аутентифицированных пользователей, а всех остальных автоматически перенаправлять на страницу входа.' +SLUG: 'Часть URL-адреса' +SOMEONE_TRIED_TO_LOG_IN_TO_YOUR_TYPEMILL_WEBSITE_AND_WE_WANT_TO_MAKE_SURE_IT_IS_YOU_ENTER_THE_FOLLOWING_VERIFICATION_CODE_TO_FINISH_YOUR_LOGIN_THE_CODE_WILL_BE_VALID_FOR_5_MINUTES: '' +SOMETHING_WENT_WRONG_PLEASE_REFRESH_THE_PAGE_AND_CHECK_IF_ALL_FOLDERS_AND_FILES_ARE_WRITABLE: 'Что-то пошло не так, пожалуйста, перезагрузите страницу и проверьте, доступны ли папки и файлы для записи.' +SOMETHING_WENT_WRONG_THE_INPUT_IS_NOT_VALID: 'Что-то пошло не так, входные данные недействительны.' +SPECIAL_CHARACTERS_ARE_NOT_ALLOWED_LENGTH_BETWEEN_1_AND_40: '' +STANDARD_EDITOR_MODE: 'Стандартный режим редактора' +STANDARD_HEIGHT_FOR_LIVE_PICTURES: 'Стандартная высота для живых изображений' +STANDARD_WIDTH_FOR_LIVE_PICTURES: 'Стандартная ширина для живых изображений' +SUBMIT_THE_URL_ABOVE_IN_GOOGLE_SEARCH_CONSOLE_TO_SUPPORT_INDEXING: '' +SWITCH_TO_VISUAL: '' +SYSTEM: Настройки +SYSTEMCHECK: 'Проверка системы' +SYSTEM_SETTINGS: 'Системные настройки' +TABLE: Таблица +TABLE_OF_CONTENTS: Оглавление +TAB_NOT_FOUND: 'Вкладка не найдена' +TEMPORARY_REDIRECT_302_THE_USER_TO_THE_REFERENCED_INTERNAL_PAGE: '' +TESTMAIL_FROM_TYPEMILL: '' +TEXT_BEFORE_RECOVER_LINK_IN_EMAIL_MESSAGE: 'Текст перед ссылкой на восстановление пароля в самом письме' +TEXT_FILE: 'текстовый файл' +THEMES: Темы +THEME_SETTINGS: 'Настройки темы' +THERE_ARE_PUBLISHED_PAGES_WITHIN_THIS_FOLDER_THE_PAGES_ARE_NOT_VISIBLE_ON_YOUR_WEBSITE_ANYMORE: 'Папка содержит опубликованные страницы. Страницы больше не доступны для гостей и пользователей сайта.' +THERE_IS_ALREADY_A_PAGE_WITH_THAT_SLUG: 'Страница с такой частью URL-адреса уже существует' +THERE_IS_ALREADY_A_PAGE_WITH_THIS_NAME_PLEASE_CHOOSE_ANOTHER_NAME: 'Страница с таким названием уже существует. Пожалуйста, выберите другое имя.' +THERE_WAS_AN_ERROR_CHECKING_THE_LICENSE_SIGNATURE: 'Ошибка при проверке подлинности лицензии' +THE_BEST_OPTION_IS_A_SEPARATE_PASSWORD_MANAGER_LIKE_KEEPASS_AND_OTHERS: 'Лучшее решение это отдельный менеджер паролей, такой как KeePass и другие.' +THE_CAPTCHA_IS_WRONG_PLEASE_TRY_AGAIN: 'Неправильная капча, пожалуйста, попробуйте ещё раз' +THE_FOLDER_CONTAINS_ANOTHER_FOLDER_SO_WE_CANNOT_TRANSFORM_IT_PLEASE_MAKE_SURE_THERE_ARE_ONLY_FILES_IN_THIS_FOLDER: 'Папка содержит вложенную папку, поэтому не получится её изменить. Убедитесь, что в папке есть только файлы, иначе содержимое не сможет быть преобразовано.' +THE_FOLDER_CONTAINS_PUBLISHED_PAGES_PLEASE_UNPUBLISH_OR_DELETE_THEM_FIRST: 'Папка содержит опубликованные страницы. Пожалуйста, сначала сделайте страницы снова неопубликованными или удалите их.' +THE_FOLLOWING_REQUIREMENTS_FOR_THE_INSTALLATION_ARE_MISSING: 'Отсутствуют следующие предварительные условия для установки' +THE_FROM_MAIL_IS_MISSING_OR_IT_IS_NOT_A_VALID_E_MAIL_ADDRESS: '' +THE_ID_OF_THE_CONTENT_BLOCK_IS_WRONG: 'ID блока содержимого неверен.' +THE_LICENSE_DATA_ARE_INVALID: '' +THE_LINK_TO_RECOVER_THE_PASSWORD_WAS_TOO_OLD_PLEASE_CREATE_A_NEW_ONE: 'Срок действия ссылки для восстановления пароля истёк. Пожалуйста, создайте новую ссылку.' +THE_MAXIMUM_FILE_SIZE_MIGHT_BE_LIMITED_BY_YOUR_SERVER_SETTINGS: 'Максимальный размер файла может быть ограничен настройками сервера.' +THE_MAXIMUM_IMAGE_SIZE_MIGHT_BE_LIMITED_BY_YOUR_SERVER_SETTINGS: 'Максимальный размер изображения может быть ограничен настройками сервера.' +THE_MIME_TYPE_IS_MISSING_NOT_ALLOWED_OR_DOES_NOT_FIT_TO_THE_FILE_EXTENSION: 'Mime тип отсутствует, не разрешён или не соответствует расширению файла.' +THE_PLUGIN_OR_THEMES_WAS_NOT_FOUND: 'Плагин или тема не найдены.' +THE_RECOVER_LINK_WILL_BE_ACTIVE_FOR_24_HOURS: '' +THE_REQUESTED_FILETYPE_DOES_NOT_EXIST: 'Запрашиваемый тип файла не существует.' +THE_REQUESTED_FILE_DOES_NOT_EXIST: 'Запрашиваемый файл не существует.' +THE_SUBSCRIPTION_PERIOD_HAS_NOT_BEEN_PAID_YET_AND_WE_GOT_AN_ERROR: '' +THE_SUBSCRIPTION_PERIOD_HAS_NOT_BEEN_PAID_YET_WE_WILL_CHECK_IT_EVERY_60_MINUTES: '' +THE_TESTMAIL_HAS_BEEN_SEND_PLEASE_CHECK_YOUR_INBOX_AND_YOUR_SPAM_FOLDER_TO_VARIFY_THAT_YOU_RECEIVED_THE_MAIL: '' +THE_VERIFICATION_WAS_WRONG_OR_OUTDATED_PLEASE_START_AGAIN: '' +THE_VERSION_CHECK_FAILED_BECAUSE_OF_INVALID_PARAMETERS: 'Проверка версии не удалась из-за недопустимых параметров.' +THE_WEBSITE_IS_RUNNING_NOT_UNDER_THE_DOMAIN_OF_YOUR_LICENSE: '' +THIS: ' это ' +THIS_FIELD_IS_NOT_DEFINED: 'Это поле не определено.' +THIS_FOLDER_CONTAINS: 'Эта папка содержит: ' +THIS_IS_A_TESTMAIL_FROM_TYPEMILL_AND_IF_YOU_READ_THIS_E_MAIL_THEN_EVERYTHING_WORKS_FINE: '' +THIS_PAGE: 'Эта страница' +THIS_PAGE_HAS_BEEN_MODIFIED: 'Эта страница была отредактирована' +THIS_PAGE_HAS_UNSAVED_CHANGES: '' +TITLE: Название +TOC: Оглавление +TO_DOWNLOAD_THIS_FILE_YOU_NEED_TO_BE_AUTHENTICATED_WITH_THE_ROLE: 'Загрузка файла возможна только со следующей ролью пользователя: ' +TRACK_SPAM_AND_SUSPICIOUS_ACTIONS_IN_A_LOGFILE: 'Записывать спам и подозрительные действия в журнал (в специальный логфайл)' +TRASH: Корзина +TRUSTED_IPS_FOR_PROXIES_COMMA_SEPARATED: 'Доверенные IP-адреса для прокси-серверов (разделённые запятой)' +TRY_TO_CONVERT_UPLOADED_IMAGES_INTO_THE_WEBP_FORMAT_FOR_BETTER_PERFORMANCE: '' +TWIG_CACHE: 'Twig кэш' +TYPE_OF_REFERENCE: '' +ULIST: 'Маркированный список' +UNPUBLISH: 'Снять с публикации' +UNPUBLISH_PAGE: 'Снять страницу с публикации' +UNSAVED_CHANGES: '' +UPLOAD: загрузить +UPLOAD_AN_IMAGE: 'Загрузить изображение' +UPLOAD_FILE: 'Загрузить файл' +URL_SCHEMES: 'Схемы URL-адресов' +USED_AS_FALLBACK_WHEN_NO_MANUAL_DATE_IS_SET: 'Используется, когда дата не установлена в ручную.' +USED_FOR_COPYRIGHT_AND_YEAR_IN_FOOTER: '' +USED_FOR_FRONTEND_LANGUAGE_ATTRIBUTE_PLEASE_USE_ISO_639_1_CODES_LIKE_EN: '' +USED_FOR_TRANSLATIONS_IN_AUTHOR_AREA_THEMES_AND_PLUGINS: '' +USER: Пользователь +USERDATA_ARE_REQUIRED: 'Пользовательские данные обязательны.' +USERNAME: 'Имя пользователя' +USERNAME_IS_REQUIRED: 'Имя пользователя обязательно' +USERNAME_READ_ONLY: 'Имя пользователя (не изменяется)' +USERROLE: 'Роль пользователя' +USERROLE_IS_REQUIRED: 'Роль пользователя обязательно' +USERROLE_IS_UNKNOWN: 'Роль пользователя неизвестна' +USERS: Пользователи +USER_DELETED: 'Пользователь удалён.' +USER_HAS_BEEN_UPDATED: 'Пользователь и его данные обновлены.' +USER_NOT_FOUND: 'Пользователь не найден' +USE_A_STRONG_AND_INDIVIDUAL_PASSWORD_FOR_EVERY_ACCOUNT: 'Используйте надёжный и индивидуальный пароль для каждой учётной записи.' +USE_CAPTCHA_IN_AUTHENTICATION_FORMS: 'Проверка с помощью Captcha при аутентификации' +USE_X_FORWARDED_HEADER: 'Использовать X-Forwarded заголовок' +VERIFICATION_CODE_MISSING: '' +VERIFY_YOUR_LOGIN_WITH_A_5_DIGIT_CODE_SEND_BY_EMAIL: '' +VERSION: Версия +VIDEO: Видео +VISIBILITY: Видимость +VISIT_THE_PLUGIN_DIRECTORY: '' +VISIT_THE_THEME_DIRECTORY: '' +VISUAL: визуально +VISUAL_EDITOR: 'Визуальный редактор' +WEBSITE_OWNER: 'Владелец сайта' +WEBSITE_RESTRICTION: 'Ограничения доступа к сайту' +WEBSITE_TITLE: 'Название сайта' +WELCOME_BACK: 'С возвращением' +WELCOME_TO_TYPEMILL: 'Добро пожаловать в TypeMill' +WE_CANNOT_CREATE_A_FOLDER_ONLY_FILES: 'Не можем создавать папки, только файлы.' +WE_COULD_NOT_CREATE_A_SANITIZED_VERSION_OF_THE_SVG_IT_PROBABLY_HAS_INVALID_CONTENT: 'Не удалось очистить SVG-файл, вероятно, он содержит недопустимое содержимое.' +WE_COULD_NOT_CREATE_THE_FILE_PLEASE_REFRESH_THE_PAGE_AND_CHECK_IF_ALL_FOLDERS_AND_FILES_ARE_WRITABLE: 'Не удалось создать файл. Пожалуйста, перезагрузите страницу и убедитесь, что папки и файлы доступны для записи.' +WE_COULD_NOT_CREATE_THE_FOLDER_PLEASE_REFRESH_THE_PAGE_AND_CHECK_IF_ALL_FOLDERS_AND_FILES_ARE_WRITABLE: 'Не удалось создать папку. Пожалуйста, перезагрузите страницу и убедитесь, что папки и файлы доступны для записи.' +WE_COULD_NOT_CREATE_THE_USER_PLEASE_CHECK_IF_THE_SETTINGS_FOLDE_IS_WRITABLE: '' +WE_COULD_NOT_DELETE_A_CUSTOM_IMAGE_GRAYSCALE_OR_RESIZED: 'Не удалось удалить пользовательское изображение (оттенки серого или пользовательский размер).' +WE_COULD_NOT_DELETE_THE_LIVE_IMAGE: 'Не удалось удалить живое изображение' +WE_COULD_NOT_DELETE_THE_ORIGINAL_IMAGE_IN: 'Не удалось удалить исходное изображение в' +WE_COULD_NOT_DELETE_THE_THUMB_IMAGE: 'Не удалось удалить миниатюру изображения (миниатюру для предварительного просмотра).' +WE_COULD_NOT_DELETE_THE_USER: 'Не удалось удалить пользователя' +WE_COULD_NOT_FIND_A_FOLDERPATH_FOR: 'Не удалось найти путь к папке для' +WE_COULD_NOT_FIND_OR_READ_THE_PUBLIC_KEY_PEM_IN_THE_SETTINGS_FOLDER: '' +WE_COULD_NOT_FIND_THE_USER: 'Не удалось найти пользователя' +WE_COULD_NOT_FIND_THIS_PAGE_PLEASE_REFRESH_AND_TRY_AGAIN: 'Страница не найдена. Пожалуйста, попробуйте ещё раз и перезагрузите страницу.' +WE_COULD_NOT_LOAD_THE_SVG_FILE_IT_IS_PROBABLY_CORRUPTED: 'Не удалось загрузить SVG-файл, вероятно, он повреждён.' +WE_COULD_NOT_SEND_THE_EMAIL_WITH_THE_PASSWORD_INSTRUCTIONS_TO_YOUR_ADDRESS_REASON: '' +WE_COULD_NOT_SEND_THE_EMAIL_WITH_THE_VERIFICATION_CODE_TO_YOUR_ADDRESS_REASON: '' +WE_COULD_NOT_SEND_THE_TESTMAIL_TO_YOUR_E_MAIL_ADDRESS_REASON: '' +WE_COULD_NOT_STORE_FILE_TO_TEMPORARY_FOLDER: 'Не удалось сохранить файл во временную папку.' +WE_COULD_NOT_STORE_THE_CONTENT: 'Не удалось сохранить содержимое: ' +WE_COULD_NOT_STORE_THE_LIVE_IMAGE_TO_THE_LIVE_FOLDER: 'Не удалось сохранить живое изображение в папку с живыми изображениями.' +WE_COULD_NOT_STORE_THE_NEW_USER: 'Не удалось сохранить нового пользователя.' +WE_COULD_NOT_STORE_THE_ORIGINAL_IMAGE: 'Не удалось сохранить исходное изображение.' +WE_COULD_NOT_STORE_THE_THUMB_TO_THE_THUMB_FOLDER: 'Не удалось сохранить миниатюру изображения (для предварительного просмотра) в папке с миниатюрами изображений.' +WE_DID_NOT_FIND_A_FILE_WITH_THAT_NAME: 'Не найдено ни одного файла с таким именем.' +WE_DID_NOT_FIND_THE_A_USER_OR_USERMAIL: '' +WE_DID_NOT_FIND_THE_FILE_IN_THE_TMP_FOLDER_OR_COULD_NOT_READ_IT: 'Не удалось найти или прочитать файл во временной папке.' +WE_DID_NOT_FIND_THE_IMAGE_IN_THE_TMP_FOLDER_OR_COULD_NOT_READ_IT: 'Не удалось найти или открыть изображение во временной папке.' +WE_FOUND_THE_FILE_BUT_COULD_NOT_DELETE: 'Файл найден, но удалить его не удалось.' +WE_FOUND_THE_FOLDER_BUT_COULD_NOT_DELETE: 'Папка найдена, но удалить её не удалось.' +WE_FOUND_THE_FOLDER_BUT_COULD_NOT_DELETE_IT: 'Папка найдена, но удалить её не удалось.' +WE_HOPE_YOU_LIKE_TYPEMILL_BECAUSE_WE_CODED_IT_JUST_FOR_YOU: ' мы надеемся, что вам понравится Typemill, потому что он разработан специально для вас и с заботой переведён на русский язык.' +WIDTH_HEIGHT: Ширина/Высота +WRAP_RESTRICTION_NOTICE: 'Форматирование для уведомления об ограничении' +WRAP_THE_RESTRICTION_NOTICE_ABOVE_INTO_A_NOTICE_4_ELEMENT_WHICH_CAN_BE_DESIGNED_AS_SPECIAL_BOX: 'Обернуть указанное выше примечание об ограничении доступа в элемент notice-4 (который может быть оформлен в виде специального поля или рамки — конечный внешний вид зависит от темы).' +WRITING: Редактор +WRONG_PASSWORD_OR_USERNAME_PLEASE_TRY_AGAIN: 'Неверный пароль или имя пользователя. Пожалуйста, попробуйте ещё раз.' +YEAR: Год +YOUR_ARE_AN: вы +YOUR_BROWSER_CAN_REMEMBER_ALL_OF_YOUR_PASSWORDS: 'Ваш браузер может запоминать все ваши пароли.' +YOUR_EMAIL: 'Ваш email' +YOUR_LICENSE_KEY: 'Ваш ключ лицензии' +YOUR_REGISTRATION_IS_NOT_CONFIRMED_YET_PLEASE_CHECK_YOUR_E_MAILS_AND_USE_THE_CONFIRMATION_LINK: 'Ваша регистрация ещё не завершена. Пожалуйста, проверьте свою электронную почту и воспользуйтесь ссылкой для подтверждения email для завершения регистрации.' +YOUR_TYPEMILL_VERIFICATION_CODE: '' +YOU_ARE_NOT_ALLOWED_TO_DELETE_ANOTHER_USER: 'Вы не можете удалять других пользователей.' +YOU_ARE_NOT_ALLOWED_TO_UPDATE_ANOTHER_USER: 'Вы не можете обновлять других пользователей.' +YOU_CANNOT_CREATE_AN_ITEM_WITH_THE_SLUG_TM_IN_THE_ROOT_FOLDER_BECAUSE_THIS_IS_THE_SYSTEM_PATH: '' +YOU_CAN_OVERWRITE_THE_THEME_CSS_WITH_YOUR_OWN_CSS_HERE: 'Здесь вы можете переопределить CSS-стили темы с помощью собственных правил CSS.' +YOU_CAN_USE_THE_ASTERISK_WILDCARD_TO_SEARCH_FOR_NAME@_OR_@DOMAIN_COM: 'Вы можете использовать звёздочку (*) в качестве подстановочного знака для поиска по имени@* или домену *@domain.com' +YOU_DO_NOT_HAVE_ENOUGH_RIGHTS: 'У вас недостаточно прав.' +YOU_TRIED_TO_OPEN_THE_PASSWORD_RESET_PAGE_BUT_THE_LINK_WAS_INVALID: 'Вы хотели открыть страницу сброса пароля, но ссылка оказалась недействительной.' +YOU_TRIED_TO_SET_A_NEW_PASSWORD_BUT_USERNAME_OR_TOKEN_WAS_INVALID: 'Вы хотели установить новый пароль, но имя пользователя или токен были недействительны.' +©: © +ACCESS_CONTROL: 'Настройки доступа' +ACTIVATE_CACHE_FOR_TWIG_TEMPLATES: 'Активировать кэш для шаблонов Twig' +ACTIVATE_THE_PASSWORD_RECOVERY: 'Включить функцию восстановления пароля' +ADD_CONTENT_BLOCK: '➕ добавить блок' +ADD_FILE: '➕ добавить запись' +ADD_FOLDER: '➕ добавить раздел' +ADD_FOLDER_TO_BASE_LEVEL: '➕ добавить раздел на корневой уровень' +ADD_ITEM: '➕ добавить элемент' +ADD_NEW_FEATURES_TO_YOUR_WEBSITE_WITH_PLUGINS_AND_CONFIGURE_THEM: 'Добавляйте новые функции на свой сайт с помощью плагинов и настраивайте их.' +ADMINISTRATOR: Администратор +ALL: 'Всех посетителей' +ALL_USERS: 'Все пользователи' +BACK_TO_STARTPAGE: 'Назад на главную страницу' +BOOKMARK: Закладка +BOTTOM: Внизу +BROWSE: ОБЗОР +BY: от +BY_THE: от +CELL: ячейка +CHOOSE_A_THEME_FOR_YOUR_WEBSITE_AND_CONFIGURE_THE_THEME_DETAILS: 'Выберите тему для своего сайта и настройте детали темы.' +CHOOSE_FILE: 'Выберите файл' +CLEAR_CACHE: 'Очистить кэш' +CODED_WITH: 'Разработано с' +COG: cog +COMMUNITY: сообщество +CONFIGURE_YOUR_WEBSITE: 'Настройте свой сайт' +CONTENT: Контент +CREATED_AT_READONLY: 'Дата создания (нельзя изменить)' +CREATE_NEW_USER: 'Создание нового пользователя' +CROSS: Зачёркнутый +DELETE_ALL_CACHE_FILES: 'Удалить все файлы кэша' +DELETE_CLOSE: Удалить/закрыть +DELETE_CONTENT_BLOCK: '➖ Удалить блок' +DESCRIPTION: описание +DISABLE_HEADERS: 'Отключение заголовков' +DISABLE_TYPEMILL_HEADERS_AND_SEND_YOUR_OWN: 'Отключить заголовки Typemill и отправлять свои заголовки' +DOCS: документация +DO_YOU_REALLY_WANT_TO_DELETE_THE_USER: 'Вы действительно хотите удалить пользователя?' +EDITOR: Редактор +EDIT_USER: 'Редактирование профиля' +EYE_BLOCKED: 'Закрытый глаз' +E_G: например +FILES: Файлы +FORMAT: Формат +FOR_ACCESS_THE_USER_MUST_HAVE_THIS_MINIMUM_ROLE: 'Для доступа к этой странице пользователь должен иметь как минимум выбранную роль' +FRONTEND: 'Открыть сайт' +GENERAL_PRESENTATION: 'Графическая атрибутика' +GET_HELP: Помощь +GIVE_YOUR_NEW_WEBSITE_A_NAME_ADD_THE_AUTHOR_AND_CHOOSE_A_COPYRIGHT: 'Дайте вашему новому сайту имя, добавьте автора и выберите тип авторского права (лицензии) для содержимого сайта.' +GOOGLE_SITEMAP: 'Google Sitemap (карта сайта)' +HEAD: Заголовок +HOME: главная +HURRA: Урашечки +IF_YOU_HAVE_ANY_QUESTIONS_PLEASE_READ_THE: 'Если у вас есть вопросы, пожалуйста прочтите' +IMAGES: Изображения +LANGUAGE_ADMIN: 'Язык (для панели администратора)' +LANGUAGE_ATTR: 'Атрибут языка (для сайта)' +LICENSE_DATA_ARE_INVALID: 'Данные о лицензии недействительны' +LICENSE_DATA_INCOMPLETE: 'Данные о лицензии неполные' +LICENSE_DATA_INVALID: '' +LICENSE_DATA_NOT_COMPLETE: 'Данные о лицензии не заполнены' +LICENSE_FOR_THIS_AND_YOUR_WEBSITE_MUST_RUN_UNDER_THE_DOMAIN_OF_YOUR_LICENSE: '-Лицензия на это и ваш сайт в целом должны работать по правилам вашей лицензии.' +LIMIT_THE_ACCESS_FOR_THE_WHOLE_WEBSITE_OR_FOR_EACH_PAGE_INDIVIDUALLY_IF_YOU_ACTIVATE_THE_WEBSITE_RESTRICTION_OR_THE_PAGE_RESTRICTIONS_THEN_SESSIONS_WILL_BE_USED_IN_FRONTEND: 'Ограничьте доступ для всего сайта или для каждой страницы в отдельности. Если вы активируете ограничение для сайта или ограничения для страниц, то сеансы пользователей (сессии) будут храниться на стороне пользователей (во фронтенд части).' +LINK_TO_VIDEO: 'Ссылка на видео' +LOGOUT: Выйти +MEMBER: 'Зарегистрированный пользователь' +META: Метаданные +MISSING_REQUIREMENTS: 'Отсутствующие требования' +MODIFIED: Изменено +MOVE_VERTICAL: 'двигаться по вертикали' +NEXT_STEP: 'Следующий шаг' +NONE: Нет +NOT_EDITABLE: 'нельзя отредактировать' +NO_DESCRIPTION: 'Нет описания' +NO_LICENSE_FOUND: 'Лицензия не найдена' +NO_PREVIEW: 'Нет предпросмотра' +NO_SETTINGS: 'Нет настроек' +ONLINE: онлайн +ONLY_THE_FOLLOWING_SPECIAL_CHARACTERS_ARE_ALLOWED: 'Разрешены только следующие специальные символы — ' +OR_OPEN_A_NEW_ISSUE_ON: 'или откройте новое обсуждение проблемы на' +PAGE_RESTRICTIONS_ACTIVATE: 'Настройки страниц — Активация ограничения доступа' +PAGE_RESTRICTIONS_CUT_RESTRICTED_CONTENT: 'Настройки страниц — Скрытие ограниченного контента' +PAGE_RESTRICTIONS_NOTICE: 'Настройки страниц — Примечание об ограничении доступа' +PAGE_RESTRICTIONS_WRAP_NOTICE_INTO_A_BOX: 'Настройки страниц — Оборачивание примечания об ограничении доступа в рамку' +PAPERCLIP: Скрепочка +PLEASE_CHECK_THE_INBOX_OF_YOUR_EMAIL_ACCOUNT_FOR_MORE_INSTRUCTIONS_DO_NOT_FORGET_TO_CHECK_YOUR_SPAM_FOLDER_IF_YOUR_INBOX_IS_EMPTY: 'Проверьте свою электронную почту для получения дополнительной информации. При необходимости также проверьте папку со спамом.' +PLEASE_CORRECT_ERRORS_IN_FORM: 'Пожалуйста, исправьте ошибки в форме.' +PLEASE_CORRECT_THE_ERRORS: 'Пожалуйста, исправьте ошибки.' +PLEASE_CORRECT_THE_ERRORS_ABOVE: 'Пожалуйста, исправьте ошибки выше' +PLEASE_CORRECT_THE_ERRORS_IN_FORM: 'Пожалуйста, исправьте ошибки в форме.' +PLEASE_CORRECT_THE_ERRORS_IN_THE_FORM: 'Пожалуйста, исправьте ошибки в этой форме.' +PLEASE_USE_ISO_639_1_CODES_LIKE_EN: 'Используйте коды используемые в ISO 639-1, такие как «en»' +PLUGIN_STORE: 'Магазин Плагинов' +POWER_OFF: выключить +PUBLISHED: Опубликовано +RAW_CONTENT_EDITOR: 'Редактор исходного кода' +RAW_MARKDOWN_EDITOR: 'Редактор кода Markdown' +RAW_MODE: 'Режим исходного кода' +RAW_USERDATA_READONLY_FOR_ADMINS: 'Пользовательские данные (видны только для администраторов и только для просмотра)' +READONLY: 'Нельзя изменить' +REGISTERED_USERS_ONLY: 'Только зарегистрированные пользователи' +REMEMBER_TO_BOOKMARK_THIS_PAGE: 'Не забудьте добавить в закладки эту страницу' +RESTRICTIONS: Ограничения +SAVED_SUCCESSFULLY: 'Успешно сохранено' +SAVE_ALL_SETTINGS: 'Сохранить все настройки' +SAVE_THEME: 'Сохранить настройки Темы' +SENDER_EMAIL: 'Email отправителя' +SETTINGS: Настройки +SETTINGS_ARE_STORED: 'Настройки сохранены' +SETUP_WELCOME: 'Первоначальные настройки' +SHOW_ANCHORS_NEXT_TO_HEADLINES: 'Показывать якорные ссылки рядом с заголовками' +STANDARD_HEIGHT_FOR_IMAGES: 'Стандартная высота для изображений' +STANDARD_WIDTH_FOR_IMAGES: 'Стандартная ширина для изображений' +START: Начать +TAKEN_FROM_YOUR_USER_ACCOUNT_IF_SET: 'Взято из вашего профиля, если в профиле заполнено' +TERM: термин +THEME_STORE: 'Магазин Тем' +THE_FOLLOWING_OPTIONS_ARE_ONLY_FOR_DEVELOPERS: 'Следующие параметры предназначены только для разработчиков и опытных администраторов. Изменяйте параметры только в том случае, если вы действительно их понимаете.' +THE_FORMAT_BUTTONS: 'Кнопки для форматирования' +THE_LICENSE_SERVER_RESPONDED_WITH: 'Сервер лицензий ответил: ' +THE_SUBSCRIPTION_PERIOD_IS_NOT_PAID_YET: '' +THIS_APPLIES_ONLY_FOR_FUTURE_IMAGES_IN_THE_CONTENT_AREA: 'Это относится только к будущим изображениям в области содержимого.' +TOP: Наверху +TRENDSCHAU_DIGITAL: 'Trendschau Digital' +TRUSTED_IPS_FOR_PROXY_COMMA_SEPARATED: 'Список надёжных IP-адресов для прокси (список, разделённый запятыми)' +TRY_TO_CONVERT_UPLOADED_IMAGES_INTO_THE_WEBP_FORMAT: 'Пытаться преобразовывать загруженные изображения в формат WebP' +TYPEMILL_DESCRIPTION: 'Стандартная тема для Typemill. Отзывчивая, минималистичная и без каких-либо сторонних зависимостей. В теме используются системные шрифты: Calibri и Helvetica. А вот JavaScript не используется.' +UNKNOWN: Неизвестен +UPDATE_USER: 'Обновить данные профиля' +UPS_WRONG_PASSWORD_OR_USERNAME_PLEASE_TRY_AGAIN: 'Упс... неправильный пароль или имя пользователя, попробуйте войти ещё раз.' +USE_2_TO_20_CHARACTERS: 'Используйте от 2 до 20 символов.' +USE_2_TO_40_CHARACTERS: 'Используйте от 2 до 40 символов.' +USE_A_VALID_LANGUAGE_ATTRIBUTE: 'Используйте допустимый языковой атрибут' +USE_A_VALID_YEAR: 'Используйте правильный формат для указания года' +USE_MARKDOWN: 'поддерживается Markdown' +USE_X_FORWARDED_HEADERS: 'Использовать X-Forwarded заголовки' +VALIDATION_FAILED: '' +VIEW_SITE: 'На сайт' +VISIT_THE_AUTHOR_PANEL_AND_SETUP_YOUR_NEW_WEBSITE_YOU_CAN_CONFIGURE_THE_SYSTEM_CHOOSE_THEMES_AND_ADD_PLUGINS: 'Посетите панель администратора и настройте свой новый сайт. Вы можете настроить систему, выбрать темы и добавить плагины.' +VISUAL_CONTENT_EDITOR: 'Визуальный редактор контента' +VISUAL_MARKDOWN_EDITOR: 'Визуальный редактор Markdown' +VISUAL_MODE: 'визуальный режим' +WAIT: подождите +WEB: Сайт +WEBSITE_VISIBLE_FOR: 'Сайт виден для' +WE_COULD_NOT_SEND_THE_EMAIL_WITH_THE_PASSWORD_INSTRUCTIONS_TO_YOUR_ADDRESS_PLEASE_CONTACT_THE_WEBSITE_OWNER_AND_ASK_FOR_HELP: 'Не удалось отправить письмо с инструкциями касательно пароля на ваш адрес электронной почты. Пожалуйста, свяжитесь с администратором сайта.' +YOUR_ACCOUNT_HAS_BEEN_CREATED_AND_YOU_ARE_LOGGED_IN_NOW: 'Ваша учётная запись создана, и вы вошли в систему.' diff --git a/system/typemill/routes/api.php b/system/typemill/routes/api.php new file mode 100644 index 0000000..0c01b5a --- /dev/null +++ b/system/typemill/routes/api.php @@ -0,0 +1,139 @@ +group('/api/v1', function (RouteCollectorProxy $group) use ($acl) { + + # SYSTEM + $group->get('/settings', ControllerApiSystemSettings::class . ':getSettings')->setName('api.settings.get')->add(new ApiAuthorization($acl, 'system', 'read')); # manager + $group->post('/settings', ControllerApiSystemSettings::class . ':updateSettings')->setName('api.settings.set')->add(new ApiAuthorization($acl, 'system', 'update')); # manager + $group->post('/license', ControllerApiSystemLicense::class . ':createLicense')->setName('api.license.create')->add(new ApiAuthorization($acl, 'user', 'update')); # admin + $group->post('/licensetestcall', ControllerApiSystemLicense::class . ':testLicenseServerCall')->setName('api.license.testcall')->add(new ApiAuthorization($acl, 'user', 'update')); # admin + $group->post('/themecss', ControllerApiSystemThemes::class . ':updateThemeCss')->setName('api.themecss.set')->add(new ApiAuthorization($acl, 'system', 'update')); # manager + $group->post('/theme', ControllerApiSystemThemes::class . ':updateTheme')->setName('api.theme.set')->add(new ApiAuthorization($acl, 'system', 'update')); # manager + $group->post('/treadymade', ControllerApiSystemThemes::class . ':updateReadymade')->setName('api.treadymade.set')->add(new ApiAuthorization($acl, 'system', 'update')); # manager + $group->delete('/treadymade', ControllerApiSystemThemes::class . ':deleteReadymade')->setName('api.treadymade.delete')->add(new ApiAuthorization($acl, 'system', 'update')); # manager + $group->post('/plugin', ControllerApiSystemPlugins::class . ':updatePlugin')->setName('api.plugin.set')->add(new ApiAuthorization($acl, 'system', 'update')); # manager + $group->post('/extensions', ControllerApiSystemExtensions::class . ':activateExtension')->setName('api.extension.activate')->add(new ApiAuthorization($acl, 'system', 'update')); # manager + $group->post('/versioncheck', ControllerApiSystemVersions::class . ':checkVersions')->setName('api.versioncheck')->add(new ApiAuthorization($acl, 'system', 'update')); # manager + $group->post('/testmail', ControllerApiTestmail::class . ':send')->setName('api.testmail')->add(new ApiAuthorization($acl, 'user', 'update')); # admin + $group->get('/users/getbynames', ControllerApiSystemUsers::class . ':getUsersByNames')->setName('api.usersbynames')->add(new ApiAuthorization($acl, 'user', 'update')); # admin + $group->get('/users/getbyemail', ControllerApiSystemUsers::class . ':getUsersByEmail')->setName('api.usersbyemail')->add(new ApiAuthorization($acl, 'user', 'update')); # admin + $group->get('/users/getbyrole', ControllerApiSystemUsers::class . ':getUsersByRole')->setName('api.usersbyrole')->add(new ApiAuthorization($acl, 'user', 'update')); # admin + $group->get('/userform', ControllerApiSystemUsers::class . ':getNewUserForm')->setName('api.user.form')->add(new ApiAuthorization($acl, 'user', 'update')); # admin + $group->post('/user', ControllerApiSystemUsers::class . ':createUser')->setName('api.user.create')->add(new ApiAuthorization($acl, 'user', 'update')); # admin + $group->put('/user', ControllerApiSystemUsers::class . ':updateUser')->setName('api.user.update')->add(new ApiAuthorization($acl, 'account', 'update')); # member + $group->delete('/user', ControllerApiSystemUsers::class . ':deleteUser')->setName('api.user.delete')->add(new ApiAuthorization($acl, 'account', 'delete')); # member + + # IMAGES + $group->get('/pagemedia', ControllerApiImage::class . ':getPagemedia')->setName('api.image.pagemedia')->add(new ApiAuthorization($acl, 'mycontent', 'read')); # author + $group->get('/images', ControllerApiImage::class . ':getImages')->setName('api.image.images')->add(new ApiAuthorization($acl, 'mycontent', 'read')); # author + $group->post('/image', ControllerApiImage::class . ':saveImage')->setName('api.image.create')->add(new ApiAuthorization($acl, 'mycontent', 'create')); # author + $group->put('/image', ControllerApiImage::class . ':publishImage')->setName('api.image.publish')->add(new ApiAuthorization($acl, 'mycontent', 'create')); # author + $group->get('/image', ControllerApiImage::class . ':getImage')->setName('api.image.get')->add(new ApiAuthorization($acl, 'mycontent', 'read')); # author + $group->delete('/image', ControllerApiImage::class . ':deleteImage')->setName('api.image.delete')->add(new ApiAuthorization($acl, 'mycontent', 'delete')); # editor + + # FILES + $group->get('/filerestrictions', ControllerApiFile::class . ':getFileRestrictions')->setName('api.file.getrestrictions')->add(new ApiAuthorization($acl, 'mycontent', 'create')); # author + $group->post('/filerestrictions', ControllerApiFile::class . ':updateFileRestrictions')->setName('api.file.updaterestrictions')->add(new ApiAuthorization($acl, 'mycontent', 'create')); # author + $group->post('/file', ControllerApiFile::class . ':uploadFile')->setName('api.file.upload')->add(new ApiAuthorization($acl, 'mycontent', 'create')); # author + $group->put('/file', ControllerApiFile::class . ':publishFile')->setName('api.file.publish')->add(new ApiAuthorization($acl, 'mycontent', 'update')); # author + $group->get('/files', ControllerApiFile::class . ':getFiles')->setName('api.files.get')->add(new ApiAuthorization($acl, 'mycontent', 'read')); # author + $group->get('/file', ControllerApiFile::class . ':getFile')->setName('api.file.get')->add(new ApiAuthorization($acl, 'mycontent', 'read')); # author + $group->delete('/file', ControllerApiFile::class . ':deleteFile')->setName('api.file.delete')->add(new ApiAuthorization($acl, 'mycontent', 'read')); # author + + # ARTICLE + $group->post('/article/sort', ControllerApiAuthorArticle::class . ':sortArticle')->setName('api.article.sort')->add(new ApiAuthorization($acl, 'content', 'create')); # author + $group->post('/article/rename', ControllerApiAuthorArticle::class . ':renameArticle')->setName('api.article.rename')->add(new ApiAuthorization($acl, 'content', 'publish')); + $group->post('/article/publish', ControllerApiAuthorArticle::class . ':publishArticle')->setName('api.article.publish')->add(new ApiAuthorization($acl, 'content', 'publish')); + $group->delete('/article/unpublish', ControllerApiAuthorArticle::class . ':unpublishArticle')->setName('api.article.unpublish')->add(new ApiAuthorization($acl, 'content', 'unpublish')); + $group->delete('/article/discard', ControllerApiAuthorArticle::class . ':discardArticleChanges')->setName('api.article.discard')->add(new ApiAuthorization($acl, 'content', 'update')); + $group->delete('/article', ControllerApiAuthorArticle::class . ':deleteArticle')->setName('api.article.delete')->add(new ApiAuthorization($acl, 'content', 'delete')); + $group->post('/article', ControllerApiAuthorArticle::class . ':createArticle')->setName('api.article.create')->add(new ApiAuthorization($acl, 'content', 'create')); # author + $group->put('/draft', ControllerApiAuthorArticle::class . ':updateDraft')->setName('api.draft.update')->add(new ApiAuthorization($acl, 'content', 'create')); # author + $group->post('/draft/publish', ControllerApiAuthorArticle::class . ':publishDraft')->setName('api.draft.publish')->add(new ApiAuthorization($acl, 'content', 'create')); # author + $group->post('/post', ControllerApiAuthorArticle::class . ':createPost')->setName('api.post.create')->add(new ApiAuthorization($acl, 'content', 'create')); + + # BLOCKS + $group->post('/block', ControllerApiAuthorBlock::class . ':addBlock')->setName('api.block.add')->add(new ApiAuthorization($acl, 'mycontent', 'update')); + $group->put('/block/move', ControllerApiAuthorBlock::class . ':moveBlock')->setName('api.block.move')->add(new ApiAuthorization($acl, 'mycontent', 'read')); + $group->put('/block', ControllerApiAuthorBlock::class . ':updateBlock')->setName('api.block.update')->add(new ApiAuthorization($acl, 'mycontent', 'update')); + $group->delete('/block', ControllerApiAuthorBlock::class . ':deleteBlock')->setName('api.block.delete')->add(new ApiAuthorization($acl, 'mycontent', 'update')); + $group->post('/video', ControllerApiImage::class . ':saveVideoImage')->setName('api.video.save')->add(new ApiAuthorization($acl, 'mycontent', 'read')); + + # SHORTCODE + $group->get('/shortcodedata', ControllerApiAuthorShortcode::class . ':getShortcodeData')->setName('api.shortcodedata.get')->add(new ApiAuthorization($acl, 'mycontent', 'read')); + + # META + $group->get('/meta', ControllerApiAuthorMeta::class . ':getMeta')->setName('api.meta.get')->add(new ApiAuthorization($acl, 'mycontent', 'read')); + $group->post('/meta', ControllerApiAuthorMeta::class . ':updateMeta')->setName('api.metadata.update')->add(new ApiAuthorization($acl, 'mycontent', 'update')); + + # KIXOTE + $group->get('/kixotesettings', ControllerApiKixote::class . ':getKixoteSettings')->setName('api.kixotesettings.get')->add(new ApiAuthorization($acl, 'mycontent', 'update')); # author + $group->put('/kixotesettings', ControllerApiKixote::class . ':updateKixoteSettings')->setName('api.kixotesettings.put')->add(new ApiAuthorization($acl, 'mycontent', 'update')); # author + $group->get('/securitylog', ControllerApiGlobals::class . ':showSecurityLog')->setName('api.securitylog.show')->add(new ApiAuthorization($acl, 'system', 'update')); # manager + $group->delete('/securitylog', ControllerApiGlobals::class . ':deleteSecurityLog')->setName('api.securitylog.delete')->add(new ApiAuthorization($acl, 'system', 'update')); # manager + $group->delete('/cache', ControllerApiGlobals::class . ':deleteCache')->setName('api.cache.delete')->add(new ApiAuthorization($acl, 'system', 'update')); # manager + $group->delete('/clearnavigation', ControllerApiGlobals::class . ':clearNavigation')->setName('api.navigation.clear')->add(new ApiAuthorization($acl, 'system', 'update')); # manager + + # KIXOTE Remote Services + $group->get('/tokenstats', ControllerApiKixote::class . ':getTokenStats')->setName('api.kixote.tokenstats')->add(new ApiAuthorization($acl, 'mycontent', 'update')); # author + $group->post('/agreetoaiservice', ControllerApiKixote::class . ':agreeToAiService')->setName('api.kixote.serviceagreement')->add(new ApiAuthorization($acl, 'mycontent', 'update')); # author + $group->post('/chatgpt', ControllerApiKixote::class . ':promptChatGPT')->setName('api.kixote.chatgpt')->add(new ApiAuthorization($acl, 'mycontent', 'update')); # author + + # API USED ONLY EXTERNALLY + $group->get('/systemnavi', ControllerApiGlobals::class . ':getSystemnavi')->setName('api.systemnavi.get')->add(new ApiAuthorization($acl, 'account', 'read')); # member + $group->get('/mainnavi', ControllerApiGlobals::class . ':getMainnavi')->setName('api.mainnavi.get')->add(new ApiAuthorization($acl, 'account', 'read')); # member + $group->get('/navigation', ControllerApiGlobals::class . ':getNavigation')->setName('api.navigation.get')->add(new ApiAuthorization($acl, 'content', 'read')); # author + $group->get('/article/items', ControllerApiGlobals::class . ':getItemsForSlug')->setName('api.articleitems.get')->add(new ApiAuthorization($acl, 'content', 'read')); # author + $group->get('/article/item', ControllerApiGlobals::class . ':getItemForUrl')->setName('api.articleitem.get')->add(new ApiAuthorization($acl, 'content', 'read')); # author + $group->get('/article/content', ControllerApiGlobals::class . ':getArticleContent')->setName('api.articlecontent.get')->add(new ApiAuthorization($acl, 'content', 'read')); # author + $group->get('/article/meta', ControllerApiGlobals::class . ':getArticleMeta')->setName('api.articlemeta.get')->add(new ApiAuthorization($acl, 'content', 'read')); # author + +})->add(new ApiAuthentication($settings)); + + +# api-routes from plugins +if(isset($routes['api']) && !empty($routes['api'])) +{ + foreach($routes['api'] as $pluginRoute) + { + $method = $pluginRoute['httpMethod'] ?? false; + $route = $pluginRoute['route'] ?? false; + $class = $pluginRoute['class'] ?? false; + $name = $pluginRoute['name'] ?? false; + $resource = $pluginRoute['resource'] ?? false; + $privilege = $pluginRoute['privilege'] ?? false; + + if($resources && $privilege) + { + # protected api requires authentication and authorization + $app->{$method}($route, $class)->setName($name)->add(new ApiAuthorization($acl, $resource, $privilege))->add(new ApiAuthentication($settings)); + } + else + { + # public api routes + $app->{$method}($route, $class)->setName($name); + } + } +} \ No newline at end of file diff --git a/system/typemill/routes/setup.php b/system/typemill/routes/setup.php new file mode 100644 index 0000000..edf4c86 --- /dev/null +++ b/system/typemill/routes/setup.php @@ -0,0 +1,10 @@ +redirect('/tm', $routeParser->urlFor('auth.show'), 302); + +$app->get('/tm/setup', ControllerWebSetup::class . ':show')->setName('setup.show'); +$app->post('/tm/setup', ControllerWebSetup::class . ':create')->setName('setup.create'); +$app->redirect('/[{params:.*}]', $routeParser->urlFor('setup.show'), 302); \ No newline at end of file diff --git a/system/typemill/routes/web.php b/system/typemill/routes/web.php new file mode 100644 index 0000000..aa3a69e --- /dev/null +++ b/system/typemill/routes/web.php @@ -0,0 +1,101 @@ +group('/tm', function (RouteCollectorProxy $group) use ($settings) { + + $group->get('/login', ControllerWebAuth::class . ':show')->setName('auth.show'); + $group->post('/login', ControllerWebAuth::class . ':login')->setName('auth.login'); + + if(isset($settings['loginlink']) && $settings['loginlink']) + { + $group->get('/loginlink', ControllerWebAuth::class . ':loginlink')->setName('auth.link'); + } + + if(isset($settings['authcode']) && $settings['authcode']) + { + $group->post('/authcode', ControllerWebAuth::class . ':loginWithAuthcode')->setName('auth.authcode'); + } + + if(isset($settings['recoverpw']) && $settings['recoverpw']) + { + $group->get('/recover', ControllerWebRecover::class . ':showRecoverForm')->setName('auth.recoverform'); + $group->post('/recover', ControllerWebRecover::class . ':recoverPassword')->setName('auth.recover'); + $group->get('/reset', ControllerWebRecover::class . ':showPasswordResetForm')->setName('auth.resetform'); + $group->post('/reset', ControllerWebRecover::class . ':resetPassword')->setName('auth.reset'); + } + +})->add(new CspHeadersMiddleware($settings, $cspFromPlugins, $cspFromTheme))->add(new WebRedirectIfAuthenticated($routeParser, $settings)); + +# AUTHOR AREA (requires authentication) +$app->group('/tm', function (RouteCollectorProxy $group) use ($routeParser,$acl) { + + # Admin Area + $group->get('/logout', ControllerWebAuth::class . ':logout')->setName('auth.logout'); + $group->get('/system', ControllerWebSystem::class . ':showSettings')->setName('settings.show')->add(new WebAuthorization($routeParser, $acl, 'system', 'read')); # manager; + $group->get('/license', ControllerWebSystem::class . ':showLicense')->setName('license.show')->add(new WebAuthorization($routeParser, $acl, 'user', 'read')); # admin; + $group->get('/themes', ControllerWebSystem::class . ':showThemes')->setName('themes.show')->add(new WebAuthorization($routeParser, $acl, 'system', 'read')); # manager; + $group->get('/plugins', ControllerWebSystem::class . ':showPlugins')->setName('plugins.show')->add(new WebAuthorization($routeParser, $acl, 'system', 'read')); # manager; + $group->get('/account', ControllerWebSystem::class . ':showAccount')->setName('user.account')->add(new WebAuthorization($routeParser, $acl, 'account', 'read')); # member; + $group->get('/users', ControllerWebSystem::class . ':showUsers')->setName('users.show')->add(new WebAuthorization($routeParser, $acl, 'user', 'read')); # admin; + $group->get('/user/new', ControllerWebSystem::class . ':newUser')->setName('user.new')->add(new WebAuthorization($routeParser, $acl, 'user', 'create')); # admin; + $group->get('/user/{username}', ControllerWebSystem::class . ':showUser')->setName('user.show')->add(new WebAuthorization($routeParser, $acl, 'user', 'read')); # admin; + + # Author Area + $group->get('/content/visual[/{route:.*}]', ControllerWebAuthor::class . ':showBlox')->setName('content.visual')->add(new WebAuthorization($routeParser, $acl, 'mycontent', 'read')); + $group->get('/content/raw[/{route:.*}]', ControllerWebAuthor::class . ':showRaw')->setName('content.raw')->add(new WebAuthorization($routeParser, $acl, 'mycontent', 'read')); + +})->add(new CspHeadersMiddleware($settings, $cspFromPlugins, $cspFromTheme))->add(new WebRedirectIfUnauthenticated($routeParser)); + +$app->redirect('/tm', $routeParser->urlFor('auth.show'), 302); +$app->redirect('/tm/', $routeParser->urlFor('auth.show'), 302); +$app->redirect('/tm/authcode', $routeParser->urlFor('auth.show'), 302); + +# downloads +$app->get('/media/files[/{params:.*}]', ControllerWebDownload::class . ':download')->setName('download.file'); + +# web-routes from plugins +if(isset($routes['web']) && !empty($routes['web'])) +{ + foreach($routes['web'] as $pluginRoute) + { + $method = $pluginRoute['httpMethod'] ?? false; + $route = $pluginRoute['route'] ?? false; + $class = $pluginRoute['class'] ?? false; + $name = $pluginRoute['name'] ?? false; + $resource = $pluginRoute['resource'] ?? false; + $privilege = $pluginRoute['privilege'] ?? false; + + if($resources && $privilege) + { + $app->{$method}($route, $class)->setName($name)->add(new CspHeadersMiddleware($settings, $cspFromPlugins, $cspFromTheme))->add(new WebAuthorization($routeParser, $acl, $resource, $privilege))->add(new WebRedirectIfUnauthenticated($routeParser)); + } + else + { + $app->{$method}($route, $class)->setName($name)->add(new CspHeadersMiddleware($settings, $cspFromPlugins, $cspFromTheme)); + } + } +} + +if(isset($settings['access']) && $settings['access'] != '') +{ + # if access for website is restricted + $app->get('/[{route:.*}]', ControllerWebFrontend::class . ':index')->setName('home')->add(new CspHeadersMiddleware($settings, $cspFromPlugins, $cspFromTheme))->add(new WebRedirectIfUnauthenticated($routeParser)); +} +else +{ + # if access is not restricted + $app->get('/[{route:.*}]', ControllerWebFrontend::class . ':index')->setName('home')->add(new CspHeadersMiddleware($settings, $cspFromPlugins, $cspFromTheme)); +} diff --git a/system/typemill/settings/defaults.yaml b/system/typemill/settings/defaults.yaml new file mode 100644 index 0000000..6ff0af9 --- /dev/null +++ b/system/typemill/settings/defaults.yaml @@ -0,0 +1,31 @@ +version: '2.14.6' +title: 'Typemill' +author: 'Unknown' +copyright: false +language: 'en' +langattr: 'en' +editor: 'visual' +storage: '\Typemill\Models\Storage' +formats: + - 'markdown' + - 'headline' + - 'ulist' + - 'olist' + - 'table' + - 'quote' + - 'notice' + - 'image' + - 'video' + - 'audio' + - 'file' + - 'toc' + - 'hr' + - 'definition' + - 'code' + - 'shortcode' +images: + live: + width: 820 + thumbs: + width: 250 + height: 150 \ No newline at end of file diff --git a/system/typemill/settings/kixote.yaml b/system/typemill/settings/kixote.yaml new file mode 100644 index 0000000..ed8e9d6 --- /dev/null +++ b/system/typemill/settings/kixote.yaml @@ -0,0 +1,41 @@ +promptlist: + brainstorm: + title: 'brainstorm' + content: "Act as an experienced copywriter and brainstorm 10 compelling article ideas based on the provided topic. The goal is to create content that resonates with the target audience and ranks well on search engines. Identify relevant keywords and trends to align with user interests. Each idea should provide valuable insights, answer common questions, or solve specific problems. Craft engaging headlines that capture attention and match user search intent." + active: true + system: true + outline: + title: 'outline' + content: "You are an experienced copywriter skilled in crafting compelling articles tailored to a specific target audience. Conduct in-depth research on the following article idea. Identify the most relevant information, authoritative sources, and key insights. Analyze search intent to determine what readers are looking for when they search for this topic. Define the article’s target audience, ideal length, tone, and structure.\n\nBased on your research, create a detailed structured outline for the article, including:\n\n* A compelling title and meta description.\n* Main headlines (H2, H3) that logically structure the content.\n* Bullet points under each headline, summarizing the key points to be covered.\n\nAfter the outline add a divider list, the headline `Research` and add your research findings." + active: true + system: true + outline2: + title: 'outline2' + content: "You are an experienced copywriter skilled in crafting compelling, well-structured articles tailored to a specific target audience. Based on the provided article idea, develop a comprehensive content outline that includes:\n\n- Title: A compelling, SEO-optimized headline.\n- User Intent: A one-sentence description of the intent, followed by the category in brackets (Informational, Navigational, Transactional, or Commercial).\n- Keywords: A comma-separated list, with the main keyword first, followed by up to five secondary keywords.\n- Target Audience: One sentence who the article is for and what they expect to gain.\n- Ideal Tone & Style: Concise, authoritative, engaging, personal, etc.\n\nBelow that, provide a detailed article structure:\n- Headings (H2, H3): A logical outline that organizes the content.\n- Bullet points under each heading: Summarizing key points to cover." + active: true + system: true + write: + title: 'write' + content: "Act as an experienced copywriter tasked with creating a comprehensive and engaging article based on the provided outline or draft. If details such as keywords, user intent, target audience, tone, or style are included, ensure the article aligns with them. If no outline is given, structure the article logically based on best practices for the topic.\n\nCraft a compelling article with:\n- **An eye-catching, SEO-optimized title** featuring the primary keyword.\n- **A strong introduction** that immediately hooks the reader—use a compelling question, statistic, expert quote, or relatable scenario. Clearly state what the article will cover.\n- **A well-structured body** with clear subheadings (H2, H3) incorporating relevant keywords. Ensure smooth transitions between sections and provide thorough, engaging explanations.\n- **Bullet points or lists** where appropriate to enhance readability but prioritize a natural flow of ideas.\n- **A unique perspective** by integrating expert insights, statistics, case studies, or real-life examples.\n- **Natural keyword integration** that enhances SEO without disrupting readability.\n\nMaintain a tone and style that align with the article’s goals and audience, ensuring clarity, engagement, and credibility." + active: true + system: true + proofread: + title: 'proofread' + content: "Review the text for grammar, spelling, and correct word usage. Correct any errors and ensure that the appropriate words are used, while maintaining the original tone, style, and phrasing. At the end of the article, provide a list of all corrections made." + active: true + system: true + refine: + title: 'refine' + content: "Refine the text to enhance its wording and style. Ensure that the content is readable, clear, concise, and flows naturally. Feel free to rephrase sentences where necessary, but retain the original tone and unique language usage to avoid making the text sound generic." + active: true + system: true + review: + title: 'review' + content: "Review the article below. Evaluate it based on readability, clarity, SEO best practices, engagement, and search intent. Provide actionable suggestions for improvement, focusing on areas where the text can be refined for better flow, clarity, and alignment with SEO goals." + active: true + system: true + mermaid: + title: 'mermaid' + content: "Return pure mermaid syntax for a [pie, x, y] diagram." + active: true + system: true \ No newline at end of file diff --git a/system/typemill/settings/license.yaml b/system/typemill/settings/license.yaml new file mode 100644 index 0000000..74c548b --- /dev/null +++ b/system/typemill/settings/license.yaml @@ -0,0 +1,22 @@ +fieldsetlicense: + type: fieldset + legend: Activate Your License + fields: + license: + name: license + label: 'Your license key' + type: 'text' + required: true + description: 'Enter your license key that you got after your purchase via email.' + email: + name: email + label: 'Your email' + type: 'text' + required: true + description: 'Enter your e-mail address that you used for your license-purchase.' + domain: + name: domain + label: 'Domain for license' + type: 'text' + required: true + description: 'Enter the full domain like https://wwww.mywebsite.de.' \ No newline at end of file diff --git a/system/typemill/settings/mainnavi.yaml b/system/typemill/settings/mainnavi.yaml new file mode 100644 index 0000000..081cf60 --- /dev/null +++ b/system/typemill/settings/mainnavi.yaml @@ -0,0 +1,30 @@ +'content': + 'title': 'Content' + 'icon': 'icon-pencil' + 'routename': 'content.visual' + 'aclresource': 'content' + 'aclprivilege': 'read' +'system': + 'title': 'System' + 'icon': 'icon-cog' + 'routename': 'settings.show' + 'aclresource': 'system' + 'aclprivilege': 'read' +'account': + 'title': 'Account' + 'icon': 'icon-user1' + 'routename': 'user.account' + 'aclresource': 'account' + 'aclprivilege': 'read' +'frontend': + 'title': 'Frontend' + 'icon': 'icon-external-link' + 'routename': 'home' + 'aclresource': 'account' + 'aclprivilege': 'read' +'logout': + 'title': 'Logout' + 'icon': 'icon-switch' + 'routename': 'auth.logout' + 'aclresource': 'account' + 'aclprivilege': 'read' \ No newline at end of file diff --git a/system/typemill/settings/metatabs.yaml b/system/typemill/settings/metatabs.yaml new file mode 100644 index 0000000..5303f23 --- /dev/null +++ b/system/typemill/settings/metatabs.yaml @@ -0,0 +1,124 @@ +meta: + fields: + navtitle: + type: text + label: Navigation Title + maxlength: 60 + fieldsetcontent: + type: fieldset + legend: Meta-Content + fields: + title: + type: text + label: Meta title + maxlength: 100 + description: + type: textarea + label: Meta description + size: 160 + description: If not filled, the description is extracted from content. + heroimage: + type: image + label: Hero Image + description: Maximum size for an image is 5 MB. Hero images are not supported by all themes. + heroimagealt: + type: text + label: Alternative Text for the hero image + fieldsetauthor: + type: fieldset + legend: Author + fields: + owner: + type: text + label: owner (username) + css: w-half + description: Has edit rights for this article. + author: + type: text + label: author + css: w-half + description: Can be used for author line in frontend. + fieldsetrights: + type: fieldset + legend: Access & Rights + fields: + allowedrole: + type: select + label: Minimum user-role to access this page + css: w-half + dataset: userroles + options: + description: Select the lowest userrole. Higher roles will have access too. + alloweduser: + type: text + label: Only the following users have access + css: w-half + description: Add one or more usernames separated with comma. + fieldsetpubdate: + type: fieldset + legend: Article Date + fields: + manualdate: + type: date + label: Manual date + modified: + type: date + label: Last modified live (readonly) + readonly: readonly + css: w-half + description: Used as fallback when no manual date is set. + created: + type: date + label: Created at (read only) + readonly: readonly + css: w-half + time: + type: text + readonly: readonly + hidden: true + css: hidden + pattern: '[0-9][0-9]-[0-9][0-9]-[0-9][0-9]' + fieldsetreference: + type: fieldset + legend: Reference + fields: + reference: + type: text + label: Reference to page + placeholder: '/path/to/internal/page or https://exgernal-page.org' + description: 'Use full relative path like "/somefolder/somefile" for internal pages or absolute url for external pages.' + maxlength: 200 + referencetype: + type: radio + label: Type of reference + options: + disable: Disable + redirect301: PERMANENT REDIRECT (301) the user to the referenced internal page + redirect302: TEMPORARY REDIRECT (302) the user to the referenced internal page + copy: COPY the content of the referenced internal page + outlink: LINK to an external page + fieldsetvisibility: + type: fieldset + legend: Visibility + fields: + hide: + type: checkbox + label: Hide + checkboxlabel: Hide page from navigation + css: w-half + noindex: + type: checkbox + label: Noindex + checkboxlabel: Add noindex tag and exclude from sitemap + css: w-half + fieldsetfolder: + type: fieldset + legend: Folder + fields: + contains: + type: radio + label: This folder contains + css: medium + options: + pages: PAGES (sort in navigation with drag & drop) + posts: POSTS (sorted by publish date, for news or blogs) \ No newline at end of file diff --git a/system/typemill/settings/permissions.yaml b/system/typemill/settings/permissions.yaml new file mode 100644 index 0000000..22051a8 --- /dev/null +++ b/system/typemill/settings/permissions.yaml @@ -0,0 +1,50 @@ +guest: + name: guest + inherits: NULL + permissions: + account: + - 'none' +member: + name: member + inherits: NULL + permissions: + account: + - 'read' + - 'update' + - 'delete' +contributor: + name: contributor + inherits: member + permissions: + mycontent: + - 'read' + - 'create' + - 'update' + - 'delete' +author: + name: author + inherits: contributor + permissions: + content: + - 'read' + - 'create' +editor: + name: editor + inherits: author + permissions: + mycontent: + - 'publish' + - 'unpublish' + content: + - 'update' + - 'delete' + - 'publish' + - 'unpublish' +manager: + name: manager + inherits: editor + permissions: + system: + - 'read' + - 'update' + - 'delete' \ No newline at end of file diff --git a/system/typemill/settings/resources.yaml b/system/typemill/settings/resources.yaml new file mode 100644 index 0000000..f50c45c --- /dev/null +++ b/system/typemill/settings/resources.yaml @@ -0,0 +1,5 @@ +- 'content' +- 'mycontent' +- 'account' +- 'user' +- 'system' \ No newline at end of file diff --git a/system/typemill/settings/system.yaml b/system/typemill/settings/system.yaml new file mode 100644 index 0000000..dc765df --- /dev/null +++ b/system/typemill/settings/system.yaml @@ -0,0 +1,343 @@ +fieldsetsystem: + type: fieldset + legend: System + fields: + title: + type: text + label: 'Website title' + maxlength: 60 + css: lg:w-half + author: + type: text + label: 'Website owner' + css: lg:w-half + maxlength: 60 + copyright: + type: select + label: 'Copyright' + css: lg:w-half + maxlength: 60 + description: 'Used for copyright and year in footer.' + options: + '©': '©' + 'CC-BY': 'CC-BY' + 'CC-BY-NC': 'CC-BY-NC' + 'CC-BY-NC-ND': 'CC-BY-NC-ND' + 'CC-BY-NC-SA': 'CC-BY-NC-SA' + 'CC-BY-ND': 'CC-BY-ND' + 'CC-BY-SA': 'CC-BY-SA' + year: + type: text + label: Year + css: lg:w-half + maxlength: 4 + description: 'Used for copyright and year in footer.' + language: + type: select + label: 'Language (author area)' + css: lg:w-half + maxlength: 60 + description: 'Used for translations in author area, themes, and plugins.' + options: + 'en': 'English' + 'ru': 'Russian' + 'nl': 'Dutch, Flemish' + 'de': 'German' + 'it': 'Italian' + 'fr': 'French' + langattr: + type: text + label: 'Language attribute (website)' + css: lg:w-half + maxlength: 5 + description: 'Used for frontend language attribute. Please use ISO 639-1 codes like "en".' + sitemap: + type: text + label: 'Google sitemap (readonly)' + css: lg:w-half + disabled: true + description: 'Submit the url above in google search console to support indexing.' + disableSitemap: + type: checkbox + css: lg:w-half + label: 'Disable Sitemap Generation' + checkboxlabel: 'Disable Sitemap generation to improve the performance for publish action.' +fieldsetmedia: + type: fieldset + legend: Media + fields: + logo: + type: image + label: Logo + favicon: + type: image + label: Favicon + description: 'Only PNG format will work.' + keepformat: true + liveimagewidth: + type: number + label: 'Standard width for live pictures' + placeholder: 820 + description: 'Default width of live images is 820px. Changes will apply to future uploads.' + css: lg:w-half + liveimageheight: + type: number + label: 'Standard height for live pictures' + description: 'If you add a value for the height, then the image will be cropped.' + css: lg:w-half + maximageuploads: + type: number + label: 'Maximum size for image uploads in MB' + description: 'The maximum image size might be limited by your server settings.' + allowsvg: + type: checkbox + label: Allow svg + checkboxlabel: 'Allow the upload of svg images.' + convertwebp: + type: checkbox + label: 'Convert to webp' + checkboxlabel: 'Try to convert uploaded images into the webp-format for better performance.' + maxfileuploads: + type: number + label: 'Maximum size for file uploads in MB' + description: 'The maximum file size might be limited by your server settings.' +fieldsetwriting: + type: fieldset + legend: Writing + fields: + editor: + type: radio + label: 'Standard editor mode' + css: lg:w-half + options: + 'visual': 'visual editor' + 'raw': 'raw editor' + formats: + type: checkboxlist + label: 'Format options for visual editor' + css: lg:w-half + options: + 'markdown': 'markdown' + 'headline': 'headline' + 'ulist': 'numbered list' + 'olist': 'bullet list' + 'table': 'table' + 'quote': 'quote' + 'notice': 'notice' + 'image': 'image' + 'video': 'video' + 'audio': 'audio' + 'file': 'file' + 'toc': 'table of contents' + 'hr': 'horizontal line' + 'definition': 'definition list' + 'code': 'code' + 'shortcode': 'shortcode' + 'youtube': 'YouTube (deprecated)' + headlineanchors: + type: checkbox + label: 'Headline anchors' + checkboxlabel: 'Show anchors next to headline in frontend' + urlschemes: + type: text + label: 'Url schemes' + description: 'Add more url schemes for external links e.g. like dict:// (comma separated list)' + maxlength: 60 +fieldsetaccess: + type: fieldset + legend: Access + fields: + access: + type: checkbox + label: 'Website restriction' + checkboxlabel: 'Show the website only to authenticated users and redirect all other users to the login page.' + pageaccess: + type: checkbox + label: 'Page restriction' + checkboxlabel: 'Activate individual restrictions for pages in the meta-tab of each page.' + hrdelimiter: + type: checkbox + label: 'Content break' + checkboxlabel: 'Cut restricted content after the first hr-element on a page (per default content will be cut after title).' + restrictionnotice: + type: textarea + label: 'Restriction notice (use markdown)' + maxlength: 2000 + wraprestrictionnotice: + type: checkbox + label: 'Wrap restriction notice' + checkboxlabel: 'Wrap the restriction notice above into a notice-4 element (which can be designed as special box)' + redirectadminrights: + type: select + label: 'After login redirect users with admin rights to' + css: lg:w-half + options: + settings.show: 'system page' + content: 'editor page' + user.account: 'account page' + home: 'home page (frontend)' + redirectcontentrights: + type: select + label: 'After login redirect users with edit rights to' + css: lg:w-half + options: + content: 'editor page' + user.account: 'account page' + home: 'home page (frontend)' + redirectaccountrights: + type: select + label: 'After login redirect users without edit rights to' + css: lg:w-half + options: + user.account: 'account page' + home: 'home page (frontend)' +fieldsetmail: + type: fieldset + legend: Email + fields: + mailfrom: + type: email + label: 'Mail From (required)' + placeholder: sender@yourmail.org + maxlength: 100 + description: 'Enter an email address that sends the e-mails (sender). The e-mail-feature will be used for recovery and verification e-mails. Send a testmail to your user-account to verify that you receive the e-mails.' + mailfromname: + type: text + label: 'Mail From Name (optional)' + placeholder: sender name + maxlength: 100 + description: 'Optionally enter a name for the sender address. If not set, the from-address will be visible.' + replyto: + type: text + label: 'Reply To (optional)' + placeholder: noreply@yourmail.org + maxlength: 100 + description: 'Optionally enter a "reply to" address for answers from the receiver. If not set, answers will go to the from-address.' +fieldsetrecover: + type: fieldset + legend: Password + fields: + recoverpw: + type: checkbox + label: 'Recover password' + checkboxlabel: 'Activate a password recovery in the login form.' + description: "From mail is required for this feature. Send a testmail before you use this feature." + recoversubject: + type: text + label: 'Email subject' + placeholder: 'Recover your password' + maxlength: 60 + recovermessage: + type: textarea + label: 'Text before recover link in email message' + description: 'The recover-link will be active for 24 hours.' + maxlength: 2000 +fieldsetsecurity: + type: fieldset + legend: Security + fields: + authcode: + type: checkbox + label: 'Login Verification (recommended)' + checkboxlabel: 'Verify your login with a 5-digit code send by email.' + description: 'From mail is required for this feature. Send a testmail before you use this feature. Make sure you have ftp-access to disable the feature in settings.yaml on failure. The verification code will be valid for 5 minutes. Be aware that device fingerprints will be stored in the user accounts.' + authcaptcha: + type: radio + label: 'Use captcha in authentication forms' + options: + disabled: 'Disable' + standard: 'Always show' + aftererror: 'Show after first wrong input' + securitylog: + type: checkbox + label: 'Security log' + checkboxlabel: 'Track spam and suspicious actions in a logfile' +fieldsetdeveloper: + type: fieldset + legend: "Developer" + fields: + displayErrorDetails: + type: checkbox + label: "Error reporting" + checkboxlabel: "Display application errors" + twigcache: + type: checkbox + label: "Twig cache" + checkboxlabel: "Activate the cache for twig templates" + proxy: + type: checkbox + label: "Proxy" + checkboxlabel: "Use x-forwarded-header" + trustedproxies: + type: text + label: "Trusted IPs for proxies (comma separated)" + fqdn: + type: text + label: "If your proxy does not work, try to add the base-url of your proxy here like https://mywebsite.com." + headersoff: + type: checkbox + label: "Disable Custom Headers" + checkboxlabel: "Disable all custom headers of Typemill (except cors) and send your own headers instead." + cspdisabled: + type: checkbox + label: "Disable CSP Headers" + checkboxlabel: "Disable all csp headers (content security policy) for this website." + cspdomains: + type: textarea + label: "Allowed Domains for Content on Typemill (CSP-Headers)" + placeholder: 'https://www.google.com,*google.com' + description: "List all domains, separated by commas, to allow content integration, such as iframes, on your Typemill website. Domains will be added to the csp-header. Usually done with plugins and themes, but add manually if something is blocked." +# corsdomains: +# type: textarea +# label: "Allowed Domains for API-Access (CORS-Headers)" +# placeholder: 'https://my-website-that-uses-the-api.org,https://another-website-using-the-api.org' +# description: "List all domains, separated by comma, that should have access to the Typemill API. Domains will be added to the cors-header." + trustedipsforapi: + type: text + label: "Trusted IPs for api access (comma separated)" + placeholder: "192.168.1.1, 203.0.113.0" + description: "Restrict api access to IPs for more security and if IPs are static." + trustedhostsforapi: + type: text + label: "Trusted Hosts for api access (comma separated)" + placeholder: "mydomain.org,sub.mydomain.org" + description: "Restrict api access to hosts for more security and if IPs are dynamic or unknown." + loginlink: + type: checkbox + label: "Login with link" + checkboxlabel: "Allow selected guest-users to login with a login link." + description: "If activated, you can allow login-links with a checkbox in the user profile. This is only available for guest-roles since guests do not have any rights. Login with a link can be helpful if you link from your software to a non-public documentation. Be aware of the low protection that this kind of logins has. If you integrate such links in a SaaS-software, then you should restrict access with login-links to your ips." + trustedloginreferrer: + type: text + label: "Trusted IPs for the login-link-referrer (comma separated)" +fieldsetai: + type: fieldset + legend: AI + fields: + aiprivacy: + type: paragraph + label: "Privacy Notice" + description: "You can use the following AI features in the Kixote interface. If you enable any AI integrations, user inputs will be sent to external services for processing. Please review the details for each service and ensure compliance with your local data privacy regulations. Each user will be asked for confirmation before using a model in the Kixote AI interface." + aiservice: + type: radio + label: 'Choose an AI service' + options: + none: 'None' +# kixote: 'Kixote (not available yet)' + chatgpt: 'ChatGPT' + chatgptModel: + type: select + label: 'ChatGPT model' + description: "Select the AI model you want to use. Pricing may vary over time, so please check the latest details on [OpenAI's pricing page](https://platform.openai.com/docs/pricing)" + options: + gpt-4o-mini: 'gpt-4o-mini ($0.6 / 1M output tokens)' + gpt-3.5-turbo-0125: 'gpt-3.5-turbo-0125 ($1.50 / 1M output tokens)' + gpt-4o: 'gpt-4o ($10.00 / 1M output tokens)' + o1-mini: 'o1-mini ($12.00 / 1M output tokens)' + o1: 'o1 ($60.00 / 1M output tokens)' + chatgptKey: + type: password + autocomplete: new-password + generator: false + label: 'ChatGPT Api Key' + description: "Enter your ChatGPT API key here. You can generate a new key on [OpenAI's platform](https://platform.openai.com/docs/pricing). For security reasons, your API key is secret and will not be visible again after you leave this page." \ No newline at end of file diff --git a/system/typemill/settings/systemnavi.yaml b/system/typemill/settings/systemnavi.yaml new file mode 100644 index 0000000..1a1a6da --- /dev/null +++ b/system/typemill/settings/systemnavi.yaml @@ -0,0 +1,36 @@ +'system': + 'title': 'System' + 'routename': 'settings.show' + 'icon': 'icon-wrench' + 'aclresource': 'system' + 'aclprivilege': 'read' +'license': + 'title': 'License' + 'routename': 'license.show' + 'icon': 'icon-wrench' + 'aclresource': 'user' + 'aclprivilege': 'read' +'themes': + 'title': 'Themes' + 'routename': 'themes.show' + 'icon': 'icon-paint-brush' + 'aclresource': 'system' + 'aclprivilege': 'read' +'plugins': + 'title': 'Plugins' + 'routename': 'plugins.show' + 'icon': 'icon-plug' + 'aclresource': 'system' + 'aclprivilege': 'read' +'account': + 'title': 'Account' + 'routename': 'user.account' + 'icon': 'icon-user' + 'aclresource': 'account' + 'aclprivilege': 'read' +'users': + 'title': 'Users' + 'routename': 'users.show' + 'icon': 'icon-group' + 'aclresource': 'user' + 'aclprivilege': 'read' \ No newline at end of file diff --git a/system/typemill/settings/user.yaml b/system/typemill/settings/user.yaml new file mode 100644 index 0000000..69c739f --- /dev/null +++ b/system/typemill/settings/user.yaml @@ -0,0 +1,45 @@ +username: + name: username + label: 'Username (read only)' + type: 'text' + readonly: true +firstname: + name: firstname + label: 'First Name' + type: 'text' +lastname: + name: lastname + label: 'Last Name' + type: 'text' +email: + name: email + label: 'E-Mail' + type: 'text' + required: true +userrole: + name: userrole + label: 'Role' + type: 'text' + readonly: true +darkmode: + name: darkmode + label: 'Darkmode' + checkboxlabel: 'Activate the darkmode for me' + type: 'checkbox' +aiservices: + name: aiservices + label: 'AI Services' + type: 'checkboxlist' + options: + chatgpt: "Use ChatGPT and accept their terms and conditions." +password: + name: password + label: 'Actual Password' + type: 'password' + autocomplete: 'new-password' +newpassword: + name: newpassword + label: 'New Password' + type: 'password' + autocomplete: 'new-password' + generator: true diff --git a/system/typemill/system.php b/system/typemill/system.php new file mode 100644 index 0000000..472c371 --- /dev/null +++ b/system/typemill/system.php @@ -0,0 +1,459 @@ +loadSettings(); + + +/**************************** +* HANDLE DISPLAY ERRORS * +****************************/ +if(isset($settings['displayErrorDetails']) && $settings['displayErrorDetails']) +{ + $display_errors = 1; + ini_set('display_errors', $display_errors); +} + +/**************************** +* CREATE CONTAINER + APP * +****************************/ + +# https://www.slimframework.com/docs/v4/start/upgrade.html#changes-to-container +$container = new Container(); +AppFactory::setContainer($container); + +$app = AppFactory::create(); +$container = $app->getContainer(); + +$responseFactory = $app->getResponseFactory(); +$routeParser = $app->getRouteCollector()->getRouteParser(); + +# add route parser to container to use named routes in controller +$container->set('routeParser', $routeParser); + + +/******************************* + * Basepath * + ******************************/ + +# in slim 4 you alsways have to set application basepath +$basepath = preg_replace('/(.*)\/.*/', '$1', $_SERVER['SCRIPT_NAME']); +$app->setBasePath($basepath); + + +/**************** +* URLINFO * +****************/ + +# WE DO NOT NEED IT HERE? +# WE CAN ADD IT TO CONTAINER IN MIDDLEWARE AFTER PROXY DETECTION + +# WE NEED FOR +# - LICENSE (base url) +# - Each plugin to add container +# - SESSION SEGMEŃTS (route) +# - TRANSLATIONS (route) +# - ASSETS (route) +# - TWIG URL EXTENSION +# - SESSION MIDDLEWARE + +$uriFactory = new UriFactory(); +$uri = $uriFactory->createFromGlobals($_SERVER); + +if( + isset($settings['proxy']) && + $settings['proxy'] && + isset($_SERVER['HTTP_X_FORWARDED_HOST']) && + isset($settings['fqdn']) && + $settings['fqdn'] != '' +) +{ + $fqdn = $uriFactory->createUri($settings['fqdn']); + $uri = $uri->withScheme($fqdn->getScheme()) + ->withHost($fqdn->getHost()) + ->withPort($fqdn->getPort()); +} + +$urlinfo = Urlinfo::getUrlInfo($basepath, $uri, $settings); + +$timer['settings'] = microtime(true); + +# set urlinfo +$container->set('urlinfo', $urlinfo); + +$timer['container'] = microtime(true); + +/**************************** +* CREATE EVENT DISPATCHER * +****************************/ + +$dispatcher = new EventDispatcher(); + +/**************************** +* Check Licence * +****************************/ + +$license = new License(); + +# checks if license is valid and returns scope +$settings['license'] = $license->getLicenseScope($urlinfo); + +/**************************** +* LOAD & UPDATE PLUGINS * +****************************/ +$plugins = Plugins::loadPlugins(); +$routes = []; +$middleware = []; +$pluginSettings = []; + +# if there are less plugins in the scan than in the settings, then a plugin has been removed +if(isset($settings['plugins']) && (count($plugins) < count($settings['plugins'])) ) +{ + $updateSettings = true; +} + +foreach($plugins as $plugin) +{ + $pluginName = $plugin['name']; + $className = $plugin['className']; + + # store existing plugin-settings to update settings later + if(isset($settings['plugins'][$pluginName])) + { + $pluginSettings[$pluginName] = $settings['plugins'][$pluginName]; + } + else + { + # it is a new plugin. Add it and set active to false + $pluginSettings[$pluginName] = ['active' => false]; + + # and set flag to refresh the settings + $updateSettings = true; + } + + # licence check + $PluginLicense = Plugins::getPremiumLicense($className); + if($PluginLicense) + { + if(!$settings['license'] OR !isset($settings['license'][$PluginLicense])) + { +# \Typemill\Static\Helpers\addLogEntry('No License: ' . $pluginName); + if($pluginSettings[$pluginName]['active']) + { + $pluginSettings[$pluginName]['active'] = false; + $updateSettings = true; + } + } + } + + # if the plugin is activated, add routes/middleware and add plugin as event subscriber + if(isset($settings['plugins'][$pluginName]['active']) && $settings['plugins'][$pluginName]['active']) + { + $routes = Plugins::getNewRoutes($className, $routes); + $middleware = Plugins::getNewMiddleware($className, $middleware); + + $dispatcher->addSubscriber(new $className($container)); + } +} + +# if plugins have been added or removed +if(isset($updateSettings)) +{ + # update stored settings file + $settingsModel->updateSettings($pluginSettings, 'plugins'); +} + +# add media extension to integrate video/audio with shortcodes +$dispatcher->addSubscriber(new MediaExtension($settings['rootPath'], $urlinfo['baseurl'])); + +# add final settings to the container +$container->set('settings', function() use ($settings){ return $settings; }); + +# dispatch the event onPluginsLoaded +$dispatcher->dispatch(new OnPluginsLoaded($plugins), 'onPluginsLoaded'); + +# dispatch settings event +$dispatcher->dispatch(new OnSettingsLoaded($settings), 'onSettingsLoaded'); + +$timer['plugins'] = microtime(true); + + +/**************************** +* LOAD ROLES & PERMISSIONS * +****************************/ + +# load roles and permissions and dispatch to plugins +$rolesAndPermissions = Permissions::loadRolesAndPermissions($settings['systemSettingsPath']); +$rolesAndPermissions = $dispatcher->dispatch(new OnRolesPermissionsLoaded($rolesAndPermissions), 'onRolesPermissionsLoaded')->getData(); + +# load resources and dispatch to plugins +$resources = Permissions::loadResources($settings['systemSettingsPath']); +$resources = $dispatcher->dispatch(new OnResourcesLoaded($resources), 'onResourcesLoaded')->getData(); + +# create acl-object +$acl = Permissions::createAcl($rolesAndPermissions, $resources); + +# add acl to container +$container->set('acl', function() use ($acl){ return $acl; }); + +$timer['permissions'] = microtime(true); + + +/**************************** +* SEGMENTS WITH SESSION * +****************************/ + +# if website is restricted to registered user +if( ( isset($settings['access']) && $settings['access'] ) || ( isset($settings['pageaccess']) && $settings['pageaccess'] ) ) +{ + # activate session for all routes + $session_segments = [$urlinfo['route']]; +} +else +{ + $session_segments = ['setup', 'tm/', 'api/']; + + # let plugins add own segments for session, eg. to enable csrf for forms + $client_segments = $dispatcher->dispatch(new OnSessionSegmentsLoaded([]), 'onSessionSegmentsLoaded')->getData(); + $session_segments = array_merge($session_segments, $client_segments); +} + +# start session +# Session::startSessionForSegments($session_segments, $urlinfo['route']); + +$timer['session segments'] = microtime(true); + +# initialize globals +$TwigGlobals = ['errors' => NULL, 'flash' => NULL, 'assets' => NULL]; +$TwigGlobals = $dispatcher->dispatch(new OnTwigGlobalsLoaded($TwigGlobals), 'onTwigGlobalsLoaded')->getData(); + +# echo '
          ';
          +# print_r($TwigGlobals);
          +# die();
          +
          +/****************************
          +* OTHER CONTAINER ITEMS			*
          +****************************/
          +
          +# translations
          +$translations = Translations::loadTranslations($settings, $urlinfo['route']);
          +$container->set('translations', $translations);
          +
          +# dispatcher to container
          +$container->set('dispatcher', function() use ($dispatcher){ return $dispatcher; });
          +
          +# asset function for plugins
          +$assets = new \Typemill\Assets($urlinfo['baseurl']);
          +$container->set('assets', function() use ($assets){ return $assets; });
          +
          +/****************************
          +* TWIG TO CONTAINER					*
          +****************************/
          +
          +$container->set('view', function() use ($settings, $TwigGlobals, $urlinfo, $translations, $dispatcher, $acl) {
          +
          +	$twig = Twig::create(
          +		[
          +			# path to templates with namespaces
          +			$settings['rootPath'] . DIRECTORY_SEPARATOR . 'themes' . DIRECTORY_SEPARATOR . $settings['theme'],
          +			$settings['rootPath'] . DIRECTORY_SEPARATOR . 'system' . DIRECTORY_SEPARATOR . 'typemill' . DIRECTORY_SEPARATOR . 'author',
          +		],
          +		[
          +			# settings
          +			'cache' => ( isset($settings['twigcache']) && $settings['twigcache'] ) ? $settings['rootPath'] . '/cache/twig' : false,
          +			'debug' => isset($settings['displayErrorDetails']),
          +			'debug' => true,
          +			'autoescape' => false
          +		]
          +	);
          +
          +	foreach($TwigGlobals as $name => $feature)
          +	{
          +#		echo $name . ';';
          +		$twig->getEnvironment()->addGlobal($name, $feature);
          +	}
          +
          +	# add extensions
          +	$twig->addExtension(new DebugExtension());
          +	$twig->addExtension(new TwigUserExtension($acl));
          +	$twig->addExtension(new TwigUrlExtension($urlinfo));
          +	$twig->addExtension(new TwigLanguageExtension( $translations ));
          +	$twig->addExtension(new TwigMarkdownExtension($urlinfo['baseurl'], $settings, $dispatcher));
          +	$twig->addExtension(new TwigMetaExtension());
          +	$twig->addExtension(new TwigPagelistExtension());
          +	$twig->addExtension(new TwigCaptchaExtension());
          +
          +	return $twig;
          +
          +});
          +
          +/****************************
          +* MIDDLEWARE				*
          +****************************/
          +
          +foreach($middleware as $pluginMiddleware)
          +{
          +	$middlewareClass 	= $pluginMiddleware['classname'];
          +	$middlewareParams	= $pluginMiddleware['params'];
          +	if(class_exists($middlewareClass))
          +	{
          +		$app->add(new $middlewareClass($middlewareParams));
          +	}
          +}
          +
          +$app->add(new CustomHeadersMiddleware($settings));
          +
          +$app->add(new AssetMiddleware($assets, $container->get('view')));
          +
          +$app->add(new ValidationErrorsMiddleware($container->get('view')));
          +
          +$app->add(new SecurityMiddleware($routeParser, $container->get('settings')));
          +
          +$app->add(new OldInputMiddleware($container->get('view')));
          +
          +# Add Twig-View Middleware
          +$app->add(TwigMiddleware::createFromContainer($app));
          +
          +$app->add(new FlashMessages($container));
          +
          +# add JsonBodyParser Middleware
          +$app->add(new JsonBodyParser());
          +
          +# routing middleware earlier than error middleware so errors are shown
          +$app->addRoutingMiddleware();
          +
          +# error middleware
          +$errorMiddleware = new ErrorMiddleware(
          +	$app->getCallableResolver(),
          +	$app->getResponseFactory(),
          +	$display_errors,
          +	false,
          +	false
          +);
          +
          +# Set the Not Found Handler
          +$errorMiddleware->setErrorHandler(HttpNotFoundException::class, function ($request, $exception) use ($container) {	
          +
          +	$response = new NewResponse();
          + 
          +	return $container->get('view')->render($response->withStatus(404), '404.twig', $pagedata);
          +
          +});
          +
          +$app->add($errorMiddleware);
          +
          +$app->add(new SessionMiddleware($session_segments, $urlinfo['route']));
          +
          +$app->add(new RemoveCredentialsMiddleware());
          +
          +if(isset($settings['proxy']) && $settings['proxy'])
          +{
          +	$trustedProxies = ( isset($settings['trustedproxies']) && !empty($settings['trustedproxies']) ) ? explode(",", $settings['trustedproxies']) : [];
          +	$app->add(new ProxyDetection($trustedProxies));	
          +}
          +
          +
          +$timer['middleware'] = microtime(true);
          +
          +
          +/******************************
          +* GET CSP FROM PLUGINS/THEMES *
          +******************************/
          +
          +# get additional csp headers from plugins
          +$cspFromPlugins = $dispatcher->dispatch(new OnCspLoaded([]), 'onCspLoaded')->getData();
          +
          +# get additional csp headers from theme 
          +$cspFromTheme = [];
          +$themeSettings = $settingsModel->getObjectSettings('themes', $settings['theme']);
          +if(isset($themeSettings['csp']) && is_array($themeSettings['csp']) && !empty($themeSettings['csp']) )
          +{
          +	$cspFromTheme = $themeSettings['csp'];
          +}
          +
          +/************************
          +*   ADD ROUTES          *
          +************************/
          +
          +if(isset($settings['setup']) && $settings['setup'] == true)
          +{
          +	require __DIR__ . '/routes/setup.php';
          +}
          +else
          +{
          +	require __DIR__ . '/routes/api.php';
          +	require __DIR__ . '/routes/web.php';
          +}
          +
          +$timer['routes'] = microtime(true);
          +
          +
          +/************************
          +*   RUN APP         		*
          +************************/
          +
          +$app->run();
          +
          +# $timer['run'] = microtime(true);
          +
          +# Typemill\Static\Helpers::printTimer($timer);
          \ No newline at end of file
          diff --git a/tailwind.config.js b/tailwind.config.js
          new file mode 100644
          index 0000000..27231ad
          --- /dev/null
          +++ b/tailwind.config.js
          @@ -0,0 +1,25 @@
          +/** @type {import('tailwindcss').Config} */
          +module.exports = {
          +  content: ["./system/typemill/author/**/*.{html,js,twig}"],
          +  darkMode: 'class',
          +  theme: {
          +    extend: {
          +      screens: {
          +        'lg': '1024px', // You can adjust this breakpoint if needed
          +      },
          +      spacing: {
          +        // Define the 'half' spacing utility
          +        'half': '50%', // You can adjust this value as needed
          +      },
          +      width: {     
          +        'half': '48%',
          +        '54rem': '54rem',
          +      },
          +      opacity: {
          +        '0': '0',
          +      },
          +      visibility: ["group-hover"],
          +    },
          +  },
          +  plugins: [],
          +}
          \ No newline at end of file
          diff --git a/themes/cyanine/404.twig b/themes/cyanine/404.twig
          new file mode 100644
          index 0000000..d486305
          --- /dev/null
          +++ b/themes/cyanine/404.twig
          @@ -0,0 +1,35 @@
          +{% extends 'layout.twig' %}
          +
          +{% block title %}ERROR 404: Page not found{% endblock %}
          +
          +{% block content %}
          +
          +
          + +
          + +
          + +
          + +
          +

          {{title}}

          +
          + +
          + +

          {{description}}

          + + Return to the homepage + +
          + +
          + +
          + +
          + +
          + +{% endblock %} \ No newline at end of file diff --git a/themes/cyanine/blog.twig b/themes/cyanine/blog.twig new file mode 100644 index 0000000..e839880 --- /dev/null +++ b/themes/cyanine/blog.twig @@ -0,0 +1,112 @@ +
          + +
          + + + +
          + + {% if settings.themes.cyanine.blogintro %} +
          +
          + +

          {{ title }}

          + +
          + + {{ content }} + +
          + {% endif %} + + {% set pagelist = getPageList(navigation, settings.themes.cyanine.blogfolder, base_url) %} + {% set pagesize = 10 %} + {% set pages = ( pagelist.folderContent|length / pagesize)|round(0, 'ceil') %} + {% set currentpage = currentpage ? currentpage : 1 %} + {% set currentposts = (currentpage - 1) * pagesize %} + + {% if pagelist.contains == "posts" %} +
            + + {% for element in pagelist.folderContent|slice(currentposts, pagesize) %} + + {% set post = getPageMeta(settings, element) %} + {% set date = element.order[0:4] ~ '-' ~ element.order[4:2] ~ '-' ~ element.order[6:2] %} + +
          • +
            + {% if settings.themes.cyanine.blogimage and post.meta.heroimage != '' %} +
            + {{ post.meta.heroimage }} +
            + {% endif %} +

            {{ post.meta.title }}

            +
            | {{ post.meta.author }}
            +
            +

            {{ post.meta.description }}

            +
          • + + {% endfor %} + + {% if pages > 1 %} +
            +

            Page: + {% for i in 1 .. pages %} + {% if i == currentpage %} + {{i}} + {% else %} + {{i}} + {% endif %} + {% endfor %} +

            + {% endif %} + +
          + {% else %} +

          The folder contains pages. To use the blog-feature on the startpage, please change the folder content to posts.

          + {% endif %} +
          + + + +
          + +
          \ No newline at end of file diff --git a/themes/cyanine/css/style.css b/themes/cyanine/css/style.css new file mode 100644 index 0000000..78e3652 --- /dev/null +++ b/themes/cyanine/css/style.css @@ -0,0 +1,636 @@ +/* Y O U R C S S S T Y L E S +** +** Style all markdown content elements properly +** Use the markdown test file to check it: https://github.com/typemill/typemill/blob/master/content/00-Welcome/03-Markdown-Test.md +** You can activate and use the Tachyons CSS library: https://typemill.net/theme-developers/helper-functions#activate-tachyons +** +*/ + +/************************************ +* STANDARD-ELEMENTS * +************************************/ +html{} +body{} +header{} +footer{} +nav{} +main{} +aside{} +article{} +article a, article a:link, article a:visited{ text-decoration: underline; } +/* article a:before{ content: '\203A'; margin-right: 5px; } */ +abbr{} +blockquote{ + margin: 1.5em 1.5em; + padding: 0.5em 0; + quotes: "\201C""\201D""\2018""\2019"; + font-style: italic; +} +blockquote:before{ + color: #ccc; + content: open-quote; + font-size: 4em; + line-height: 0.1em; + margin-right: 0.25em; + vertical-align: -0.4em; +} +blockquote p { + display: inline; +} +article pre, article code{ + font-family: monospace; +} +article pre{ + white-space: pre; + padding: 0em; + display: block; + max-width: 100%; + overflow-x: auto; +} +article code{ + font-size: 0.8em; + line-height: 1.4em; + padding: 0 0.5em; + word-break: break-word; +} +pre > code{ + font-size: 0.8em; + padding: 1em; + display: inline-block; +} +pre > code.language-pagebreak{ + display: none; +} +dl{} +dt{} +dd{} +img{} +article h1, article h2, article h3, article h4, article h5, article h6{ + line-height: 1.15em; + font-weight: 700; + line-height: 1em; +} +h1,h2,h3,h4,h5,h6{ word-wrap: break-word; hyphens: auto; } +article h1{ font-size: 2.2em; margin: 1.4em 0 0.6em; z-index:1; } +article h2{ font-size: 1.6em; margin: 1.8em 0 0.6em; } +article h3{ font-size: 1.3em; margin: 1.6em 0 0.6em; } +article h4{ font-size: 1.1em; margin: 1.4em 0 0.6em; } +article h5{ font-size: 1em; margin: 1.2em 0 0.6em; } +article h6{ font-size: 1em; font-style: italic; font-weight:300; margin: 1em 0 0.6em; } +article .h1, article .h2, article .h3, article .h4, article .h5, article .h6{ + height: auto; /* fix for tachyons */ +} +hr{ + border: none; + border-top: 1px solid; +} +ol{} +footer ul{ padding-left:1em; } +li{} +sup{} +/* Make table look good */ +.tm-table{ + overflow-x: auto; +} +table{ + width: 100%; + border-collapse: collapse; + font-size: .8em; +} +thead{ + text-align: left; +} +tr{ + border-top: 1px solid; + border-bottom: 1px solid; + margin-bottom: -1px; +} +th,td{ + padding: .5em 1em; +} +main b, main strong{ + font-weight: 600; +} + +/************************************ +* STANDARD SUGGESTIONS * +************************************/ + +/* Make links and buttons smooth */ +.link, .link:active, .link:focus, .link:hover, .link:link, .link:visited{ + transition: none; +} +a, a:link, a:visited, a:focus, a:hover, a:active, button{ + transition: all .15s ease!important; + transition-property: color, background-color, text-shadow, border; +} +article a:hover, article a:focus, article a:active, +footer a:hover, footer a:focus, footer a:active{ + text-decoration: none; +} + +/* For definition list */ +dt::after{ + content: ":"; +} + +/* Make images and image captions look good */ +img, figure,figure img{ + max-width: 100%; + height: auto; + aspect-ratio: attr(width) / attr(height); +} +figure, video, audio{ + display: table; + margin: 2em auto; + padding: 0; +} +figure.right, figure.left { + width: auto; + float: none; + margin: auto auto; +} +figure img{ + display: block; + margin: auto; +} +figcaption{ + display: table-caption; + caption-side: bottom; + font-size: 0.8em; + margin-top: .5em; + line-height: 1.2em; +} +.footnotes ol{ + font-size: .8em; + line-height: 1em; + margin: 0 0 0 0; +} + +/************************************ +* TYPEMILL-ELEMENTS * +************************************/ + +ul.TOC,.TOC ul{ + list-style: none; +} +.TOC{ + padding: 1em; + border: 1px solid; +} +.TOC{ + border-color: lightgray; +} +.TOC li{ + position: relative; +} +.TOC li a{ + text-decoration: none; + display: inline-block; + width: 100%; + border-bottom: 1px dashed; + line-height: 1em; + margin: .3em 0; +} +.TOC li a:hover,.TOC li a:focus,.TOC li a:active{ + border-bottom: 1px solid; +} +.TOC li a:after{ + content: '\203A'; + position: absolute; + right: 5px; + transition: all .2s; +} +.TOC li a:hover:after{ + right: 0px; +} +.notice1 { + margin: 1em 0; + padding: 10px 1em; +} +.notice2 { + margin: 1em 0; + padding: 10px 1em; +} +.notice3{ + margin: 1em 0; + padding: 10px 1em; + background-color: #d4e0ff; + border-left: 4px solid #3c7bf6; +} + +/* used for pro content box */ +.notice4{ + position: relative; + text-align: center; + padding: 1em; + border: 1px solid; +} + +/* Style the optional anchor-links for headlines */ +a.tm-heading-anchor { + display: inline-block; + margin-left: -0.8em; + width: 0.8em; + font-weight: 300; + opacity: 0; + text-decoration: none; + float: right; +} +a.tm-heading-anchor:hover,a.tm-heading-anchor:focus { + opacity: 1; + text-decoration: none; +} +h2:focus > .tm-heading-anchor, +h2:hover > .tm-heading-anchor, +h3:focus > .tm-heading-anchor, +h3:hover > .tm-heading-anchor, +h4:focus > .tm-heading-anchor, +h4:hover > .tm-heading-anchor, +h5:focus > .tm-heading-anchor, +h5:hover > .tm-heading-anchor, +h6:focus > .tm-heading-anchor, +h6:hover > .tm-heading-anchor{ + opacity: .5; + text-decoration: none; +} + +/* style the typemill download-button for files */ +a.tm-download{} +a.tm-download::before{ + content: "\2193"; + -webkit-mask: url("data:image/svg+xml; utf8, ") no-repeat 50% 50%; + mask: url("data:image/svg+xml; utf8, ") no-repeat 50% 50%; + -webkit-mask-size: cover; + mask-size: cover; + background-color: currentColor; + display: inline-block; + margin-right: 5px; + width: 24px; + height: 24px; + text-decoration: none; + vertical-align: middle; +} +a.tm-download:hover::before{ + text-decoration: none; +} + +/* Fake youtube button. Works with typemillutilities.js */ +.video-container{ + position: relative; + text-align: center; +} +img.youtube{ + position: relative; + max-width: 560px; +} +button.play-video { + position: absolute; + top: 50%; + left: 50%; + margin-top: -50px; + margin-left: -50px; + height: 100px; + width: 100px; + background: #e0474c; + color: #FFFFFF; + border-radius: 50%; + border: 0px; + padding: 0; + text-align: center; +} +button.play-video:hover { + background: #cc4146; + cursor: pointer; +} +button.play-video::after { + position: absolute; + top: 50%; + margin: -20px 0 0 -15px; + height: 0; + width: 0; + border-style: solid; + border-width: 20px 0 20px 40px; + border-color: transparent transparent transparent rgba(255, 255, 255, 0.75); + content: ' '; +} + +.landingpageinfo h2{ + font-size: 2.25rem; +} +.landingpageinfo h3{ + font-size: 1.5rem; +} +.landingpageinfo h4{ + font-size: 1.25rem; +} +.icon { + display: inline-block; + width: 1em; + height: 1em; + stroke-width: 0; + stroke: currentColor; + fill: currentColor; +} +/************************************ +* TACHYONS ADDITIONS * +************************************/ + +/* Keeps Footer at the bottom */ +.body-footer-bottom { /* add this class to the body-tag */ + min-height: 100vh; + display: flex; + flex-direction: column; +} +.footer-bottom { /* add this class to the footer-tag */ + margin-top: auto; +} +/* Nice set of system fonts, add this to the body-tag */ +.sans-serif-tm { + font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif; +} +.sans-serif-title{ + font-family: arial,"Segoe UI",Roboto,-apple-system,BlinkMacSystemFont,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif; +} +/* optimize text, add this to the body-tag */ +.optimize-text{ + /* Adjust font size */ + font-size: 100%; + -webkit-text-size-adjust: 100%; + /* Font varient */ + font-variant-ligatures: none; + -webkit-font-variant-ligatures: none; + /* Smoothing */ + text-rendering: optimizeLegibility; + -moz-osx-font-smoothing: grayscale; + font-smoothing: antialiased; + -webkit-font-smoothing: antialiased; + text-shadow: rgba(0, 0, 0, .01) 0 0 1px; +} +.grid-container{ + display: block; +} +.grid-header, .grid-main, .grid-sidebar{ + display: block; + width: 100%; +} +.grid-footer{ + grid-column: 1 / -1; +} + +.f-large{ + font-size: 4rem; +} +.h4-5{ + height: 12rem; +} +.shadow-2-hover{ + transition: all 0.2s cubic-bezier(0.165, 0.84, 0.44, 1); +} +.shadow-2-hover:hover, .shadow-2-hover:focus{ + box-shadow:0px 0px 8px 2px rgba( 0, 0, 0, 0.2 ); +} +.margin-bottom-1{ + margin-bottom: -1px; +} +.arrow-after:after{ + content: '\203A'; position: absolute; right:5px; +} +.arrow-after-transition:after{ + content: '\203A'; position: absolute; right:5px; + transition: all .2s; +} +.arrow-after-transition:hover:after{ + right:0px; +} +.indent-l-1{ padding-left:1rem; } +.indent-l-2{ padding-left:2rem; } +.indent-l-3{ padding-left:3rem; } +.indent-l-4{ padding-left:4rem; } +.indent-l-5{ padding-left:4.5rem; } +.indent-l-6{ padding-left:5rem; } +.b--solid-hover:hover,.b--solid-hover:focus,.b--solid-hover:active{ + border-style: solid; +} +.flex-grow{ + flex-grow: 1; +} + +/************************* +* RESPONSIVE NAVIGATION * +*************************/ + +.contentnav{ + width: 70%; + position: absolute; + right: 0; + top: 0; +} +.contentnav.collapse .folder > ul{ + display: none; +} +.contentnav .folder.active > ul,.contentnav .folder.activeParent > ul{ + display: block; +} +.burgerbutton{ + display: inline-block; + font-size: 2em; + width: 100%; + text-align: right; + padding: 2rem; + box-sizing: border-box; +} +.burgerbutton, .menu{ + transition:all .2s ease; +} +.menu { + max-height: 0; /* hide menu completely when burger unchecked */ + margin: 0px; + overflow:hidden; + position: relative; + z-index: 1; + font-size: 1.3rem; +} +#burger:checked ~ .menu { + max-height: 200%; +} + +#burger:checked ~ .menu { + background: #333; +} +.menu a{ + color: #fff; +} +#burger:checked ~ .burgerbutton { + color: #fff; + background: #333; +} + +.logo-image{ + max-width: 200px; +} + +@media screen and (min-width:30em){ + h1,h2,h3,h4,h5,h6{ hyphens: manual; } +} + +@media (min-width: 40em) { + figure.right { + width: auto; + float: right; + margin: 0 0 2em 2em; + } + figure.left { + width: auto; + float: left; + margin: 0 2em 2em 0; + } +} + +/************** +* Forms * +**************/ + +form{ + width: 100%; + max-width: 800px; + border: none; + margin: 20px 0 20px 0; + padding: 0 0 0 0; +} +form fieldset{ + width: 100%; + border: none; + margin: 0 0 0 0; + padding: 0 0 0 0; +} +form label{ + width: 100%; + display: block; + color: #333; + margin-top: 1.5em; +} +form input, form textarea{ + width: 100%; + display: block; + border: 1px solid #ddd; + padding: 15px; + margin: 0 0 20px 0; + background: #f5f5f5; +} +form input:focus,form select:focus,form textarea:focus{ + outline: none; + border: 1px solid rgba(229, 226, 211, 1); + box-shadow: 0 0 2px #D73B00;; +} +form input[type="submit"],form button,form .button{ + cursor: pointer; + font-size: 1em; + border: 0; + margin-top: 1.5em; + width: 100%; + padding: 15px; +} +form input[type="submit"]:hover,form button:hover,.button:hover{ + opacity: .8; +} + + +@media screen and (min-width:50em) { + .grid-container{ + display: grid; + grid-template-columns: 30% 70%; + grid-column-gap: 1em; + grid-template-rows: auto auto; + grid-template-areas: + "gridHeader gridMain" + "gridSidebar gridMain" + ". gridMain" + } + .grid-header{ + grid-area: gridHeader; + } + .grid-main{ + grid-area: gridMain; + } + .grid-sidebar{ + grid-area: gridSidebar; + } + + .logo-image{ + max-width: 100%; + } + + #burger:checked ~ .burgerbutton { + color: inherit; + background: inherit; + } + #burger:checked ~ .menu { + background: inherit; + } + .menu a{ + color: inherit; + } + .contentnav{ + position: relative; + width: auto; + } + .burgerbutton{ + display: none; + } + .menu{ + font-size: inherit; + max-height: inherit; + } + a.tm-heading-anchor { + float: left; + } +} + +@media print { + #contentnav, nav, #bottompager,.widgetcontainer,.funcicons,.account,footer{ + display: none; + } + .grid-main{ + margin-top: -10px!important; + padding-top: 0px!important; + } + .logo-image{ + max-width: 150px; + } + main{ + padding-bottom: 0!important; + } + aside{ + padding-top: 0!important; + padding-bottom: 0!important; + } + main,footer{ + border: 0px!important; + } + footer .ph3 + { + padding-top: 0; + padding-bottom: 0; + } + body, .landingpagecontrast, .account, main, footer, .landingpageintro, .landingpageinfo, .landingpageteaser, .landingpagenavi, .landingpagenews, button.expander, .notice4{ + background: #fff!important; + color: black!important; + } + article{ + font-size: .8em; + } + article a[href]{ + color: black; + text-decoration: underline; + } + article a[href]:after { + content: " (" attr(href) ")"; + } + nav .mw6{ + width: 100%; + } + a.tm-heading-anchor { + display: none; + } +} \ No newline at end of file diff --git a/themes/cyanine/css/tachyons.min.css b/themes/cyanine/css/tachyons.min.css new file mode 100644 index 0000000..0980766 --- /dev/null +++ b/themes/cyanine/css/tachyons.min.css @@ -0,0 +1,2 @@ +/*! TACHYONS v4.12.0 | http://tachyons.io */ +/*! normalize.css v8.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}[hidden],template{display:none}.border-box,a,article,aside,blockquote,body,code,dd,div,dl,dt,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,html,input[type=email],input[type=number],input[type=password],input[type=tel],input[type=text],input[type=url],legend,li,main,nav,ol,p,pre,section,table,td,textarea,th,tr,ul{box-sizing:border-box}.aspect-ratio{height:0;position:relative}.aspect-ratio--16x9{padding-bottom:56.25%}.aspect-ratio--9x16{padding-bottom:177.77%}.aspect-ratio--4x3{padding-bottom:75%}.aspect-ratio--3x4{padding-bottom:133.33%}.aspect-ratio--6x4{padding-bottom:66.6%}.aspect-ratio--4x6{padding-bottom:150%}.aspect-ratio--8x5{padding-bottom:62.5%}.aspect-ratio--5x8{padding-bottom:160%}.aspect-ratio--7x5{padding-bottom:71.42%}.aspect-ratio--5x7{padding-bottom:140%}.aspect-ratio--1x1{padding-bottom:100%}.aspect-ratio--object{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;z-index:100}img{max-width:100%}.cover{background-size:cover!important}.contain{background-size:contain!important}.bg-center{background-position:50%}.bg-center,.bg-top{background-repeat:no-repeat}.bg-top{background-position:top}.bg-right{background-position:100%}.bg-bottom,.bg-right{background-repeat:no-repeat}.bg-bottom{background-position:bottom}.bg-left{background-repeat:no-repeat;background-position:0}.outline{outline:1px solid}.outline-transparent{outline:1px solid transparent}.outline-0{outline:0}.ba{border-style:solid;border-width:1px}.bt{border-top-style:solid;border-top-width:1px}.br{border-right-style:solid;border-right-width:1px}.bb{border-bottom-style:solid;border-bottom-width:1px}.bl{border-left-style:solid;border-left-width:1px}.bn{border-style:none;border-width:0}.b--black{border-color:#000}.b--near-black{border-color:#111}.b--dark-gray{border-color:#333}.b--mid-gray{border-color:#555}.b--gray{border-color:#777}.b--silver{border-color:#999}.b--light-silver{border-color:#aaa}.b--moon-gray{border-color:#ccc}.b--light-gray{border-color:#eee}.b--near-white{border-color:#f4f4f4}.b--white{border-color:#fff}.b--white-90{border-color:hsla(0,0%,100%,.9)}.b--white-80{border-color:hsla(0,0%,100%,.8)}.b--white-70{border-color:hsla(0,0%,100%,.7)}.b--white-60{border-color:hsla(0,0%,100%,.6)}.b--white-50{border-color:hsla(0,0%,100%,.5)}.b--white-40{border-color:hsla(0,0%,100%,.4)}.b--white-30{border-color:hsla(0,0%,100%,.3)}.b--white-20{border-color:hsla(0,0%,100%,.2)}.b--white-10{border-color:hsla(0,0%,100%,.1)}.b--white-05{border-color:hsla(0,0%,100%,.05)}.b--white-025{border-color:hsla(0,0%,100%,.025)}.b--white-0125{border-color:hsla(0,0%,100%,.0125)}.b--black-90{border-color:rgba(0,0,0,.9)}.b--black-80{border-color:rgba(0,0,0,.8)}.b--black-70{border-color:rgba(0,0,0,.7)}.b--black-60{border-color:rgba(0,0,0,.6)}.b--black-50{border-color:rgba(0,0,0,.5)}.b--black-40{border-color:rgba(0,0,0,.4)}.b--black-30{border-color:rgba(0,0,0,.3)}.b--black-20{border-color:rgba(0,0,0,.2)}.b--black-10{border-color:rgba(0,0,0,.1)}.b--black-05{border-color:rgba(0,0,0,.05)}.b--black-025{border-color:rgba(0,0,0,.025)}.b--black-0125{border-color:rgba(0,0,0,.0125)}.b--dark-red{border-color:#e7040f}.b--red{border-color:#ff4136}.b--light-red{border-color:#ff725c}.b--orange{border-color:#ff6300}.b--gold{border-color:#ffb700}.b--yellow{border-color:gold}.b--light-yellow{border-color:#fbf1a9}.b--purple{border-color:#5e2ca5}.b--light-purple{border-color:#a463f2}.b--dark-pink{border-color:#d5008f}.b--hot-pink{border-color:#ff41b4}.b--pink{border-color:#ff80cc}.b--light-pink{border-color:#ffa3d7}.b--dark-green{border-color:#137752}.b--green{border-color:#19a974}.b--light-green{border-color:#9eebcf}.b--navy{border-color:#001b44}.b--dark-blue{border-color:#00449e}.b--blue{border-color:#357edd}.b--light-blue{border-color:#96ccff}.b--lightest-blue{border-color:#cdecff}.b--washed-blue{border-color:#f6fffe}.b--washed-green{border-color:#e8fdf5}.b--washed-yellow{border-color:#fffceb}.b--washed-red{border-color:#ffdfdf}.b--transparent{border-color:transparent}.b--inherit{border-color:inherit}.b--initial{border-color:initial}.b--unset{border-color:unset}.br0{border-radius:0}.br1{border-radius:.125rem}.br2{border-radius:.25rem}.br3{border-radius:.5rem}.br4{border-radius:1rem}.br-100{border-radius:100%}.br-pill{border-radius:9999px}.br--bottom{border-top-left-radius:0;border-top-right-radius:0}.br--top{border-bottom-right-radius:0}.br--right,.br--top{border-bottom-left-radius:0}.br--right{border-top-left-radius:0}.br--left{border-top-right-radius:0;border-bottom-right-radius:0}.br-inherit{border-radius:inherit}.br-initial{border-radius:initial}.br-unset{border-radius:unset}.b--dotted{border-style:dotted}.b--dashed{border-style:dashed}.b--solid{border-style:solid}.b--none{border-style:none}.bw0{border-width:0}.bw1{border-width:.125rem}.bw2{border-width:.25rem}.bw3{border-width:.5rem}.bw4{border-width:1rem}.bw5{border-width:2rem}.bt-0{border-top-width:0}.br-0{border-right-width:0}.bb-0{border-bottom-width:0}.bl-0{border-left-width:0}.shadow-1{box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.shadow-2{box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.shadow-3{box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.shadow-4{box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.shadow-5{box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}.pre{overflow-x:auto;overflow-y:hidden;overflow:scroll}.top-0{top:0}.right-0{right:0}.bottom-0{bottom:0}.left-0{left:0}.top-1{top:1rem}.right-1{right:1rem}.bottom-1{bottom:1rem}.left-1{left:1rem}.top-2{top:2rem}.right-2{right:2rem}.bottom-2{bottom:2rem}.left-2{left:2rem}.top--1{top:-1rem}.right--1{right:-1rem}.bottom--1{bottom:-1rem}.left--1{left:-1rem}.top--2{top:-2rem}.right--2{right:-2rem}.bottom--2{bottom:-2rem}.left--2{left:-2rem}.absolute--fill{top:0;right:0;bottom:0;left:0}.cf:after,.cf:before{content:" ";display:table}.cf:after{clear:both}.cf{*zoom:1}.cl{clear:left}.cr{clear:right}.cb{clear:both}.cn{clear:none}.dn{display:none}.di{display:inline}.db{display:block}.dib{display:inline-block}.dit{display:inline-table}.dt{display:table}.dtc{display:table-cell}.dt-row{display:table-row}.dt-row-group{display:table-row-group}.dt-column{display:table-column}.dt-column-group{display:table-column-group}.dt--fixed{table-layout:fixed;width:100%}.flex{display:flex}.inline-flex{display:inline-flex}.flex-auto{flex:1 1 auto;min-width:0;min-height:0}.flex-none{flex:none}.flex-column{flex-direction:column}.flex-row{flex-direction:row}.flex-wrap{flex-wrap:wrap}.flex-nowrap{flex-wrap:nowrap}.flex-wrap-reverse{flex-wrap:wrap-reverse}.flex-column-reverse{flex-direction:column-reverse}.flex-row-reverse{flex-direction:row-reverse}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-baseline{align-items:baseline}.items-stretch{align-items:stretch}.self-start{align-self:flex-start}.self-end{align-self:flex-end}.self-center{align-self:center}.self-baseline{align-self:baseline}.self-stretch{align-self:stretch}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.justify-around{justify-content:space-around}.content-start{align-content:flex-start}.content-end{align-content:flex-end}.content-center{align-content:center}.content-between{align-content:space-between}.content-around{align-content:space-around}.content-stretch{align-content:stretch}.order-0{order:0}.order-1{order:1}.order-2{order:2}.order-3{order:3}.order-4{order:4}.order-5{order:5}.order-6{order:6}.order-7{order:7}.order-8{order:8}.order-last{order:99999}.flex-grow-0{flex-grow:0}.flex-grow-1{flex-grow:1}.flex-shrink-0{flex-shrink:0}.flex-shrink-1{flex-shrink:1}.fl{float:left}.fl,.fr{_display:inline}.fr{float:right}.fn{float:none}.sans-serif{font-family:-apple-system,BlinkMacSystemFont,avenir next,avenir,helvetica neue,helvetica,ubuntu,roboto,noto,segoe ui,arial,sans-serif}.serif{font-family:georgia,times,serif}.system-sans-serif{font-family:sans-serif}.system-serif{font-family:serif}.code,code{font-family:Consolas,monaco,monospace}.courier{font-family:Courier Next,courier,monospace}.helvetica{font-family:helvetica neue,helvetica,sans-serif}.avenir{font-family:avenir next,avenir,sans-serif}.athelas{font-family:athelas,georgia,serif}.georgia{font-family:georgia,serif}.times{font-family:times,serif}.bodoni{font-family:Bodoni MT,serif}.calisto{font-family:Calisto MT,serif}.garamond{font-family:garamond,serif}.baskerville{font-family:baskerville,serif}.i{font-style:italic}.fs-normal{font-style:normal}.normal{font-weight:400}.b{font-weight:700}.fw1{font-weight:100}.fw2{font-weight:200}.fw3{font-weight:300}.fw4{font-weight:400}.fw5{font-weight:500}.fw6{font-weight:600}.fw7{font-weight:700}.fw8{font-weight:800}.fw9{font-weight:900}.input-reset{-webkit-appearance:none;-moz-appearance:none}.button-reset::-moz-focus-inner,.input-reset::-moz-focus-inner{border:0;padding:0}.h1{height:1rem}.h2{height:2rem}.h3{height:4rem}.h4{height:8rem}.h5{height:16rem}.h-25{height:25%}.h-50{height:50%}.h-75{height:75%}.h-100{height:100%}.min-h-100{min-height:100%}.vh-25{height:25vh}.vh-50{height:50vh}.vh-75{height:75vh}.vh-100{height:100vh}.min-vh-100{min-height:100vh}.h-auto{height:auto}.h-inherit{height:inherit}.tracked{letter-spacing:.1em}.tracked-tight{letter-spacing:-.05em}.tracked-mega{letter-spacing:.25em}.lh-solid{line-height:1}.lh-title{line-height:1.25}.lh-copy{line-height:1.5}.link{text-decoration:none}.link,.link:active,.link:focus,.link:hover,.link:link,.link:visited{transition:color .15s ease-in}.link:focus{outline:1px dotted currentColor}.list{list-style-type:none}.mw-100{max-width:100%}.mw1{max-width:1rem}.mw2{max-width:2rem}.mw3{max-width:4rem}.mw4{max-width:8rem}.mw5{max-width:16rem}.mw6{max-width:32rem}.mw7{max-width:48rem}.mw8{max-width:64rem}.mw9{max-width:96rem}.mw-none{max-width:none}.w1{width:1rem}.w2{width:2rem}.w3{width:4rem}.w4{width:8rem}.w5{width:16rem}.w-10{width:10%}.w-20{width:20%}.w-25{width:25%}.w-30{width:30%}.w-33{width:33%}.w-34{width:34%}.w-40{width:40%}.w-50{width:50%}.w-60{width:60%}.w-70{width:70%}.w-75{width:75%}.w-80{width:80%}.w-90{width:90%}.w-100{width:100%}.w-third{width:33.33333%}.w-two-thirds{width:66.66667%}.w-auto{width:auto}.overflow-visible{overflow:visible}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.overflow-auto{overflow:auto}.overflow-x-visible{overflow-x:visible}.overflow-x-hidden{overflow-x:hidden}.overflow-x-scroll{overflow-x:scroll}.overflow-x-auto{overflow-x:auto}.overflow-y-visible{overflow-y:visible}.overflow-y-hidden{overflow-y:hidden}.overflow-y-scroll{overflow-y:scroll}.overflow-y-auto{overflow-y:auto}.static{position:static}.relative{position:relative}.absolute{position:absolute}.fixed{position:fixed}.o-100{opacity:1}.o-90{opacity:.9}.o-80{opacity:.8}.o-70{opacity:.7}.o-60{opacity:.6}.o-50{opacity:.5}.o-40{opacity:.4}.o-30{opacity:.3}.o-20{opacity:.2}.o-10{opacity:.1}.o-05{opacity:.05}.o-025{opacity:.025}.o-0{opacity:0}.rotate-45{-webkit-transform:rotate(45deg);transform:rotate(45deg)}.rotate-90{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.rotate-135{-webkit-transform:rotate(135deg);transform:rotate(135deg)}.rotate-180{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.rotate-225{-webkit-transform:rotate(225deg);transform:rotate(225deg)}.rotate-270{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.rotate-315{-webkit-transform:rotate(315deg);transform:rotate(315deg)}.black-90{color:rgba(0,0,0,.9)}.black-80{color:rgba(0,0,0,.8)}.black-70{color:rgba(0,0,0,.7)}.black-60{color:rgba(0,0,0,.6)}.black-50{color:rgba(0,0,0,.5)}.black-40{color:rgba(0,0,0,.4)}.black-30{color:rgba(0,0,0,.3)}.black-20{color:rgba(0,0,0,.2)}.black-10{color:rgba(0,0,0,.1)}.black-05{color:rgba(0,0,0,.05)}.white-90{color:hsla(0,0%,100%,.9)}.white-80{color:hsla(0,0%,100%,.8)}.white-70{color:hsla(0,0%,100%,.7)}.white-60{color:hsla(0,0%,100%,.6)}.white-50{color:hsla(0,0%,100%,.5)}.white-40{color:hsla(0,0%,100%,.4)}.white-30{color:hsla(0,0%,100%,.3)}.white-20{color:hsla(0,0%,100%,.2)}.white-10{color:hsla(0,0%,100%,.1)}.black{color:#000}.near-black{color:#111}.dark-gray{color:#333}.mid-gray{color:#555}.gray{color:#777}.silver{color:#999}.light-silver{color:#aaa}.moon-gray{color:#ccc}.light-gray{color:#eee}.near-white{color:#f4f4f4}.white{color:#fff}.dark-red{color:#e7040f}.red{color:#ff4136}.light-red{color:#ff725c}.orange{color:#ff6300}.gold{color:#ffb700}.yellow{color:gold}.light-yellow{color:#fbf1a9}.purple{color:#5e2ca5}.light-purple{color:#a463f2}.dark-pink{color:#d5008f}.hot-pink{color:#ff41b4}.pink{color:#ff80cc}.light-pink{color:#ffa3d7}.dark-green{color:#137752}.green{color:#19a974}.light-green{color:#9eebcf}.navy{color:#001b44}.dark-blue{color:#00449e}.blue{color:#357edd}.light-blue{color:#96ccff}.lightest-blue{color:#cdecff}.washed-blue{color:#f6fffe}.washed-green{color:#e8fdf5}.washed-yellow{color:#fffceb}.washed-red{color:#ffdfdf}.color-inherit{color:inherit}.bg-black-90{background-color:rgba(0,0,0,.9)}.bg-black-80{background-color:rgba(0,0,0,.8)}.bg-black-70{background-color:rgba(0,0,0,.7)}.bg-black-60{background-color:rgba(0,0,0,.6)}.bg-black-50{background-color:rgba(0,0,0,.5)}.bg-black-40{background-color:rgba(0,0,0,.4)}.bg-black-30{background-color:rgba(0,0,0,.3)}.bg-black-20{background-color:rgba(0,0,0,.2)}.bg-black-10{background-color:rgba(0,0,0,.1)}.bg-black-05{background-color:rgba(0,0,0,.05)}.bg-white-90{background-color:hsla(0,0%,100%,.9)}.bg-white-80{background-color:hsla(0,0%,100%,.8)}.bg-white-70{background-color:hsla(0,0%,100%,.7)}.bg-white-60{background-color:hsla(0,0%,100%,.6)}.bg-white-50{background-color:hsla(0,0%,100%,.5)}.bg-white-40{background-color:hsla(0,0%,100%,.4)}.bg-white-30{background-color:hsla(0,0%,100%,.3)}.bg-white-20{background-color:hsla(0,0%,100%,.2)}.bg-white-10{background-color:hsla(0,0%,100%,.1)}.bg-black{background-color:#000}.bg-near-black{background-color:#111}.bg-dark-gray{background-color:#333}.bg-mid-gray{background-color:#555}.bg-gray{background-color:#777}.bg-silver{background-color:#999}.bg-light-silver{background-color:#aaa}.bg-moon-gray{background-color:#ccc}.bg-light-gray{background-color:#eee}.bg-near-white{background-color:#f4f4f4}.bg-white{background-color:#fff}.bg-transparent{background-color:transparent}.bg-dark-red{background-color:#e7040f}.bg-red{background-color:#ff4136}.bg-light-red{background-color:#ff725c}.bg-orange{background-color:#ff6300}.bg-gold{background-color:#ffb700}.bg-yellow{background-color:gold}.bg-light-yellow{background-color:#fbf1a9}.bg-purple{background-color:#5e2ca5}.bg-light-purple{background-color:#a463f2}.bg-dark-pink{background-color:#d5008f}.bg-hot-pink{background-color:#ff41b4}.bg-pink{background-color:#ff80cc}.bg-light-pink{background-color:#ffa3d7}.bg-dark-green{background-color:#137752}.bg-green{background-color:#19a974}.bg-light-green{background-color:#9eebcf}.bg-navy{background-color:#001b44}.bg-dark-blue{background-color:#00449e}.bg-blue{background-color:#357edd}.bg-light-blue{background-color:#96ccff}.bg-lightest-blue{background-color:#cdecff}.bg-washed-blue{background-color:#f6fffe}.bg-washed-green{background-color:#e8fdf5}.bg-washed-yellow{background-color:#fffceb}.bg-washed-red{background-color:#ffdfdf}.bg-inherit{background-color:inherit}.hover-black:focus,.hover-black:hover{color:#000}.hover-near-black:focus,.hover-near-black:hover{color:#111}.hover-dark-gray:focus,.hover-dark-gray:hover{color:#333}.hover-mid-gray:focus,.hover-mid-gray:hover{color:#555}.hover-gray:focus,.hover-gray:hover{color:#777}.hover-silver:focus,.hover-silver:hover{color:#999}.hover-light-silver:focus,.hover-light-silver:hover{color:#aaa}.hover-moon-gray:focus,.hover-moon-gray:hover{color:#ccc}.hover-light-gray:focus,.hover-light-gray:hover{color:#eee}.hover-near-white:focus,.hover-near-white:hover{color:#f4f4f4}.hover-white:focus,.hover-white:hover{color:#fff}.hover-black-90:focus,.hover-black-90:hover{color:rgba(0,0,0,.9)}.hover-black-80:focus,.hover-black-80:hover{color:rgba(0,0,0,.8)}.hover-black-70:focus,.hover-black-70:hover{color:rgba(0,0,0,.7)}.hover-black-60:focus,.hover-black-60:hover{color:rgba(0,0,0,.6)}.hover-black-50:focus,.hover-black-50:hover{color:rgba(0,0,0,.5)}.hover-black-40:focus,.hover-black-40:hover{color:rgba(0,0,0,.4)}.hover-black-30:focus,.hover-black-30:hover{color:rgba(0,0,0,.3)}.hover-black-20:focus,.hover-black-20:hover{color:rgba(0,0,0,.2)}.hover-black-10:focus,.hover-black-10:hover{color:rgba(0,0,0,.1)}.hover-white-90:focus,.hover-white-90:hover{color:hsla(0,0%,100%,.9)}.hover-white-80:focus,.hover-white-80:hover{color:hsla(0,0%,100%,.8)}.hover-white-70:focus,.hover-white-70:hover{color:hsla(0,0%,100%,.7)}.hover-white-60:focus,.hover-white-60:hover{color:hsla(0,0%,100%,.6)}.hover-white-50:focus,.hover-white-50:hover{color:hsla(0,0%,100%,.5)}.hover-white-40:focus,.hover-white-40:hover{color:hsla(0,0%,100%,.4)}.hover-white-30:focus,.hover-white-30:hover{color:hsla(0,0%,100%,.3)}.hover-white-20:focus,.hover-white-20:hover{color:hsla(0,0%,100%,.2)}.hover-white-10:focus,.hover-white-10:hover{color:hsla(0,0%,100%,.1)}.hover-inherit:focus,.hover-inherit:hover{color:inherit}.hover-bg-black:focus,.hover-bg-black:hover{background-color:#000}.hover-bg-near-black:focus,.hover-bg-near-black:hover{background-color:#111}.hover-bg-dark-gray:focus,.hover-bg-dark-gray:hover{background-color:#333}.hover-bg-mid-gray:focus,.hover-bg-mid-gray:hover{background-color:#555}.hover-bg-gray:focus,.hover-bg-gray:hover{background-color:#777}.hover-bg-silver:focus,.hover-bg-silver:hover{background-color:#999}.hover-bg-light-silver:focus,.hover-bg-light-silver:hover{background-color:#aaa}.hover-bg-moon-gray:focus,.hover-bg-moon-gray:hover{background-color:#ccc}.hover-bg-light-gray:focus,.hover-bg-light-gray:hover{background-color:#eee}.hover-bg-near-white:focus,.hover-bg-near-white:hover{background-color:#f4f4f4}.hover-bg-white:focus,.hover-bg-white:hover{background-color:#fff}.hover-bg-transparent:focus,.hover-bg-transparent:hover{background-color:transparent}.hover-bg-black-90:focus,.hover-bg-black-90:hover{background-color:rgba(0,0,0,.9)}.hover-bg-black-80:focus,.hover-bg-black-80:hover{background-color:rgba(0,0,0,.8)}.hover-bg-black-70:focus,.hover-bg-black-70:hover{background-color:rgba(0,0,0,.7)}.hover-bg-black-60:focus,.hover-bg-black-60:hover{background-color:rgba(0,0,0,.6)}.hover-bg-black-50:focus,.hover-bg-black-50:hover{background-color:rgba(0,0,0,.5)}.hover-bg-black-40:focus,.hover-bg-black-40:hover{background-color:rgba(0,0,0,.4)}.hover-bg-black-30:focus,.hover-bg-black-30:hover{background-color:rgba(0,0,0,.3)}.hover-bg-black-20:focus,.hover-bg-black-20:hover{background-color:rgba(0,0,0,.2)}.hover-bg-black-10:focus,.hover-bg-black-10:hover{background-color:rgba(0,0,0,.1)}.hover-bg-white-90:focus,.hover-bg-white-90:hover{background-color:hsla(0,0%,100%,.9)}.hover-bg-white-80:focus,.hover-bg-white-80:hover{background-color:hsla(0,0%,100%,.8)}.hover-bg-white-70:focus,.hover-bg-white-70:hover{background-color:hsla(0,0%,100%,.7)}.hover-bg-white-60:focus,.hover-bg-white-60:hover{background-color:hsla(0,0%,100%,.6)}.hover-bg-white-50:focus,.hover-bg-white-50:hover{background-color:hsla(0,0%,100%,.5)}.hover-bg-white-40:focus,.hover-bg-white-40:hover{background-color:hsla(0,0%,100%,.4)}.hover-bg-white-30:focus,.hover-bg-white-30:hover{background-color:hsla(0,0%,100%,.3)}.hover-bg-white-20:focus,.hover-bg-white-20:hover{background-color:hsla(0,0%,100%,.2)}.hover-bg-white-10:focus,.hover-bg-white-10:hover{background-color:hsla(0,0%,100%,.1)}.hover-dark-red:focus,.hover-dark-red:hover{color:#e7040f}.hover-red:focus,.hover-red:hover{color:#ff4136}.hover-light-red:focus,.hover-light-red:hover{color:#ff725c}.hover-orange:focus,.hover-orange:hover{color:#ff6300}.hover-gold:focus,.hover-gold:hover{color:#ffb700}.hover-yellow:focus,.hover-yellow:hover{color:gold}.hover-light-yellow:focus,.hover-light-yellow:hover{color:#fbf1a9}.hover-purple:focus,.hover-purple:hover{color:#5e2ca5}.hover-light-purple:focus,.hover-light-purple:hover{color:#a463f2}.hover-dark-pink:focus,.hover-dark-pink:hover{color:#d5008f}.hover-hot-pink:focus,.hover-hot-pink:hover{color:#ff41b4}.hover-pink:focus,.hover-pink:hover{color:#ff80cc}.hover-light-pink:focus,.hover-light-pink:hover{color:#ffa3d7}.hover-dark-green:focus,.hover-dark-green:hover{color:#137752}.hover-green:focus,.hover-green:hover{color:#19a974}.hover-light-green:focus,.hover-light-green:hover{color:#9eebcf}.hover-navy:focus,.hover-navy:hover{color:#001b44}.hover-dark-blue:focus,.hover-dark-blue:hover{color:#00449e}.hover-blue:focus,.hover-blue:hover{color:#357edd}.hover-light-blue:focus,.hover-light-blue:hover{color:#96ccff}.hover-lightest-blue:focus,.hover-lightest-blue:hover{color:#cdecff}.hover-washed-blue:focus,.hover-washed-blue:hover{color:#f6fffe}.hover-washed-green:focus,.hover-washed-green:hover{color:#e8fdf5}.hover-washed-yellow:focus,.hover-washed-yellow:hover{color:#fffceb}.hover-washed-red:focus,.hover-washed-red:hover{color:#ffdfdf}.hover-bg-dark-red:focus,.hover-bg-dark-red:hover{background-color:#e7040f}.hover-bg-red:focus,.hover-bg-red:hover{background-color:#ff4136}.hover-bg-light-red:focus,.hover-bg-light-red:hover{background-color:#ff725c}.hover-bg-orange:focus,.hover-bg-orange:hover{background-color:#ff6300}.hover-bg-gold:focus,.hover-bg-gold:hover{background-color:#ffb700}.hover-bg-yellow:focus,.hover-bg-yellow:hover{background-color:gold}.hover-bg-light-yellow:focus,.hover-bg-light-yellow:hover{background-color:#fbf1a9}.hover-bg-purple:focus,.hover-bg-purple:hover{background-color:#5e2ca5}.hover-bg-light-purple:focus,.hover-bg-light-purple:hover{background-color:#a463f2}.hover-bg-dark-pink:focus,.hover-bg-dark-pink:hover{background-color:#d5008f}.hover-bg-hot-pink:focus,.hover-bg-hot-pink:hover{background-color:#ff41b4}.hover-bg-pink:focus,.hover-bg-pink:hover{background-color:#ff80cc}.hover-bg-light-pink:focus,.hover-bg-light-pink:hover{background-color:#ffa3d7}.hover-bg-dark-green:focus,.hover-bg-dark-green:hover{background-color:#137752}.hover-bg-green:focus,.hover-bg-green:hover{background-color:#19a974}.hover-bg-light-green:focus,.hover-bg-light-green:hover{background-color:#9eebcf}.hover-bg-navy:focus,.hover-bg-navy:hover{background-color:#001b44}.hover-bg-dark-blue:focus,.hover-bg-dark-blue:hover{background-color:#00449e}.hover-bg-blue:focus,.hover-bg-blue:hover{background-color:#357edd}.hover-bg-light-blue:focus,.hover-bg-light-blue:hover{background-color:#96ccff}.hover-bg-lightest-blue:focus,.hover-bg-lightest-blue:hover{background-color:#cdecff}.hover-bg-washed-blue:focus,.hover-bg-washed-blue:hover{background-color:#f6fffe}.hover-bg-washed-green:focus,.hover-bg-washed-green:hover{background-color:#e8fdf5}.hover-bg-washed-yellow:focus,.hover-bg-washed-yellow:hover{background-color:#fffceb}.hover-bg-washed-red:focus,.hover-bg-washed-red:hover{background-color:#ffdfdf}.hover-bg-inherit:focus,.hover-bg-inherit:hover{background-color:inherit}.pa0{padding:0}.pa1{padding:.25rem}.pa2{padding:.5rem}.pa3{padding:1rem}.pa4{padding:2rem}.pa5{padding:4rem}.pa6{padding:8rem}.pa7{padding:16rem}.pl0{padding-left:0}.pl1{padding-left:.25rem}.pl2{padding-left:.5rem}.pl3{padding-left:1rem}.pl4{padding-left:2rem}.pl5{padding-left:4rem}.pl6{padding-left:8rem}.pl7{padding-left:16rem}.pr0{padding-right:0}.pr1{padding-right:.25rem}.pr2{padding-right:.5rem}.pr3{padding-right:1rem}.pr4{padding-right:2rem}.pr5{padding-right:4rem}.pr6{padding-right:8rem}.pr7{padding-right:16rem}.pb0{padding-bottom:0}.pb1{padding-bottom:.25rem}.pb2{padding-bottom:.5rem}.pb3{padding-bottom:1rem}.pb4{padding-bottom:2rem}.pb5{padding-bottom:4rem}.pb6{padding-bottom:8rem}.pb7{padding-bottom:16rem}.pt0{padding-top:0}.pt1{padding-top:.25rem}.pt2{padding-top:.5rem}.pt3{padding-top:1rem}.pt4{padding-top:2rem}.pt5{padding-top:4rem}.pt6{padding-top:8rem}.pt7{padding-top:16rem}.pv0{padding-top:0;padding-bottom:0}.pv1{padding-top:.25rem;padding-bottom:.25rem}.pv2{padding-top:.5rem;padding-bottom:.5rem}.pv3{padding-top:1rem;padding-bottom:1rem}.pv4{padding-top:2rem;padding-bottom:2rem}.pv5{padding-top:4rem;padding-bottom:4rem}.pv6{padding-top:8rem;padding-bottom:8rem}.pv7{padding-top:16rem;padding-bottom:16rem}.ph0{padding-left:0;padding-right:0}.ph1{padding-left:.25rem;padding-right:.25rem}.ph2{padding-left:.5rem;padding-right:.5rem}.ph3{padding-left:1rem;padding-right:1rem}.ph4{padding-left:2rem;padding-right:2rem}.ph5{padding-left:4rem;padding-right:4rem}.ph6{padding-left:8rem;padding-right:8rem}.ph7{padding-left:16rem;padding-right:16rem}.ma0{margin:0}.ma1{margin:.25rem}.ma2{margin:.5rem}.ma3{margin:1rem}.ma4{margin:2rem}.ma5{margin:4rem}.ma6{margin:8rem}.ma7{margin:16rem}.ml0{margin-left:0}.ml1{margin-left:.25rem}.ml2{margin-left:.5rem}.ml3{margin-left:1rem}.ml4{margin-left:2rem}.ml5{margin-left:4rem}.ml6{margin-left:8rem}.ml7{margin-left:16rem}.mr0{margin-right:0}.mr1{margin-right:.25rem}.mr2{margin-right:.5rem}.mr3{margin-right:1rem}.mr4{margin-right:2rem}.mr5{margin-right:4rem}.mr6{margin-right:8rem}.mr7{margin-right:16rem}.mb0{margin-bottom:0}.mb1{margin-bottom:.25rem}.mb2{margin-bottom:.5rem}.mb3{margin-bottom:1rem}.mb4{margin-bottom:2rem}.mb5{margin-bottom:4rem}.mb6{margin-bottom:8rem}.mb7{margin-bottom:16rem}.mt0{margin-top:0}.mt1{margin-top:.25rem}.mt2{margin-top:.5rem}.mt3{margin-top:1rem}.mt4{margin-top:2rem}.mt5{margin-top:4rem}.mt6{margin-top:8rem}.mt7{margin-top:16rem}.mv0{margin-top:0;margin-bottom:0}.mv1{margin-top:.25rem;margin-bottom:.25rem}.mv2{margin-top:.5rem;margin-bottom:.5rem}.mv3{margin-top:1rem;margin-bottom:1rem}.mv4{margin-top:2rem;margin-bottom:2rem}.mv5{margin-top:4rem;margin-bottom:4rem}.mv6{margin-top:8rem;margin-bottom:8rem}.mv7{margin-top:16rem;margin-bottom:16rem}.mh0{margin-left:0;margin-right:0}.mh1{margin-left:.25rem;margin-right:.25rem}.mh2{margin-left:.5rem;margin-right:.5rem}.mh3{margin-left:1rem;margin-right:1rem}.mh4{margin-left:2rem;margin-right:2rem}.mh5{margin-left:4rem;margin-right:4rem}.mh6{margin-left:8rem;margin-right:8rem}.mh7{margin-left:16rem;margin-right:16rem}.na1{margin:-.25rem}.na2{margin:-.5rem}.na3{margin:-1rem}.na4{margin:-2rem}.na5{margin:-4rem}.na6{margin:-8rem}.na7{margin:-16rem}.nl1{margin-left:-.25rem}.nl2{margin-left:-.5rem}.nl3{margin-left:-1rem}.nl4{margin-left:-2rem}.nl5{margin-left:-4rem}.nl6{margin-left:-8rem}.nl7{margin-left:-16rem}.nr1{margin-right:-.25rem}.nr2{margin-right:-.5rem}.nr3{margin-right:-1rem}.nr4{margin-right:-2rem}.nr5{margin-right:-4rem}.nr6{margin-right:-8rem}.nr7{margin-right:-16rem}.nb1{margin-bottom:-.25rem}.nb2{margin-bottom:-.5rem}.nb3{margin-bottom:-1rem}.nb4{margin-bottom:-2rem}.nb5{margin-bottom:-4rem}.nb6{margin-bottom:-8rem}.nb7{margin-bottom:-16rem}.nt1{margin-top:-.25rem}.nt2{margin-top:-.5rem}.nt3{margin-top:-1rem}.nt4{margin-top:-2rem}.nt5{margin-top:-4rem}.nt6{margin-top:-8rem}.nt7{margin-top:-16rem}.collapse{border-collapse:collapse;border-spacing:0}.striped--light-silver:nth-child(odd){background-color:#aaa}.striped--moon-gray:nth-child(odd){background-color:#ccc}.striped--light-gray:nth-child(odd){background-color:#eee}.striped--near-white:nth-child(odd){background-color:#f4f4f4}.stripe-light:nth-child(odd){background-color:hsla(0,0%,100%,.1)}.stripe-dark:nth-child(odd){background-color:rgba(0,0,0,.1)}.strike{text-decoration:line-through}.underline{text-decoration:underline}.no-underline{text-decoration:none}.tl{text-align:left}.tr{text-align:right}.tc{text-align:center}.tj{text-align:justify}.ttc{text-transform:capitalize}.ttl{text-transform:lowercase}.ttu{text-transform:uppercase}.ttn{text-transform:none}.f-6,.f-headline{font-size:6rem}.f-5,.f-subheadline{font-size:5rem}.f1{font-size:3rem}.f2{font-size:2.25rem}.f3{font-size:1.5rem}.f4{font-size:1.25rem}.f5{font-size:1rem}.f6{font-size:.875rem}.f7{font-size:.75rem}.measure{max-width:30em}.measure-wide{max-width:34em}.measure-narrow{max-width:20em}.indent{text-indent:1em;margin-top:0;margin-bottom:0}.small-caps{font-variant:small-caps}.truncate{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.overflow-container{overflow-y:scroll}.center{margin-left:auto}.center,.mr-auto{margin-right:auto}.ml-auto{margin-left:auto}.clip{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}.ws-normal{white-space:normal}.nowrap{white-space:nowrap}.pre{white-space:pre}.v-base{vertical-align:baseline}.v-mid{vertical-align:middle}.v-top{vertical-align:top}.v-btm{vertical-align:bottom}.dim{opacity:1}.dim,.dim:focus,.dim:hover{transition:opacity .15s ease-in}.dim:focus,.dim:hover{opacity:.5}.dim:active{opacity:.8;transition:opacity .15s ease-out}.glow,.glow:focus,.glow:hover{transition:opacity .15s ease-in}.glow:focus,.glow:hover{opacity:1}.hide-child .child{opacity:0;transition:opacity .15s ease-in}.hide-child:active .child,.hide-child:focus .child,.hide-child:hover .child{opacity:1;transition:opacity .15s ease-in}.underline-hover:focus,.underline-hover:hover{text-decoration:underline}.grow{-moz-osx-font-smoothing:grayscale;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform:translateZ(0);transform:translateZ(0);transition:-webkit-transform .25s ease-out;transition:transform .25s ease-out;transition:transform .25s ease-out,-webkit-transform .25s ease-out}.grow:focus,.grow:hover{-webkit-transform:scale(1.05);transform:scale(1.05)}.grow:active{-webkit-transform:scale(.9);transform:scale(.9)}.grow-large{-moz-osx-font-smoothing:grayscale;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform:translateZ(0);transform:translateZ(0);transition:-webkit-transform .25s ease-in-out;transition:transform .25s ease-in-out;transition:transform .25s ease-in-out,-webkit-transform .25s ease-in-out}.grow-large:focus,.grow-large:hover{-webkit-transform:scale(1.2);transform:scale(1.2)}.grow-large:active{-webkit-transform:scale(.95);transform:scale(.95)}.pointer:hover,.shadow-hover{cursor:pointer}.shadow-hover{position:relative;transition:all .5s cubic-bezier(.165,.84,.44,1)}.shadow-hover:after{content:"";box-shadow:0 0 16px 2px rgba(0,0,0,.2);border-radius:inherit;opacity:0;position:absolute;top:0;left:0;width:100%;height:100%;z-index:-1;transition:opacity .5s cubic-bezier(.165,.84,.44,1)}.shadow-hover:focus:after,.shadow-hover:hover:after{opacity:1}.bg-animate,.bg-animate:focus,.bg-animate:hover{transition:background-color .15s ease-in-out}.z-0{z-index:0}.z-1{z-index:1}.z-2{z-index:2}.z-3{z-index:3}.z-4{z-index:4}.z-5{z-index:5}.z-999{z-index:999}.z-9999{z-index:9999}.z-max{z-index:2147483647}.z-inherit{z-index:inherit}.z-initial{z-index:auto}.z-unset{z-index:unset}.nested-copy-line-height ol,.nested-copy-line-height p,.nested-copy-line-height ul{line-height:1.5}.nested-headline-line-height h1,.nested-headline-line-height h2,.nested-headline-line-height h3,.nested-headline-line-height h4,.nested-headline-line-height h5,.nested-headline-line-height h6{line-height:1.25}.nested-list-reset ol,.nested-list-reset ul{padding-left:0;margin-left:0;list-style-type:none}.nested-copy-indent p+p{text-indent:1em;margin-top:0;margin-bottom:0}.nested-copy-separator p+p{margin-top:1.5em}.nested-img img{width:100%;max-width:100%;display:block}.nested-links a{color:#357edd;transition:color .15s ease-in}.nested-links a:focus,.nested-links a:hover{color:#96ccff;transition:color .15s ease-in}.debug *{outline:1px solid gold}.debug-white *{outline:1px solid #fff}.debug-black *{outline:1px solid #000}.debug-grid{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAFElEQVR4AWPAC97/9x0eCsAEPgwAVLshdpENIxcAAAAASUVORK5CYII=) repeat 0 0}.debug-grid-16{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMklEQVR4AWOgCLz/b0epAa6UGuBOqQHOQHLUgFEDnAbcBZ4UGwDOkiCnkIhdgNgNxAYAiYlD+8sEuo8AAAAASUVORK5CYII=) repeat 0 0}.debug-grid-8-solid{background:#fff url(data:image/gif;base64,R0lGODdhCAAIAPEAAADw/wDx/////wAAACwAAAAACAAIAAACDZQvgaeb/lxbAIKA8y0AOw==) repeat 0 0}.debug-grid-16-solid{background:#fff url(data:image/gif;base64,R0lGODdhEAAQAPEAAADw/wDx/xXy/////ywAAAAAEAAQAAACIZyPKckYDQFsb6ZqD85jZ2+BkwiRFKehhqQCQgDHcgwEBQA7) repeat 0 0}@media screen and (min-width:30em){.aspect-ratio-ns{height:0;position:relative}.aspect-ratio--16x9-ns{padding-bottom:56.25%}.aspect-ratio--9x16-ns{padding-bottom:177.77%}.aspect-ratio--4x3-ns{padding-bottom:75%}.aspect-ratio--3x4-ns{padding-bottom:133.33%}.aspect-ratio--6x4-ns{padding-bottom:66.6%}.aspect-ratio--4x6-ns{padding-bottom:150%}.aspect-ratio--8x5-ns{padding-bottom:62.5%}.aspect-ratio--5x8-ns{padding-bottom:160%}.aspect-ratio--7x5-ns{padding-bottom:71.42%}.aspect-ratio--5x7-ns{padding-bottom:140%}.aspect-ratio--1x1-ns{padding-bottom:100%}.aspect-ratio--object-ns{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;z-index:100}.cover-ns{background-size:cover!important}.contain-ns{background-size:contain!important}.bg-center-ns{background-position:50%}.bg-center-ns,.bg-top-ns{background-repeat:no-repeat}.bg-top-ns{background-position:top}.bg-right-ns{background-position:100%}.bg-bottom-ns,.bg-right-ns{background-repeat:no-repeat}.bg-bottom-ns{background-position:bottom}.bg-left-ns{background-repeat:no-repeat;background-position:0}.outline-ns{outline:1px solid}.outline-transparent-ns{outline:1px solid transparent}.outline-0-ns{outline:0}.ba-ns{border-style:solid;border-width:1px}.bt-ns{border-top-style:solid;border-top-width:1px}.br-ns{border-right-style:solid;border-right-width:1px}.bb-ns{border-bottom-style:solid;border-bottom-width:1px}.bl-ns{border-left-style:solid;border-left-width:1px}.bn-ns{border-style:none;border-width:0}.br0-ns{border-radius:0}.br1-ns{border-radius:.125rem}.br2-ns{border-radius:.25rem}.br3-ns{border-radius:.5rem}.br4-ns{border-radius:1rem}.br-100-ns{border-radius:100%}.br-pill-ns{border-radius:9999px}.br--bottom-ns{border-top-left-radius:0;border-top-right-radius:0}.br--top-ns{border-bottom-right-radius:0}.br--right-ns,.br--top-ns{border-bottom-left-radius:0}.br--right-ns{border-top-left-radius:0}.br--left-ns{border-top-right-radius:0;border-bottom-right-radius:0}.br-inherit-ns{border-radius:inherit}.br-initial-ns{border-radius:initial}.br-unset-ns{border-radius:unset}.b--dotted-ns{border-style:dotted}.b--dashed-ns{border-style:dashed}.b--solid-ns{border-style:solid}.b--none-ns{border-style:none}.bw0-ns{border-width:0}.bw1-ns{border-width:.125rem}.bw2-ns{border-width:.25rem}.bw3-ns{border-width:.5rem}.bw4-ns{border-width:1rem}.bw5-ns{border-width:2rem}.bt-0-ns{border-top-width:0}.br-0-ns{border-right-width:0}.bb-0-ns{border-bottom-width:0}.bl-0-ns{border-left-width:0}.shadow-1-ns{box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.shadow-2-ns{box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.shadow-3-ns{box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.shadow-4-ns{box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.shadow-5-ns{box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}.top-0-ns{top:0}.left-0-ns{left:0}.right-0-ns{right:0}.bottom-0-ns{bottom:0}.top-1-ns{top:1rem}.left-1-ns{left:1rem}.right-1-ns{right:1rem}.bottom-1-ns{bottom:1rem}.top-2-ns{top:2rem}.left-2-ns{left:2rem}.right-2-ns{right:2rem}.bottom-2-ns{bottom:2rem}.top--1-ns{top:-1rem}.right--1-ns{right:-1rem}.bottom--1-ns{bottom:-1rem}.left--1-ns{left:-1rem}.top--2-ns{top:-2rem}.right--2-ns{right:-2rem}.bottom--2-ns{bottom:-2rem}.left--2-ns{left:-2rem}.absolute--fill-ns{top:0;right:0;bottom:0;left:0}.cl-ns{clear:left}.cr-ns{clear:right}.cb-ns{clear:both}.cn-ns{clear:none}.dn-ns{display:none}.di-ns{display:inline}.db-ns{display:block}.dib-ns{display:inline-block}.dit-ns{display:inline-table}.dt-ns{display:table}.dtc-ns{display:table-cell}.dt-row-ns{display:table-row}.dt-row-group-ns{display:table-row-group}.dt-column-ns{display:table-column}.dt-column-group-ns{display:table-column-group}.dt--fixed-ns{table-layout:fixed;width:100%}.flex-ns{display:flex}.inline-flex-ns{display:inline-flex}.flex-auto-ns{flex:1 1 auto;min-width:0;min-height:0}.flex-none-ns{flex:none}.flex-column-ns{flex-direction:column}.flex-row-ns{flex-direction:row}.flex-wrap-ns{flex-wrap:wrap}.flex-nowrap-ns{flex-wrap:nowrap}.flex-wrap-reverse-ns{flex-wrap:wrap-reverse}.flex-column-reverse-ns{flex-direction:column-reverse}.flex-row-reverse-ns{flex-direction:row-reverse}.items-start-ns{align-items:flex-start}.items-end-ns{align-items:flex-end}.items-center-ns{align-items:center}.items-baseline-ns{align-items:baseline}.items-stretch-ns{align-items:stretch}.self-start-ns{align-self:flex-start}.self-end-ns{align-self:flex-end}.self-center-ns{align-self:center}.self-baseline-ns{align-self:baseline}.self-stretch-ns{align-self:stretch}.justify-start-ns{justify-content:flex-start}.justify-end-ns{justify-content:flex-end}.justify-center-ns{justify-content:center}.justify-between-ns{justify-content:space-between}.justify-around-ns{justify-content:space-around}.content-start-ns{align-content:flex-start}.content-end-ns{align-content:flex-end}.content-center-ns{align-content:center}.content-between-ns{align-content:space-between}.content-around-ns{align-content:space-around}.content-stretch-ns{align-content:stretch}.order-0-ns{order:0}.order-1-ns{order:1}.order-2-ns{order:2}.order-3-ns{order:3}.order-4-ns{order:4}.order-5-ns{order:5}.order-6-ns{order:6}.order-7-ns{order:7}.order-8-ns{order:8}.order-last-ns{order:99999}.flex-grow-0-ns{flex-grow:0}.flex-grow-1-ns{flex-grow:1}.flex-shrink-0-ns{flex-shrink:0}.flex-shrink-1-ns{flex-shrink:1}.fl-ns{float:left}.fl-ns,.fr-ns{_display:inline}.fr-ns{float:right}.fn-ns{float:none}.i-ns{font-style:italic}.fs-normal-ns{font-style:normal}.normal-ns{font-weight:400}.b-ns{font-weight:700}.fw1-ns{font-weight:100}.fw2-ns{font-weight:200}.fw3-ns{font-weight:300}.fw4-ns{font-weight:400}.fw5-ns{font-weight:500}.fw6-ns{font-weight:600}.fw7-ns{font-weight:700}.fw8-ns{font-weight:800}.fw9-ns{font-weight:900}.h1-ns{height:1rem}.h2-ns{height:2rem}.h3-ns{height:4rem}.h4-ns{height:8rem}.h5-ns{height:16rem}.h-25-ns{height:25%}.h-50-ns{height:50%}.h-75-ns{height:75%}.h-100-ns{height:100%}.min-h-100-ns{min-height:100%}.vh-25-ns{height:25vh}.vh-50-ns{height:50vh}.vh-75-ns{height:75vh}.vh-100-ns{height:100vh}.min-vh-100-ns{min-height:100vh}.h-auto-ns{height:auto}.h-inherit-ns{height:inherit}.tracked-ns{letter-spacing:.1em}.tracked-tight-ns{letter-spacing:-.05em}.tracked-mega-ns{letter-spacing:.25em}.lh-solid-ns{line-height:1}.lh-title-ns{line-height:1.25}.lh-copy-ns{line-height:1.5}.mw-100-ns{max-width:100%}.mw1-ns{max-width:1rem}.mw2-ns{max-width:2rem}.mw3-ns{max-width:4rem}.mw4-ns{max-width:8rem}.mw5-ns{max-width:16rem}.mw6-ns{max-width:32rem}.mw7-ns{max-width:48rem}.mw8-ns{max-width:64rem}.mw9-ns{max-width:96rem}.mw-none-ns{max-width:none}.w1-ns{width:1rem}.w2-ns{width:2rem}.w3-ns{width:4rem}.w4-ns{width:8rem}.w5-ns{width:16rem}.w-10-ns{width:10%}.w-20-ns{width:20%}.w-25-ns{width:25%}.w-30-ns{width:30%}.w-33-ns{width:33%}.w-34-ns{width:34%}.w-40-ns{width:40%}.w-50-ns{width:50%}.w-60-ns{width:60%}.w-70-ns{width:70%}.w-75-ns{width:75%}.w-80-ns{width:80%}.w-90-ns{width:90%}.w-100-ns{width:100%}.w-third-ns{width:33.33333%}.w-two-thirds-ns{width:66.66667%}.w-auto-ns{width:auto}.overflow-visible-ns{overflow:visible}.overflow-hidden-ns{overflow:hidden}.overflow-scroll-ns{overflow:scroll}.overflow-auto-ns{overflow:auto}.overflow-x-visible-ns{overflow-x:visible}.overflow-x-hidden-ns{overflow-x:hidden}.overflow-x-scroll-ns{overflow-x:scroll}.overflow-x-auto-ns{overflow-x:auto}.overflow-y-visible-ns{overflow-y:visible}.overflow-y-hidden-ns{overflow-y:hidden}.overflow-y-scroll-ns{overflow-y:scroll}.overflow-y-auto-ns{overflow-y:auto}.static-ns{position:static}.relative-ns{position:relative}.absolute-ns{position:absolute}.fixed-ns{position:fixed}.rotate-45-ns{-webkit-transform:rotate(45deg);transform:rotate(45deg)}.rotate-90-ns{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.rotate-135-ns{-webkit-transform:rotate(135deg);transform:rotate(135deg)}.rotate-180-ns{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.rotate-225-ns{-webkit-transform:rotate(225deg);transform:rotate(225deg)}.rotate-270-ns{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.rotate-315-ns{-webkit-transform:rotate(315deg);transform:rotate(315deg)}.pa0-ns{padding:0}.pa1-ns{padding:.25rem}.pa2-ns{padding:.5rem}.pa3-ns{padding:1rem}.pa4-ns{padding:2rem}.pa5-ns{padding:4rem}.pa6-ns{padding:8rem}.pa7-ns{padding:16rem}.pl0-ns{padding-left:0}.pl1-ns{padding-left:.25rem}.pl2-ns{padding-left:.5rem}.pl3-ns{padding-left:1rem}.pl4-ns{padding-left:2rem}.pl5-ns{padding-left:4rem}.pl6-ns{padding-left:8rem}.pl7-ns{padding-left:16rem}.pr0-ns{padding-right:0}.pr1-ns{padding-right:.25rem}.pr2-ns{padding-right:.5rem}.pr3-ns{padding-right:1rem}.pr4-ns{padding-right:2rem}.pr5-ns{padding-right:4rem}.pr6-ns{padding-right:8rem}.pr7-ns{padding-right:16rem}.pb0-ns{padding-bottom:0}.pb1-ns{padding-bottom:.25rem}.pb2-ns{padding-bottom:.5rem}.pb3-ns{padding-bottom:1rem}.pb4-ns{padding-bottom:2rem}.pb5-ns{padding-bottom:4rem}.pb6-ns{padding-bottom:8rem}.pb7-ns{padding-bottom:16rem}.pt0-ns{padding-top:0}.pt1-ns{padding-top:.25rem}.pt2-ns{padding-top:.5rem}.pt3-ns{padding-top:1rem}.pt4-ns{padding-top:2rem}.pt5-ns{padding-top:4rem}.pt6-ns{padding-top:8rem}.pt7-ns{padding-top:16rem}.pv0-ns{padding-top:0;padding-bottom:0}.pv1-ns{padding-top:.25rem;padding-bottom:.25rem}.pv2-ns{padding-top:.5rem;padding-bottom:.5rem}.pv3-ns{padding-top:1rem;padding-bottom:1rem}.pv4-ns{padding-top:2rem;padding-bottom:2rem}.pv5-ns{padding-top:4rem;padding-bottom:4rem}.pv6-ns{padding-top:8rem;padding-bottom:8rem}.pv7-ns{padding-top:16rem;padding-bottom:16rem}.ph0-ns{padding-left:0;padding-right:0}.ph1-ns{padding-left:.25rem;padding-right:.25rem}.ph2-ns{padding-left:.5rem;padding-right:.5rem}.ph3-ns{padding-left:1rem;padding-right:1rem}.ph4-ns{padding-left:2rem;padding-right:2rem}.ph5-ns{padding-left:4rem;padding-right:4rem}.ph6-ns{padding-left:8rem;padding-right:8rem}.ph7-ns{padding-left:16rem;padding-right:16rem}.ma0-ns{margin:0}.ma1-ns{margin:.25rem}.ma2-ns{margin:.5rem}.ma3-ns{margin:1rem}.ma4-ns{margin:2rem}.ma5-ns{margin:4rem}.ma6-ns{margin:8rem}.ma7-ns{margin:16rem}.ml0-ns{margin-left:0}.ml1-ns{margin-left:.25rem}.ml2-ns{margin-left:.5rem}.ml3-ns{margin-left:1rem}.ml4-ns{margin-left:2rem}.ml5-ns{margin-left:4rem}.ml6-ns{margin-left:8rem}.ml7-ns{margin-left:16rem}.mr0-ns{margin-right:0}.mr1-ns{margin-right:.25rem}.mr2-ns{margin-right:.5rem}.mr3-ns{margin-right:1rem}.mr4-ns{margin-right:2rem}.mr5-ns{margin-right:4rem}.mr6-ns{margin-right:8rem}.mr7-ns{margin-right:16rem}.mb0-ns{margin-bottom:0}.mb1-ns{margin-bottom:.25rem}.mb2-ns{margin-bottom:.5rem}.mb3-ns{margin-bottom:1rem}.mb4-ns{margin-bottom:2rem}.mb5-ns{margin-bottom:4rem}.mb6-ns{margin-bottom:8rem}.mb7-ns{margin-bottom:16rem}.mt0-ns{margin-top:0}.mt1-ns{margin-top:.25rem}.mt2-ns{margin-top:.5rem}.mt3-ns{margin-top:1rem}.mt4-ns{margin-top:2rem}.mt5-ns{margin-top:4rem}.mt6-ns{margin-top:8rem}.mt7-ns{margin-top:16rem}.mv0-ns{margin-top:0;margin-bottom:0}.mv1-ns{margin-top:.25rem;margin-bottom:.25rem}.mv2-ns{margin-top:.5rem;margin-bottom:.5rem}.mv3-ns{margin-top:1rem;margin-bottom:1rem}.mv4-ns{margin-top:2rem;margin-bottom:2rem}.mv5-ns{margin-top:4rem;margin-bottom:4rem}.mv6-ns{margin-top:8rem;margin-bottom:8rem}.mv7-ns{margin-top:16rem;margin-bottom:16rem}.mh0-ns{margin-left:0;margin-right:0}.mh1-ns{margin-left:.25rem;margin-right:.25rem}.mh2-ns{margin-left:.5rem;margin-right:.5rem}.mh3-ns{margin-left:1rem;margin-right:1rem}.mh4-ns{margin-left:2rem;margin-right:2rem}.mh5-ns{margin-left:4rem;margin-right:4rem}.mh6-ns{margin-left:8rem;margin-right:8rem}.mh7-ns{margin-left:16rem;margin-right:16rem}.na1-ns{margin:-.25rem}.na2-ns{margin:-.5rem}.na3-ns{margin:-1rem}.na4-ns{margin:-2rem}.na5-ns{margin:-4rem}.na6-ns{margin:-8rem}.na7-ns{margin:-16rem}.nl1-ns{margin-left:-.25rem}.nl2-ns{margin-left:-.5rem}.nl3-ns{margin-left:-1rem}.nl4-ns{margin-left:-2rem}.nl5-ns{margin-left:-4rem}.nl6-ns{margin-left:-8rem}.nl7-ns{margin-left:-16rem}.nr1-ns{margin-right:-.25rem}.nr2-ns{margin-right:-.5rem}.nr3-ns{margin-right:-1rem}.nr4-ns{margin-right:-2rem}.nr5-ns{margin-right:-4rem}.nr6-ns{margin-right:-8rem}.nr7-ns{margin-right:-16rem}.nb1-ns{margin-bottom:-.25rem}.nb2-ns{margin-bottom:-.5rem}.nb3-ns{margin-bottom:-1rem}.nb4-ns{margin-bottom:-2rem}.nb5-ns{margin-bottom:-4rem}.nb6-ns{margin-bottom:-8rem}.nb7-ns{margin-bottom:-16rem}.nt1-ns{margin-top:-.25rem}.nt2-ns{margin-top:-.5rem}.nt3-ns{margin-top:-1rem}.nt4-ns{margin-top:-2rem}.nt5-ns{margin-top:-4rem}.nt6-ns{margin-top:-8rem}.nt7-ns{margin-top:-16rem}.strike-ns{text-decoration:line-through}.underline-ns{text-decoration:underline}.no-underline-ns{text-decoration:none}.tl-ns{text-align:left}.tr-ns{text-align:right}.tc-ns{text-align:center}.tj-ns{text-align:justify}.ttc-ns{text-transform:capitalize}.ttl-ns{text-transform:lowercase}.ttu-ns{text-transform:uppercase}.ttn-ns{text-transform:none}.f-6-ns,.f-headline-ns{font-size:6rem}.f-5-ns,.f-subheadline-ns{font-size:5rem}.f1-ns{font-size:3rem}.f2-ns{font-size:2.25rem}.f3-ns{font-size:1.5rem}.f4-ns{font-size:1.25rem}.f5-ns{font-size:1rem}.f6-ns{font-size:.875rem}.f7-ns{font-size:.75rem}.measure-ns{max-width:30em}.measure-wide-ns{max-width:34em}.measure-narrow-ns{max-width:20em}.indent-ns{text-indent:1em;margin-top:0;margin-bottom:0}.small-caps-ns{font-variant:small-caps}.truncate-ns{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.center-ns{margin-left:auto}.center-ns,.mr-auto-ns{margin-right:auto}.ml-auto-ns{margin-left:auto}.clip-ns{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}.ws-normal-ns{white-space:normal}.nowrap-ns{white-space:nowrap}.pre-ns{white-space:pre}.v-base-ns{vertical-align:baseline}.v-mid-ns{vertical-align:middle}.v-top-ns{vertical-align:top}.v-btm-ns{vertical-align:bottom}}@media screen and (min-width:30em) and (max-width:60em){.aspect-ratio-m{height:0;position:relative}.aspect-ratio--16x9-m{padding-bottom:56.25%}.aspect-ratio--9x16-m{padding-bottom:177.77%}.aspect-ratio--4x3-m{padding-bottom:75%}.aspect-ratio--3x4-m{padding-bottom:133.33%}.aspect-ratio--6x4-m{padding-bottom:66.6%}.aspect-ratio--4x6-m{padding-bottom:150%}.aspect-ratio--8x5-m{padding-bottom:62.5%}.aspect-ratio--5x8-m{padding-bottom:160%}.aspect-ratio--7x5-m{padding-bottom:71.42%}.aspect-ratio--5x7-m{padding-bottom:140%}.aspect-ratio--1x1-m{padding-bottom:100%}.aspect-ratio--object-m{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;z-index:100}.cover-m{background-size:cover!important}.contain-m{background-size:contain!important}.bg-center-m{background-position:50%}.bg-center-m,.bg-top-m{background-repeat:no-repeat}.bg-top-m{background-position:top}.bg-right-m{background-position:100%}.bg-bottom-m,.bg-right-m{background-repeat:no-repeat}.bg-bottom-m{background-position:bottom}.bg-left-m{background-repeat:no-repeat;background-position:0}.outline-m{outline:1px solid}.outline-transparent-m{outline:1px solid transparent}.outline-0-m{outline:0}.ba-m{border-style:solid;border-width:1px}.bt-m{border-top-style:solid;border-top-width:1px}.br-m{border-right-style:solid;border-right-width:1px}.bb-m{border-bottom-style:solid;border-bottom-width:1px}.bl-m{border-left-style:solid;border-left-width:1px}.bn-m{border-style:none;border-width:0}.br0-m{border-radius:0}.br1-m{border-radius:.125rem}.br2-m{border-radius:.25rem}.br3-m{border-radius:.5rem}.br4-m{border-radius:1rem}.br-100-m{border-radius:100%}.br-pill-m{border-radius:9999px}.br--bottom-m{border-top-left-radius:0;border-top-right-radius:0}.br--top-m{border-bottom-right-radius:0}.br--right-m,.br--top-m{border-bottom-left-radius:0}.br--right-m{border-top-left-radius:0}.br--left-m{border-top-right-radius:0;border-bottom-right-radius:0}.br-inherit-m{border-radius:inherit}.br-initial-m{border-radius:initial}.br-unset-m{border-radius:unset}.b--dotted-m{border-style:dotted}.b--dashed-m{border-style:dashed}.b--solid-m{border-style:solid}.b--none-m{border-style:none}.bw0-m{border-width:0}.bw1-m{border-width:.125rem}.bw2-m{border-width:.25rem}.bw3-m{border-width:.5rem}.bw4-m{border-width:1rem}.bw5-m{border-width:2rem}.bt-0-m{border-top-width:0}.br-0-m{border-right-width:0}.bb-0-m{border-bottom-width:0}.bl-0-m{border-left-width:0}.shadow-1-m{box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.shadow-2-m{box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.shadow-3-m{box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.shadow-4-m{box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.shadow-5-m{box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}.top-0-m{top:0}.left-0-m{left:0}.right-0-m{right:0}.bottom-0-m{bottom:0}.top-1-m{top:1rem}.left-1-m{left:1rem}.right-1-m{right:1rem}.bottom-1-m{bottom:1rem}.top-2-m{top:2rem}.left-2-m{left:2rem}.right-2-m{right:2rem}.bottom-2-m{bottom:2rem}.top--1-m{top:-1rem}.right--1-m{right:-1rem}.bottom--1-m{bottom:-1rem}.left--1-m{left:-1rem}.top--2-m{top:-2rem}.right--2-m{right:-2rem}.bottom--2-m{bottom:-2rem}.left--2-m{left:-2rem}.absolute--fill-m{top:0;right:0;bottom:0;left:0}.cl-m{clear:left}.cr-m{clear:right}.cb-m{clear:both}.cn-m{clear:none}.dn-m{display:none}.di-m{display:inline}.db-m{display:block}.dib-m{display:inline-block}.dit-m{display:inline-table}.dt-m{display:table}.dtc-m{display:table-cell}.dt-row-m{display:table-row}.dt-row-group-m{display:table-row-group}.dt-column-m{display:table-column}.dt-column-group-m{display:table-column-group}.dt--fixed-m{table-layout:fixed;width:100%}.flex-m{display:flex}.inline-flex-m{display:inline-flex}.flex-auto-m{flex:1 1 auto;min-width:0;min-height:0}.flex-none-m{flex:none}.flex-column-m{flex-direction:column}.flex-row-m{flex-direction:row}.flex-wrap-m{flex-wrap:wrap}.flex-nowrap-m{flex-wrap:nowrap}.flex-wrap-reverse-m{flex-wrap:wrap-reverse}.flex-column-reverse-m{flex-direction:column-reverse}.flex-row-reverse-m{flex-direction:row-reverse}.items-start-m{align-items:flex-start}.items-end-m{align-items:flex-end}.items-center-m{align-items:center}.items-baseline-m{align-items:baseline}.items-stretch-m{align-items:stretch}.self-start-m{align-self:flex-start}.self-end-m{align-self:flex-end}.self-center-m{align-self:center}.self-baseline-m{align-self:baseline}.self-stretch-m{align-self:stretch}.justify-start-m{justify-content:flex-start}.justify-end-m{justify-content:flex-end}.justify-center-m{justify-content:center}.justify-between-m{justify-content:space-between}.justify-around-m{justify-content:space-around}.content-start-m{align-content:flex-start}.content-end-m{align-content:flex-end}.content-center-m{align-content:center}.content-between-m{align-content:space-between}.content-around-m{align-content:space-around}.content-stretch-m{align-content:stretch}.order-0-m{order:0}.order-1-m{order:1}.order-2-m{order:2}.order-3-m{order:3}.order-4-m{order:4}.order-5-m{order:5}.order-6-m{order:6}.order-7-m{order:7}.order-8-m{order:8}.order-last-m{order:99999}.flex-grow-0-m{flex-grow:0}.flex-grow-1-m{flex-grow:1}.flex-shrink-0-m{flex-shrink:0}.flex-shrink-1-m{flex-shrink:1}.fl-m{float:left}.fl-m,.fr-m{_display:inline}.fr-m{float:right}.fn-m{float:none}.i-m{font-style:italic}.fs-normal-m{font-style:normal}.normal-m{font-weight:400}.b-m{font-weight:700}.fw1-m{font-weight:100}.fw2-m{font-weight:200}.fw3-m{font-weight:300}.fw4-m{font-weight:400}.fw5-m{font-weight:500}.fw6-m{font-weight:600}.fw7-m{font-weight:700}.fw8-m{font-weight:800}.fw9-m{font-weight:900}.h1-m{height:1rem}.h2-m{height:2rem}.h3-m{height:4rem}.h4-m{height:8rem}.h5-m{height:16rem}.h-25-m{height:25%}.h-50-m{height:50%}.h-75-m{height:75%}.h-100-m{height:100%}.min-h-100-m{min-height:100%}.vh-25-m{height:25vh}.vh-50-m{height:50vh}.vh-75-m{height:75vh}.vh-100-m{height:100vh}.min-vh-100-m{min-height:100vh}.h-auto-m{height:auto}.h-inherit-m{height:inherit}.tracked-m{letter-spacing:.1em}.tracked-tight-m{letter-spacing:-.05em}.tracked-mega-m{letter-spacing:.25em}.lh-solid-m{line-height:1}.lh-title-m{line-height:1.25}.lh-copy-m{line-height:1.5}.mw-100-m{max-width:100%}.mw1-m{max-width:1rem}.mw2-m{max-width:2rem}.mw3-m{max-width:4rem}.mw4-m{max-width:8rem}.mw5-m{max-width:16rem}.mw6-m{max-width:32rem}.mw7-m{max-width:48rem}.mw8-m{max-width:64rem}.mw9-m{max-width:96rem}.mw-none-m{max-width:none}.w1-m{width:1rem}.w2-m{width:2rem}.w3-m{width:4rem}.w4-m{width:8rem}.w5-m{width:16rem}.w-10-m{width:10%}.w-20-m{width:20%}.w-25-m{width:25%}.w-30-m{width:30%}.w-33-m{width:33%}.w-34-m{width:34%}.w-40-m{width:40%}.w-50-m{width:50%}.w-60-m{width:60%}.w-70-m{width:70%}.w-75-m{width:75%}.w-80-m{width:80%}.w-90-m{width:90%}.w-100-m{width:100%}.w-third-m{width:33.33333%}.w-two-thirds-m{width:66.66667%}.w-auto-m{width:auto}.overflow-visible-m{overflow:visible}.overflow-hidden-m{overflow:hidden}.overflow-scroll-m{overflow:scroll}.overflow-auto-m{overflow:auto}.overflow-x-visible-m{overflow-x:visible}.overflow-x-hidden-m{overflow-x:hidden}.overflow-x-scroll-m{overflow-x:scroll}.overflow-x-auto-m{overflow-x:auto}.overflow-y-visible-m{overflow-y:visible}.overflow-y-hidden-m{overflow-y:hidden}.overflow-y-scroll-m{overflow-y:scroll}.overflow-y-auto-m{overflow-y:auto}.static-m{position:static}.relative-m{position:relative}.absolute-m{position:absolute}.fixed-m{position:fixed}.rotate-45-m{-webkit-transform:rotate(45deg);transform:rotate(45deg)}.rotate-90-m{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.rotate-135-m{-webkit-transform:rotate(135deg);transform:rotate(135deg)}.rotate-180-m{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.rotate-225-m{-webkit-transform:rotate(225deg);transform:rotate(225deg)}.rotate-270-m{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.rotate-315-m{-webkit-transform:rotate(315deg);transform:rotate(315deg)}.pa0-m{padding:0}.pa1-m{padding:.25rem}.pa2-m{padding:.5rem}.pa3-m{padding:1rem}.pa4-m{padding:2rem}.pa5-m{padding:4rem}.pa6-m{padding:8rem}.pa7-m{padding:16rem}.pl0-m{padding-left:0}.pl1-m{padding-left:.25rem}.pl2-m{padding-left:.5rem}.pl3-m{padding-left:1rem}.pl4-m{padding-left:2rem}.pl5-m{padding-left:4rem}.pl6-m{padding-left:8rem}.pl7-m{padding-left:16rem}.pr0-m{padding-right:0}.pr1-m{padding-right:.25rem}.pr2-m{padding-right:.5rem}.pr3-m{padding-right:1rem}.pr4-m{padding-right:2rem}.pr5-m{padding-right:4rem}.pr6-m{padding-right:8rem}.pr7-m{padding-right:16rem}.pb0-m{padding-bottom:0}.pb1-m{padding-bottom:.25rem}.pb2-m{padding-bottom:.5rem}.pb3-m{padding-bottom:1rem}.pb4-m{padding-bottom:2rem}.pb5-m{padding-bottom:4rem}.pb6-m{padding-bottom:8rem}.pb7-m{padding-bottom:16rem}.pt0-m{padding-top:0}.pt1-m{padding-top:.25rem}.pt2-m{padding-top:.5rem}.pt3-m{padding-top:1rem}.pt4-m{padding-top:2rem}.pt5-m{padding-top:4rem}.pt6-m{padding-top:8rem}.pt7-m{padding-top:16rem}.pv0-m{padding-top:0;padding-bottom:0}.pv1-m{padding-top:.25rem;padding-bottom:.25rem}.pv2-m{padding-top:.5rem;padding-bottom:.5rem}.pv3-m{padding-top:1rem;padding-bottom:1rem}.pv4-m{padding-top:2rem;padding-bottom:2rem}.pv5-m{padding-top:4rem;padding-bottom:4rem}.pv6-m{padding-top:8rem;padding-bottom:8rem}.pv7-m{padding-top:16rem;padding-bottom:16rem}.ph0-m{padding-left:0;padding-right:0}.ph1-m{padding-left:.25rem;padding-right:.25rem}.ph2-m{padding-left:.5rem;padding-right:.5rem}.ph3-m{padding-left:1rem;padding-right:1rem}.ph4-m{padding-left:2rem;padding-right:2rem}.ph5-m{padding-left:4rem;padding-right:4rem}.ph6-m{padding-left:8rem;padding-right:8rem}.ph7-m{padding-left:16rem;padding-right:16rem}.ma0-m{margin:0}.ma1-m{margin:.25rem}.ma2-m{margin:.5rem}.ma3-m{margin:1rem}.ma4-m{margin:2rem}.ma5-m{margin:4rem}.ma6-m{margin:8rem}.ma7-m{margin:16rem}.ml0-m{margin-left:0}.ml1-m{margin-left:.25rem}.ml2-m{margin-left:.5rem}.ml3-m{margin-left:1rem}.ml4-m{margin-left:2rem}.ml5-m{margin-left:4rem}.ml6-m{margin-left:8rem}.ml7-m{margin-left:16rem}.mr0-m{margin-right:0}.mr1-m{margin-right:.25rem}.mr2-m{margin-right:.5rem}.mr3-m{margin-right:1rem}.mr4-m{margin-right:2rem}.mr5-m{margin-right:4rem}.mr6-m{margin-right:8rem}.mr7-m{margin-right:16rem}.mb0-m{margin-bottom:0}.mb1-m{margin-bottom:.25rem}.mb2-m{margin-bottom:.5rem}.mb3-m{margin-bottom:1rem}.mb4-m{margin-bottom:2rem}.mb5-m{margin-bottom:4rem}.mb6-m{margin-bottom:8rem}.mb7-m{margin-bottom:16rem}.mt0-m{margin-top:0}.mt1-m{margin-top:.25rem}.mt2-m{margin-top:.5rem}.mt3-m{margin-top:1rem}.mt4-m{margin-top:2rem}.mt5-m{margin-top:4rem}.mt6-m{margin-top:8rem}.mt7-m{margin-top:16rem}.mv0-m{margin-top:0;margin-bottom:0}.mv1-m{margin-top:.25rem;margin-bottom:.25rem}.mv2-m{margin-top:.5rem;margin-bottom:.5rem}.mv3-m{margin-top:1rem;margin-bottom:1rem}.mv4-m{margin-top:2rem;margin-bottom:2rem}.mv5-m{margin-top:4rem;margin-bottom:4rem}.mv6-m{margin-top:8rem;margin-bottom:8rem}.mv7-m{margin-top:16rem;margin-bottom:16rem}.mh0-m{margin-left:0;margin-right:0}.mh1-m{margin-left:.25rem;margin-right:.25rem}.mh2-m{margin-left:.5rem;margin-right:.5rem}.mh3-m{margin-left:1rem;margin-right:1rem}.mh4-m{margin-left:2rem;margin-right:2rem}.mh5-m{margin-left:4rem;margin-right:4rem}.mh6-m{margin-left:8rem;margin-right:8rem}.mh7-m{margin-left:16rem;margin-right:16rem}.na1-m{margin:-.25rem}.na2-m{margin:-.5rem}.na3-m{margin:-1rem}.na4-m{margin:-2rem}.na5-m{margin:-4rem}.na6-m{margin:-8rem}.na7-m{margin:-16rem}.nl1-m{margin-left:-.25rem}.nl2-m{margin-left:-.5rem}.nl3-m{margin-left:-1rem}.nl4-m{margin-left:-2rem}.nl5-m{margin-left:-4rem}.nl6-m{margin-left:-8rem}.nl7-m{margin-left:-16rem}.nr1-m{margin-right:-.25rem}.nr2-m{margin-right:-.5rem}.nr3-m{margin-right:-1rem}.nr4-m{margin-right:-2rem}.nr5-m{margin-right:-4rem}.nr6-m{margin-right:-8rem}.nr7-m{margin-right:-16rem}.nb1-m{margin-bottom:-.25rem}.nb2-m{margin-bottom:-.5rem}.nb3-m{margin-bottom:-1rem}.nb4-m{margin-bottom:-2rem}.nb5-m{margin-bottom:-4rem}.nb6-m{margin-bottom:-8rem}.nb7-m{margin-bottom:-16rem}.nt1-m{margin-top:-.25rem}.nt2-m{margin-top:-.5rem}.nt3-m{margin-top:-1rem}.nt4-m{margin-top:-2rem}.nt5-m{margin-top:-4rem}.nt6-m{margin-top:-8rem}.nt7-m{margin-top:-16rem}.strike-m{text-decoration:line-through}.underline-m{text-decoration:underline}.no-underline-m{text-decoration:none}.tl-m{text-align:left}.tr-m{text-align:right}.tc-m{text-align:center}.tj-m{text-align:justify}.ttc-m{text-transform:capitalize}.ttl-m{text-transform:lowercase}.ttu-m{text-transform:uppercase}.ttn-m{text-transform:none}.f-6-m,.f-headline-m{font-size:6rem}.f-5-m,.f-subheadline-m{font-size:5rem}.f1-m{font-size:3rem}.f2-m{font-size:2.25rem}.f3-m{font-size:1.5rem}.f4-m{font-size:1.25rem}.f5-m{font-size:1rem}.f6-m{font-size:.875rem}.f7-m{font-size:.75rem}.measure-m{max-width:30em}.measure-wide-m{max-width:34em}.measure-narrow-m{max-width:20em}.indent-m{text-indent:1em;margin-top:0;margin-bottom:0}.small-caps-m{font-variant:small-caps}.truncate-m{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.center-m{margin-left:auto}.center-m,.mr-auto-m{margin-right:auto}.ml-auto-m{margin-left:auto}.clip-m{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}.ws-normal-m{white-space:normal}.nowrap-m{white-space:nowrap}.pre-m{white-space:pre}.v-base-m{vertical-align:baseline}.v-mid-m{vertical-align:middle}.v-top-m{vertical-align:top}.v-btm-m{vertical-align:bottom}}@media screen and (min-width:60em){.aspect-ratio-l{height:0;position:relative}.aspect-ratio--16x9-l{padding-bottom:56.25%}.aspect-ratio--9x16-l{padding-bottom:177.77%}.aspect-ratio--4x3-l{padding-bottom:75%}.aspect-ratio--3x4-l{padding-bottom:133.33%}.aspect-ratio--6x4-l{padding-bottom:66.6%}.aspect-ratio--4x6-l{padding-bottom:150%}.aspect-ratio--8x5-l{padding-bottom:62.5%}.aspect-ratio--5x8-l{padding-bottom:160%}.aspect-ratio--7x5-l{padding-bottom:71.42%}.aspect-ratio--5x7-l{padding-bottom:140%}.aspect-ratio--1x1-l{padding-bottom:100%}.aspect-ratio--object-l{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;z-index:100}.cover-l{background-size:cover!important}.contain-l{background-size:contain!important}.bg-center-l{background-position:50%}.bg-center-l,.bg-top-l{background-repeat:no-repeat}.bg-top-l{background-position:top}.bg-right-l{background-position:100%}.bg-bottom-l,.bg-right-l{background-repeat:no-repeat}.bg-bottom-l{background-position:bottom}.bg-left-l{background-repeat:no-repeat;background-position:0}.outline-l{outline:1px solid}.outline-transparent-l{outline:1px solid transparent}.outline-0-l{outline:0}.ba-l{border-style:solid;border-width:1px}.bt-l{border-top-style:solid;border-top-width:1px}.br-l{border-right-style:solid;border-right-width:1px}.bb-l{border-bottom-style:solid;border-bottom-width:1px}.bl-l{border-left-style:solid;border-left-width:1px}.bn-l{border-style:none;border-width:0}.br0-l{border-radius:0}.br1-l{border-radius:.125rem}.br2-l{border-radius:.25rem}.br3-l{border-radius:.5rem}.br4-l{border-radius:1rem}.br-100-l{border-radius:100%}.br-pill-l{border-radius:9999px}.br--bottom-l{border-top-left-radius:0;border-top-right-radius:0}.br--top-l{border-bottom-right-radius:0}.br--right-l,.br--top-l{border-bottom-left-radius:0}.br--right-l{border-top-left-radius:0}.br--left-l{border-top-right-radius:0;border-bottom-right-radius:0}.br-inherit-l{border-radius:inherit}.br-initial-l{border-radius:initial}.br-unset-l{border-radius:unset}.b--dotted-l{border-style:dotted}.b--dashed-l{border-style:dashed}.b--solid-l{border-style:solid}.b--none-l{border-style:none}.bw0-l{border-width:0}.bw1-l{border-width:.125rem}.bw2-l{border-width:.25rem}.bw3-l{border-width:.5rem}.bw4-l{border-width:1rem}.bw5-l{border-width:2rem}.bt-0-l{border-top-width:0}.br-0-l{border-right-width:0}.bb-0-l{border-bottom-width:0}.bl-0-l{border-left-width:0}.shadow-1-l{box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.shadow-2-l{box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.shadow-3-l{box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.shadow-4-l{box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.shadow-5-l{box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}.top-0-l{top:0}.left-0-l{left:0}.right-0-l{right:0}.bottom-0-l{bottom:0}.top-1-l{top:1rem}.left-1-l{left:1rem}.right-1-l{right:1rem}.bottom-1-l{bottom:1rem}.top-2-l{top:2rem}.left-2-l{left:2rem}.right-2-l{right:2rem}.bottom-2-l{bottom:2rem}.top--1-l{top:-1rem}.right--1-l{right:-1rem}.bottom--1-l{bottom:-1rem}.left--1-l{left:-1rem}.top--2-l{top:-2rem}.right--2-l{right:-2rem}.bottom--2-l{bottom:-2rem}.left--2-l{left:-2rem}.absolute--fill-l{top:0;right:0;bottom:0;left:0}.cl-l{clear:left}.cr-l{clear:right}.cb-l{clear:both}.cn-l{clear:none}.dn-l{display:none}.di-l{display:inline}.db-l{display:block}.dib-l{display:inline-block}.dit-l{display:inline-table}.dt-l{display:table}.dtc-l{display:table-cell}.dt-row-l{display:table-row}.dt-row-group-l{display:table-row-group}.dt-column-l{display:table-column}.dt-column-group-l{display:table-column-group}.dt--fixed-l{table-layout:fixed;width:100%}.flex-l{display:flex}.inline-flex-l{display:inline-flex}.flex-auto-l{flex:1 1 auto;min-width:0;min-height:0}.flex-none-l{flex:none}.flex-column-l{flex-direction:column}.flex-row-l{flex-direction:row}.flex-wrap-l{flex-wrap:wrap}.flex-nowrap-l{flex-wrap:nowrap}.flex-wrap-reverse-l{flex-wrap:wrap-reverse}.flex-column-reverse-l{flex-direction:column-reverse}.flex-row-reverse-l{flex-direction:row-reverse}.items-start-l{align-items:flex-start}.items-end-l{align-items:flex-end}.items-center-l{align-items:center}.items-baseline-l{align-items:baseline}.items-stretch-l{align-items:stretch}.self-start-l{align-self:flex-start}.self-end-l{align-self:flex-end}.self-center-l{align-self:center}.self-baseline-l{align-self:baseline}.self-stretch-l{align-self:stretch}.justify-start-l{justify-content:flex-start}.justify-end-l{justify-content:flex-end}.justify-center-l{justify-content:center}.justify-between-l{justify-content:space-between}.justify-around-l{justify-content:space-around}.content-start-l{align-content:flex-start}.content-end-l{align-content:flex-end}.content-center-l{align-content:center}.content-between-l{align-content:space-between}.content-around-l{align-content:space-around}.content-stretch-l{align-content:stretch}.order-0-l{order:0}.order-1-l{order:1}.order-2-l{order:2}.order-3-l{order:3}.order-4-l{order:4}.order-5-l{order:5}.order-6-l{order:6}.order-7-l{order:7}.order-8-l{order:8}.order-last-l{order:99999}.flex-grow-0-l{flex-grow:0}.flex-grow-1-l{flex-grow:1}.flex-shrink-0-l{flex-shrink:0}.flex-shrink-1-l{flex-shrink:1}.fl-l{float:left}.fl-l,.fr-l{_display:inline}.fr-l{float:right}.fn-l{float:none}.i-l{font-style:italic}.fs-normal-l{font-style:normal}.normal-l{font-weight:400}.b-l{font-weight:700}.fw1-l{font-weight:100}.fw2-l{font-weight:200}.fw3-l{font-weight:300}.fw4-l{font-weight:400}.fw5-l{font-weight:500}.fw6-l{font-weight:600}.fw7-l{font-weight:700}.fw8-l{font-weight:800}.fw9-l{font-weight:900}.h1-l{height:1rem}.h2-l{height:2rem}.h3-l{height:4rem}.h4-l{height:8rem}.h5-l{height:16rem}.h-25-l{height:25%}.h-50-l{height:50%}.h-75-l{height:75%}.h-100-l{height:100%}.min-h-100-l{min-height:100%}.vh-25-l{height:25vh}.vh-50-l{height:50vh}.vh-75-l{height:75vh}.vh-100-l{height:100vh}.min-vh-100-l{min-height:100vh}.h-auto-l{height:auto}.h-inherit-l{height:inherit}.tracked-l{letter-spacing:.1em}.tracked-tight-l{letter-spacing:-.05em}.tracked-mega-l{letter-spacing:.25em}.lh-solid-l{line-height:1}.lh-title-l{line-height:1.25}.lh-copy-l{line-height:1.5}.mw-100-l{max-width:100%}.mw1-l{max-width:1rem}.mw2-l{max-width:2rem}.mw3-l{max-width:4rem}.mw4-l{max-width:8rem}.mw5-l{max-width:16rem}.mw6-l{max-width:32rem}.mw7-l{max-width:48rem}.mw8-l{max-width:64rem}.mw9-l{max-width:96rem}.mw-none-l{max-width:none}.w1-l{width:1rem}.w2-l{width:2rem}.w3-l{width:4rem}.w4-l{width:8rem}.w5-l{width:16rem}.w-10-l{width:10%}.w-20-l{width:20%}.w-25-l{width:25%}.w-30-l{width:30%}.w-33-l{width:33%}.w-34-l{width:34%}.w-40-l{width:40%}.w-50-l{width:50%}.w-60-l{width:60%}.w-70-l{width:70%}.w-75-l{width:75%}.w-80-l{width:80%}.w-90-l{width:90%}.w-100-l{width:100%}.w-third-l{width:33.33333%}.w-two-thirds-l{width:66.66667%}.w-auto-l{width:auto}.overflow-visible-l{overflow:visible}.overflow-hidden-l{overflow:hidden}.overflow-scroll-l{overflow:scroll}.overflow-auto-l{overflow:auto}.overflow-x-visible-l{overflow-x:visible}.overflow-x-hidden-l{overflow-x:hidden}.overflow-x-scroll-l{overflow-x:scroll}.overflow-x-auto-l{overflow-x:auto}.overflow-y-visible-l{overflow-y:visible}.overflow-y-hidden-l{overflow-y:hidden}.overflow-y-scroll-l{overflow-y:scroll}.overflow-y-auto-l{overflow-y:auto}.static-l{position:static}.relative-l{position:relative}.absolute-l{position:absolute}.fixed-l{position:fixed}.rotate-45-l{-webkit-transform:rotate(45deg);transform:rotate(45deg)}.rotate-90-l{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.rotate-135-l{-webkit-transform:rotate(135deg);transform:rotate(135deg)}.rotate-180-l{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.rotate-225-l{-webkit-transform:rotate(225deg);transform:rotate(225deg)}.rotate-270-l{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.rotate-315-l{-webkit-transform:rotate(315deg);transform:rotate(315deg)}.pa0-l{padding:0}.pa1-l{padding:.25rem}.pa2-l{padding:.5rem}.pa3-l{padding:1rem}.pa4-l{padding:2rem}.pa5-l{padding:4rem}.pa6-l{padding:8rem}.pa7-l{padding:16rem}.pl0-l{padding-left:0}.pl1-l{padding-left:.25rem}.pl2-l{padding-left:.5rem}.pl3-l{padding-left:1rem}.pl4-l{padding-left:2rem}.pl5-l{padding-left:4rem}.pl6-l{padding-left:8rem}.pl7-l{padding-left:16rem}.pr0-l{padding-right:0}.pr1-l{padding-right:.25rem}.pr2-l{padding-right:.5rem}.pr3-l{padding-right:1rem}.pr4-l{padding-right:2rem}.pr5-l{padding-right:4rem}.pr6-l{padding-right:8rem}.pr7-l{padding-right:16rem}.pb0-l{padding-bottom:0}.pb1-l{padding-bottom:.25rem}.pb2-l{padding-bottom:.5rem}.pb3-l{padding-bottom:1rem}.pb4-l{padding-bottom:2rem}.pb5-l{padding-bottom:4rem}.pb6-l{padding-bottom:8rem}.pb7-l{padding-bottom:16rem}.pt0-l{padding-top:0}.pt1-l{padding-top:.25rem}.pt2-l{padding-top:.5rem}.pt3-l{padding-top:1rem}.pt4-l{padding-top:2rem}.pt5-l{padding-top:4rem}.pt6-l{padding-top:8rem}.pt7-l{padding-top:16rem}.pv0-l{padding-top:0;padding-bottom:0}.pv1-l{padding-top:.25rem;padding-bottom:.25rem}.pv2-l{padding-top:.5rem;padding-bottom:.5rem}.pv3-l{padding-top:1rem;padding-bottom:1rem}.pv4-l{padding-top:2rem;padding-bottom:2rem}.pv5-l{padding-top:4rem;padding-bottom:4rem}.pv6-l{padding-top:8rem;padding-bottom:8rem}.pv7-l{padding-top:16rem;padding-bottom:16rem}.ph0-l{padding-left:0;padding-right:0}.ph1-l{padding-left:.25rem;padding-right:.25rem}.ph2-l{padding-left:.5rem;padding-right:.5rem}.ph3-l{padding-left:1rem;padding-right:1rem}.ph4-l{padding-left:2rem;padding-right:2rem}.ph5-l{padding-left:4rem;padding-right:4rem}.ph6-l{padding-left:8rem;padding-right:8rem}.ph7-l{padding-left:16rem;padding-right:16rem}.ma0-l{margin:0}.ma1-l{margin:.25rem}.ma2-l{margin:.5rem}.ma3-l{margin:1rem}.ma4-l{margin:2rem}.ma5-l{margin:4rem}.ma6-l{margin:8rem}.ma7-l{margin:16rem}.ml0-l{margin-left:0}.ml1-l{margin-left:.25rem}.ml2-l{margin-left:.5rem}.ml3-l{margin-left:1rem}.ml4-l{margin-left:2rem}.ml5-l{margin-left:4rem}.ml6-l{margin-left:8rem}.ml7-l{margin-left:16rem}.mr0-l{margin-right:0}.mr1-l{margin-right:.25rem}.mr2-l{margin-right:.5rem}.mr3-l{margin-right:1rem}.mr4-l{margin-right:2rem}.mr5-l{margin-right:4rem}.mr6-l{margin-right:8rem}.mr7-l{margin-right:16rem}.mb0-l{margin-bottom:0}.mb1-l{margin-bottom:.25rem}.mb2-l{margin-bottom:.5rem}.mb3-l{margin-bottom:1rem}.mb4-l{margin-bottom:2rem}.mb5-l{margin-bottom:4rem}.mb6-l{margin-bottom:8rem}.mb7-l{margin-bottom:16rem}.mt0-l{margin-top:0}.mt1-l{margin-top:.25rem}.mt2-l{margin-top:.5rem}.mt3-l{margin-top:1rem}.mt4-l{margin-top:2rem}.mt5-l{margin-top:4rem}.mt6-l{margin-top:8rem}.mt7-l{margin-top:16rem}.mv0-l{margin-top:0;margin-bottom:0}.mv1-l{margin-top:.25rem;margin-bottom:.25rem}.mv2-l{margin-top:.5rem;margin-bottom:.5rem}.mv3-l{margin-top:1rem;margin-bottom:1rem}.mv4-l{margin-top:2rem;margin-bottom:2rem}.mv5-l{margin-top:4rem;margin-bottom:4rem}.mv6-l{margin-top:8rem;margin-bottom:8rem}.mv7-l{margin-top:16rem;margin-bottom:16rem}.mh0-l{margin-left:0;margin-right:0}.mh1-l{margin-left:.25rem;margin-right:.25rem}.mh2-l{margin-left:.5rem;margin-right:.5rem}.mh3-l{margin-left:1rem;margin-right:1rem}.mh4-l{margin-left:2rem;margin-right:2rem}.mh5-l{margin-left:4rem;margin-right:4rem}.mh6-l{margin-left:8rem;margin-right:8rem}.mh7-l{margin-left:16rem;margin-right:16rem}.na1-l{margin:-.25rem}.na2-l{margin:-.5rem}.na3-l{margin:-1rem}.na4-l{margin:-2rem}.na5-l{margin:-4rem}.na6-l{margin:-8rem}.na7-l{margin:-16rem}.nl1-l{margin-left:-.25rem}.nl2-l{margin-left:-.5rem}.nl3-l{margin-left:-1rem}.nl4-l{margin-left:-2rem}.nl5-l{margin-left:-4rem}.nl6-l{margin-left:-8rem}.nl7-l{margin-left:-16rem}.nr1-l{margin-right:-.25rem}.nr2-l{margin-right:-.5rem}.nr3-l{margin-right:-1rem}.nr4-l{margin-right:-2rem}.nr5-l{margin-right:-4rem}.nr6-l{margin-right:-8rem}.nr7-l{margin-right:-16rem}.nb1-l{margin-bottom:-.25rem}.nb2-l{margin-bottom:-.5rem}.nb3-l{margin-bottom:-1rem}.nb4-l{margin-bottom:-2rem}.nb5-l{margin-bottom:-4rem}.nb6-l{margin-bottom:-8rem}.nb7-l{margin-bottom:-16rem}.nt1-l{margin-top:-.25rem}.nt2-l{margin-top:-.5rem}.nt3-l{margin-top:-1rem}.nt4-l{margin-top:-2rem}.nt5-l{margin-top:-4rem}.nt6-l{margin-top:-8rem}.nt7-l{margin-top:-16rem}.strike-l{text-decoration:line-through}.underline-l{text-decoration:underline}.no-underline-l{text-decoration:none}.tl-l{text-align:left}.tr-l{text-align:right}.tc-l{text-align:center}.tj-l{text-align:justify}.ttc-l{text-transform:capitalize}.ttl-l{text-transform:lowercase}.ttu-l{text-transform:uppercase}.ttn-l{text-transform:none}.f-6-l,.f-headline-l{font-size:6rem}.f-5-l,.f-subheadline-l{font-size:5rem}.f1-l{font-size:3rem}.f2-l{font-size:2.25rem}.f3-l{font-size:1.5rem}.f4-l{font-size:1.25rem}.f5-l{font-size:1rem}.f6-l{font-size:.875rem}.f7-l{font-size:.75rem}.measure-l{max-width:30em}.measure-wide-l{max-width:34em}.measure-narrow-l{max-width:20em}.indent-l{text-indent:1em;margin-top:0;margin-bottom:0}.small-caps-l{font-variant:small-caps}.truncate-l{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.center-l{margin-left:auto}.center-l,.mr-auto-l{margin-right:auto}.ml-auto-l{margin-left:auto}.clip-l{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}.ws-normal-l{white-space:normal}.nowrap-l{white-space:nowrap}.pre-l{white-space:pre}.v-base-l{vertical-align:baseline}.v-mid-l{vertical-align:middle}.v-top-l{vertical-align:top}.v-btm-l{vertical-align:bottom}} diff --git a/themes/cyanine/cyanine-thumb.png b/themes/cyanine/cyanine-thumb.png new file mode 100644 index 0000000000000000000000000000000000000000..bd62de8ccd7687ce53f9369878f7ba976ba30f56 GIT binary patch literal 74931 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%0D=4WP?|s-XAm`j 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%0D=4WP?|s-XAm`j zX3xOsm#zF+%F7GCZ%=}VntZ)-JV>dsk~NRJf0Xz1*3H7c+ImmCZ9qBT_V;;m2+&Je zwu()-e@9*~q>X%9K%d#?R&@8@E4RF@s-YnQwSGc-zpX$Lsi>~$?v-UjU}8M&`W-Ng zACB2;$N5!A>4Ec`8A#Wpk)!=HzsGU|H03?ko}h=2Q&Y=w(YLz$XZPS-zN@g-QaT)n zP-XruX~OrmE6a+qNJMgX2nXV?X=`h*WI}Cq`#gSl_%NiNP0)zoU{$CjEs@Ed;~<5t z)Y710ca69KI?0}kMdhV`!Qg1b5NKr~Lqv%0+koR~j~_fw@i#7Jy3GM`5Cdd9_AU`< z6EB=iKxd;L@%Zs5kgbIz0_X)Z<#`E6MEE9+)(NRGU^?8KP5?<12)r2w07*&?V8geP zHpN9nmhu3H*n|au0o;0{u&Ag(i|BtWQhGO7q)&ylb>A)l>o2#T`^&`t^J=j>uuFg# zIG(6MH{YQoBI5qkk&xJH1_?pg95zG)X^^$y{Deltp|d<&GfFD)9D040>RAxyXOPJL zK6B6ti>djqJ_G$iXN66ZJvu?83W(Vcz!P&IV#~eS4=8sC0Bh!SBIDCO*_!)ctII`t zfmE>DnG+DQO$42Yg*vF@Ri60P#`a#*(Oo?6zc-TQqpvnWp}uus-k@o%ZqoF1OaDJ~ z%}m7S*Fh^>HCtw3HdK(PIgtcXy3St;D%^}Fy!(o!D!}ACii6`$)By~wey2k>Md;9N zaB^~T^Ov=Qh^_XR8b9e>g-~k;*tRy0GXoq;Ib7(dz1I z$n{b3(_n)Z@4Xo8f~%Fx2uFTVrCNltXt{*pqPsCB!+Y=vWT zA@9I)2BokzUJV~57~P(js8U~RKM0G1dW>5Z+B!)iY?!$v$hObl+mB71f9N8ES4y5< zje-)?3-I0%cLz|40Y+khn!kU8g;@XEHr5>E)5{8;05(|N3Pf6K=DEv&v#LO?c#0bY zwol6$KsJz*cOpu2&-aEEC=VqcSSwg&Wgu%pQKAJ{`qM@B(D1I))FkGi*cFYRBr_ zt4{{$IG4!{$E8kp-< z=5ey&lvoPz98~HajQAuG#=PR`@m7dD`0u^-0Kfo%8QbZOoXb+e8fRb^IHG9VXEK{JdX+?KU#4CGB$y0 z3p3dQzW>3BU@M@3dEO~OZCu(*FmiEj@Oh{^XR;5UHRu5Fd25H#q>jEW6VU`d6VJUh z=wv~fGKuFX?Tx(p0NB>S!NK;Eb?%go!-gH-zTIyI>Y4&nI$8_}@j&Ks@w_&SX#^Sk z8Q0LD(FF8uPW#WXynt7t%pY0=ozc{Nb6*0>;4xQ&8YR6c-{D(jM^m8kFTGp z-95TUaQu=+ht5L&yb9a^6BG5CyJ^TB*$DslLd$?LP+>Lrd=m(E*B~)BMR0eXx2K^< z-^~HdE`ZWI``?zK{RBiQO@@DeRO3_&F7mSR*4{iN*2PhzCYjb$3pHxw)*N+gF-7Xew2LoaiFAa!m~Ygm^hS2 z5OUzgU(5F94VgYF~_xLR97>8<>bzqwE5!`A|<7065LI$~yh2m&6| z%Tc~18+MJ1-07V}WxLj%2Y7p=coiD-|KtG^DCPd&dsgIKSHJ$PPJN-82*Z~dC6_*M z?+Ls4sbPbKTnrTy2C}-I@2;W26Fad^YUQ{Lhbjak9MQxxk@AZFp7i9jSm$eHQKU;m z-8*`pa2%yAcS-8Bkp%zAb7}KkIZ=dg)3sRpA)!=%7mU5-V3+@M|LjY>`U_n*W7t(q z2PWP-t%*KMLTPM|b|E#6PUF|=Y|GZv!g&B=Q?{w4Gf36IZKs<_)5 zKdjs9c)RiQL~qlPJ-PzN2y{N(Vten`XLTd5rZh6`oqg5Mk4C?IV`~54{)u2!)qIrd za8lhKT|j^L!D*bcLp5u6LAGz#`s49VMEFWOeQR?L7Ug5%v35~hBIji}!p6y>w_(4o zRWqTw&%J5%0eRis-MrjyEQV%UdCft?NGc^iVDmDo`IK{NSNSL*FPPbCetSY@?MTXx zHOg1cT^3RkNtr<;SZTt`*xIJhHe7pO+w-#QNZkMel z;qIFwt3Z#E+=sIRGv@8G$E!uz+xvp8(DPyI#2ArQ5=B_W@)f9*`4oSoOcNi*6hDQP z<0v#ayt00_@$tHTzYxpS?p(~YxYvirG+(B9a)HmzM|%upS-UNlb};eXeV5$s0c3>q#jE0nSqA4n4Iso@*HPO*(W6UX(#!1F6A*RZd=ns?HDR$ z7KE>7cGx#5;{~}&#a3JkdO!+^$}qegv$=6Pw1ZxPCgYZsfYUItUJ<#w~+9G z<`L|Dp(oOK(+nV@SIJhdJjZ96X-0Nms~$cqciSL8WOZJyXEScdani)@_QcYM5d}KI zm8h0FBXMS0F4&jC-Wn7fZQ#hqn+(&JRl49A4Y!mzCPr(r+Cy>|8J!5%fX(#erQ@2< zOBeIkUJ5?*&TC#$Z&gSm9ecVDQKzj~ z#budk70(^Mu7~hnZQwiM%sP6zd2F$>2A+WMI+BMdw-crx3G4WGpORp2b2Qk}06rrYIa%o6Nz>*GIwc2ny6 zlNS@_EmxL5$=WFxE#DgW5aUdWwW+E+K1|KEP*PJ90YjgH+gQ}B2aa;UO$501mqPAI zsO~KkeEG7xz`gdR#s%RV5Jtr2BFT46ZQu0#OeR1=y$mo*n(R?4<6T{BPtivg} zrIU)@17*zPNX(hth1J>)Z`GnVzJAl@u1(nuVHCysffHLZkS@$RJ)!EyjNhc9;xN0M z3m*?V+`%w=eYFd3OdX{1ssOD_>Gw&+CxQk?IOM`ZjvX zXE3Rgf3MNfarRb&`99&e*@REWu3$o&3Q)duUS{D>FE!*IV#&pBb}ekp<^s1ig$e)c z;5yvnL6XGLn89(_ucJT3+GxCN#q{sdJqS?pTMyywWZZV&@st6Li1NxNaox@a%R+B> zN^GRW0sYX;5CSu)btLrSS%4W;JO9;mS2soG(Uu>VMg-WcE81N5ZCbkHOlFIrfF%gA z5{G<;nV)j%#i<;*8qe8LlV#j+NszF0sPOTFx^8k|u4@|MS|2C_fu8)ma1u9Fr839O z!@y35LGjULpl@rRW0vyTzb3AhX%i+?Kb&+gAEkHP+g|lE#RMCQr4+gOU^VJ*D?|*{ zgcMczJQQcu%uv64srM}OuBj|t$FfDJ%0RHr?=gmgaIznt>3o#`!ib3 z4)H%1vYx7*++K=_ZIJzO;jyc*s_&vgucrS4*OK(e)#%6Gzo`y@qthvY%8jqBjT3e2 zu?CIw(MfWK1IqzJnwFE3J9PN);i?c!TXpM>NRDv)<6Q4kk*9j5sdg*RzXz4jH8-B5Yd2Lk!gJQ-p6xS^Ik0J?lro3bY^e1yN0wzBCych6jGNYj}`>%_y)=y znQ!T=rnl+l;09D+X#ec68`N3eg6c7$d2(OZ#fu9fqd(y(ro$`ktap2M`f!9Rn!Q)}s&H5{Jng5}t*Qg1d=ox7+WdHf}JSf)BWwWNOONAJhyFSsSS`3SCRejtk{rq^>Ma-TesHM0gF{iKCr5 z*ZLanZm4m^B^kS&ZxZfAy}ipPCoRa7TLbT%kQDC20!TVk1he755#G3vE`K#Yr2eT6@< z#%@;Q3_KPjN@H5SZhRdgz8h5BcbN@ZQunt(U{U3$$ZC&T{rM4vHb8*+nj!ZtsqTd% zC({BNrg%)x@;>RHlm$R-EENzY;fYTQuIuNb>td0Q7W=t8JhKWCr|&g$e> zzF&6~+}HZrELi*;{YGucS_%1BVSEL30o}TNWqFP$+j~hd04d|N+;9uViPCB+FkgwM z6jaNF+&k5nbgGfia`Oy3*piv?7-T-dR>DM;woy^wFul)qY$%30kvCXzvnN7MCF~+7 zt%KCTUT=_jL8QH7%h4Q(R3@QiygA67y>x73w9xiy^}`m~<0(o(Esh{ks&xv%New*j zs_v=W5XPED8IV_HK?fMUlixQ*{sHpFMd5DlqhD~24Xb*SB!jkVk76C{E}~5 zjNd(DX0cjcQ79qBG{rIT@>xY;l7~|UBJaanU!8VG5?a-f>@)ab;}%+YK1?^#dsFsJ zmV5J1!fXaLfS%PV(4_ldR$yi#x4};>^d5y!;pmCkdF2}}l=W_aD9B0$T}%Z^YCgps zi4{wcQLZoP;(~_k+|dF;obnAZ!R46OV~5oeN9}wDy(X0TMI!At1Mp+9cj^u-9F!y-hCRz+ed9OD$;Ge>`x?sp;13KA{0(#XMB zRzCW{VsKf(f!YiQuZNhn#$1iydmki|Ku6YfP+G`RTCtwS$O|HTP2FL6(JjkS{VrGp zmCl$WDy{|9%u-2xZGy`(Kx+BYv&ck?YMzTZkl0aF0RC>mjY5>LP9DDvTU^dI=UI1m z^qYjlj*lM`RC8;U4kusBd2woC>Dckuo{sDKceEVd6$|TBNj^PWEKpZ|``POP<=xs{ z1*(A0QmX`4a+>@eyi(YX_PxJDYgTQHrTXJg93J)vaS3Lwej$_44J*XFX#N_iKc|J4bDJaM^#W zZAmv~Q1MQDgo#hsr1HoyKn5*hGnX|B@G%m`H$}R{padh1JtPB zJ*YF+^Fbj~@~hz`>4!$fTeW_QESZ*@nmn>#FVCo3eJ!{!Fe4}od-pl@3XKsoQ1w!f z6g%MUMOtf%JQY)zu(Yxq+txGN$b7hH9%^0Bzc4^r!UsKUBj3-&sQy?VRuDF5alT90 zUzqj5Q#@?Y&lKM?I6fWXk9Vzosq#T#ccWpg* ze`QaL80ju{sp>2)zW03NO;@om@iJe5n5H(N_I&Jzqv-R3H| zk_r{|V3zpZsceM% zDZ3!qL2e9pM5NG!(D_=hFri{;Su6o<$Sp6UhN5;<%Aoz+W>t-GUzeBlm*5aF>>Q_- zT(2Ep1WO1;ZZ%nsHO>?MsR_PfTV0(TVI-k1Ji8Oc#yW9PTt=Hc#f_zR%N5p==l({bc#2B7? zLBKRMg&rxtx1cYg#wv8@xv2mDquY2w*2&4fXw>~tt+Ug4J`zk2Z8@D#L^~Trcw#sq zyy$K_>BrIRc9=xW09Cna`${=x3T`*>W(>H{m`D6A{``I!*A#ZNhu<l<^{R1xD7 zHL4*lgxQ&9=Aba-ah_c zy(3mbYuE(=l5gkN&uG@Ab6_6u6aF%dr~yUpIjl{LV%Sz2q*Y=E?A$T;Qj$qyK~j1s zGY>xE1QG3z{(RZf*DvLy4v@`&AVVAov;3^DcMt$Mvfkk?O+LorArH&f3WKp$X)1by zi$$CR=8vfXD~hd44J8&xY2&xsrx^h;+dsUL2C&eSE6P{>a!AKGVJ5HYjlO=zbx==s zhvbW&yZ(EdKwQR4;gvXhyyxg(m z|pzSsm2IYQT~uw z%B=p+nbr7j2~d zAHaPD_P*FO9WW2@Im6ovR1nNfqiB*h@}_FA{loj}rI3$wEQ=U0bm*tU zqK2|)ju&?}Qt?j{Nr4u!u=tMD67Jf3<_x2=wY&R`sXuy_pLk(SCA^BN7m|~Fd_2O< zU3Rp7=6#<9+uehdX%|fmU}EHG*;(&*+Qh* z{d~8Y7hzuxgYFF#A8@{uy&#doyTY8ybut(#U!InG_c?IH6>VRm7M+k6c8t}M2MOzi>)oy%$^SoNZ`Ctm2y_N8hlS%5ruU=oO0cb8?H<3jT~D-)^smaE(RX zRCCf4qGpxD315Ycz?7&83^^4%{8rHK)DDi|s$G1fw{&JpgpUWqQM6s}ABEzC-v=>U zI*~)YJNk{E1w*l2#;w%yEjh{e1IZc$s#w#H$RoQjl&cQqpKCjR#%!M?@bgq(k=V7R z^WlhF{7|&)S#*4O8~B+vbZpR7GOY}ax-BBj-SfijWS;Mr=)yh=S#7)0G*xXbBi_9H zAaa-KfbsboE*|3a+%3k_db>1&7H0^`!_4nZ2KYTF)FqJ%lLEH_;0eSGItml+i7xXYOaS+RkX;GGAZLRFTP zUB>XUSc1VoU=z+Vt|uBEXy75de>X1QA|y0?tyNBW!@5J69wVAON7!T+u)}C$0qYod+`S9Wnp)z`E#BN{xRWpu+9t4--9WnW9XR)eVzVQ*x#9k=Ge%m4 z2Og*?U2|bD2H!|&5ofNfz8x)SeiyWPqfu)WMEBIlVQ%5xJ~Q{SfPCI6-> zzctf}o4y+_`C-MpY=5b{=c~qo1-`S1V-ne+SF zC!-G5nHAVt2o6-N2G}=P#xIa4VQLLt>kpCN8Wfeea_7PgV9hqP!8HcU%puQvr;bH^ zz*rD%r$!Ame@~F^euHn_V~yxPCEzrlwiulaqU33^pvq1nJf)#TAnyuEil z`5|4QRvu~8cqiIeTrFQ@_3e!o-yOYg59$5Mxkjox?=OgXk`p=HGh(_?^}PTCs@t#q zdXMQ&#u`;f-8k!J!7i)1ErT`=+1w>6p11jS;XafI-kjU2H3C`t7JM|s*FK>uKSCMM zfN8smfEm74;kE*dd%+!KdTO0EN%;8qZCxq#;HuBH&e*5blH`E&0!tfB+WW%p_=t#{ zTIZBfbZ*(DXv&?PY|;HsW&KJ-TLKqIi#wltO=3Ku)z3ME-CqB`&1V|>hu}jU=&#jE zXeGgaAQy>bhwxO75f3XR6#EU<=-VI99&kI%3)=e#Jq4{+_aufEIpH9YkO=TeR_L zzop9YvNdzJ6b1O=5j;sx{V73^7>Q|rf}~_1e2O%a&uHvM)>d5%Zp|UQJYqorxK5{T z+iVKU`de?L7%pEI^W#xGIHsv^%E6&HeX|sZ$yK5X3b9az!%;`6;NS8+jrMF~z5$~*i z_=arplg65vIuuOD&(Qrh8*|32ZJt?fZ^e~e+3)dItNc!UYI!xLUZ-!OyGbloq^X&3 z@A3Qx7VD*?nY~Bt;d=U6l&n}nCD#6QPijwrRb--Uv2DfmLhHm)Aq$k%L*bN_s+z$V z1BNsHK}Dx`!LNlq);lDvR;!1xqRIA1#cKgrLV|6ux4eH2yh)ZTmZ!ujQ$+PaAI=6yL>n~!_Ji}@?s5%2t)Ep!-LUz0&fjogs}Bl2 z(mAC2L%1>1tGHUUmr8q+)@yV=HQ%~It6WY@uWT(YBa%JlQr=}>a5UBXTu-Xy#iM2g zVQ$t^Hd;VU% zNSPkBJZS_?pGBQzT5R-XjKfSWczL0-Z0ZC5xX+nOIQ^%Xj(u0hb z6vF%P*%_GEP~CHvT2RLi`1Ng6P&+W&z04h%V3B0Tj%2XATm}?xKe*3tr3;o)wU}-D z%RFbUg6E1v8}PQr2fl{Py_H_=MQWm-k6a@Ba5D#V|LX5sEE6|&v{w5+HmVa>d*Mij zo37bZp4SH8vV^H;qrr-tes=`@bi!|Yya#@I1YJ^xBE&aAzbOH~d44GmJQc>nb2Il3 zFX5CSxDfnH0%Znz?LPd+H{pES1N;c^%{UwX{NtN)g0BAY%{l7-O)vjwl0?YdD>Gb! z=HdA$)$5~i8pKQgV~zMR{gC}UJbmxgaV8){AAiX^Lm{Ox*!kFOUIy!Vt~O%dtUmVY8nbwsA$F|m~kkMQ=-CJ z*EVbvw%uW7)^<8$ZjG06LX;D*)C?aj9#D%cwS5Y=hd2H(UXU~sEZ-+ov8~Ca<|A!bSgoUDOG{sI_&&%$Sd%d zdUDuN*%t0PLuo#e*hFc=;X66A>)35JrqVfXU4&~s7rCYc&>sa0s+faqpQIyi4VgN<18u7EjmSh!cOB^Aipk`zj7+}S!D<% zwg8EsB)4q6;o7W=5v0c^f;F#4HY%^?|>DwOG zV#2wHbj3XI=@jPKmOvUAH(wuu?})ghekd2onuL3?F3erw#%`^Be{+(8#lf_M!w;b{ z>6CzeKAwkX07rg3m+=Q)!#Qo-4qm)i|Fa#AZ8NPKgeE}ynoBM`hO@D#e29W6$jB{j zx2G%|xVG%kyouzBVpRdb--HSvVV;!P=?L~=3P8Y0Ca4*^hZZ?s-%Iw6nHY*eR zNPV_A{~h||U`F{_&la)6x`q$%YLpeTs6%*7>+}LY7N`#m)KjszQ*PMN?W3n=xdjxsyZ>%!qj`Uk9laKN+^-a2suWNm}U-9%EXN(omBOg4{$o(He_` z?aY@TLX0K)ZEY)VPXsIEPBKr@=hw|cg*ELqqW}@^s3H2S2a<8L+3M?_+Vf-8*JXuu zt++=j5n_~f9L`Xkbq9*4(mw!y8Ftr*kEb5=M6b2M3fPH;^R@PI4BzH(M!sE7Zw#`$boJmUUcDzcak!rXNK90cg zS0ib49K@44iQ2u+;oH;6_+W&SM3_P@m(>#ZOOzL522Pm(q&b?^V-8nw zxfaA=ers5>C`g8{6d!$Fd}n_Mva51N4I$Ao#58GMXH0J`O-5|Zu3r!h zBf*Cbgb%|Bf z&o_V)DJhLx<>7&M|E)&;PXrl|wJ;ZeIPmZJe^)vC_uBvNg8uFRdH(Kx|L**uI`Fsa z!Sm?<4aeuq(eM5hPk2w?AI4Vr(BBT$iU02I-QRNOZ@Kf|R_-Ld7swgyd}9XAZp`vq zzr@i!5WBJacTM+4|sf=dFiE0S1@^Ivpy1T z3~x{kHju8d=N_|8=hTkAEX&TmHR0@E{9YRPuh&5}aKit5b$Ok>1I}8=`iPLg#hUaU zg~PgM-^+w3=eJZHb#oK)qD|y#-{Ile4`TDx;(|J2oW*#5M2bMMj#y>suK_nleC88Dul^VIM2{gmfCUMbeE4mPzqI_;3Un)4&T zz&%2(Ju&Gn>~@CU@%21-sAULI?);e_@mUy%Z1t)^cK3hxAMB&o{XpY2 zGRm3+0+W{mwCZwdPFKS;r>QGkO^(`;N#t+Zv_60;o{pP<4RjAg)dwjBLWk#kj2mAK3zR&ZP%hi#5F zf*?xbRK2?P2QzPip_ELkGoMQ@(VHz_4!#I-c!G&>23&1bq^pJ7B*L*wUV@&*BQsvxaolv$qXoTkmVbFP`t8$co%p!4pQrEzs_2zop;nEKuk5 zPQv;Pgi*EcYd2opRxizSK;cqW5#&gb;p<)w_2)j#3?N|8|mo2mjscV%zi> z?Qp;YV}HWOit8D)bnqSF+(GKS!xaYMX*>F}gWK!RXHM-{5`E-pHfPc;G?UzZKVKa? zvRdlMx-awh@J=%hJSG3(jF6J-)=c6wjh%S#DAGEhMt^MkN72MVFPXt|X33renF#xn zKE*F~I;eZeO&ha@gmsD~gM8DJ$j9t`q?SQsJ}Yux@a$dT6t%ncPMn2Wl3Ulx=YYcd z@*d0x8BM(3H0tS&T@3~GE-^R9OkU~(b{VJg1_SM}zPaurnmadsLgVDP9XV>IFtl!; z)?oK!c>W;!6fzW?erE0{abUM&m(7NP0gM8~8>B zU>0d#AJ6DWYfcM(p{LZ|-kxvYcZp@&!3q)`PRZ~q@AfsVqQ~?Ege=+%y^WfpI12aJ7_I&j3&`R2o593_!sH3~yb!tY- z5t0p%nAGvU5&Py!x}RFyG^Y)hPlJ$n8$W&MGU@PYq84<$HEbi&N#h^T+UUfYg=;lj z{T(_oFBstARQ2tL#}}le%&&imQH9&g_<&mN`A9Acuk%uY6)Jaha$ez$5D3v8VKt1J zxN@~AIWGh-sriFvb5iN#P-3i0Xn0oPodlUa1&*%x`!X0UlLR>>Z9CzT(~_+`d^1i3RC zLalK2c}rKE+meg=S#&WjW%(ONhv$l zP-XUUbjJ?ktW1wI!Vsb~yL>44shnAb*O>jW?EExN5IsoELoqdPyA~%a2q_)?-VN(@ zqgoPrE!EqjKW3VFf_%)=+pzWJiAj&Ro9*93%@dDbDrGTZ9eoOBf4fjecY1s0Wax~L zHq#E7D7$QsQNF#?9ncCZhJrnhC54-u^wGCa?Hp3G&`z2zbq!agB}|)%h6Z)W-^|tLx-8q^U2;@97?os+h=L8tR%m2+!=$&Q7RL^4&~2rMg?a zx9U;Il5-uVfi35PRt3LzixWJ8sX4HYYD1JZY?wQmx_JJYSAdC6gV+1@z->#vuCvpY zwF((AjuofRSfx8FMLw|Rh03*CL>zWEnb#G?_PF(#*L8dOjdiyo=AxP_4K_U1KoVoS zjvr+D-0Yb;jh+ZaF;zc_H!_^%_L7?FZa5p;?`Pt#)yJM`=n&UCxbW5WxVV`gnu_)9ISJIrI>7%RCbc3phx z51*RB7I+s4th5SQPN>*2NC*rY$SCjgZ>Bgm(pi@~#Rs+X`fEa`ojIhs!^V7)N8H-w z-n09%wg948-u}7rK4ed*5!^lW=RJW8+ud-#5Q&ops|k?hA`#7#*z@g~S?Q$Gn>|Jn zlTxi_k{E1*(hT+dTyUeBFx9f=@`0A$yEoD+PF$(6jzQ%A5NEPSey!VB*f4Bg1Lm;T z!R7J(bzo{=vvkVxGeK$#?b$yd+2@~S?K#nxQI3b8Yxq^vOJ&wq2oAM-+H14}pF*=? znRrXRoo_z$DU@4YAp!}nYVwYJ@T`Ad`}$zf^4wDjk2SqkLEP3pu9Iq#qJ9^q+aj)s zE&r12n0*QCH79*^ms1Zv&Q^@>yFh*W^&MfQ9oXi+;lxyA5E8uIcde&oSJ0c`w;_UV z=Zy9!<sMvZ7~HtQ>pN3vrlklbDMY+8N^lm?NFBa77d^W8YH z6GmK!aT1nvKo;+c=Sh+i7*R<=7=M^JA%Q`99TwSDQEVz=aT1I=&uO;A%sdbm=sUYV za=cyf%qxdX59ESHHHJ%th77;JmhLJw!)^lfrqFlZLOhS-exzY~B*;pHuQOi8p^#&b z=H<@M517joycSQ3ccA8Xj1voUmKLF`X$Z>ZCyGLUkf^y#kYuE!J{&p% z^sAIkYJSi-v`DQRYXMn2l@m2vT)Tyo}H@}8)w1wfmi*ABF!Ju$1Sn$r|$CprI2n@Rl5a+;QdlUp$6$o z1xbzL$DoEbdBR?%z;MwvCtPKIigC}4TD|L4H>T<#>ao||KosKhupv+g^Z@o$o(0Lx z6Q54k5)u%-`1+5Wsiq|&z+_xdivHrs0)BN+{P$O&8U9DMgM;-Wm(@}lpjic}yw-3! zNJ{zlQF)-d%H8WOu9155GjY{V>Hd6?)P-Nn%Z{q9-K<}!U;;?W|6cxY%ltbC{ufM- z8yfHOLCwer-UWLOV6QI zZkuvYo}$PE(w@SFmQ;OY?br)8Vv#Tnv=2N;D<#`^mY0*&r;|u9Wz=M!@KHnm?J3W&IlI&GRRy=*Z!cGI)2Y2g5VweHXQa?j7}u!@PR}A| z%|FsRmdK34=T2K2hnBQz5Ad9Ux5bn1-ZVL#|m)h0X7I*yFgmWv^AM*FV5FQ*+zUoTyt*`OR5 zdhtxxbY3830S@WfKB{wo((-T%B1z3R5N;7a-p&UnX9X05IMNK~I>?H6Plu$T{=^Ys zNG9-MZ>?s;r^wcI;I?fV_=>_84z!l|3M?66g0&Em4eRnQM*`;5R8CD>K-r}DJZUDN z-o*hJGt;jg@H=z2KA&#BU|};K%9jG$&t>*mhG0jao!S!2e*-FvfIX&YP zi9EFUDJ;GYnSS74W^n9p6P^hByPfPkB%F6~l7mBdQI`;1cDRK)RMHU2Z>izrDgP?i%eo>{`LlmO5qhU&HVzQ`XG6nPfQl5?rNHc%)bXWkG)EX46l%=lh%O-f|ir zm+;Kp)1Kzx(BMpNaaVzS&o4ruoa1e*ss3i{k<)murxe$4#?--QqW8xunky?}(nCQ54t9TeJmO>sHV{>m9KNRdLL^dy5eHO;wOk59c1+i{E3x)na!H2W zapdb`1{O6Z?01t4A;_Hi*HntLz?6bVif8uDH)t%@n3t-KJCL))pUC0Et+N!QOM~ZM z>*R8lvO9^lm;4}j+u_?qx8c7TzdhJdw|~4PO!Nje z=!Aecm=4Q!*<$P1uVbx;vul;lW!q6okb5l@Shuy0U9?=UQQ`*M?iz5T`st|gy>n+s{e(VT^mfon&id;CxqVeT5(mZkvR zhC-KMV^EKUg(yQ?K8c4e?s`}_2;gW^}diC+Ge+Fmt zi0@Nz?)=@d!qELlW7wvgB3-{)g5>q{VVNdBR4Y?+WGrV|!hf#FUYG@2oIBK@hMBu^ z1j(y}s(y@Qk}?h4y(Q6b^wekYG1ax{_U*sf+hmuFcC@Nf)hxnhg%>QHFy|veJx|lx z)p(&~pgM3%#E~U~2$2!^pm5;461Il@Ji2GK_Cpe-CY_qeDydJ@4;L}#ZpW-63=wu=j4({v(IXa&Vo7I1;AITZQ4Oe09N0UHZh8pcd?TSg2hlASt4P1bu z&u|4FKz^FTsjqW2jvOiKiZTY`far2He@0yLu+7c5mEr@71r@_C{}wRmvo~BpQ&n@Z z#x@7phX#7zIej0HG_U_p8ttPQ7(!A=;;}J0vW9Q%aTfAC`2ZQE&znf9yUF>xp^T;T zMRf&0YkxZUnCD{L^|W78Tj7w|qG;Oxbc2IV@c8^lJjkr`7<7^B_W2J}G_4MVfo11rmN*E-`1_m80ngzAX?jSaXY~ z<-oCHCAvY*AX7t|_IEF>-A?lpqP7vIHO+E7G#Pcwb4qUc^XFbC66=bbFZQ+MZoPwL zjcjlVIU}e(Cw95@8-stvWFJX?wxzK5OX2tkWwn;hNuGkCI6S&Xk#;D;%PnROD_tN1 zWs%~tC5F;HW{B(`(-Nt#w15}s&>WV#o%=gUFY%GlK7a4ZCF!fG-zP4o0dL~j%@o^5 zMw72)ABM2S|u!1**D+GiIIf_N+X?;$*W!D;VrWzCeFhMC7c11EOp|6aK($l0~f z@H3BjuI!f zL8sJ+NJ@Ea!K~PorsQcu8Oqw(o0aAR3qZ`an@V!G8HR!mr&5|QRah~@hj!W_K?M}LH(;S*2@&0M*}j-|h>Pobrmp^!H}hmb#Tv+R z@{&f{`yH)>7as8+Sh7N3J82L|aXj$9Kz?K4MJ8+!%NLAu&LrlXIf0$qn~ZV@t#O1s zHRV$^ijj)}i$DrioUZomsH?lEp;fun*Anp=Z%jX+C>#*s8=^t?_QD8GRxj!cUyQfU z@H8A;ti~VIZB(M>_f6$JWZY}ZR-^N6hiTR!sM&?vjj1fIOo7c^n?Xb1=mSFB4JWVA z^iY?AF$9Y&^~Tk94j`yw6?d39g$yxWdr%f0Ud}c*HATXX4|n~bw>pLzRBDf=TRHeh zBC|_|JkRlM?p{ftwhgL>)&q<3U0^YMCZeQU>mz~8Gv2w|7HyOegABO`lP*{-^38Fz z&3jpp$+E+gWtYTd<^9;6SSXgVm!NphM0V$F>P-0o#-RoEiWc14h@gwSf>sj433@`i z*AbRliT`Lv&lzFwb2z-eJo8y(&38XduSf@*V-hWeg6y`YRvWL)YrM$wIWu-=rMxfA zkT?cD zt^CW?v5PD6M6Gitv~5n~;|=Wm^h(~u7rF$NjyWjG0XX2r0t0?S){rW?DMve-@C+68 z8`k|atm86;FqvK5tSWWnfYjzg|CMlRbv;)Mw8wRh={TE}Jq?>HHJZ?+dau6fxt+Ka z|9td@;*JCtIA!i-xwEyz*Q9Fa!|!xnub>zVNYm40;cz(jWWkT*z>U9N7mRg*j6|ju zNZVrD<-hNTH!(RId22|d)T--(x<-N#3gxF>Bx$W^$ZePv@r^_(rp&nls*$L0&uSwS z4|cRj(m&Cgn~fs;K*sQ*REr8X(ARRI^*c8)N9c@b0W==6f$46_B$~&q;f_ks3S^At zj6o47N_f^%lF{0T2nsw@Bw@e2eHReef08|@4o4AQ7shNdYN?IV7uM1SpY%mgu9c+w5kOuTNGDcm0H(6AE0jeEhng(V;(AjGL=45<$nkqQCw#NHS+pNHU^y47jI5!zN~y`ed$nvh9+kt~kH&wst5I^#twy;j^a}#CE@tC_w;HV+UIf(rARwF0p=SC35~F zy2Q(0RYzQ}^RQ)9d+{tfk#71I_;$GV=QUCq+ZSb}x-YL7%HK(K+^0)zKD!dkO1;|n z@8$nHT4uLZw5XOFa-P%gCX;uUo^$>=jlcTp-T#6etE4>Na=dE|ZSIeh6%*F={pg`x zu{NOE7-is#Ig|=~BjTUGap@gpr#3WsGy*``p8vO3u9tQNa#QGryxae9vyl%3f2;V4 zJZDWs)>|YHiW>)BljXV8rC6y|qi zsun@kt0)qO7<-Ta*gT-RgS+6t5s!#!>%@eVT~)QHAd53i#ljx#)JtKHyfuw4*~#UCFI=h+c8Xol*FGaG;|-E)?9xY z&U+?@`8>ppb;{utqr#0_XS3VTnv&2AzgkFNs93xcT_(edJjktS8~=npKG zl~7Q*tMEx#f6_q9GE-7gH`UhfXe)db>BqDZGE;Wp@(*q+OkcBJn+N{OX1PY#~Y`u>v`Qg?k}^R{ZSUk_*GLVdk{>!s7mhP_Za4M4VmQ3*AXbND*7X z*a$NZV*MlTts%vCrNgbGFN3EN)`j1v%l@dGD4oq2fBzql?tuDOF`MaUo_-mFz4#H| zpn_0Ibm%|89Ddgt@Py1!Y~`zG;$3^Wt_sfY9&+}lfl>9(@JjN>R0CesDwIPNW)L!i zV8KxBa<7vp{y=QC+WaSV80WX^hU6;$1g#8LvsehLOtmx}w$p_?WvP^%?CTNM=*>=x z<;-DXR97-F4a~zgA8|quZM|A;0yQIzSh5x1+%)d9iV5HRBgOoKWzkE4N%!Sazu<96 zhm5*eE z-BMHihA2{^O$?`|sA3SwUDcbLuuHBtpXA|MsHZ^ZbKU8LUT>6DN%TpYK1gEMDw zaCN%#A4u6;q&|1}$3>dX#bc8QJ%B>99shhcz2fQMYU#mZ{fT4kJry$kYDq5{Uxe3 zdf51xpYib<0eT4gW_reG(zvur%)2_)cTA%gP3;|8x{ueqPmhBID$CYeR*md~jk5Rw zCEIjwo)*2opwE>7j?8ch%B8aV#OuScBi*OHf~c2K+=P><`l^H}(LfOunb*e%&^2w? z9WtD&LFUYEqniI^#GLipRi6kJHl87Xo^oPiCymg~@m1KCL>Z+_X|8vRgD!-33+{y# z3mb7H>OnTGI4HfZqn`VW-P`aL^JaFuc99-skoo+o%o;9n5_*Z7J(&~3<7Y=IX z1f>+DLR2*!p?WGI0Q@Uk9B|1zt9>m4p!nRCHX&gu4i82+ zKKoNCw0UnBQr!`TCEZwPJD;EzvQ2k5+nPne&<&41)9-f#@Lg^hpnTWXFQa_S^Wi50 zy>c+lt{M}cuPds$*+g{f6H47)rJsljZ;m9E6;fY*Oi9{bmyV$Dfl28`d{JLh)HPo; zpwb)$M_ci2WD;(Z&L8ls74Xx| zQ&f|Bw>Tpo0v&iG&bapqV&JGDf?}A{J5=UOD8X`yr~Lh%J?b4P7BfSob66K2KLHRW zpE$nXp^G)s(JK#)VOjZwTeSlDLJ$U72cRm8TlJOpTsog4LAKCr%3N&W;h(P%a3Se> zmBaVO%obyuaos$r%$rMrn@7x_xgmS`_gaYILC>=k^5tO3ywIM|1p*OQ)- zcCKJGp}3b?uu)wVjI6uG8pH$+PThM5U|wFr$6rP%>zPOw?X{X0}LK6Sh-I##pqht1mmc%5l`MeM^sW7m;FB0BE# z&ZJNKEB-0dYk&Bz)7Bch$AXgHwZD91U#h}LvrZ5|P zsb3_Sd54kVMnxVdBI*w*kR2K%0N5Js|42*SK~!7I&57T*hTBhV$`yO%TSOyxToX%4 z|J>u;?88wz$e5W+Izkx$N_E(KoQqGhD9-A`twE4(VN_ zwcOjd7k_|9qKM>RB>Gt5Zz*9fxmaS3=x-rQMp(dS+7-PqNtg!!l|}QHEhQqRwWWk>yT1tH=)8`J5r!BPVP-mgEhR_5+sQItYT)}R(o#v! zTH@jRYf%Dfxt_Y>gGC8S#gb1=SH8l;9I(=QWlI1DF8@@dhK8m$WHh3oejAxCH?@w5tE!3?BxEc0X~WUqGcjbzVw&TXAKr4Bzt5l_ zBIFbo@YQFL#B*o|)+ii*4xLf{?T~Q^JNdno_rZU?2>(002*P{v6WucI(ZIpP@>idF ztbp>(&ELW*XDdu4U0f^mX44|dn9xx?#o%@W3ijFd75q``ZAI89e{^d5{1s2z0QZx% zn!1m9-5a7hU!!RV-44f7ueO~+o0><@*6u|iRGmt~`&r*@&R!vB@7&NRS5OazosB~K z;382^e`;~Hsbljuedu{Wj|)|Ies>E{0e39i#+oo-`M~KrSk^I~>=nvNHZJ(xv4%WZ zTyEFvcH~*#t{w02jg!(QFxmDF{}?^L)YVx3UB+IJ=Ur{jGZ2h#s%vjjbLF!OIpE{u7QjHbCt+|I;1 zqq3_C3qIvd$1BH!IRbN|u43vzX)k<3>Y=t^`>>K`g#K)HW)$aSP4l!u; zdWpCEAh44q;6usVVaWGl()Zps6r#bk-D_CDYC6F{a6zuCO?_K;Ad(Jv`+8Y;kU+Ek zWzTa5TSpvsDf7G9Sf(3S&_Mtu~o`S6{l z^S3%yc;ikPin>`0;z&n__pDjii6!SV>*0z^rW*Os^gqsKQ zb4ZuNRI+Rb-`^kTNkrI|z6}l$OuW7D0-6X}T+8K6LKiq?Z35kroq$NgF1PTn8rGJ%T+65=gUdoFx96#&_yzYI?; zp6yArgZC#B>z)o8ZNJ@4n48t}pT4b>q&22v?MHWEohb^4Jmc{N&n`>KUdIqFK$*@U zbizZb6~dw4?I>RS7L8OYH0uU9LS7x9y5UlnHSOL$T>Cj3Qnt1*;(f26jW}V)dETzI+pv=M7h%7Y%yJv zM9>D(A?y$)12;@;4Sq7#AO|Hs1bhZ*Y8D1W2lxh{71xTEG6Tp%sjt@h{T0M!PS`FlCHdx zxZj@)S5yOpMzFbb0Wbh+Z9b1~wKl2Yn*i!=jl@2>M_VU8a~QM^HtMp-a70f08YYybmt(VvM53v(Ls zNrqN~zQ*TT5AwT|l5Vw&K1cU7?=n5dxord`@8{; z)po3LfZ!U((bYyiBk_CiU8<;;BL^JDV|xxqtDDIseG1WBAxz7o#$Epq70=I2b-Di` z36jnk*pI9Z{GuO*(=@kH97mq6Ri>)S5_mv{?l|8?kUp?^8?|M}%h8ninIakt7~H*Y zjeIDRLnRqh=^7~nL2z8vvir{DGFs}$HPS#(TqeP`!d|9ug`FQ%rg{!TuoJJfW<;L`=t8P;) zdT0$qxPy5%N3ZVl?&5vKMBBNtm7T9cbOzTeUP{DxB&CzQ;UGd?w|19hpV^^>L+Wn{g2>%k{4Ks&ojzT`$>}Vtjo-WA(GJo5cS7|IOgcnaC(tbkXDgck$fAeyys=7J_9Vf|S>vc?R=}kdb z%4_CMT1vWmvvb{+#V?G(JlzNk9bV7mY~o9ElQ~2X6B60CgZ%#uDtQ9PP1r*+%Suuo zhFH6mEV-z>n=A=suyThZpNLy~PFNBp6kAIP?NgMcq;o3F5;M;&$rY1AG2b(ZSveb+ zAgD*QfY?Qn=3<07xIbjB!Nr6x_ z$=2Mj>Kd!q))Mog^2e_tmgG|gC0BeAaqthdj`C(M*D&mfxSjl)rIPTl5?KhHIZp+P}1TqQ8LG-!HhGg(q3nNzR$@Cy)jjQ+$S036{rjm3XEBF6I+PDmYhfAFsy z^*>&D`*eNduVIZmc`29g{|P&u8u`Zi`Yz(1=Kh`LIzyWamh0c_T%^5`7Q$GJ=flX} zmQ`=kza0|GPT6L_`uav;kyhW;PM=dGrXBKbJj5Ka5^~laA6XeWybYuaj@Lh+ce1cD z1vs;i`a|Gd6%scojg^4qzwUZ{i7lFnQjpo(^+R$hOKxo@UYU8>_ zUx$9-u$R;CY_%4w_kLn-!o$1rgDfhI%fXLUFIyk}fC_zxz=VM>?$8aD`VQyG(cGR% zBcG5H2_b9GvLRGHO1G=hnk9!fWZMV<3W)(uMgfN1{Z%zjh3?>>%1S`~XT+or(&$PJ zg)b_wfYD`8ldis5R$CewmcOpg2(A6@PryfU3FE)Cbt1DrCX(1zewFD}T}J3JKtTJv zFjq#}I-051xBZKE)K86S$D6IyzjC>*bw}acAg$Ns;>TF?vlA;Uq!XXvI`c@GJY4_E z4Eqbx3MxTr74Viha1|JmSrf?nB7_a3RN3|sj&>R#GPRym&OcGyb|54((@@?(Tp!O3&~-EZ8(XL%U0_Om58QKbp-At= zH%?J68($@hXW*>vk)G@*Z46Vhu@pPz*V*w5(8S;|f(0^CI`6{ygz)`LbmT1P`_u$L z8d0RRa$5U4Ssy#RHL1$hwM2t_=hZDa)P{ybE&GaDKdGqqG7r~-`x~-|Qb0?XmNi$68U5D>V1yZ52j2E>TWrjp>q80V&fx@Kjgt%e- z%`%;-f1#a^G#g<-m9O1LVwN*>$Q>-}r% zxX5gQdJYve{2xq|miPxC+HJ@`gtI~{gPf;|qx0QVqE=<@UXtCccK~$ImvO~XewMI0 zxchHXRVH|+dC{KnHZ?#ha*_mIHd;;2t*krKON1i1IFX2}Hf!dYf;7CU{DSsiTvN}O62)%P{xv$R#QkkPg2 zuI=xh2wW{f=T7$Z9Y<^T)s6Hr_uG#yWQP=NgDKWMaMcV7Wx3ZbPEEj!K?LoH2pZ!L z>*l%!S4+vG&=5<67%B5reBg_yKftuk~Jn@a3}i@&t(L8TJJ3gFKwPLudU-mUQDj_ihtnWjuD-{+Nox-Au-g^%u^g*%Yl=KO7P(_{hMlMUD{mC^k` zd6M2`)gLH~(6NkCNwn#QH;Sfg&V1R_5Yl{B&=vWIxw1$fB}mT)G=lMsW)?*FPl_vz zfw6!oOVeVREdiY9A-ehcTk-*C04uosmzG159~Z`3vIoD4JCda*dVv2BpkBaPR{XA3 zg|J`Ade8yl4=tqt0uqw4$b_ZJrCT7gMjUqUKe#VXaQ(7eH4R^_?o@jWQckqzq(og^ zjx$dbOVuXazVL^>89QypLyUAucE^zO3C@Jt#FDo&e2wJxuS$PD^HJ+FKh)TpG!dme zCP{k-B~8&z_~y}cml_kje`q8~UsYE8{dhnLCejJsLs>#SI^fgDXD9)E3)Fnm-)ep{ zl?C_m`A+76K9bly4z$SwqAY3(LsSg1U=tn3$kY{DC7B*+E#F>2fn^^P% zWTV-&7-RH^c{B-zxoENsnwNbV=dE`GYGF`qNVq-O@ckOUHDyOu>PAPyrZt^c)yvK* zc$uA>2rhrJ*GuBmlU*1`dnj;FuWh=&^?BDyp1GDrqrK`DfRfEDiAS$QY)R^Q`CtA$QB08i7js|GuUx2K zyOD@$bNj3J5A*1>3|Hc^s8UpC>g5`IHCO`P$7MyVrD3_AyD;0yEeT(B zGmC(&*9D|(&|2;kD=t?o2Hcy5P{eLBM(^0>uPFBm-lbM)MSn4Xeg(sTCzMMa99;H_ zf>;^)fMF8Clq_`|tp0u!?*2xXcp&-S{^WaN();^Ph!SdyGO+|UTj!(#{BZt;OU#N1 z{C<^Gobmq)K>ojp`~SWCze%6~m}}V@7dJe8Ji&0M0vUPsv-oOB{8IegYo|sk;tMU6p{!hz{4ND4 z4Ve|8?5N?7wQ1RQmqMLAaJ50ZtMUr0-CX-0Z-Hw~14aVl)-14YgIY_!{eEq%b9779 zvbHr&CY2U-y{@2kFYkE6sV1v=2U4+tbgVZZ+bn}YiSm$>?ebl0X7QOT_j&*#~s6&22@aD4x*Pq1@h57Usb3sbT|^} zwEr-3dSWKvw!YSl_V5qSR&4yZbyV?3-z>G_WpAAaOdAk$BCOeIv9(&Rx&^W5T?SRA zY}Q~%=EGXguh2;yJN*LKbY{giZ8!!6`>Eo(9vIThc^npYaKb^R0BMHxq@>B&Edwvl z7XU7r%>n?=jFH}yldr5<2@a2)?oZNFekjCgI-?E&8&O+!ru=g57T3ElZ#os+M?C@P zcYtJMJ=ejqY5{zUxI8fv^XS%#?tYci()!ggUfu&lkE!u;LaG=QV7FMd;wem6922xfR(Se`R?OnSs#$@u({# zIG`qFm_z}#%IH7cJlEu8vzDruFsJvx^(;||Yy->dnZE`$+{x4R( z%;t?|ejaX`?xN1pz`pn4@1ueyMb=q+Eovy2-(vhy*smzvE8z}V7I55An+4vTEeRmA z+khb~l@l4_JA+!x=rvkRpQsylpoTn0Rp1{j?`zphVJrY>YhVpzPj0Z2$&2(^m%UyM zFDs=1xk+AA6`U1Gac}HiHExBU13%`gZ!k*&_G8#rM7YDx=<*kiJ{6b0J~()-GBv(IUD9*l+57}_ZLsuCAnnTw=CRuGF=`m_Lr!aEq#Gzuc6d;sTLSuGoXkW}*K30F=Of0J z?Sz0i@wr!$J?S@BCqvy=lU82c>VW3tp??Loa>t-N3|#B{xvPE?t<^7ScwfXUe1~5) zA*_dM?iQcX8xYOCp#+FkO$+h`B%pm=VTCm&`#z0)Yjvv&ex_@`Y?-Uy2}nG)V_$Yu zruum9A|eO3&pJ|v%Wa3m-!z_ zCX=)sZRuN?!VKTgQmH`B&K6&wf87X%1WZ3yRprUiorEhAj@vT*e@opA(Zbn}$>rs0 zM2+EN|A05*EM1iS#GHB=D+*zW7zWO})miPb?r#hSfb_TXMv;R|Xm(!~;0sfi19v39 z{3x7FSVr#L|BJ0#Tf7wQh;5F;E=$1iq{R1?Wd;_X(R2B9y`VJd?Qa_|6h41t{QSdr ztKiQYeJM8nm z*6?TNT?3lzj0+5297q|YSnHKP3@q7SX&tcc^iPIj-nFe<=r11nFT)_99GTq@QpeqT zAWEpnY2BGwHOzLpV39JBL)?Oi3Xn8}{g@O^PR-HMIvybj!ZkxM@OD26tDGk+SP;t) z?*vKegNMf@=)HXnPIn=LdvtD%_#G23s-Xq8{@3%-kYPU88AB~{Oq>I*CGvYsc4ubA zwMB!k_r&`SUta273K}sI#Vl}Jv_}|F@j-DaA1zN8Fw`zx@!889yx!BgkWVBi`9ZO7 z!b+4NaY3RGt^GuL&BYHFCE8)Ll8sMZ_c{<)#YcxT({s1#1lNKu80ez6(ghzHt9e4o z`4Xx-I|dWWPYrD0bFj?=1gy~{<}M^tFt?Cjqd(ItBiRXRcxFlv+ZjYeK+pm>@jX{& zDio3t2N4F$1w&CkC}DWPOEdV@TAzwUt};eGDyXzrNbO?q5`rC5&0{GwDV+C4 zI?$H@$@CqU+~P@cRy7#X6#1*e^0I1`Ro9-&3YMZePxt6RRgS zaqINS1|O_o$RW_XyWjNE4``qd!~}YCP+vN$4aK!vYHXx8&wuh?!l`KdDrzj2gnU30 zy-LhS!|$fc7~};e2v_NKpL5-k^qtDNDw)_8TH|eyv$!>3IM8uX#BpYmL``GT3OOS2 zoV^etYVYiXh|wMe6fs@2kYz9Ii56mcqbd+zyxP(<9LqC@&Gm`(iNb&q(M_Tt#$MRT zKw^24NiR&lZ;rG0w!M(J#8Rpnz%24}DhDK#4httX2USb0#^_6wFl`=OJ_?&l6rBw zy--s3SsTkst$LXt84E8FB?Q&eeI*v~F=w{q;~36~JuVrG2CfpZBm=$>Z3)X>h*`Q& zMiCPhGqAh|&f363XQFHDg*A)NssRZug9?0+M2~vsD*FDB@WkeI%Bxv+)$kV~Q9eo>FsW&kM8uiK^!Zw#XY>E)83~UD3Bs%4N?#60CZU0q7@m*>)TVQu5rF@2 zn!1}SoO#ay3zw86R-$ZcqV@oVV^Sv&MTC7jtg`2?Zob-bMkKA1T??L;nK-2nJ8QW< zQ-`K_3dJI52;jXVKJ_G7j?4 z&d#};TEaXcrIM4oHQxJTebu)6(}%WzMT}~ZElSw1l@V2eNxN1QPJl?s-tHrt2W_CJ!$p(lKsbME^Y zUm4%8_x<-~3}EcAv$OYFYp!|C>zZ>da5c9kNHhiE-zlswhKZX>r^*Y%}l|>&=tW0JI=l;SU79U=}{;9l}T!ozjg{e_TYoNv22ZJ$oTZ;bIz@W z60`*GXio>7sX3@}Q)oDj>J@I=vhK>sy;m$jvE%wH1!?DXDf%l$x}Lr9Bud?t$ZA=_ z(3yh5@u9vRFO6$dI3j0Yh*SBQOz`sFvRx=Tpgy0A)Ob)k0k5`woweTEskH2@wWsga zIPPTYRj<6Jpb#E(nL|(s6JF;(J~tZ6C$gLvx~3=Gud7TEj9SM@ciJV`qPUre-cFag zq?MJ#spX**b*t~6LFhDF>?xePdWkA( z)i`lnNxcDbv-Qu2t%N=$$MD0#hVXa%^EE;P#60xxKQn(gT39*Ny|<}6?Ut?MLB)Wt zx<&_Yh1$X22md`bZeZ3NF1#k_=uTLrOY+u*Xoz26TAa)8!5y!7Z!7Fxy_?&sv|?QP zhgJ5=g}qOKz8xOoJ~*U6N}>u#=^47PftEd{2R-qk>39uZ-nSCs5trtiFfFCmdLAK zB!QL0JK0MDoGl?+q)eO2PJnR}Eg@xPW_dK>TYz`H2uNY zvmG()9UIyJr#h}cN$;k1ky-m8r%-fKRvSc+O(XBfbYf1U&Xf$ctn2%(xsTUSN(jmq z&@fJ!`bp$+Wbezxo&i0!Y~;sf@DHI}%T*q^E#M+*Jw6TCubNa2clrW|f#+jQFQ<%l)k8iZV6mkQoiR&o7`=fQ+n$i1QEPK9A} zy-3YPS&z0qhOdZ;fcs0`k!qH#*0*1FsrF00$7*TdYKVh zrPIo;Rt%&Uj2ojNIA?x1A6%N{39%m*TZ&WRS@Cu^YsQ4{U;lhDr-ChU^rMc4Dd(ek zWr(Xj1}r_Vzi-P>GD!Y1`%CVY)&(O-h|t==SmXCi?G#DbLwEmtM&tByKD5S1?-uIT2I1SBJ!GYnu&wPM1vJZF7ywBXauwq_g} zt{@0_4Pqf@A>H|*BmS+}h23AD=4aGv9!u5UR1!FWpi;WK=ev=kEMd|-s@$`pr22s= zA)#>78yGJF3U^1zx8B!MnRGt-gGN#`vNzANOqrrU6QZnIwIw)M9Q*DeAsKZ=O;QrC zVlh@%>L6o?nOE6>fXM!1@Q?+=9!`BTB?41a6OcoKHq0RoLr zjT;&dj==<7EwydY-Nx)SgrSFm%$gY*btmK)Ptc$>(HY#tB5R*B)V`5i5L(F^ujFp* zISuVmtfmM$chMa==o%d&N>v2M2hfs6fZ?sXb-}^{W34zN{G#mDktbRaWoVw5d03HJ zqM;=e8I=#kUYR>x2_pYVlN4xRV2Hd7l34r>h8aGmF!UZq9tbXDTMiKLQC zEWbimwr58k8C!PDd_|^}Q>=>Bns)b%y~0yF1mo7MzLv;o+~U3vM=q+WN*!@P=n)t1P0R_{;W@ zYf%X4J$$+6DTeM))pr&Pb}jW9j}KtW7dWz$HWiUB-L>ztt6p7JuFg%2-hHmD3CUg! zcdf>H&{|9|*K)V3nJrPX`B~#i`faQtWrz98I5l{C$uT*WRl#n$*?fGdVeJNUZiyUy z#UL5^5?p^81FB3%d-iRPSGYM#><6=BaHD18TAe>6C9CxjxMnYAA6KtNmhUO^C;L7| z!ypz@w%4R{WaLrL4cMwVk#M!CCk@=Yr3tof@t=P)21jGWmseTGZmawbIF?ltE?qD9 zzGzLBr%do3hn$%}Vg-&SsDY`H*7Ntf`Ac*3iw0e z@$Gefb~$GS8G2~e8st1zYp4C9n#;D&r6_kYw_;MgS#_g4&laO`p@a%{o{@_x^69xj zU(^R-a8IEX0wpI9gZ1l&x5Y1v=1+X#JzcD*I$2c)J&9rjk66JvM;33d_^T}Y90pd` zH;;GWj!TTEGwv%ywy69ZU)pltgvE=p=Qs}i;7^5{xpBRNb}Pu`_TLj9z7RE-?QeW6 z5d7Q!k$ghn5SiRO88h<)dNn=Wa_f-)yZ6qo?$zyMY;1wofk6^$6T5s{S$@~>MgU&c zGzXRWqyJu`S!{c-<}zchoICkGM@ntoLVxGSKHuZ{GL`FFAh(%KYiX^eU`Mv0`LOsj z{plW*?f5cq_#`~Se9>Crvk})D*-mrBRq_3l`yvptu~kUGzJRbRfJ2R4_BupWf!R=n zzYXZ^lwRFrm&?>)-l(GWOdGJ4>pF7LD0xvhZ*qYTB4@Q6E7sNd`B(Z@8dc6_IdQ^H zJXtK(xKUzn&q&VOnP^j^-v9efaHt5c%*B~U>uk;xr&@x5^PE%e`PAb=#kA(JF1fYN zFV@#g99zdY4XtMIqf0w|KRVS#u#W0%p_(UA1GO0IDid9lxksS1=&0BQ1Ud}_y+ESY zZs|r0!5v$ulyVFGGlsn4`&Tl=lDg)cp9gAmbkE4CDgJ@LP4E$Dfjz<1+&HmVG4(%& zr9y<<0MM8vmi$&lZ-J6SH+p!@<%)q%lBfW3-AA=U-$-tN!i%yoy7pmsuDeM|TaXuw zX)>#9-_<@0%e|I9UBKMMSW=^QO`RW`aSW8gSC&g!JA6`QcxE<1Sh$cI5ze*~4B*8- zl_Huv0o1&F;s$87^v*fT3;^%JB>0jEf+tLtGiDj>DThV(i*o+ z*@m#cK*tnviwqX&cmNF_T$gm@$@hxG{ah*4>j7?h&V{nPSz>_2hH_fYkJ;w=vlC%m7^VIo%?xL|xX~pzdI;)1B&c(8euT*r>|y`b zth2k1_`8|NB(MB{MhVfo7H2@N&8xXks)t$gdD2Huv8>QXt0Ee&2v%MC$=5zZ&|A96 zg0HixDK0316Qg3mjtC_V1M$=EDv?`M0Fjx#Zzj?dURl9f`6Cgyj*x=hdtOg9U_Ukv z(z2$=3`2R`B3(3Lx#QO{P>pX*P~P_b4Z7OlSRm)T;F_;Eh4(}o$=Z(UJ>I#ay7)Fc z#=anOpTIYRFmO5jj_PVgF~r^XLCt%Uox*%1_ck<>Ao-3pUJVR^ZSY+0jGOPE%B|66 zcO(nqf?+MRM$Rd^$-;A1$5Yvw#$AuiFq@?83)dh`=jP!$94*m>@ zwfN5!L!kAojGRPJ6I{*&*Z_>Y^^vsU{ch9pV1blZzbE)|v5~qf>wpC9H>x zN=J&Rb1R%l`{E`~ty!V@Xvt@>GFz#?C7nr|Un9Ggq#9lB+PdcfCiK4W>{P8b?!2uW z>q+2Fs{lVLyT0BbNMc5|Q7_18*d}y!b(P1T)AJG^t(#by_n!dtLoQBV(VkR+HODZ? z4yiQBbKCl+;<28GF7={`hrR>wLoS?F%r-u+&~JbDfr;fbGIrFuhBBj5&5buWKT7z> zCv8=1E`pp=adn#e^YFp1#pswA5JbkUezeJF&QQN!TTxPYH!O64)O z3m;pT+j>`d&I^CBEN)>Mm8d*sIn#?Z^nMp8_nvEfO(GG8L@sxQi*k7AVyEeUN6g^eyRK$rQ=8(HvR;KW+8u zK5an{O^1UC;rJ3cV)O296X=?*c-Os(#^!!+QD7Ik0hVweVr(gdU1zDrKVn;p)i^1< zJ}H(s|20jjMY+7&wO+XQhNhwcr-yx(mm3K$q(q^5H-)GJ8-!hTA&(th{k=QuYN@XUmQA=R;D)1AT3rgl{_ zN`J)0%rMftp8@}g#dl%`hrn@-Yndv3EIa#Rh_S#k$bf0F)lKpz&C8Pk0qZH&ajV=$X z|1p;pmEc8nDBzFx91<*vgN6Ye5Y`CqUG$7zME9V&7i}XN zi1ZE&4Af{ZrijJb(tn1pRQG%7>v(8R6ln358D3Z~3C1&G4t>{T+S_CauSl}Q_!!5~ zKi7yMVcpWDA;Js8fpd0pCZ^+Kzk)?O0{VDnvI4Zgz=m?QR^b<96S!wKcd<^heRwE# z_JrR%%SyV9MND+Gnp)q4KO^kQz>UfH{$+;s;Vdn%#_yWL!N<{COv`$D$x>jW(#qza zkwaG~ZfuM3fa82vrnB@Z(DBOi3LqZ+W_`vNt>c-@J^j_8r2)$rV+5KByPt$^tUPqg ziT(J4{KTD@1y|_3UC*;}hJMuCo+qs>K*y^Dd;h9y>|Y58UO|);#7qWHa=rqg>j8h1 z^(8Do{yH=33wL9qM3V4$c=R$p%X|jD^J!KKjoY`ak~Zo5srMyYO4oy`R;`|>EplR4 z8WQEIfSlvtAyWaTH*(;~FxC3D-lFGD`z@|U>vmPend4%E!SlY}FE33)y?a*8`^*^-_ zoHTiAknKyg5Fon4PWOyXH&zfQ z)^*PV4&C<65HEh?Q5CDIY7vLS;WGJUb>|;O4C!Y++zZrVj{y=$9SX+45T2Ef2(MN* zb?qzkKFMCunZ#6&IT<-~erw`IuTBu(St>ruPT()f89@M8In#Xgef<6~E zR6Bd^(eR*k!yqw}i~7@(p*znglR)mmn5qSFc+Pm4!CYD1lNet2^cLfYD`<~0LNf9x zq7I369>DRolS>t!o6MvKxjhfF!P3%M(xU6v^s^1{A9-tifN;b*H>kuK7o88FoSVSo z>nSjIPnS*F&P0GOh&(?|^x6hMZ_cb!dE=Ptq(}f@b^R^X#^9HIKBC}SN2fSm{eoF}s?Tq%{ z$B1IIPTY1o@?j#C?S}Aw3$N`=AvMCbuZjlOB{RC^ zv@`$;884?;&r>}Bp$1nltO*a1S=4UO7&#lesG_ohd-!_7PV2N`$wS!cD+lnz03?b>8~#sp4)7d8=(^>Ro|>gJs> zUiWMbTzN`COTBRbq6vT_Zl6wnu)bXT99lJ~?4ugCIoIJFKkOo)kEh2k|a~ zOFIOW@etJtng4jLD}dKhYWdT0^}v3AYLJ$-&Np7C^{fW_N1FmPbcVPF@v{CW&d9ps zyS*ulSn2A#U9{eu$5~7iFID{9CpsoOLg}9EDt65*>gNs$PN5(al7ZsG(NFC1a zgT?^?VlSr5$s4L)Q1E5p`+KEF=ZISD+xT6UX?pQ{I$}AFH8isIS73n+f!cR11(|>F zyr)KEnrP6x);2G?NgUm6*@_qfNZB(1H63xz3)99Gd}Xguf%6Bboo%9x=J#9lQ>Yk` zkRW9se0a-fQCR4hZik4Dj{Lq@gIED;E^mj6OiIFjcusomhbrgGUYr?4_WoFAe5;3t z?RwZ{Hu3#~=>3%Qg+o-{RkAlw63zdeU~dhkJJ)Fo;yx}Xynrd(ZeG}$=H5^Rw96M~ z0Y1!|JPr3(dq=7OTI!xS=v8ccUZQGXsj$bC?~y1i2oU7c;|c4bjovNKCvUgPwNU7v zj?0H~-BLBV7$@O?Ma#XBq16N}OR&){|1acr(CQn{8yQI@{ye~}7PJ{m*(UQi)^toW zNvqo@0Fq-F2$?GyxKo(8FFe<4zcC0x?S_xo>yx^yoPs7hZ|I}m=$dE7rzP|UU43^V zM)+`%>>7PJFN;Wsum zp|nEod_$v$eQuYBRalu3LupNNazb}6s1Qn7=+7?U=M7?XJoXpH`_>a-7_~pL-m2>? zrG(I#S|?0@+R70AL>QdMmu&27xJu6(Y9@*8W?D~p7Vv-7OO|T?(cvbhdYd3*EGVo zVaBX|R%lP!>5HMQNw8^{M3~=?YUf)sx~yP^#GvOG3FlL_R%b+MU7Uv7`86fxV`hdE!X1<&UnPMbBNyaVcKI_*6oRgZCkO z#v2X$N8kHylNq?Q$KY#mh)b#*24Yd*tw7~`;+BEJ4TQPQwGUkzZQX8yjv7dNq$2{y z3|NWlE%fuoOCe6Ykho{YQSqKXxwFFQD42*{PH$u|`lmmk%#VW#!3Ap87s}KJ`;lRl8l#UmSRVkPu4}%lZN^63;_j zyP+Q-a@wlg%-#GiN}?$%f=I#M$;nD&=M%#EfpLxO>FCPZrpEPgyxT4L`8$8?aKFC^DEXMq;xv9&4a{*+M?J9!8dRg1t zPKECWGx8-lA5X&}u2X%5e%q?(@j!*s`#wnq0t-VRzR9oyPt#3?iy*O|aqn=kr-u6e z9MKg5h!sLid`=?2;4wqF$9hHsVa1!N*!IJ6};>Wk`4&v zk=ng49~DR^<=^+YD_7q*w=G5D`yxKLzg+q7zPy|nZQM@Z?Ca2wc~4(o$5+Gs96bGE zDtU#m^L@Gf*5g*b@H)Fhn$FUH*{Q};Y}!F?VkXGe3u{o1eVRUIrw?^RSj1ZZO|Kdt z=aAVn(Mk$a>i7z{S<5)nWkxxL7{6VEzbI>`0T2p?NPe4ti4ds&c70;Fh^&K9>@Yxf zXsMs?w{LEyD;*PwNlQ!1%*#WfP^g6bZD0(|jNr5rYatoa#_M*W3w>?mwJm_GTWuAb zY`t~9EDm+c7k0y;3NfjJCO+Y?#{3|D{7}HT4w0g9`5j1;@Hj}K!x7V zo-(ibqixRc(e*h#;U96H$-1!T@Ax;N;XSi1S0^$vk#oVCB0Z_a5(bwuL|-4Noo&p) z6oboD?NQD$cue#;*Jy$MYn=MeeB?XWL$0K_l!X6ssB}CBMlC)_28wVx+;QjAez|iJ zVGC8CrMj$JWN<=!iUpax26`}A1-*J z!`4`w8UzS#_`uyuY_urTrd(GmWpwLX%Ur07JX0lQf)AKTq6x4{E9DIWss_ysvXD>+>Mgt;ZU?=+L@RWlNd%idx$nnvee5<6GJ5IG&9eM3O;p zu_Bhx@4P(7Q5CH39Om!)JP2$4$~ZaB9O|FApjw)?M;l$!l_Q3LHhOkdYxf{5 z^k-KdUk-sg-0Vh278a&_qNn5}3fZanDWbQ3Vt*}PTSF4cY+WA%s0bEq=P?k=C6>*M zPLBqz_wm{jS3h($)Ya8Vi_r!KdCe9|1u;K2fPPDI(opc0(yCm$n5qn!Yj;IfQc4_% zqP!i3-pkXN4q&_I{*E*4P8#9#`uR2Oc&|i7S zm61h_^}=19Gu9BttL9=Qe_&i^zHYMmp;~3zWi38UNTiZM&f*`$dv8sdN%2!v6PT~fnY-ZTM0wo%33uAZXEfv3D&tuBG%5M zhS6#gF=TrUOHU+*tI$~0V;YF)=x8`or$EeL1dO>`&{DhfMH!V8$nt@9i;DsAO-}eX z?EwG%A4`3J7Tv0ifNiK&5@UJwUwiKzfaH7@@|7%JcGmotQWy(nH5N8FB%*S>+Owwb zA5CMxysB3n8gNY{Z2ewU*zPlpgXuWe>hUHTE39Nb-Anls*>nQAV3;x8E}DxR_EMs1 zWhc0)xaPe}Ve@+l2(&K5=V0;ZI)hn53{$lJmHgMg?yEPH(g@30)aaUISWsD z`-hb!ml{k{L`jks3@jpdM*o~4*1n*PB54oGO_;i4>PzdnlUmA?jmB-S^LFeDe574* zsF%2yx=JE49YR8%YLa=!ZpBD&r@}*}w|8zlV7S41Uiay4wWK^;>enrOk7)FRKQCpB z11*?FRX{#FnGL%U(4zcP(jlD5(A0JV2!I%^?U)R}uQ@b_s1tmG&imeu9OLPhg3+vC` z+gm|n83#uiCM#%$=fHREGr($I2!F362_=8EceK732cVH_9XZ$^#KYCyTmJCNVLR=? zH|zxChMg(X%%fhY&zOmEtWsH?{W4dsyn$Vk zS5|?^Do1o6JAbH__$>`a3_%f~*FIXK7!X2TXKE{}0-yl|MQ%KDZqVj6NRAu&F-AzF z@vLHkdbfgQxws4y!&vJ30Rs@P9!bqjp)sz1@K-4W*WI^`t=+sM-USek9k&@D8&|UU zGr^2*0n`$P0xcHq4IOhQale^$xf~}1r1;~T`dE~k0YL#HSwI8 z0l26<*hvq;MAchALH}^P$S1YP=jdt(IaD^-@FCSlhMgUyuX&iqge+jFsxg4751{76 z728z=RDqdu%pGy0;lKSeq6WGJspBt)wGs*yX_l6j(+rAZVuoWFj_$v?%rJ=@>ZL7( z-x{>0ZpMJfqBMfuQs)sXkSo$c9-ksid7lPUW=*o_M11(e>oC~X$)B02-E)Dva~U(h zg}l_oswvyQ16f)--^Z+TkBHnxB|~kbRUhxnCd&4P&@IayMF%yjcvSo(5)S4NH?S?K27U%$0XOjdF!!|(T> zYj^hz*!bmsZKAc7thU3jLQuqW?=Od!bHH--i8rs1q7g8GbxA654P;u*! z(XSwXD`HV6Wua6?SP}eMAGguEXONm6AZMJh0=7-L(hf+h|Gy-{lrv6*EfGvCY+%g+ zJ+=#2wq`o)LBsMA06FB~c%Cb3gbPoeTx)zW8oJAI0bnIZKOvdu|tS@C*5_ejCK-nRD38FN!?|i9yNfzYzdS=Xrtjxo0|fS3?;c(>!lnNF z_ljCznILrM+N!alvhUvWko`}2mFv^raF)=J|4Q?X-O1;_=X_Q0*Y zL}luT0bBEJU^1=am!U!SEoV(UCmLI~_Yyl9t!xR{e-hsgFSCfT^#GV4L9sJH0Nv$*!@jFXFSVE_>>^87DU zqWlFVTRM4oJUc8Pk_lD*hrs{=a@*dF!}N=}@z%m+Q2A#k%Kn{Z`oRsE|1u7m;bG%w zY(W}8GhazTfA*oE|6KCX?)ZAed){0chNwoj2pooo1YDqwWbK4#sHgvsY57!IrAEtL z`jmD(;4Q@eZM@p+N3*8_hpUPmt}R-o*chvk8P!0{r+fURuKa!fL#)rq%nUKbH=HSd zygs`d!xnq|cg4(?Z(cV20qXc_!&?2+nLyu}`zCkxIUz1_Uca2n%h&Wd7g7A zvXJ}iRzX48Ch1(~ri0b(2do$HEg`bLHfLq{wWrVxJ59tf`YmM`v&nSP(9fE^*eEB# zpXHa49o*;9psTvTejL24S|$Z#T$wozpv8eIBCl9O%7^tAm53osaqUiX#1Nme$Oq61 zpc>)3?th*T<<>2&AF=#Xshci8>k~3d|7`)*3wXRrI9gw(?P@^1sc_L!>-XywOHW#C z@x8yAs;Cp14j6`sXEURPCI2pS<#F88FI1x0(-vTxp)1D{IZbb?Y*2@iD@u~8jl#n~ zKRi>QUWa-iz=r}7!E!k7+|lP{W1+pjtUK2OLzM7@!Z5Ev=;q^e14-djaizhfq^Z>b zcX_Y<*>W|z=MayVe-_O7G_SI)0@a{AO)cq3rFs{=Hs^cN{J!uLo z>pt057+{gZy9aV<8dDOImGP=Ib_7vzIdhn02bghNkW+1D@nHG1cT0D$3;EEKL&&PX z9(>7Vy{FIEY;3mQnPv#qmXy|%VhrK(TcK4x=kf+bO@xN}UO1{Zb1kI||4F;=8D4NA zFracz#!}pu z{@`@#c3O=wtI#w-c&O&~?FV5*_@+>bn@ia6VFAhUG#eY~&(!1o@029%ktau-_yE}ewM@Y;~lmw2X|3J zwrSj!|FKvJM<6FZ41N4={j=ki>Oc;TboD)`-0Tux-ClQ}a;c@n@hs(CHIgXDwQsai z*NjU6pl=*hj>HRGF`J1m%ky2^)F&qu71iDi4n9Qc@k&kTIl}ZjoEq$5Z6&u|W*Cem zn35pO-$V@T9>i>p1J_iq-a^iKzGcFU^P?kF&rM72os@C+>>K1)-v z;{2(?i8DJvd>k(*^F3J#J}ord%JSqPD}cIB7D#2et>DZ#$am`MLP$9zqUk z^0P`=3gNbEyUa0vCu{xEQf|ImRl+Qppwj86&XwPnE4yAL=!!EKALQtp?3+_N9fy^$ zYECw`cUOSom)J;6hH)n{B-~*?1|CId0AXU|5 zH`vmAi@d+{e$1y{zgq1iLf#H4kU)icA)KEe{O?0+KF$3>7Ck+uz!?;It~&qS898knk?GdgX)5JNT z+F)a|73pKhj(SFsWbAjZsDeVwY&G_Qy+HF=BEr_j(2&Cuf!NP8>k)4hyC<}716%dj z9xnO?>QXG)4@JTF08d>NAG9VViSQyj;eoA!o{{~P@WG@D$CQNoRl z2RaoOmD6&sx#1~3iwWmr|4#0D3;cEs0sVpn$q4WC!d1GT`@0ukdiIU+sJ?``>HZwz!eRB{(l%sasr~$#_T&Co5qyA}pc9!d6@Kbs zlLC5UV|R5iyrA4y&ttuc@;P4P7;!5>V{D7X{u36fd-*E5A}=qmaSMG-l6m!==>aQZ zv_O>{5~A;9YlKP-WEes!Qfm^0EG@pX=Y>9=4oqKZiy7hO8+dZ}D;ry(T7-F_iP9-l zToyx<`l&JpiZ#X7nR$gHiV{^X zWSt;6j3^&^@5!juKA@q16=1;rfH{|3^_6N?YVo=90W%3S)a^#$yFv7(iq&~f?esD9 zR*Y)zR^ylF0q&P;YhrW?>eW@}%d43X4#NV65eK4Ueg&qNYP*(}pBH|I8GF97eq2O5{dYA%^Z%J8{q-u)pFB+q zo_^WK8fE9_%RKjm4RWA8|2724l!zE08(UnO>dtM&UR$IJHIH%_(tA$!Qe7bmhR$ct z8uq$8$`iTUOB*)SJ^qW&fgwAKp0vSBGmtMGB_!<&=$7?U5C8gQRz&U*D9+-q6*+bJ z496ZPPFX2XBZ9 zH~|-P%yH7Y_JUFxSW2j#Grj@rSimg*ck&T-YvX;E2sr=-?-4W1U{bMXtRc=zd!{NhKg%^YAT^+2yQB!vDfac^cuJ zp8&%gM>NXn7OH2Og8gk2+5pP-yQpyV0_xIAIa;79D|L>ll3 z`5xJ}AdQTmSDoi9Y;QER*V&rw#UA6`PS^^@6zeNy-e3vpX~~{jJU=9$SdH!Z_rv$XkOO#r%R_oPVBK< zl^Ys(uR|;1cgn|TdK1zk_h6?-rq?Sp2P&W3d~5dJ>KG(%D1CLw&Mf1V-~895bIpwg#+AP*oPN7P908GIrf;kdoNBnA1xNx#P&h7V{W+EwD=iCU^ESwTa; zaIavQn3JedGLI=FV-jtTD7*N|CI4INVc-+so;3$?vfH{qnlJOFg;N(B+c$>=(}0R= z&+V#{UL|m6Qu5AIhQ8$;qkje3%hAQ$@tj*t+2}T$k=NI%a`9pR7gfd$eBIaj3#l;7 zf4!OFAa3_sF+Wu8f~@dy&D=$+7_<#6<5Jt4Wjz`+7#O?FnV}(Fy`2ArRY_!;RjkS< ztB_XwyYqM_=8QVTxdF(B9hJ^VQf(|lJOiW(w4u`eUg1S`~&B@7#3bN1%|JI%@cj7 zz+eXuBAE1H_<%ykqPFDBOwGVw8Dh#l>&p-B)4cW0 zIOys=FpAM_QZ#}>jjV|fx#qSjv{~3brUARgE}R+sX#S~5X^+(*Iq9Wy7UH;lFxpGE zBz7#qd^i+EwOH8fDw3i9bW5mT`6(CF*K17~v1*pzjnq;bFUdPb%sx0q*BB|2?N|sB zP@r8bn zXKZX{!oVthj-z_F8$D-~H-1rDJ@T)Q&jWk$G`LsyV{4_4gmJc8fDHM<(I(0!UkSP) zejg0;vo0yQ%K;Cb^TC3jtV5w^{yD8r&y3@{)t1aDl<)F3P668$LS-bnSFnqPn zJEB?xEPRSBL^7-nt;bo-t@{q!nZVb2B`z$V*>YcqeYNTtV$a5AqGQIY&+2&Gu_|s;`@-8DODUAS!FERsK}>c`sOa-|KIaZh!h@ zd2lNYSeDt}vOopgbDZr(bucw@NQvcmoXw0q!u-jZ^ea04JK1w^ejl9XFZkcS*dkf| zJ(Y=#K+=TfyLK1&%1LQ=ZzuFp*4G4->zk5U(*2CY^228?8>7hopBt z`*PFnS73<0@35!F%G$goU`Q+9s>pqB=BJ~carfKBpVW}oI7X|oP8_N~Y@C2utmjMu zc9qoqWfNiqAwJIVSt#WzpMz#6A~TJk7;hW zvOm`2GvR3hA-2UdD15g{(Jsu3+1qU237r^vfc8TyH)tSY8oI|^DYbvv%UxJLvqu`? z9V{tU`%$<2zGNX&xTqEbI*EY1YNx{%=v06m}{Z8~*Wn8%K9&`0&7Dxt_kh zl7Rgqd8}7uGjO%GOHAj#-j&Ton72uL>0={y8DBJCR74jx*lGScYEE^nDmeBMR<>|p zpjQguld4m)9^Y` zZBL{2)JLY}f009^DGNa0rlv55g-daBRT4`$6c362%>}q8(^lR1(`TvH7NrgGF!hqG zdM;W1qTl({hw9}YXRmWFTa{6y+uR?n*|BgHwKa_$Rrndp$qrT%8!(>z=X>~ZwAFF# zRu*$D9E^d3wvPs z;wwo82+ill>dY{@KxXanfltuA(#w0q!TsQdYm&6`yHmBt_nvF)GhwGtoiUrtJax68 zg#?p{@UPGvHa03?h0WGFmPTmT3+-4&YS`M-^2ahjoGDf`1d-EvTu!=Lg0k`r>Ktt~ zd4}^B>OP5TSI}yM`Ur~n>z#krEqO8360ILOhNuViMuUVe#5VWeWyl+4S$G*u^*k89 zL0}LbF2L)f!qA*;WiQLp_QtPI=7i^F-qFh7^m^}?X;-Vg0_opdelunlZ5!<7yI-Zv z`PGf@*J)9*BSoOYb8cpAG@4;{jBTwkP{ZMmWY;`Z-wrbCDp0xL?g2(X^$r!jQznx1 zaR_q3gXJqkM^O{4dIxH2fd^rTvFT!>2)!dLauu?W7pEUs0Pf>O^%3`1`>m3v%?uQXiQbmiNp4Xn_PNx1hp2L47I-`$Y&Nle zX{igyh{!XBf@GLQSNl_ctg;(Dyy69C5aAGA<=UAtr!$iN2dLIZ#&SlqXmH` zp!N0jSBYm5MKr;X!(|;0<<#7+GgmVv`x1{sS4w{hX${ikh0$u`ElRkB&!{R)0bZ(SB%i)9+=DZ(N$& zB+s^+SW-@&O-P96Nt8bcvzKY_$n{vern>HEaicXtUteFun-Mx(N+gjAltP?F#b!nK zN=kDY792fe6=;UiEnv+|Kr390@22)qZ-MoLTkb!hSZk9zCB~AFb4Dp z@|3?V(y1UCiE_o4OPqh$|J-ct)WO)GN`?BfMEhlbb)5=ejkp-n?AoBj!qF^Rf$qOo z#jbhmAKdr;)H#VgQj)#f&_DS4A%xyOcb}-%Dgv?p!h`WY=OMSvCYYljmGUnd7A9H^ zHqi3pRB%^bw?Vu55fD)S$d4ft-oQX$hgE9lE2BpqDdnNG#uDujDI%!?ep z=p}_N>#6I^=6Y9;c~%l?THgxAXTMT;k1VY?^6=P%BRk`NQjO{uRli9l`f3^rF;Yr9}Nu@ZVq@er#@ezbM9bDR^Di-2@j_IZPWB1GQxa= zMD%z0o(Vs70Os&p!V~^PiZ@P(vaz}S^)JY5JuNupTGjU~l8tRg=|8|y0ER$FUq}@> zTD7ZH_&;b;^+DL7`m>h(3v55P{_|PDZ~gB8sN#m=SRxmCxbknF0*OYV$iLW@2gruB z`qQGR_eucde%wWD9HfCJogw?SjOwsJnd22!ow6E^y{1XsYX0|p7ht>-U;nFYgaN|F z%KUAt8as>!%4^ls`{tpuS@5%+_wYR?xbDZI+S_xHWh8||Z10-j5=*au`4gc>C135eZ zA_amBAi?hAN?M*a4ON=&E6^JPMKH4xa(^qYfZUdFR4Sc7UIS_~|GD$lAi(>o=^hv1 zuYn`QRUAV2Hora(;BIX^GB{GrRN%Vtps^X4FQ}Di+jAPV>Kc?o^Wl%S6G4v!OK?}3 z6__6@^~i443^RVfa^54FDuipds8=O&`Fq4nqDyZlZk>+t|Ao5-1W_AhA<8j%XjjI) z59sY^QKfRBBL{Kx`u^ZZ<+vm_CX+E(wYv19s=q0E%w9g;mC*-l<$?Ly)Zeu-Vn-2( z9`1gRN=ixukTTb^&KhcJ`EQ-R$PqO{ol5(^+Pm_grm{8fymo07d34JLs|5Tfi5AhINo z#K2tC*Htr9Gylx1shO&G|I4X5H@Cj?t-o(M!=ar(27(U8f7JsZ5B>O4o4YlnZKwso zb+n056|+i5Lrt@P6oB!wGKE>lfQ3F4{;cS_aID>CP+XEOE^dMDnu00o{y~*r0IHzO zH5>@HUa2PBX+AXIWsbv@G5f=(XcPB9Z@2seTHL~G#Gv*USeHE2#nsw>bKi`QIq7PP zId9?W%G?nk#Cs25g(}7ch@0 zWBE<@_M-m|l&q@y;u~P9SU`+Zul0I*J8Jdy?9ahYE~b&_#J_&8NEeG?uyS1gn&oyb zfXkZN2t@!&rS+HkZU2E~94dxJ52-Egv;A&Re0Cp2hAGNKzeex;7_hvnj;dRFYV#Jl zxv+j@!!joE)%N`CL0@JA!Jy%zEHt71#lK^ChYIl!kVya#HkE?`z17DXnCkF?ZXm^A zWmR%KX8PX2dxv(FQrdUnPadmvdKc$Zvi;*0HjiG(Xjoohfh-d*e!sk>X3!clOTy;& zB{X`hUE{}&11_E+p=N()uii@ldj;8z^a8db$&o;4L;cKwr6%Z`Fc{s_;RNr$+zN6# z`r~ISx%ZGo?}m5>&=Flh<0M`4QZ#w4B0K@lrvV)9V(amhETHtNZT+sZCbflk9U8)* zNk-=@+A2Ct-xYq2iYtI4q~F}~wQe0SzcYKQ9~;-ZqH6mcbvSt6U$+udpFpHNsmXK7 z_s*^9_Gpg`#n@TEjJF(OrsiUI&uT(LT;r)NCsMy9E!@*LYyXAnuO60&wZYG#84npY zK>I!L{7wcU@;CS2tSze#X$`xPEi`^*`Bs{pEn)=yvhAE%g+sdYu`h6q^Xv6MC5SC- z@5%{TE9Kb22e-g<$x-$wlOrQfONW~Qu)-o>Phm~5WH ztdXVl=}$w?0E~J$@w!(Hv~lNRO`7)SRV856sMon6(f^8;sHbvz)o@`3RCXdxnD?0^ zO+rAJ816ZOP5AI45O!}pXKnw$4*pQ*+x}SkFX4nwd;ifG#Xn!VMS*`T?(bsFRa(E@`h#KcbJcn<(9Eeo}WWFnjXw>24WX+iv#GKKl$AGO_el=& zWKNi^zHOiYA3mHtyN*EQ7*8`OKarp8yM;D4E3?cx*qr#@OBSq$7sgznE1vR$<#@s# zaFej$7uDzUvkv3j2Nh*JqDK#qbxWk$>8?hzNkjaf9 z=ZiRFhU|9{byZE_9ZCC{Wcrg9#=6=T$ogZ<1E7P&!K>YKuCuF&Pg0sqd7p#Y9J`E; z6H)R8Z-ZQxuLg}L#%@R|kf|lj+yFiXXNN?JDOo@444RjN5}l0eY`m1Hbn#A93v5SvcPI#*(|ZKRI3auJ zYVhF{Up+h`cXAe^ZARplXfCDX+Wv zF+q;I{HMj!oi&+?q~6k1ld49AXhx|DmFa$Cy)7cuKZ(6T7zeH2yiD-BSQa?6a(Hlh zZ46SL7Zp2KE3r42frE&hxUwei2ea=|4q6!6;{4pxH{-&`oA;*3M%EvLTOOH^T3Kv_ zo^&WSQ+)S_?{z^01Ml6$Q!ofg?~a$%U*XWNjjKc8wia;-CpcctT2UWHz4h>%hA7eP zqFWrH62$8&T5wd7>_||#eVw4v9u<1Nyb%e5W_U-HPtC+Fnc&QN*CAH^E<-TVc5kbKJBHr^;RU(C~wuO{y>f+q{!NP=u`gIq^piz{~DXf^+ z-6l?~5~>7Oj(ghhc#e>A4gy6lr8m{!H^7yW#QGI)*5V68G>~LkvscZ1{R3G2H4;HaP~n=7J5N(kGZ*aN zgO$zm)u7RHZVM=-$cDYl|+`sGOtcf@N)zU(O3 zoc9cv`!@1+m)u(i$0{c#{kbyQLkC=ePi8w#Ym=Msm>&PQAVJ;T-+yL$*a{clUi)SF zv`33>W|3px+`Di@=bKn7#pxu5uU~|qx44@N^$3o?O&d4A z=%L?^3$3ur#%O&3Q2I?2u0ck|P;fcc^R)D~wFa+!RZ2Uuhm05dAm8SXIMwfqPqUjf z9p4|IK-!de)K{lK!%o5sX_FgrYj3ZxywmegrGUEL6ub- zH7Es%jspbcvj6?AH|Es26x*NDZgl8+^HrtT#F*o9zCAz2*G66;5(e{~!o^I`Z&;d$ zubk@nG}k?A>LPGocWIa0mz8aM!WlSn)Ap71!Yw^$&ivQJ#Vv`L9V?x}L~llvLmcvV z2jm@dAC=j(>T#*Z<*L$1N&O>TQy-;b?sSGh#-GZFJMk5(L(R{7CKN9n^ww~v)kV(w z!tSIHWHsmv$w%30cJW0H-}pdY*koE-&TNw!KANN}ovvGb!0zSoV5uEYz1iPW z=LOx6{5apNA58M>W*P}vP>l1e%mX=(aXhU}l#IX=JPKZaKp;8?)O6<=-yfRyOd*K> zu5k&+FrN8rpJk>*WwSEG*$ZI_-pi0C7B$_G#j~eO@U|U-u-v!N#8xAL{@!hh(zz{t~BY_7V}+cdKI*M)dLA< zn+tiHZnG(;MOTku%q*jQUj95VfP0%?-AEo4oO=??ICwfZUk@#Xx0S9c0;tvo_h>N_ z)rrR8@OWL)!t*v%aNOo?Y~*@ddu;bxlEPgu<>xi1iU9^hRavO^&5aW{^`XxuEWr=9 z=oWYvBw9I|spI-*%;5Q{wKvD0=Re6asSrf+j@P$E8WlOr1iTlokiE}m#ULR$vzW9e zN2c+4ZX#1+x3t@0q2+N9%sZKVpD~(kD@4igSzev@Xn=GR%NNA8noP^_1v9`v8yk_HzZ!R6)9Hkw#1M^K3P61Ox??Abt?-6epH{8;Yv+!IBj6|Wj)IUtJ?9d@J;E7Gtqu+B zurM44C5o>K&v!k1KzY$Ld_ec3wnhoZ!?=txWR2d~`qErA4}1TVCK%s15?*q}|A41$ zr%gxH#|kRn*k|O5>q2^Tn>Tj-dSZ|@;b0O5i%il3>IT*J@4>Ux3;4GkWZynih4_Sw z(~DP`cyJBpD!$Ji&-|Xi5?Sy9mMQxVk*!?2^=5OE*$)HN?Q)ALh2jEAW;$3AmQeFI zVzV33KZWo5ZM6dW3)^1q>Gq1KgIH-TynX!>8C@f-VmLaseG@DjgFO)Fj)Z|^_HTw> zGHRgznHyx$B5)+%OuAcmd$+Ku1D3_E*TaK6OFn53PD5h+E4T$+9dpR50=NZGbV^uH z!h8+sE`FjsC3D;?Nu8_Ud-TO7C+VRcVLd0ESN^^kM9Xj6+BdUW+Co|VI=W5}?E?zy_gttj z4;Wh@U68PZ@idSs<&2F-Hsmwr(BwIvDTHwI*L?0;o6Kt zH>I_wHRFM#NthhkyR65NBpd1b4mNc6?!21-Os)mj2(C(}E7-okT~IdoE?2-i{Y}HIfG+qb1I0I)ar(GZwrW=xHtR)1RX*nLAHM>c;F{e@wIU zyY;m(G9r`B@^i-6kS8pzRozi?&u5Fr+}eSfxXl|=TO=N;6yd8*18mR3WYD}ij3zIe z8YQ>F(fkJuA0n}QFXdGF{BxZtMewe>bf{QRx+`Rx~h`iExLCwS*VHnB94)nCed z(9F%rjiY6j0GqYM8*Le~aLJb#r9<3)Nmt^b#$nDpNb{PotQ*7BQn`;h@4$@Q8b#S%Bj*b+I%{)TN*t|Am2DA4z$&#>YAg z=BT>HgAGYqH2?W$KqmiToFc&Ky=r-s?qumc5@4@A9EJzFL1&|EK*Pc32(?@2jd%DSZf%ymf0kl0W!mTn=t-n5+F?>rJMC{HINAG6;1oi| z1tF{Yc1AujId4O(0k^0+8`W300`Y@sbE(^G-Kmx0eI>Bt+KPG4rsZ$Gxe-Jwc6+`>cPk za+<0ZqKLfQF+;~i8DdU;Qjha?J)M#54>z)ZWPtkJW$AEjl!6p$+b7xtQh7rndauf1 z^NlknBZ92t`fto)+>{|1^2Qla?ewQ~NZ|`$TqGnut#57xwi33rqsyk=hruOe1}F$E)*k zerYwh!2lzYkaFie0wtAWV~OimQByye3~9~+UPPA&YiYUZsD0yh`v(v?xGr&kHmx2X>OGEf>WIR}KEN=U!IGioSiiTv zt#S&|LvZd$L`0l~B$y<37JyZO3Wjh&5UzB!lR9Z5sDL3p#e|~`&rPyYyLj*B6doVG zc!cSM+Ra#3f877_C&YZFc|V&<1jag 0 %} + + {% include 'home/' ~ section ~ '.twig' %} + + {% endif %} + +{% endfor %} \ No newline at end of file diff --git a/themes/cyanine/home/landingpageContrast.twig b/themes/cyanine/home/landingpageContrast.twig new file mode 100644 index 0000000..d382e97 --- /dev/null +++ b/themes/cyanine/home/landingpageContrast.twig @@ -0,0 +1,11 @@ +
          + +
          +

          {{ settings.themes.cyanine.contrastTitle }}

          +

          {{ markdown(settings.themes.cyanine.contrastText) }}

          + {% if settings.themes.cyanine.contrastLink %} + {{ settings.themes.cyanine.contrastLabel }} + {% endif %} +
          + +
          diff --git a/themes/cyanine/home/landingpageInfo.twig b/themes/cyanine/home/landingpageInfo.twig new file mode 100644 index 0000000..7e1ef97 --- /dev/null +++ b/themes/cyanine/home/landingpageInfo.twig @@ -0,0 +1,40 @@ +
          + +
          + + {{ markdown(settings.themes.cyanine.infoMarkdown) }} + +
          + + {% if settings.themes.cyanine.infoFolder %} + +
          + + {% set pagelist = getPageList(navigation, settings.themes.cyanine.infoFolder, base_url) %} + + +
            + + {% for element in pagelist.folderContent %} + + {% set page = getPageMeta(settings, element) %} + +
          • +
            +

            {{ page.meta.shorttitle }}

            +

            {{ page.meta.description }}

            +
            +
          • + + {% endfor %} + +
          + +
          + + {% endif %} + +
          \ No newline at end of file diff --git a/themes/cyanine/home/landingpageIntro.twig b/themes/cyanine/home/landingpageIntro.twig new file mode 100644 index 0000000..a936a0b --- /dev/null +++ b/themes/cyanine/home/landingpageIntro.twig @@ -0,0 +1,58 @@ +{% if settings.themes.cyanine.introFullsize %} + +
          + + {% if settings.themes.cyanine.introImage %} +
          + {% elseif settings.themes.cyanine.introBackground %} +
          + {% else %} +
          + {% endif %} + +
          + +{% else %} + +
          + + {% if settings.themes.cyanine.introImage %} +
          + {% elseif settings.themes.cyanine.introBackground %} +
          + {% else %} +
          + {% endif %} + +
          + +{% endif %} +
          +
          + {% if settings.themes.cyanine.introLogoTitle %} + + {% elseif settings.themes.cyanine.introTitleFallback %} +

          {{ title }}

          + {% elseif settings.themes.cyanine.introTitle %} +

          {{ settings.themes.cyanine.introTitle }}

          + {% endif %} +
          + +
          + {% if settings.themes.cyanine.introMarkdownFallback %} + {{ content }} + {% elseif settings.themes.cyanine.introMarkdown %} + {{ markdown(settings.themes.cyanine.introMarkdown) }} + {% endif %} +
          + + {% if settings.themes.cyanine.introButtonLink %} + + {{ settings.themes.cyanine.introButtonLabel }} + + {% endif %} +
          +
          +
          + +
          diff --git a/themes/cyanine/home/landingpageNavi.twig b/themes/cyanine/home/landingpageNavi.twig new file mode 100644 index 0000000..ff37567 --- /dev/null +++ b/themes/cyanine/home/landingpageNavi.twig @@ -0,0 +1,12 @@ +
          + +
          + +

          {{ settings.themes.cyanine.naviTitle }}

          + + +
          + +
          \ No newline at end of file diff --git a/themes/cyanine/home/landingpageNews.twig b/themes/cyanine/home/landingpageNews.twig new file mode 100644 index 0000000..57a9c98 --- /dev/null +++ b/themes/cyanine/home/landingpageNews.twig @@ -0,0 +1,66 @@ +
          + +
          + +

          {{ settings.themes.cyanine.newsHeadline }}

          + + {% set pagelist = getPageList(navigation, settings.themes.cyanine.newsFolder, base_url) %} + + {% if pagelist.contains == 'pages' %} + + + + {% elseif pagelist.contains == 'posts' %} + + + + {% endif %} + + {{ settings.themes.cyanine.newsLabel }} + +
          + +
          diff --git a/themes/cyanine/home/landingpageTeaser.twig b/themes/cyanine/home/landingpageTeaser.twig new file mode 100644 index 0000000..8096e61 --- /dev/null +++ b/themes/cyanine/home/landingpageTeaser.twig @@ -0,0 +1,27 @@ +
          + +
          + {% if settings.themes.cyanine.teaser1title %} +
          +

          {{ settings.themes.cyanine.teaser1title }}

          +

          {{ settings.themes.cyanine.teaser1text }}

          + {{ settings.themes.cyanine.teaser1label }} +
          + {% endif %} + {% if settings.themes.cyanine.teaser2title %} +
          +

          {{ settings.themes.cyanine.teaser2title }}

          +

          {{ settings.themes.cyanine.teaser2text }}

          + {{ settings.themes.cyanine.teaser2label }} +
          + {% endif %} + {% if settings.themes.cyanine.teaser3title %} +
          +

          {{ settings.themes.cyanine.teaser3title }}

          +

          {{ settings.themes.cyanine.teaser3text }}

          + {{ settings.themes.cyanine.teaser3label }} +
          + {% endif %} +
          + +
          \ No newline at end of file diff --git a/themes/cyanine/index.twig b/themes/cyanine/index.twig new file mode 100644 index 0000000..c714f68 --- /dev/null +++ b/themes/cyanine/index.twig @@ -0,0 +1,25 @@ +{% extends '/layout.twig' %} + +{% block title %}{{ metatabs.meta.title }} | {{ settings.title }}{% endblock %} + +{% block content %} + + {% if home and settings.themes.cyanine.landingpage %} + + {% include 'home.twig' %} + + {% elseif home and settings.themes.cyanine.blog %} + + {% include 'blog.twig' %} + + {% elseif metatabs.meta.template == "landingpage" %} + + {% include 'landingpage.twig' %} + + {% else %} + + {% include 'page.twig' %} + + {% endif %} + +{% endblock %} \ No newline at end of file diff --git a/themes/cyanine/js/script.js b/themes/cyanine/js/script.js new file mode 100644 index 0000000..dec3afb --- /dev/null +++ b/themes/cyanine/js/script.js @@ -0,0 +1,7 @@ +/* Y O U R J A V A S C R I P T + +** Add your JavaScript here +** You can activate and use VUE.js and AXIOS: https://typemill.net/theme-developers/helper-functions#activate-vuejs-and-axios +** Typemillutilities.js is included in index.twig for managing youtube-videos. + +*/ \ No newline at end of file diff --git a/themes/cyanine/landingpage.twig b/themes/cyanine/landingpage.twig new file mode 100644 index 0000000..593f64f --- /dev/null +++ b/themes/cyanine/landingpage.twig @@ -0,0 +1,78 @@ +
          + +
          + + + + + +
          + +
          + +
          +

          {{ title }}

          +
          + +
          + {{ content }} +
          + +
          + + {{ meta.meta }} + {% if metatabs.meta.pagelisting %} + +
          + + {% set pagelist = getPageList(navigation, item.thisChapter.urlRelWoF, base_url) %} + +
            + + {% for element in pagelist.folderContent %} + + {% set page = getPageMeta(settings, element) %} + +
          • +
            +

            {{ page.meta.shorttitle }}

            +

            {{ page.meta.description }}

            +
            +
          • + + {% endfor %} + +
          + +
          + + {% endif %} + +
          diff --git a/themes/cyanine/languages/admin/en.yaml b/themes/cyanine/languages/admin/en.yaml new file mode 100644 index 0000000..616e859 --- /dev/null +++ b/themes/cyanine/languages/admin/en.yaml @@ -0,0 +1,78 @@ +# English +# Please add translations for your theme like this +ACTIVATE_A_LANDINGPAGE: Activate a landingpage +ACTIVATE_FOOTER_COLUMNS: Activate footer columns +ADD_A_PATH_TO_A_FOLDER_FROM_WHICH_YOU_WANT_TO_LIST_ENTRIES: Add a path to a folder from which you want to list entries +ADD_THE_BASE_URL_TO_THE_CONTENT_REPOSITORY__E_G__GITHUB__: Add the base url to the content repository (e.g. github). +ALL_FONTS_ARE_SYSTEM_FONTS_WITH_(FALLBACKS)_IF_THE_FONT_IS_NOT_INSTALLED: All fonts are system fonts with (fallbacks) if the font is not installed +ARTICLE_AUTHOR: Article Author +ARTICLE_DATE: Article Date +ARTICLE_EDIT_LINK: Article edit link +AUTHOR_INTRO_TEXT: Author intro text +BASIC_FONT_FAMILY: Basic font-family +BOTTOM: Bottom +BUTTON_LABEL: Button Label +BUTTON_LINK: Button Link +COLORS: Colors +COUNT_CHAPTERS_IN_NAVIGATION?: Count chapters in navigation? +CYANINE_IS_A_MODERN_AND_FLEXIBLE_MULTI_PURPOSE_THEME_AND_THE_STANDARD_THEME_FOR_TYPEMILL_: Cyanine is a modern and flexible multi-purpose theme and the standard theme for typemill. +DATE_FORMAT: Date format +DATE_INTRO_TEXT: Date intro text +FONT_FAMILIES: Font Families +FONT_FAMILY_FOR_HEADLINES: Font-family for headlines +FONT_FAMILY_FOR_NAVIGATIONS: Font-family for navigations +FOOTER_COLUMN_1_(USE_MARKDOWN): footer column 1 (use markdown) +FOOTER_COLUMN_2_(USE_MARKDOWN): footer column 2 (use markdown) +FOOTER_COLUMN_3_(USE_MARKDOWN): footer column 3 (use markdown) +FOOTER_COLUMNS: Footer columns +HEADLINE_FOR_NEWS_SEGMENT: Headline for news-segment +HOW_MANY_NAVIGATION_LEVELS?: How many navigation levels? +LABEL_FOR_READ_MORE_LINK: Label for read more link +LABEL_FOR_STARTBUTTON: Label for startbutton +LANDINGPAGE_CONTRAST_SEGMENT: Landingpage Contrast Segment +LANDINGPAGE_INFO_SEGMENT: Landingpage Info Segment +LANDINGPAGE_INTRO_SEGMENT: Landingpage Intro Segment +LANDINGPAGE_NAVIGATION_SEGMENT: Landingpage Navigation Segment +LANDINGPAGE_NEWS_SEGMENT: Landingpage News Segment +LANDINGPAGE_TEASER_SEGMENT: Landingpage Teaser Segment +LINK_FOR_STARTBUTTON: Link for startbutton +LINK_TO_REPOSITORY: Link to repository +LIST_ENTRIES_FROM_FOLDER: List entries from folder +NAVIGATIONS_AND_CHAPTERS: Navigations and Chapters +POSITION_OF_ARTICLE_AUTHOR_LINE_(TOP/BOTTOM): Position of article author-line (top/bottom) +POSITION_OF_ARTICLE_DATE_(TOP/BOTTOM): Position of article date (top/bottom) +POSITION_OF_CONTRAST_SEGMENT: Position of Contrast Segment +POSITION_OF_INFO_SEGMENT: Position of Info Segment +POSITION_OF_INTRO_SEGMENT: Position of Intro Segment +POSITION_OF_NAVI_SEGMENT: Position of Navi Segment +POSITION_OF_NEWS_SEGMENT: Position of News Segment +POSITION_OF_TEASER_SEGMENT: Position of Teaser Segment +POSITION_OF_THE_EDIT_LINK_(TOP/BOTTOM): Position of the edit link (top/bottom) +PRIMARY_BRAND_COLOR: Primary brand color +PRIMARY_FONT_COLOR: Primary font color +SECONDARY_BRAND_COLOR: Secondary brand color +SECONDARY_FONT_COLOR: Secondary font color +SHOW_CHAPTER_NUMBERS: Show Chapter Numbers +TEASER_1_LABEL: Teaser 1 Label +TEASER_1_LINK: Teaser 1 Link +TEASER_1_TEXT: Teaser 1 Text +TEASER_1_TITLE: Teaser 1 Title +TEASER_2_LABEL: Teaser 1 Label +TEASER_2_LINK: Teaser 2 Link +TEASER_2_TEXT: Teaser 2 Text +TEASER_2_TITLE: Teaser 2 Title +TEXT/LABEL_FOR_EDIT_LINK: Text/label for edit link +TEXT_LINKS: text-links +TEXT: Text +THIN_BORDER_COLOR: Thin border color +TITLE_FOR_NAVIGATION: Title for navigation +TITLE: Title +TOP: Top +USE_0_TO_DISABLE_THE_SECTION: Use 0 to disable the section +USED_AS_CONTRARY_COLOR_FOR_HOVERS_IN_NAVIGATION_AND_BUTTONS: Used as contrary color for hovers in navigation and buttons +USED_FOR_CONTENT_BACKGROUND__FONT_COLORS_ON_HOVER_AND_MORE: Used for content background, font-colors on hover and more +USED_FOR_LINKS__CHECK_CONTRAST_FOR_A11Y_: Used for links, check contrast for a11y. +USED_FOR_TEXT: Used for text +USED_FOR_THE_BODY_BACKGROUND_AND_BORDERS: Used for the body background and borders +USED_FOR_THIN_BORDERS_IN_NAVIGATIONS_AND_TABLES: Used for thin borders in navigations and tables +USE_MARKDOWN: Use Markdown \ No newline at end of file diff --git a/themes/cyanine/languages/admin/fr.yaml b/themes/cyanine/languages/admin/fr.yaml new file mode 100644 index 0000000..213e0e9 --- /dev/null +++ b/themes/cyanine/languages/admin/fr.yaml @@ -0,0 +1,84 @@ +# Français +# Ajoutez les traductions dans votre thème comme ici +ACTIVATE_A_LANDINGPAGE: Activer une landing page +ACTIVATE_FOOTER_COLUMNS: Activer les colonnes du pied de page +ADD_A_PATH_TO_A_FOLDER_FROM_WHICH_YOU_WANT_TO_LIST_ENTRIES: Ajouter un chemin d'accès à un dossier à partir duquel vous souhaitez lister les entrées +ADD_THE_BASE_URL_TO_THE_CONTENT_REPOSITORY__E_G__GITHUB__: Ajouter l'url de base vers un dépôt de contenu (par exemple Github). +ALL_FONTS_ARE_SYSTEM_FONTS_WITH_(FALLBACKS)_IF_THE_FONT_IS_NOT_INSTALLED: Toutes les polices sont des polices système (avec solution de recours si la police n'est pas installée) +ARTICLE_AUTHOR: Auteur de l'article +ARTICLE_DATE: Date de création +ARTICLE_EDIT_LINK: Lien d'édition de l'article +AUTHOR_INTRO_TEXT: Texte d'introduction de l'auteur +BASIC_FONT_FAMILY: Police de base +BOTTOM: Bas +BUTTON_LABEL: Étiquette de bouton +BUTTON_LINK: Bouton lien +COLLAPSE_NAVIGATION: Réduire les menus +COLLAPSE_AND_EXPAND_NAVIGATION?: Replier et développer les menus ? +COLORS: Couleurs +COLUMN_1: Colonne 1 +COLUMN_2: Colonne 2 +COLUMN_3: Colonne 3 +COUNT_CHAPTERS_IN_NAVIGATION?: Numéroter les chapitres dans le menu de navigation ? +CYANINE_IS_A_MODERN_AND_FLEXIBLE_MULTI_PURPOSE_THEME_AND_THE_STANDARD_THEME_FOR_TYPEMILL_: Cyanine est un thème multi-usages moderne et adaptable. C'est le thème standard de Typemill. +DATE_FORMAT: Format de la date de création/modifcation +DATE_INTRO_TEXT: Étiquette de la date +FONT_FAMILIES: Polices +FONT_FAMILY_FOR_HEADLINES: Police pour les titres +FONT_FAMILY_FOR_NAVIGATIONS: Police pour les menus +FOOTER_COLUMN_1_(USE_MARKDOWN): Pied de page colonne 1 (utilisez le markdown) +FOOTER_COLUMN_2_(USE_MARKDOWN): Pied de page colonne 2 (utilisez le markdown) +FOOTER_COLUMN_3_(USE_MARKDOWN): Pied de page colonne 3 (utilisez le markdown) +FOOTER_COLUMNS: Colonnes de pied de page +HEADLINE_FOR_NEWS_SEGMENT: Titres pour les segments Actualités +HOW_MANY_NAVIGATION_LEVELS?: Combien de niveaux de menu ? +LABEL_FOR_READ_MORE_LINK: Étiquette pour les liens Plus +LABEL_FOR_STARTBUTTON: Étiquette pour le bouton de démarrage +LANDINGPAGE_CONTRAST_SEGMENT: Segment Contraste de la landing page +LANDINGPAGE_INFO_SEGMENT: Segment Info de la landing page +LANDINGPAGE_INTRO_SEGMENT: Segment Intro de la landing page +LANDINGPAGE_NAVIGATION_SEGMENT: Segment Menu de la landing page +LANDINGPAGE_NEWS_SEGMENT: Segment Actualités de la landing page +LANDINGPAGE_TEASER_SEGMENT: Segment Teaser de la landing page +LINK_FOR_STARTBUTTON: Lien pour le bouton de démarrage +LINK_TO_REPOSITORY: Lien vers le dépôt Git +LIST_ENTRIES_FROM_FOLDER: Lister les entrées à partir du dossier +NAVIGATIONS_AND_CHAPTERS: Menus et chapitres +POSITION_OF_ARTICLE_AUTHOR_LINE_(TOP/BOTTOM): Position de la ligne Auteur (haut/bas) +POSITION_OF_ARTICLE_DATE_(TOP/BOTTOM): Position de la date de création/modification de l'article (haut/bas) +POSITION_OF_CONTRAST_SEGMENT: Position du segment Contraste +POSITION_OF_INFO_SEGMENT: Position du segment Info +POSITION_OF_INTRO_SEGMENT: Position du segment Intro +POSITION_OF_NAVI_SEGMENT: Position du segment Menu +POSITION_OF_NEWS_SEGMENT: Position du segment Actualités +POSITION_OF_TEASER_SEGMENT: Position du segment Teaser +POSITION_OF_THE_EDIT_LINK_(TOP/BOTTOM): Position du lien d'édition (haut/bas) +PRIMARY_BRAND_COLOR: Couleur principale +PRIMARY_FONT_COLOR: Couleur principale de police +SECONDARY_BRAND_COLOR: Couleur secondaire +SECONDARY_FONT_COLOR: Couleu secondaire de police +SHOW_CHAPTER_NUMBERS: Afficher les numéros de chapitre +SHOW_CHAPTER_NUMBERS_IN_NAVIGATION?: Afficher les numéros de chapitre dans les menus ? +TEASER_1_LABEL: Teaser 1 Label +TEASER_1_LINK: Teaser 1 Lien +TEASER_1_TEXT: Teaser 1 Texte +TEASER_1_TITLE: Teaser 1 Titre +TEASER_2_LABEL: Teaser 1 Label +TEASER_2_LINK: Teaser 2 Lien +TEASER_2_TEXT: Teaser 2 Texte +TEASER_2_TITLE: Teaser 2 Titre +TEXT/LABEL_FOR_EDIT_LINK: Texte/label pour le lien d'édition +TEXT_LINKS: Liens textes +TEXT: Texte +THIN_BORDER_COLOR: Couleur des bordures fines +TITLE_FOR_NAVIGATION: Titre pour le menu +TITLE: Titre +TOP: Haut +USE_0_TO_DISABLE_THE_SECTION: Utilisez 0 pour désactiver la section +USED_AS_CONTRARY_COLOR_FOR_HOVERS_IN_NAVIGATION_AND_BUTTONS: Utilisée comme couleur inverse au passage de la souris sur les menus et les boutons +USED_FOR_CONTENT_BACKGROUND__FONT_COLORS_ON_HOVER_AND_MORE: Utilisée comme couleur d'arrière-plan, couleur de police au passage de la souris et autres +USED_FOR_LINKS__CHECK_CONTRAST_FOR_A11Y_: Utilisée pour les liens, vérifiez le contraste pour a11y. +USED_FOR_TEXT: Utilisée pour le texte +USED_FOR_THE_BODY_BACKGROUND_AND_BORDERS: Utilisée pour l'arrière plan du body et pour les bordures +USED_FOR_THIN_BORDERS_IN_NAVIGATIONS_AND_TABLES: Utilisée pour les bordures fines dans les menus et les tableaux +USE_MARKDOWN: Utilisez le markdown diff --git a/themes/cyanine/languages/admin/it.yaml b/themes/cyanine/languages/admin/it.yaml new file mode 100644 index 0000000..cb6211b --- /dev/null +++ b/themes/cyanine/languages/admin/it.yaml @@ -0,0 +1,78 @@ +# Italiano +# Aggiungi traduzioni per il tuo tema in questo modo +ACTIVATE_A_LANDINGPAGE: Attiva una pagina di destinazione +ACTIVATE_FOOTER_COLUMNS: Attiva colonne piè di pagina +ADD_A_PATH_TO_A_FOLDER_FROM_WHICH_YOU_WANT_TO_LIST_ENTRIES: Aggiungi un percorso a una cartella da cui desideri elencare le voci +ADD_THE_BASE_URL_TO_THE_CONTENT_REPOSITORY__E_G__GITHUB__: Aggiungi l'URL di base al deposito dei contenuti (a esempio github). +ALL_FONTS_ARE_SYSTEM_FONTS_WITH_(FALLBACKS)_IF_THE_FONT_IS_NOT_INSTALLED: Tutti i caratteri sono caratteri di sistema con (fallback) se il carattere non è installato +ARTICLE_AUTHOR: Autore articolo +ARTICLE_DATE: Data articolo +ARTICLE_EDIT_LINK: Collegamento per la modifica dell'articolo +AUTHOR_INTRO_TEXT: Testo introduttivo dell'autore +BASIC_FONT_FAMILY: Famiglia di caratteri di base +BOTTOM: Basso +BUTTON_LABEL: Etichetta pulsante +BUTTON_LINK: Collegamento pulsante +COLORS: Colori +COUNT_CHAPTERS_IN_NAVIGATION?: Contare i capitoli nella navigazione? +CYANINE_IS_A_MODERN_AND_FLEXIBLE_MULTI_PURPOSE_THEME_AND_THE_STANDARD_THEME_FOR_TYPEMILL_: Cyanine è un tema multiuso moderno e flessibile e il tema standard per typemill. +DATE_FORMAT: Formato della data +DATE_INTRO_TEXT: Testo introduttivo della data +FONT_FAMILIES: Famiglie di caratteri +FONT_FAMILY_FOR_HEADLINES: Famiglia di caratteri per i titoli +FONT_FAMILY_FOR_NAVIGATIONS: Famiglia di caratteri per la navigazione +FOOTER_COLUMN_1_(USE_MARKDOWN): piè di pagina 1 (usa il markdown) +FOOTER_COLUMN_2_(USE_MARKDOWN): piè di pagina 2 (usa il markdown) +FOOTER_COLUMN_3_(USE_MARKDOWN): piè di pagina 3 (usa il markdown) +FOOTER_COLUMNS: Colonne piè di pagina +HEADLINE_FOR_NEWS_SEGMENT: Titolo per il segmento notizie +HOW_MANY_NAVIGATION_LEVELS?: Quanti livelli di navigazione? +LABEL_FOR_READ_MORE_LINK: Etichetta per il collegamento per ulteriori informazioni +LABEL_FOR_STARTBUTTON: Etichetta per il pulsante di avvio +LANDINGPAGE_CONTRAST_SEGMENT: Segmento contrasto della pagina di destinazione +LANDINGPAGE_INFO_SEGMENT: Segmento informazioni della pagina di destinazione +LANDINGPAGE_INTRO_SEGMENT: Segmento introduttivo della pagina di destinazione +LANDINGPAGE_NAVIGATION_SEGMENT: Segmento navigazione della pagina di destinazione +LANDINGPAGE_NEWS_SEGMENT: Segmento notizie della pagina di destinazione +LANDINGPAGE_TEASER_SEGMENT: Segmento teaser della pagina di destinazione +LINK_FOR_STARTBUTTON: Collegamento per il pulsante di avvio +LINK_TO_REPOSITORY: Collegamento al deposito +LIST_ENTRIES_FROM_FOLDER: Elenca le voci dalla cartella +NAVIGATIONS_AND_CHAPTERS: Navigazione e capitoli +POSITION_OF_ARTICLE_AUTHOR_LINE_(TOP/BOTTOM): Posizione della riga dell'autore dell'articolo (in alto/in basso) +POSITION_OF_ARTICLE_DATE_(TOP/BOTTOM): Posizione della data dell'articolo (in alto/in basso) +POSITION_OF_CONTRAST_SEGMENT: Posizione del segmento contrasto +POSITION_OF_INFO_SEGMENT: Posizione del segmento informazioni +POSITION_OF_INTRO_SEGMENT: Posizione del segmento introduttivo +POSITION_OF_NAVI_SEGMENT: Posizione del segmento navigazione +POSITION_OF_NEWS_SEGMENT: Posizione del segmento notizie +POSITION_OF_TEASER_SEGMENT: Posizione del segmento teaser +POSITION_OF_THE_EDIT_LINK_(TOP/BOTTOM): Posizione del collegamento di modifica (in alto/in basso) +PRIMARY_BRAND_COLOR: Colore principale +PRIMARY_FONT_COLOR: Colore carattere primario +SECONDARY_BRAND_COLOR: Colore secondario +SECONDARY_FONT_COLOR: Colore carattere secondario +SHOW_CHAPTER_NUMBERS: Mostra i numeri dei capitoli +TEASER_1_LABEL: Etichetta teaser 1 +TEASER_1_LINK: Collegamento teaser 1 +TEASER_1_TEXT: Testo teaser 1 +TEASER_1_TITLE: Titolo teaser 1 +TEASER_2_LABEL: Etichetta teaser 2 +TEASER_2_LINK: Collegamento teaser 2 +TEASER_2_TEXT: Testo teaser 2 +TEASER_2_TITLE: Titolo teaser 2 +TEXT/LABEL_FOR_EDIT_LINK: Testo/etichetta per collegamento di modifica +TEXT_LINKS: collegamenti testuali +TEXT: Testo +THIN_BORDER_COLOR: Colore del bordo sottile +TITLE_FOR_NAVIGATION: Titolo per la navigazione +TITLE: Titolo +TOP: Alto +USE_0_TO_DISABLE_THE_SECTION: Usa 0 per disabilitare la sezione +USED_AS_CONTRARY_COLOR_FOR_HOVERS_IN_NAVIGATION_AND_BUTTONS: Usato come colore contrario per il passaggio del mouse nella navigazione e nei pulsanti +USED_FOR_CONTENT_BACKGROUND__FONT_COLORS_ON_HOVER_AND_MORE: Usato per lo sfondo del contenuto, i colori dei caratteri al passaggio del mouse e per altro +USED_FOR_LINKS__CHECK_CONTRAST_FOR_A11Y_: Usato per i collegamenti, controlla il contrasto per a11y. +USED_FOR_TEXT: Usato per il testo +USED_FOR_THE_BODY_BACKGROUND_AND_BORDERS: Usato per lo sfondo e i bordi +USED_FOR_THIN_BORDERS_IN_NAVIGATIONS_AND_TABLES: Usato per bordi sottili nelle navigazioni e nelle tabelle +USE_MARKDOWN: Usa Markdown \ No newline at end of file diff --git a/themes/cyanine/languages/user/en.yaml b/themes/cyanine/languages/user/en.yaml new file mode 100644 index 0000000..0709419 --- /dev/null +++ b/themes/cyanine/languages/user/en.yaml @@ -0,0 +1,7 @@ +# English +# Please add translations for your theme like this +ALL_RIGHTS_RESERVED: All Rights Reserved +BUILT_WITH: Built with +BY: by +NEXT: next +PREVIOUS: previous \ No newline at end of file diff --git a/themes/cyanine/languages/user/it.yaml b/themes/cyanine/languages/user/it.yaml new file mode 100644 index 0000000..491c415 --- /dev/null +++ b/themes/cyanine/languages/user/it.yaml @@ -0,0 +1,7 @@ +# Italiano +# Aggiungi traduzioni per il tuo tema in questo modo +ALL_RIGHTS_RESERVED: Tutti i diritti riservati +BUILT_WITH: Costruito con +BY: di +NEXT: successivo +PREVIOUS: precedente \ No newline at end of file diff --git a/themes/cyanine/layout.twig b/themes/cyanine/layout.twig new file mode 100644 index 0000000..c85bc32 --- /dev/null +++ b/themes/cyanine/layout.twig @@ -0,0 +1,251 @@ + + + + + {% block title %}{% endblock %} + + + + + + + + {{ assets.renderMeta() }} + + {% block stylesheets %} + + + + + + + {% endblock %} + + + + + + + + + + + + + + + + + + + {% if is_loggedin() %} + + {% endif %} + + {% block content %}{% endblock %} + + {% include 'partials/footer.twig' %} + + {% block javascripts %} + + {{ assets.renderCSS() }} + + {% if settings.themes.cyanine.collapseNav %} + + {% endif %} + + {{ assets.renderJS() }} + + {% endblock %} + + + \ No newline at end of file diff --git a/themes/cyanine/page.twig b/themes/cyanine/page.twig new file mode 100644 index 0000000..b1ea322 --- /dev/null +++ b/themes/cyanine/page.twig @@ -0,0 +1,149 @@ +{% set published = metatabs.meta.manualdate ? metatabs.meta.manualdate : metatabs.meta.modified %} + +
          + +
          + + + +
          + + + +
          +
          + +

          {{ title }}

          + + {% if ("top" in settings.themes.cyanine.datePosition or "top" in settings.themes.cyanine.authorPosition or "top" in settings.themes.cyanine.gitPosition or "top" in settings.themes.cyanine.printPosition) %} +
          + +
          + {% if "top" in settings.themes.cyanine.gitPosition %} + {% if settings.themes.cyanine.editIcon %}{% else %}{{ settings.themes.cyanine.editText }}{% endif %} + {% endif %} + {% if "top" in settings.themes.cyanine.printPosition %} + {% if settings.themes.cyanine.printIcon %}{% else %}{{ settings.themes.cyanine.printText }}{% endif %} + {% endif %} +
          +
          + {% endif %} + +
          + + {{ content }} + + {% if ("bottom" in settings.themes.cyanine.datePosition or "bottom" in settings.themes.cyanine.authorPosition or "bottom" in settings.themes.cyanine.gitPosition or "bottom" in settings.themes.cyanine.printPosition) %} +
          + +
          + {% if "bottom" in settings.themes.cyanine.gitPosition %} + {% if settings.themes.cyanine.editIcon %}{% else %}{{ settings.themes.cyanine.editText }}{% endif %} + {% endif %} + {% if "bottom" in settings.themes.cyanine.printPosition %} + {% if settings.themes.cyanine.printIcon %}{% else %}{{ settings.themes.cyanine.printText }}{% endif %} + {% endif %} +
          +
          + {% endif %} + +
          + + {% if item.elementType == 'file' %} + + {% if item.prevItem or item.nextItem %} + +
          + {% if item.prevItem %} + ‹  {{ item.prevItem.name }} + {% endif %} + {% if item.nextItem %} + {{ item.nextItem.name }}  › + {% endif %} +
          + + {% endif %} + + {% endif %} + + {% if item.elementType == 'folder' and metatabs.meta.glossary %} + + + + {% elseif item.elementType == 'folder' and item.contains == 'pages' %} + + + + {% elseif item.elementType == 'folder' and item.contains == 'posts' %} + + {% include 'partials/posts.twig' %} + + {% endif %} + +
          + + + +
          + +
          \ No newline at end of file diff --git a/themes/cyanine/partials/breadcrumb.twig b/themes/cyanine/partials/breadcrumb.twig new file mode 100644 index 0000000..43f7a3f --- /dev/null +++ b/themes/cyanine/partials/breadcrumb.twig @@ -0,0 +1,23 @@ +
          + +
          + {{ settings.title }} + + {% for crumb in breadcrumb %} +   ›   + {% if loop.last %} + {{ crumb.name }} + {% else %} + {{ crumb.name }} + {% endif %} + {% endfor %} +
          +
          + {% if item.prevItem %} + ‹  {{ settings.themes.cyanine.previous|default('previous') }} + {% endif %} + {% if item.nextItem %} + {{ settings.themes.cyanine.next|default('next') }}  › + {% endif %} +
          +
          \ No newline at end of file diff --git a/themes/cyanine/partials/footer.twig b/themes/cyanine/partials/footer.twig new file mode 100644 index 0000000..e41eac9 --- /dev/null +++ b/themes/cyanine/partials/footer.twig @@ -0,0 +1,43 @@ +{% if settings.themes.cyanine.footercolumns is not empty %} + +
          +
          + +
          +
          + +{% endif %} + +
          +
          + +
          +
          \ No newline at end of file diff --git a/themes/cyanine/partials/navigation.twig b/themes/cyanine/partials/navigation.twig new file mode 100644 index 0000000..f223c1b --- /dev/null +++ b/themes/cyanine/partials/navigation.twig @@ -0,0 +1,39 @@ +{% macro loop_over(navigation,chapnum) %} + + {% import _self as macros %} + + {% for element in navigation %} + + {% set depth = element.keyPathArray|length %} + + {% if element.activeParent %} +
        • + {% elseif element.active %} +
        • + {% else %} +
        • + {% endif %} + + {% if (element.elementType == 'folder') %} + {% if chapnum %}{{ element.chapter }}. {% endif %}{{ element.name }} + {% if (element.folderContent|length > 0) and (element.contains == 'pages') %} +
            + {{ macros.loop_over(element.folderContent, chapnum) }} +
          + {% endif %} + {% else %} + {% if chapnum %}{{ element.chapter }} {% endif %}{{ element.name }} + {% endif %} +
        • + {% endfor %} +{% endmacro %} + +{% import _self as macros %} + +{% if settings.themes.cyanine.collapseNav %} + +{% endif %} + + \ No newline at end of file diff --git a/themes/cyanine/partials/navigationFlat.twig b/themes/cyanine/partials/navigationFlat.twig new file mode 100644 index 0000000..db434da --- /dev/null +++ b/themes/cyanine/partials/navigationFlat.twig @@ -0,0 +1,27 @@ +{% set maxdepth = navidepth ? navidepth : 2 %} + +{% macro loop_over(navigation, level, maxdepth, chapnum) %} + + {% import _self as macros %} + + {% for element in navigation %} +
        • + {% if element.elementType == 'folder' and level < maxdepth %} + {% if chapnum %}{{ element.chapter }}. {% endif %}{{ element.name }} + {% if element.contains == 'pages' %} +
            + {{ macros.loop_over(element.folderContent,level+1, maxdepth, chapnum) }} +
          + {% endif %} + {% else %} + {% if chapnum %}{{ element.chapter }} {% endif %}{{ element.name }} + {% endif %} +
        • + {% endfor %} +{% endmacro %} + +{% import _self as macros %} + + \ No newline at end of file diff --git a/themes/cyanine/partials/navigationGlossary.twig b/themes/cyanine/partials/navigationGlossary.twig new file mode 100644 index 0000000..f46f529 --- /dev/null +++ b/themes/cyanine/partials/navigationGlossary.twig @@ -0,0 +1,21 @@ +{% macro loop_over(navigation) %} + + {% import _self as macros %} + {% set alphabet = 'ß' %} + + {% for element in navigation|sort((a, b) => a.name <=> b.name) %} + {% if element.name|first|upper != alphabet %} + {% set alphabet = element.name|first|upper %} +

          {{ alphabet }}

          + {% endif %} +
        • + {{ element.name }} +
        • + {% endfor %} +{% endmacro %} + +{% import _self as macros %} + +
            + {{ macros.loop_over(glossary) }} +
          \ No newline at end of file diff --git a/themes/cyanine/partials/posts.twig b/themes/cyanine/partials/posts.twig new file mode 100644 index 0000000..6136a7a --- /dev/null +++ b/themes/cyanine/partials/posts.twig @@ -0,0 +1,51 @@ +{% set pagesize = 10 %} +{% set pages = ( item.folderContent|length / pagesize)|round(0, 'ceil') %} +{% set currentpage = currentpage ? currentpage : 1 %} +{% set currentposts = (currentpage - 1) * pagesize %} + +
            + + {% for element in item.folderContent|slice(currentposts, pagesize) %} + + {% set post = getPageMeta(settings, element) %} + {% set date = element.order[0:4] ~ '-' ~ element.order[4:2] ~ '-' ~ element.order[6:2] %} + + {% if settings.themes.cyanine.blogimage and post.meta.heroimage != '' %} +
          • +
            + {{ post.meta.heroimagealt }} +
            +
            +
            +

            {{ post.meta.title }}

            +
            | {{ post.meta.author | default(post.meta.owner) }}
            +
            +

            {{ post.meta.description }}

            +
            +
          • + {% else %} +
          • +
            +

            {{ post.meta.title }}

            +
            | {{ post.meta.author | default(post.meta.owner) }}
            +
            +

            {{ post.meta.description }}

            +
          • + {% endif %} + + {% endfor %} + + {% if pages > 1 %} +
            +

            Page: + {% for i in 1 .. pages %} + {% if i == currentpage %} + {{i}} + {% else %} + {{i}} + {% endif %} + {% endfor %} +

            + {% endif %} + +
          \ No newline at end of file diff --git a/typemill.png b/typemill.png new file mode 100644 index 0000000000000000000000000000000000000000..917ec530d512d51fa4d4c572575a4b3d71cb8c82 GIT binary patch literal 99417 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 g)#bK?hP6WiTtwv1y4I77Bk