From 3b0b6546ca4e982f9795afeff45e9f0c5668efd4 Mon Sep 17 00:00:00 2001 From: Bastian Allgeier Date: Tue, 28 Nov 2023 09:33:56 +0100 Subject: [PATCH] Upgrade to 4.0.0 --- .gitignore | 1 + .../20180905_through-the-desert/note.txt | 29 - .../20181005_chasing-waterfalls/note.txt | 29 - .../20190625_a-night-in-the-forest/note.txt | 29 - .../20200421_across-the-ocean/note.txt | 29 - .../desert.jpg | Bin .../desert.jpg.txt | 0 .../20200905_through-the-desert/note.txt | 29 + .../20200913_himalaya-and-back/note.txt | 29 - .../20201210_exploring-the-universe/note.txt | 29 - .../20211005_chasing-waterfalls/note.txt | 29 + .../waterfall.jpg | Bin .../waterfall.jpg.txt | 0 .../forest.jpg | Bin .../forest.jpg.txt | 0 .../20220625_a-night-in-the-forest/note.txt | 29 + .../20230421_across-the-ocean/note.txt | 29 + .../ocean.jpg | Bin .../ocean.jpg.txt | 0 .../himalaya.jpg | Bin .../himalaya.jpg.txt | 0 .../20230913_himalaya-and-back/note.txt | 29 + .../dark-forest.jpg | Bin .../dark-forest.jpg.txt | 0 .../20231124_exploring-the-universe/note.txt | 29 + .../tent-in-the-woods.jpg | Bin .../tent-in-the-woods.jpg.txt | 0 .../universe.jpg | Bin .../universe.jpg.txt | 0 .../_drafts/in-the-jungle-of-sumatra/note.txt | 14 +- kirby/.editorconfig | 4 + kirby/LICENSE.md | 71 +- kirby/SECURITY.md | 21 +- kirby/bootstrap.php | 4 +- kirby/composer.json | 17 +- kirby/composer.lock | 139 +- kirby/config/aliases.php | 38 +- kirby/config/api/authentication.php | 8 +- kirby/config/api/collections.php | 22 +- kirby/config/api/models.php | 1 + kirby/config/api/models/File.php | 2 +- kirby/config/api/models/FileBlueprint.php | 5 +- kirby/config/api/models/FileVersion.php | 2 +- kirby/config/api/models/Language.php | 2 +- kirby/config/api/models/License.php | 17 + kirby/config/api/models/Page.php | 2 +- kirby/config/api/models/PageBlueprint.php | 5 +- kirby/config/api/models/Role.php | 2 +- kirby/config/api/models/Site.php | 2 +- kirby/config/api/models/SiteBlueprint.php | 2 +- kirby/config/api/models/System.php | 2 +- kirby/config/api/models/Translation.php | 2 +- kirby/config/api/models/User.php | 2 +- kirby/config/api/models/UserBlueprint.php | 5 +- kirby/config/api/routes.php | 5 +- kirby/config/api/routes/files.php | 38 +- kirby/config/api/routes/kql.php | 35 + kirby/config/api/routes/pages.php | 3 +- kirby/config/api/routes/site.php | 2 +- kirby/config/areas/account.php | 1 + kirby/config/areas/account/dialogs.php | 36 + kirby/config/areas/account/drawers.php | 19 + kirby/config/areas/account/views.php | 10 +- kirby/config/areas/fields/dialogs.php | 61 + kirby/config/areas/fields/drawers.php | 61 + kirby/config/areas/files/dialogs.php | 42 +- kirby/config/areas/lab.php | 11 + kirby/config/areas/lab/drawers.php | 30 + kirby/config/areas/lab/views.php | 138 ++ kirby/config/areas/languages.php | 2 +- kirby/config/areas/languages/dialogs.php | 121 +- kirby/config/areas/languages/views.php | 109 +- kirby/config/areas/search.php | 11 + kirby/config/areas/search/views.php | 17 + kirby/config/areas/site.php | 2 + kirby/config/areas/site/dialogs.php | 230 ++- kirby/config/areas/site/drawers.php | 33 + kirby/config/areas/site/dropdowns.php | 5 - kirby/config/areas/site/requests.php | 66 + kirby/config/areas/site/searches.php | 51 +- kirby/config/areas/system/dialogs.php | 86 +- kirby/config/areas/system/views.php | 25 +- kirby/config/areas/users.php | 1 + kirby/config/areas/users/dialogs.php | 39 +- kirby/config/areas/users/drawers.php | 18 + kirby/config/areas/users/searches.php | 22 +- kirby/config/areas/users/views.php | 4 + kirby/config/blocks/gallery/gallery.yml | 2 +- kirby/config/blocks/heading/heading.yml | 29 +- kirby/config/blocks/image/image.yml | 4 +- kirby/config/blueprints/blocks/code.yml | 56 - kirby/config/blueprints/blocks/heading.yml | 20 - kirby/config/blueprints/blocks/image.yml | 16 - kirby/config/blueprints/blocks/quote.yml | 12 - kirby/config/blueprints/blocks/table.yml | 25 - kirby/config/blueprints/blocks/text.yml | 5 - kirby/config/blueprints/blocks/video.yml | 8 - kirby/config/blueprints/files/default.yml | 2 - kirby/config/blueprints/pages/default.yml | 3 - kirby/config/blueprints/site.yml | 7 - kirby/config/components.php | 103 +- kirby/config/fields/color.php | 98 + kirby/config/fields/files.php | 6 +- kirby/config/fields/headline.php | 9 +- kirby/config/fields/hidden.php | 4 +- kirby/config/fields/info.php | 1 - kirby/config/fields/link.php | 156 ++ kirby/config/fields/list.php | 6 + kirby/config/fields/mixins/layout.php | 2 +- kirby/config/fields/mixins/options.php | 2 +- kirby/config/fields/mixins/upload.php | 39 +- kirby/config/fields/multiselect.php | 24 +- kirby/config/fields/object.php | 2 +- kirby/config/fields/structure.php | 121 +- kirby/config/fields/tags.php | 15 + kirby/config/fields/text.php | 7 + kirby/config/fields/users.php | 36 +- kirby/config/fields/writer.php | 63 +- kirby/config/helpers.php | 60 +- kirby/config/methods.php | 353 ++-- kirby/config/routes.php | 47 +- kirby/config/sections/files.php | 20 +- kirby/config/sections/info.php | 4 + kirby/config/sections/mixins/layout.php | 2 +- kirby/config/sections/mixins/sort.php | 4 + kirby/config/sections/pages.php | 61 +- kirby/config/setup.php | 6 - kirby/config/tags.php | 81 +- kirby/i18n/translations/bg.json | 257 ++- kirby/i18n/translations/ca.json | 175 +- kirby/i18n/translations/cs.json | 237 ++- kirby/i18n/translations/da.json | 185 +- kirby/i18n/translations/de.json | 173 +- kirby/i18n/translations/el.json | 297 ++- kirby/i18n/translations/en.json | 150 +- kirby/i18n/translations/eo.json | 151 +- kirby/i18n/translations/es_419.json | 237 ++- kirby/i18n/translations/es_ES.json | 213 ++- kirby/i18n/translations/fa.json | 265 ++- kirby/i18n/translations/fi.json | 189 +- kirby/i18n/translations/fr.json | 161 +- kirby/i18n/translations/hu.json | 235 ++- kirby/i18n/translations/id.json | 191 +- kirby/i18n/translations/is_IS.json | 181 +- kirby/i18n/translations/it.json | 151 +- kirby/i18n/translations/ko.json | 255 ++- kirby/i18n/translations/lt.json | 151 +- kirby/i18n/translations/nb.json | 253 ++- kirby/i18n/translations/nl.json | 229 ++- kirby/i18n/translations/pl.json | 193 +- kirby/i18n/translations/pt_BR.json | 209 ++- kirby/i18n/translations/pt_PT.json | 213 ++- kirby/i18n/translations/ro.json | 189 +- kirby/i18n/translations/ru.json | 259 ++- kirby/i18n/translations/sk.json | 191 +- kirby/i18n/translations/sv_SE.json | 217 ++- kirby/i18n/translations/tr.json | 211 ++- kirby/package-lock.json | 6 + kirby/panel/dist/css/style.css | 1 - kirby/panel/dist/css/style.min.css | 1 + kirby/panel/dist/img/icons.svg | 1083 ++++++----- kirby/panel/dist/js/Docs.min.js | 1 + kirby/panel/dist/js/DocsView.min.js | 1 + kirby/panel/dist/js/Highlight.min.js | 1 + kirby/panel/dist/js/IndexView.min.js | 1 + kirby/panel/dist/js/PlaygroundView.min.js | 1 + .../js/container-query-polyfill.modern.min.js | 1 + kirby/panel/dist/js/index.js | 1 - kirby/panel/dist/js/index.min.js | 1 + kirby/panel/dist/js/plugins.js | 138 +- kirby/panel/dist/js/vendor.js | 6 - kirby/panel/dist/js/vendor.min.js | 16 + kirby/panel/dist/js/vue.js | 11 - kirby/panel/dist/js/vue.min.js | 11 + .../{vuedraggable.js => vuedraggable.min.js} | 0 kirby/src/Api/Api.php | 255 ++- kirby/src/Api/Collection.php | 22 +- kirby/src/Api/Model.php | 17 +- kirby/src/Blueprint/Collection.php | 4 +- kirby/src/Blueprint/Config.php | 2 +- kirby/src/Blueprint/Extension.php | 2 +- kirby/src/Blueprint/Factory.php | 2 +- kirby/src/Blueprint/Node.php | 2 +- kirby/src/Blueprint/NodeI18n.php | 2 +- kirby/src/Blueprint/NodeIcon.php | 2 +- kirby/src/Blueprint/NodeProperty.php | 2 +- kirby/src/Blueprint/NodeString.php | 2 +- kirby/src/Blueprint/NodeText.php | 4 +- kirby/src/Cms/Api.php | 92 +- kirby/src/Cms/App.php | 451 ++--- kirby/src/Cms/AppCaches.php | 13 +- kirby/src/Cms/AppErrors.php | 52 +- kirby/src/Cms/AppPlugins.php | 243 +-- kirby/src/Cms/AppTranslations.php | 26 +- kirby/src/Cms/AppUsers.php | 50 +- kirby/src/Cms/Auth.php | 147 +- kirby/src/Cms/Auth/Challenge.php | 6 +- kirby/src/Cms/Auth/EmailChallenge.php | 5 +- kirby/src/Cms/Auth/Status.php | 131 +- kirby/src/Cms/Auth/TotpChallenge.php | 65 + kirby/src/Cms/Block.php | 87 +- kirby/src/Cms/BlockConverter.php | 4 +- kirby/src/Cms/Blocks.php | 40 +- kirby/src/Cms/Blueprint.php | 163 +- kirby/src/Cms/Collection.php | 50 +- kirby/src/Cms/Collections.php | 24 +- kirby/src/Cms/ContentLock.php | 52 +- kirby/src/Cms/ContentLocks.php | 28 +- kirby/src/Cms/Core.php | 52 +- kirby/src/Cms/Email.php | 48 +- kirby/src/Cms/Event.php | 68 +- kirby/src/Cms/Fieldset.php | 132 +- kirby/src/Cms/Fieldsets.php | 19 +- kirby/src/Cms/File.php | 472 ++--- kirby/src/Cms/FileActions.php | 219 ++- kirby/src/Cms/FileBlueprint.php | 30 +- kirby/src/Cms/FileModifications.php | 65 +- kirby/src/Cms/FilePermissions.php | 11 +- kirby/src/Cms/FilePicker.php | 8 +- kirby/src/Cms/FileRules.php | 85 +- kirby/src/Cms/FileVersion.php | 60 +- kirby/src/Cms/Files.php | 29 +- kirby/src/Cms/Find.php | 33 +- kirby/src/Cms/HasChildren.php | 83 +- kirby/src/Cms/HasFiles.php | 73 +- kirby/src/Cms/HasMethods.php | 20 +- kirby/src/Cms/HasSiblings.php | 14 - kirby/src/Cms/Helpers.php | 51 +- kirby/src/Cms/Html.php | 48 +- kirby/src/Cms/Ingredients.php | 21 +- kirby/src/Cms/Item.php | 54 +- kirby/src/Cms/Items.php | 61 +- kirby/src/Cms/Language.php | 457 ++--- kirby/src/Cms/LanguageRouter.php | 49 +- kirby/src/Cms/LanguageRoutes.php | 32 +- kirby/src/Cms/LanguageRules.php | 8 - kirby/src/Cms/LanguageVariable.php | 122 ++ kirby/src/Cms/Languages.php | 30 +- kirby/src/Cms/Layout.php | 39 +- kirby/src/Cms/LayoutColumn.php | 33 +- kirby/src/Cms/LayoutColumns.php | 4 +- kirby/src/Cms/Layouts.php | 43 +- kirby/src/Cms/License.php | 523 ++++++ kirby/src/Cms/LicenseStatus.php | 127 ++ kirby/src/Cms/LicenseType.php | 111 ++ kirby/src/Cms/Loader.php | 42 +- kirby/src/Cms/Media.php | 82 +- kirby/src/Cms/Model.php | 2 +- kirby/src/Cms/ModelPermissions.php | 66 +- kirby/src/Cms/ModelWithContent.php | 532 ++++-- kirby/src/Cms/Nest.php | 2 + kirby/src/Cms/NestObject.php | 1 + kirby/src/Cms/Page.php | 604 +++---- kirby/src/Cms/PageActions.php | 228 ++- kirby/src/Cms/PageBlueprint.php | 19 +- kirby/src/Cms/PagePermissions.php | 25 +- kirby/src/Cms/PagePicker.php | 65 +- kirby/src/Cms/PageRules.php | 186 +- kirby/src/Cms/PageSiblings.php | 9 - kirby/src/Cms/Pages.php | 106 +- kirby/src/Cms/Pagination.php | 13 - kirby/src/Cms/Permissions.php | 72 +- kirby/src/Cms/Picker.php | 47 +- kirby/src/Cms/Plugin.php | 79 +- kirby/src/Cms/PluginAsset.php | 120 ++ kirby/src/Cms/PluginAssets.php | 162 +- kirby/src/Cms/R.php | 6 +- kirby/src/Cms/Responder.php | 75 +- kirby/src/Cms/Response.php | 6 +- kirby/src/Cms/Role.php | 119 +- kirby/src/Cms/Roles.php | 22 +- kirby/src/Cms/S.php | 6 +- kirby/src/Cms/Search.php | 45 +- kirby/src/Cms/Section.php | 31 +- kirby/src/Cms/Site.php | 310 +--- kirby/src/Cms/SiteActions.php | 45 +- kirby/src/Cms/SiteBlueprint.php | 6 +- kirby/src/Cms/SitePermissions.php | 2 +- kirby/src/Cms/SiteRules.php | 6 - kirby/src/Cms/Structure.php | 64 +- kirby/src/Cms/StructureObject.php | 186 +- kirby/src/Cms/System.php | 174 +- kirby/src/Cms/Translation.php | 59 +- kirby/src/Cms/Translations.php | 35 +- kirby/src/Cms/Url.php | 12 +- kirby/src/Cms/User.php | 421 ++--- kirby/src/Cms/UserActions.php | 164 +- kirby/src/Cms/UserBlueprint.php | 1 - kirby/src/Cms/UserPermissions.php | 26 +- kirby/src/Cms/UserPicker.php | 20 +- kirby/src/Cms/UserRules.php | 78 +- kirby/src/Cms/Users.php | 37 +- kirby/src/Cms/Visitor.php | 6 +- kirby/src/{Cms => Content}/Content.php | 91 +- kirby/src/Content/ContentStorage.php | 314 ++++ kirby/src/Content/ContentStorageHandler.php | 96 + .../{Cms => Content}/ContentTranslation.php | 151 +- kirby/src/{Cms => Content}/Field.php | 107 +- .../PlainTextContentStorageHandler.php | 253 +++ kirby/src/Data/Data.php | 27 +- kirby/src/Data/Txt.php | 9 +- kirby/src/Database/Database.php | 6 +- kirby/src/Database/Db.php | 5 +- kirby/src/Database/Query.php | 9 +- kirby/src/Database/Sql.php | 2 +- kirby/src/Email/Body.php | 45 +- kirby/src/Email/Email.php | 259 +-- kirby/src/Exception/AuthException.php | 21 + .../src/Exception/BadMethodCallException.php | 8 +- kirby/src/Exception/DuplicateException.php | 6 +- kirby/src/Exception/ErrorPageException.php | 6 +- kirby/src/Exception/Exception.php | 70 +- .../Exception/InvalidArgumentException.php | 8 +- kirby/src/Exception/LogicException.php | 6 +- kirby/src/Exception/NotFoundException.php | 6 +- kirby/src/Exception/PermissionException.php | 6 +- kirby/src/Filesystem/Asset.php | 26 +- kirby/src/Filesystem/Dir.php | 4 +- kirby/src/Filesystem/F.php | 40 +- kirby/src/Filesystem/File.php | 78 +- kirby/src/Filesystem/Filename.php | 13 +- kirby/src/Filesystem/IsFile.php | 34 +- kirby/src/Filesystem/Mime.php | 65 +- kirby/src/Form/Field.php | 175 +- kirby/src/Form/Field/BlocksField.php | 144 +- kirby/src/Form/Field/LayoutField.php | 137 +- kirby/src/Form/FieldClass.php | 435 ++--- kirby/src/Form/Fields.php | 5 - kirby/src/Form/Form.php | 121 +- kirby/src/Form/Mixin/EmptyState.php | 4 +- kirby/src/Form/Mixin/Max.php | 2 +- kirby/src/Form/Mixin/Min.php | 2 +- kirby/src/Form/Options.php | 205 --- kirby/src/Form/OptionsApi.php | 244 --- kirby/src/Form/OptionsQuery.php | 273 --- kirby/src/Form/Validations.php | 88 +- kirby/src/Http/Cookie.php | 40 +- kirby/src/Http/Environment.php | 64 +- kirby/src/Http/Header.php | 24 +- kirby/src/Http/Params.php | 2 +- kirby/src/Http/Query.php | 2 +- kirby/src/Http/Remote.php | 27 +- kirby/src/Http/Request.php | 37 +- kirby/src/Http/Request/Auth.php | 11 +- kirby/src/Http/Request/Data.php | 1 + kirby/src/Http/Response.php | 13 +- kirby/src/Http/Route.php | 2 +- kirby/src/Http/Uri.php | 73 +- kirby/src/Http/Url.php | 20 +- kirby/src/Http/Visitor.php | 32 +- kirby/src/Image/Camera.php | 10 +- kirby/src/Image/Darkroom.php | 34 +- kirby/src/Image/Darkroom/GdLib.php | 25 + kirby/src/Image/Darkroom/ImageMagick.php | 34 +- kirby/src/Image/Dimensions.php | 29 +- kirby/src/Image/Exif.php | 77 +- kirby/src/Image/Focus.php | 110 ++ kirby/src/Image/Image.php | 2 +- kirby/src/Image/Location.php | 16 +- kirby/src/Image/QrCode.php | 1603 +++++++++++++++++ kirby/src/Option/OptionsApi.php | 2 +- kirby/src/Option/OptionsQuery.php | 2 +- kirby/src/Panel/Assets.php | 298 +++ kirby/src/Panel/ChangesDialog.php | 71 + kirby/src/Panel/Dialog.php | 37 +- kirby/src/Panel/Document.php | 233 +-- kirby/src/Panel/Drawer.php | 21 + kirby/src/Panel/Dropdown.php | 84 +- kirby/src/Panel/Field.php | 68 +- kirby/src/Panel/File.php | 160 +- kirby/src/Panel/Home.php | 33 +- kirby/src/Panel/Json.php | 57 +- kirby/src/Panel/Lab/Category.php | 134 ++ kirby/src/Panel/Lab/Docs.php | 340 ++++ kirby/src/Panel/Lab/Example.php | 271 +++ kirby/src/Panel/Lab/Snippet.php | 26 + kirby/src/Panel/Lab/Template.php | 34 + kirby/src/Panel/Menu.php | 221 +++ kirby/src/Panel/Model.php | 159 +- kirby/src/Panel/Page.php | 51 +- kirby/src/Panel/PageCreateDialog.php | 313 ++++ kirby/src/Panel/Panel.php | 158 +- kirby/src/Panel/Request.php | 24 + kirby/src/Panel/Search.php | 12 +- kirby/src/Panel/Site.php | 6 + kirby/src/Panel/User.php | 41 +- kirby/src/Panel/UserTotpDisableDialog.php | 114 ++ kirby/src/Panel/UserTotpEnableDialog.php | 95 + kirby/src/Panel/View.php | 160 +- kirby/src/Parsley/Element.php | 23 +- kirby/src/Parsley/Inline.php | 83 +- kirby/src/Parsley/Parsley.php | 37 +- kirby/src/Parsley/Schema/Blocks.php | 56 +- kirby/src/Query/Expression.php | 2 +- kirby/src/Query/Query.php | 5 + kirby/src/Query/Segments.php | 2 +- kirby/src/Sane/DomHandler.php | 62 +- kirby/src/Sane/Handler.php | 16 +- kirby/src/Sane/Html.php | 7 +- kirby/src/Sane/Sane.php | 64 +- kirby/src/Sane/Svg.php | 21 +- kirby/src/Sane/Svgz.php | 15 +- kirby/src/Sane/Xml.php | 10 +- kirby/src/Session/AutoSession.php | 28 +- kirby/src/Session/FileSessionStore.php | 77 +- kirby/src/Session/Session.php | 320 ++-- kirby/src/Session/SessionData.php | 219 +-- kirby/src/Session/SessionStore.php | 17 +- kirby/src/Session/Sessions.php | 107 +- kirby/src/Template/Slots.php | 5 +- kirby/src/Template/Snippet.php | 32 +- kirby/src/Template/Template.php | 24 +- kirby/src/Text/KirbyTag.php | 4 +- kirby/src/Toolkit/A.php | 671 ++++--- kirby/src/Toolkit/Collection.php | 24 +- kirby/src/Toolkit/Component.php | 142 +- kirby/src/Toolkit/Config.php | 5 +- kirby/src/Toolkit/Controller.php | 2 + kirby/src/Toolkit/Date.php | 16 +- kirby/src/Toolkit/Dom.php | 66 +- kirby/src/Toolkit/Html.php | 4 +- kirby/src/Toolkit/I18n.php | 259 +-- kirby/src/Toolkit/Iterator.php | 19 +- kirby/src/Toolkit/LazyValue.php | 48 + kirby/src/Toolkit/Locale.php | 8 +- kirby/src/Toolkit/Obj.php | 1 + kirby/src/Toolkit/Pagination.php | 81 +- kirby/src/Toolkit/Properties.php | 2 + kirby/src/Toolkit/Query.php | 251 --- kirby/src/Toolkit/Silo.php | 2 +- kirby/src/Toolkit/Str.php | 216 ++- kirby/src/Toolkit/Totp.php | 144 ++ kirby/src/Toolkit/V.php | 11 +- kirby/src/Toolkit/View.php | 12 +- kirby/src/Toolkit/Xml.php | 21 +- kirby/src/Uuid/BlockUuid.php | 2 +- kirby/src/Uuid/FieldUuid.php | 37 +- kirby/src/Uuid/FileUuid.php | 17 +- kirby/src/Uuid/HasUuids.php | 5 + kirby/src/Uuid/ModelUuid.php | 13 - kirby/src/Uuid/PageUuid.php | 10 +- kirby/src/Uuid/SiteUuid.php | 9 + kirby/src/Uuid/StructureUuid.php | 2 +- kirby/src/Uuid/UserUuid.php | 10 + kirby/src/Uuid/Uuid.php | 82 +- kirby/vendor/autoload.php | 2 +- kirby/vendor/bin/yaml-lint | 5 +- kirby/vendor/christian-riesen/base32/LICENSE | 19 + .../christian-riesen/base32/src/Base32.php | 168 ++ .../christian-riesen/base32/src/Base32Hex.php | 68 + kirby/vendor/composer/autoload_classmap.php | 39 +- kirby/vendor/composer/autoload_psr4.php | 1 + kirby/vendor/composer/autoload_real.php | 10 +- kirby/vendor/composer/autoload_static.php | 57 +- kirby/vendor/composer/installed.json | 144 +- kirby/vendor/composer/installed.php | 41 +- .../laminas/laminas-escaper/composer.json | 14 +- .../laminas/laminas-escaper/src/Escaper.php | 18 +- .../vendor/phpmailer/phpmailer/composer.json | 1 + .../phpmailer/language/phpmailer.lang-as.php | 35 + .../phpmailer/language/phpmailer.lang-bn.php | 35 + .../phpmailer/language/phpmailer.lang-da.php | 1 + .../phpmailer/language/phpmailer.lang-pl.php | 9 +- .../phpmailer/phpmailer/src/PHPMailer.php | 130 +- kirby/vendor/phpmailer/phpmailer/src/POP3.php | 2 +- kirby/vendor/phpmailer/phpmailer/src/SMTP.php | 33 +- .../symfony/deprecation-contracts/LICENSE | 2 +- .../deprecation-contracts/composer.json | 4 +- .../deprecation-contracts/function.php | 2 +- .../symfony/yaml/Command/LintCommand.php | 79 +- kirby/vendor/symfony/yaml/Dumper.php | 16 +- .../symfony/yaml/Exception/ParseException.php | 30 +- kirby/vendor/symfony/yaml/Inline.php | 227 +-- kirby/vendor/symfony/yaml/Parser.php | 138 +- kirby/vendor/symfony/yaml/Tag/TaggedValue.php | 8 +- kirby/vendor/symfony/yaml/Unescaper.php | 82 +- kirby/vendor/symfony/yaml/Yaml.php | 11 +- kirby/vendor/symfony/yaml/composer.json | 11 +- kirby/views/panel.php | 8 +- site/blueprints/pages/about.yml | 2 +- 480 files changed, 21371 insertions(+), 13327 deletions(-) delete mode 100644 content/2_notes/20180905_through-the-desert/note.txt delete mode 100644 content/2_notes/20181005_chasing-waterfalls/note.txt delete mode 100644 content/2_notes/20190625_a-night-in-the-forest/note.txt delete mode 100644 content/2_notes/20200421_across-the-ocean/note.txt rename content/2_notes/{20180905_through-the-desert => 20200905_through-the-desert}/desert.jpg (100%) rename content/2_notes/{20180905_through-the-desert => 20200905_through-the-desert}/desert.jpg.txt (100%) create mode 100644 content/2_notes/20200905_through-the-desert/note.txt delete mode 100644 content/2_notes/20200913_himalaya-and-back/note.txt delete mode 100644 content/2_notes/20201210_exploring-the-universe/note.txt create mode 100644 content/2_notes/20211005_chasing-waterfalls/note.txt rename content/2_notes/{20181005_chasing-waterfalls => 20211005_chasing-waterfalls}/waterfall.jpg (100%) rename content/2_notes/{20181005_chasing-waterfalls => 20211005_chasing-waterfalls}/waterfall.jpg.txt (100%) rename content/2_notes/{20190625_a-night-in-the-forest => 20220625_a-night-in-the-forest}/forest.jpg (100%) rename content/2_notes/{20190625_a-night-in-the-forest => 20220625_a-night-in-the-forest}/forest.jpg.txt (100%) create mode 100644 content/2_notes/20220625_a-night-in-the-forest/note.txt create mode 100644 content/2_notes/20230421_across-the-ocean/note.txt rename content/2_notes/{20200421_across-the-ocean => 20230421_across-the-ocean}/ocean.jpg (100%) rename content/2_notes/{20200421_across-the-ocean => 20230421_across-the-ocean}/ocean.jpg.txt (100%) rename content/2_notes/{20200913_himalaya-and-back => 20230913_himalaya-and-back}/himalaya.jpg (100%) rename content/2_notes/{20200913_himalaya-and-back => 20230913_himalaya-and-back}/himalaya.jpg.txt (100%) create mode 100644 content/2_notes/20230913_himalaya-and-back/note.txt rename content/2_notes/{20201210_exploring-the-universe => 20231124_exploring-the-universe}/dark-forest.jpg (100%) rename content/2_notes/{20201210_exploring-the-universe => 20231124_exploring-the-universe}/dark-forest.jpg.txt (100%) create mode 100644 content/2_notes/20231124_exploring-the-universe/note.txt rename content/2_notes/{20201210_exploring-the-universe => 20231124_exploring-the-universe}/tent-in-the-woods.jpg (100%) rename content/2_notes/{20201210_exploring-the-universe => 20231124_exploring-the-universe}/tent-in-the-woods.jpg.txt (100%) rename content/2_notes/{20201210_exploring-the-universe => 20231124_exploring-the-universe}/universe.jpg (100%) rename content/2_notes/{20201210_exploring-the-universe => 20231124_exploring-the-universe}/universe.jpg.txt (100%) create mode 100644 kirby/config/api/models/License.php create mode 100644 kirby/config/api/routes/kql.php create mode 100644 kirby/config/areas/account/drawers.php create mode 100644 kirby/config/areas/fields/dialogs.php create mode 100644 kirby/config/areas/fields/drawers.php create mode 100644 kirby/config/areas/lab.php create mode 100644 kirby/config/areas/lab/drawers.php create mode 100644 kirby/config/areas/lab/views.php create mode 100644 kirby/config/areas/search.php create mode 100644 kirby/config/areas/search/views.php create mode 100644 kirby/config/areas/site/drawers.php create mode 100644 kirby/config/areas/site/requests.php create mode 100644 kirby/config/areas/users/drawers.php delete mode 100644 kirby/config/blueprints/blocks/code.yml delete mode 100644 kirby/config/blueprints/blocks/heading.yml delete mode 100644 kirby/config/blueprints/blocks/image.yml delete mode 100644 kirby/config/blueprints/blocks/quote.yml delete mode 100644 kirby/config/blueprints/blocks/table.yml delete mode 100644 kirby/config/blueprints/blocks/text.yml delete mode 100644 kirby/config/blueprints/blocks/video.yml delete mode 100644 kirby/config/blueprints/files/default.yml delete mode 100644 kirby/config/blueprints/pages/default.yml delete mode 100644 kirby/config/blueprints/site.yml create mode 100644 kirby/config/fields/color.php create mode 100644 kirby/config/fields/link.php create mode 100644 kirby/package-lock.json delete mode 100644 kirby/panel/dist/css/style.css create mode 100644 kirby/panel/dist/css/style.min.css create mode 100644 kirby/panel/dist/js/Docs.min.js create mode 100644 kirby/panel/dist/js/DocsView.min.js create mode 100644 kirby/panel/dist/js/Highlight.min.js create mode 100644 kirby/panel/dist/js/IndexView.min.js create mode 100644 kirby/panel/dist/js/PlaygroundView.min.js create mode 100644 kirby/panel/dist/js/container-query-polyfill.modern.min.js delete mode 100644 kirby/panel/dist/js/index.js create mode 100644 kirby/panel/dist/js/index.min.js delete mode 100644 kirby/panel/dist/js/vendor.js create mode 100644 kirby/panel/dist/js/vendor.min.js delete mode 100644 kirby/panel/dist/js/vue.js create mode 100644 kirby/panel/dist/js/vue.min.js rename kirby/panel/dist/js/{vuedraggable.js => vuedraggable.min.js} (100%) create mode 100644 kirby/src/Cms/Auth/TotpChallenge.php create mode 100644 kirby/src/Cms/LanguageVariable.php create mode 100644 kirby/src/Cms/License.php create mode 100644 kirby/src/Cms/LicenseStatus.php create mode 100644 kirby/src/Cms/LicenseType.php create mode 100644 kirby/src/Cms/PluginAsset.php rename kirby/src/{Cms => Content}/Content.php (72%) create mode 100644 kirby/src/Content/ContentStorage.php create mode 100644 kirby/src/Content/ContentStorageHandler.php rename kirby/src/{Cms => Content}/ContentTranslation.php (56%) rename kirby/src/{Cms => Content}/Field.php (67%) create mode 100644 kirby/src/Content/PlainTextContentStorageHandler.php create mode 100644 kirby/src/Exception/AuthException.php delete mode 100644 kirby/src/Form/Options.php delete mode 100644 kirby/src/Form/OptionsApi.php delete mode 100644 kirby/src/Form/OptionsQuery.php create mode 100644 kirby/src/Image/Focus.php create mode 100644 kirby/src/Image/QrCode.php create mode 100644 kirby/src/Panel/Assets.php create mode 100644 kirby/src/Panel/ChangesDialog.php create mode 100644 kirby/src/Panel/Drawer.php create mode 100644 kirby/src/Panel/Lab/Category.php create mode 100644 kirby/src/Panel/Lab/Docs.php create mode 100644 kirby/src/Panel/Lab/Example.php create mode 100644 kirby/src/Panel/Lab/Snippet.php create mode 100644 kirby/src/Panel/Lab/Template.php create mode 100644 kirby/src/Panel/Menu.php create mode 100644 kirby/src/Panel/PageCreateDialog.php create mode 100644 kirby/src/Panel/Request.php create mode 100644 kirby/src/Panel/UserTotpDisableDialog.php create mode 100644 kirby/src/Panel/UserTotpEnableDialog.php create mode 100644 kirby/src/Toolkit/LazyValue.php delete mode 100644 kirby/src/Toolkit/Query.php create mode 100644 kirby/src/Toolkit/Totp.php create mode 100644 kirby/vendor/christian-riesen/base32/LICENSE create mode 100644 kirby/vendor/christian-riesen/base32/src/Base32.php create mode 100644 kirby/vendor/christian-riesen/base32/src/Base32Hex.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-as.php create mode 100644 kirby/vendor/phpmailer/phpmailer/language/phpmailer.lang-bn.php diff --git a/.gitignore b/.gitignore index e371e8b..224510b 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,4 @@ Icon # --------------- /site/config/.license +/site/config/config.starterkit.test.php diff --git a/content/2_notes/20180905_through-the-desert/note.txt b/content/2_notes/20180905_through-the-desert/note.txt deleted file mode 100644 index 569bd77..0000000 --- a/content/2_notes/20180905_through-the-desert/note.txt +++ /dev/null @@ -1,29 +0,0 @@ -Title: Through the desert - ----- - -Subheading: - ----- - -Cover: - ----- - -Text: [{"content":{"text":"

Far far away, behind the word mountains, far from the countries Vokalia and Consonantia, there live the blind texts. Separated they live in Bookmarksgrove right at the coast of the Semantics, a large language ocean. A small river named Duden flows by their place and supplies it with the necessary regelialia. It is a paradisematic country, in which roasted parts of sentences fly into your mouth. Even the all-powerful Pointing has no control about the blind texts it is an almost unorthographic life One day however a small line of blind text by the name of Lorem Ipsum decided to leave for the far World of Grammar.<\/p>

The Big Oxmox advised her not to do so, because there were thousands of bad Commas, wild Question Marks and devious Semikoli, but the Little Blind Text didn\u2019t listen. She packed her seven versalia, put her initial into the belt and made herself on the way. When she reached the first hills of the Italic Mountains, she had a last view back on the skyline of her hometown Bookmarksgrove, the headline of Alphabet Village and the subline of her own road, the Line Lane. Pityful a rethoric question ran over her cheek, then she continued her way. On her way she met a copy.<\/p>

The copy warned the Little Blind Text, that where it came from it would have been rewritten a thousand times and everything that was left from its origin would be the word \"and\" and the Little Blind Text should turn around and return to its own, safe country. But nothing the copy said could convince her and so it didn\u2019t take long until a few insidious Copy Writers ambushed her, made her drunk with Longe and Parole and dragged her into their agency, where they abused her for their projects again and again. And if she hasn\u2019t been rewritten, then they are still using her. Far far away, behind the word mountains, far from the countries Vokalia and Consonantia, there live the blind texts. Separated they live in Bookmarksgrove right at the coast of the Semantics, a large language ocean.<\/p>

A small river named Duden flows by their place and supplies it with the necessary regelialia. It is a paradisematic country, in which roasted parts of sentences fly into your mouth. Even the all-powerful Pointing has no control about the blind texts it is an almost unorthographic life One day however a small line of blind text by the name of Lorem Ipsum decided to leave for the far World of Grammar. The Big Oxmox advised her not to do so, because there were thousands of bad Commas, wild Question Marks and devious Semikoli, but the Little Blind Text didn\u2019t listen. She packed her seven versalia, put her initial into the belt and made herself on the way. When she reached the first hills of the Italic Mountains, she had a last view back on the skyline of her hometown Bookmarksgrove, the headline of Alphabet Village and the subline of her own road, the Line Lane. Pityful a rethoric question ran over her cheek, then she continued her way. On her way she met a copy. The copy warned the Little Blind Text, that where it came from it would have been rewritten a thousand times and everything that was left from its origin would be the word \"and\" and the Little Blind Text should turn around and return to its own, safe country. But nothing the copy said could convince her and so it didn\u2019t take long until a few insidious Copy Writers ambushed her, made her drunk<\/p>"},"id":"b03f40c3-1dc5-4a89-8874-24f616f86e51","isHidden":false,"type":"text"}] - ----- - -Date: 2018-09-05 - ----- - -Author: - ----- - -Tags: sahara, desert, africa - ----- - -Uuid: nFku43Ki3pyS9NyM \ No newline at end of file diff --git a/content/2_notes/20181005_chasing-waterfalls/note.txt b/content/2_notes/20181005_chasing-waterfalls/note.txt deleted file mode 100644 index 8efab70..0000000 --- a/content/2_notes/20181005_chasing-waterfalls/note.txt +++ /dev/null @@ -1,29 +0,0 @@ -Title: Chasing waterfalls - ----- - -Subheading: - ----- - -Cover: - ----- - -Text: [{"content":{"text":"

Far far away, behind the word mountains, far from the countries Vokalia and Consonantia, there live the blind texts. Separated they live in Bookmarksgrove right at the coast of the Semantics, a large language ocean<\/strong>. A small river named Duden flows by their place and supplies it with the necessary regelialia. It is a paradisematic country, in which roasted parts of sentences fly into your mouth. Even the all-powerful Pointing has no control about the blind texts it is an almost unorthographic life One day however a small line of blind text by the name of Lorem Ipsum decided to leave for the far World of Grammar.<\/p>

The Big Oxmox advised her not to do so, because there were thousands of bad Commas, wild Question Marks and devious Semikoli, but the Little Blind Text didn\u2019t listen. She packed her seven versalia, put her initial into the belt and made herself on the way. When she reached the first hills of the Italic Mountains, she had a last view back on the skyline of her hometown Bookmarksgrove, the headline of Alphabet Village and the subline of her own road, the Line Lane. Pityful a rethoric question ran over her cheek, then she continued her way. On her way she met a copy.<\/p>

The copy warned the Little Blind Text, that where it came from it would have been rewritten a thousand times and everything that was left from its origin would be the word \"and\" and the Little Blind Text should turn around and return to its own, safe country. But nothing the copy said could convince her and so it didn\u2019t take long until a few insidious Copy Writers ambushed her, made her drunk with Longe and Parole and dragged her into their agency, where they abused her for their projects again and again. And if she hasn\u2019t been rewritten, then they are still using her. Far far away, behind the word mountains, far from the countries Vokalia and Consonantia, there live the blind texts. Separated they live in Bookmarksgrove right at the coast of the Semantics, a large language ocean.<\/p>

A small river named Duden flows by their place and supplies it with the necessary regelialia. It is a paradisematic country, in which roasted parts of sentences fly into your mouth. Even the all-powerful Pointing has no control about the blind texts it is an almost unorthographic life One day however a small line of blind text by the name of Lorem Ipsum decided to leave for the far World of Grammar. The Big Oxmox advised her not to do so, because there were thousands of bad Commas, wild Question Marks and devious Semikoli, but the Little Blind Text didn\u2019t listen. She packed her seven versalia, put her initial into the belt and made herself on the way. When she reached the first hills of the Italic Mountains, she had a last view back on the skyline of her hometown Bookmarksgrove, the headline of Alphabet Village and the subline of her own road, the Line Lane. Pityful a rethoric question ran over her cheek, then she continued her way. On her way she met a copy. The copy warned the Little Blind Text, that where it came from it would have been rewritten a thousand times and everything that was left from its origin would be the word \"and\" and the Little Blind Text should turn around and return to its own, safe country. But nothing the copy said could convince her and so it didn\u2019t take long until a few insidious Copy Writers ambushed her, made her drunk<\/p>"},"id":"fff3f248-9a0f-44ec-af0c-a59126be24a4","isHidden":false,"type":"text"}] - ----- - -Date: 2018-10-05 - ----- - -Author: - ----- - -Tags: water, nature, waterfalls, landscape - ----- - -Uuid: T18qI7ZUqldFRwhl \ No newline at end of file diff --git a/content/2_notes/20190625_a-night-in-the-forest/note.txt b/content/2_notes/20190625_a-night-in-the-forest/note.txt deleted file mode 100644 index 1625d64..0000000 --- a/content/2_notes/20190625_a-night-in-the-forest/note.txt +++ /dev/null @@ -1,29 +0,0 @@ -Title: A night in the forest - ----- - -Text: [{"content":{"text":"

Far far away, behind the word mountains, far from the countries Vokalia and Consonantia, there live the blind texts. Separated they live in Bookmarksgrove right at the coast of the Semantics, a large language ocean. A small river named Duden flows by their place and supplies it with the necessary regelialia. It is a paradisematic country, in which roasted parts of sentences fly into your mouth. Even the all-powerful Pointing has no control about the blind texts it is an almost unorthographic life One day however a small line of blind text by the name of Lorem Ipsum decided to leave for the far World of Grammar.<\/p>

The Big Oxmox advised her not to do so, because there were thousands of bad Commas, wild Question Marks and devious Semikoli, but the Little Blind Text didn\u2019t listen. She packed her seven versalia, put her initial into the belt and made herself on the way. When she reached the first hills of the Italic Mountains, she had a last view back on the skyline of her hometown Bookmarksgrove, the headline of Alphabet Village and the subline of her own road, the Line Lane. Pityful a rethoric question ran over her cheek, then she continued her way. On her way she met a copy.<\/p>

The copy warned the Little Blind Text, that where it came from it would have been rewritten a thousand times and everything that was left from its origin would be the word \"and\" and the Little Blind Text should turn around and return to its own, safe country. But nothing the copy said could convince her and so it didn\u2019t take long until a few insidious Copy Writers ambushed her, made her drunk with Longe and Parole and dragged her into their agency, where they abused her for their projects again and again. And if she hasn\u2019t been rewritten, then they are still using her. Far far away, behind the word mountains, far from the countries Vokalia and Consonantia, there live the blind texts. Separated they live in Bookmarksgrove right at the coast of the Semantics, a large language ocean.<\/p>

A small river named Duden flows by their place and supplies it with the necessary regelialia. It is a paradisematic country, in which roasted parts of sentences fly into your mouth. Even the all-powerful Pointing has no control about the blind texts it is an almost unorthographic life One day however a small line of blind text by the name of Lorem Ipsum decided to leave for the far World of Grammar. The Big Oxmox advised her not to do so, because there were thousands of bad Commas, wild Question Marks and devious Semikoli, but the Little Blind Text didn\u2019t listen. She packed her seven versalia, put her initial into the belt and made herself on the way. When she reached the first hills of the Italic Mountains, she had a last view back on the skyline of her hometown Bookmarksgrove, the headline of Alphabet Village and the subline of her own road, the Line Lane. Pityful a rethoric question ran over her cheek, then she continued her way. On her way she met a copy. The copy warned the Little Blind Text, that where it came from it would have been rewritten a thousand times and everything that was left from its origin would be the word \"and\" and the Little Blind Text should turn around and return to its own, safe country. But nothing the copy said could convince her and so it didn\u2019t take long until a few insidious Copy Writers ambushed her, made her drunk<\/p>"},"id":"_nEOMzN6tX","isHidden":false,"type":"text"}] - ----- - -Date: 2019-06-25 00:00:00 - ----- - -Author: - ----- - -Tags: forest, trees - ----- - -Subheading: - ----- - -Cover: - ----- - -Uuid: jTP2YTVPzlmTTw6y \ No newline at end of file diff --git a/content/2_notes/20200421_across-the-ocean/note.txt b/content/2_notes/20200421_across-the-ocean/note.txt deleted file mode 100644 index a6b7067..0000000 --- a/content/2_notes/20200421_across-the-ocean/note.txt +++ /dev/null @@ -1,29 +0,0 @@ -Title: Across the ocean - ----- - -Text: [{"content":{"text":"

Far far away, behind the word mountains, far from the countries Vokalia and Consonantia, there live the blind texts. Separated they live in Bookmarksgrove right at the coast of the Semantics, a large language ocean. A small river named Duden flows by their place and supplies it with the necessary regelialia. It is a paradisematic country, in which roasted parts of sentences fly into your mouth. Even the all-powerful Pointing has no control about the blind texts it is an almost unorthographic life One day however a small line of blind text by the name of Lorem Ipsum decided to leave for the far World of Grammar.<\/p>

The Big Oxmox advised her not to do so, because there were thousands of bad Commas, wild Question Marks and devious Semikoli, but the Little Blind Text didn\u2019t listen. She packed her seven versalia, put her initial into the belt and made herself on the way. When she reached the first hills of the Italic Mountains, she had a last view back on the skyline of her hometown Bookmarksgrove, the headline of Alphabet Village and the subline of her own road, the Line Lane. Pityful a rethoric question ran over her cheek, then she continued her way. On her way she met a copy.<\/p>

The copy warned the Little Blind Text, that where it came from it would have been rewritten a thousand times and everything that was left from its origin would be the word \"and\" and the Little Blind Text should turn around and return to its own, safe country. But nothing the copy said could convince her and so it didn\u2019t take long until a few insidious Copy Writers ambushed her, made her drunk with Longe and Parole and dragged her into their agency, where they abused her for their projects again and again. And if she hasn\u2019t been rewritten, then they are still using her. Far far away, behind the word mountains, far from the countries Vokalia and Consonantia, there live the blind texts. Separated they live in Bookmarksgrove right at the coast of the Semantics, a large language ocean.<\/p>

A small river named Duden flows by their place and supplies it with the necessary regelialia. It is a paradisematic country, in which roasted parts of sentences fly into your mouth. Even the all-powerful Pointing has no control about the blind texts it is an almost unorthographic life One day however a small line of blind text by the name of Lorem Ipsum decided to leave for the far World of Grammar. The Big Oxmox advised her not to do so, because there were thousands of bad Commas, wild Question Marks and devious Semikoli, but the Little Blind Text didn\u2019t listen. She packed her seven versalia, put her initial into the belt and made herself on the way. When she reached the first hills of the Italic Mountains, she had a last view back on the skyline of her hometown Bookmarksgrove, the headline of Alphabet Village and the subline of her own road, the Line Lane. Pityful a rethoric question ran over her cheek, then she continued her way. On her way she met a copy. The copy warned the Little Blind Text, that where it came from it would have been rewritten a thousand times and everything that was left from its origin would be the word \"and\" and the Little Blind Text should turn around and return to its own, safe country. But nothing the copy said could convince her and so it didn\u2019t take long until a few insidious Copy Writers ambushed her, made her drunk<\/p>"},"id":"a647447a-4226-4fe8-824e-85663f7a80f2","isHidden":false,"type":"text"}] - ----- - -Date: 2020-04-21 00:00:00 - ----- - -Author: - ----- - -Tags: ocean, pacific - ----- - -Subheading: - ----- - -Cover: - ----- - -Uuid: QJJVlR8YxRqritd9 \ No newline at end of file diff --git a/content/2_notes/20180905_through-the-desert/desert.jpg b/content/2_notes/20200905_through-the-desert/desert.jpg similarity index 100% rename from content/2_notes/20180905_through-the-desert/desert.jpg rename to content/2_notes/20200905_through-the-desert/desert.jpg diff --git a/content/2_notes/20180905_through-the-desert/desert.jpg.txt b/content/2_notes/20200905_through-the-desert/desert.jpg.txt similarity index 100% rename from content/2_notes/20180905_through-the-desert/desert.jpg.txt rename to content/2_notes/20200905_through-the-desert/desert.jpg.txt diff --git a/content/2_notes/20200905_through-the-desert/note.txt b/content/2_notes/20200905_through-the-desert/note.txt new file mode 100644 index 0000000..be565af --- /dev/null +++ b/content/2_notes/20200905_through-the-desert/note.txt @@ -0,0 +1,29 @@ +Title: Through the desert + +---- + +Text: [{"content":{"text":"

Far far away, behind the word mountains, far from the countries Vokalia and Consonantia, there live the blind texts. Separated they live in Bookmarksgrove right at the coast of the Semantics, a large language ocean. A small river named Duden flows by their place and supplies it with the necessary regelialia. It is a paradisematic country, in which roasted parts of sentences fly into your mouth. Even the all-powerful Pointing has no control about the blind texts it is an almost unorthographic life One day however a small line of blind text by the name of Lorem Ipsum decided to leave for the far World of Grammar.

The Big Oxmox advised her not to do so, because there were thousands of bad Commas, wild Question Marks and devious Semikoli, but the Little Blind Text didn’t listen. She packed her seven versalia, put her initial into the belt and made herself on the way. When she reached the first hills of the Italic Mountains, she had a last view back on the skyline of her hometown Bookmarksgrove, the headline of Alphabet Village and the subline of her own road, the Line Lane. Pityful a rethoric question ran over her cheek, then she continued her way. On her way she met a copy.

The copy warned the Little Blind Text, that where it came from it would have been rewritten a thousand times and everything that was left from its origin would be the word \"and\" and the Little Blind Text should turn around and return to its own, safe country. But nothing the copy said could convince her and so it didn’t take long until a few insidious Copy Writers ambushed her, made her drunk with Longe and Parole and dragged her into their agency, where they abused her for their projects again and again. And if she hasn’t been rewritten, then they are still using her. Far far away, behind the word mountains, far from the countries Vokalia and Consonantia, there live the blind texts. Separated they live in Bookmarksgrove right at the coast of the Semantics, a large language ocean.

A small river named Duden flows by their place and supplies it with the necessary regelialia. It is a paradisematic country, in which roasted parts of sentences fly into your mouth. Even the all-powerful Pointing has no control about the blind texts it is an almost unorthographic life One day however a small line of blind text by the name of Lorem Ipsum decided to leave for the far World of Grammar. The Big Oxmox advised her not to do so, because there were thousands of bad Commas, wild Question Marks and devious Semikoli, but the Little Blind Text didn’t listen. She packed her seven versalia, put her initial into the belt and made herself on the way. When she reached the first hills of the Italic Mountains, she had a last view back on the skyline of her hometown Bookmarksgrove, the headline of Alphabet Village and the subline of her own road, the Line Lane. Pityful a rethoric question ran over her cheek, then she continued her way. On her way she met a copy. The copy warned the Little Blind Text, that where it came from it would have been rewritten a thousand times and everything that was left from its origin would be the word \"and\" and the Little Blind Text should turn around and return to its own, safe country. But nothing the copy said could convince her and so it didn’t take long until a few insidious Copy Writers ambushed her, made her drunk

"},"id":"b03f40c3-1dc5-4a89-8874-24f616f86e51","isHidden":false,"type":"text"}] + +---- + +Cover: + +---- + +Date: 2020-09-05 00:00:00 + +---- + +Author: + +---- + +Tags: sahara, desert, africa + +---- + +Subheading: + +---- + +Uuid: nFku43Ki3pyS9NyM \ No newline at end of file diff --git a/content/2_notes/20200913_himalaya-and-back/note.txt b/content/2_notes/20200913_himalaya-and-back/note.txt deleted file mode 100644 index 769207a..0000000 --- a/content/2_notes/20200913_himalaya-and-back/note.txt +++ /dev/null @@ -1,29 +0,0 @@ -Title: Himalaya and back - ----- - -Text: [{"content":{"text":"Far far away, behind the word mountains, far from the countries Vokalia and Consonantia, there live the blind texts. Separated they live in Bookmarksgrove right at the coast of the Semantics, a large language ocean. A small river named Duden flows by their place and supplies it with the necessary regelialia. It is a paradisematic country, in which roasted parts of sentences fly into your mouth. Even the all-powerful Pointing has no control about the blind texts it is an almost unorthographic life One day however a small line of blind text by the name of Lorem Ipsum decided to leave for the far World of Grammar."},"id":"ad72d143-a58f-4c7f-af4e-aa2f5d40d55a","isHidden":false,"type":"text"},{"content":{"text":"The Big Oxmox advised her not to do so, because there were thousands of bad Commas, wild Question Marks and devious Semikoli, but the Little Blind Text didn\u2019t listen. She packed her seven versalia, put her initial into the belt and made herself on the way. When she reached the first hills of the Italic Mountains, she had a last view back on the skyline of her hometown Bookmarksgrove, the headline of Alphabet Village and the subline of her own road, the Line Lane. Pityful a rethoric question ran over her cheek, then she continued her way. On her way she met a copy."},"id":"9fe2ae56-5578-449b-9a12-87e9592a4762","isHidden":false,"type":"text"},{"content":{"text":"The copy warned the Little Blind Text, that where it came from it would have been rewritten a thousand times and everything that was left from its origin would be the word \"and\" and the Little Blind Text should turn around and return to its own, safe country. But nothing the copy said could convince her and so it didn\u2019t take long until a few insidious Copy Writers ambushed her, made her drunk with Longe and Parole and dragged her into their agency, where they abused her for their projects again and again. And if she hasn\u2019t been rewritten, then they are still using her. Far far away, behind the word mountains, far from the countries Vokalia and Consonantia, there live the blind texts. Separated they live in Bookmarksgrove right at the coast of the Semantics, a large language ocean."},"id":"76378830-650c-4bf6-b37e-b08c020848a4","isHidden":false,"type":"text"},{"content":{"text":"A small river named Duden flows by their place and supplies it with the necessary regelialia. It is a paradisematic country, in which roasted parts of sentences fly into your mouth. Even the all-powerful Pointing has no control about the blind texts it is an almost unorthographic life One day however a small line of blind text by the name of Lorem Ipsum decided to leave for the far World of Grammar. The Big Oxmox advised her not to do so, because there were thousands of bad Commas, wild Question Marks and devious Semikoli, but the Little Blind Text didn\u2019t listen. She packed her seven versalia, put her initial into the belt and made herself on the way. When she reached the first hills of the Italic Mountains, she had a last view back on the skyline of her hometown Bookmarksgrove, the headline of Alphabet Village and the subline of her own road, the Line Lane. Pityful a rethoric question ran over her cheek, then she continued her way. On her way she met a copy. The copy warned the Little Blind Text, that where it came from it would have been rewritten a thousand times and everything that was left from its origin would be the word \"and\" and the Little Blind Text should turn around and return to its own, safe country. But nothing the copy said could convince her and so it didn\u2019t take long until a few insidious Copy Writers ambushed her, made her drunk"},"id":"e413187a-c2cd-42a7-908e-11c153af6907","isHidden":false,"type":"text"}] - ----- - -Date: 2020-09-13 00:00:00 - ----- - -Author: - ----- - -Tags: environment, nepal, mountains - ----- - -Subheading: - ----- - -Cover: - ----- - -Uuid: nRb6jA7zJf4Gv8Ti \ No newline at end of file diff --git a/content/2_notes/20201210_exploring-the-universe/note.txt b/content/2_notes/20201210_exploring-the-universe/note.txt deleted file mode 100644 index 5f55ba1..0000000 --- a/content/2_notes/20201210_exploring-the-universe/note.txt +++ /dev/null @@ -1,29 +0,0 @@ -Title: Exploring the universe - ----- - -Text: [{"content":{"text":"

Far far away, behind the word mountains, far from the countries Vokalia and Consonantia, there live the blind texts. Separated they live in Bookmarksgrove right at the coast of the Semantics, a large language ocean. A small river named Duden flows by their<\/strong> place and supplies it with the necessary regelialia. It is a paradisematic country, in which roasted parts of sentences fly into your mouth. Even the all-powerful Pointing has no control about the blind texts it is an almost unorthographic life One day however a small line of blind text by the name of Lorem Ipsum decided to leave for the far World of Grammar.<\/p>"},"id":"_MtYF6qOmq","isHidden":false,"type":"text"},{"content":{"location":"kirby","image":["file:\/\/xkPVM7MdXoEn4do6"],"src":"","alt":"Staring at stars","caption":"Staring at stars","link":"","ratio":"16\/9","crop":"true"},"id":"_200yrtaus","isHidden":false,"type":"image"},{"content":{"text":"

The Big Oxmox<\/strong> advised her not to do so, because there were thousands of bad Commas, wild Question Marks and devious Semikoli, but the Little Blind Text didn\u2019t listen. She packed her seven versalia, put her initial into the belt and made herself on the way. When she reached the first hills of the Italic Mountains<\/a>, she had a last view back on the skyline of her hometown Bookmarksgrove, the headline of Alphabet Village and the subline of her own road, the Line Lane. Pityful a rethoric question ran over her cheek, then she continued her way. On her way she met a copy.<\/p>"},"id":"_J5cUP6Q8A","isHidden":false,"type":"text"},{"content":{"text":"Time flies like an arrow; fruit flies like a banana","citation":"Anthony Oettinger<\/a>"},"id":"_sxaa7bs0y","isHidden":false,"type":"quote"},{"content":{"level":"h2","text":"Let's put a heading here"},"id":"_mg1sjf8a6","isHidden":false,"type":"heading"},{"content":{"text":"

But nothing the copy<\/u> said could convince her and so it didn\u2019t take long until a few insidious Copy Writers ambushed her, made her drunk with Longe and Parole and dragged her into their agency, where they abused her for their projects<\/em> again and again. And if she hasn\u2019t been rewritten, then they are still using her. Far far away, behind the word mountains, far from the countries Vokalia and Consonantia, there live the blind texts. Separated they live in Bookmarksgrove right at the coast of the Semantics, a large language ocean.<\/p>"},"id":"_1gcmahr5x","isHidden":false,"type":"text"},{"content":{"url":"https:\/\/vimeo.com\/54400762","caption":"I found this when searching for stars on Vimeo"},"id":"_d5m9sxprv","isHidden":false,"type":"video"},{"content":{"text":"## This is created by some good old markdown\n\nYou can mix it with the other blocks to get even more **flexibility**. Markdown can be mixed<\/strong> with HTML too, which is nice. And of course (link: https:\/\/getkirby.com text: Kirbytags) are cool too."},"id":"_mxb6p3yof","isHidden":false,"type":"markdown"},{"content":{"text":"

")},{text:i[1].replace(/^(
  • <\/p><\/li>)/,"

      ")}])}}},(function(){var t=this;return(0,t._self._c)("k-input",{ref:"input",staticClass:"k-block-type-list-input",attrs:{keys:t.keys,marks:t.marks,value:t.content.text,type:"list"},on:{input:function(e){return t.update({text:e})}}})}),[],!1,null,null,null,null).exports,pi=Object.freeze(Object.defineProperty({__proto__:null,default:di},Symbol.toStringTag,{value:"Module"}));const hi=ut({computed:{placeholder(){return this.field("text",{}).placeholder}},methods:{focus(){this.$refs.input.focus()}}},(function(){var t=this;return(0,t._self._c)("k-input",{ref:"input",staticClass:"k-block-type-markdown-input",attrs:{buttons:!1,placeholder:t.placeholder,spellcheck:!1,value:t.content.text,font:"monospace",type:"textarea"},on:{input:function(e){return t.update({text:e})}}})}),[],!1,null,null,null,null).exports,mi=Object.freeze(Object.defineProperty({__proto__:null,default:hi},Symbol.toStringTag,{value:"Module"}));const fi=ut({computed:{citationField(){return this.field("citation",{})},textField(){return this.field("text",{})}},methods:{focus(){this.$refs.text.focus()}}},(function(){var t=this,e=t._self._c;return e("div",{staticClass:"k-block-type-quote-editor"},[e("k-writer",{ref:"text",staticClass:"k-block-type-quote-text",attrs:{inline:t.textField.inline??!1,marks:t.textField.marks,placeholder:t.textField.placeholder,value:t.content.text},on:{input:function(e){return t.update({text:e})}}}),e("k-writer",{ref:"citation",staticClass:"k-block-type-quote-citation",attrs:{inline:t.citationField.inline??!0,marks:t.citationField.marks,placeholder:t.citationField.placeholder,value:t.content.citation},on:{input:function(e){return t.update({citation:e})}}})],1)}),[],!1,null,null,null,null).exports,gi=Object.freeze(Object.defineProperty({__proto__:null,default:fi},Symbol.toStringTag,{value:"Module"}));const ki=ut({inheritAttrs:!1,computed:{columns(){return this.table.columns??this.fields},fields(){return this.table.fields??{}},rows(){return this.content.rows??[]},table(){let t=null;for(const e of Object.values(this.fieldset.tabs??{}))e.fields.rows&&(t=e.fields.rows);return t??{}}}},(function(){var t=this;return(0,t._self._c)("k-table",{staticClass:"k-block-type-table-preview",attrs:{columns:t.columns,empty:t.$t("field.structure.empty"),rows:t.rows},nativeOn:{dblclick:function(e){return t.open.apply(null,arguments)}}})}),[],!1,null,null,null,null).exports,bi=Object.freeze(Object.defineProperty({__proto__:null,default:ki},Symbol.toStringTag,{value:"Module"}));const vi=ut({computed:{component(){const t="k-"+this.textField.type+"-input";return this.$helper.isComponent(t)?t:"k-writer-input"},isSplitable(){return this.content.text.length>0&&!1===this.input().isCursorAtStart&&!1===this.input().isCursorAtEnd},keys(){const t={"Mod-Enter":this.split};return!0===this.textField.inline&&(t.Enter=this.split),t},textField(){return this.field("text",{})}},methods:{focus(){this.$refs.input.focus()},input(){return this.$refs.input.$refs.input},merge(t){this.update({text:t.map((t=>t.content.text)).join(this.textField.inline?" ":"")})},split(){var t,e;const i=null==(e=(t=this.input()).getSplitContent)?void 0:e.call(t);i&&("writer"===this.textField.type&&(i[0]=i[0].replace(/(

      <\/p>)$/,""),i[1]=i[1].replace(/^(

      <\/p>)/,"")),this.$emit("split",i.map((t=>({text:t})))))}}},(function(){var t=this;return(0,t._self._c)(t.component,t._b({ref:"input",tag:"component",staticClass:"k-block-type-text-input",attrs:{keys:t.keys,value:t.content.text},on:{input:function(e){return t.update({text:e})}}},"component",t.textField,!1))}),[],!1,null,null,null,null).exports,yi=Object.freeze(Object.defineProperty({__proto__:null,default:vi},Symbol.toStringTag,{value:"Module"}));const $i=ut({computed:{captionMarks(){return this.field("caption",{marks:!0}).marks},video(){return this.$helper.embed.video(this.content.url??"",!0)}}},(function(){var t=this,e=t._self._c;return e("k-block-figure",{attrs:{caption:t.content.caption,"caption-marks":t.captionMarks,"empty-text":t.$t("field.blocks.video.placeholder")+" …","is-empty":!t.video,"empty-icon":"video"},on:{open:t.open,update:t.update}},[e("k-frame",{attrs:{ratio:"16/9"}},[t.video?e("iframe",{attrs:{src:t.video,referrerpolicy:"strict-origin-when-cross-origin"}}):t._e()])],1)}),[],!1,null,null,null,null).exports,wi=Object.freeze(Object.defineProperty({__proto__:null,default:$i},Symbol.toStringTag,{value:"Module"}));const xi=ut({inheritAttrs:!1,props:{attrs:{default:()=>({}),type:[Array,Object]},content:{default:()=>({}),type:[Array,Object]},endpoints:{default:()=>({}),type:[Array,Object]},fieldset:{default:()=>({}),type:Object},id:String,isBatched:Boolean,isFull:Boolean,isHidden:Boolean,isLastSelected:Boolean,isMergable:Boolean,isSelected:Boolean,name:String,next:Object,prev:Object,type:String},emits:["append","chooseToAppend","chooseToConvert","chooseToPrepend","close","copy","duplicate","focus","hide","merge","open","paste","prepend","remove","selectDown","selectUp","show","sortDown","sortUp","split","submit","update"],computed:{className(){let t=["k-block-type-"+this.type];return this.fieldset.preview!==this.type&&t.push("k-block-type-"+this.fieldset.preview),!1===this.wysiwyg&&t.push("k-block-type-default"),t},containerType(){const t=this.fieldset.preview;return!1!==t&&(t&&this.$helper.isComponent("k-block-type-"+t)?t:!!this.$helper.isComponent("k-block-type-"+this.type)&&this.type)},customComponent(){return this.wysiwyg?this.wysiwygComponent:"k-block-type-default"},isEditable(){return!1!==this.fieldset.editable},listeners(){return{append:t=>this.$emit("append",t),chooseToAppend:t=>this.$emit("chooseToAppend",t),chooseToConvert:t=>this.$emit("chooseToConvert",t),chooseToPrepend:t=>this.$emit("chooseToPrepend",t),close:()=>this.$emit("close"),copy:()=>this.$emit("copy"),duplicate:()=>this.$emit("duplicate"),focus:()=>this.$emit("focus"),hide:()=>this.$emit("hide"),merge:()=>this.$emit("merge"),open:t=>this.open(t),paste:()=>this.$emit("paste"),prepend:t=>this.$emit("prepend",t),remove:()=>this.remove(),removeSelected:()=>this.$emit("removeSelected"),show:()=>this.$emit("show"),sortDown:()=>this.$emit("sortDown"),sortUp:()=>this.$emit("sortUp"),split:t=>this.$emit("split",t),update:t=>this.$emit("update",t)}},tabs(){const t=this.fieldset.tabs??{};for(const[e,i]of Object.entries(t))for(const[n]of Object.entries(i.fields??{}))t[e].fields[n].section=this.name,t[e].fields[n].endpoints={field:this.endpoints.field+"/fieldsets/"+this.type+"/fields/"+n,section:this.endpoints.section,model:this.endpoints.model};return t},wysiwyg(){return!1!==this.wysiwygComponent},wysiwygComponent(){return!!this.containerType&&"k-block-type-"+this.containerType}},methods:{backspace(t){if(t.target.matches("[contenteditable], input, textarea"))return!1;t.preventDefault(),this.remove()},close(){this.$panel.drawer.close(this.id)},focus(){var t,e;"function"==typeof(null==(t=this.$refs.editor)?void 0:t.focus)?this.$refs.editor.focus():null==(e=this.$refs.container)||e.focus()},goTo(t){var e;t&&(null==(e=t.$refs.container)||e.focus(),t.open(null,!0))},isSplitable(){var t;return!0!==this.isFull&&(!!this.$refs.editor&&((this.$refs.editor.isSplitable??!0)&&"function"==typeof(null==(t=this.$refs.editor)?void 0:t.split)))},onClose(){this.$emit("close"),this.focus()},onFocusIn(t){var e,i;(null==(i=null==(e=this.$refs.options)?void 0:e.$el)?void 0:i.contains(t.target))||this.$emit("focus",t)},onInput(t){this.$emit("update",t)},open(t,e=!1){this.isEditable&&!this.isBatched&&(this.$panel.drawer.open({component:"k-block-drawer",id:this.id,tab:t,on:{close:this.onClose,input:this.onInput,next:()=>this.goTo(this.next),prev:()=>this.goTo(this.prev),remove:this.remove,show:this.show,submit:this.submit},props:{hidden:this.isHidden,icon:this.fieldset.icon??"box",next:this.next,prev:this.prev,tabs:this.tabs,title:this.fieldset.name,value:this.content},replace:e}),this.$emit("open"))},remove(){if(this.isBatched)return this.$emit("removeSelected");this.$panel.dialog.open({component:"k-remove-dialog",props:{text:this.$t("field.blocks.delete.confirm")},on:{submit:()=>{this.$panel.dialog.close(),this.close(),this.$emit("remove",this.id)}}})},show(){this.$emit("show")},submit(){this.close(),this.$emit("submit")}}},(function(){var t=this,e=t._self._c;return e("div",{ref:"container",staticClass:"k-block-container",class:["k-block-container-fieldset-"+t.type,t.containerType?"k-block-container-type-"+t.containerType:""],attrs:{"data-batched":t.isBatched,"data-disabled":t.fieldset.disabled,"data-hidden":t.isHidden,"data-id":t.id,"data-last-selected":t.isLastSelected,"data-selected":t.isSelected,"data-translate":t.fieldset.translate,tabindex:"0"},on:{keydown:[function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"j",void 0,e.key,void 0)?null:e.ctrlKey?(e.preventDefault(),e.stopPropagation(),t.$emit("merge")):null},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"down",40,e.key,["Down","ArrowDown"])?null:e.ctrlKey&&e.altKey?(e.preventDefault(),e.stopPropagation(),t.$emit("selectDown")):null},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"up",38,e.key,["Up","ArrowUp"])?null:e.ctrlKey&&e.altKey?(e.preventDefault(),e.stopPropagation(),t.$emit("selectUp")):null},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"down",40,e.key,["Down","ArrowDown"])?null:e.ctrlKey&&e.shiftKey?(e.preventDefault(),e.stopPropagation(),t.$emit("sortDown")):null},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"up",38,e.key,["Up","ArrowUp"])?null:e.ctrlKey&&e.shiftKey?(e.preventDefault(),e.stopPropagation(),t.$emit("sortUp")):null},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"backspace",void 0,e.key,void 0)?null:e.ctrlKey?(e.stopPropagation(),t.backspace.apply(null,arguments)):null}],focus:function(e){return e.stopPropagation(),t.$emit("focus")},focusin:function(e){return e.stopPropagation(),t.onFocusIn.apply(null,arguments)}}},[e("div",{staticClass:"k-block",class:t.className},[e(t.customComponent,t._g(t._b({ref:"editor",tag:"component",attrs:{tabs:t.tabs}},"component",t.$props,!1),t.listeners))],1),e("k-block-options",t._g({ref:"options",attrs:{"is-batched":t.isBatched,"is-editable":t.isEditable,"is-full":t.isFull,"is-hidden":t.isHidden,"is-mergable":t.isMergable,"is-splitable":t.isSplitable()}},{...t.listeners,split:()=>t.$refs.editor.split(),open:()=>{"function"==typeof t.$refs.editor.open?t.$refs.editor.open():t.open()}}))],1)}),[],!1,null,null,null,null).exports;const _i=ut({inheritAttrs:!1,props:{autofocus:Boolean,disabled:Boolean,empty:String,endpoints:Object,fieldsets:Object,fieldsetGroups:Object,group:String,max:{type:Number,default:null},value:{type:Array,default:()=>[]}},data(){return{blocks:this.value??[],isEditing:!1,isMultiSelectKey:!1,selected:[]}},computed:{draggableOptions(){return{id:this._uid,handle:".k-sort-handle",list:this.blocks,move:this.move,delay:10,data:{fieldsets:this.fieldsets,isFull:this.isFull},options:{group:this.group}}},hasFieldsets(){return this.$helper.object.length(this.fieldsets)>0},isEmpty(){return 0===this.blocks.length},isFull(){return null!==this.max&&this.blocks.length>=this.max},isMergable(){if(this.selected.length<2)return!1;const t=this.selected.map((t=>this.find(t)));return!(new Set(t.map((t=>t.type))).size>1)&&"function"==typeof this.ref(t[0]).$refs.editor.merge}},watch:{value(){this.blocks=this.value}},created(){this.$events.on("blur",this.onBlur),this.$events.on("click",this.onClickGlobal),this.$events.on("copy",this.onCopy),this.$events.on("keydown",this.onKey),this.$events.on("keyup",this.onKey),this.$events.on("paste",this.onPaste)},destroyed(){this.$events.off("blur",this.onBlur),this.$events.off("click",this.onClickGlobal),this.$events.off("copy",this.onCopy),this.$events.off("keydown",this.onKey),this.$events.off("keyup",this.onKey),this.$events.off("paste",this.onPaste)},mounted(){!0===this.$props.autofocus&&setTimeout(this.focus,100)},methods:{async add(t="text",e){const i=await this.$api.get(this.endpoints.field+"/fieldsets/"+t);this.blocks.splice(e,0,i),this.save(),await this.$nextTick(),this.focusOrOpen(i)},choose(t){if(1===this.$helper.object.length(this.fieldsets))return this.add(Object.values(this.fieldsets)[0].type,t);this.$panel.dialog.open({component:"k-block-selector",props:{fieldsetGroups:this.fieldsetGroups,fieldsets:this.fieldsets},on:{submit:e=>{this.add(e,t),this.$panel.dialog.close()},paste:e=>{this.paste(e,t)}}})},chooseToConvert(t){this.$panel.dialog.open({component:"k-block-selector",props:{disabledFieldsets:[t.type],fieldsetGroups:this.fieldsetGroups,fieldsets:this.fieldsets,headline:this.$t("field.blocks.changeType")},on:{submit:e=>{this.convert(e,t),this.$panel.dialog.close()},paste:this.paste}})},copy(t){if(0===this.blocks.length)return!1;if(0===this.selected.length)return!1;let e=[];for(const i of this.blocks)this.selected.includes(i.id)&&e.push(i);if(0===e.length)return!1;this.$helper.clipboard.write(e,t),this.selected=e.map((t=>t.id)),this.$panel.notification.success({message:this.$t("copy.success",{count:e.length}),icon:"template"})},copyAll(){this.selectAll(),this.copy(),this.deselectAll()},async convert(t,e){var i;const n=this.findIndex(e.id);if(-1===n)return!1;const s=t=>{let e={};for(const i of Object.values((null==t?void 0:t.tabs)??{}))e={...e,...i.fields};return e},o=this.blocks[n],l=await this.$api.get(this.endpoints.field+"/fieldsets/"+t),r=this.fieldsets[o.type],a=this.fieldsets[t];if(!a)return!1;let u=l.content;const c=s(a),d=s(r);for(const[p,h]of Object.entries(c)){const t=d[p];(null==t?void 0:t.type)===h.type&&(null==(i=null==o?void 0:o.content)?void 0:i[p])&&(u[p]=o.content[p])}this.blocks[n]={...l,id:o.id,content:u},this.save()},deselect(t){const e=this.selected.findIndex((e=>e===t.id));-1!==e&&this.selected.splice(e,1)},deselectAll(){this.selected=[]},async duplicate(t,e){const i={...this.$helper.clone(t),id:this.$helper.uuid()};this.blocks.splice(e+1,0,i),this.save()},fieldset(t){return this.fieldsets[t.type]??{icon:"box",name:t.type,tabs:{content:{fields:{}}},type:t.type}},find(t){return this.blocks.find((e=>e.id===t))},findIndex(t){return this.blocks.findIndex((e=>e.id===t))},focus(t){const e=this.ref(t);this.selected=[(null==t?void 0:t.id)??this.blocks[0]],null==e||e.focus(),null==e||e.$el.scrollIntoView({block:"nearest"})},focusOrOpen(t){this.fieldsets[t.type].wysiwyg?this.focus(t):this.open(t)},hide(t){Vue.set(t,"isHidden",!0),this.save()},isInputEvent(){const t=document.querySelector(":focus");return null==t?void 0:t.matches("input, textarea, [contenteditable], .k-writer")},isLastSelected(t){const[e]=this.selected.slice(-1);return e&&t.id===e},isOnlyInstance:()=>1===document.querySelectorAll(".k-blocks").length,isSelected(t){return this.selected.includes(t.id)},async merge(){if(this.isMergable){const t=this.selected.map((t=>this.find(t)));this.ref(t[0]).$refs.editor.merge(t);for(const e of t.slice(1))this.remove(e);await this.$nextTick(),this.focus(t[0])}},move(t){if(t.from!==t.to){const e=t.draggedContext.element,i=t.relatedContext.component.componentData||t.relatedContext.component.$parent.componentData;if(!1===Object.keys(i.fieldsets).includes(e.type))return!1;if(!0===i.isFull)return!1}return!0},onBlur(){0===this.selected.length&&(this.isMultiSelectKey=!1)},onClickBlock(t,e){e&&this.isMultiSelectKey&&this.onKey(e),this.isMultiSelectKey&&(e.preventDefault(),e.stopPropagation(),this.isSelected(t)?this.deselect(t):this.select(t))},onClickGlobal(t){var e;if("function"==typeof t.target.closest&&(t.target.closest(".k-dialog")||t.target.closest(".k-drawer")))return;const i=document.querySelector(".k-overlay:last-of-type");!1!==this.$el.contains(t.target)||!1!==(null==i?void 0:i.contains(t.target))?i&&!1===(null==(e=this.$el.closest(".k-layout-column"))?void 0:e.contains(t.target))&&this.deselectAll():this.deselectAll()},onCopy(t){return!1!==this.$el.contains(t.target)&&!0!==this.isEditing&&!0!==this.$panel.dialog.isOpen&&!0!==this.isInputEvent(t)&&this.copy(t)},onFocus(t){!1===this.isMultiSelectKey&&(this.selected=[t.id])},async onKey(t){if(this.isMultiSelectKey=t.metaKey||t.ctrlKey||t.altKey,"Escape"===t.code&&this.selected.length>1){const t=this.find(this.selected[0]);await this.$nextTick(),this.focus(t)}},onPaste(t){return!0!==this.isInputEvent(t)&&(!0!==this.isEditing&&!0!==this.$panel.dialog.isOpen&&((0!==this.selected.length||!1!==this.$el.contains(t.target))&&this.paste(t)))},open(t){var e;null==(e=this.$refs["block-"+t.id])||e[0].open()},async paste(t,e){const i=this.$helper.clipboard.read(t);let n=await this.$api.post(this.endpoints.field+"/paste",{html:i});if(void 0===e){let t=this.selected[this.selected.length-1];-1===(e=this.findIndex(t))&&(e=this.blocks.length),e++}if(this.max){const t=this.max-this.blocks.length;n=n.slice(0,t)}this.blocks.splice(e,0,...n),this.save(),this.$panel.notification.success({message:this.$t("paste.success",{count:n.length}),icon:"download"})},pasteboard(){this.$panel.dialog.open({component:"k-block-pasteboard",on:{paste:this.paste}})},prevNext(t){var e;if(this.blocks[t])return null==(e=this.$refs["block-"+this.blocks[t].id])?void 0:e[0]},ref(t){var e,i;return null==(i=this.$refs["block-"+((null==t?void 0:t.id)??(null==(e=this.blocks[0])?void 0:e.id))])?void 0:i[0]},remove(t){const e=this.findIndex(t.id);-1!==e&&(this.deselect(t),this.$delete(this.blocks,e),this.save())},removeAll(){this.$panel.dialog.open({component:"k-remove-dialog",props:{text:this.$t("field.blocks.delete.confirm.all"),submitButton:this.$t("delete.all")},on:{submit:()=>{this.selected=[],this.blocks=[],this.save(),this.$panel.dialog.close()}}})},removeSelected(){this.$panel.dialog.open({component:"k-remove-dialog",props:{text:this.$t("field.blocks.delete.confirm.selected")},on:{submit:()=>{for(const t of this.selected){const e=this.findIndex(t);-1!==e&&this.$delete(this.blocks,e)}this.deselectAll(),this.save(),this.$panel.dialog.close()}}})},save(){this.$emit("input",this.blocks)},select(t){!1===this.isSelected(t)&&this.selected.push(t.id)},selectDown(){const t=this.selected[this.selected.length-1],e=this.findIndex(t)+1;e=0&&this.select(this.blocks[e])},selectAll(){this.selected=Object.values(this.blocks).map((t=>t.id))},show(t){Vue.set(t,"isHidden",!1),this.save()},async sort(t,e,i){if(i<0)return;let n=this.$helper.clone(this.blocks);n.splice(e,1),n.splice(i,0,t),this.blocks=n,this.save(),await this.$nextTick(),this.focus(t)},async split(t,e,i){const n=this.$helper.clone(t);n.content={...n.content,...i[0]};const s=await this.$api.get(this.endpoints.field+"/fieldsets/"+t.type);s.content={...s.content,...n.content,...i[1]},this.blocks.splice(e,1,n,s),this.save(),await this.$nextTick(),this.focus(s)},update(t,e){const i=this.findIndex(t.id);if(-1!==i)for(const n in e)Vue.set(this.blocks[i].content,n,e[n]);this.save()}}},(function(){var t=this,e=t._self._c;return e("div",{staticClass:"k-blocks",attrs:{"data-disabled":t.disabled,"data-empty":0===t.blocks.length}},[t.hasFieldsets?[e("k-draggable",t._b({staticClass:"k-blocks-list",attrs:{"data-multi-select-key":t.isMultiSelectKey},on:{sort:t.save},scopedSlots:t._u([0===t.blocks.length?{key:"footer",fn:function(){return[e("k-empty",{staticClass:"k-blocks-empty",attrs:{icon:"box"},on:{click:function(e){return t.choose(t.blocks.length)}}},[t._v(" "+t._s(t.empty??t.$t("field.blocks.empty"))+" ")])]},proxy:!0}:null],null,!0)},"k-draggable",t.draggableOptions,!1),t._l(t.blocks,(function(i,n){return e("k-block",t._b({key:i.id,ref:"block-"+i.id,refInFor:!0,attrs:{endpoints:t.endpoints,fieldset:t.fieldset(i),"is-batched":t.isSelected(i)&&t.selected.length>1,"is-last-selected":t.isLastSelected(i),"is-full":t.isFull,"is-hidden":!0===i.isHidden,"is-mergable":t.isMergable,"is-selected":t.isSelected(i),next:t.prevNext(n+1),prev:t.prevNext(n-1)},on:{append:function(e){return t.add(e,n+1)},chooseToAppend:function(e){return t.choose(n+1)},chooseToConvert:function(e){return t.chooseToConvert(i)},chooseToPrepend:function(e){return t.choose(n)},close:function(e){t.isEditing=!1},copy:function(e){return t.copy()},duplicate:function(e){return t.duplicate(i,n)},focus:function(e){return t.onFocus(i)},hide:function(e){return t.hide(i)},merge:function(e){return t.merge()},open:function(e){t.isEditing=!0},paste:function(e){return t.pasteboard()},prepend:function(e){return t.add(e,n)},remove:function(e){return t.remove(i)},removeSelected:t.removeSelected,show:function(e){return t.show(i)},selectDown:t.selectDown,selectUp:t.selectUp,sortDown:function(e){return t.sort(i,n,n+1)},sortUp:function(e){return t.sort(i,n,n-1)},split:function(e){return t.split(i,n,e)},update:function(e){return t.update(i,e)}},nativeOn:{click:function(e){return t.onClickBlock(i,e)}}},"k-block",i,!1))})),1)]:e("k-empty",{attrs:{icon:"box"}},[t._v(" "+t._s(t.$t("field.blocks.fieldsets.empty"))+" ")])],2)}),[],!1,null,null,null,null).exports;const Si=ut({inheritAttrs:!1,props:{caption:String,captionMarks:{default:!0,type:[Boolean,Array]},isEmpty:Boolean,emptyIcon:String,emptyText:String}},(function(){var t=this,e=t._self._c;return e("figure",{staticClass:"k-block-figure"},[t.isEmpty?e("k-button",{staticClass:"k-block-figure-empty",attrs:{icon:t.emptyIcon,text:t.emptyText},on:{click:function(e){return t.$emit("open")}}}):e("span",{staticClass:"k-block-figure-container",on:{dblclick:function(e){return t.$emit("open")}}},[t._t("default")],2),t.caption?e("figcaption",[e("k-writer",{attrs:{inline:!0,marks:t.captionMarks,value:t.caption},on:{input:function(e){return t.$emit("update",{caption:e})}}})],1):t._e()],1)}),[],!1,null,null,null,null).exports;const Ci=ut({props:{isBatched:Boolean,isEditable:Boolean,isFull:Boolean,isHidden:Boolean,isMergable:Boolean,isSplitable:Boolean},computed:{buttons(){return this.isBatched?[{icon:"template",title:this.$t("copy"),click:()=>this.$emit("copy")},{when:this.isMergable,icon:"merge",title:this.$t("merge"),click:()=>this.$emit("merge")},{icon:"trash",title:this.$t("remove"),click:()=>this.$emit("removeSelected")}]:[{when:this.isEditable,icon:"edit",title:this.$t("edit"),click:()=>this.$emit("open")},{icon:"add",title:this.$t("insert.after"),disabled:this.isFull,click:()=>this.$emit("chooseToAppend")},{icon:"trash",title:this.$t("delete"),click:()=>this.$emit("remove")},{icon:"sort",title:this.$t("sort.drag"),class:"k-sort-handle",key:t=>this.sort(t)},{icon:"dots",title:this.$t("more"),dropdown:[{icon:"angle-up",label:this.$t("insert.before"),disabled:this.isFull,click:()=>this.$emit("chooseToPrepend")},{icon:"angle-down",label:this.$t("insert.after"),disabled:this.isFull,click:()=>this.$emit("chooseToAppend")},"-",{when:this.isEditable,icon:"edit",label:this.$t("edit"),click:()=>this.$emit("open")},{icon:"refresh",label:this.$t("field.blocks.changeType"),click:()=>this.$emit("chooseToConvert")},{when:this.isSplitable,icon:"split",label:this.$t("split"),click:()=>this.$emit("split")},"-",{icon:"template",label:this.$t("copy"),click:()=>this.$emit("copy")},{icon:"download",label:this.$t("paste.after"),disabled:this.isFull,click:()=>this.$emit("paste")},"-",{icon:this.isHidden?"preview":"hidden",label:this.isHidden?this.$t("show"):this.$t("hide"),click:()=>this.$emit(this.isHidden?"show":"hide")},{icon:"copy",label:this.$t("duplicate"),click:()=>this.$emit("duplicate")},"-",{icon:"trash",label:this.$t("delete"),click:()=>this.$emit("remove")}]}]}},methods:{open(){this.$refs.options.open()},sort(t){switch(t.preventDefault(),t.key){case"ArrowUp":this.$emit("sortUp");break;case"ArrowDown":this.$emit("sortDown")}}}},(function(){return(0,this._self._c)("k-toolbar",{staticClass:"k-block-options",attrs:{buttons:this.buttons},nativeOn:{mousedown:function(t){t.preventDefault()}}})}),[],!1,null,null,null,null).exports;const Oi=ut({inheritAttrs:!1,computed:{shortcut(){return this.$helper.keyboard.metaKey()+"+v"}},methods:{paste(t){this.$emit("close"),this.$emit("paste",t)}}},(function(){var t=this,e=t._self._c;return e("k-dialog",{ref:"dialog",staticClass:"k-block-importer",attrs:{"cancel-button":!1,"submit-button":!1,visible:!0,size:"large"},on:{cancel:function(e){return t.$emit("cancel")},submit:function(e){return t.$emit("submit")}}},[e("label",{attrs:{for:"pasteboard"},domProps:{innerHTML:t._s(t.$t("field.blocks.fieldsets.paste",{shortcut:t.shortcut}))}}),e("textarea",{attrs:{id:"pasteboard"},on:{paste:function(e){return e.preventDefault(),t.paste.apply(null,arguments)}}})])}),[],!1,null,null,null,null).exports;const Ai=ut({mixins:[Lt],inheritAttrs:!1,props:{cancelButton:{default:!1},disabledFieldsets:{default:()=>[],type:Array},fieldsets:{type:Object},fieldsetGroups:{type:Object},headline:{type:String},size:{default:"medium"},submitButton:{default:!1},value:{default:null,type:String}},data:()=>({selected:null}),computed:{groups(){const t={};let e=0;const i=this.fieldsetGroups??{blocks:{label:this.$t("field.blocks.fieldsets.label"),sets:Object.keys(this.fieldsets)}};for(const n in i){const s=i[n];s.open=!1!==s.open,s.fieldsets=s.sets.filter((t=>this.fieldsets[t])).map((t=>(e++,{...this.fieldsets[t],index:e}))),0!==s.fieldsets.length&&(t[n]=s)}return t},shortcut(){return this.$helper.keyboard.metaKey()+"+v"}},created(){this.$events.on("paste",this.paste)},destroyed(){this.$events.off("paste",this.paste)},methods:{paste(t){this.$emit("paste",t),this.close()}}},(function(){var t=this,e=t._self._c;return e("k-dialog",t._b({staticClass:"k-block-selector",on:{cancel:function(e){return t.$emit("cancel")},submit:function(e){return t.$emit("submit",t.value)}}},"k-dialog",t.$props,!1),[t.headline?e("k-headline",[t._v(" "+t._s(t.headline)+" ")]):t._e(),t._l(t.groups,(function(i,n){return e("details",{key:n,attrs:{open:i.open}},[e("summary",[t._v(t._s(i.label))]),e("k-navigate",{staticClass:"k-block-types"},t._l(i.fieldsets,(function(i){return e("k-button",{key:i.name,attrs:{disabled:t.disabledFieldsets.includes(i.type),icon:i.icon??"box",text:i.name,size:"lg"},on:{click:function(e){return t.$emit("submit",i.type)}},nativeOn:{focus:function(e){return t.$emit("input",i.type)}}})})),1)],1)})),e("p",{staticClass:"k-clipboard-hint",domProps:{innerHTML:t._s(t.$t("field.blocks.fieldsets.paste",{shortcut:t.shortcut}))}})],2)}),[],!1,null,null,null,null).exports;const Mi=ut({inheritAttrs:!1,props:{fieldset:{default:()=>({}),type:Object},content:{default:()=>({}),type:Object}},computed:{icon(){return this.fieldset.icon??"box"},label(){if(!this.fieldset.label||0===this.fieldset.label.length)return!1;if(this.fieldset.label===this.fieldset.name)return!1;let t=this.$helper.string.template(this.fieldset.label,this.content);return"…"!==t&&(t=this.$helper.string.stripHTML(t),this.$helper.string.unescapeHTML(t))},name(){return this.fieldset.name}}},(function(){var t=this,e=t._self._c;return e("div",{staticClass:"k-block-title"},[e("k-icon",{staticClass:"k-block-icon",attrs:{type:t.icon}}),t.name?e("span",{staticClass:"k-block-name"},[t._v(" "+t._s(t.name)+" ")]):t._e(),t.label?e("span",{staticClass:"k-block-label"},[t._v(" "+t._s(t.label)+" ")]):t._e()],1)}),[],!1,null,null,null,null).exports;const ji=ut({inheritAttrs:!1,props:{content:[Object,Array],fieldset:Object,id:String},methods:{field(t,e=null){let i=null;for(const n of Object.values(this.fieldset.tabs??{}))n.fields[t]&&(i=n.fields[t]);return i??e},open(){this.$emit("open")},update(t){this.$emit("update",{...this.content,...t})}}},null,null,!1,null,null,null,null).exports,Ti={install(t){t.component("k-block",xi),t.component("k-blocks",_i),t.component("k-block-figure",Si),t.component("k-block-options",Ci),t.component("k-block-pasteboard",Oi),t.component("k-block-selector",Ai),t.component("k-block-title",Mi),t.component("k-block-type",ji);const e=Object.assign({"./Types/Code.vue":Ze,"./Types/Default.vue":ti,"./Types/Fields.vue":ii,"./Types/Gallery.vue":si,"./Types/Heading.vue":li,"./Types/Image.vue":ai,"./Types/Line.vue":ci,"./Types/List.vue":pi,"./Types/Markdown.vue":mi,"./Types/Quote.vue":gi,"./Types/Table.vue":bi,"./Types/Text.vue":yi,"./Types/Video.vue":wi});for(const i in e){const n=i.match(/\/([a-zA-Z]*)\.vue/)[1].toLowerCase();let s=e[i].default;s.extends=ji,t.component("k-block-type-"+n,s)}}};const Ii=ut({mixins:[Re],inheritAttrs:!1,props:{autofocus:Boolean,empty:String,fieldsets:Object,fieldsetGroups:Object,group:String,max:{type:Number,default:null},value:{type:Array,default:()=>[]}},data:()=>({opened:[]}),computed:{hasFieldsets(){return this.$helper.object.length(this.fieldsets)>0},isEmpty(){return 0===this.value.length},isFull(){return null!==this.max&&this.value.length>=this.max},options(){return[{click:()=>this.$refs.blocks.copyAll(),disabled:this.isEmpty,icon:"template",text:this.$t("copy.all")},{click:()=>this.$refs.blocks.pasteboard(),disabled:this.isFull,icon:"download",text:this.$t("paste")},"-",{click:()=>this.$refs.blocks.removeAll(),disabled:this.isEmpty,icon:"trash",text:this.$t("delete.all")}]}},methods:{focus(){this.$refs.blocks.focus()}}},(function(){var t=this,e=t._self._c;return e("k-field",t._b({staticClass:"k-blocks-field",scopedSlots:t._u([!t.disabled&&t.hasFieldsets?{key:"options",fn:function(){return[e("k-button-group",{attrs:{layout:"collapsed"}},[e("k-button",{attrs:{autofocus:t.autofocus,disabled:t.isFull,responsive:!0,text:t.$t("add"),icon:"add",variant:"filled",size:"xs"},on:{click:function(e){return t.$refs.blocks.choose(t.value.length)}}}),e("k-button",{attrs:{icon:"dots",variant:"filled",size:"xs"},on:{click:function(e){return t.$refs.options.toggle()}}}),e("k-dropdown-content",{ref:"options",attrs:{options:t.options,"align-x":"end"}})],1)]},proxy:!0}:null],null,!0)},"k-field",t.$props,!1),[e("k-blocks",t._g({ref:"blocks",attrs:{autofocus:t.autofocus,compact:!1,disabled:t.disabled,empty:t.empty,endpoints:t.endpoints,fieldsets:t.fieldsets,"fieldset-groups":t.fieldsetGroups,group:t.group,max:t.max,value:t.value},on:{close:function(e){t.opened=e},open:function(e){t.opened=e}}},t.$listeners)),t.disabled||t.isEmpty||t.isFull||!t.hasFieldsets?t._e():e("footer",[e("k-button",{attrs:{title:t.$t("add"),icon:"add",size:"xs",variant:"filled"},on:{click:function(e){return t.$refs.blocks.choose(t.value.length)}}})],1)],1)}),[],!1,null,null,null,null).exports,Ei={mixins:[Te,st],props:{columns:{default:1,type:Number},max:Number,min:Number,theme:String,value:{type:Array,default:()=>[]}}};const Li=ut({mixins:[Ie,Ei],data:()=>({selected:[]}),computed:{choices(){return this.options.map(((t,e)=>({autofocus:this.autofocus&&0===e,checked:this.selected.includes(t.value),disabled:this.disabled||t.disabled,info:t.info,label:t.text,name:this.name??this.id,type:"checkbox",value:t.value})))}},watch:{value:{handler(t){this.selected=Array.isArray(t)?t:[],this.validate()},immediate:!0}},methods:{focus(){var t;null==(t=this.$el.querySelector("input"))||t.focus()},input(t,e){if(!0===e)this.selected.push(t);else{const e=this.selected.indexOf(t);-1!==e&&this.selected.splice(e,1)}this.$emit("input",this.selected)},select(){this.focus()},validate(){this.$emit("invalid",this.$v.$invalid,this.$v)}},validations(){return{selected:{required:!this.required||t.required,min:!this.min||t.minLength(this.min),max:!this.max||t.maxLength(this.max)}}}},(function(){var t=this,e=t._self._c;return e("ul",{staticClass:"k-checkboxes-input k-grid",style:{"--columns":t.columns},attrs:{"data-variant":"choices"}},t._l(t.choices,(function(i,n){return e("li",{key:n},[e("k-choice-input",t._b({on:{input:function(e){return t.input(i.value,e)}}},"k-choice-input",i,!1))],1)})),0)}),[],!1,null,null,null,null).exports,Di={props:{counter:{type:Boolean,default:!0}},computed:{counterOptions(){const t=this.counterValue??this.value;if(null===t||this.disabled||!1===this.counter)return!1;let e=0;return t&&(e=Array.isArray(t)?t.length:String(t).length),{count:e,min:this.min??this.minlength,max:this.max??this.maxlength}}}};const Bi=ut({mixins:[Re,He,Ei,Di],inheritAttrs:!1,methods:{focus(){this.$refs.input.focus()}}},(function(){var t,e=this,i=e._self._c;return i("k-field",e._b({staticClass:"k-checkboxes-field",attrs:{counter:e.counterOptions}},"k-field",e.$props,!1),[(null==(t=e.options)?void 0:t.length)?i("k-checkboxes-input",e._g(e._b({ref:"input",attrs:{id:e._uid,theme:"field"}},"k-checkboxes-input",e.$props,!1),e.$listeners)):i("k-empty",{attrs:{text:e.$t("options.none"),icon:"checklist"}})],1)}),[],!1,null,null,null,null).exports,qi={mixins:[Te,H,J,et,it,ot,lt,at],props:{ariaLabel:String,type:{default:"text",type:String},value:{type:String}}};const Pi=ut({mixins:[Ie,qi]},(function(){var t=this;return(0,t._self._c)("input",t._b({directives:[{name:"direction",rawName:"v-direction"}],staticClass:"k-string-input",attrs:{"aria-label":t.ariaLabel,"data-font":t.font},on:{input:function(e){return t.$emit("input",e.target.value)}}},"input",{autocomplete:t.autocomplete,autofocus:t.autofocus,disabled:t.disabled,id:t.id,maxlength:t.maxlength,minlength:t.minlength,name:t.name,pattern:t.pattern,placeholder:t.placeholder,required:t.required,spellcheck:t.spellcheck,type:t.type,value:t.value},!1))}),[],!1,null,null,null,null).exports,Ni={mixins:[qi],props:{alpha:{type:Boolean,default:!0},autocomplete:{default:"off",type:String},format:{type:String,default:"hex",validator:t=>["hex","rgb","hsl"].includes(t)},spellcheck:{default:!1,type:Boolean}}};const zi=ut({mixins:[Pi,Ni],watch:{value(){this.validate()}},mounted(){this.validate()},methods:{convert(t){if(!t)return t;try{return this.$library.colors.toString(t,this.format,this.alpha)}catch(e){const i=document.createElement("div");return i.style.color=t,document.body.append(i),t=window.getComputedStyle(i).color,i.remove(),this.$library.colors.toString(t,this.format,this.alpha)}},convertAndEmit(t){this.emit(this.convert(t))},emit(t){this.$emit("input",t)},onBlur(){this.convertAndEmit(this.value)},onPaste(t){t instanceof ClipboardEvent&&(t=this.$helper.clipboard.read(t,!0)),this.convertAndEmit(t)},async onSave(){var t;this.convertAndEmit(this.value),await this.$nextTick(),null==(t=this.$el.form)||t.requestSubmit()},validate(){let t="";null===this.$library.colors.parse(this.value)&&(t=this.$t("error.validation.color",{format:this.format})),this.$el.setCustomValidity(t)}}},(function(){var t=this;return(0,t._self._c)("k-string-input",t._b({staticClass:"k-colorname-input",attrs:{type:"text"},on:{input:function(e){return t.$emit("input",e)}},nativeOn:{blur:function(e){return t.onBlur.apply(null,arguments)},paste:function(e){return t.onPaste.apply(null,arguments)},keydown:[function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"s",void 0,e.key,void 0)?null:e.metaKey?(e.stopPropagation(),e.preventDefault(),t.onSave.apply(null,arguments)):null},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"enter",13,e.key,"Enter")?null:t.onSave.apply(null,arguments)}]}},"k-string-input",t.$props,!1))}),[],!1,null,null,null,null).exports;const Fi=ut({mixins:[Re,He,Ni],inheritAttrs:!1,props:{icon:{type:String,default:"pipette"},mode:{type:String,default:"picker",validator:t=>["picker","input","options"].includes(t)},options:{type:Array,default:()=>[]}},data:()=>({isInvalid:!1}),computed:{convertedOptions(){return this.options.map((t=>({...t,value:this.convert(t.value)})))},currentOption(){return this.convertedOptions.find((t=>t.value===this.value))}},methods:{convert(t){return this.$library.colors.toString(t,this.format,this.alpha)}}},(function(){var t,e=this,i=e._self._c;return i("k-field",e._b({staticClass:"k-color-field",attrs:{input:e._uid}},"k-field",e.$props,!1),["options"===e.mode?i("k-coloroptions-input",e._b({staticClass:"k-color-field-options",attrs:{options:e.convertedOptions},on:{input:function(t){return e.$emit("input",t)}}},"k-coloroptions-input",e.$props,!1)):i("k-input",e._b({attrs:{theme:"field",type:"color"},scopedSlots:e._u([{key:"before",fn:function(){return["picker"===e.mode?[i("button",{staticClass:"k-color-field-picker-toggle",attrs:{disabled:e.disabled,type:"button"},on:{click:function(t){return e.$refs.picker.toggle()}}},[i("k-color-frame",{attrs:{color:e.value}})],1),i("k-dropdown-content",{ref:"picker",staticClass:"k-color-field-picker"},[i("k-colorpicker-input",e._b({ref:"color",attrs:{options:e.convertedOptions},on:{input:function(t){return e.$emit("input",t)}},nativeOn:{click:function(t){t.stopPropagation()}}},"k-colorpicker-input",e.$props,!1))],1)]:i("k-color-frame",{attrs:{color:e.value}})]},proxy:!0},{key:"default",fn:function(){return[i("k-colorname-input",e._b({on:{input:function(t){return e.$emit("input",t)}}},"k-colorname-input",e.$props,!1))]},proxy:!0},(null==(t=e.currentOption)?void 0:t.text)?{key:"after",fn:function(){return[e._v(" "+e._s(e.currentOption.text)+" ")]},proxy:!0}:null,"picker"===e.mode?{key:"icon",fn:function(){return[i("k-button",{staticClass:"k-input-icon-button",attrs:{icon:e.icon},on:{click:function(t){return t.stopPropagation(),e.$refs.picker.toggle()}}})]},proxy:!0}:null],null,!0)},"k-input",e.$props,!1))],1)}),[],!1,null,null,null,null).exports,Ri={mixins:[Te],props:{display:{type:String,default:"DD.MM.YYYY"},max:String,min:String,step:{type:Object,default:()=>({size:1,unit:"day"})},type:{type:String,default:"date"},value:String}};const Yi=ut({mixins:[Ie,Ri],data:()=>({dt:null,formatted:null}),computed:{inputType:()=>"date",pattern(){return this.$library.dayjs.pattern(this.display)},rounding(){return{...this.$options.props.step.default(),...this.step}}},watch:{value:{handler(t,e){if(t!==e){const e=this.toDatetime(t);this.commit(e)}},immediate:!0}},created(){this.$events.on("keydown.cmd.s",this.onBlur)},destroyed(){this.$events.off("keydown.cmd.s",this.onBlur)},methods:{async alter(t){let e=this.parse()??this.round(this.$library.dayjs()),i=this.rounding.unit,n=this.rounding.size;const s=this.selection();null!==s&&("meridiem"===s.unit?(t="pm"===e.format("a")?"subtract":"add",i="hour",n=12):(i=s.unit,i!==this.rounding.unit&&(n=1))),e=e[t](n,i).round(this.rounding.unit,this.rounding.size),this.commit(e),this.emit(e),await this.$nextTick(),this.select(s)},commit(t){this.dt=t,this.formatted=this.pattern.format(t),this.$emit("invalid",this.$v.$invalid,this.$v)},emit(t){this.$emit("input",this.toISO(t))},onArrowDown(){this.alter("subtract")},onArrowUp(){this.alter("add")},onBlur(){const t=this.parse();this.commit(t),this.emit(t)},async onEnter(){this.onBlur(),await this.$nextTick(),this.$emit("submit")},onInput(t){const e=this.parse(),i=this.pattern.format(e);if(!t||i==t)return this.commit(e),this.emit(e)},async onTab(t){if(""==this.$refs.input.value)return;this.onBlur(),await this.$nextTick();const e=this.selection();if(this.$refs.input&&e.start===this.$refs.input.selectionStart&&e.end===this.$refs.input.selectionEnd-1)if(t.shiftKey){if(0===e.index)return;this.selectPrev(e.index)}else{if(e.index===this.pattern.parts.length-1)return;this.selectNext(e.index)}else{if(this.$refs.input&&this.$refs.input.selectionStart==e.end+1&&e.index==this.pattern.parts.length-1)return;if(this.$refs.input&&this.$refs.input.selectionEnd-1>e.end){const t=this.pattern.at(this.$refs.input.selectionEnd,this.$refs.input.selectionEnd);this.select(this.pattern.parts[t.index])}else this.select(this.pattern.parts[e.index])}t.preventDefault()},parse(){let t=this.$refs.input.value;return t=this.$library.dayjs.interpret(t,this.inputType),this.round(t)},round(t){return null==t?void 0:t.round(this.rounding.unit,this.rounding.size)},select(t){var e;t||(t=this.selection()),null==(e=this.$refs.input)||e.setSelectionRange(t.start,t.end+1)},selectFirst(){this.select(this.pattern.parts[0])},selectLast(){this.select(this.pattern.parts[this.pattern.parts.length-1])},selectNext(t){this.select(this.pattern.parts[t+1])},selectPrev(t){this.select(this.pattern.parts[t-1])},selection(){return this.pattern.at(this.$refs.input.selectionStart,this.$refs.input.selectionEnd)},toDatetime(t){return this.round(this.$library.dayjs.iso(t,this.inputType))},toISO(t){return null==t?void 0:t.toISO(this.inputType)}},validations(){return{value:{min:!this.dt||!this.min||(()=>this.dt.validate(this.min,"min",this.rounding.unit)),max:!this.dt||!this.max||(()=>this.dt.validate(this.max,"max",this.rounding.unit)),required:!this.required||(()=>!!this.dt)}}}},(function(){var t=this;return(0,t._self._c)("input",{directives:[{name:"direction",rawName:"v-direction"}],ref:"input",class:`k-text-input k-${t.type}-input`,attrs:{id:t.id,autofocus:t.autofocus,disabled:t.disabled,placeholder:t.display,required:t.required,autocomplete:"off",spellcheck:"false",type:"text"},domProps:{value:t.formatted},on:{blur:t.onBlur,focus:function(e){return t.$emit("focus")},input:function(e){return t.onInput(e.target.value)},keydown:[function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"down",40,e.key,["Down","ArrowDown"])?null:(e.stopPropagation(),e.preventDefault(),t.onArrowDown.apply(null,arguments))},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"up",38,e.key,["Up","ArrowUp"])?null:(e.stopPropagation(),e.preventDefault(),t.onArrowUp.apply(null,arguments))},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"enter",13,e.key,"Enter")?null:(e.stopPropagation(),e.preventDefault(),t.onEnter.apply(null,arguments))},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"tab",9,e.key,"Tab")?null:t.onTab.apply(null,arguments)}]}})}),[],!1,null,null,null,null).exports;const Ui=ut({mixins:[Re,He,Ri],inheritAttrs:!1,props:{calendar:{type:Boolean,default:!0},icon:{type:String,default:"calendar"},time:{type:[Boolean,Object],default:()=>({})},times:{type:Boolean,default:!0}},data(){return{isInvalid:!1,iso:this.toIso(this.value)}},computed:{isEmpty(){return this.time?null===this.iso.date&&this.iso.time:null===this.iso.date}},watch:{value(t,e){t!==e&&(this.iso=this.toIso(t))}},methods:{focus(){this.$refs.dateInput.focus()},now(){const t=this.$library.dayjs();return{date:t.toISO("date"),time:this.time?t.toISO("time"):"00:00:00"}},onInput(){if(this.isEmpty)return this.$emit("input","");const t=this.$library.dayjs.iso(this.iso.date+" "+this.iso.time);(t||null!==this.iso.date&&null!==this.iso.time)&&this.$emit("input",(null==t?void 0:t.toISO())??"")},onDateInput(t){t&&!this.iso.time&&(this.iso.time=this.now().time),this.iso.date=t,this.onInput()},onDateInvalid(t){this.isInvalid=t},onTimeInput(t){t&&!this.iso.date&&(this.iso.date=this.now().date),this.iso.time=t,this.onInput()},onTimesInput(t){var e;null==(e=this.$refs.times)||e.close(),this.onTimeInput(t+":00")},toIso(t){const e=this.$library.dayjs.iso(t);return{date:(null==e?void 0:e.toISO("date"))??null,time:(null==e?void 0:e.toISO("time"))??null}}}},(function(){var t=this,e=t._self._c;return e("k-field",t._b({staticClass:"k-date-field",attrs:{input:t._uid}},"k-field",t.$props,!1),[e("div",{ref:"body",staticClass:"k-date-field-body",attrs:{"data-has-time":Boolean(t.time),"data-invalid":!t.novalidate&&t.isInvalid}},[e("k-input",t._b({ref:"dateInput",attrs:{id:t._uid,autofocus:t.autofocus,disabled:t.disabled,display:t.display,max:t.max,min:t.min,required:t.required,value:t.value,theme:"field",type:"date"},on:{invalid:t.onDateInvalid,input:t.onDateInput,submit:function(e){return t.$emit("submit")}},scopedSlots:t._u([t.calendar?{key:"icon",fn:function(){return[e("k-button",{staticClass:"k-input-icon-button",attrs:{disabled:t.disabled,icon:t.icon,title:t.$t("date.select")},on:{click:function(e){return t.$refs.calendar.toggle()}}}),e("k-dropdown-content",{ref:"calendar",attrs:{"align-x":"end"}},[e("k-calendar",{attrs:{value:t.iso.date,min:t.min,max:t.max},on:{input:t.onDateInput}})],1)]},proxy:!0}:null],null,!0)},"k-input",t.$props,!1)),t.time?e("k-input",{ref:"timeInput",attrs:{disabled:t.disabled,display:t.time.display,required:t.required,step:t.time.step,value:t.iso.time,icon:t.time.icon,theme:"field",type:"time"},on:{input:t.onTimeInput,submit:function(e){return t.$emit("submit")}},scopedSlots:t._u([t.times?{key:"icon",fn:function(){return[e("k-button",{staticClass:"k-input-icon-button",attrs:{disabled:t.disabled,icon:t.time.icon??"clock",title:t.$t("time.select")},on:{click:function(e){return t.$refs.times.toggle()}}}),e("k-dropdown-content",{ref:"times",attrs:{"align-x":"end"}},[e("k-timeoptions-input",{attrs:{display:t.time.display,value:t.value},on:{input:t.onTimesInput}})],1)]},proxy:!0}:null],null,!0)}):t._e()],1)])}),[],!1,null,null,null,null).exports,Hi={mixins:[Te,J,et,it,ot,lt,at],props:{autocomplete:{type:[Boolean,String],default:"off"},preselect:Boolean,type:{type:String,default:"text"},value:String}};const Vi=ut({mixins:[Ie,Hi],data(){return{listeners:{...this.$listeners,input:t=>this.onInput(t.target.value)}}},watch:{value(){this.onInvalid()}},mounted(){this.onInvalid(),this.$props.autofocus&&this.focus(),this.$props.preselect&&this.select()},methods:{onInput(t){this.$emit("input",t)},onInvalid(){this.$emit("invalid",this.$v.$invalid,this.$v)},select(){this.$refs.input.select()}},validations(){return{value:{required:!this.required||t.required,minLength:!this.minlength||t.minLength(this.minlength),maxLength:!this.maxlength||t.maxLength(this.maxlength),email:"email"!==this.type||t.email,url:"url"!==this.type||t.url,pattern:!this.pattern||(t=>!this.required&&!t||!this.$refs.input.validity.patternMismatch)}}}},(function(){var t=this;return(0,t._self._c)("input",t._g(t._b({directives:[{name:"direction",rawName:"v-direction"}],ref:"input",staticClass:"k-text-input",attrs:{"data-font":t.font}},"input",{autocomplete:t.autocomplete,autofocus:t.autofocus,disabled:t.disabled,id:t.id,minlength:t.minlength,name:t.name,pattern:t.pattern,placeholder:t.placeholder,required:t.required,spellcheck:t.spellcheck,type:t.type,value:t.value},!1),t.listeners))}),[],!1,null,null,null,null).exports,Ki={mixins:[Hi],props:{autocomplete:{type:String,default:"email"},placeholder:{type:String,default:()=>window.panel.$t("email.placeholder")},type:{type:String,default:"email"}}};const Wi=ut({extends:Vi,mixins:[Ki]},null,null,!1,null,null,null,null).exports;const Ji=ut({mixins:[Re,He,Ki],inheritAttrs:!1,props:{link:{type:Boolean,default:!0},icon:{type:String,default:"email"}},computed:{mailto(){var t;return(null==(t=this.value)?void 0:t.length)>0?"mailto:"+this.value:null}},methods:{focus(){this.$refs.input.focus()}}},(function(){var t=this,e=t._self._c;return e("k-field",t._b({staticClass:"k-email-field",attrs:{input:t._uid}},"k-field",t.$props,!1),[e("k-input",t._g(t._b({ref:"input",attrs:{id:t._uid,theme:"field",type:"email"},scopedSlots:t._u([{key:"icon",fn:function(){return[t.link?e("k-button",{staticClass:"k-input-icon-button",attrs:{icon:t.icon,link:t.mailto,title:t.$t("open"),tabindex:"-1",target:"_blank"}}):t._e()]},proxy:!0}])},"k-input",t.$props,!1),t.$listeners))],1)}),[],!1,null,null,null,null).exports;const Gi=ut({type:"model",mixins:[Re,V,tt],inheritAttrs:!1,props:{empty:String,info:String,link:Boolean,max:Number,multiple:Boolean,parent:String,search:Boolean,size:String,text:String,value:{type:Array,default:()=>[]}},data(){return{selected:this.value}},computed:{buttons(){return[{autofocus:this.autofocus,text:this.$t("select"),icon:"checklist",responsive:!0,click:()=>this.open()}]},collection(){return{empty:this.emptyProps,items:this.selected,layout:this.layout,link:this.link,size:this.size,sortable:!this.disabled&&this.selected.length>1,theme:this.disabled?"disabled":null}},hasDropzone:()=>!1,isInvalid(){return this.required&&0===this.selected.length||this.min&&this.selected.lengththis.max},more(){return!this.max||this.max>this.selected.length}},watch:{value(t){this.selected=t}},methods:{drop(){},focus(){},onInput(){this.$emit("input",this.selected)},open(){if(this.disabled)return!1;this.$panel.dialog.open({component:`k-${this.$options.type}-dialog`,props:{endpoint:this.endpoints.field,hasSearch:this.search,max:this.max,multiple:this.multiple,value:this.selected.map((t=>t.id))},on:{submit:t=>{this.select(t),this.$panel.dialog.close()}}})},remove(t){this.selected.splice(t,1),this.onInput()},removeById(t){this.selected=this.selected.filter((e=>e.id!==t)),this.onInput()},select(t){if(0===t.length)return this.selected=[],void this.onInput();this.selected=this.selected.filter((e=>t.find((t=>t.id===e.id))));for(const e of t)this.selected.find((t=>e.id===t.id))||this.selected.push(e);this.onInput()}}},(function(){var t=this,e=t._self._c;return e("k-field",t._b({class:`k-models-field k-${t.$options.type}-field`,scopedSlots:t._u([t.disabled?null:{key:"options",fn:function(){return[e("k-button-group",{ref:"buttons",staticClass:"k-field-options",attrs:{buttons:t.buttons,layout:"collapsed",size:"xs",variant:"filled"}})]},proxy:!0}],null,!0)},"k-field",t.$props,!1),[e("k-dropzone",{attrs:{disabled:!t.hasDropzone},on:{drop:t.drop}},[e("k-collection",t._b({on:{empty:t.open,sort:t.onInput,sortChange:function(e){return t.$emit("change",e)}},scopedSlots:t._u([t.disabled?null:{key:"options",fn:function({index:i}){return[e("k-button",{attrs:{title:t.$t("remove"),icon:"remove"},on:{click:function(e){return t.remove(i)}}})]}}],null,!0)},"k-collection",t.collection,!1))],1)],1)}),[],!1,null,null,null,null).exports;const Xi=ut({extends:Gi,type:"files",props:{uploads:[Boolean,Object,Array]},computed:{buttons(){const t=Gi.computed.buttons.call(this);return this.hasDropzone&&t.unshift({autofocus:this.autofocus,text:this.$t("upload"),responsive:!0,icon:"upload",click:()=>this.$panel.upload.pick(this.uploadOptions)}),t},emptyProps(){return{icon:"image",text:this.empty??this.$t("field.files.empty")}},hasDropzone(){return!this.disabled&&this.more&&this.uploads},uploadOptions(){return{accept:this.uploads.accept,max:this.max,multiple:this.multiple,url:this.$panel.urls.api+"/"+this.endpoints.field+"/upload",on:{done:t=>{!1===this.multiple&&(this.selected=[]);for(const e of t)void 0===this.selected.find((t=>t.id===e.id))&&this.selected.push(e);this.onInput(),this.$events.emit("model.update")}}}}},created(){this.$events.on("file.delete",this.removeById)},destroyed(){this.$events.off("file.delete",this.removeById)},methods:{drop(t){return!1!==this.uploads&&this.$panel.upload.open(t,this.uploadOptions)}}},null,null,!1,null,null,null,null).exports;const Zi=ut({},(function(){return(0,this._self._c)("div",{staticClass:"k-field k-gap-field"})}),[],!1,null,null,null,null).exports;const Qi=ut({mixins:[G,Q],inheritAttrs:!1},(function(){var t=this,e=t._self._c;return e("div",{staticClass:"k-headline-field"},[e("k-headline",{staticClass:"h2"},[t._v(" "+t._s(t.label)+" ")]),t.help?e("footer",{staticClass:"k-field-footer"},[e("k-text",{staticClass:"k-help k-field-help",attrs:{html:t.help}})],1):t._e()],1)}),[],!1,null,null,null,null).exports;const tn=ut({mixins:[G,Q],props:{icon:String,text:String,theme:{type:String,default:"info"}}},(function(){var t=this,e=t._self._c;return e("div",{staticClass:"k-field k-info-field"},[t.label?e("k-headline",[t._v(t._s(t.label))]):t._e(),e("k-box",{attrs:{icon:t.icon,theme:t.theme}},[e("k-text",{attrs:{html:t.text}})],1),t.help?e("footer",{staticClass:"k-field-footer"},[e("k-text",{staticClass:"k-help k-field-help",attrs:{html:t.help}})],1):t._e()],1)}),[],!1,null,null,null,null).exports;const en=ut({mixins:[Re],inheritAttrs:!1,props:{autofocus:Boolean,empty:String,fieldsetGroups:Object,fieldsets:Object,layouts:{type:Array,default:()=>[["1/1"]]},selector:Object,settings:Object,value:{type:Array,default:()=>[]}},computed:{isEmpty(){return 0===this.value.length},options(){return[{click:()=>this.$refs.layouts.copy(),disabled:this.isEmpty,icon:"template",text:this.$t("copy.all")},{click:()=>this.$refs.layouts.pasteboard(),icon:"download",text:this.$t("paste")},"-",{click:()=>this.$refs.layouts.removeAll(),disabled:this.isEmpty,icon:"trash",text:this.$t("delete.all")}]}}},(function(){var t=this,e=t._self._c;return e("k-field",t._b({staticClass:"k-layout-field",scopedSlots:t._u([t.disabled?null:{key:"options",fn:function(){return[e("k-button-group",{attrs:{layout:"collapsed"}},[e("k-button",{attrs:{autofocus:t.autofocus,text:t.$t("add"),icon:"add",variant:"filled",size:"xs"},on:{click:function(e){return t.$refs.layouts.select(0)}}}),e("k-button",{attrs:{icon:"dots",variant:"filled",size:"xs"},on:{click:function(e){return t.$refs.options.toggle()}}}),e("k-dropdown-content",{ref:"options",attrs:{options:t.options,"align-x":"end"}})],1)]},proxy:!0}],null,!0)},"k-field",t.$props,!1),[e("k-layouts",t._b({ref:"layouts",on:{input:function(e){return t.$emit("input",e)}}},"k-layouts",t.$props,!1)),t.disabled?t._e():e("footer",[e("k-button",{attrs:{title:t.$t("add"),icon:"add",size:"xs",variant:"filled"},on:{click:function(e){return t.$refs.layouts.select(t.value.length)}}})],1)],1)}),[],!1,null,null,null,null).exports;const nn=ut({},(function(){return(0,this._self._c)("hr",{staticClass:"k-line-field"})}),[],!1,null,null,null,null).exports;const sn=ut({mixins:[{mixins:[Re,He,st],props:{value:{default:"",type:String}}}],inheritAttrs:!1,data:()=>({model:null,linkType:null,linkValue:null,expanded:!1,isInvalid:!1}),computed:{currentType(){return this.activeTypes[this.linkType]??Object.values(this.activeTypes)[0]},availableTypes(){return{url:{detect:t=>/^(http|https):\/\//.test(t),icon:"url",label:this.$t("url"),link:t=>t,placeholder:this.$t("url.placeholder"),input:"url",value:t=>t},page:{detect:t=>!0===this.isPageUUID(t),icon:"page",label:this.$t("page"),link:t=>t,placeholder:this.$t("select")+" …",input:"text",value:t=>t},file:{detect:t=>!0===this.isFileUUID(t),icon:"file",label:this.$t("file"),link:t=>t,placeholder:this.$t("select")+" …",value:t=>t},email:{detect:t=>t.startsWith("mailto:"),icon:"email",label:this.$t("email"),link:t=>t.replace(/^mailto:/,""),placeholder:this.$t("email.placeholder"),input:"email",value:t=>"mailto:"+t},tel:{detect:t=>t.startsWith("tel:"),icon:"phone",label:this.$t("tel"),link:t=>t.replace(/^tel:/,""),pattern:"[+]{0,1}[0-9]+",placeholder:this.$t("tel.placeholder"),input:"tel",value:t=>"tel:"+t},anchor:{detect:t=>t.startsWith("#"),icon:"anchor",label:"Anchor",link:t=>t,pattern:"^#.+",placeholder:"#element",input:"text",value:t=>t},custom:{detect:()=>!0,icon:"title",label:this.$t("custom"),link:t=>t,input:"text",value:t=>t}}},activeTypes(){var t;if(!(null==(t=this.options)?void 0:t.length))return this.availableTypes;const e={};for(const i of this.options)e[i]=this.availableTypes[i];return e},activeTypesOptions(){const t=[];for(const e in this.activeTypes)t.push({click:()=>this.switchType(e),current:e===this.linkType,icon:this.activeTypes[e].icon,label:this.activeTypes[e].label});return t}},watch:{value:{handler(t,e){if(t===e)return;const i=this.detect(t);this.linkType=this.linkType??i.type,this.linkValue=i.link,t!==e&&this.preview(i)},immediate:!0}},created(){this.$events.on("click",this.onOutsideClick)},destroyed(){this.$events.off("click",this.onOutsideClick)},methods:{clear(){this.$emit("input",""),this.expanded=!1},detect(t){if(0===(t=t??"").length)return{type:"url",link:""};for(const e in this.availableTypes)if(!0===this.availableTypes[e].detect(t))return{type:e,link:this.availableTypes[e].link(t)}},focus(){var t;null==(t=this.$refs.input)||t.focus()},getFileUUID:t=>t.replace("/@/file/","file://"),getPageUUID:t=>t.replace("/@/page/","page://"),isFileUUID:t=>!0===t.startsWith("file://")||!0===t.startsWith("/@/file/"),isPageUUID:t=>"site://"===t||!0===t.startsWith("page://")||!0===t.startsWith("/@/page/"),onInput(t){const e=(null==t?void 0:t.trim())??"";if(!e.length)return this.$emit("input","");this.$emit("input",this.currentType.value(e))},onInvalid(t){this.isInvalid=!!t},onOutsideClick(t){!1===this.$el.contains(t.target)&&(this.expanded=!1)},async preview({type:t,link:e}){this.model="page"===t&&e?await this.previewForPage(e):"file"===t&&e?await this.previewForFile(e):e?{label:e}:null},async previewForFile(t){try{const e=await this.$api.files.get(null,t,{select:"filename, panelImage"});return{label:e.filename,image:e.panelImage}}catch(e){return null}},async previewForPage(t){if("site://"===t)return{label:this.$t("view.site")};try{return{label:(await this.$api.pages.get(t,{select:"title"})).title}}catch(e){return null}},selectModel(t){t.uuid?this.onInput(t.uuid):(this.switchType("url"),this.onInput(t.url))},async switchType(t){t!==this.linkType&&(this.isInvalid=!1,this.linkType=t,this.linkValue="","page"===this.linkType||"file"===this.linkType?this.expanded=!0:this.expanded=!1,this.$emit("input",""),await this.$nextTick(),this.focus())},toggle(){this.expanded=!this.expanded}}},(function(){var t=this,e=t._self._c;return e("k-field",t._b({staticClass:"k-link-field",attrs:{input:t._uid}},"k-field",t.$props,!1),[e("k-input",t._b({attrs:{invalid:t.isInvalid,icon:!1,theme:"field"}},"k-input",t.$props,!1),[e("div",{staticClass:"k-link-input-header"},[e("k-button",{staticClass:"k-link-input-toggle",attrs:{disabled:t.disabled,dropdown:!0,icon:t.currentType.icon,variant:"filled"},on:{click:function(e){return t.$refs.types.toggle()}}},[t._v(" "+t._s(t.currentType.label)+" ")]),e("k-dropdown-content",{ref:"types",attrs:{options:t.activeTypesOptions}}),"page"===t.linkType||"file"===t.linkType?e("div",{staticClass:"k-link-input-model",on:{click:t.toggle}},[t.model?e("k-tag",{staticClass:"k-link-input-model-preview",attrs:{image:t.model.image?{...t.model.image,cover:!0,back:"gray-200"}:null,removable:!t.disabled},on:{remove:t.clear}},[t._v(" "+t._s(t.model.label)+" ")]):e("k-button",{staticClass:"k-link-input-model-placeholder"},[t._v(" "+t._s(t.currentType.placeholder)+" ")]),e("k-button",{staticClass:"k-link-input-model-toggle",attrs:{icon:"bars"}})],1):e("k-"+t.currentType.input+"-input",{ref:"input",tag:"component",attrs:{id:t._uid,pattern:t.currentType.pattern??null,placeholder:t.currentType.placeholder,value:t.linkValue},on:{invalid:t.onInvalid,input:t.onInput}})],1),"page"===t.linkType?e("div",{directives:[{name:"show",rawName:"v-show",value:t.expanded,expression:"expanded"}],staticClass:"k-link-input-body",attrs:{"data-type":"page"}},[e("div",{staticClass:"k-page-browser"},[e("k-page-tree",{attrs:{current:t.getPageUUID(t.value),root:!1},on:{select:function(e){return t.selectModel(e)}}})],1)]):"file"===t.linkType?e("div",{directives:[{name:"show",rawName:"v-show",value:t.expanded,expression:"expanded"}],staticClass:"k-link-input-body",attrs:{"data-type":"file"}},[e("k-file-browser",{attrs:{selected:t.getFileUUID(t.value)},on:{select:function(e){return t.selectModel(e)}}})],1):t._e()])],1)}),[],!1,null,null,null,null).exports;const on=t=>({$from:e})=>((t,e)=>{for(let i=t.depth;i>0;i--){const n=t.node(i);if(e(n))return{pos:i>0?t.before(i):0,start:t.start(i),depth:i,node:n}}})(e,t),ln=t=>e=>{if((t=>t instanceof o)(e)){const{node:i,$from:n}=e;if(((t,e)=>Array.isArray(t)&&t.indexOf(e.type)>-1||e.type===t)(t,i))return{node:i,pos:n.pos,depth:n.depth}}},rn=(t,e,i={})=>{const n=ln(e)(t.selection)||on((t=>t.type===e))(t.selection);return 0!==$t(i)&&n?n.node.hasMarkup(e,{...n.node.attrs,...i}):!!n};function an(t=null,e=null){if(!t||!e)return!1;const i=t.parent.childAfter(t.parentOffset);if(!i.node)return!1;const n=i.node.marks.find((t=>t.type===e));if(!n)return!1;let s=t.index(),o=t.start()+i.offset,l=s+1,r=o+i.node.nodeSize;for(;s>0&&n.isInSet(t.parent.child(s-1).marks);)s-=1,o-=t.parent.child(s).nodeSize;for(;l{s=[...s,...t.marks]}));const o=s.find((t=>t.type.name===e.name));return o?o.attrs:{}},getNodeAttrs:function(t,e){const{from:i,to:n}=t.selection;let s=[];t.doc.nodesBetween(i,n,(t=>{s=[...s,t]}));const o=s.reverse().find((t=>t.type.name===e.name));return o?o.attrs:{}},markInputRule:function(t,i,n){return new e(t,((t,e,s,o)=>{const l=n instanceof Function?n(e):n,{tr:r}=t,a=e.length-1;let u=o,c=s;if(e[a]){const n=s+e[0].indexOf(e[a-1]),l=n+e[a-1].length-1,d=n+e[a-1].lastIndexOf(e[a]),p=d+e[a].length,h=function(t,e,i){let n=[];return i.doc.nodesBetween(t,e,((t,e)=>{n=[...n,...t.marks.map((i=>({start:e,end:e+t.nodeSize,mark:i})))]})),n}(s,o,t).filter((t=>{const{excluded:e}=t.mark.type;return e.find((t=>t.name===i.name))})).filter((t=>t.end>n));if(h.length)return!1;pn&&r.delete(n,d),c=n,u=c+e[a].length}return r.addMark(c,u,i.create(l)),r.removeStoredMark(i),r}))},markIsActive:function(t,e){const{from:i,$from:n,to:s,empty:o}=t.selection;return o?!!e.isInSet(t.storedMarks||n.marks()):!!t.doc.rangeHasMark(i,s,e)},markPasteRule:function(t,e,o){const l=(i,n)=>{const r=[];return i.forEach((i=>{var s;if(i.isText){const{text:l,marks:a}=i;let u,c=0;const d=!!a.filter((t=>"link"===t.type.name))[0];for(;!d&&null!==(u=t.exec(l));)if((null==(s=null==n?void 0:n.type)?void 0:s.allowsMarkType(e))&&u[1]){const t=u.index,n=t+u[0].length,s=t+u[0].indexOf(u[1]),l=s+u[1].length,a=o instanceof Function?o(u):o;t>0&&r.push(i.cut(c,t)),r.push(i.cut(s,l).mark(e.create(a).addToSet(i.marks))),c=n}cnew n(l(t.content),t.openStart,t.openEnd)}})},minMax:function(t=0,e=0,i=0){return Math.min(Math.max(parseInt(t,10),e),i)},nodeIsActive:rn,nodeInputRule:function(t,i,n){return new e(t,((t,e,s,o)=>{const l=n instanceof Function?n(e):n,{tr:r}=t;return e[0]&&r.replaceWith(s-1,o,i.create(l)),r}))},pasteRule:function(t,e,o){const l=i=>{const n=[];return i.forEach((i=>{if(i.isText){const{text:s}=i;let l,r=0;do{if(l=t.exec(s),l){const t=l.index,s=t+l[0].length,a=o instanceof Function?o(l[0]):o;t>0&&n.push(i.cut(r,t)),n.push(i.cut(t,s).mark(e.create(a).addToSet(i.marks))),r=s}}while(l);rnew n(l(t.content),t.openStart,t.openEnd)}})},removeMark:function(t){return(e,i)=>{const{tr:n,selection:s}=e;let{from:o,to:l}=s;const{$from:r,empty:a}=s;if(a){const e=an(r,t);o=e.from,l=e.to}return n.removeMark(o,l,t),i(n)}},toggleBlockType:function(t,e,i={}){return(n,s,o)=>rn(n,t,i)?l(e)(n,s,o):l(t,i)(n,s,o)},toggleList:function(t,e){return(i,n,s)=>{const{schema:o,selection:l}=i,{$from:u,$to:c}=l,d=u.blockRange(c);if(!d)return!1;const p=on((t=>un(t,o)))(l);if(d.depth>=1&&p&&d.depth-p.depth<=1){if(p.node.type===t)return r(e)(i,n,s);if(un(p.node,o)&&t.validContent(p.node.content)){const{tr:e}=i;return e.setNodeMarkup(p.pos,t),n&&n(e),!1}}return a(t)(i,n,s)}},toggleWrap:function(t,e={}){return(i,n,s)=>rn(i,t,e)?u(i,n):c(t,e)(i,n,s)},updateMark:function(t,e){return(i,n)=>{const{tr:s,selection:o,doc:l}=i,{ranges:r,empty:a}=o;if(a){const{from:i,to:n}=an(o.$from,t);l.rangeHasMark(i,n,t)&&s.removeMark(i,n,t),s.addMark(i,n,t.create(e))}else r.forEach((i=>{const{$to:n,$from:o}=i;l.rangeHasMark(o.pos,n.pos,t)&&s.removeMark(o.pos,n.pos,t),s.addMark(o.pos,n.pos,t.create(e))}));return n(s)}}};class dn{emit(t,...e){this._callbacks=this._callbacks??{};const i=this._callbacks[t]??[];for(const n of i)n.apply(this,e);return this}off(t,e){if(arguments.length){const i=this._callbacks?this._callbacks[t]:null;i&&(e?this._callbacks[t]=i.filter((t=>t!==e)):delete this._callbacks[t])}else this._callbacks={};return this}on(t,e){return this._callbacks=this._callbacks??{},this._callbacks[t]=this._callbacks[t]??[],this._callbacks[t].push(e),this}}class pn{constructor(t=[],e){for(const i of t)i.bindEditor(e),i.init();this.extensions=t}commands({schema:t,view:e}){return this.extensions.filter((t=>t.commands)).reduce(((i,n)=>{const{name:s,type:o}=n,l={},r=n.commands({schema:t,utils:cn,...["node","mark"].includes(o)?{type:t[`${o}s`][s]}:{}}),a=(t,i)=>{l[t]=t=>{if("function"!=typeof i||!e.editable)return!1;e.focus();const n=i(t);return"function"==typeof n?n(e.state,e.dispatch,e):n}};if("object"==typeof r)for(const[t,e]of Object.entries(r))a(t,e);else a(s,r);return{...i,...l}}),{})}buttons(t="mark"){const e={};for(const i of this.extensions)if(i.type===t&&i.button)if(Array.isArray(i.button))for(const t of i.button)e[t.id??t.name]=t;else e[i.name]=i.button;return e}getAllowedExtensions(t){return t instanceof Array||!t?t instanceof Array?this.extensions.filter((e=>!t.includes(e.name))):this.extensions:[]}getFromExtensions(t,e,i=this.extensions){return i.filter((t=>["extension"].includes(t.type))).filter((e=>e[t])).map((i=>i[t]({...e,utils:cn})))}getFromNodesAndMarks(t,e,i=this.extensions){return i.filter((t=>["node","mark"].includes(t.type))).filter((e=>e[t])).map((i=>i[t]({...e,type:e.schema[`${i.type}s`][i.name],utils:cn})))}inputRules({schema:t,excludedExtensions:e}){const i=this.getAllowedExtensions(e);return[...this.getFromExtensions("inputRules",{schema:t},i),...this.getFromNodesAndMarks("inputRules",{schema:t},i)].reduce(((t,e)=>[...t,...e]),[])}keymaps({schema:t}){return[...this.getFromExtensions("keys",{schema:t}),...this.getFromNodesAndMarks("keys",{schema:t})].map((t=>v(t)))}get marks(){return this.extensions.filter((t=>"mark"===t.type)).reduce(((t,{name:e,schema:i})=>({...t,[e]:i})),{})}get nodes(){return this.extensions.filter((t=>"node"===t.type)).reduce(((t,{name:e,schema:i})=>({...t,[e]:i})),{})}get options(){const{view:t}=this;return this.extensions.reduce(((e,i)=>({...e,[i.name]:new Proxy(i.options,{set(e,i,n){const s=e[i]!==n;return Object.assign(e,{[i]:n}),s&&t.updateState(t.state),!0}})})),{})}pasteRules({schema:t,excludedExtensions:e}){const i=this.getAllowedExtensions(e);return[...this.getFromExtensions("pasteRules",{schema:t},i),...this.getFromNodesAndMarks("pasteRules",{schema:t},i)].reduce(((t,e)=>[...t,...e]),[])}plugins({schema:t}){return[...this.getFromExtensions("plugins",{schema:t}),...this.getFromNodesAndMarks("plugins",{schema:t})].reduce(((t,e)=>[...t,...e]),[]).map((t=>t instanceof i?t:new i(t)))}}class hn{constructor(t={}){this.options={...this.defaults,...t}}init(){return null}bindEditor(t=null){this.editor=t}get name(){return null}get type(){return"extension"}get defaults(){return{}}plugins(){return[]}inputRules(){return[]}pasteRules(){return[]}keys(){return{}}}class mn extends hn{constructor(t={}){super(t)}get type(){return"node"}get schema(){return{}}commands(){return{}}}class fn extends mn{get defaults(){return{inline:!1}}get name(){return"doc"}get schema(){return{content:this.options.inline?"inline*":"block+"}}}class gn extends mn{get button(){return{id:this.name,icon:"paragraph",label:window.panel.$t("toolbar.button.paragraph"),name:this.name,separator:!0}}commands({utils:t,schema:e,type:i}){return{paragraph:()=>this.editor.activeNodes.includes("bulletList")?t.toggleList(e.nodes.bulletList,e.nodes.listItem):this.editor.activeNodes.includes("orderedList")?t.toggleList(e.nodes.orderedList,e.nodes.listItem):this.editor.activeNodes.includes("quote")?t.toggleWrap(e.nodes.quote):t.setBlockType(i)}}get schema(){return{content:"inline*",group:"block",draggable:!1,parseDOM:[{tag:"p"}],toDOM:()=>["p",0]}}get name(){return"paragraph"}}let kn=class extends mn{get name(){return"text"}get schema(){return{group:"inline"}}};class bn extends dn{constructor(t={}){super(),this.defaults={autofocus:!1,content:"",disableInputRules:!1,disablePasteRules:!1,editable:!0,element:null,extensions:[],emptyDocument:{type:"doc",content:[]},events:{},inline:!1,parseOptions:{},topNode:"doc",useBuiltInExtensions:!0},this.init(t)}blur(){this.view.dom.blur()}get builtInExtensions(){return!0!==this.options.useBuiltInExtensions?[]:[new fn({inline:this.options.inline}),new kn,new gn]}buttons(t){return this.extensions.buttons(t)}clearContent(t=!1){this.setContent(this.options.emptyDocument,t)}command(t,...e){var i,n;null==(n=(i=this.commands)[t])||n.call(i,...e)}createCommands(){return this.extensions.commands({schema:this.schema,view:this.view})}createDocument(t,e=this.options.parseOptions){if(null===t)return this.schema.nodeFromJSON(this.options.emptyDocument);if("object"==typeof t)try{return this.schema.nodeFromJSON(t)}catch(i){return window.console.warn("Invalid content.","Passed value:",t,"Error:",i),this.schema.nodeFromJSON(this.options.emptyDocument)}if("string"==typeof t){const i=`

      ${t}
      `,n=(new window.DOMParser).parseFromString(i,"text/html").body.firstElementChild;return y.fromSchema(this.schema).parse(n,e)}return!1}createEvents(){const t=this.options.events??{};for(const[e,i]of Object.entries(t))this.on(e,i);return t}createExtensions(){return new pn([...this.builtInExtensions,...this.options.extensions],this)}createFocusEvents(){const t=(t,e,i=!0)=>{this.focused=i,this.emit(i?"focus":"blur",{event:e,state:t.state,view:t});const n=this.state.tr.setMeta("focused",i);this.view.dispatch(n)};return new i({props:{attributes:{tabindex:0},handleDOMEvents:{focus:(e,i)=>t(e,i,!0),blur:(e,i)=>t(e,i,!1)}}})}createInputRules(){return this.extensions.inputRules({schema:this.schema,excludedExtensions:this.options.disableInputRules})}createKeymaps(){return this.extensions.keymaps({schema:this.schema})}createMarks(){return this.extensions.marks}createNodes(){return this.extensions.nodes}createPasteRules(){return this.extensions.pasteRules({schema:this.schema,excludedExtensions:this.options.disablePasteRules})}createPlugins(){return this.extensions.plugins({schema:this.schema})}createSchema(){return new $({topNode:this.options.topNode,nodes:this.nodes,marks:this.marks})}createState(){return w.create({schema:this.schema,doc:this.createDocument(this.options.content),plugins:[...this.plugins,x({rules:this.inputRules}),...this.pasteRules,...this.keymaps,v({Backspace:O}),v(A),this.createFocusEvents()]})}createView(){return new _(this.element,{dispatchTransaction:this.dispatchTransaction.bind(this),attributes:{class:"k-text"},editable:()=>this.options.editable,handlePaste:(t,e)=>{if("function"==typeof this.events.paste){const t=e.clipboardData.getData("text/html"),i=e.clipboardData.getData("text/plain");if(!0===this.events.paste(e,t,i))return!0}},handleDrop:(...t)=>{this.emit("drop",...t)},state:this.createState()})}destroy(){this.view&&this.view.destroy()}dispatchTransaction(t){const e=this.state,i=this.state.apply(t);this.view.updateState(i),this.setActiveNodesAndMarks();const n={editor:this,getHTML:this.getHTML.bind(this),getJSON:this.getJSON.bind(this),state:this.state,transaction:t};this.emit("transaction",n),!t.docChanged&&t.getMeta("preventUpdate")||this.emit("update",n);const{from:s,to:o}=this.state.selection,l=!e||!e.selection.eq(i.selection);this.emit(i.selection.empty?"deselect":"select",{...n,from:s,hasChanged:l,to:o})}focus(t=null){if(this.view.focused&&null===t||!1===t)return;const{from:e,to:i}=this.selectionAtPosition(t);this.setSelection(e,i),setTimeout((()=>this.view.focus()),10)}getHTML(t=this.state.doc.content){const e=document.createElement("div"),i=S.fromSchema(this.schema).serializeFragment(t);return e.appendChild(i),this.options.inline&&e.querySelector("p")?e.querySelector("p").innerHTML:e.innerHTML}getHTMLStartToSelection(){const t=this.state.doc.slice(0,this.selection.head).content;return this.getHTML(t)}getHTMLSelectionToEnd(){const t=this.state.doc.slice(this.selection.head).content;return this.getHTML(t)}getHTMLStartToSelectionToEnd(){return[this.getHTMLStartToSelection(),this.getHTMLSelectionToEnd()]}getJSON(){return this.state.doc.toJSON()}getMarkAttrs(t=null){return this.activeMarkAttrs[t]}getSchemaJSON(){return JSON.parse(JSON.stringify({nodes:this.nodes,marks:this.marks}))}init(t={}){this.options={...this.defaults,...t},this.element=this.options.element,this.focused=!1,this.events=this.createEvents(),this.extensions=this.createExtensions(),this.nodes=this.createNodes(),this.marks=this.createMarks(),this.schema=this.createSchema(),this.keymaps=this.createKeymaps(),this.inputRules=this.createInputRules(),this.pasteRules=this.createPasteRules(),this.plugins=this.createPlugins(),this.view=this.createView(),this.commands=this.createCommands(),this.setActiveNodesAndMarks(),!1!==this.options.autofocus&&this.focus(this.options.autofocus),this.emit("init",{view:this.view,state:this.state}),this.extensions.view=this.view,this.setContent(this.options.content,!0)}insertText(t,e=!1){const{tr:i}=this.state,n=i.insertText(t);if(this.view.dispatch(n),e){const e=i.selection.from,n=e-t.length;this.setSelection(n,e)}}isEditable(){return this.options.editable}isEmpty(){if(this.state)return 0===this.state.doc.textContent.length}get isActive(){return Object.entries({...this.activeMarks,...this.activeNodes}).reduce(((t,[e,i])=>({...t,[e]:(t={})=>i(t)})),{})}removeMark(t){if(this.schema.marks[t])return cn.removeMark(this.schema.marks[t])(this.state,this.view.dispatch)}get selection(){return this.state.selection}get selectionAtEnd(){return C.atEnd(this.state.doc)}get selectionIsAtEnd(){return this.selection.head===this.selectionAtEnd.head}get selectionAtStart(){return C.atStart(this.state.doc)}get selectionIsAtStart(){return this.selection.head===this.selectionAtStart.head}selectionAtPosition(t=null){return null===t?this.selection:"start"===t||!0===t?this.selectionAtStart:"end"===t?this.selectionAtEnd:{from:t,to:t}}setActiveNodesAndMarks(){this.activeMarks=Object.values(this.schema.marks).filter((t=>cn.markIsActive(this.state,t))).map((t=>t.name)),this.activeMarkAttrs=Object.entries(this.schema.marks).reduce(((t,[e,i])=>({...t,[e]:cn.getMarkAttrs(this.state,i)})),{}),this.activeNodes=Object.values(this.schema.nodes).filter((t=>cn.nodeIsActive(this.state,t))).map((t=>t.name)),this.activeNodeAttrs=Object.entries(this.schema.nodes).reduce(((t,[e,i])=>({...t,[e]:cn.getNodeAttrs(this.state,i)})),{})}setContent(t={},e=!1,i){const{doc:n,tr:s}=this.state,o=this.createDocument(t,i),l=s.replaceWith(0,n.content.size,o).setMeta("preventUpdate",!e);this.view.dispatch(l)}setSelection(t=0,e=0){const{doc:i,tr:n}=this.state,s=cn.minMax(t,0,i.content.size),o=cn.minMax(e,0,i.content.size),l=C.create(i,s,o),r=n.setSelection(l);this.view.dispatch(r)}get state(){var t;return null==(t=this.view)?void 0:t.state}toggleMark(t){if(this.schema.marks[t])return cn.toggleMark(this.schema.marks[t])(this.state,this.view.dispatch)}updateMark(t,e){if(this.schema.marks[t])return cn.updateMark(this.schema.marks[t],e)(this.state,this.view.dispatch)}}class vn extends hn{command(){return()=>{}}remove(){this.editor.removeMark(this.name)}get schema(){return{}}get type(){return"mark"}toggle(){return this.editor.toggleMark(this.name)}update(t){this.editor.updateMark(this.name,t)}}class yn extends vn{get button(){return{icon:"bold",label:window.panel.$t("toolbar.button.bold")}}commands(){return()=>this.toggle()}inputRules({type:t,utils:e}){return[e.markInputRule(/(?:\*\*|__)([^*_]+)(?:\*\*|__)$/,t)]}keys(){return{"Mod-b":()=>this.toggle()}}get name(){return"bold"}pasteRules({type:t,utils:e}){return[e.markPasteRule(/(?:\*\*|__)([^*_]+)(?:\*\*|__)/g,t)]}get schema(){return{parseDOM:[{tag:"strong"},{tag:"b",getAttrs:t=>"normal"!==t.style.fontWeight&&null},{style:"font-weight",getAttrs:t=>/^(bold(er)?|[5-9]\d{2,})$/.test(t)&&null}],toDOM:()=>["strong",0]}}}class $n extends vn{get button(){return{icon:"clear",label:window.panel.$t("toolbar.button.clear")}}commands(){return()=>this.clear()}clear(){const{state:t}=this.editor,{from:e,to:i}=t.tr.selection;for(const n of this.editor.activeMarks){const s=t.schema.marks[n],o=this.editor.state.tr.removeMark(e,i,s);this.editor.view.dispatch(o)}}get name(){return"clear"}}let wn=class extends vn{get button(){return{icon:"code",label:window.panel.$t("toolbar.button.code")}}commands(){return()=>this.toggle()}inputRules({type:t,utils:e}){return[e.markInputRule(/(?:`)([^`]+)(?:`)$/,t)]}keys(){return{"Mod-`":()=>this.toggle()}}get name(){return"code"}pasteRules({type:t,utils:e}){return[e.markPasteRule(/(?:`)([^`]+)(?:`)/g,t)]}get schema(){return{excludes:"_",parseDOM:[{tag:"code"}],toDOM:()=>["code",0]}}};class xn extends vn{get button(){return{icon:"email",label:window.panel.$t("toolbar.button.email")}}commands(){return{email:t=>{if(t.altKey||t.metaKey)return this.remove();this.editor.emit("email",this.editor)},insertEmail:(t={})=>{const{selection:e}=this.editor.state;if(e.empty&&this.editor.insertText(t.href,!0),t.href)return this.update(t)},removeEmail:()=>this.remove(),toggleEmail:(t={})=>{var e;(null==(e=t.href)?void 0:e.length)>0?this.editor.command("insertEmail",t):this.editor.command("removeEmail")}}}get defaults(){return{target:null}}get name(){return"email"}pasteRules({type:t,utils:e}){return[e.pasteRule(/^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/gi,t,(t=>({href:t})))]}plugins(){return[{props:{handleClick:(t,e,i)=>{const n=this.editor.getMarkAttrs("email");n.href&&!0===i.altKey&&i.target instanceof HTMLAnchorElement&&(i.stopPropagation(),window.open(n.href))}}}]}get schema(){return{attrs:{href:{default:null},title:{default:null}},inclusive:!1,parseDOM:[{tag:"a[href^='mailto:']",getAttrs:t=>({href:t.getAttribute("href").replace("mailto:",""),title:t.getAttribute("title")})}],toDOM:t=>["a",{...t.attrs,href:"mailto:"+t.attrs.href},0]}}}class _n extends vn{get button(){return{icon:"italic",label:window.panel.$t("toolbar.button.italic")}}commands(){return()=>this.toggle()}inputRules({type:t,utils:e}){return[e.markInputRule(/(?:^|\s)((?:\*)((?:[^*]+))(?:\*))$/,t),e.markInputRule(/(?:^|\s)((?:_)((?:[^_]+))(?:_))$/,t)]}keys(){return{"Mod-i":()=>this.toggle()}}get name(){return"italic"}pasteRules({type:t,utils:e}){return[e.markPasteRule(/_([^_]+)_/g,t),e.markPasteRule(/\*([^*]+)\*/g,t)]}get schema(){return{parseDOM:[{tag:"i"},{tag:"em"},{style:"font-style=italic"}],toDOM:()=>["em",0]}}}let Sn=class extends vn{get button(){return{icon:"url",label:window.panel.$t("toolbar.button.link")}}commands(){return{link:t=>{if(t.altKey||t.metaKey)return this.remove();this.editor.emit("link",this.editor)},insertLink:(t={})=>{const{selection:e}=this.editor.state;if(e.empty&&!1===this.editor.activeMarks.includes("link")&&this.editor.insertText(t.href,!0),t.href)return this.update(t)},removeLink:()=>this.remove(),toggleLink:(t={})=>{var e;(null==(e=t.href)?void 0:e.length)>0?this.editor.command("insertLink",t):this.editor.command("removeLink")}}}get defaults(){return{target:null}}get name(){return"link"}pasteRules({type:t,utils:e}){return[e.pasteRule(/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z]{2,}\b([-a-zA-Z0-9@:%_+.~#?&//=,]*)/gi,t,(t=>({href:t})))]}plugins(){return[{props:{handleClick:(t,e,i)=>{const n=this.editor.getMarkAttrs("link");n.href&&!0===i.altKey&&i.target instanceof HTMLAnchorElement&&(i.stopPropagation(),window.open(n.href,n.target))}}}]}get schema(){return{attrs:{href:{default:null},target:{default:null},title:{default:null}},inclusive:!1,parseDOM:[{tag:"a[href]:not([href^='mailto:'])",getAttrs:t=>({href:t.getAttribute("href"),target:t.getAttribute("target"),title:t.getAttribute("title")})}],toDOM:t=>["a",{...t.attrs},0]}}};class Cn extends vn{get button(){return{icon:"strikethrough",label:window.panel.$t("toolbar.button.strike")}}commands(){return()=>this.toggle()}inputRules({type:t,utils:e}){return[e.markInputRule(/~([^~]+)~$/,t)]}keys(){return{"Mod-d":()=>this.toggle()}}get name(){return"strike"}pasteRules({type:t,utils:e}){return[e.markPasteRule(/~([^~]+)~/g,t)]}get schema(){return{parseDOM:[{tag:"s"},{tag:"del"},{tag:"strike"},{style:"text-decoration",getAttrs:t=>"line-through"===t}],toDOM:()=>["s",0]}}}let On=class extends vn{get button(){return{icon:"superscript",label:window.panel.$t("toolbar.button.sup")}}commands(){return()=>this.toggle()}get name(){return"sup"}get schema(){return{parseDOM:[{tag:"sup"}],toDOM:()=>["sup",0]}}};class An extends vn{get button(){return{icon:"subscript",label:window.panel.$t("toolbar.button.sub")}}commands(){return()=>this.toggle()}get name(){return"sub"}get schema(){return{parseDOM:[{tag:"sub"}],toDOM:()=>["sub",0]}}}class Mn extends vn{get button(){return{icon:"underline",label:window.panel.$t("toolbar.button.underline")}}commands(){return()=>this.toggle()}keys(){return{"Mod-u":()=>this.toggle()}}get name(){return"underline"}get schema(){return{parseDOM:[{tag:"u"},{style:"text-decoration",getAttrs:t=>"underline"===t}],toDOM:()=>["u",0]}}}class jn extends mn{get button(){return{id:this.name,icon:"list-bullet",label:window.panel.$t("toolbar.button.ul"),name:this.name,when:["listItem","bulletList","orderedList","paragraph"]}}commands({type:t,schema:e,utils:i}){return()=>i.toggleList(t,e.nodes.listItem)}inputRules({type:t,utils:e}){return[e.wrappingInputRule(/^\s*([-+*])\s$/,t)]}keys({type:t,schema:e,utils:i}){return{"Shift-Ctrl-8":i.toggleList(t,e.nodes.listItem)}}get name(){return"bulletList"}get schema(){return{content:"listItem+",group:"block",parseDOM:[{tag:"ul"}],toDOM:()=>["ul",0]}}}class Tn extends mn{commands({utils:t,type:e}){return()=>this.createHardBreak(t,e)}createHardBreak(t,e){return t.chainCommands(t.exitCode,((t,i)=>(i(t.tr.replaceSelectionWith(e.create()).scrollIntoView()),!0)))}get defaults(){return{enter:!1,text:!1}}keys({utils:t,type:e}){const i=this.createHardBreak(t,e);let n={"Mod-Enter":i,"Shift-Enter":i};return this.options.enter&&(n.Enter=i),n}get name(){return"hardBreak"}get schema(){return{inline:!0,group:"inline",selectable:!1,parseDOM:[{tag:"br"}],toDOM:()=>["br"]}}}class In extends mn{get button(){const t=this.options.levels.map((t=>({id:`h${t}`,command:`h${t}`,icon:`h${t}`,label:window.panel.$t("toolbar.button.heading."+t),attrs:{level:t},name:this.name,when:["heading","paragraph"]})));return t[t.length-1].separator=!0,t}commands({type:t,schema:e,utils:i}){let n={toggleHeading:n=>i.toggleBlockType(t,e.nodes.paragraph,n)};for(const s of this.options.levels)n[`h${s}`]=()=>i.toggleBlockType(t,e.nodes.paragraph,{level:s});return n}get defaults(){return{levels:[1,2,3,4,5,6]}}inputRules({type:t,utils:e}){return this.options.levels.map((i=>e.textblockTypeInputRule(new RegExp(`^(#{1,${i}})\\s$`),t,(()=>({level:i})))))}keys({type:t,utils:e}){return this.options.levels.reduce(((i,n)=>({...i,[`Shift-Ctrl-${n}`]:e.setBlockType(t,{level:n})})),{})}get name(){return"heading"}get schema(){return{attrs:{level:{default:1}},content:"inline*",group:"block",defining:!0,draggable:!1,parseDOM:this.options.levels.map((t=>({tag:`h${t}`,attrs:{level:t}}))),toDOM:t=>[`h${t.attrs.level}`,0]}}}class En extends mn{commands({type:t}){return()=>(e,i)=>i(e.tr.replaceSelectionWith(t.create()))}inputRules({type:t,utils:e}){return[e.nodeInputRule(/^(?:---|___\s|\*\*\*\s)$/,t)]}get name(){return"horizontalRule"}get schema(){return{group:"block",parseDOM:[{tag:"hr"}],toDOM:()=>["hr"]}}}class Ln extends mn{keys({type:t,utils:e}){return{Enter:e.splitListItem(t),"Shift-Tab":e.liftListItem(t),Tab:e.sinkListItem(t)}}get name(){return"listItem"}get schema(){return{content:"paragraph block*",defining:!0,draggable:!1,parseDOM:[{tag:"li"}],toDOM:()=>["li",0]}}}class Dn extends mn{get button(){return{id:this.name,icon:"list-numbers",label:window.panel.$t("toolbar.button.ol"),name:this.name,when:["listItem","bulletList","orderedList","paragraph"],separator:!0}}commands({type:t,schema:e,utils:i}){return()=>i.toggleList(t,e.nodes.listItem)}inputRules({type:t,utils:e}){return[e.wrappingInputRule(/^(\d+)\.\s$/,t,(t=>({order:+t[1]})),((t,e)=>e.childCount+e.attrs.order===+t[1]))]}keys({type:t,schema:e,utils:i}){return{"Shift-Ctrl-9":i.toggleList(t,e.nodes.listItem)}}get name(){return"orderedList"}get schema(){return{attrs:{order:{default:1}},content:"listItem+",group:"block",parseDOM:[{tag:"ol",getAttrs:t=>({order:t.hasAttribute("start")?+t.getAttribute("start"):1})}],toDOM:t=>1===t.attrs.order?["ol",0]:["ol",{start:t.attrs.order},0]}}}class Bn extends mn{get button(){return{id:this.name,icon:"quote",label:window.panel.$t("field.blocks.quote.name"),name:this.name}}commands({type:t,utils:e}){return()=>e.toggleWrap(t)}inputRules({type:t,utils:e}){return[e.wrappingInputRule(/^\s*>\s$/,t)]}keys({utils:t}){return{"Shift-Tab":(e,i)=>t.lift(e,i)}}get name(){return"quote"}get schema(){return{content:"block+",group:"block",defining:!0,draggable:!1,parseDOM:[{tag:"blockquote"}],toDOM:()=>["blockquote",0]}}}let qn=class extends hn{commands(){return{undo:()=>M,redo:()=>j,undoDepth:()=>T,redoDepth:()=>I}}get defaults(){return{depth:"",newGroupDelay:""}}keys(){return{"Mod-z":M,"Mod-y":j,"Shift-Mod-z":j,"Mod-я":M,"Shift-Mod-я":j}}get name(){return"history"}plugins(){return[E({depth:this.options.depth,newGroupDelay:this.options.newGroupDelay})]}};class Pn extends hn{commands(){return{insertHtml:t=>(e,i)=>{let n=document.createElement("div");n.innerHTML=t.trim();const s=y.fromSchema(e.schema).parse(n);i(e.tr.replaceSelectionWith(s).scrollIntoView())}}}}class Nn extends hn{keys(){const t={};for(const e in this.options)t[e]=()=>(this.options[e](),!0);return t}}let zn=class extends hn{constructor(t){super(),this.writer=t}get component(){return this.writer.$refs.toolbar}init(){this.editor.on("deselect",(({event:t})=>{var e;return null==(e=this.component)?void 0:e.close(t)})),this.editor.on("select",(({hasChanged:t})=>{var e;!1!==t&&(null==(e=this.component)||e.open())}))}get type(){return"toolbar"}};const Fn={mixins:[V,W,lt,at],props:{breaks:Boolean,code:Boolean,emptyDocument:{type:Object,default:()=>({type:"doc",content:[]})},extensions:Array,headings:{default:()=>[1,2,3,4,5,6],type:[Array,Boolean]},inline:Boolean,keys:Object,marks:{type:[Array,Boolean],default:!0},nodes:{type:[Array,Boolean],default:()=>["heading","bulletList","orderedList"]},paste:{type:Function,default:()=>()=>!1},toolbar:{type:Object,default:()=>({inline:!0})},value:{type:String,default:""}}};const Rn=ut({mixins:[Fn],data(){return{editor:null,json:{},html:this.value,isEmpty:!0}},computed:{isCursorAtEnd(){return this.editor.selectionIsAtEnd},isCursorAtStart(){return this.editor.selectionIsAtStart},toolbarOptions(){return{marks:Array.isArray(this.marks)?this.marks:void 0,...this.toolbar,editor:this.editor}}},watch:{value(t,e){t!==e&&t!==this.html&&(this.html=t,this.editor.setContent(this.html))}},mounted(){this.editor=new bn({autofocus:this.autofocus,content:this.value,editable:!this.disabled,element:this.$el,emptyDocument:this.emptyDocument,parseOptions:{preserveWhitespace:!0},events:{link:t=>{this.$panel.dialog.open({component:"k-link-dialog",props:{value:t.getMarkAttrs("link")},on:{cancel:()=>t.focus(),submit:e=>{this.$panel.dialog.close(),t.command("toggleLink",e)}}})},email:t=>{this.$panel.dialog.open({component:"k-email-dialog",props:{value:this.editor.getMarkAttrs("email")},on:{cancel:()=>t.focus(),submit:e=>{this.$panel.dialog.close(),t.command("toggleEmail",e)}}})},paste:this.paste,update:t=>{if(!this.editor)return;const e=JSON.stringify(this.editor.getJSON());e!==JSON.stringify(this.json)&&(this.json=e,this.isEmpty=t.editor.isEmpty(),this.html=t.editor.getHTML(),this.isEmpty&&(0===t.editor.activeNodes.length||t.editor.activeNodes.includes("paragraph"))&&(this.html=""),this.$emit("input",this.html))}},extensions:[...this.createMarks(),...this.createNodes(),new Nn(this.keys),new qn,new Pn,new zn(this),...this.extensions||[]],inline:this.inline}),this.isEmpty=this.editor.isEmpty(),this.json=this.editor.getJSON(),this.$panel.events.on("click",this.onBlur),this.$panel.events.on("focus",this.onBlur)},beforeDestroy(){this.editor.destroy(),this.$panel.events.off("click",this.onBlur),this.$panel.events.off("focus",this.onBlur)},methods:{command(t,...e){this.editor.command(t,...e)},createMarks(){return this.filterExtensions({clear:new $n,code:new wn,underline:new Mn,strike:new Cn,link:new Sn,email:new xn,bold:new yn,italic:new _n,sup:new On,sub:new An,...this.createMarksFromPanelPlugins()},this.marks)},createMarksFromPanelPlugins(){const t=window.panel.plugins.writerMarks??{},e={};for(const i in t)e[i]=Object.create(vn.prototype,Object.getOwnPropertyDescriptors({name:i,...t[i]}));return e},createNodes(){const t=new Tn({text:!0,enter:this.inline});return!0===this.inline?[t]:this.filterExtensions({bulletList:new jn,orderedList:new Dn,heading:new In({levels:this.headings}),horizontalRule:new En,listItem:new Ln,quote:new Bn,...this.createNodesFromPanelPlugins()},this.nodes,((e,i)=>((e.includes("bulletList")||e.includes("orderedList"))&&i.push(new Ln),i.push(t),i)))},createNodesFromPanelPlugins(){const t=window.panel.plugins.writerNodes??{},e={};for(const i in t)e[i]=Object.create(mn.prototype,Object.getOwnPropertyDescriptors({name:i,...t[i]}));return e},getHTML(){return this.editor.getHTML()},filterExtensions(t,e,i){!1===e?e=[]:!0!==e&&!1!==Array.isArray(e)||(e=Object.keys(t));let n=[];for(const s in t)e.includes(s)&&n.push(t[s]);return"function"==typeof i&&(n=i(e,n)),n},focus(){this.editor.focus()},getSplitContent(){return this.editor.getHTMLStartToSelectionToEnd()},onBlur(t){var e;!1===this.$el.contains(t.target)&&(null==(e=this.$refs.toolbar)||e.close())},onCommand(t,...e){this.editor.command(t,...e)}}},(function(){var t=this,e=t._self._c;return e("div",{directives:[{name:"direction",rawName:"v-direction"}],ref:"editor",staticClass:"k-writer",attrs:{"data-disabled":t.disabled,"data-empty":t.isEmpty,"data-placeholder":t.placeholder,"data-toolbar-inline":Boolean(t.toolbar.inline),spellcheck:t.spellcheck}},[t.editor&&!t.disabled?e("k-writer-toolbar",t._b({ref:"toolbar",on:{command:t.onCommand}},"k-writer-toolbar",t.toolbarOptions,!1)):t._e()],1)}),[],!1,null,null,null,null).exports,Yn={mixins:[Te,Fn,et,it],computed:{counterValue(){const t=this.$helper.string.stripHTML(this.value);return this.$helper.string.unescapeHTML(t)}}};const Un=ut({mixins:[Ie,Yn],watch:{value(){this.onInvalid()}},mounted(){this.onInvalid(),this.$props.autofocus&&this.focus()},methods:{focus(){this.$refs.input.focus()},onInvalid(){this.$emit("invalid",this.$v.$invalid,this.$v)}},validations(){return{counterValue:{required:!this.required||t.required,minLength:!this.minlength||t.minLength(this.minlength),maxLength:!this.maxlength||t.maxLength(this.maxlength)}}}},(function(){var t=this;return(0,t._self._c)("k-writer",t._b({ref:"input",staticClass:"k-writer-input",on:{input:function(e){return t.$emit("input",e)}}},"k-writer",t.$props,!1))}),[],!1,null,null,null,null).exports;class Hn extends fn{get schema(){return{content:this.options.nodes.join("|")}}}const Vn={mixins:[Yn],inheritAttrs:!1,props:{nodes:{type:Array,default:()=>["bulletList","orderedList"]}}};const Kn=ut({mixins:[Ie,Vn],data(){return{list:this.value,html:this.value}},computed:{listExtensions(){return[new Hn({inline:!0,nodes:this.nodes})]}},watch:{value(t){t!==this.html&&(this.list=t,this.html=t)}},methods:{focus(){this.$refs.input.focus()},onInput(t){let e=(new DOMParser).parseFromString(t,"text/html").querySelector("ul, ol");e&&0!==e.textContent.trim().length?(this.list=t,this.html=t.replace(/(

      |<\/p>)/gi,""),this.$emit("input",this.html)):this.$emit("input",this.list="")}}},(function(){var t=this;return(0,t._self._c)("k-writer",t._b({ref:"input",staticClass:"k-list-input",attrs:{extensions:t.listExtensions,value:t.list},on:{input:t.onInput}},"k-writer",t.$props,!1))}),[],!1,null,null,null,null).exports;const Wn=ut({mixins:[Re,He,Vn],inheritAttrs:!1,methods:{focus(){this.$refs.input.focus()}}},(function(){var t=this,e=t._self._c;return e("k-field",t._b({staticClass:"k-list-field",attrs:{input:t._uid,counter:!1}},"k-field",t.$props,!1),[e("k-input",t._b({ref:"input",attrs:{id:t._uid,type:"list",theme:"field"},on:{input:function(e){return t.$emit("input",e)}}},"k-input",t.$props,!1))],1)}),[],!1,null,null,null,null).exports,Jn={mixins:[W,X,st],inheritAttrs:!1,props:{layout:String,sort:{default:!1,type:Boolean},value:{default:()=>[],type:Array}}};const Gn=ut({mixins:[Jn],props:{draggable:{default:!0,type:Boolean}},data:()=>({tags:[]}),computed:{dragOptions(){return{delay:1,disabled:!this.isDraggable,draggable:".k-tag",handle:".k-tag-text"}},isDraggable(){return!0!==this.sort&&!1!==this.draggable&&0!==this.tags.length&&!0!==this.disabled}},watch:{value:{handler(){let t=this.$helper.object.clone(this.value);if(!0===this.sort){const e=[];for(const i of this.options){const n=t.indexOf(i.value);-1!==n&&(e.push(i),t.splice(n,1))}e.push(...t),t=e}this.tags=t.map(this.tag).filter((t=>t))},immediate:!0}},methods:{edit(t,e,i){!1===this.disabled&&this.$emit("edit",t,e,i)},focus(t="last"){this.$refs.navigate.move(t)},index(t){return this.tags.findIndex((e=>e.value===t.value))},input(){this.$emit("input",this.tags.map((t=>t.value)))},navigate(t){this.focus(t)},remove(t){this.tags.length<=1?this.navigate("last"):this.navigate("prev"),this.tags.splice(t,1),this.input()},option(t){return this.options.find((e=>e.value===t.value))},select(){this.focus()},tag(t){"object"!=typeof t&&(t={value:t});const e=this.option(t);return e||{text:this.$helper.string.escapeHTML(t.text??t.value),value:t.value}}}},(function(){var t=this,e=t._self._c;return e("k-navigate",{ref:"navigate",attrs:{axis:"list"===t.layout?"y":"x",select:":where(.k-tag, .k-tags-navigatable):not(:disabled)"}},[e("k-draggable",{staticClass:"k-tags",attrs:{list:t.tags,options:t.dragOptions,"data-layout":t.layout},on:{end:t.input},scopedSlots:t._u([{key:"footer",fn:function(){return[t._t("default")]},proxy:!0}],null,!0)},t._l(t.tags,(function(i,n){return e("k-tag",{key:n,attrs:{disabled:t.disabled,image:i.image,removable:!t.disabled,name:"tag"},on:{remove:function(e){return t.remove(n,i)}},nativeOn:{click:function(t){t.stopPropagation()},keypress:function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"enter",13,e.key,"Enter")?null:t.edit(n,i,e)},dblclick:function(e){return t.edit(n,i,e)}}},[e("span",{domProps:{innerHTML:t._s(i.text)}})])})),1)],1)}),[],!1,null,null,null,null).exports,Xn={mixins:[nt,rt,Jn,Ee],props:{value:{default:()=>[],type:Array}},watch:{value:{handler(){this.$emit("invalid",this.$v.$invalid,this.$v)},immediate:!0}},validations(){return{value:{required:!this.required||t.required,minLength:!this.min||t.minLength(this.min),maxLength:!this.max||t.maxLength(this.max)}}},methods:{open(){this.$refs.dropdown.open(this.$el)}}};const Zn=ut({mixins:[Ie,Xn]},(function(){var t=this,e=t._self._c;return e("div",{staticClass:"k-multiselect-input"},[e("k-tags",t._b({ref:"tags",on:{input:function(e){return t.$emit("input",e)}},nativeOn:{click:function(e){return e.stopPropagation(),t.open.apply(null,arguments)}}},"k-tags",t.$props,!1),[!t.max||t.value.length({editing:null}),computed:{creatableOptions(){return this.options.filter((t=>!1===this.value.includes(t.value)))},picklist(){return{disabled:this.disabled,create:this.showCreate,ignore:this.ignore,min:this.min,max:this.max,search:this.showSearch}},replacableOptions(){return this.options.filter((t=>{var e;return!1===this.value.includes(t.value)||t.value===(null==(e=this.editing)?void 0:e.tag.value)}))},showCreate(){return"options"!==this.accept&&(!this.editing||{submit:this.$t("replace.with")})},showSearch(){return!1!==this.search&&(this.editing?{placeholder:this.$t("replace.with"),...this.search}:"options"===this.accept?{placeholder:this.$t("filter"),...this.search}:this.search)}},methods:{create(t){const e=this.$refs.tags.tag(t);if(!0===this.isAllowed(e)){const t=this.$helper.object.clone(this.value);t.push(e.value),this.$emit("input",t)}this.$refs.create.close()},async edit(t,e){this.editing={index:t,tag:e},this.$refs.replace.open()},focus(){this.$refs.create.open()},isAllowed(t){return"object"==typeof t&&0!==t.value.trim().length&&(!("options"===this.accept&&!this.$refs.tags.option(t))&&!0!==this.value.includes(t.value))},pick(t){this.$emit("input",t),this.$refs.create.close()},replace(t){const{index:e}=this.editing,i=this.$refs.tags.tag(t);if(this.$refs.replace.close(),this.editing=null,!1===this.isAllowed(i))return!1;const n=this.$helper.object.clone(this.value);n.splice(e,1,i.value),this.$emit("input",n),this.$refs.tags.navigate(e)},toggle(t){return!(t.metaKey||t.altKey||t.ctrlKey)&&("ArrowDown"===t.key?(this.$refs.create.open(),void t.preventDefault()):void(String.fromCharCode(t.keyCode).match(/(\w)/g)&&this.$refs.create.open()))}}},(function(){var t,e=this,i=e._self._c;return i("div",{staticClass:"k-tags-input"},[i("k-tags",e._b({ref:"tags",on:{edit:e.edit,input:function(t){return e.$emit("input",t)}},nativeOn:{click:function(t){var i,n;t.stopPropagation(),null==(n=null==(i=e.$refs.toggle)?void 0:i.$el)||n.click()}}},"k-tags",e.$props,!1),[!e.max||e.value.lengththis.onInput(t.target.value),blur:this.onBlur}}},watch:{value(t){this.number=t},number:{immediate:!0,handler(){this.onInvalid()}}},mounted(){this.$props.autofocus&&this.focus(),this.$props.preselect&&this.select()},methods:{decimals(){const t=Number(this.step??0);return Math.floor(t)===t?0:-1!==t.toString().indexOf("e")?parseInt(t.toFixed(16).split(".")[1].split("").reverse().join("")).toString().length:t.toString().split(".")[1].length??0},format(t){if(isNaN(t)||""===t)return"";const e=this.decimals();return t=e?parseFloat(t).toFixed(e):Number.isInteger(this.step)?parseInt(t):parseFloat(t)},clean(){this.number=this.format(this.number)},emit(t){t=parseFloat(t),isNaN(t)&&(t=""),t!==this.value&&this.$emit("input",t)},onInvalid(){this.$emit("invalid",this.$v.$invalid,this.$v)},onInput(t){this.number=t,this.emit(t)},onBlur(){this.clean(),this.emit(this.number)},select(){this.$refs.input.select()}},validations(){return{value:{required:!this.required||t.required,min:!this.min||t.minValue(this.min),max:!this.max||t.maxValue(this.max)}}}},(function(){var t=this;return(0,t._self._c)("input",t._g(t._b({ref:"input",staticClass:"k-number-input",attrs:{step:t.stepNumber,type:"number"},domProps:{value:t.number},on:{keydown:[function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"s",void 0,e.key,void 0)?null:e.ctrlKey?t.clean.apply(null,arguments):null},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"s",void 0,e.key,void 0)?null:e.metaKey?t.clean.apply(null,arguments):null}]}},"input",{autofocus:t.autofocus,disabled:t.disabled,id:t.id,max:t.max,min:t.min,name:t.name,placeholder:t.placeholder,required:t.required},!1),t.listeners))}),[],!1,null,null,null,null).exports;const os=ut({mixins:[Re,He,ns],inheritAttrs:!1,methods:{focus(){this.$refs.input.focus()}}},(function(){var t=this,e=t._self._c;return e("k-field",t._b({staticClass:"k-number-field",attrs:{input:t._uid}},"k-field",t.$props,!1),[e("k-input",t._g(t._b({ref:"input",attrs:{id:t._uid,theme:"field",type:"number"}},"k-input",t.$props,!1),t.$listeners))],1)}),[],!1,null,null,null,null).exports;const ls=ut({mixins:[Re,He],props:{empty:String,fields:[Object,Array],value:[String,Object]},data:()=>({object:{}}),computed:{hasFields(){return this.$helper.object.length(this.fields)>0},isEmpty(){return null===this.object||0===this.$helper.object.length(this.object)},isInvalid(){return!0===this.required&&this.isEmpty}},watch:{value:{handler(t){this.object=this.valueToObject(t)},immediate:!0}},methods:{add(){this.object=this.$helper.field.form(this.fields),this.save(),this.open()},cell(t,e){this.$set(this.object,t,e),this.save()},form(t){const e=this.$helper.field.subfields(this,this.fields);if(t)for(const i in e)e[i].autofocus=i===t;return e},remove(){this.object={},this.save()},open(t){if(this.disabled)return!1;this.$panel.drawer.open({component:"k-form-drawer",props:{breadcrumb:[],icon:"box",tab:"object",tabs:{object:{fields:this.form(t)}},title:this.label,value:this.object},on:{input:t=>{for(const e in t)this.$set(this.object,e,t[e]);this.save()}}})},save(){this.$emit("input",this.object)},valueToObject:t=>"object"!=typeof t?{}:t}},(function(){var t=this,e=t._self._c;return e("k-field",t._b({staticClass:"k-object-field",scopedSlots:t._u([!t.disabled&&t.hasFields?{key:"options",fn:function(){return[t.isEmpty?e("k-button",{attrs:{icon:"add",size:"xs",variant:"filled"},on:{click:t.add}}):e("k-button",{attrs:{icon:"remove",size:"xs",variant:"filled"},on:{click:t.remove}})]},proxy:!0}:null],null,!0)},"k-field",t.$props,!1),[t.hasFields?[t.isEmpty?e("k-empty",{attrs:{"data-invalid":t.isInvalid,icon:"box"},on:{click:t.add}},[t._v(" "+t._s(t.empty??t.$t("field.object.empty"))+" ")]):e("table",{staticClass:"k-table k-object-field-table",attrs:{"aria-disabled":t.disabled,"data-invalid":t.isInvalid}},[e("tbody",[t._l(t.fields,(function(i){return[i.saveable&&t.$helper.field.isVisible(i,t.value)?e("tr",{key:i.name,on:{click:function(e){return t.open(i.name)}}},[e("th",{attrs:{"data-has-button":"","data-mobile":"true"}},[e("button",{attrs:{type:"button"}},[t._v(t._s(i.label))])]),e("k-table-cell",{attrs:{column:i,field:i,mobile:!0,value:t.object[i.name]},on:{input:function(e){return t.cell(i.name,e)}}})],1):t._e()]}))],2)])]:[e("k-empty",{attrs:{icon:"box"}},[t._v(t._s(t.$t("fields.empty")))])]],2)}),[],!1,null,null,null,null).exports;const rs=ut({extends:Gi,type:"pages",computed:{emptyProps(){return{icon:"page",text:this.empty??this.$t("field.pages.empty")}}}},null,null,!1,null,null,null,null).exports,as={mixins:[Hi],props:{autocomplete:{type:String,default:"new-password"},type:{type:String,default:"password"}}};const us=ut({extends:Vi,mixins:[as]},null,null,!1,null,null,null,null).exports;const cs=ut({mixins:[Re,He,as,Di],inheritAttrs:!1,props:{minlength:{type:Number,default:8},icon:{type:String,default:"key"}},methods:{focus(){this.$refs.input.focus()}}},(function(){var t=this,e=t._self._c;return e("k-field",t._b({staticClass:"k-password-field",attrs:{input:t._uid,counter:t.counterOptions},scopedSlots:t._u([{key:"options",fn:function(){return[t._t("options")]},proxy:!0}],null,!0)},"k-field",t.$props,!1),[e("k-input",t._g(t._b({ref:"input",attrs:{id:t._uid,theme:"field",type:"password"}},"k-input",t.$props,!1),t.$listeners))],1)}),[],!1,null,null,null,null).exports,ds={mixins:[Te,st],props:{columns:Number,reset:{default:!0,type:Boolean},theme:String,value:[String,Number,Boolean]}};const ps=ut({mixins:[Ie,ds],computed:{choices(){return this.options.map(((t,e)=>({autofocus:this.autofocus&&0===e,checked:this.value===t.value,disabled:this.disabled||t.disabled,info:t.info,label:t.text,name:this.name??this.id,type:"radio",value:t.value})))}},watch:{value:{handler(){this.validate()},immediate:!0}},methods:{focus(){var t;null==(t=this.$el.querySelector("input"))||t.focus()},select(){this.focus()},toggle(t){t===this.value&&this.reset&&!this.required&&this.$emit("input","")},validate(){this.$emit("invalid",this.$v.$invalid,this.$v)}},validations(){return{value:{required:!this.required||t.required}}}},(function(){var t=this,e=t._self._c;return e("ul",{staticClass:"k-radio-input k-grid",style:{"--columns":t.columns},attrs:{"data-variant":"choices"}},t._l(t.choices,(function(i,n){return e("li",{key:n},[e("k-choice-input",t._b({on:{input:function(e){return t.$emit("input",i.value)}},nativeOn:{click:function(e){return e.stopPropagation(),t.toggle(i.value)}}},"k-choice-input",i,!1))],1)})),0)}),[],!1,null,null,null,null).exports;const hs=ut({mixins:[Re,He,ds],inheritAttrs:!1,methods:{focus(){this.$refs.input.focus()}}},(function(){var t,e=this,i=e._self._c;return i("k-field",e._b({staticClass:"k-radio-field"},"k-field",e.$props,!1),[(null==(t=e.options)?void 0:t.length)?i("k-radio-input",e._g(e._b({ref:"input",attrs:{id:e._uid,theme:"field"}},"k-radio-input",e.$props,!1),e.$listeners)):i("k-empty",{attrs:{text:e.$t("options.none"),icon:"checklist"}})],1)}),[],!1,null,null,null,null).exports,ms={mixins:[Te],props:{default:[Number,String],max:{type:Number,default:100},min:{type:Number,default:0},step:{type:[Number,String],default:1},tooltip:{type:[Boolean,Object],default:()=>({before:null,after:null})},value:[Number,String]}};const fs=ut({mixins:[Ie,ms],computed:{baseline(){return this.min<0?0:this.min},label(){return this.required||this.value||0===this.value?this.format(this.position):"–"},position(){return this.value||0===this.value?this.value:this.default??this.baseline}},watch:{position(){this.onInvalid()}},mounted(){this.onInvalid(),this.$props.autofocus&&this.focus()},methods:{focus(){var t;null==(t=this.$el.querySelector("input"))||t.focus()},format(t){const e=document.lang?document.lang.replace("_","-"):"en",i=this.step.toString().split("."),n=i.length>1?i[1].length:0;return new Intl.NumberFormat(e,{minimumFractionDigits:n}).format(t)},onInvalid(){this.$emit("invalid",this.$v.$invalid,this.$v)},onInput(t){this.$emit("input",t)}},validations(){return{position:{required:!this.required||t.required,min:!this.min||t.minValue(this.min),max:!this.max||t.maxValue(this.max)}}}},(function(){var t=this,e=t._self._c;return e("div",{staticClass:"k-range-input",attrs:{"data-disabled":t.disabled}},[e("input",t._b({ref:"range",attrs:{type:"range"},domProps:{value:t.position},on:{input:function(e){return t.$emit("input",e.target.valueAsNumber)}}},"input",{autofocus:t.autofocus,disabled:t.disabled,id:t.id,max:t.max,min:t.min,name:t.name,required:t.required,step:t.step},!1)),t.tooltip?e("output",{staticClass:"k-range-input-tooltip",attrs:{for:t.id}},[t.tooltip.before?e("span",{staticClass:"k-range-input-tooltip-before"},[t._v(t._s(t.tooltip.before))]):t._e(),e("span",{staticClass:"k-range-input-tooltip-text"},[t._v(t._s(t.label))]),t.tooltip.after?e("span",{staticClass:"k-range-input-tooltip-after"},[t._v(t._s(t.tooltip.after))]):t._e()]):t._e()])}),[],!1,null,null,null,null).exports;const gs=ut({mixins:[He,Re,ms],inheritAttrs:!1,methods:{focus(){this.$refs.input.focus()}}},(function(){var t=this,e=t._self._c;return e("k-field",t._b({staticClass:"k-range-field",attrs:{input:t._uid}},"k-field",t.$props,!1),[e("k-input",t._g(t._b({ref:"input",attrs:{id:t._uid,theme:"field",type:"range"}},"k-input",t.$props,!1),t.$listeners))],1)}),[],!1,null,null,null,null).exports,ks={mixins:[Te,st,lt],props:{ariaLabel:String,default:String,empty:{type:[Boolean,String],default:!0},value:{type:[String,Number,Boolean],default:""}}};const bs=ut({mixins:[Ie,ks],data(){return{selected:this.value,listeners:{...this.$listeners,click:t=>this.onClick(t),change:t=>this.onInput(t.target.value),input:()=>{}}}},computed:{emptyOption(){return this.placeholder??"—"},hasEmptyOption(){return!1!==this.empty&&!(this.required&&this.default)},isEmpty(){return null===this.selected||void 0===this.selected||""===this.selected},label(){const t=this.text(this.selected);return""===this.selected||null===this.selected||null===t?this.emptyOption:t}},watch:{value(t){this.selected=t,this.onInvalid()}},mounted(){this.onInvalid(),this.$props.autofocus&&this.focus()},methods:{focus(){this.$refs.input.focus()},onClick(t){t.stopPropagation(),this.$emit("click",t)},onInvalid(){this.$emit("invalid",this.$v.$invalid,this.$v)},onInput(t){this.selected=t,this.$emit("input",this.selected)},select(){this.focus()},text(t){let e=null;for(const i of this.options)i.value==t&&(e=i.text);return e}},validations(){return{selected:{required:!this.required||t.required}}}},(function(){var t=this,e=t._self._c;return e("span",{staticClass:"k-select-input",attrs:{"data-disabled":t.disabled,"data-empty":t.isEmpty}},[e("select",t._g({ref:"input",staticClass:"k-select-input-native",attrs:{id:t.id,autofocus:t.autofocus,"aria-label":t.ariaLabel,disabled:t.disabled,name:t.name,required:t.required},domProps:{value:t.selected}},t.listeners),[t.hasEmptyOption?e("option",{attrs:{disabled:t.required,value:""}},[t._v(" "+t._s(t.emptyOption)+" ")]):t._e(),t._l(t.options,(function(i){return e("option",{key:i.value,attrs:{disabled:i.disabled},domProps:{value:i.value}},[t._v(" "+t._s(i.text)+" ")])}))],2),t._v(" "+t._s(t.label)+" ")])}),[],!1,null,null,null,null).exports;const vs=ut({mixins:[Re,He,ks],inheritAttrs:!1,props:{icon:{type:String,default:"angle-down"}},methods:{focus(){this.$refs.input.focus()}}},(function(){var t=this,e=t._self._c;return e("k-field",t._b({staticClass:"k-select-field",attrs:{input:t._uid}},"k-field",t.$props,!1),[e("k-input",t._g(t._b({ref:"input",attrs:{id:t._uid,theme:"field",type:"select"}},"k-input",t.$props,!1),t.$listeners))],1)}),[],!1,null,null,null,null).exports,ys={mixins:[Hi],props:{allow:{type:String,default:""},formData:{type:Object,default:()=>({})},sync:{type:String}}};const $s=ut({extends:Vi,mixins:[ys],data(){return{slug:this.sluggify(this.value),slugs:this.$panel.language.rules??this.$panel.system.slugs,syncValue:null}},watch:{formData:{handler(t){return!this.disabled&&(!(!this.sync||void 0===t[this.sync])&&(t[this.sync]!=this.syncValue&&(this.syncValue=t[this.sync],void this.onInput(this.sluggify(this.syncValue)))))},deep:!0,immediate:!0},value(t){(t=this.sluggify(t))!==this.slug&&(this.slug=t,this.$emit("input",this.slug))}},methods:{sluggify(t){return this.$helper.slug(t,[this.slugs,this.$panel.system.ascii],this.allow)},onInput(t){this.slug=this.sluggify(t),this.$emit("input",this.slug)}}},(function(){var t=this;return(0,t._self._c)("input",t._g(t._b({directives:[{name:"direction",rawName:"v-direction"}],ref:"input",staticClass:"k-text-input",attrs:{autocomplete:"off",spellcheck:"false",type:"text"},domProps:{value:t.slug}},"input",{autofocus:t.autofocus,disabled:t.disabled,id:t.id,minlength:t.minlength,name:t.name,pattern:t.pattern,placeholder:t.placeholder,required:t.required},!1),t.listeners))}),[],!1,null,null,null,null).exports;const ws=ut({mixins:[Re,He,ys],inheritAttrs:!1,props:{icon:{type:String,default:"url"},path:{type:String},wizard:{type:[Boolean,Object],default:!1}},data(){return{slug:this.value}},computed:{preview(){return void 0!==this.help?this.help:void 0!==this.path?this.path+this.value:null}},watch:{value(){this.slug=this.value}},methods:{focus(){this.$refs.input.focus()},onWizard(){var t;this.formData[null==(t=this.wizard)?void 0:t.field]&&(this.slug=this.formData[this.wizard.field])}}},(function(){var t=this,e=t._self._c;return e("k-field",t._b({staticClass:"k-slug-field",attrs:{input:t._uid,help:t.preview},scopedSlots:t._u([t.wizard&&t.wizard.text?{key:"options",fn:function(){return[e("k-button",{attrs:{text:t.wizard.text,icon:"sparkling"},on:{click:t.onWizard}})]},proxy:!0}:null],null,!0)},"k-field",t.$props,!1),[e("k-input",t._g(t._b({ref:"input",attrs:{id:t._uid,value:t.slug,theme:"field",type:"slug"}},"k-input",t.$props,!1),t.$listeners))],1)}),[],!1,null,null,null,null).exports;const xs=ut({mixins:[Re],inheritAttrs:!1,props:{autofocus:Boolean,columns:Object,duplicate:{type:Boolean,default:!0},empty:String,fields:[Array,Object],limit:Number,max:Number,min:Number,prepend:{type:Boolean,default:!1},sortable:{type:Boolean,default:!0},sortBy:String,value:{type:Array,default:()=>[]}},data:()=>({items:[],page:1}),computed:{dragOptions(){return{disabled:!this.isSortable,fallbackClass:"k-sortable-row-fallback"}},index(){return this.limit?(this.page-1)*this.limit+1:1},more(){return!0!==this.disabled&&!(this.max&&this.items.length>=this.max)},hasFields(){return this.$helper.object.length(this.fields)>0},isInvalid(){return!0!==this.disabled&&(!!(this.min&&this.items.lengththis.max))},isSortable(){return!this.sortBy&&(!this.limit&&(!0!==this.disabled&&(!(this.items.length<=1)&&!1!==this.sortable)))},pagination(){let t=0;return this.limit&&(t=(this.page-1)*this.limit),{page:this.page,offset:t,limit:this.limit,total:this.items.length,align:"center",details:!0}},options(){if(this.disabled)return[];let t=[],e=this.duplicate&&this.more;return t.push({icon:"edit",text:this.$t("edit"),click:"edit"}),t.push({disabled:!e,icon:"copy",text:this.$t("duplicate"),click:"duplicate"}),t.push("-"),t.push({icon:"trash",text:e?this.$t("delete"):null,click:"remove"}),t},paginatedItems(){return this.limit?this.items.slice(this.pagination.offset,this.pagination.offset+this.limit):this.items}},watch:{value:{handler(t){t!==this.items&&(this.items=this.toItems(t))},immediate:!0}},methods:{add(t=null){if(!1===this.more)return!1;(t=t??this.$helper.field.form(this.fields))._id=t._id??this.$helper.uuid(),!0===this.prepend?this.items.unshift(t):this.items.push(t),this.save(),this.open(t)},close(){this.$panel.drawer.close(this._uid)},focus(){var t,e;null==(e=null==(t=this.$refs.add)?void 0:t.focus)||e.call(t)},form(t){const e=this.$helper.field.subfields(this,this.fields);if(t)for(const i in e)e[i].autofocus=i===t;return e},findIndex(t){return this.items.findIndex((e=>e._id===t._id))},navigate(t,e){const i=this.findIndex(t);!0!==this.disabled&&-1!==i&&this.open(this.items[i+e],null,!0)},open(t,e,i=!1){const n=this.findIndex(t);if(!0===this.disabled||-1===n)return!1;this.$panel.drawer.open({component:"k-structure-drawer",id:this._uid,props:{icon:this.icon??"list-bullet",next:this.items[n+1],prev:this.items[n-1],tabs:{content:{fields:this.form(e)}},title:this.label,value:t},replace:i,on:{input:e=>{const i=this.findIndex(t);this.$panel.drawer.props.next=this.items[i+1],this.$panel.drawer.props.prev=this.items[i-1],this.$set(this.items,i,e),this.save()},next:()=>{this.navigate(t,1)},prev:()=>{this.navigate(t,-1)},remove:()=>{this.remove(t)}}})},option(t,e){switch(t){case"remove":this.remove(e);break;case"duplicate":this.add({...e,_id:this.$helper.uuid()});break;case"edit":this.open(e)}},paginate({page:t}){this.page=t},remove(t){const e=this.findIndex(t);this.disabled||-1===e||this.$panel.dialog.open({component:"k-remove-dialog",props:{text:this.$t("field.structure.delete.confirm")},on:{submit:()=>{this.items.splice(e,1),this.save(),this.$panel.dialog.close(),this.close(),0===this.paginatedItems.length&&this.page>1&&this.page--}}})},removeAll(){this.$panel.dialog.open({component:"k-remove-dialog",props:{text:this.$t("field.structure.delete.confirm.all")},on:{submit:()=>{this.page=1,this.items=[],this.save(),this.$panel.dialog.close()}}})},save(t=this.items){this.$emit("input",t)},sort(t){return this.sortBy?t.sortBy(this.sortBy):t},toItems(t){return!1===Array.isArray(t)?[]:(t=t.map((t=>({_id:t._id??this.$helper.uuid(),...t}))),this.sort(t))}}},(function(){var t=this,e=t._self._c;return e("k-field",t._b({staticClass:"k-structure-field",nativeOn:{click:function(t){t.stopPropagation()}},scopedSlots:t._u([t.hasFields&&!t.disabled?{key:"options",fn:function(){return[e("k-button-group",{attrs:{layout:"collapsed"}},[e("k-button",{attrs:{autofocus:t.autofocus,disabled:!t.more,responsive:!0,text:t.$t("add"),icon:"add",variant:"filled",size:"xs"},on:{click:function(e){return t.add()}}}),e("k-button",{attrs:{icon:"dots",size:"xs",variant:"filled"},on:{click:function(e){return t.$refs.options.toggle()}}}),e("k-dropdown-content",{ref:"options",attrs:{options:[{click:()=>t.add(),disabled:!t.more,icon:"add",text:t.$t("add")},{click:()=>t.removeAll(),disabled:0===t.items.length||t.disabled,icon:"trash",text:t.$t("delete.all")}],"align-x":"end"}})],1)]},proxy:!0}:null],null,!0)},"k-field",t.$props,!1),[t.hasFields?[0===t.items.length?e("k-empty",{attrs:{"data-invalid":t.isInvalid,icon:"list-bullet"},on:{click:function(e){return t.add()}}},[t._v(" "+t._s(t.empty??t.$t("field.structure.empty"))+" ")]):[e("k-table",{attrs:{columns:t.columns,disabled:t.disabled,fields:t.fields,empty:t.$t("field.structure.empty"),index:t.index,options:t.options,pagination:!!t.limit&&t.pagination,rows:t.paginatedItems,sortable:t.isSortable,"data-invalid":t.isInvalid},on:{cell:function(e){return t.open(e.row,e.columnIndex)},input:t.save,option:t.option,paginate:t.paginate}}),t.more?e("footer",[e("k-button",{attrs:{title:t.$t("add"),icon:"add",size:"xs",variant:"filled"},on:{click:function(e){return t.add()}}})],1):t._e()]]:[e("k-empty",{attrs:{icon:"list-bullet"}},[t._v(t._s(t.$t("fields.empty")))])]],2)}),[],!1,null,null,null,null).exports,_s={mixins:[Hi],props:{autocomplete:{default:"tel"},placeholder:{default:()=>window.panel.$t("tel.placeholder")},type:{default:"tel"}}};const Ss=ut({extends:Vi,mixins:[_s]},null,null,!1,null,null,null,null).exports;const Cs=ut({mixins:[Re,He,_s],inheritAttrs:!1,props:{icon:{type:String,default:"phone"}},methods:{focus(){this.$refs.input.focus()}}},(function(){var t=this,e=t._self._c;return e("k-field",t._b({staticClass:"k-tel-field",attrs:{input:t._uid}},"k-field",t.$props,!1),[e("k-input",t._g(t._b({ref:"input",attrs:{id:t._uid,theme:"field",type:"tel"}},"k-input",t.$props,!1),t.$listeners))],1)}),[],!1,null,null,null,null).exports;const Os=ut({mixins:[Re,He,Hi,Di],inheritAttrs:!1,computed:{inputType(){return this.$helper.isComponent(`k-${this.type}-input`)?this.type:"text"}},methods:{focus(){this.$refs.input.focus()},select(){this.$refs.input.select()}}},(function(){var t=this,e=t._self._c;return e("k-field",t._b({staticClass:"k-text-field",attrs:{input:t._uid,counter:t.counterOptions},scopedSlots:t._u([{key:"options",fn:function(){return[t._t("options")]},proxy:!0}],null,!0)},"k-field",t.$props,!1),[e("k-input",t._g(t._b({ref:"input",attrs:{id:t._uid,type:t.inputType,theme:"field"}},"k-input",t.$props,!1),t.$listeners))],1)}),[],!1,null,null,null,null).exports,As={props:{buttons:{type:[Array,Boolean],default:!0},uploads:[Boolean,Object,Array]}};const Ms=ut({mixins:[As],computed:{commands(){return{headlines:{label:this.$t("toolbar.button.headings"),icon:"title",dropdown:[{label:this.$t("toolbar.button.heading.1"),icon:"h1",click:()=>this.command("prepend","#")},{label:this.$t("toolbar.button.heading.2"),icon:"h2",click:()=>this.command("prepend","##")},{label:this.$t("toolbar.button.heading.3"),icon:"h3",click:()=>this.command("prepend","###")}]},bold:{label:this.$t("toolbar.button.bold"),icon:"bold",click:()=>this.command("toggle","**"),shortcut:"b"},italic:{label:this.$t("toolbar.button.italic"),icon:"italic",click:()=>this.command("toggle","*"),shortcut:"i"},link:{label:this.$t("toolbar.button.link"),icon:"url",click:()=>this.command("dialog","link"),shortcut:"k"},email:{label:this.$t("toolbar.button.email"),icon:"email",click:()=>this.command("dialog","email"),shortcut:"e"},file:{label:this.$t("toolbar.button.file"),icon:"attachment",click:()=>this.command("file"),dropdown:this.uploads?[{label:this.$t("toolbar.button.file.select"),icon:"check",click:()=>this.command("file")},{label:this.$t("toolbar.button.file.upload"),icon:"upload",click:()=>this.command("upload")}]:void 0},code:{label:this.$t("toolbar.button.code"),icon:"code",click:()=>this.command("toggle","`")},ul:{label:this.$t("toolbar.button.ul"),icon:"list-bullet",click:()=>this.command("insert",((t,e)=>e.split("\n").map((t=>"- "+t)).join("\n")))},ol:{label:this.$t("toolbar.button.ol"),icon:"list-numbers",click:()=>this.command("insert",((t,e)=>e.split("\n").map(((t,e)=>e+1+". "+t)).join("\n")))}}},default:()=>["headlines","|","bold","italic","code","|","link","email","file","|","ul","ol"],layout(){var t;if(!1===this.buttons)return[];const e=[],i=Array.isArray(this.buttons)?this.buttons:this.default,n={...this.commands,...window.panel.plugins.textareaButtons??{}};for(const s of i)if("|"===s)e.push("|");else if(n[s]){const i=n[s];i.click=null==(t=i.click)?void 0:t.bind(this),e.push(i)}return e}},methods:{close(){this.$refs.toolbar.close()},command(t,...e){this.$emit("command",t,...e)},shortcut(t,e){var i;const n=this.layout.find((e=>e.shortcut===t));n&&(e.preventDefault(),null==(i=n.click)||i.call(n))}}},(function(){return(0,this._self._c)("k-toolbar",{ref:"toolbar",staticClass:"k-textarea-toolbar",attrs:{buttons:this.layout}})}),[],!1,null,null,null,null).exports,js={mixins:[As,Te,J,et,it,lt,at],props:{endpoints:Object,preselect:Boolean,size:String,theme:String,value:String}};const Ts=ut({mixins:[Ie,js],data:()=>({over:!1}),computed:{uploadOptions(){const t=this.restoreSelectionCallback();return{url:this.$panel.urls.api+"/"+this.endpoints.field+"/upload",multiple:!1,on:{cancel:t,done:e=>{t((()=>this.insertUpload(e)))}}}}},watch:{async value(){this.onInvalid(),await this.$nextTick(),this.$library.autosize.update(this.$refs.input)}},async mounted(){await this.$nextTick(),this.$library.autosize(this.$refs.input),this.onInvalid(),this.$props.autofocus&&this.focus(),this.$props.preselect&&this.select()},methods:{dialog(t){const e=this.restoreSelectionCallback();this.$panel.dialog.open({component:"k-toolbar-"+t+"-dialog",props:{value:this.parseSelection()},on:{cancel:e,submit:t=>{this.$panel.dialog.close(),e((()=>this.insert(t)))}}})},file(){const t=this.restoreSelectionCallback();this.$panel.dialog.open({component:"k-files-dialog",props:{endpoint:this.endpoints.field+"/files",multiple:!1},on:{cancel:t,submit:e=>{t((()=>this.insertFile(e))),this.$panel.dialog.close()}}})},focus(){this.$refs.input.focus()},insert(t){const e=this.$refs.input,i=e.value;"function"==typeof t&&(t=t(this.$refs.input,this.selection())),setTimeout((()=>{if(e.focus(),document.execCommand("insertText",!1,t),e.value===i){const i=e.selectionStart,n=e.selectionEnd,s=i===n?"end":"select";e.setRangeText(t,i,n,s)}this.$emit("input",e.value)}))},insertFile(t){(null==t?void 0:t.length)>0&&this.insert(t.map((t=>t.dragText)).join("\n\n"))},insertUpload(t){this.insertFile(t),this.$events.emit("model.update")},onCommand(t,...e){if("function"!=typeof this[t])return console.warn(t+" is not a valid command");this[t](...e)},onDrop(t){if(this.uploads&&this.$helper.isUploadEvent(t))return this.$panel.upload.open(t.dataTransfer.files,this.uploadOptions);"text"===this.$panel.drag.type&&(this.focus(),this.insert(this.$panel.drag.data))},onFocus(t){this.$emit("focus",t)},onInput(t){this.$emit("input",t.target.value)},onInvalid(){this.$emit("invalid",this.$v.$invalid,this.$v)},onOut(){this.$refs.input.blur(),this.over=!1},onOver(t){if(this.uploads&&this.$helper.isUploadEvent(t))return t.dataTransfer.dropEffect="copy",this.focus(),void(this.over=!0);"text"===this.$panel.drag.type&&(t.dataTransfer.dropEffect="copy",this.focus(),this.over=!0)},onShortcut(t){var e;!1!==this.buttons&&"Meta"!==t.key&&"Control"!==t.key&&(null==(e=this.$refs.toolbar)||e.shortcut(t.key,t))},onSubmit(t){return this.$emit("submit",t)},parseSelection(){const t=this.selection();if(0===(null==t?void 0:t.length))return{href:null,title:null};let e;e=this.$panel.config.kirbytext?/^\(link:\s*(?.*?)(?:\s*text:\s*(?.*?))?\)$/is:/^(\[(?.*?)\]\((?.*?)\))|(<(?.*?)>)$/is;const i=e.exec(t);return null!==i?{href:i.groups.url??i.groups.link,title:i.groups.text??null}:{href:null,title:t}},prepend(t){this.insert(t+" "+this.selection())},restoreSelectionCallback(){const t=this.$refs.input.selectionStart,e=this.$refs.input.selectionEnd;return i=>{setTimeout((()=>{this.$refs.input.setSelectionRange(t,e),i&&i()}))}},select(){this.$refs.select()},selection(){return this.$refs.input.value.substring(this.$refs.input.selectionStart,this.$refs.input.selectionEnd)},toggle(t,e){e=e??t;const i=this.selection();return i.startsWith(t)&&i.endsWith(e)?this.insert(i.slice(t.length).slice(0,i.length-t.length-e.length)):this.wrap(t,e)},upload(){this.$panel.upload.pick(this.uploadOptions)},wrap(t,e){this.insert(t+this.selection()+(e??t))}},validations(){return{value:{required:!this.required||t.required,minLength:!this.minlength||t.minLength(this.minlength),maxLength:!this.maxlength||t.maxLength(this.maxlength)}}}},(function(){var t=this,e=t._self._c;return e("div",{staticClass:"k-textarea-input",attrs:{"data-over":t.over,"data-size":t.size}},[e("div",{staticClass:"k-textarea-input-wrapper"},[t.buttons&&!t.disabled?e("k-textarea-toolbar",{ref:"toolbar",attrs:{buttons:t.buttons,disabled:t.disabled,uploads:t.uploads},on:{command:t.onCommand},nativeOn:{mousedown:function(t){t.preventDefault()}}}):t._e(),e("textarea",t._b({directives:[{name:"direction",rawName:"v-direction"}],ref:"input",staticClass:"k-textarea-input-native",attrs:{"data-font":t.font},on:{click:function(e){var i;null==(i=t.$refs.toolbar)||i.close()},focus:t.onFocus,input:t.onInput,keydown:[function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"enter",13,e.key,"Enter")?null:e.metaKey?t.onSubmit.apply(null,arguments):null},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"enter",13,e.key,"Enter")?null:e.ctrlKey?t.onSubmit.apply(null,arguments):null},function(e){return e.metaKey?e.ctrlKey||e.shiftKey||e.altKey?null:t.onShortcut.apply(null,arguments):null},function(e){return e.ctrlKey?e.shiftKey||e.altKey||e.metaKey?null:t.onShortcut.apply(null,arguments):null}],dragover:t.onOver,dragleave:t.onOut,drop:t.onDrop}},"textarea",{autofocus:t.autofocus,disabled:t.disabled,id:t.id,minlength:t.minlength,name:t.name,placeholder:t.placeholder,required:t.required,spellcheck:t.spellcheck,value:t.value},!1))],1)])}),[],!1,null,null,null,null).exports;const Is=ut({mixins:[Re,He,js,Di],inheritAttrs:!1,methods:{focus(){this.$refs.input.focus()}}},(function(){var t=this,e=t._self._c;return e("k-field",t._b({staticClass:"k-textarea-field",attrs:{input:t._uid,counter:t.counterOptions}},"k-field",t.$props,!1),[e("k-input",t._g(t._b({ref:"input",attrs:{id:t._uid,type:"textarea",theme:"field"}},"k-input",t.$props,!1),t.$listeners))],1)}),[],!1,null,null,null,null).exports,Es={props:{display:{type:String,default:"HH:mm"},max:String,min:String,step:{type:Object,default:()=>({size:5,unit:"minute"})},type:{type:String,default:"time"},value:String}};const Ls=ut({mixins:[Yi,Es],computed:{inputType:()=>"time"}},null,null,!1,null,null,null,null).exports;const Ds=ut({mixins:[Re,He,Es],inheritAttrs:!1,props:{icon:{type:String,default:"clock"},times:{type:Boolean,default:!0}},methods:{focus(){this.$refs.input.focus()},select(t){var e;this.$emit("input",t),null==(e=this.$refs.times)||e.close()}}},(function(){var t=this,e=t._self._c;return e("k-field",t._b({staticClass:"k-time-field",attrs:{input:t._uid}},"k-field",t.$props,!1),[e("k-input",t._b({ref:"input",attrs:{id:t._uid,theme:"field",type:"time"},on:{input:function(e){return t.$emit("input",e??"")}},scopedSlots:t._u([t.times?{key:"icon",fn:function(){return[e("k-button",{staticClass:"k-input-icon-button",attrs:{disabled:t.disabled,icon:t.icon??"clock",title:t.$t("time.select")},on:{click:function(e){return t.$refs.times.toggle()}}}),e("k-dropdown-content",{ref:"times",attrs:{"align-x":"end"}},[e("k-timeoptions-input",{attrs:{display:t.display,value:t.value},on:{input:t.select}})],1)]},proxy:!0}:null],null,!0)},"k-input",t.$props,!1))],1)}),[],!1,null,null,null,null).exports,Bs={props:{autofocus:Boolean,disabled:Boolean,id:[Number,String],text:{type:[Array,String]},required:Boolean,value:Boolean}};const qs=ut({mixins:[Ie,Bs],computed:{label(){const t=this.text??[this.$t("off"),this.$t("on")];return Array.isArray(t)?this.value?t[1]:t[0]:t}},watch:{value(){this.onInvalid()}},mounted(){this.onInvalid(),this.$props.autofocus&&this.focus()},methods:{onEnter(t){"Enter"===t.key&&this.$refs.input.click()},onInput(t){this.$emit("input",t)},onInvalid(){this.$emit("invalid",this.$v.$invalid,this.$v)},select(){this.$refs.input.focus()}},validations(){return{value:{required:!this.required||t.required}}}},(function(){var t=this;return(0,t._self._c)("k-choice-input",{ref:"input",staticClass:"k-toggle-input",attrs:{id:t.id,checked:t.value,disabled:t.disabled,label:t.label,type:"checkbox",variant:"toggle"},on:{input:function(e){return t.$emit("input",e)}}})}),[],!1,null,null,null,null).exports;const Ps=ut({mixins:[Re,He,Bs],inheritAttrs:!1,methods:{focus(){this.$refs.input.focus()}}},(function(){var t=this,e=t._self._c;return e("k-field",t._b({staticClass:"k-toggle-field",attrs:{input:t._uid}},"k-field",t.$props,!1),[e("k-input",t._g(t._b({ref:"input",attrs:{id:t._uid,theme:"field",type:"toggle"}},"k-input",t.$props,!1),t.$listeners))],1)}),[],!1,null,null,null,null).exports,Ns={mixins:[Te],props:{columns:Number,grow:Boolean,labels:Boolean,options:Array,reset:Boolean,value:[String,Number,Boolean]}};const zs=ut({mixins:[Ie,Ns],watch:{value(){this.onInvalid()}},mounted(){this.onInvalid(),this.$props.autofocus&&this.focus()},methods:{focus(){var t;null==(t=this.$el.querySelector("input[checked]")||this.$el.querySelector("input"))||t.focus()},onClick(t){t===this.value&&this.reset&&!this.required&&this.$emit("input","")},onInput(t){this.$emit("input",t)},onInvalid(){this.$emit("invalid",this.$v.$invalid,this.$v)},select(){this.focus()}},validations(){return{value:{required:!this.required||t.required}}}},(function(){var t=this,e=t._self._c;return e("ul",{staticClass:"k-toggles-input",style:{"--options":t.columns??t.options.length},attrs:{"data-invalid":t.$v.$invalid,"data-labels":t.labels}},t._l(t.options,(function(i,n){return e("li",{key:n,attrs:{"data-disabled":t.disabled}},[e("input",{staticClass:"input-hidden",attrs:{id:t.id+"-"+n,"aria-label":i.text,disabled:t.disabled,name:t.id,type:"radio"},domProps:{value:i.value,checked:t.value===i.value},on:{click:function(e){return t.onClick(i.value)},change:function(e){return t.onInput(i.value)}}}),e("label",{attrs:{for:t.id+"-"+n,title:i.text}},[i.icon?e("k-icon",{attrs:{type:i.icon}}):t._e(),t.labels||!i.icon?e("span",{staticClass:"k-toggles-text",domProps:{innerHTML:t._s(i.text)}}):t._e()],1)])})),0)}),[],!1,null,null,null,null).exports;const Fs=ut({mixins:[Re,He,Ns],inheritAttrs:!1,methods:{focus(){this.$refs.input.focus()},onInput(t){this.$emit("input",t)}}},(function(){var t,e=this,i=e._self._c;return i("k-field",e._b({staticClass:"k-toggles-field"},"k-field",e.$props,!1),[(null==(t=e.options)?void 0:t.length)?i("k-input",e._g(e._b({ref:"input",class:{grow:e.grow},attrs:{id:e._uid,theme:"field",type:"toggles"}},"k-input",e.$props,!1),e.$listeners)):i("k-empty",{attrs:{text:e.$t("options.none"),icon:"checklist"}})],1)}),[],!1,null,null,null,null).exports,Rs={mixins:[Hi],props:{autocomplete:{type:String,default:"url"},placeholder:{type:String,default:()=>window.panel.$t("url.placeholder")},type:{type:String,default:"url"}}};const Ys=ut({extends:Vi,mixins:[Rs]},null,null,!1,null,null,null,null).exports;const Us=ut({mixins:[Re,He,Rs],inheritAttrs:!1,props:{link:{type:Boolean,default:!0},icon:{type:String,default:"url"}},methods:{focus(){this.$refs.input.focus()}}},(function(){var t=this,e=t._self._c;return e("k-field",t._b({staticClass:"k-url-field",attrs:{input:t._uid}},"k-field",t.$props,!1),[e("k-input",t._g(t._b({ref:"input",attrs:{id:t._uid,theme:"field",type:"url"},scopedSlots:t._u([{key:"icon",fn:function(){return[t.link?e("k-button",{staticClass:"k-input-icon-button",attrs:{icon:t.icon,link:t.value,title:t.$t("open"),tabindex:"-1",target:"_blank"}}):t._e()]},proxy:!0}])},"k-input",t.$props,!1),t.$listeners))],1)}),[],!1,null,null,null,null).exports;const Hs=ut({extends:Gi,type:"users",computed:{emptyProps(){return{icon:"users",text:this.empty??this.$t("field.users.empty")}}}},null,null,!1,null,null,null,null).exports;const Vs=ut({mixins:[Re,He,Yn,Di],inheritAttrs:!1,methods:{focus(){this.$refs.input.focus()}}},(function(){var t=this,e=t._self._c;return e("k-field",t._b({staticClass:"k-writer-field",attrs:{input:t._uid,counter:t.counterOptions}},"k-field",t.$props,!1),[e("k-input",t._b({ref:"input",attrs:{after:t.after,before:t.before,icon:t.icon,theme:"field",type:"writer"},on:{input:function(e){return t.$emit("input",e)}}},"k-input",t.$props,!1))],1)}),[],!1,null,null,null,null).exports,Ks={install(t){t.component("k-blocks-field",Ii),t.component("k-checkboxes-field",Bi),t.component("k-color-field",Fi),t.component("k-date-field",Ui),t.component("k-email-field",Ji),t.component("k-files-field",Xi),t.component("k-gap-field",Zi),t.component("k-headline-field",Qi),t.component("k-info-field",tn),t.component("k-layout-field",en),t.component("k-line-field",nn),t.component("k-link-field",sn),t.component("k-list-field",Wn),t.component("k-multiselect-field",is),t.component("k-number-field",os),t.component("k-object-field",ls),t.component("k-pages-field",rs),t.component("k-password-field",cs),t.component("k-radio-field",hs),t.component("k-range-field",gs),t.component("k-select-field",vs),t.component("k-slug-field",ws),t.component("k-structure-field",xs),t.component("k-tags-field",es),t.component("k-text-field",Os),t.component("k-textarea-field",Is),t.component("k-tel-field",Cs),t.component("k-time-field",Ds),t.component("k-toggle-field",Ps),t.component("k-toggles-field",Fs),t.component("k-url-field",Us),t.component("k-users-field",Hs),t.component("k-writer-field",Vs)}},Ws={mixins:[ms],props:{max:{default:1,type:Number},min:{default:0,type:Number},step:{default:.01,type:Number},tooltip:{default:!1,type:[Boolean,Object]}}};const Js=ut({mixins:[fs,Ws]},(function(){var t=this;return(0,t._self._c)("k-range-input",t._b({staticClass:"k-alpha-input",on:{input:function(e){return t.$emit("input",e)}}},"k-range-input",t.$props,!1))}),[],!1,null,null,null,null).exports;const Gs=ut({mixins:[Te],props:{max:String,min:String,value:{default:"",type:String}},data(){return{maxdate:null,mindate:null,month:null,selected:null,today:this.$library.dayjs(),year:null}},computed:{numberOfDays(){return this.toDate().daysInMonth()},firstWeekday(){const t=this.toDate().day();return t>0?t:7},weekdays(){return["mon","tue","wed","thu","fri","sat","sun"].map((t=>this.$t("days."+t)))},weeks(){const t=this.firstWeekday-1;return Math.ceil((this.numberOfDays+t)/7)},monthnames(){return["january","february","march","april","may","june","july","august","september","october","november","december"].map((t=>this.$t("months."+t)))},months(){var t=[];return this.monthnames.forEach(((e,i)=>{t.push({value:i,text:e})})),t},years(){const t=this.year-20,e=this.year+20;return this.toOptions(t,e)}},watch:{max:{handler(t,e){t!==e&&(this.maxdate=this.$library.dayjs.interpret(t,"date"))},immediate:!0},min:{handler(t,e){t!==e&&(this.mindate=this.$library.dayjs.interpret(t,"date"))},immediate:!0},value:{handler(t,e){t!==e&&(this.selected=this.$library.dayjs.interpret(t,"date"),this.show(this.selected))},immediate:!0}},methods:{days(t){let e=[];const i=7*(t-1)+1,n=i+7;for(let s=i;sthis.numberOfDays;e.push(i?"":t)}return e},isDisabled(t){const e=this.toDate(t);return this.disabled||e.isBefore(this.mindate,"day")||e.isAfter(this.maxdate,"day")},isSelected(t){return this.toDate(t).isSame(this.selected,"day")},isToday(t){return this.toDate(t).isSame(this.today,"day")},onNext(){const t=this.toDate().add(1,"month");this.show(t)},onPrev(){const t=this.toDate().subtract(1,"month");this.show(t)},select(t){this.$emit("input",(null==t?void 0:t.toISO("date"))??null)},show(t){this.month=(t??this.today).month(),this.year=(t??this.today).year()},toDate(t=1,e=this.month){return this.$library.dayjs(`${this.year}-${e+1}-${t}`)},toOptions(t,e){for(var i=[],n=t;n<=e;n++)i.push({value:n,text:this.$helper.pad(n)});return i}}},(function(){var t=this,e=t._self._c;return e("fieldset",{staticClass:"k-calendar-input",on:{click:function(t){t.stopPropagation()}}},[e("legend",{staticClass:"sr-only"},[t._v(t._s(t.$t("date.select")))]),e("nav",[e("k-button",{attrs:{title:t.$t("prev"),icon:"angle-left"},on:{click:t.onPrev}}),e("span",{staticClass:"k-calendar-selects"},[e("k-select-input",{attrs:{"aria-label":t.$t("month"),autofocus:t.autofocus,options:t.months,empty:!1,required:!0,value:t.month},on:{input:function(e){t.month=Number(e)}}}),e("k-select-input",{attrs:{"aria-label":t.$t("year"),options:t.years,empty:!1,required:!0,value:t.year},on:{input:function(e){t.year=Number(e)}}})],1),e("k-button",{attrs:{title:t.$t("next"),icon:"angle-right"},on:{click:t.onNext}})],1),e("table",{key:t.year+"-"+t.month,staticClass:"k-calendar-table"},[e("thead",[e("tr",t._l(t.weekdays,(function(i){return e("th",{key:"weekday_"+i},[t._v(" "+t._s(i)+" ")])})),0)]),e("tbody",t._l(t.weeks,(function(i){return e("tr",{key:"week_"+i},t._l(t.days(i),(function(i,n){return e("td",{key:"day_"+n,staticClass:"k-calendar-day",attrs:{"aria-current":!!t.isToday(i)&&"date","aria-selected":!!t.isSelected(i)&&"date"}},[i?e("k-button",{attrs:{disabled:t.isDisabled(i),text:i},on:{click:function(e){t.select(t.toDate(i))}}}):t._e()],1)})),0)})),0),e("tfoot",[e("tr",[e("td",{staticClass:"k-calendar-today",attrs:{colspan:"7"}},[e("k-button",{attrs:{disabled:t.disabled,text:t.$t("today")},on:{click:function(e){t.show(t.today),t.select(t.today)}}})],1)])])]),e("input",{staticClass:"sr-only",attrs:{id:t.id,disabled:t.disabled,min:t.min,max:t.max,name:t.name,required:t.required,tabindex:"-1",type:"date"},domProps:{value:t.value}})])}),[],!1,null,null,null,null).exports;const Xs=ut({mixins:[Ie,{mixins:[Te],props:{checked:{type:Boolean},info:{type:String},label:{type:String},type:{default:"checkbox",type:String},value:{type:[Boolean,Number,String]},variant:{type:String}}}]},(function(){var t=this,e=t._self._c;return e("label",{staticClass:"k-choice-input",attrs:{"aria-disabled":t.disabled}},[e("input",t._b({class:{"sr-only":"invisible"===t.variant},attrs:{"data-variant":t.variant},on:{input:function(e){return t.$emit("input",e.target.checked)}}},"input",{autofocus:t.autofocus,id:t.id,checked:t.checked,disabled:t.disabled,name:t.name,required:t.required,type:t.type,value:t.value},!1)),t.label||t.info?e("span",{staticClass:"k-choice-input-label"},[e("span",{staticClass:"k-choice-input-label-text",domProps:{innerHTML:t._s(t.label)}}),t.info?e("span",{staticClass:"k-choice-input-label-info",domProps:{innerHTML:t._s(t.info)}}):t._e()]):t._e()])}),[],!1,null,null,null,null).exports;const Zs=ut({extends:Xs},null,null,!1,null,null,null,null).exports;const Qs=ut({mixins:[ps,{mixins:[ds],props:{format:{type:String,default:"hex",validator:t=>["hex","rgb","hsl"].includes(t)},value:{type:String}}}],computed:{choices(){return this.options.map((t=>({...t,title:t.text??t.value,value:this.colorToString(t.value)})))}},methods:{colorToString(t){try{return this.$library.colors.toString(t,this.format)}catch{return t}}}},(function(){var t=this,e=t._self._c;return t.choices.length?e("fieldset",{staticClass:"k-coloroptions-input",attrs:{disabled:t.disabled}},[e("legend",{staticClass:"sr-only"},[t._v(t._s(t.$t("options")))]),e("ul",t._l(t.choices,(function(i,n){return e("li",{key:n},[e("label",{attrs:{title:i.title}},[e("input",{staticClass:"input-hidden",attrs:{autofocus:t.autofocus&&0===n,disabled:t.disabled,name:t.name??t._uid,required:t.required,type:"radio"},domProps:{checked:i.value===t.value,value:i.value},on:{click:function(e){return t.toggle(i.value)},input:function(e){return t.$emit("input",i.value)}}}),e("k-color-frame",{attrs:{color:i.value}})],1)])})),0)]):t._e()}),[],!1,null,null,null,null).exports;const to=ut({mixins:[Ie,{mixins:[Te,st],props:{alpha:{default:!0,type:Boolean},format:{default:"hex",type:String,validator:t=>["hex","rgb","hsl"].includes(t)},value:{type:[Object,String]}}}],data:()=>({color:{h:0,s:0,v:1,a:1},formatted:null}),computed:{coords(){return this.value?{x:100*this.color.s,y:100*(1-this.color.v)}:null},hsl(){try{const t=this.$library.colors.convert(this.color,"hsl");return{h:t.h,s:(100*t.s).toFixed()+"%",l:(100*t.l).toFixed()+"%",a:t.a}}catch{return{h:0,s:"0%",l:"0%",a:1}}}},watch:{value:{handler(t,e){if(t===e||t===this.formatted)return;const i=this.$library.colors.parseAs(t??"","hsv");i?(this.formatted=this.$library.colors.toString(i,this.format),this.color=i):(this.formatted=null,this.color={h:0,s:0,v:1,a:1})},immediate:!0}},methods:{between:(t,e,i)=>Math.min(Math.max(t,e),i),emit(){return this.formatted=this.$library.colors.toString(this.color,this.format),this.$emit("input",this.formatted)},focus(){this.$refs.coords.focus()},setAlpha(t){this.color.a=this.alpha?this.between(Number(t),0,1):1,this.emit()},setCoords(t){if(!t)return this.$emit("input","");const e=Math.round(t.x),i=Math.round(t.y);this.color.s=this.between(e/100,0,1),this.color.v=this.between(1-i/100,0,1),this.emit()},setHue(t){this.color.h=this.between(Number(t),0,360),this.emit()}}},(function(){var t=this,e=t._self._c;return e("fieldset",{staticClass:"k-colorpicker-input",style:{"--h":t.hsl.h,"--s":t.hsl.s,"--l":t.hsl.l,"--a":t.hsl.a}},[e("legend",{staticClass:"sr-only"},[t._v(t._s(t.$t("color")))]),e("k-coords-input",{ref:"coords",attrs:{autofocus:t.autofocus,disabled:t.disabled,required:t.required,value:t.coords},on:{input:function(e){return t.setCoords(e)}}}),e("label",{attrs:{"aria-label":t.$t("hue")}},[e("k-hue-input",{attrs:{disabled:t.disabled,required:t.required,value:t.color.h},on:{input:function(e){return t.setHue(e)}}})],1),t.alpha?e("label",{attrs:{"aria-label":t.$t("alpha")}},[e("k-alpha-input",{attrs:{disabled:t.disabled,required:t.required,value:t.color.a},on:{input:function(e){return t.setAlpha(e)}}})],1):t._e(),e("k-coloroptions-input",{attrs:{disabled:t.disabled,format:t.format,options:t.options,required:t.required,value:t.value},on:{input:function(e){return t.$emit("input",e)}}}),e("input",{staticClass:"input-hidden",attrs:{name:t.name,required:t.required,tabindex:"-1",type:"text"},domProps:{value:t.formatted}})],1)}),[],!1,null,null,null,null).exports;const eo=ut({mixins:[Ie,{mixins:[Te],props:{reset:{default:!0,type:Boolean},value:{default:()=>({x:0,y:0}),type:Object}}}],data:()=>({x:0,y:0}),watch:{value:{handler(t){const e=this.parse(t);this.x=(null==e?void 0:e.x)??0,this.y=(null==e?void 0:e.y)??0},immediate:!0}},methods:{focus(){var t;null==(t=this.$el.querySelector("button"))||t.focus()},getCoords:(t,e)=>({x:Math.min(Math.max(t.clientX-e.left,0),e.width),y:Math.min(Math.max(t.clientY-e.top,0),e.height)}),onDelete(){this.reset&&!this.required&&this.$emit("input",null)},onDrag(t){if(0!==t.button)return;const e=t=>this.onMove(t),i=()=>{window.removeEventListener("mousemove",e),window.removeEventListener("mouseup",i)};window.addEventListener("mousemove",e),window.addEventListener("mouseup",i)},onEnter(){var t;null==(t=this.$el.form)||t.requestSubmit()},onInput(t,e){if(t.preventDefault(),t.stopPropagation(),this.disabled)return!1;this.x=Math.min(Math.max(parseFloat(e.x??this.x??0),0),100),this.y=Math.min(Math.max(parseFloat(e.y??this.y??0),0),100),this.$emit("input",{x:this.x,y:this.y})},onKeys(t){const e=t.shiftKey?10:1,i={ArrowUp:{y:this.y-e},ArrowDown:{y:this.y+e},ArrowLeft:{x:this.x-e},ArrowRight:{x:this.x+e}};i[t.key]&&this.onInput(t,i[t.key])},async onMove(t){const e=this.$el.getBoundingClientRect(),i=this.getCoords(t,e),n=i.x/e.width*100,s=i.y/e.height*100;this.onInput(t,{x:n,y:s}),await this.$nextTick(),this.focus()},parse(t){if("object"==typeof t)return t;const e={"top left":{x:0,y:0},"top center":{x:50,y:0},"top right":{x:100,y:0},"center left":{x:0,y:50},center:{x:50,y:50},"center center":{x:50,y:50},"center right":{x:100,y:50},"bottom left":{x:0,y:100},"bottom center":{x:50,y:100},"bottom right":{x:100,y:100}};if(e[t])return e[t];const i=t.split(",").map((t=>t.trim()));return{x:i[0],y:i[1]??0}}}},(function(){var t=this,e=t._self._c;return e("div",{staticClass:"k-coords-input",attrs:{"aria-disabled":t.disabled,"data-empty":!t.value},on:{mousedown:t.onDrag,click:t.onMove,keydown:t.onKeys}},[t._t("default"),e("button",{staticClass:"k-coords-input-thumb",style:{left:`${t.x}%`,top:`${t.y}%`},attrs:{id:t.id,autofocus:t.autofocus,disabled:t.disabled},on:{keydown:[function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"enter",13,e.key,"Enter")?null:(e.preventDefault(),t.onEnter.apply(null,arguments))},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"delete",[8,46],e.key,["Backspace","Delete","Del"])?null:t.onDelete.apply(null,arguments)}]}}),e("input",{staticClass:"input-hidden",attrs:{name:t.name,required:t.required,tabindex:"-1",type:"text"},domProps:{value:t.value?[t.value.x,t.value.y]:null}})],2)}),[],!1,null,null,null,null).exports,io={mixins:[ms],props:{max:{default:360,type:Number},min:{default:0,type:Number},step:{default:1,type:Number},tooltip:{default:!1,type:[Boolean,Object]}}};const no=ut({mixins:[fs,io]},(function(){var t=this;return(0,t._self._c)("k-range-input",t._b({staticClass:"k-hue-input",on:{input:function(e){return t.$emit("input",e)}}},"k-range-input",t.$props,!1))}),[],!1,null,null,null,null).exports;const so=ut({mixins:[Pi,{mixins:[qi],props:{autocomplete:{default:"off"},placeholder:{default:()=>window.panel.$t("search")+" …",type:String},spellcheck:{default:!1}}}]},(function(){var t=this;return(0,t._self._c)("k-string-input",t._b({staticClass:"k-search-input",attrs:{type:"search"},on:{input:function(e){return t.$emit("input",e)}}},"k-string-input",t.$props,!1))}),[],!1,null,null,null,null).exports;const oo=ut({mixins:[Ie,{mixins:[Te]}],props:{display:{type:String,default:"HH:mm"},value:String},computed:{day(){return this.formatTimes([6,7,8,9,10,11,"-",12,13,14,15,16,17])},night(){return this.formatTimes([18,19,20,21,22,23,"-",0,1,2,3,4,5])}},methods:{focus(){this.$el.querySelector("button").focus()},formatTimes(t){return t.map((t=>{if("-"===t)return t;const e=this.$library.dayjs(t+":00","H:mm");return{display:e.format(this.display),select:e.toISO("time")}}))},select(t){this.$emit("input",t)}}},(function(){var t=this,e=t._self._c;return e("div",{staticClass:"k-timeoptions-input"},[e("div",[e("h3",[e("k-icon",{attrs:{type:"sun"}}),t._v(" "),e("span",{staticClass:"sr-only"},[t._v(t._s(t.$t("day")))])],1),e("ul",t._l(t.day,(function(i,n){return e("li",{key:i.select},["-"===i?e("hr"):e("k-button",{attrs:{autofocus:t.autofocus&&0===n,disabled:t.disabled,selected:i.select===t.value&&"time"},on:{click:function(e){return t.select(i.select)}}},[t._v(" "+t._s(i.display)+" ")])],1)})),0)]),e("div",[e("h3",[e("k-icon",{attrs:{type:"moon"}}),t._v(" "),e("span",{staticClass:"sr-only"},[t._v(t._s(t.$t("night")))])],1),e("ul",t._l(t.night,(function(i){return e("li",{key:i.select},["-"===i?e("hr"):e("k-button",{attrs:{disabled:t.disabled,selected:i.select===t.value&&"time"},on:{click:function(e){return t.select(i.select)}}},[t._v(" "+t._s(i.display)+" ")])],1)})),0)])])}),[],!1,null,null,null,null).exports,lo={install(t){t.component("k-alpha-input",Js),t.component("k-calendar-input",Gs),t.component("k-checkbox-input",Zs),t.component("k-checkboxes-input",Li),t.component("k-choice-input",Xs),t.component("k-colorname-input",zi),t.component("k-coloroptions-input",Qs),t.component("k-colorpicker-input",to),t.component("k-coords-input",eo),t.component("k-date-input",Yi),t.component("k-email-input",Wi),t.component("k-hue-input",no),t.component("k-list-input",Kn),t.component("k-multiselect-input",Zn),t.component("k-number-input",ss),t.component("k-password-input",us),t.component("k-picklist-input",De),t.component("k-radio-input",ps),t.component("k-range-input",fs),t.component("k-search-input",so),t.component("k-select-input",bs),t.component("k-slug-input",$s),t.component("k-string-input",Pi),t.component("k-tags-input",ts),t.component("k-tel-input",Ss),t.component("k-text-input",Vi),t.component("k-textarea-input",Ts),t.component("k-time-input",Ls),t.component("k-timeoptions-input",oo),t.component("k-toggle-input",qs),t.component("k-toggles-input",zs),t.component("k-url-input",Ys),t.component("k-writer-input",Un),t.component("k-calendar",Gs),t.component("k-times",oo)}};const ro=ut({props:{attrs:[Array,Object],columns:Array,disabled:Boolean,endpoints:Object,fieldsetGroups:Object,fieldsets:Object,id:String,isSelected:Boolean,layouts:Array,settings:Object},computed:{options(){return[{click:()=>this.$emit("prepend"),icon:"angle-up",text:this.$t("insert.before")},{click:()=>this.$emit("append"),icon:"angle-down",text:this.$t("insert.after")},"-",{click:()=>this.openSettings(),icon:"settings",text:this.$t("settings"),when:!1===this.$helper.object.isEmpty(this.settings)},{click:()=>this.$emit("duplicate"),icon:"copy",text:this.$t("duplicate")},{click:()=>this.$emit("change"),disabled:1===this.layouts.length,icon:"dashboard",text:this.$t("field.layout.change")},"-",{click:()=>this.$emit("copy"),icon:"template",text:this.$t("copy")},{click:()=>this.$emit("paste"),icon:"download",text:this.$t("paste.after")},"-",{click:()=>this.remove(),icon:"trash",text:this.$t("field.layout.delete")}]},tabs(){let t=this.settings.tabs;for(const[e,i]of Object.entries(t))for(const n in i.fields)t[e].fields[n].endpoints={field:this.endpoints.field+"/fields/"+n,section:this.endpoints.section,model:this.endpoints.model};return t}},methods:{openSettings(){this.$panel.drawer.open({component:"k-form-drawer",props:{icon:"settings",tabs:this.tabs,title:this.$t("settings"),value:this.attrs},on:{input:t=>this.$emit("updateAttrs",t)}})},remove(){this.$panel.dialog.open({component:"k-remove-dialog",props:{text:this.$t("field.layout.delete.confirm")},on:{submit:()=>{this.$emit("remove"),this.$panel.dialog.close()}}})}}},(function(){var t=this,e=t._self._c;return e("section",{staticClass:"k-layout",attrs:{"data-selected":t.isSelected,tabindex:"0"},on:{click:function(e){return t.$emit("select")}}},[e("k-grid",{staticClass:"k-layout-columns"},t._l(t.columns,(function(i,n){return e("k-layout-column",t._b({key:i.id,attrs:{endpoints:t.endpoints,"fieldset-groups":t.fieldsetGroups,fieldsets:t.fieldsets},on:{input:function(e){return t.$emit("updateColumn",{column:i,columnIndex:n,blocks:e})}}},"k-layout-column",i,!1))})),1),t.disabled?t._e():e("nav",{staticClass:"k-layout-toolbar"},[t.settings?e("k-button",{staticClass:"k-layout-toolbar-button",attrs:{title:t.$t("settings"),icon:"settings"},on:{click:t.openSettings}}):t._e(),e("k-button",{staticClass:"k-layout-toolbar-button",attrs:{icon:"angle-down"},on:{click:function(e){return t.$refs.options.toggle()}}}),e("k-dropdown-content",{ref:"options",attrs:{options:t.options,"align-x":"end"}}),e("k-sort-handle")],1)],1)}),[],!1,null,null,null,null).exports;const ao=ut({props:{blocks:Array,endpoints:Object,fieldsetGroups:Object,fieldsets:Object,id:String,isSelected:Boolean,width:{type:String,default:"1/1"}}},(function(){var t=this,e=t._self._c;return e("div",{staticClass:"k-column k-layout-column",style:{"--width":t.width},attrs:{id:t.id,tabindex:"0"},on:{dblclick:function(e){return t.$refs.blocks.choose(t.blocks.length)}}},[e("k-blocks",{ref:"blocks",attrs:{endpoints:t.endpoints,"fieldset-groups":t.fieldsetGroups,fieldsets:t.fieldsets,value:t.blocks,group:"layout"},on:{input:function(e){return t.$emit("input",e)}},nativeOn:{dblclick:function(t){t.stopPropagation()}}})],1)}),[],!1,null,null,null,null).exports;const uo=ut({props:{disabled:Boolean,empty:String,endpoints:Object,fieldsetGroups:Object,fieldsets:Object,layouts:Array,max:Number,selector:Object,settings:Object,value:Array},data(){return{current:null,nextIndex:null,rows:this.value,selected:null}},computed:{draggableOptions(){return{id:this._uid,handle:!0,list:this.rows}}},watch:{value(){this.rows=this.value}},methods:{copy(t,e){if(0===this.rows.length)return!1;const i=void 0!==e?this.rows[e]:this.rows;this.$helper.clipboard.write(JSON.stringify(i),t),this.$panel.notification.success({message:this.$t("copy.success",{count:i.length??1}),icon:"template"})},change(t,e){const i=e.columns.map((t=>t.width)),n=this.layouts.findIndex((t=>t.toString()===i.toString()));this.$panel.dialog.open({component:"k-layout-selector",props:{label:this.$t("field.layout.change"),layouts:this.layouts,selector:this.selector,value:this.layouts[n]},on:{submit:i=>{this.onChange(i,n,{rowIndex:t,layoutIndex:n,layout:e}),this.$panel.dialog.close()}}})},duplicate(t,e){const i=this.$helper.clone(e),n=this.updateIds(i);this.rows.splice(t+1,0,...n),this.save()},async onAdd(t){let e=await this.$api.post(this.endpoints.field+"/layout",{columns:t});this.rows.splice(this.nextIndex,0,e),this.save()},async onChange(t,e,i){if(e===this.layouts[i.layoutIndex])return;const n=i.layout,s=await this.$api.post(this.endpoints.field+"/layout",{attrs:n.attrs,columns:t}),o=n.columns.filter((t=>{var e;return(null==(e=null==t?void 0:t.blocks)?void 0:e.length)>0})),l=[];if(0===o.length)l.push(s);else{const t=Math.ceil(o.length/s.columns.length)*s.columns.length;for(let e=0;e{var n;return t.blocks=(null==(n=o[i+e])?void 0:n.blocks)??[],t})),t.columns.filter((t=>{var e;return null==(e=null==t?void 0:t.blocks)?void 0:e.length})).length&&l.push(t)}}this.rows.splice(i.rowIndex,1,...l),this.save()},async paste(t,e=this.rows.length){let i=await this.$api.post(this.endpoints.field+"/layout/paste",{json:this.$helper.clipboard.read(t)});i.length&&(this.rows.splice(e,0,...i),this.save()),this.$panel.notification.success({message:this.$t("paste.success",{count:i.length}),icon:"download"})},pasteboard(t){this.$panel.dialog.open({component:"k-block-pasteboard",on:{paste:e=>this.paste(e,t)}})},remove(t){const e=this.rows.findIndex((e=>e.id===t.id));-1!==e&&this.$delete(this.rows,e),this.save()},removeAll(){this.$panel.dialog.open({component:"k-remove-dialog",props:{text:this.$t("field.layout.delete.confirm.all")},on:{submit:()=>{this.rows=[],this.save(),this.$panel.dialog.close()}}})},save(){this.$emit("input",this.rows)},select(t){if(this.nextIndex=t,1===this.layouts.length)return this.onAdd(this.layouts[0]);this.$panel.dialog.open({component:"k-layout-selector",props:{layouts:this.layouts,selector:this.selector,value:null},on:{submit:t=>{this.onAdd(t),this.$panel.dialog.close()}}})},updateAttrs(t,e){this.rows[t].attrs=e,this.save()},updateColumn(t){this.rows[t.index].columns[t.columnIndex].blocks=t.blocks,this.save()},updateIds(t){return!1===Array.isArray(t)&&(t=[t]),t.map((t=>(t.id=this.$helper.uuid(),t.columns=t.columns.map((t=>(t.id=this.$helper.uuid(),t.blocks=t.blocks.map((t=>(t.id=this.$helper.uuid(),t))),t))),t)))}}},(function(){var t=this,e=t._self._c;return e("div",[t.rows.length?[e("k-draggable",t._b({staticClass:"k-layouts",on:{sort:t.save}},"k-draggable",t.draggableOptions,!1),t._l(t.rows,(function(i,n){return e("k-layout",t._b({key:i.id,attrs:{disabled:t.disabled,endpoints:t.endpoints,"fieldset-groups":t.fieldsetGroups,fieldsets:t.fieldsets,"is-selected":t.selected===i.id,layouts:t.layouts,settings:t.settings},on:{append:function(e){return t.select(n+1)},change:function(e){return t.change(n,i)},copy:function(e){return t.copy(e,n)},duplicate:function(e){return t.duplicate(n,i)},paste:function(e){return t.pasteboard(n+1)},prepend:function(e){return t.select(n)},remove:function(e){return t.remove(i)},select:function(e){t.selected=i.id},updateAttrs:function(e){return t.updateAttrs(n,e)},updateColumn:function(e){return t.updateColumn({layout:i,index:n,...e})}}},"k-layout",i,!1))})),1)]:e("k-empty",{staticClass:"k-layout-empty",attrs:{icon:"dashboard"},on:{click:function(e){return t.select(0)}}},[t._v(" "+t._s(t.empty??t.$t("field.layout.empty"))+" ")])],2)}),[],!1,null,null,null,null).exports;const co=ut({mixins:[Lt],inheritAttrs:!1,props:{cancelButton:{default:!1},label:{default(){return this.$t("field.layout.select")},type:String},layouts:{type:Array},selector:Object,submitButton:{default:!1},value:{type:Array}}},(function(){var t,e,i=this,n=i._self._c;return n("k-dialog",i._b({staticClass:"k-layout-selector",attrs:{size:(null==(t=i.selector)?void 0:t.size)??"medium"},on:{cancel:function(t){return i.$emit("cancel")},submit:function(t){return i.$emit("submit",i.value)}}},"k-dialog",i.$props,!1),[n("h3",{staticClass:"k-label"},[i._v(i._s(i.label))]),n("k-navigate",{staticClass:"k-layout-selector-options",style:{"--columns":Number((null==(e=i.selector)?void 0:e.columns)??3)},attrs:{axis:"x"}},i._l(i.layouts,(function(t,e){return n("button",{key:e,staticClass:"k-layout-selector-option",attrs:{"aria-current":i.value===t,"aria-label":t.join(","),value:t},on:{click:function(e){return i.$emit("input",t)}}},[n("k-grid",{attrs:{"aria-hidden":""}},i._l(t,(function(t,e){return n("k-column",{key:e,attrs:{width:t}})})),1)],1)})),0)],1)}),[],!1,null,null,null,null).exports,po={install(t){t.component("k-layout",ro),t.component("k-layout-column",ao),t.component("k-layouts",uo),t.component("k-layout-selector",co)}},ho={inheritAttrs:!1,props:{column:{default:()=>({}),type:Object},field:{default:()=>({}),type:Object},value:{}}},mo={props:{html:{type:Boolean}}};const fo=ut({mixins:[mo],inheritAttrs:!1,props:{bubbles:[Array,String]},computed:{items(){let t=this.bubbles;return"string"==typeof t&&(t=t.split(",")),t.map((t=>"string"===t?{text:t}:t))}}},(function(){var t=this,e=t._self._c;return e("ul",{staticClass:"k-bubbles"},t._l(t.items,(function(i,n){return e("li",{key:n},[e("k-bubble",t._b({attrs:{html:t.html}},"k-bubble",i,!1))],1)})),0)}),[],!1,null,null,null,null).exports;const go=ut({mixins:[ho,mo],props:{value:{default:()=>[],type:[Array,String]}},computed:{bubbles(){let t=this.value;const e=this.column.options??this.field.options??[];return"string"==typeof t&&(t=t.split(",")),(t??[]).map((t=>{"string"==typeof t&&(t={value:t,text:t});for(const i of e)i.value===t.value&&(t.text=i.text);return t}))}}},(function(){var t=this,e=t._self._c;return e("div",{staticClass:"k-bubbles-field-preview",class:t.$options.class},[e("k-bubbles",{attrs:{bubbles:t.bubbles,html:t.html}})],1)}),[],!1,null,null,null,null).exports;const ko=ut({extends:go,inheritAttrs:!1,class:"k-array-field-preview",computed:{bubbles(){return[{text:1===this.value.length?`1 ${this.$t("entry")}`:`${this.value.length} ${this.$t("entries")}`}]}}},null,null,!1,null,null,null,null).exports;const bo=ut({mixins:[ho],props:{value:String},computed:{text(){var t;if(!this.value)return;const e=this.$library.colors.toString(this.value,this.field.format,this.field.alpha),i=null==(t=this.field.options)?void 0:t.find((t=>this.$library.colors.toString(t.value,this.field.format,this.field.alpha)===e));return i?i.text:null}}},(function(){var t=this,e=t._self._c;return e("div",{staticClass:"k-color-field-preview"},[e("k-color-frame",{attrs:{color:t.value}}),t.text?[t._v(" "+t._s(t.text)+" ")]:t._e()],2)}),[],!1,null,null,null,null).exports;const vo=ut({mixins:[ho],inheritAttrs:!1,computed:{text(){return this.value}}},(function(){var t=this;return(0,t._self._c)("p",{staticClass:"k-text-field-preview",class:t.$options.class},[t._v(" "+t._s(t.column.before)+" "),t._t("default",(function(){return[t._v(t._s(t.text))]})),t._v(" "+t._s(t.column.after)+" ")],2)}),[],!1,null,null,null,null).exports;const yo=ut({extends:vo,props:{value:String},class:"k-date-field-preview",computed:{display(){return this.column.display??this.field.display},format(){var t;let e=this.display??"YYYY-MM-DD";return(null==(t=this.time)?void 0:t.display)&&(e+=" "+this.time.display),e},parsed(){return this.$library.dayjs(this.value)},text(){var t;return null==(t=this.parsed)?void 0:t.format(this.format)},time(){return this.column.time??this.field.time}}},null,null,!1,null,null,null,null).exports;const $o=ut({mixins:[ho],props:{value:[String,Object]},computed:{link(){return"object"==typeof this.value?this.value.href:this.value},text(){return"object"==typeof this.value?this.value.text:this.link}}},(function(){var t=this,e=t._self._c;return e("p",{staticClass:"k-url-field-preview",class:t.$options.class,attrs:{"data-link":t.link}},[t._v(" "+t._s(t.column.before)+" "),e("k-link",{attrs:{to:t.link},nativeOn:{click:function(t){t.stopPropagation()}}},[e("span",[t._v(t._s(t.text))])]),t._v(" "+t._s(t.column.after)+" ")],1)}),[],!1,null,null,null,null).exports;const wo=ut({extends:$o,class:"k-email-field-preview"},null,null,!1,null,null,null,null).exports;const xo=ut({extends:go,class:"k-files-field-preview",props:{html:{type:Boolean,default:!0}},computed:{bubbles(){return this.value.map((t=>({text:t.filename,link:t.link,image:t.image})))}}},null,null,!1,null,null,null,null).exports;const _o=ut({mixins:[ho],props:{value:Object},computed:{status(){var t;return{...this.$helper.page.status(null==(t=this.value)?void 0:t.status),...this.value}}}},(function(){var t=this,e=t._self._c;return t.value?e("k-button",t._b({staticClass:"k-flag-field-preview",attrs:{size:"md"}},"k-button",t.status,!1)):t._e()}),[],!1,null,null,null,null).exports;const So=ut({mixins:[ho],props:{value:String},computed:{html(){return this.value}}},(function(){var t=this,e=t._self._c;return e("div",{staticClass:"k-html-field-preview",class:t.$options.class},[t._v(" "+t._s(t.column.before)+" "),e("k-text",{attrs:{html:t.html}}),t._v(" "+t._s(t.column.after)+" ")],1)}),[],!1,null,null,null,null).exports;const Co=ut({mixins:[ho],props:{value:[Object]}},(function(){var t=this,e=t._self._c;return t.value?e("k-item-image",{staticClass:"k-image-field-preview",attrs:{image:t.value}}):t._e()}),[],!1,null,null,null,null).exports;const Oo=ut({extends:go,class:"k-object-field-preview",props:{value:[Array,Object]},computed:{bubbles(){return this.value?[{text:"{ ... }"}]:[]}}},null,null,!1,null,null,null,null).exports;const Ao=ut({extends:go,inheritAttrs:!1,class:"k-pages-field-preview",props:{html:{type:Boolean,default:!0}}},null,null,!1,null,null,null,null).exports;const Mo=ut({extends:yo,class:"k-time-field-preview",computed:{format(){return this.display??"HH:mm"},parsed(){return this.$library.dayjs.iso(this.value,"time")},text(){var t;return null==(t=this.parsed)?void 0:t.format(this.format)}}},null,null,!1,null,null,null,null).exports;const jo=ut({mixins:[ho],computed:{text(){return!1!==this.column.text?this.field.text:null}}},(function(){var t=this,e=t._self._c;return e("div",{staticClass:"k-toggle-field-preview"},[e("k-toggle-input",{attrs:{text:t.text,value:t.value},on:{input:function(e){return t.$emit("input",e)}},nativeOn:{click:function(t){t.stopPropagation()}}})],1)}),[],!1,null,null,null,null).exports;const To=ut({extends:go,class:"k-users-field-preview",computed:{bubble(){return this.value.map((t=>({text:t.username,link:t.link,image:t.image})))}}},null,null,!1,null,null,null,null).exports,Io={install(t){t.component("k-array-field-preview",ko),t.component("k-bubbles-field-preview",go),t.component("k-color-field-preview",bo),t.component("k-date-field-preview",yo),t.component("k-email-field-preview",wo),t.component("k-files-field-preview",xo),t.component("k-flag-field-preview",_o),t.component("k-html-field-preview",So),t.component("k-image-field-preview",Co),t.component("k-object-field-preview",Oo),t.component("k-pages-field-preview",Ao),t.component("k-text-field-preview",vo),t.component("k-toggle-field-preview",jo),t.component("k-time-field-preview",Mo),t.component("k-url-field-preview",$o),t.component("k-users-field-preview",To),t.component("k-list-field-preview",So),t.component("k-writer-field-preview",So),t.component("k-checkboxes-field-preview",go),t.component("k-multiselect-field-preview",go),t.component("k-radio-field-preview",go),t.component("k-select-field-preview",go),t.component("k-tags-field-preview",go),t.component("k-toggles-field-preview",go)}};const Eo=ut({mixins:[{props:{buttons:{type:Array,default:()=>[]},theme:{type:String,default:"light"}}}],methods:{close(){for(const t in this.$refs){const e=this.$refs[t][0];"function"==typeof(null==e?void 0:e.close)&&e.close()}}}},(function(){var t=this,e=t._self._c;return e("nav",{staticClass:"k-toolbar",attrs:{"data-theme":t.theme}},[t._l(t.buttons,(function(i,n){var s;return["|"===i?e("hr",{key:n}):i.when??1?e("k-button",{key:n,class:["k-toolbar-button",i.class],attrs:{current:i.current,disabled:i.disabled,icon:i.icon,title:i.label,tabindex:"0"},on:{click:function(e){var s,o;(null==(s=i.dropdown)?void 0:s.length)?t.$refs[n+"-dropdown"][0].toggle():null==(o=i.click)||o.call(i,e)}},nativeOn:{keydown:function(t){var e;null==(e=i.key)||e.call(i,t)}}}):t._e(),(i.when??1)&&(null==(s=i.dropdown)?void 0:s.length)?e("k-dropdown-content",{key:n+"-dropdown",ref:n+"-dropdown",refInFor:!0,attrs:{options:i.dropdown,theme:"dark"===t.theme?"light":"dark"}}):t._e()]}))],2)}),[],!1,null,null,null,null).exports;const Lo=ut({extends:qt,methods:{submit(){const t=this.values.href??"",e=this.values.title??"";return this.$panel.config.kirbytext?(null==e?void 0:e.length)>0?this.$emit("submit",`(email: ${t} text: ${e})`):this.$emit("submit",`(email: ${t})`):(null==e?void 0:e.length)>0?this.$emit("submit",`[${e}](mailto:${t})`):this.$emit("submit",`<${t}>`)}}},null,null,!1,null,null,null,null).exports;const Do=ut({extends:Wt,props:{fields:{default:()=>({href:{label:window.panel.$t("link"),type:"link",placeholder:window.panel.$t("url.placeholder"),icon:"url"},title:{label:window.panel.$t("link.text"),type:"text",icon:"title"}})}},methods:{submit(){const t=this.values.href??"",e=this.values.title??"";return this.$panel.config.kirbytext?(null==e?void 0:e.length)>0?this.$emit("submit",`(link: ${t} text: ${e})`):this.$emit("submit",`(link: ${t})`):(null==e?void 0:e.length)>0?this.$emit("submit",`[${e}](${t})`):this.$emit("submit",`<${t}>`)}}},null,null,!1,null,null,null,null).exports,Bo={install(t){t.component("k-toolbar",Eo),t.component("k-textarea-toolbar",Ms),t.component("k-toolbar-email-dialog",Lo),t.component("k-toolbar-link-dialog",Do)}};const qo=ut({props:{editor:{required:!0,type:Object},inline:{default:!0,type:Boolean},marks:{default:()=>["bold","italic","underline","strike","code","|","link","email","|","clear"],type:[Array,Boolean]},nodes:{default:!0,type:[Array,Boolean]}},data:()=>({isOpen:!1,position:{x:0,y:0}}),computed:{activeNode(){return Object.values(this.nodeButtons).find((t=>this.isNodeActive(t)))??!1},buttons(){var t,e,i;const n=[];if(this.hasNodes){const s=[];let o=0;for(const n in this.nodeButtons){const l=this.nodeButtons[n];s.push({current:(null==(t=this.activeNode)?void 0:t.id)===l.id,disabled:!1===(null==(i=null==(e=this.activeNode)?void 0:e.when)?void 0:i.includes(l.name)),icon:l.icon,label:l.label,click:()=>this.command(l.command??n)}),!0===l.separator&&o!==Object.keys(this.nodeButtons).length-1&&s.push("-"),o++}n.push({current:Boolean(this.activeNode),icon:this.activeNode.icon??"title",dropdown:s})}if(this.hasNodes&&this.hasMarks&&n.push("|"),this.hasMarks)for(const s in this.markButtons){const t=this.markButtons[s];"|"!==t?n.push({current:this.editor.activeMarks.includes(s),icon:t.icon,label:t.label,click:e=>this.command(t.command??s,e)}):n.push("|")}return n},hasMarks(){return this.$helper.object.length(this.markButtons)>0},hasNodes(){return this.$helper.object.length(this.nodeButtons)>1},markButtons(){const t=this.editor.buttons("mark");if(!1===this.marks||0===this.$helper.object.length(t))return{};if(!0===this.marks)return t;const e={};for(const[i,n]of this.marks.entries())"|"===n?e["divider"+i]="|":t[n]&&(e[n]=t[n]);return e},nodeButtons(){const t=this.editor.buttons("node");if(!1===this.nodes||0===this.$helper.object.length(t))return{};if("block+"!==this.editor.nodes.doc.content&&t.paragraph&&delete t.paragraph,!0===this.nodes)return t;const e={};for(const i of this.nodes)t[i]&&(e[i]=t[i]);return e}},methods:{close(t){t&&!1!==this.$el.contains(t.relatedTarget)||(this.isOpen=!1)},command(t,...e){this.$emit("command",t,...e)},isNodeActive(t){if(!1===this.editor.activeNodes.includes(t.name))return!1;if("paragraph"===t.name)return!1===this.editor.activeNodes.includes("listItem")&&!1===this.editor.activeNodes.includes("quote");if(t.attrs){if(void 0===Object.values(this.editor.activeNodeAttrs).find((e=>JSON.stringify(e)===JSON.stringify(t.attrs))))return!1}return!0},open(){this.isOpen=!0,this.inline&&this.$nextTick(this.setPosition)},setPosition(){const t=this.$el.getBoundingClientRect(),e=this.editor.element.getBoundingClientRect(),i=document.querySelector(".k-panel-menu").getBoundingClientRect(),{from:n,to:s}=this.editor.selection,o=this.editor.view.coordsAtPos(n),l=this.editor.view.coordsAtPos(s,!0),r=new DOMRect(o.left,o.top,l.right-o.left,l.bottom-o.top);let a=r.x-e.x+r.width/2-t.width/2,u=r.y-e.y-t.height-5;if(t.widthe.width&&(a=e.width-t.width);else{const n=e.x+a,s=n+t.width,o=i.width+20,l=20;nwindow.innerWidth-l&&(a-=s-(window.innerWidth-l))}this.position={x:a,y:u}}}},(function(){var t=this,e=t._self._c;return t.isOpen||!t.inline?e("k-toolbar",{ref:"toolbar",staticClass:"k-writer-toolbar",style:{top:t.position.y+"px",left:t.position.x+"px"},attrs:{buttons:t.buttons,"data-inline":t.inline,theme:t.inline?"dark":"light"}}):t._e()}),[],!1,null,null,null,null).exports,Po={install(t){t.component("k-writer-toolbar",qo),t.component("k-writer",Rn)}},No={install(t){t.component("k-counter",Ne),t.component("k-autocomplete",Pe),t.component("k-form",ze),t.component("k-form-buttons",Fe),t.component("k-field",Ye),t.component("k-fieldset",Ue),t.component("k-input",Ve),t.component("k-login",Ke),t.component("k-login-code",We),t.component("k-upload",Je),t.component("k-login-alert",Ge),t.use(Ti),t.use(lo),t.use(Ks),t.use(po),t.use(Io),t.use(Bo),t.use(Po)}},zo=()=>R((()=>import("./IndexView.min.js")),["./IndexView.min.js","./vendor.min.js"],import.meta.url),Fo=()=>R((()=>import("./DocsView.min.js")),["./DocsView.min.js","./Docs.min.js","./vendor.min.js"],import.meta.url),Ro=()=>R((()=>import("./PlaygroundView.min.js")),["./PlaygroundView.min.js","./Docs.min.js","./vendor.min.js"],import.meta.url),Yo={install(t){t.component("k-lab-index-view",zo),t.component("k-lab-docs-view",Fo),t.component("k-lab-playground-view",Ro)}};const Uo=ut({props:{cover:Boolean,ratio:String},computed:{ratioPadding(){return this.$helper.ratio(this.ratio)}},created(){window.panel.deprecated(" will be removed in a future version. Use the instead.")}},(function(){var t=this;return(0,t._self._c)("span",{staticClass:"k-aspect-ratio",style:{"padding-bottom":t.ratioPadding},attrs:{"data-cover":t.cover}},[t._t("default")],2)}),[],!1,null,null,null,null).exports;const Ho=ut({props:{align:{type:String,default:"start"}},mounted(){(this.$slots.left||this.$slots.center||this.$slots.right)&&window.panel.deprecated(": left/centre/right slots will be removed in a future version. Use with default slot only instead.")}},(function(){var t=this,e=t._self._c;return e("div",{staticClass:"k-bar",attrs:{"data-align":t.align}},[t.$slots.left||t.$slots.center||t.$slots.right?[t.$slots.left?e("div",{staticClass:"k-bar-slot",attrs:{"data-position":"left"}},[t._t("left")],2):t._e(),t.$slots.center?e("div",{staticClass:"k-bar-slot",attrs:{"data-position":"center"}},[t._t("center")],2):t._e(),t.$slots.right?e("div",{staticClass:"k-bar-slot",attrs:{"data-position":"right"}},[t._t("right")],2):t._e()]:t._t("default")],2)}),[],!1,null,null,null,null).exports;const Vo=ut({props:{align:{type:String,default:"start"},button:Boolean,height:String,icon:String,theme:{type:String},text:String,html:{type:Boolean}},computed:{element(){return this.button?"button":"div"},type(){return this.button?"button":null}}},(function(){var t=this,e=t._self._c;return e(t.element,{tag:"component",staticClass:"k-box",style:t.height?{"--box-height":t.height}:null,attrs:{"data-align":t.align,"data-theme":t.theme,type:t.type}},[t.icon?e("k-icon",{attrs:{type:t.icon}}):t._e(),t._t("default",(function(){return[t.html?e("k-text",{attrs:{html:t.text}}):e("k-text",[t._v(" "+t._s(t.text)+" ")])]}),null,{html:t.html,text:t.text})],2)}),[],!1,null,null,null,null).exports;const Ko=ut({inheritAttrs:!1,props:{back:String,color:String,element:{type:String,default:"li"},html:{type:Boolean},image:Object,link:String,text:String},created(){this.back&&window.panel.deprecated(": `back` prop will be removed in a future version. Use the `--bubble-back` CSS property instead."),this.color&&window.panel.deprecated(": `color` prop will be removed in a future version. Use the `--bubble-text` CSS property instead.")}},(function(){var t=this,e=t._self._c;return e(t.link?"k-link":"p",{tag:"component",staticClass:"k-bubble",style:{color:t.$helper.color(t.color),background:t.$helper.color(t.back)},attrs:{to:t.link,"data-has-text":Boolean(t.text)},nativeOn:{click:function(t){t.stopPropagation()}}},[t._t("image",(function(){var i;return[(null==(i=t.image)?void 0:i.src)?e("k-image-frame",t._b({},"k-image-frame",t.image,!1)):t.image?e("k-icon-frame",t._b({},"k-icon-frame",t.image,!1)):e("span")]})),t.text?[t.html?e("span",{staticClass:"k-bubble-text",domProps:{innerHTML:t._s(t.text)}}):e("span",{staticClass:"k-bubble-text"},[t._v(t._s(t.text))])]:t._e()],2)}),[],!1,null,null,null,null).exports;const Wo=ut({props:{width:{type:String,default:"1/1"},sticky:Boolean}},(function(){var t=this,e=t._self._c;return e("div",{staticClass:"k-column",style:{"--width":t.width},attrs:{"data-sticky":t.sticky}},[t.sticky?e("div",[t._t("default")],2):t._t("default")],2)}),[],!1,null,null,null,null).exports,Jo={props:{element:{type:String,default:"div"},fit:String,ratio:String,cover:Boolean,back:String,theme:String}};const Go=ut({mixins:[Jo],inheritAttrs:!1,computed:{background(){return this.$helper.color(this.back)}}},(function(){var t=this;return(0,t._self._c)(t.element,{tag:"compontent",staticClass:"k-frame",style:{"--fit":t.fit??(t.cover?"cover":"contain"),"--ratio":t.ratio,"--back":t.background},attrs:{"data-theme":t.theme}},[t._t("default")],2)}),[],!1,null,null,null,null).exports;const Xo=ut({mixins:[{mixins:[Jo],props:{color:String}}],inheritAttrs:!1},(function(){var t=this;return(0,t._self._c)("k-frame",t._b({staticClass:"k-color-frame",style:{color:t.color}},"k-frame",t.$props,!1),[t._t("default")],2)}),[],!1,null,null,null,null).exports;const Zo=ut({props:{disabled:{type:Boolean}},emits:["drop"],data:()=>({files:[],dragging:!1,over:!1}),methods:{cancel(){this.reset()},reset(){this.dragging=!1,this.over=!1},onDrop(t){return!0===this.disabled||!1===this.$helper.isUploadEvent(t)?this.reset():(this.$events.emit("dropzone.drop"),this.files=t.dataTransfer.files,this.$emit("drop",this.files),void this.reset())},onEnter(t){!1===this.disabled&&this.$helper.isUploadEvent(t)&&(this.dragging=!0)},onLeave(){this.reset()},onOver(t){!1===this.disabled&&this.$helper.isUploadEvent(t)&&(t.dataTransfer.dropEffect="copy",this.over=!0)}}},(function(){var t=this;return(0,t._self._c)("div",{staticClass:"k-dropzone",attrs:{"data-dragging":t.dragging,"data-over":t.over},on:{dragenter:t.onEnter,dragleave:t.onLeave,dragover:t.onOver,drop:t.onDrop}},[t._t("default")],2)}),[],!1,null,null,null,null).exports;const Qo=ut({props:{gutter:String,variant:String},created(){this.gutter&&window.panel.deprecated(': the `gutter` prop will be removed in a future version. Use `style="gap: "` or `variant` prop instead.')}},(function(){var t=this;return(0,t._self._c)("div",{staticClass:"k-grid",attrs:{"data-gutter":t.gutter,"data-variant":t.variant}},[t._t("default")],2)}),[],!1,null,null,null,null).exports;const tl=ut({props:{editable:{type:Boolean},tabs:Array},created(){this.tabs&&window.panel.deprecated(": `tabs` prop isn't supported anymore and has no effect. Use `` as standalone component instead."),(this.$slots.left||this.$slots.right)&&window.panel.deprecated(": left/right slots will be removed in a future version. Use `buttons` slot instead.")}},(function(){var t=this,e=t._self._c;return e("header",{staticClass:"k-header",attrs:{"data-has-buttons":Boolean(t.$slots.buttons||t.$slots.left||t.$slots.right)}},[e("h1",{staticClass:"k-header-title"},[t.editable?e("button",{staticClass:"k-header-title-button",attrs:{type:"button"},on:{click:function(e){return t.$emit("edit")}}},[e("span",{staticClass:"k-header-title-text"},[t._t("default")],2),e("span",{staticClass:"k-header-title-icon"},[e("k-icon",{attrs:{type:"edit"}})],1)]):e("span",{staticClass:"k-header-title-text"},[t._t("default")],2)]),t.$slots.buttons||t.$slots.left||t.$slots.right?e("div",{staticClass:"k-header-buttons"},[t._t("buttons"),t._t("left"),t._t("right")],2):t._e()])}),[],!1,null,null,null,null).exports,el={props:{alt:String,color:String,type:String}};const il=ut({mixins:[el]},(function(){var t=this,e=t._self._c;return e("svg",{staticClass:"k-icon",style:{color:t.$helper.color(t.color)},attrs:{"aria-label":t.alt,role:t.alt?"img":null,"aria-hidden":!t.alt,"data-type":t.type}},[e("use",{attrs:{"xlink:href":"#icon-"+t.type}})])}),[],!1,null,null,null,null).exports;const nl=ut({mixins:[{mixins:[Jo,el],props:{type:null,icon:String}}],inheritAttrs:!1,computed:{isEmoji(){return this.$helper.string.hasEmoji(this.icon)}}},(function(){var t=this,e=t._self._c;return e("k-frame",t._b({staticClass:"k-icon-frame",attrs:{element:"figure"}},"k-frame",t.$props,!1),[t.isEmoji?e("span",{attrs:{"data-type":"emoji"}},[t._v(t._s(t.icon))]):e("k-icon",t._b({},"k-icon",{color:t.color,type:t.icon,alt:t.alt},!1))],1)}),[],!1,null,null,null,null).exports;const sl=ut({mixins:[{mixins:[Jo],props:{alt:String,sizes:String,src:String,srcset:String}}],inheritAttrs:!1},(function(){var t=this,e=t._self._c;return e("k-frame",t._g(t._b({staticClass:"k-image-frame k-image",attrs:{element:"figure"}},"k-frame",t.$props,!1),t.$listeners),[t.src?e("img",{key:t.src,attrs:{alt:t.alt??"",src:t.src,srcset:t.srcset,sizes:t.sizes},on:{dragstart:function(t){t.preventDefault()}}}):t._e()])}),[],!1,null,null,null,null).exports;const ol=ut({mixins:[{props:{autofocus:{default:!0,type:Boolean},nested:{default:!1,type:Boolean},type:{default:"overlay",type:String},visible:{default:!1,type:Boolean}}}],inheritAttrs:!0,watch:{visible(t,e){t!==e&&this.toggle()}},mounted(){this.toggle()},methods:{cancel(){this.$emit("cancel"),this.close()},close(){if(!1!==this.$refs.overlay.open)return this.nested?this.onClose():void this.$refs.overlay.close()},focus(){this.$helper.focus(this.$refs.overlay)},onCancel(t){this.nested&&(t.preventDefault(),this.cancel())},onClick(t){t.target.matches(".k-portal")&&this.cancel()},onClose(){this.$emit("close")},open(){!0!==this.$refs.overlay.open&&this.$refs.overlay.showModal(),setTimeout((()=>{!0===this.autofocus&&this.focus(),this.$emit("open")}))},toggle(){!0===this.visible?this.open():this.close()}}},(function(){var t=this;return(0,t._self._c)("dialog",{ref:"overlay",staticClass:"k-overlay",attrs:{"data-type":t.type},on:{cancel:t.onCancel,mousedown:t.onClick,touchdown:t.onClick,close:t.onClose}},[t._t("default")],2)}),[],!1,null,null,null,null).exports;const ll=ut({props:{label:String,value:String,icon:String,info:String,theme:String,link:String,click:Function,dialog:{type:[String,Object]}},computed:{component(){return null!==this.target?"k-link":"div"},target(){return this.link?this.link:this.click?this.click:this.dialog?()=>this.$dialog(this.dialog):null}}},(function(){var t=this,e=t._self._c;return e(t.component,{tag:"component",staticClass:"k-stat",attrs:{"data-theme":t.theme,to:t.target}},[t.label?e("dt",{staticClass:"k-stat-label"},[t.icon?e("k-icon",{attrs:{type:t.icon}}):t._e(),t._v(" "+t._s(t.label)+" ")],1):t._e(),t.value?e("dd",{staticClass:"k-stat-value"},[t._v(t._s(t.value))]):t._e(),t.info?e("dd",{staticClass:"k-stat-info"},[t._v(t._s(t.info))]):t._e()])}),[],!1,null,null,null,null).exports;const rl=ut({props:{reports:{type:Array,default:()=>[]},size:{type:String,default:"large"}},methods:{component(t){return null!==this.target(t)?"k-link":"div"},target(t){return t.link?t.link:t.click?t.click:t.dialog?()=>this.$dialog(t.dialog):null}}},(function(){var t=this,e=t._self._c;return e("dl",{staticClass:"k-stats",attrs:{"data-size":t.size}},t._l(t.reports,(function(i,n){return e("k-stat",t._b({key:n},"k-stat",i,!1))})),1)}),[],!1,null,null,null,null).exports;const al=ut({inheritAttrs:!1,props:{columns:{type:Object,default:()=>({})},disabled:Boolean,fields:{type:Object,default:()=>({})},empty:String,index:{type:[Number,Boolean],default:1},rows:Array,options:{default:()=>[],type:[Array,Function]},pagination:[Object,Boolean],sortable:Boolean},data(){return{values:this.rows}},computed:{colspan(){let t=this.columnsCount;return this.hasIndexColumn&&t++,this.hasOptions&&t++,t},columnsCount(){return this.$helper.object.length(this.columns)},dragOptions(){return{disabled:!this.sortable,fallbackClass:"k-table-row-fallback",ghostClass:"k-table-row-ghost"}},hasIndexColumn(){return this.sortable||!1!==this.index},hasOptions(){var t;return this.$scopedSlots.options||(null==(t=this.options)?void 0:t.length)>0||Object.values(this.values).filter((t=>null==t?void 0:t.options)).length>0}},watch:{rows(){this.values=this.rows}},methods:{isColumnEmpty(t){return 0===this.rows.filter((e=>!1===this.$helper.object.isEmpty(e[t]))).length},label(t,e){return t.label??this.$helper.string.ucfirst(e)},onChange(t){this.$emit("change",t)},onCell(t){this.$emit("cell",t)},onCellUpdate({columnIndex:t,rowIndex:e,value:i}){this.values[e][t]=i,this.$emit("input",this.values)},onHeader(t){this.$emit("header",t)},onOption(t,e,i){this.$emit("option",t,e,i)},onSort(){this.$emit("input",this.values),this.$emit("sort",this.values)},width(t){return"string"!=typeof t?"auto":!1===t.includes("/")?t:this.$helper.ratio(t,"auto",!1)}}},(function(){var t=this,e=t._self._c;return e("div",{staticClass:"k-table",attrs:{"aria-disabled":t.disabled}},[e("table",{attrs:{"data-disabled":t.disabled,"data-indexed":t.hasIndexColumn}},[e("thead",[e("tr",[t.hasIndexColumn?e("th",{staticClass:"k-table-index-column",attrs:{"data-mobile":""}},[t._v(" # ")]):t._e(),t._l(t.columns,(function(i,n){return e("th",{key:n+"-header",staticClass:"k-table-column",style:{width:t.width(i.width)},attrs:{"data-align":i.align,"data-mobile":i.mobile},on:{click:function(e){return t.onHeader({column:i,columnIndex:n})}}},[t._t("header",(function(){return[t._v(" "+t._s(t.label(i,n))+" ")]}),null,{column:i,columnIndex:n,label:t.label(i,n)})],2)})),t.hasOptions?e("th",{staticClass:"k-table-options-column",attrs:{"data-mobile":""}}):t._e()],2)]),e("k-draggable",{attrs:{list:t.values,options:t.dragOptions,handle:!0,element:"tbody"},on:{change:t.onChange,end:t.onSort}},[0===t.rows.length?e("tr",[e("td",{staticClass:"k-table-empty",attrs:{colspan:t.colspan}},[t._v(" "+t._s(t.empty)+" ")])]):t._l(t.values,(function(i,n){return e("tr",{key:n},[t.hasIndexColumn?e("td",{staticClass:"k-table-index-column",attrs:{"data-sortable":t.sortable&&!1!==i.sortable,"data-mobile":""}},[t._t("index",(function(){return[e("div",{staticClass:"k-table-index",domProps:{textContent:t._s(t.index+n)}})]}),null,{row:i,rowIndex:n}),t.sortable&&!1!==i.sortable?e("k-sort-handle",{staticClass:"k-table-sort-handle"}):t._e()],2):t._e(),t._l(t.columns,(function(s,o){return e("k-table-cell",{key:n+"-"+o,staticClass:"k-table-column",style:{width:t.width(s.width)},attrs:{column:s,field:t.fields[o],row:i,mobile:s.mobile,value:i[o]},on:{input:function(e){return t.onCellUpdate({columnIndex:o,rowIndex:n,value:e})}},nativeOn:{click:function(e){return t.onCell({row:i,rowIndex:n,column:s,columnIndex:o})}}})})),t.hasOptions?e("td",{staticClass:"k-table-options-column",attrs:{"data-mobile":""}},[t._t("options",(function(){return[e("k-options-dropdown",{attrs:{options:i.options??t.options,text:(i.options??t.options).length>1},on:{option:function(e){return t.onOption(e,i,n)}}})]}),null,{row:i,rowIndex:n})],2):t._e()],2)}))],2)],1),t.pagination?e("k-pagination",t._b({staticClass:"k-table-pagination",on:{paginate:function(e){return t.$emit("paginate",e)}}},"k-pagination",t.pagination,!1)):t._e()],1)}),[],!1,null,null,null,null).exports;const ul=ut({inheritAttrs:!1,props:{column:Object,field:Object,mobile:{type:Boolean,default:!1},row:Object,value:{default:""}},computed:{component(){return this.$helper.isComponent(`k-${this.type}-field-preview`)?`k-${this.type}-field-preview`:this.$helper.isComponent(`k-table-${this.type}-cell`)?`k-table-${this.type}-cell`:Array.isArray(this.value)?"k-array-field-preview":"object"==typeof this.value?"k-object-field-preview":"k-text-field-preview"},type(){var t;return this.column.type??(null==(t=this.field)?void 0:t.type)}}},(function(){var t=this,e=t._self._c;return e("td",{staticClass:"k-table-cell",attrs:{"data-align":t.column.align,"data-mobile":t.mobile}},[!1===t.$helper.object.isEmpty(t.value)?e(t.component,{tag:"component",attrs:{column:t.column,field:t.field,row:t.row,value:t.value},on:{input:function(e){return t.$emit("input",e)}}}):t._e()],1)}),[],!1,null,null,null,null).exports;const cl=ut({props:{tab:String,tabs:{type:Array,default:()=>[]},theme:{type:String,default:"passive"}},data(){return{observer:null,visible:this.tabs,invisible:[]}},computed:{current(){const t=this.tabs.find((t=>t.name===this.tab))??this.tabs[0];return null==t?void 0:t.name},dropdown(){return this.invisible.map(this.button)}},watch:{tabs:{async handler(){var t;null==(t=this.observer)||t.disconnect(),await this.$nextTick(),this.$el instanceof Element&&(this.observer=new ResizeObserver(this.resize),this.observer.observe(this.$el))},immediate:!0}},destroyed(){var t;null==(t=this.observer)||t.disconnect()},methods:{button(t){return{link:t.link,current:t.name===this.current,icon:t.icon,title:t.label,text:t.label??t.text??t.name}},async resize(){const t=this.$el.offsetWidth;this.visible=this.tabs,this.invisible=[],await this.$nextTick();const e=[...this.$refs.visible].map((t=>t.$el.offsetWidth));let i=32;for(let n=0;nt)return this.visible=this.tabs.slice(0,n),void(this.invisible=this.tabs.slice(n))}}},(function(){var t=this,e=t._self._c;return t.tabs.length>1?e("nav",{staticClass:"k-tabs"},[t._l(t.visible,(function(i){return e("k-button",t._b({key:i.name,ref:"visible",refInFor:!0,staticClass:"k-tab-button",attrs:{variant:"dimmed"}},"k-button",t.btn=t.button(i),!1),[t._v(" "+t._s(t.btn.text)+" "),i.badge?e("span",{staticClass:"k-tabs-badge",attrs:{"data-theme":t.theme}},[t._v(" "+t._s(i.badge)+" ")]):t._e()])})),t.invisible.length?[e("k-button",{staticClass:"k-tab-button k-tabs-dropdown-button",attrs:{current:!!t.invisible.find((e=>t.tab===e.name)),title:t.$t("more"),icon:"dots",variant:"dimmed"},on:{click:function(e){return e.stopPropagation(),t.$refs.more.toggle()}}}),e("k-dropdown-content",{ref:"more",staticClass:"k-tabs-dropdown",attrs:{options:t.dropdown,"align-x":"end"}})]:t._e()],2):t._e()}),[],!1,null,null,null,null).exports;const dl=ut({props:{align:String},created(){window.panel.deprecated(" will be removed in a future version.")}},(function(){var t=this;return(0,t._self._c)("div",{staticClass:"k-view",attrs:{"data-align":t.align}},[t._t("default")],2)}),[],!1,null,null,null,null).exports,pl={install(t){t.component("k-aspect-ratio",Uo),t.component("k-bar",Ho),t.component("k-box",Vo),t.component("k-bubble",Ko),t.component("k-bubbles",fo),t.component("k-color-frame",Xo),t.component("k-column",Wo),t.component("k-dropzone",Zo),t.component("k-frame",Go),t.component("k-grid",Qo),t.component("k-header",tl),t.component("k-icon-frame",nl),t.component("k-image-frame",sl),t.component("k-image",sl),t.component("k-overlay",ol),t.component("k-stat",ll),t.component("k-stats",rl),t.component("k-table",al),t.component("k-table-cell",ul),t.component("k-tabs",cl),t.component("k-view",dl)}};const hl=ut({components:{draggable:()=>R((()=>import("./vuedraggable.min.js")),[],import.meta.url)},props:{data:Object,element:{type:String,default:"div"},handle:[String,Boolean],list:[Array,Object],move:Function,options:Object},emits:["change","end","sort","start"],computed:{dragOptions(){let t=this.handle;return!0===t&&(t=".k-sort-handle"),{fallbackClass:"k-sortable-fallback",fallbackOnBody:!0,forceFallback:!0,ghostClass:"k-sortable-ghost",handle:t,scroll:document.querySelector(".k-panel-main"),...this.options}}},methods:{onStart(t){this.$panel.drag.start("data",{}),this.$emit("start",t)},onEnd(t){this.$panel.drag.stop(),this.$emit("end",t)}}},(function(){var t=this;return(0,t._self._c)("draggable",t._b({staticClass:"k-draggable",attrs:{"component-data":t.data,tag:t.element,list:t.list,move:t.move},on:{change:function(e){return t.$emit("change",e)},end:t.onEnd,sort:function(e){return t.$emit("sort",e)},start:t.onStart},scopedSlots:t._u([{key:"footer",fn:function(){return[t._t("footer")]},proxy:!0}],null,!0)},"draggable",t.dragOptions,!1),[t._t("default")],2)}),[],!1,null,null,null,null).exports;const ml=ut({data:()=>({error:null}),errorCaptured(t){return this.$panel.debug&&window.console.warn(t),this.error=t,!1},render(){return this.error?this.$slots.error?this.$slots.error[0]:this.$scopedSlots.error?this.$scopedSlots.error({error:this.error}):Vue.h("k-box",{attrs:{theme:"negative"}},this.error.message??this.error):this.$slots.default[0]}},null,null,!1,null,null,null,null).exports;const fl=ut({props:{html:String},mounted(){try{let t=this.$refs.iframe.contentWindow.document;t.open(),t.write(this.html),t.close()}catch(t){console.error(t)}}},(function(){var t=this,e=t._self._c;return e("k-overlay",{staticClass:"k-fatal",attrs:{visible:!0}},[e("div",{staticClass:"k-fatal-box"},[e("div",{staticClass:"k-notification",attrs:{"data-theme":"negative"}},[e("p",[t._v("The JSON response could not be parsed")]),e("k-button",{attrs:{icon:"cancel"},on:{click:function(e){return e.stopPropagation(),t.$panel.notification.close()}}})],1),e("iframe",{ref:"iframe",staticClass:"k-fatal-iframe"})])])}),[],!1,null,null,null,null).exports;const gl=ut({icons:window.panel.plugins.icons,methods:{viewbox(t,e){const i=document.createElementNS("http://www.w3.org/2000/svg","svg");i.innerHTML=e,document.body.appendChild(i);const n=i.getBBox(),s=(n.width+2*n.x+(n.height+2*n.y))/2,o=Math.abs(s-16),l=Math.abs(s-24);return document.body.removeChild(i),o element with the corresponding viewBox attribute.`),"0 0 16 16"):"0 0 24 24"}}},(function(){var t=this,e=t._self._c;return e("svg",{staticClass:"k-icons",attrs:{"aria-hidden":"true",xmlns:"http://www.w3.org/2000/svg",overflow:"hidden"}},[e("defs",t._l(t.$options.icons,(function(i,n){return e("symbol",{key:n,attrs:{id:"icon-"+n,viewBox:t.viewbox(n,i)},domProps:{innerHTML:t._s(i)}})})),0)])}),[],!1,null,null,null,null).exports;const kl=ut({created(){window.panel.deprecated(' will be removed in a future version. Use instead.')}},(function(){var t=this._self._c;return t("span",{staticClass:"k-loader"},[t("k-icon",{staticClass:"k-loader-icon",attrs:{type:"loader"}})],1)}),[],!1,null,null,null,null).exports;const bl=ut({},(function(){var t=this,e=t._self._c;return t.$panel.notification.isOpen?e("div",{staticClass:"k-notification",attrs:{"data-theme":t.$panel.notification.theme}},[e("p",[t._v(t._s(t.$panel.notification.message))]),e("k-button",{attrs:{icon:"cancel"},on:{click:function(e){return t.$panel.notification.close()}}})],1):t._e()}),[],!1,null,null,null,null).exports;const vl=ut({},(function(){var t=this,e=t._self._c;return t.$panel.isOffline?e("div",{staticClass:"k-offline-warning"},[e("p",[e("k-icon",{attrs:{type:"bolt"}}),t._v(" "+t._s(t.$t("error.offline")))],1)]):t._e()}),[],!1,null,null,null,null).exports,yl=(t,e=!1)=>{if(t>=0&&t<=100)return!0;if(e)throw new Error("value has to be between 0 and 100");return!1};const $l=ut({props:{value:{type:Number,default:0,validator:yl}},data(){return{state:this.value}},watch:{value(t){this.state=t}},methods:{set(t){window.panel.deprecated(": `set` method will be removed in a future version. Use the `value` prop instead."),yl(t,!0),this.state=t}}},(function(){var t=this;return(0,t._self._c)("progress",{staticClass:"k-progress",attrs:{max:"100"},domProps:{value:t.state}},[t._v(t._s(t.state)+"%")])}),[],!1,null,null,null,null).exports;const wl=ut({},(function(){return(0,this._self._c)("k-button",{staticClass:"k-sort-handle k-sort-button",attrs:{title:this.$t("sort.drag"),icon:"sort","aria-hidden":"true"}})}),[],!1,null,null,null,null).exports,xl={install(t){t.component("k-draggable",hl),t.component("k-error-boundary",ml),t.component("k-fatal",fl),t.component("k-icon",il),t.component("k-icons",gl),t.component("k-loader",kl),t.component("k-notification",bl),t.component("k-offline-warning",vl),t.component("k-progress",$l),t.component("k-sort-handle",wl)}};const _l=ut({props:{crumbs:{type:Array,default:()=>[]},label:{type:String,default:"Breadcrumb"},view:Object},computed:{dropdown(){return this.segments.map((t=>({...t,text:t.label,icon:"angle-right"})))},segments(){const t=[];return this.view&&t.push({link:this.view.link,label:this.view.label??this.view.breadcrumbLabel,icon:this.view.icon,loading:this.$panel.isLoading}),[...t,...this.crumbs]}},created(){this.view&&window.panel.deprecated(": `view` prop will be removed in a future version. Use `crumbs` instead.")}},(function(){var t=this,e=t._self._c;return e("nav",{staticClass:"k-breadcrumb",attrs:{"aria-label":t.label}},[t.segments.length>1?e("div",{staticClass:"k-breadcrumb-dropdown"},[e("k-button",{attrs:{icon:"home"},on:{click:function(e){return t.$refs.dropdown.toggle()}}}),e("k-dropdown-content",{ref:"dropdown",attrs:{options:t.dropdown}})],1):t._e(),e("ol",t._l(t.segments,(function(i,n){return e("li",{key:n},[e("k-button",{staticClass:"k-breadcrumb-link",attrs:{icon:i.loading?"loader":i.icon,link:i.link,disabled:!i.link,text:i.text??i.label,title:i.text??i.label,current:n===t.segments.length-1&&"page",variant:"dimmed",size:"sm"}})],1)})),0)])}),[],!1,null,null,null,null).exports;const Sl=ut({props:{items:{type:Array},name:{default:"items",type:String},selected:{type:String},type:{default:"radio",type:String}}},(function(){var t=this,e=t._self._c;return e("nav",{staticClass:"k-browser"},[e("div",{staticClass:"k-browser-items"},t._l(t.items,(function(i){return e("label",{key:i.value,staticClass:"k-browser-item",attrs:{"aria-selected":t.selected===i.value}},[e("input",{attrs:{name:t.name,type:t.type},domProps:{checked:t.selected===i.value},on:{change:function(e){return t.$emit("select",i)}}}),i.image?e("k-item-image",{staticClass:"k-browser-item-image",attrs:{image:{...i.image,cover:!0,back:"black"}}}):t._e(),e("span",{staticClass:"k-browser-item-info"},[t._v(" "+t._s(i.label)+" ")])],1)})),0)])}),[],!1,null,null,null,null).exports;const Cl=ut({inheritAttrs:!1,props:{autofocus:Boolean,click:{type:Function,default:()=>{}},current:[String,Boolean],dialog:String,disabled:Boolean,drawer:String,dropdown:Boolean,element:String,icon:String,id:[String,Number],link:String,responsive:[Boolean,String],rel:String,role:String,selected:[String,Boolean],size:String,target:String,tabindex:String,text:[String,Number],theme:String,title:String,tooltip:String,type:{type:String,default:"button"},variant:String},computed:{attrs(){const t={"aria-current":this.current,"aria-disabled":this.disabled,"aria-selected":this.selected,"data-responsive":this.responsive,"data-size":this.size,"data-theme":this.theme,"data-variant":this.variant,id:this.id,tabindex:this.tabindex,title:this.title??this.tooltip};return"k-link"===this.component?(t.disabled=this.disabled,t.to=this.link,t.rel=this.rel,t.role=this.role,t.target=this.target):"button"===this.component&&(t.autofocus=this.autofocus,t.type=this.type),this.dropdown&&(t["aria-haspopup"]="menu",t["data-dropdown"]=this.dropdown),t},component(){return this.element?this.element:this.link?"k-link":"button"}},created(){this.tooltip&&window.panel.deprecated(": the `tooltip` prop will be removed in a future version. Use the `title` prop instead.")},methods:{focus(){var t,e;null==(e=(t=this.$el).focus)||e.call(t)},onClick(t){var e;return this.disabled?(t.preventDefault(),!1):this.dialog?this.$dialog(this.dialog):this.drawer?this.$drawer(this.drawer):(null==(e=this.click)||e.call(this,t),void this.$emit("click",t))}}},(function(){var t=this,e=t._self._c;return e(t.component,t._b({tag:"component",staticClass:"k-button",attrs:{"data-has-icon":Boolean(t.icon),"data-has-text":Boolean(t.text||t.$slots.default)},on:{click:t.onClick}},"component",t.attrs,!1),[t.icon?e("span",{staticClass:"k-button-icon"},[e("k-icon",{attrs:{type:t.icon}})],1):t._e(),t.text||t.$slots.default?e("span",{staticClass:"k-button-text"},[t._t("default",(function(){return[t._v(" "+t._s(t.text)+" ")]}))],2):t._e(),t.dropdown&&(t.text||t.$slots.default)?e("span",{staticClass:"k-button-arrow"},[e("k-icon",{attrs:{type:"angle-down"}})],1):t._e()])}),[],!1,null,null,null,null).exports;const Ol=ut({props:{buttons:Array,layout:String,variant:String,theme:String,size:String,responsive:Boolean}},(function(){var t=this,e=t._self._c;return e("div",{staticClass:"k-button-group",attrs:{"data-layout":t.layout}},[t.$slots.default?t._t("default"):t._l(t.buttons,(function(i,n){return e("k-button",t._b({key:n},"k-button",{variant:t.variant,theme:t.theme,size:t.size,responsive:t.responsive,...i},!1))}))],2)}),[],!1,null,null,null,null).exports;const Al=ut({props:{selected:{type:String}},data:()=>({files:[],page:null,view:"tree"}),methods:{selectFile(t){this.$emit("select",t)},async selectPage(t){this.page=t;const e="/"===t.id?"/site/files":"/pages/"+this.$api.pages.id(t.id)+"/files",{data:i}=await this.$api.get(e,{select:"filename,id,panelImage,url,uuid"});this.files=i.map((t=>({label:t.filename,image:t.panelImage,id:t.id,url:t.url,uuid:t.uuid,value:t.uuid??t.url}))),this.view="files"},async togglePage(){await this.$nextTick(),this.$refs.tree.scrollIntoView({behaviour:"smooth",block:"nearest",inline:"nearest"})}}},(function(){var t,e,i=this,n=i._self._c;return n("div",{staticClass:"k-file-browser",attrs:{"data-view":i.view}},[n("div",{staticClass:"k-file-browser-layout"},[n("aside",{ref:"tree",staticClass:"k-file-browser-tree"},[n("k-page-tree",{attrs:{current:null==(t=i.page)?void 0:t.value},on:{select:i.selectPage,toggleBranch:i.togglePage}})],1),n("div",{ref:"items",staticClass:"k-file-browser-items"},[n("k-button",{staticClass:"k-file-browser-back-button",attrs:{icon:"angle-left",text:null==(e=i.page)?void 0:e.label},on:{click:function(t){i.view="tree"}}}),i.files.length?n("k-browser",{attrs:{items:i.files,selected:i.selected},on:{select:i.selectFile}}):i._e()],1)])])}),[],!1,null,null,null,null).exports;const Ml=ut({props:{disabled:Boolean,rel:String,tabindex:[String,Number],target:String,title:String,to:[String,Function]},emits:["click"],computed:{href(){return"function"==typeof this.to?"":"/"!==this.to[0]||this.target?!0===this.to.includes("@")&&!1===this.to.includes("/")&&!1===this.to.startsWith("mailto:")?"mailto:"+this.to:this.to:this.$url(this.to)},relAttr(){return"_blank"===this.target?"noreferrer noopener":this.rel}},methods:{isRoutable(t){if(t.metaKey||t.altKey||t.ctrlKey||t.shiftKey)return!1;if(t.defaultPrevented)return!1;if(void 0!==t.button&&0!==t.button)return!1;if(this.target)return!1;if("string"==typeof this.href){if(this.href.includes("://")||this.href.startsWith("//"))return!1;if(this.href.includes("mailto:"))return!1}return!0},onClick(t){if(!0===this.disabled)return t.preventDefault(),!1;"function"==typeof this.to&&(t.preventDefault(),this.to()),this.isRoutable(t)&&(t.preventDefault(),this.$go(this.to)),this.$emit("click",t)}}},(function(){var t=this,e=t._self._c;return t.to&&!t.disabled?e("a",{ref:"link",staticClass:"k-link",attrs:{href:t.href,rel:t.relAttr,tabindex:t.tabindex,target:t.target,title:t.title},on:{click:t.onClick}},[t._t("default")],2):e("span",{staticClass:"k-link",attrs:{title:t.title,"aria-disabled":""}},[t._t("default")],2)}),[],!1,null,null,null,null).exports;const jl=ut({props:{tab:String,tabs:{type:Array,default:()=>[]}},computed:{withBadges(){const t=Object.keys(this.$store.getters["content/changes"]());return this.tabs.map((e=>{const i=[];for(const t in e.columns)for(const n in e.columns[t].sections)if("fields"===e.columns[t].sections[n].type)for(const s in e.columns[t].sections[n].fields)i.push(s);return e.badge=i.filter((e=>t.includes(e.toLowerCase()))).length,e}))}}},(function(){var t=this;return(0,t._self._c)("k-tabs",{staticClass:"k-model-tabs",attrs:{tab:t.tab,tabs:t.withBadges,theme:"notice"}})}),[],!1,null,null,null,null).exports;const Tl=ut({props:{axis:String,disabled:Boolean,element:{type:String,default:"div"},select:{type:String,default:":where(button, a):not(:disabled)"}},computed:{keys(){switch(this.axis){case"x":return{ArrowLeft:this.prev,ArrowRight:this.next};case"y":return{ArrowUp:this.prev,ArrowDown:this.next};default:return{ArrowLeft:this.prev,ArrowRight:this.next,ArrowUp:this.prev,ArrowDown:this.next}}}},mounted(){this.$el.addEventListener("keydown",this.keydown)},destroyed(){this.$el.removeEventListener("keydown",this.keydown)},methods:{focus(t=0,e){this.move(t,e)},keydown(t){var e;if(this.disabled)return!1;null==(e=this.keys[t.key])||e.apply(this,[t])},move(t=0,e){var i;const n=[...this.$el.querySelectorAll(this.select)];let s=n.findIndex((t=>t===document.activeElement||t.contains(document.activeElement)));switch(-1===s&&(s=0),t){case"first":t=0;break;case"next":t=s+1;break;case"last":t=n.length-1;break;case"prev":t=s-1}t<0?this.$emit("prev"):t>=n.length?this.$emit("next"):null==(i=n[t])||i.focus(),null==e||e.preventDefault()},next(t){this.move("next",t)},prev(t){this.move("prev",t)}}},(function(){var t=this;return(0,t._self._c)(t.element,{tag:"component",staticClass:"k-navigate"},[t._t("default")],2)}),[],!1,null,null,null,null).exports;const Il=ut({name:"k-tree",inheritAttrs:!1,props:{element:{type:String,default:"k-tree"},current:{type:String},items:{type:[Array,Object]},level:{default:0,type:Number}},data(){return{state:this.items}},methods:{arrow:t=>!0===t.loading?"loader":t.open?"angle-down":"angle-right",close(t){this.$set(t,"open",!1),this.$emit("close",t)},open(t){this.$set(t,"open",!0),this.$emit("open",t)},select(t){this.$emit("select",t)},toggle(t){this.$emit("toggle",t),!0===t.open?this.close(t):this.open(t)}}},(function(){var t=this,e=t._self._c;return e("ul",{staticClass:"k-tree",class:t.$options.name,style:{"--tree-level":t.level}},t._l(t.state,(function(i,n){return e("li",{key:n,attrs:{"aria-expanded":i.open,"aria-current":i.value===t.current}},[e("p",{staticClass:"k-tree-branch",attrs:{"data-has-subtree":i.hasChildren&&i.open}},[e("button",{staticClass:"k-tree-toggle",attrs:{disabled:!i.hasChildren,type:"button"},on:{click:function(e){return t.toggle(i)}}},[e("k-icon",{attrs:{type:t.arrow(i)}})],1),e("button",{staticClass:"k-tree-folder",attrs:{disabled:i.disabled,type:"button"},on:{click:function(e){return t.select(i)},dblclick:function(e){return t.toggle(i)}}},[e("k-icon-frame",{attrs:{icon:i.icon??"folder"}}),e("span",{staticClass:"k-tree-folder-label"},[t._v(t._s(i.label))])],1)]),i.hasChildren&&i.open?[e(t.$options.name,t._b({tag:"component",attrs:{items:i.children,level:t.level+1},on:{close:function(e){return t.$emit("close",e)},open:function(e){return t.$emit("open",e)},select:function(e){return t.$emit("select",e)},toggle:function(e){return t.$emit("toggle",e)}}},"component",t.$props,!1))]:t._e()],2)})),0)}),[],!1,null,null,null,null).exports;const El=ut({name:"k-page-tree",extends:Il,inheritAttrs:!1,props:{root:{default:!0,type:Boolean},current:{type:String},move:{type:String}},data:()=>({state:[]}),async created(){if(this.items)this.state=this.items;else{const t=await this.load(null);await this.open(t[0]),this.state=this.root?t:t[0].children}},methods:{async load(t){return await this.$panel.get("site/tree",{query:{move:this.move??null,parent:t}})},async open(t){if(!1===t.hasChildren)return!1;this.$set(t,"loading",!0),"string"==typeof t.children&&(t.children=await this.load(t.children)),this.$set(t,"open",!0),this.$set(t,"loading",!1)}}},null,null,!1,null,null,null,null).exports;const Ll=ut({props:{details:Boolean,limit:{type:Number,default:10},page:{type:Number,default:1},total:{type:Number,default:0},validate:{type:Function,default:()=>Promise.resolve()}},computed:{end(){return Math.min(this.start-1+this.limit,this.total)},detailsText(){return 1===this.limit?this.start:this.start+"-"+this.end},offset(){return this.start-1},pages(){return Math.ceil(this.total/this.limit)},start(){return(this.page-1)*this.limit+1}},methods:{async goTo(t){var e;try{await this.validate(t),null==(e=this.$refs.dropdown)||e.close();const i=((t=Math.max(1,Math.min(t,this.pages)))-1)*this.limit+1;this.$emit("paginate",{page:t,start:i,end:Math.min(i-1+this.limit,this.total),limit:this.limit,offset:i-1,total:this.total})}catch(i){}},prev(){this.goTo(this.page-1)},next(){this.goTo(this.page+1)}}},(function(){var t=this,e=t._self._c;return t.pages>1?e("k-button-group",{staticClass:"k-pagination",attrs:{layout:"collapsed"},nativeOn:{keydown:[function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"left",37,e.key,["Left","ArrowLeft"])||"button"in e&&0!==e.button?null:t.prev.apply(null,arguments)},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"right",39,e.key,["Right","ArrowRight"])||"button"in e&&2!==e.button?null:t.next.apply(null,arguments)}]}},[e("k-button",{attrs:{disabled:t.start<=1,title:t.$t("prev"),icon:"angle-left",size:"xs",variant:"filled"},on:{click:t.prev}}),t.details?[e("k-button",{staticClass:"k-pagination-details",attrs:{disabled:t.total<=t.limit,text:t.total>1?`${t.detailsText} / ${t.total}`:t.total,size:"xs",variant:"filled"},on:{click:function(e){return t.$refs.dropdown.toggle()}}}),e("k-dropdown-content",{ref:"dropdown",staticClass:"k-pagination-selector",attrs:{"align-x":"end"},nativeOn:{keydown:[function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"left",37,e.key,["Left","ArrowLeft"])||"button"in e&&0!==e.button?null:void e.stopPropagation()},function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"right",39,e.key,["Right","ArrowRight"])||"button"in e&&2!==e.button?null:void e.stopPropagation()}]}},[e("form",{attrs:{method:"dialog"},on:{click:function(t){t.stopPropagation()},submit:function(e){return t.goTo(t.$refs.page.value)}}},[e("label",{attrs:{for:t._uid}},[t._v(t._s(t.$t("pagination.page"))+":")]),e("select",{ref:"page",attrs:{id:t._uid,autofocus:!0}},t._l(t.pages,(function(i){return e("option",{key:i,domProps:{selected:t.page===i,value:i}},[t._v(" "+t._s(i)+" ")])})),0),e("k-button",{attrs:{type:"submit",icon:"check"}})],1)])]:t._e(),e("k-button",{attrs:{disabled:t.end>=t.total,title:t.$t("next"),icon:"angle-right",size:"xs",variant:"filled"},on:{click:t.next}})],2):t._e()}),[],!1,null,null,null,null).exports;const Dl=ut({props:{prev:{type:[Boolean,Object],default:!1},next:{type:[Boolean,Object],default:!1}},computed:{buttons(){return[{...this.button(this.prev),icon:"angle-left"},{...this.button(this.next),icon:"angle-right"}]},isFullyDisabled(){return 0===this.buttons.filter((t=>!t.disabled)).length}},methods:{button:t=>t||{disabled:!0,link:"#"}}},(function(){var t=this,e=t._self._c;return t.isFullyDisabled?t._e():e("k-button-group",{staticClass:"k-prev-next",attrs:{buttons:t.buttons,layout:"collapsed",size:"xs"}})}),[],!1,null,null,null,null).exports;const Bl=ut({props:{disabled:Boolean,image:{type:Object},removable:Boolean},computed:{isRemovable(){return this.removable&&!this.disabled}},methods:{remove(){this.isRemovable&&this.$emit("remove")},focus(){this.$refs.button.focus()}}},(function(){var t=this,e=t._self._c;return e("button",{ref:"button",staticClass:"k-tag",attrs:{"aria-disabled":t.disabled,"data-has-image":Boolean(t.image),"data-has-toggle":t.isRemovable,type:"button"},on:{keydown:function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"delete",[8,46],e.key,["Backspace","Delete","Del"])?null:(e.preventDefault(),t.remove.apply(null,arguments))}}},[t._t("image",(function(){var i;return[(null==(i=t.image)?void 0:i.src)?e("k-image-frame",t._b({staticClass:"k-tag-image"},"k-image-frame",t.image,!1)):t.image?e("k-icon-frame",t._b({staticClass:"k-tag-image"},"k-icon-frame",t.image,!1)):t._e()]})),t.$slots.default?e("span",{staticClass:"k-tag-text"},[t._t("default")],2):t._e(),t.isRemovable?e("k-icon-frame",{staticClass:"k-tag-toggle",attrs:{icon:"cancel-small"},nativeOn:{click:function(e){return e.stopPropagation(),t.remove.apply(null,arguments)}}}):t._e()],2)}),[],!1,null,null,null,null).exports;const ql=ut({inheritAttrs:!1,props:{icon:String,id:[String,Number],responsive:Boolean,theme:String,tooltip:String},created(){window.panel.deprecated(' will be removed in a future version. Use instead.')}},(function(){var t=this,e=t._self._c;return e("span",{staticClass:"k-button",attrs:{id:t.id,"data-disabled":!0,"data-responsive":t.responsive,"data-theme":t.theme,title:t.tooltip}},[t.icon?e("k-icon",{staticClass:"k-button-icon",attrs:{type:t.icon,alt:t.tooltip}}):t._e(),t.$slots.default?e("span",{staticClass:"k-button-text"},[t._t("default")],2):t._e()],1)}),[],!1,null,null,null,null).exports;const Pl=ut({inheritAttrs:!1,props:{autofocus:Boolean,current:[String,Boolean],icon:String,id:[String,Number],link:String,rel:String,responsive:Boolean,role:String,target:String,tabindex:String,theme:String,tooltip:String},created(){window.panel.deprecated(' will be removed in a future version. Use instead.')},methods:{focus(){this.$el.focus()}}},(function(){var t=this,e=t._self._c;return e("k-link",{staticClass:"k-button",attrs:{id:t.id,"aria-current":t.current,autofocus:t.autofocus,"data-theme":t.theme,"data-responsive":t.responsive,rel:t.rel,role:t.role,tabindex:t.tabindex,target:t.target,title:t.tooltip,to:t.link}},[t.icon?e("k-icon",{staticClass:"k-button-icon",attrs:{type:t.icon,alt:t.tooltip}}):t._e(),t.$slots.default?e("span",{staticClass:"k-button-text"},[t._t("default")],2):t._e()],1)}),[],!1,null,null,null,null).exports;const Nl=ut({inheritAttrs:!1,props:{autofocus:Boolean,click:{type:Function,default:()=>{}},current:[String,Boolean],icon:String,id:[String,Number],responsive:Boolean,role:String,tabindex:String,theme:String,tooltip:String,type:{type:String,default:"button"}},created(){window.panel.deprecated(" will be removed in a future version. Use instead.")}},(function(){var t=this,e=t._self._c;return e("button",{staticClass:"k-button",attrs:{id:t.id,"aria-current":t.current,autofocus:t.autofocus,"data-theme":t.theme,"data-responsive":t.responsive,role:t.role,tabindex:t.tabindex,title:t.tooltip,type:t.type},on:{click:t.click}},[t.icon?e("k-icon",{staticClass:"k-button-icon",attrs:{type:t.icon,alt:t.tooltip}}):t._e(),t.$slots.default?e("span",{staticClass:"k-button-text"},[t._t("default")],2):t._e()],1)}),[],!1,null,null,null,null).exports,zl={install(t){t.component("k-breadcrumb",_l),t.component("k-browser",Sl),t.component("k-button",Cl),t.component("k-button-group",Ol),t.component("k-file-browser",Al),t.component("k-link",Ml),t.component("k-model-tabs",jl),t.component("k-navigate",Tl),t.component("k-page-tree",El),t.component("k-pagination",Ll),t.component("k-prev-next",Dl),t.component("k-tag",Bl),t.component("k-tags",Gn),t.component("k-tree",Il),t.component("k-button-disabled",ql),t.component("k-button-link",Pl),t.component("k-button-native",Nl)}};const Fl=ut({props:{buttons:Array,headline:String,invalid:Boolean,label:String,link:String,required:Boolean}},(function(){var t=this,e=t._self._c;return e("section",{staticClass:"k-section",attrs:{"data-invalid":t.invalid}},[t.label||t.headline||t.buttons||t.$slots.options?e("header",{staticClass:"k-section-header"},[e("k-label",{attrs:{invalid:t.invalid,link:t.link,required:t.required,title:t.label??t.headline,type:"section"}},[t._v(" "+t._s(t.label??t.headline)+" ")]),t._t("options",(function(){return[t.buttons?e("k-button-group",{staticClass:"k-section-buttons",attrs:{buttons:t.buttons,size:"xs",variant:"filled"}}):t._e()]}))],2):t._e(),t._t("default")],2)}),[],!1,null,null,null,null).exports;const Rl=ut({props:{empty:String,blueprint:String,lock:[Boolean,Object],parent:String,tab:Object},computed:{content(){return this.$store.getters["content/values"]()}},methods:{exists(t){return this.$helper.isComponent(`k-${t}-section`)}}},(function(){var t=this,e=t._self._c;return 0===t.tab.columns.length?e("k-box",{attrs:{html:!0,text:t.empty,theme:"info"}}):e("k-grid",{staticClass:"k-sections",attrs:{variant:"columns"}},t._l(t.tab.columns,(function(i,n){return e("k-column",{key:t.parent+"-column-"+n,attrs:{width:i.width,sticky:i.sticky}},[t._l(i.sections,(function(s,o){return[t.$helper.field.isVisible(s,t.content)?[t.exists(s.type)?e("k-"+s.type+"-section",t._b({key:t.parent+"-column-"+n+"-section-"+o+"-"+t.blueprint,tag:"component",class:"k-section-name-"+s.name,attrs:{column:i.width,lock:t.lock,name:s.name,parent:t.parent,timestamp:t.$panel.view.timestamp},on:{submit:function(e){return t.$emit("submit",e)}}},"component",s,!1)):[e("k-box",{key:t.parent+"-column-"+n+"-section-"+o,attrs:{text:t.$t("error.section.type.invalid",{type:s.type}),icon:"alert",theme:"negative"}})]]:t._e()]}))],2)})),1)}),[],!1,null,null,null,null).exports,Yl={props:{blueprint:String,lock:[Boolean,Object],help:String,name:String,parent:String,timestamp:Number},methods:{load(){return this.$api.get(this.parent+"/sections/"+this.name)}}};const Ul=ut({mixins:[Yl],inheritAttrs:!1,data:()=>({fields:{},isLoading:!0,issue:null}),computed:{values(){return this.$store.getters["content/values"]()}},watch:{timestamp(){this.fetch()}},created(){this.onInput=zt(this.onInput,50),this.fetch()},methods:{async fetch(){try{const t=await this.load();this.fields=t.fields;for(const e in this.fields)this.fields[e].section=this.name,this.fields[e].endpoints={field:this.parent+"/fields/"+e,section:this.parent+"/sections/"+this.name,model:this.parent}}catch(t){this.issue=t}finally{this.isLoading=!1}},onInput(t,e,i){this.$store.dispatch("content/update",[i,t[i]])},onSubmit(t){this.$store.dispatch("content/update",[null,t]),this.$events.emit("keydown.cmd.s",t)}}},(function(){var t=this,e=t._self._c;return t.isLoading?t._e():e("k-section",{staticClass:"k-fields-section",attrs:{headline:t.issue?"Error":null}},[t.issue?e("k-box",{attrs:{text:t.issue.message,html:!1,icon:"alert",theme:"negative"}}):t._e(),e("k-form",{attrs:{fields:t.fields,validate:!0,value:t.values,disabled:t.lock&&"lock"===t.lock.state},on:{input:t.onInput,submit:t.onSubmit}})],1)}),[],!1,null,null,null,null).exports;const Hl=ut({inheritAttrs:!1,props:{blueprint:String,column:String,parent:String,name:String,timestamp:Number},data:()=>({data:[],error:null,isLoading:!1,isProcessing:!1,options:{columns:{},empty:null,headline:null,help:null,layout:"list",link:null,max:null,min:null,size:null,sortable:null},pagination:{page:null},searchterm:null,searching:!1}),computed:{addIcon:()=>"add",buttons(){let t=[];return this.canSearch&&t.push({icon:"filter",text:this.$t("filter"),click:this.onSearchToggle,responsive:!0}),this.canAdd&&t.push({icon:this.addIcon,text:this.$t("add"),click:this.onAdd,responsive:!0}),t},canAdd:()=>!0,canDrop:()=>!1,canSearch(){return this.options.search},collection(){return{columns:this.options.columns,empty:this.emptyPropsWithSearch,layout:this.options.layout,help:this.options.help,items:this.items,pagination:this.pagination,sortable:!this.isProcessing&&this.options.sortable,size:this.options.size}},emptyProps(){return{icon:"page",text:this.$t("pages.empty")}},emptyPropsWithSearch(){return{...this.emptyProps,text:this.searching?this.$t("search.results.none"):this.options.empty??this.emptyProps.text}},items(){return this.data},isInvalid(){var t;return!((null==(t=this.searchterm)?void 0:t.length)>0)&&(!!(this.options.min&&this.data.lengththis.options.max))},paginationId(){return"kirby$pagination$"+this.parent+"/"+this.name},type:()=>"models"},watch:{searchterm(){this.search()},timestamp(){this.reload()}},created(){this.search=zt(this.search,200),this.load()},methods:{async load(t){this.isProcessing=!0,t||(this.isLoading=!0);const e=this.pagination.page??localStorage.getItem(this.paginationId)??1;try{const t=await this.$api.get(this.parent+"/sections/"+this.name,{page:e,searchterm:this.searchterm});this.options=t.options,this.pagination=t.pagination,this.data=t.data}catch(i){this.error=i.message}finally{this.isProcessing=!1,this.isLoading=!1}},onAction(){},onAdd(){},onChange(){},onDrop(){},onSort(){},onPaginate(t){localStorage.setItem(this.paginationId,t.page),this.pagination=t,this.reload()},onSearchToggle(){this.searching=!this.searching,this.searchterm=null},async reload(){await this.load(!0)},async search(){this.pagination.page=0,await this.reload()},update(){this.reload(),this.$events.emit("model.update")}}},(function(){var t=this,e=t._self._c;return!1===t.isLoading?e("k-section",{class:`k-models-section k-${t.type}-section`,attrs:{buttons:t.buttons,"data-processing":t.isProcessing,headline:t.options.headline??" ",invalid:t.isInvalid,link:t.options.link,required:Boolean(t.options.min)}},[t.error?e("k-box",{attrs:{icon:"alert",theme:"negative"}},[e("k-text",{attrs:{size:"small"}},[e("strong",[t._v(" "+t._s(t.$t("error.section.notLoaded",{name:t.name}))+": ")]),t._v(" "+t._s(t.error)+" ")])],1):[e("k-dropzone",{attrs:{disabled:!t.canDrop},on:{drop:t.onDrop}},[t.searching&&t.options.search?e("k-input",{staticClass:"k-models-section-search",attrs:{autofocus:!0,placeholder:t.$t("filter")+" …",value:t.searchterm,icon:"search",type:"text"},on:{input:function(e){t.searchterm=e},keydown:function(e){return!e.type.indexOf("key")&&t._k(e.keyCode,"esc",27,e.key,["Esc","Escape"])?null:t.onSearchToggle.apply(null,arguments)}}}):t._e(),e("k-collection",t._g(t._b({on:{action:t.onAction,change:t.onChange,sort:t.onSort,paginate:t.onPaginate}},"k-collection",t.collection,!1),t.canAdd?{empty:t.onAdd}:{}))],1)]],2):t._e()}),[],!1,null,null,null,null).exports;const Vl=ut({extends:Hl,computed:{addIcon:()=>"upload",canAdd(){return this.$panel.permissions.files.create&&!1!==this.options.upload},canDrop(){return!1!==this.canAdd},emptyProps(){return{icon:"image",text:this.$t("files.empty")}},items(){return this.data.map((t=>(t.sortable=this.options.sortable,t.column=this.column,t.options=this.$dropdown(t.link,{query:{view:"list",update:this.options.sortable,delete:this.data.length>this.options.min}}),t.data={"data-id":t.id,"data-template":t.template},t)))},type:()=>"files",uploadOptions(){return{...this.options.upload,url:this.$panel.urls.api+"/"+this.options.upload.api,on:{complete:()=>{this.$panel.notification.success({context:"view"})}}}}},created(){this.$events.on("model.update",this.reload),this.$events.on("file.sort",this.reload)},destroyed(){this.$events.off("model.update",this.reload),this.$events.off("file.sort",this.reload)},methods:{onAction(t,e){"replace"===t&&this.replace(e)},onAdd(){this.canAdd&&this.$panel.upload.pick(this.uploadOptions)},onDrop(t){this.canAdd&&this.$panel.upload.open(t,this.uploadOptions)},async onSort(t){if(!1===this.options.sortable)return!1;this.isProcessing=!0;try{await this.$api.patch(this.options.apiUrl+"/files/sort",{files:t.map((t=>t.id)),index:this.pagination.offset}),this.$panel.notification.success(),this.$events.emit("file.sort")}catch(e){this.$panel.error(e),this.reload()}finally{this.isProcessing=!1}},replace(t){this.$panel.upload.replace(t,this.uploadOptions)}}},null,null,!1,null,null,null,null).exports;const Kl=ut({mixins:[Yl],inheritAttrs:!1,data:()=>({icon:null,label:null,text:null,theme:null}),async created(){const t=await this.load();this.icon=t.icon,this.label=t.label,this.text=t.text,this.theme=t.theme??"info"}},(function(){var t=this,e=t._self._c;return e("k-section",{staticClass:"k-info-section",attrs:{headline:t.label}},[e("k-box",{attrs:{html:!0,icon:t.icon,text:t.text,theme:t.theme}})],1)}),[],!1,null,null,null,null).exports;const Wl=ut({extends:Hl,computed:{canAdd(){return this.options.add&&this.$panel.permissions.pages.create},items(){return this.data.map((t=>{const e=!1===t.permissions.changeStatus,i=this.$helper.page.status(t.status,e);return i.click=()=>this.$dialog(t.link+"/changeStatus"),t.flag={status:t.status,disabled:e,click:()=>this.$dialog(t.link+"/changeStatus")},t.sortable=t.permissions.sort&&this.options.sortable,t.deletable=this.data.length>this.options.min,t.column=this.column,t.buttons=[i,...t.buttons??[]],t.options=this.$dropdown(t.link,{query:{view:"list",delete:t.deletable,sort:t.sortable}}),t.data={"data-id":t.id,"data-status":t.status,"data-template":t.template},t}))},type:()=>"pages"},created(){this.$events.on("page.changeStatus",this.reload),this.$events.on("page.sort",this.reload)},destroyed(){this.$events.off("page.changeStatus",this.reload),this.$events.off("page.sort",this.reload)},methods:{onAdd(){this.canAdd&&this.$dialog("pages/create",{query:{parent:this.options.link??this.parent,view:this.parent,section:this.name}})},async onChange(t){let e=null;if(t.added&&(e="added"),t.moved&&(e="moved"),e){this.isProcessing=!0;const n=t[e].element,s=t[e].newIndex+1+this.pagination.offset;try{await this.$api.pages.changeStatus(n.id,"listed",s),this.$panel.notification.success(),this.$events.emit("page.sort",n)}catch(i){this.$panel.error({message:i.message,details:i.details}),await this.reload()}finally{this.isProcessing=!1}}}}},null,null,!1,null,null,null,null).exports;const Jl=ut({mixins:[Yl],data:()=>({headline:null,isLoading:!0,reports:null,size:null}),async created(){const t=await this.load();this.isLoading=!1,this.headline=t.headline,this.reports=t.reports,this.size=t.size},methods:{}},(function(){var t=this,e=t._self._c;return!1===t.isLoading?e("k-section",{staticClass:"k-stats-section",attrs:{headline:t.headline}},[t.reports.length>0?e("k-stats",{attrs:{reports:t.reports,size:t.size}}):e("k-empty",{attrs:{icon:"chart"}},[t._v(" "+t._s(t.$t("stats.empty")))])],1):t._e()}),[],!1,null,null,null,null).exports,Gl={install(t){t.component("k-section",Fl),t.component("k-sections",Rl),t.component("k-fields-section",Ul),t.component("k-files-section",Vl),t.component("k-info-section",Kl),t.component("k-pages-section",Wl),t.component("k-stats-section",Jl)}};const Xl=ut({components:{"k-highlight":()=>R((()=>import("./Highlight.min.js")),["./Highlight.min.js","./vendor.min.js"],import.meta.url)},props:{language:{type:String}}},(function(){var t=this,e=t._self._c;return e("k-highlight",[e("div",[e("pre",{staticClass:"k-code",attrs:{"data-language":t.language}},[e("code",{key:t.$slots.default[0].text+"-"+t.language,class:t.language?`language-${t.language}`:null},[t._t("default")],2)])])])}),[],!1,null,null,null,null).exports;const Zl=ut({props:{link:String,size:{type:String},tag:{type:String,default:"h2"},theme:{type:String}},emits:["click"],created(){this.size&&window.panel.deprecated(": the `size` prop will be removed in a future version. Use the `tag` prop instead."),this.theme&&window.panel.deprecated(": the `theme` prop will be removed in a future version.")}},(function(){var t=this,e=t._self._c;return e(t.tag,{tag:"component",staticClass:"k-headline",attrs:{"data-theme":t.theme,"data-size":t.size},on:{click:function(e){return t.$emit("click",e)}}},[t.link?e("k-link",{attrs:{to:t.link}},[t._t("default")],2):t._t("default")],2)}),[],!1,null,null,null,null).exports;const Ql=ut({props:{input:{type:[String,Number]},invalid:{type:Boolean},link:{type:String},required:{default:!1,type:Boolean},type:{default:"field",type:String}},computed:{element(){return"section"===this.type?"h2":"label"}}},(function(){var t=this,e=t._self._c;return e(t.element,{tag:"component",staticClass:"k-label",class:"k-"+t.type+"-label",attrs:{for:t.input,"data-invalid":t.invalid}},[t.link?e("k-link",{attrs:{to:t.link}},[e("span",{staticClass:"k-label-text"},[t._t("default")],2)]):e("span",{staticClass:"k-label-text"},[t._t("default")],2),t.required&&!t.invalid?e("abbr",{attrs:{title:t.$t(t.type+".required")}},[t._v("✶")]):t._e(),e("abbr",{staticClass:"k-label-invalid",attrs:{title:t.$t(t.type+".invalid")}},[t._v("×")])],1)}),[],!1,null,null,null,null).exports;const tr=ut({props:{align:String,html:String,size:String,theme:String},computed:{attrs(){return{class:"k-text","data-align":this.align,"data-size":this.size,"data-theme":this.theme}}},created(){this.theme&&window.panel.deprecated(': the `theme` prop will be removed in a future version. For help text, add `.k-help "` CSS class instead.')}},(function(){var t=this,e=t._self._c;return t.html?e("div",t._b({domProps:{innerHTML:t._s(t.html)}},"div",t.attrs,!1)):e("div",t._b({},"div",t.attrs,!1),[t._t("default")],2)}),[],!1,null,null,null,null).exports,er={install(t){t.component("k-code",Xl),t.component("k-headline",Zl),t.component("k-label",Ql),t.component("k-text",tr)}};const ir=ut({props:{status:{default:"missing",type:String}}},(function(){var t=this,e=t._self._c;return t.$panel.activation.isOpen?e("div",{staticClass:"k-activation"},[e("p",[e("strong",[t._v(t._s(t.$t(`license.status.${t.status}.bubble`)))]),"missing"===t.status?[e("a",{attrs:{href:"https://getkirby.com/buy",target:"_blank"}},[t._v(t._s(t.$t("license.buy")))]),t._v(" & "),e("button",{attrs:{type:"button"},on:{click:function(e){return t.$dialog("registration")}}},[t._v(" "+t._s(t.$t("license.activate"))+" ")])]:t._e()],2),e("k-button",{staticClass:"k-activation-toggle",attrs:{icon:"cancel-small"},on:{click:function(e){return t.$panel.activation.close()}}})],1):t._e()}),[],!1,null,null,null,null).exports;const nr=ut({computed:{notification(){return"view"!==this.$panel.notification.context||this.$panel.notification.isFatal?null:this.$panel.notification}}},(function(){var t=this,e=t._self._c;return e("k-panel",{staticClass:"k-panel-inside"},[e("k-panel-menu"),e("main",{staticClass:"k-panel-main"},[e("k-topbar",{attrs:{breadcrumb:t.$panel.view.breadcrumb,view:t.$panel.view}},[t._t("topbar")],2),t._t("default")],2),t.notification&&"error"!==t.notification.type?e("k-button",{staticClass:"k-panel-notification",attrs:{icon:t.notification.icon,text:t.notification.message,theme:t.notification.theme,variant:"filled"},on:{click:function(e){return t.notification.close()}}}):t._e()],1)}),[],!1,null,null,null,null).exports;const sr=ut({data:()=>({over:!1}),computed:{activationButton(){return"missing"===this.$panel.license?{click:()=>this.$dialog("registration"),text:this.$t("activate")}:"legacy"===this.$panel.license&&{click:()=>this.$dialog("license"),text:this.$t("renew")}},hasSearch(){return this.$helper.object.length(this.$panel.searches)>0},menus(){return this.$panel.menu.entries.split("-")}}},(function(){var t=this,e=t._self._c;return e("nav",{staticClass:"k-panel-menu",attrs:{"aria-label":t.$t("menu"),"data-hover":t.$panel.menu.hover},on:{mouseenter:function(e){t.$panel.menu.hover=!0},mouseleave:function(e){t.$panel.menu.hover=!1}}},[e("div",{staticClass:"k-panel-menu-body"},[t.hasSearch?e("k-button",{staticClass:"k-panel-menu-search k-panel-menu-button",attrs:{text:t.$t("search"),icon:"search"},on:{click:function(e){return t.$panel.search()}}}):t._e(),t._l(t.menus,(function(i,n){return e("menu",{key:n,staticClass:"k-panel-menu-buttons",attrs:{"data-second-last":n===t.menus.length-2}},t._l(i,(function(i){return e("k-button",t._b({key:i.id,staticClass:"k-panel-menu-button",attrs:{title:i.title??i.text}},"k-button",i,!1))})),1)})),t.activationButton?e("menu",[e("k-button",t._b({staticClass:"k-activation-button k-panel-menu-button",attrs:{icon:"key",theme:"love",variant:"filled"}},"k-button",t.activationButton,!1)),e("k-activation",{attrs:{status:t.$panel.license}})],1):t._e()],2),e("k-button",{staticClass:"k-panel-menu-toggle",attrs:{icon:t.$panel.menu.isOpen?"angle-left":"angle-right",title:t.$panel.menu.isOpen?t.$t("collapse"):t.$t("expand"),size:"xs"},on:{click:function(e){return t.$panel.menu.toggle()}}})],1)}),[],!1,null,null,null,null).exports;const or=ut({},(function(){return(0,this._self._c)("k-panel",{staticClass:"k-panel-outside",attrs:{tabindex:"0"}},[this._t("default")],2)}),[],!1,null,null,null,null).exports;const lr=ut({},(function(){var t=this,e=t._self._c;return e("div",{staticClass:"k-panel",attrs:{"data-dragging":t.$panel.drag.isDragging,"data-loading":t.$panel.isLoading,"data-language":t.$panel.language.code,"data-language-default":t.$panel.language.isDefault,"data-menu":t.$panel.menu.isOpen?"true":"false","data-role":t.$panel.user.role,"data-translation":t.$panel.translation.code,"data-user":t.$panel.user.id,dir:t.$panel.direction}},[t._t("default"),t.$panel.dialog.isOpen&&!t.$panel.dialog.legacy?e("k-fiber-dialog"):t._e(),t.$panel.drawer.isOpen&&!t.$panel.drawer.legacy?e("k-fiber-drawer"):t._e(),t.$panel.notification.isFatal&&t.$panel.notification.isOpen?e("k-fatal",{attrs:{html:t.$panel.notification.message}}):t._e(),e("k-offline-warning"),e("k-icons"),e("k-overlay",{attrs:{nested:t.$panel.drawer.history.milestones.length>1,visible:t.$panel.drawer.isOpen,type:"drawer"},on:{close:function(e){return t.$panel.drawer.close()}}},[e("portal-target",{staticClass:"k-drawer-portal k-portal",attrs:{name:"drawer",multiple:""}})],1),e("k-overlay",{attrs:{visible:t.$panel.dialog.isOpen,type:"dialog"},on:{close:function(e){return t.$panel.dialog.close()}}},[e("portal-target",{staticClass:"k-dialog-portal k-portal",attrs:{name:"dialog",multiple:""}})],1),e("portal-target",{staticClass:"k-overlay-portal k-portal",attrs:{name:"overlay",multiple:""}})],2)}),[],!1,null,null,null,null).exports;const rr=ut({props:{breadcrumb:Array,view:Object},computed:{crumbs(){return[{link:this.view.link,label:this.view.label??this.view.breadcrumbLabel,icon:this.view.icon,loading:this.$panel.isLoading},...this.breadcrumb]}}},(function(){var t=this,e=t._self._c;return e("div",{staticClass:"k-topbar"},[e("k-button",{staticClass:"k-panel-menu-proxy",attrs:{icon:"bars"},on:{click:function(e){return t.$panel.menu.toggle()}}}),e("k-breadcrumb",{staticClass:"k-topbar-breadcrumb",attrs:{crumbs:t.crumbs}}),e("div",{staticClass:"k-topbar-spacer"}),e("div",{staticClass:"k-topbar-signals"},[t._t("default")],2)],1)}),[],!1,null,null,null,null).exports,ar={install(t){t.component("k-activation",ir),t.component("k-panel",lr),t.component("k-panel-inside",nr),t.component("k-panel-menu",sr),t.component("k-panel-outside",or),t.component("k-topbar",rr),t.component("k-inside",nr),t.component("k-outside",or)}};const ur=ut({props:{error:String,layout:String}},(function(){var t=this,e=t._self._c;return e(`k-panel-${t.layout}`,{tag:"component",staticClass:"k-error-view"},["outside"===t.layout?[e("div",[e("k-box",{attrs:{icon:"alert",theme:"negative"}},[t._v(t._s(t.error))])],1)]:[e("k-header",[t._v(t._s(t.$t("error")))]),e("k-box",{attrs:{icon:"alert",theme:"negative"}},[t._v(t._s(t.error))])]],2)}),[],!1,null,null,null,null).exports;const cr=ut({mixins:[Ft],props:{type:{default:"pages",type:String}},data:()=>({items:[],query:new URLSearchParams(window.location.search).get("query"),pagination:{}}),computed:{currentType(){return this.$panel.searches[this.type]??Object.values(this.$panel.searches)[0]},tabs(){const t=[];for(const e in this.$panel.searches){const i=this.$panel.searches[e];t.push({label:i.label,link:"/search/?type="+e+"&query="+this.query,name:e})}return t}},watch:{query:{handler(){this.search(1)},immediate:!0},type(){this.search()}},methods:{focus(){var t;null==(t=this.$refs.input)||t.focus()},onPaginate(t){this.search(t.page)},async search(t){this.$panel.isLoading=!0,t||(t=new URLSearchParams(window.location.search).get("page")??1);const e=this.$panel.url(window.location,{type:this.currentType.id,query:this.query,page:t});window.history.pushState("","",e.toString());try{if(null===this.query||this.query.length<2)throw Error("Empty query");const e=await this.$search(this.currentType.id,this.query,{page:t,limit:15});this.items=e.results,this.pagination=e.pagination}catch(i){this.items=[],this.pagination={}}finally{this.$panel.isLoading=!1}}}},(function(){var t=this,e=t._self._c;return e("k-panel-inside",{staticClass:"k-search-view"},[e("k-header",[t._v(" "+t._s(t.$t("search"))+" "),e("k-input",{ref:"input",staticClass:"k-search-view-input",attrs:{slot:"buttons","aria-label":t.$t("search"),autofocus:!0,placeholder:t.$t("search")+" …",spellcheck:!1,value:t.query,icon:"search",type:"text"},on:{input:function(e){t.query=e}},slot:"buttons"})],1),e("k-tabs",{attrs:{tab:t.currentType.id,tabs:t.tabs}}),e("div",{staticClass:"k-search-view-results"},[e("k-collection",{attrs:{items:t.items,empty:{icon:"search",text:t.$t("search.results.none")},pagination:t.pagination},on:{paginate:t.onPaginate}})],1)],1)}),[],!1,null,null,null,null).exports;const dr=ut({props:{blueprint:String,next:Object,prev:Object,permissions:{type:Object,default:()=>({})},lock:{type:[Boolean,Object]},model:{type:Object,default:()=>({})},tab:{type:Object,default:()=>({columns:[]})},tabs:{type:Array,default:()=>[]}},computed:{id(){return this.model.link},isLocked(){var t;return"lock"===(null==(t=this.lock)?void 0:t.state)},protectedFields:()=>[]},watch:{"$panel.view.timestamp":{handler(){this.$store.dispatch("content/create",{id:this.id,api:this.id,content:this.model.content,ignore:this.protectedFields})},immediate:!0}},created(){this.$events.on("model.reload",this.$reload),this.$events.on("keydown.left",this.toPrev),this.$events.on("keydown.right",this.toNext)},destroyed(){this.$events.off("model.reload",this.$reload),this.$events.off("keydown.left",this.toPrev),this.$events.off("keydown.right",this.toNext)},methods:{toPrev(t){this.prev&&"body"===t.target.localName&&this.$go(this.prev.link)},toNext(t){this.next&&"body"===t.target.localName&&this.$go(this.next.link)}}},null,null,!1,null,null,null,null).exports;const pr=ut({extends:dr,props:{preview:Object},computed:{focus(){const t=this.$store.getters["content/values"]().focus;if(!t)return;const[e,i]=t.replaceAll("%","").split(" ");return{x:parseFloat(e),y:parseFloat(i)}},isFocusable(){return!this.isLocked&&this.preview.image.src&&this.permissions.update&&(!window.panel.multilang||0===window.panel.languages.length||window.panel.language.default)}},methods:{action(t){if("replace"===t)return this.$panel.upload.replace({...this.preview,...this.model})},setFocus(t){!0===this.$helper.object.isObject(t)&&(t=`${t.x}% ${t.y}%`),this.$store.dispatch("content/update",["focus",t])}}},(function(){var t=this,e=t._self._c;return e("k-panel-inside",{staticClass:"k-file-view",attrs:{"data-has-tabs":t.tabs.length>1,"data-id":t.model.id,"data-locked":t.isLocked,"data-template":t.blueprint},scopedSlots:t._u([{key:"topbar",fn:function(){return[e("k-prev-next",{attrs:{prev:t.prev,next:t.next}})]},proxy:!0}])},[e("k-header",{staticClass:"k-file-view-header",attrs:{editable:t.permissions.changeName&&!t.isLocked},on:{edit:function(e){return t.$dialog(t.id+"/changeName")}},scopedSlots:t._u([{key:"buttons",fn:function(){return[e("k-button-group",[e("k-button",{staticClass:"k-file-view-options",attrs:{link:t.preview.url,responsive:!0,title:t.$t("open"),icon:"open",size:"sm",target:"_blank",variant:"filled"}}),e("k-button",{staticClass:"k-file-view-options",attrs:{disabled:t.isLocked,dropdown:!0,title:t.$t("settings"),icon:"cog",size:"sm",variant:"filled"},on:{click:function(e){return t.$refs.settings.toggle()}}}),e("k-dropdown-content",{ref:"settings",attrs:{options:t.$dropdown(t.id),"align-x":"end"},on:{action:t.action}}),e("k-languages-dropdown")],1),e("k-form-buttons",{attrs:{lock:t.lock}})]},proxy:!0}])},[t._v(" "+t._s(t.model.filename)+" ")]),e("k-file-preview",t._b({attrs:{focus:t.focus},on:{focus:t.setFocus}},"k-file-preview",t.preview,!1)),e("k-model-tabs",{attrs:{tab:t.tab.name,tabs:t.tabs}}),e("k-sections",{attrs:{blueprint:t.blueprint,empty:t.$t("file.blueprint",{blueprint:t.$esc(t.blueprint)}),lock:t.lock,parent:t.id,tab:t.tab}})],1)}),[],!1,null,null,null,null).exports;const hr=ut({props:{details:{default:()=>[],type:Array},focus:{type:Object},focusable:Boolean,image:{default:()=>({}),type:Object},url:String},computed:{options(){return[{icon:"open",text:this.$t("open"),link:this.url,target:"_blank"},{icon:"cancel",text:this.$t("file.focus.reset"),click:()=>this.$refs.focus.reset(),when:this.focusable&&this.focus},{icon:"preview",text:this.$t("file.focus.placeholder"),click:()=>this.$refs.focus.set(),when:this.focusable&&!this.focus}]}},methods:{setFocus(t){if(!t)return this.$emit("focus",null);this.$emit("focus",{x:t.x.toFixed(1),y:t.y.toFixed(1)})}}},(function(){var t=this,e=t._self._c;return e("div",{staticClass:"k-file-preview",attrs:{"data-has-focus":Boolean(t.focus)}},[e("div",{staticClass:"k-file-preview-thumb-column"},[e("div",{staticClass:"k-file-preview-thumb"},[t.image.src?[e("k-coords-input",{attrs:{disabled:!t.focusable,value:t.focus},on:{input:function(e){return t.setFocus(e)}}},[e("img",t._b({on:{dragstart:function(t){t.preventDefault()}}},"img",t.image,!1))]),e("k-button",{staticStyle:{color:"var(--color-gray-500)"},attrs:{icon:"dots",size:"xs"},on:{click:function(e){return t.$refs.dropdown.toggle()}}}),e("k-dropdown-content",{ref:"dropdown",attrs:{options:t.options,theme:"light"}})]:e("k-icon",{staticClass:"k-item-icon",attrs:{color:t.$helper.color(t.image.color),type:t.image.icon}})],2)]),e("div",{staticClass:"k-file-preview-details"},[e("dl",[t._l(t.details,(function(i){return e("div",{key:i.title},[e("dt",[t._v(t._s(i.title))]),e("dd",[i.link?e("k-link",{attrs:{to:i.link,tabindex:"-1",target:"_blank"}},[t._v(" /"+t._s(i.text)+" ")]):[t._v(" "+t._s(i.text)+" ")]],2)])})),t.image.src?e("div",{staticClass:"k-file-preview-focus-info"},[e("dt",[t._v(t._s(t.$t("file.focus.title")))]),e("dd",[t.focusable?e("k-file-focus-button",{ref:"focus",attrs:{focus:t.focus},on:{set:t.setFocus}}):t.focus?[t._v(" "+t._s(t.focus.x)+"% "+t._s(t.focus.y)+"% ")]:[t._v("–")]],2)]):t._e()],2)])])}),[],!1,null,null,null,null).exports;const mr=ut({props:{focus:Object},methods:{set(){this.$emit("set",{x:50,y:50})},reset(){this.$emit("set",void 0)}}},(function(){var t=this;return(0,t._self._c)("k-button",{attrs:{icon:t.focus?"cancel-small":"preview",title:t.focus?t.$t("file.focus.reset"):void 0,size:"xs",variant:"filled"},on:{click:function(e){t.focus?t.reset():t.set()}}},[t.focus?[t._v(t._s(t.focus.x)+"% "+t._s(t.focus.y)+"%")]:[t._v(t._s(t.$t("file.focus.placeholder")))]],2)}),[],!1,null,null,null,null).exports;const fr=ut({props:{languages:{type:Array,default:()=>[]},variables:{type:Boolean,default:!0}},computed:{languagesCollection(){return this.languages.map((t=>({...t,image:{back:"black",color:"gray",icon:"translate"},link:()=>{if(!1===this.variables)return null;this.$go(`languages/${t.id}`)},options:[{icon:"edit",text:this.$t("edit"),disabled:!1===this.variables,click:()=>this.$go(`languages/${t.id}`)},{icon:"cog",text:this.$t("settings"),click:()=>this.$dialog(`languages/${t.id}/update`)},{icon:"trash",text:this.$t("delete"),disabled:!1===t.deletable,click:()=>this.$dialog(`languages/${t.id}/delete`)}]})))},primaryLanguage(){return this.languagesCollection.filter((t=>t.default))},secondaryLanguages(){return this.languagesCollection.filter((t=>!1===t.default))}}},(function(){var t=this,e=t._self._c;return e("k-panel-inside",{staticClass:"k-languages-view"},[e("k-header",[t._v(" "+t._s(t.$t("view.languages"))+" "),e("k-button-group",{attrs:{slot:"buttons"},slot:"buttons"},[e("k-button",{attrs:{text:t.$t("language.create"),icon:"add",size:"sm",variant:"filled"},on:{click:function(e){return t.$dialog("languages/create")}}})],1)],1),t.languages.length>0?[e("k-section",{attrs:{headline:t.$t("languages.default")}},[e("k-collection",{attrs:{items:t.primaryLanguage}})],1),e("k-section",{attrs:{headline:t.$t("languages.secondary")}},[t.secondaryLanguages.length?e("k-collection",{attrs:{items:t.secondaryLanguages}}):e("k-empty",{attrs:{icon:"translate"},on:{click:function(e){return t.$dialog("languages/create")}}},[t._v(" "+t._s(t.$t("languages.secondary.empty"))+" ")])],1)]:0===t.languages.length?[e("k-empty",{attrs:{icon:"translate"},on:{click:function(e){return t.$dialog("languages/create")}}},[t._v(" "+t._s(t.$t("languages.empty"))+" ")])]:t._e()],2)}),[],!1,null,null,null,null).exports;const gr=ut({props:{code:String,deletable:Boolean,direction:String,id:String,info:Array,next:Object,name:String,prev:Object,translations:Array,url:String},methods:{createTranslation(){this.$dialog(`languages/${this.id}/translations/create`)},option(t,e){this.$dialog(`languages/${this.id}/translations/${window.btoa(encodeURIComponent(e.key))}/${t}`)},remove(){this.$dialog(`languages/${this.id}/delete`)},update(t){this.$dialog(`languages/${this.id}/update`,{on:{ready:()=>{this.$panel.dialog.focus(t)}}})},updateTranslation({row:t}){this.$dialog(`languages/${this.id}/translations/${window.btoa(encodeURIComponent(t.key))}/update`)}}},(function(){var t=this,e=t._self._c;return e("k-panel-inside",{staticClass:"k-language-view",scopedSlots:t._u([{key:"topbar",fn:function(){return[e("k-prev-next",{attrs:{prev:t.prev,next:t.next}})]},proxy:!0}])},[e("k-header",{attrs:{editable:!0},on:{edit:function(e){return t.update()}}},[t._v(" "+t._s(t.name)+" "),e("k-button-group",{attrs:{slot:"buttons"},slot:"buttons"},[e("k-button",{attrs:{link:t.url,title:t.$t("open"),icon:"open",size:"sm",target:"_blank",variant:"filled"}}),e("k-button",{attrs:{title:t.$t("settings"),icon:"cog",size:"sm",variant:"filled"},on:{click:function(e){return t.update()}}}),t.deletable?e("k-button",{attrs:{title:t.$t("delete"),icon:"trash",size:"sm",variant:"filled"},on:{click:function(e){return t.remove()}}}):t._e()],1)],1),e("k-section",{attrs:{headline:t.$t("language.settings")}},[e("k-stats",{attrs:{reports:t.info,size:"small"}})],1),e("k-section",{attrs:{buttons:[{click:t.createTranslation,icon:"add",text:t.$t("add")}],headline:t.$t("language.variables")}},[t.translations.length?[e("k-table",{attrs:{columns:{key:{label:t.$t("language.variable.key"),mobile:!0,width:"1/4"},value:{label:t.$t("language.variable.value"),mobile:!0}},rows:t.translations},on:{cell:t.updateTranslation,option:t.option}})]:[e("k-empty",{attrs:{icon:"translate"},on:{click:t.createTranslation}},[t._v(" "+t._s(t.$t("language.variables.empty"))+" ")])]],2)],1)}),[],!1,null,null,null,null).exports;const kr=ut({components:{"k-login-plugin":window.panel.plugins.login??Ke},props:{methods:Array,pending:Object},data:()=>({issue:""}),computed:{form(){return this.pending.email?"code":"login"},viewClass(){return"code"===this.form?"k-login-code-view":"k-login-view"}},created(){this.$store.dispatch("content/clear")},methods:{async onError(t){null!==t?(!0===t.details.challengeDestroyed&&await this.$reload({globals:["$system"]}),this.issue=t.message):this.issue=null}}},(function(){var t=this,e=t._self._c;return e("k-panel-outside",{class:t.viewClass},[e("div",{staticClass:"k-dialog k-login-dialog"},[e("h1",{staticClass:"sr-only"},[t._v(" "+t._s(t.$t("login"))+" ")]),t.issue?e("k-login-alert",{nativeOn:{click:function(e){t.issue=null}}},[t._v(" "+t._s(t.issue)+" ")]):t._e(),e("k-dialog-body",["code"===t.form?e("k-login-code",t._b({on:{error:t.onError}},"k-login-code",t.$props,!1)):e("k-login-plugin",{attrs:{methods:t.methods},on:{error:t.onError}})],1)],1)])}),[],!1,null,null,null,null).exports;const br=ut({props:{isInstallable:Boolean,isInstalled:Boolean,isOk:Boolean,requirements:Object,translations:Array},data(){return{user:{name:"",email:"",language:this.$panel.translation.code,password:"",role:"admin"}}},computed:{fields(){return{email:{label:this.$t("email"),type:"email",link:!1,autofocus:!0,required:!0},password:{label:this.$t("password"),type:"password",placeholder:this.$t("password")+" …",required:!0},language:{label:this.$t("language"),type:"select",options:this.translations,icon:"translate",empty:!1,required:!0}}},isReady(){return this.isOk&&this.isInstallable},isComplete(){return this.isOk&&this.isInstalled}},methods:{async install(){try{await this.$api.system.install(this.user),await this.$reload({globals:["$system","$translation"]}),this.$panel.notification.success({message:this.$t("welcome")+"!",icon:"smile"})}catch(t){this.$panel.error(t)}}}},(function(){var t=this,e=t._self._c;return e("k-panel-outside",{staticClass:"k-installation-view"},[e("div",{staticClass:"k-dialog k-installation-dialog"},[e("k-dialog-body",[t.isComplete?e("k-text",[e("k-headline",[t._v(t._s(t.$t("installation.completed")))]),e("k-link",{attrs:{to:"/login"}},[t._v(" "+t._s(t.$t("login"))+" ")])],1):t.isReady?e("form",{on:{submit:function(e){return e.preventDefault(),t.install.apply(null,arguments)}}},[e("h1",{staticClass:"sr-only"},[t._v(" "+t._s(t.$t("installation"))+" ")]),e("k-fieldset",{attrs:{fields:t.fields,novalidate:!0,value:t.user},on:{input:function(e){t.user=e}}}),e("k-button",{attrs:{text:t.$t("install"),icon:"check",size:"lg",theme:"positive",type:"submit",variant:"filled"}})],1):e("div",[e("k-headline",[t._v(" "+t._s(t.$t("installation.issues.headline"))+" ")]),e("ul",{staticClass:"k-installation-issues"},[!1===t.isInstallable?e("li",[e("k-icon",{attrs:{type:"alert"}}),e("span",{domProps:{innerHTML:t._s(t.$t("installation.disabled"))}})],1):t._e(),!1===t.requirements.php?e("li",[e("k-icon",{attrs:{type:"alert"}}),e("span",{domProps:{innerHTML:t._s(t.$t("installation.issues.php"))}})],1):t._e(),!1===t.requirements.server?e("li",[e("k-icon",{attrs:{type:"alert"}}),e("span",{domProps:{innerHTML:t._s(t.$t("installation.issues.server"))}})],1):t._e(),!1===t.requirements.mbstring?e("li",[e("k-icon",{attrs:{type:"alert"}}),e("span",{domProps:{innerHTML:t._s(t.$t("installation.issues.mbstring"))}})],1):t._e(),!1===t.requirements.curl?e("li",[e("k-icon",{attrs:{type:"alert"}}),e("span",{domProps:{innerHTML:t._s(t.$t("installation.issues.curl"))}})],1):t._e(),!1===t.requirements.accounts?e("li",[e("k-icon",{attrs:{type:"alert"}}),e("span",{domProps:{innerHTML:t._s(t.$t("installation.issues.accounts"))}})],1):t._e(),!1===t.requirements.content?e("li",[e("k-icon",{attrs:{type:"alert"}}),e("span",{domProps:{innerHTML:t._s(t.$t("installation.issues.content"))}})],1):t._e(),!1===t.requirements.media?e("li",[e("k-icon",{attrs:{type:"alert"}}),e("span",{domProps:{innerHTML:t._s(t.$t("installation.issues.media"))}})],1):t._e(),!1===t.requirements.sessions?e("li",[e("k-icon",{attrs:{type:"alert"}}),e("span",{domProps:{innerHTML:t._s(t.$t("installation.issues.sessions"))}})],1):t._e()]),e("k-button",{attrs:{text:t.$t("retry"),icon:"refresh",size:"lg",theme:"positive",variant:"filled"},on:{click:t.$reload}})],1)],1)],1)])}),[],!1,null,null,null,null).exports;const vr=ut({data:()=>({isLoading:!1,values:{password:null,passwordConfirmation:null}}),computed:{fields(){return{password:{autofocus:!0,label:this.$t("user.changePassword.new"),icon:"key",type:"password",width:"1/2"},passwordConfirmation:{label:this.$t("user.changePassword.new.confirm"),icon:"key",type:"password",width:"1/2"}}}},mounted(){this.$panel.title=this.$t("view.resetPassword")},methods:{async submit(){if(!this.values.password||this.values.password.length<8)return this.$panel.notification.error(this.$t("error.user.password.invalid"));if(this.values.password!==this.values.passwordConfirmation)return this.$panel.notification.error(this.$t("error.user.password.notSame"));this.isLoading=!0;try{await this.$api.users.changePassword(this.$panel.user.id,this.values.password),this.$panel.notification.success(),this.$go("/")}catch(t){this.$panel.notification.error(t)}finally{this.isLoading=!1}}}},(function(){var t=this,e=t._self._c;return e("k-panel-inside",{staticClass:"k-password-reset-view"},[e("form",{on:{submit:function(e){return e.preventDefault(),t.submit.apply(null,arguments)}}},[e("k-header",{scopedSlots:t._u([{key:"buttons",fn:function(){return[e("k-button",{attrs:{icon:"check",theme:"notice",type:"submit",variant:"filled",size:"sm"}},[t._v(" "+t._s(t.$t("change"))+" "),t.isLoading?[t._v(" … ")]:t._e()],2)]},proxy:!0}])},[t._v(" "+t._s(t.$t("view.resetPassword"))+" ")]),e("k-user-info",{attrs:{user:t.$panel.user}}),e("k-fieldset",{attrs:{fields:t.fields,value:t.values}})],1)])}),[],!1,null,null,null,null).exports;const yr=ut({props:{user:[Object,String]}},(function(){var t=this,e=t._self._c;return e("div",{staticClass:"k-user-info"},[t.user.avatar?e("k-image-frame",{attrs:{cover:!0,src:t.user.avatar.url,ratio:"1/1"}}):e("k-icon-frame",{attrs:{color:"white",back:"black",icon:"user"}}),t._v(" "+t._s(t.user.name??t.user.email??t.user)+" ")],1)}),[],!1,null,null,null,null).exports;const $r=ut({extends:dr,props:{status:Object},computed:{protectedFields:()=>["title"],statusBtn(){return{...this.$helper.page.status.call(this,this.model.status,!this.permissions.changeStatus||this.isLocked),responsive:!0,size:"sm",text:this.status.label,variant:"filled"}}}},(function(){var t=this,e=t._self._c;return e("k-panel-inside",{staticClass:"k-page-view",attrs:{"data-has-tabs":t.tabs.length>1,"data-id":t.model.id,"data-locked":t.isLocked,"data-template":t.blueprint},scopedSlots:t._u([{key:"topbar",fn:function(){return[t.model.id?e("k-prev-next",{attrs:{prev:t.prev,next:t.next}}):t._e()]},proxy:!0}])},[e("k-header",{staticClass:"k-page-view-header",attrs:{editable:t.permissions.changeTitle&&!t.isLocked},on:{edit:function(e){return t.$dialog(t.id+"/changeTitle")}},scopedSlots:t._u([{key:"buttons",fn:function(){return[e("k-button-group",[t.permissions.preview&&t.model.previewUrl?e("k-button",{staticClass:"k-page-view-preview",attrs:{link:t.model.previewUrl,title:t.$t("open"),icon:"open",target:"_blank",variant:"filled",size:"sm"}}):t._e(),e("k-button",{staticClass:"k-page-view-options",attrs:{disabled:!0===t.isLocked,dropdown:!0,title:t.$t("settings"),icon:"cog",variant:"filled",size:"sm"},on:{click:function(e){return t.$refs.settings.toggle()}}}),e("k-dropdown-content",{ref:"settings",attrs:{options:t.$dropdown(t.id),"align-x":"end"}}),e("k-languages-dropdown"),t.status?e("k-button",t._b({staticClass:"k-page-view-status",attrs:{variant:"filled"},on:{click:function(e){return t.$dialog(t.id+"/changeStatus")}}},"k-button",t.statusBtn,!1)):t._e()],1),e("k-form-buttons",{attrs:{lock:t.lock}})]},proxy:!0}])},[t._v(" "+t._s(t.model.title)+" ")]),e("k-model-tabs",{attrs:{tab:t.tab.name,tabs:t.tabs}}),e("k-sections",{attrs:{blueprint:t.blueprint,empty:t.$t("page.blueprint",{blueprint:t.$esc(t.blueprint)}),lock:t.lock,parent:t.id,tab:t.tab}})],1)}),[],!1,null,null,null,null).exports;const wr=ut({extends:dr,computed:{protectedFields:()=>["title"]}},(function(){var t=this,e=t._self._c;return e("k-panel-inside",{staticClass:"k-site-view",attrs:{"data-has-tabs":t.tabs.length>1,"data-locked":t.isLocked,"data-id":"/","data-template":"site"}},[e("k-header",{staticClass:"k-site-view-header",attrs:{editable:t.permissions.changeTitle&&!t.isLocked},on:{edit:function(e){return t.$dialog("site/changeTitle")}},scopedSlots:t._u([{key:"buttons",fn:function(){return[e("k-button-group",[e("k-button",{staticClass:"k-site-view-preview",attrs:{link:t.model.previewUrl,title:t.$t("open"),icon:"open",target:"_blank",variant:"filled",size:"sm"}}),e("k-languages-dropdown")],1),e("k-form-buttons",{attrs:{lock:t.lock}})]},proxy:!0}])},[t._v(" "+t._s(t.model.title)+" ")]),e("k-model-tabs",{attrs:{tab:t.tab.name,tabs:t.tabs}}),e("k-sections",{attrs:{blueprint:t.blueprint,empty:t.$t("site.blueprint"),lock:t.lock,tab:t.tab,parent:"site"},on:{submit:function(e){return t.$emit("submit",e)}}})],1)}),[],!1,null,null,null,null).exports;const xr=ut({components:{Plugins:ut({props:{plugins:Array}},(function(){var t=this,e=t._self._c;return t.plugins.length?e("k-section",{attrs:{headline:t.$t("plugins"),link:"https://getkirby.com/plugins"}},[e("k-table",{attrs:{index:!1,columns:{name:{label:t.$t("name"),type:"url",mobile:!0},author:{label:t.$t("author")},license:{label:t.$t("license")},version:{label:t.$t("version"),type:"update-status",mobile:!0,width:"10rem"}},rows:t.plugins}})],1):t._e()}),[],!1,null,null,null,null).exports,Security:ut({props:{exceptions:Array,security:Array,urls:Object},data(){return{issues:vt(this.security)}},async created(){console.info("Running system health checks for the Panel system view; failed requests in the following console output are expected behavior.");const t=(Promise.allSettled??Promise.all).bind(Promise),e=Object.entries(this.urls).map(this.check);await t(e),console.info(`System health checks ended. ${this.issues.length-this.security.length} issues with accessible files/folders found (see the security list in the system view).`)},methods:{async check([t,e]){if(!e)return;const{status:i}=await fetch(e,{cache:"no-store"});i<400&&this.issues.push({id:t,text:this.$t("system.issues."+t),link:"https://getkirby.com/security/"+t,icon:"folder"})},retry(){this.$go(window.location.href)}}},(function(){var t=this,e=t._self._c;return t.issues.length?e("k-section",{attrs:{headline:t.$t("security"),buttons:[{title:t.$t("retry"),icon:"refresh",click:t.retry}]}},[e("k-items",{attrs:{items:t.issues.map((t=>({image:{back:"var(--color-red-200)",icon:t.icon??"alert",color:"var(--color-red)"},target:"_blank",...t})))}})],1):t._e()}),[],!1,null,null,null,null).exports},props:{environment:Array,exceptions:Array,plugins:Array,security:Array,urls:Object},created(){this.exceptions.length>0&&(console.info("The following errors occurred during the update check of Kirby and/or plugins:"),this.exceptions.map((t=>console.warn(t))),console.info("End of errors from the update check."))}},(function(){var t=this,e=t._self._c;return e("k-panel-inside",{staticClass:"k-system-view"},[e("k-header",[t._v(" "+t._s(t.$t("view.system"))+" ")]),e("k-section",{attrs:{headline:t.$t("environment")}},[e("k-stats",{staticClass:"k-system-info",attrs:{reports:t.environment,size:"medium"}})],1),e("security",{attrs:{security:t.security,urls:t.urls}}),e("plugins",{attrs:{plugins:t.plugins}})],1)}),[],!1,null,null,null,null).exports;const _r=ut({props:{value:[String,Object]}},(function(){var t=this,e=t._self._c;return e("div",{staticClass:"k-table-update-status-cell"},["string"==typeof t.value?e("span",{staticClass:"k-table-update-status-cell-version"},[t._v(" "+t._s(t.value)+" ")]):[e("k-button",{staticClass:"k-table-update-status-cell-button",attrs:{dropdown:!0,icon:t.value.icon,href:t.value.url,text:t.value.currentVersion,theme:t.value.theme,size:"xs",variant:"filled"},on:{click:function(e){return e.stopPropagation(),t.$refs.dropdown.toggle()}}}),e("k-dropdown-content",{ref:"dropdown",attrs:{"align-x":"end"}},[e("dl",{staticClass:"k-plugin-info"},[e("dt",[t._v(t._s(t.$t("plugin")))]),e("dd",[t._v(t._s(t.value.pluginName))]),e("dt",[t._v(t._s(t.$t("version.current")))]),e("dd",[t._v(t._s(t.value.currentVersion))]),e("dt",[t._v(t._s(t.$t("version.latest")))]),e("dd",[t._v(t._s(t.value.latestVersion))]),e("dt",[t._v(t._s(t.$t("system.updateStatus")))]),e("dd",{attrs:{"data-theme":t.value.theme}},[t._v(t._s(t.value.label))])]),t.value.url?[e("hr"),e("k-button",{attrs:{icon:"open",link:t.value.url}},[t._v(" "+t._s(t.$t("versionInformation"))+" ")])]:t._e()],2)]],2)}),[],!1,null,null,null,null).exports;const Sr=ut({extends:dr},(function(){var t=this,e=t._self._c;return e("k-panel-inside",{staticClass:"k-user-view",attrs:{"data-has-tabs":t.tabs.length>1,"data-id":t.model.id,"data-locked":t.isLocked,"data-template":t.blueprint},scopedSlots:t._u([{key:"topbar",fn:function(){return[t.model.account?e("k-prev-next",{attrs:{prev:t.prev,next:t.next}}):t._e()]},proxy:!0}])},[e("k-header",{staticClass:"k-user-view-header",attrs:{editable:t.permissions.changeName&&!t.isLocked},on:{edit:function(e){return t.$dialog(t.id+"/changeName")}},scopedSlots:t._u([{key:"buttons",fn:function(){return[e("k-button-group",[e("k-button",{staticClass:"k-user-view-options",attrs:{disabled:t.isLocked,dropdown:!0,title:t.$t("settings"),icon:"cog",size:"sm",variant:"filled"},on:{click:function(e){return t.$refs.settings.toggle()}}}),e("k-dropdown-content",{ref:"settings",attrs:{"align-x":"end",options:t.$dropdown(t.id)}}),e("k-languages-dropdown")],1),e("k-form-buttons",{attrs:{lock:t.lock}})]},proxy:!0}])},[t.model.name&&0!==t.model.name.length?[t._v(" "+t._s(t.model.name)+" ")]:e("span",{staticClass:"k-user-name-placeholder"},[t._v(" "+t._s(t.$t("name"))+" … ")])],2),e("k-user-profile",{attrs:{"is-locked":t.isLocked,model:t.model,permissions:t.permissions}}),e("k-model-tabs",{attrs:{tab:t.tab.name,tabs:t.tabs}}),e("k-sections",{attrs:{blueprint:t.blueprint,empty:t.$t("user.blueprint",{blueprint:t.$esc(t.blueprint)}),lock:t.lock,parent:t.id,tab:t.tab}})],1)}),[],!1,null,null,null,null).exports;const Cr=ut({extends:Sr,prevnext:!1},null,null,!1,null,null,null,null).exports;const Or=ut({props:{model:Object},methods:{async deleteAvatar(){await this.$api.users.deleteAvatar(this.model.id),this.$panel.notification.success(),this.$reload()},uploadAvatar(){this.$panel.upload.pick({url:this.$panel.urls.api+"/"+this.model.link+"/avatar",accept:"image/*",immediate:!0,multiple:!1})}}},(function(){var t=this,e=t._self._c;return e("div",[e("k-button",{staticClass:"k-user-view-image",attrs:{title:t.$t("avatar"),variant:"filled"},on:{click:function(e){t.model.avatar?t.$refs.avatar.toggle():t.uploadAvatar()}}},[t.model.avatar?e("k-image-frame",{attrs:{cover:!0,src:t.model.avatar}}):e("k-icon-frame",{attrs:{icon:"user"}})],1),t.model.avatar?e("k-dropdown-content",{ref:"avatar",attrs:{options:[{icon:"upload",text:t.$t("change"),click:t.uploadAvatar},{icon:"trash",text:t.$t("delete"),click:t.deleteAvatar}]}}):t._e()],1)}),[],!1,null,null,null,null).exports;const Ar=ut({props:{isLocked:Boolean,model:Object,permissions:Object}},(function(){var t=this,e=t._self._c;return e("div",{staticClass:"k-user-profile"},[e("k-user-avatar",{attrs:{model:t.model,"aria-disabled":t.isLocked}}),e("k-button-group",{attrs:{buttons:[{icon:"email",text:`${t.model.email}`,title:`${t.$t("email")}: ${t.model.email}`,disabled:!t.permissions.changeEmail||t.isLocked,click:()=>t.$dialog(t.model.link+"/changeEmail")},{icon:"bolt",text:`${t.model.role}`,title:`${t.$t("role")}: ${t.model.role}`,disabled:!t.permissions.changeRole||t.isLocked,click:()=>t.$dialog(t.model.link+"/changeRole")},{icon:"translate",text:`${t.model.language}`,title:`${t.$t("language")}: ${t.model.language}`,disabled:!t.permissions.changeLanguage||t.isLocked,click:()=>t.$dialog(t.model.link+"/changeLanguage")}]}})],1)}),[],!1,null,null,null,null).exports;const Mr=ut({props:{role:Object,roles:Array,search:String,title:String,users:Object},computed:{empty(){return{icon:"users",text:this.$t("role.empty")}},items(){return this.users.data.map((t=>(t.options=this.$dropdown(t.link),t)))},tabs(){const t=[{name:"all",label:this.$t("role.all"),link:"/users"}];for(const e of this.roles)t.push({name:e.id,label:e.title,link:"/users?role="+e.id});return t}},methods:{create(){var t;this.$dialog("users/create",{query:{role:null==(t=this.role)?void 0:t.id}})},paginate(t){this.$reload({query:{page:t.page}})}}},(function(){var t,e=this,i=e._self._c;return i("k-panel-inside",{staticClass:"k-users-view"},[i("k-header",{staticClass:"k-users-view-header",scopedSlots:e._u([{key:"buttons",fn:function(){return[i("k-button",{attrs:{disabled:!e.$panel.permissions.users.create,text:e.$t("user.create"),icon:"add",size:"sm",variant:"filled"},on:{click:e.create}})]},proxy:!0}])},[e._v(" "+e._s(e.$t("view.users"))+" ")]),i("k-tabs",{attrs:{tab:(null==(t=e.role)?void 0:t.id)??"all",tabs:e.tabs}}),i("k-collection",{attrs:{empty:e.empty,items:e.items,pagination:e.users.pagination},on:{paginate:e.paginate}})],1)}),[],!1,null,null,null,null).exports;const jr=ut({props:{id:String},created(){window.panel.deprecated(" will be removed in a future version.")}},(function(){var t=this._self._c;return t("k-panel-inside",[t("k-"+this.id+"-plugin-view",{tag:"component"})],1)}),[],!1,null,null,null,null).exports,Tr={install(t){t.component("k-error-view",ur),t.component("k-search-view",cr),t.component("k-file-view",pr),t.component("k-file-preview",hr),t.component("k-file-focus-button",mr),t.component("k-languages-view",fr),t.component("k-language-view",gr),t.component("k-login-view",kr),t.component("k-installation-view",br),t.component("k-reset-password-view",vr),t.component("k-user-info",yr),t.component("k-page-view",$r),t.component("k-site-view",wr),t.component("k-system-view",xr),t.component("k-table-update-status-cell",_r),t.component("k-account-view",Cr),t.component("k-user-avatar",Or),t.component("k-user-profile",Ar),t.component("k-user-view",Sr),t.component("k-users-view",Mr),t.component("k-plugin-view",jr)}},Ir={install(t){t.use(kt),t.use(oe),t.use(_e),t.use(qe),t.use(No),t.use(Yo),t.use(pl),t.use(xl),t.use(zl),t.use(er),t.use(Gl),t.use(er),t.use(ar),t.use(Tr),t.use(L)}},Er={install(t){window.onunhandledrejection=t=>{t.preventDefault(),window.panel.error(t.reason)},t.config.errorHandler=window.panel.error.bind(window.panel)}},Lr=(t={})=>{var e=t.desc?-1:1,i=-e,n=/^0/,s=/\s+/g,o=/^\s+|\s+$/g,l=/[^\x00-\x80]/,r=/^0x[0-9a-f]+$/i,a=/(0x[\da-fA-F]+|(^[\+\-]?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?(?=\D|\s|$))|\d+)/g,u=/(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/,c=t.insensitive?function(t){return function(t){if(t.toLocaleLowerCase)return t.toLocaleLowerCase();return t.toLowerCase()}(""+t).replace(o,"")}:function(t){return(""+t).replace(o,"")};function d(t){return t.replace(a,"\0$1\0").replace(/\0$/,"").replace(/^\0/,"").split("\0")}function p(t,e){return(!t.match(n)||1===e)&&parseFloat(t)||t.replace(s," ").replace(o,"")||0}return function(t,n){var s=c(t),o=c(n);if(!s&&!o)return 0;if(!s&&o)return i;if(s&&!o)return e;var a=d(s),h=d(o),m=parseInt(s.match(r),16)||1!==a.length&&Date.parse(s),f=parseInt(o.match(r),16)||m&&o.match(u)&&Date.parse(o)||null;if(f){if(mf)return e}for(var g=a.length,k=h.length,b=0,v=Math.max(g,k);b0)return e;if(w<0)return i;if(b===v-1)return 0}else{if(y<$)return i;if(y>$)return e}}return 0}};RegExp.escape=function(t){return t.replace(new RegExp("[-/\\\\^$*+?.()[\\]{}]","gu"),"\\$&")},Array.fromObject=function(t){return Array.isArray(t)?t:Object.values(t??{})};Array.prototype.sortBy=function(t){const e=t.split(" "),i=e[0],n=e[1]??"asc",s=Lr({desc:"desc"===n,insensitive:!0});return this.sort(((t,e)=>{const n=String(t[i]??""),o=String(e[i]??"");return s(n,o)}))},Array.prototype.split=function(t){return this.reduce(((e,i)=>(i===t?e.push([]):e[e.length-1].push(i),e)),[[]])},Array.wrap=function(t){return Array.isArray(t)?t:[t]};const Dr={search:(t,e,i={})=>{if((e??"").length<=(i.min??0))return t;const n=new RegExp(RegExp.escape(e),"ig"),s=i.field??"text",o=t.filter((t=>!!t[s]&&null!==t[s].match(n)));return i.limit?o.slice(0,i.limit):o}};const Br={read:function(t,e=!1){if(!t)return null;if("string"==typeof t)return t;if(t instanceof ClipboardEvent){if(t.preventDefault(),!0===e)return t.clipboardData.getData("text/plain");const i=t.clipboardData.getData("text/html")||t.clipboardData.getData("text/plain")||null;if(i)return i.replace(/\u00a0/g," ")}return null},write:function(t,e){if("string"!=typeof t&&(t=JSON.stringify(t,null,2)),e&&e instanceof ClipboardEvent)return e.preventDefault(),e.clipboardData.setData("text/plain",t),!0;const i=document.createElement("textarea");if(i.value=t,document.body.append(i),navigator.userAgent.match(/ipad|ipod|iphone/i)){i.contentEditable=!0,i.readOnly=!0;const t=document.createRange();t.selectNodeContents(i);const e=window.getSelection();e.removeAllRanges(),e.addRange(t),i.setSelectionRange(0,999999)}else i.select();return document.execCommand("copy"),i.remove(),!0}};function qr(t){if("string"==typeof t){if("pattern"===(t=t.toLowerCase()))return"var(--pattern)";if(!1===t.startsWith("#")&&!1===t.startsWith("var(")){const e="--color-"+t;if(window.getComputedStyle(document.documentElement).getPropertyValue(e))return`var(${e})`}return t}}function Pr(t,e=!1){if(!t.match("youtu"))return!1;let i=null;try{i=new URL(t)}catch(d){return!1}const n=i.pathname.split("/").filter((t=>""!==t)),s=n[0],o=n[1],l="https://"+(!0===e?"www.youtube-nocookie.com":i.host)+"/embed",r=t=>!!t&&null!==t.match(/^[a-zA-Z0-9_-]+$/);let a=i.searchParams,u=null;switch(n.join("/")){case"embed/videoseries":case"playlist":r(a.get("list"))&&(u=l+"/videoseries");break;case"watch":r(a.get("v"))&&(u=l+"/"+a.get("v"),a.has("t")&&a.set("start",a.get("t")),a.delete("v"),a.delete("t"));break;default:i.host.includes("youtu.be")&&r(s)?(u=!0===e?"https://www.youtube-nocookie.com/embed/"+s:"https://www.youtube.com/embed/"+s,a.has("t")&&a.set("start",a.get("t")),a.delete("t")):["embed","shorts"].includes(s)&&r(o)&&(u=l+"/"+o)}if(!u)return!1;const c=a.toString();return c.length&&(u+="?"+c),u}function Nr(t,e=!1){let i=null;try{i=new URL(t)}catch(a){return!1}const n=i.pathname.split("/").filter((t=>""!==t));let s=i.searchParams,o=null;switch(!0===e&&s.append("dnt",1),i.host){case"vimeo.com":case"www.vimeo.com":o=n[0];break;case"player.vimeo.com":o=n[1]}if(!o||!o.match(/^[0-9]*$/))return!1;let l="https://player.vimeo.com/video/"+o;const r=s.toString();return r.length&&(l+="?"+r),l}const zr={youtube:Pr,vimeo:Nr,video:function(t,e=!1){return!0===t.includes("youtu")?Pr(t,e):!0===t.includes("vimeo")&&Nr(t,e)}};function Fr(t){var e;if(void 0!==t.default)return vt(t.default);const i=window.panel.app.$options.components[`k-${t.type}-field`],n=null==(e=null==i?void 0:i.options.props)?void 0:e.value;if(void 0===n)return;const s=null==n?void 0:n.default;return"function"==typeof s?s():void 0!==s?s:null}const Rr={defaultValue:Fr,form:function(t){const e={};for(const i in t){const n=Fr(t[i]);void 0!==n&&(e[i]=n)}return e},isVisible:function(t,e){if("hidden"===t.type||!0===t.hidden)return!1;if(!t.when)return!0;for(const i in t.when){const n=e[i.toLowerCase()],s=t.when[i];if((void 0!==n||""!==s&&s!==[])&&n!==s)return!1}return!0},subfields:function(t,e){let i={};for(const n in e){const s=e[n];s.section=t.name,t.endpoints&&(s.endpoints={field:t.endpoints.field+"+"+n,section:t.endpoints.section,model:t.endpoints.model}),i[n]=s}return i}},Yr=t=>t.split(".").slice(-1).join(""),Ur=t=>t.split(".").slice(0,-1).join("."),Hr=t=>Intl.NumberFormat("en",{notation:"compact",style:"unit",unit:"byte",unitDisplay:"narrow"}).format(t),Vr={extension:Yr,name:Ur,niceSize:Hr};function Kr(t,e){if("string"==typeof t&&(t=document.querySelector(t)),!t)return!1;if(!e&&t.contains(document.activeElement)&&t!==document.activeElement)return!1;const i=["[autofocus]","[data-autofocus]","input","textarea","select","[contenteditable=true]","[type=submit]","button"];e&&i.unshift(`[name="${e}"]`);const n=function(t,e){for(const i of e){const e=t.querySelector(i);if(!0===Wr(e))return e}return null}(t,i);return n?(n.focus(),n):!0===Wr(t)&&(t.focus(),t)}function Wr(t){return!!t&&(!t.matches("[disabled], [aria-disabled], input[type=hidden]")&&(!t.closest("[aria-disabled]")&&!t.closest("[disabled]")&&"function"==typeof t.focus))}const Jr=t=>"function"==typeof window.Vue.options.components[t],Gr=t=>!!t.dataTransfer&&(!!t.dataTransfer.types&&(!0===t.dataTransfer.types.includes("Files")&&!1===t.dataTransfer.types.includes("text/plain")));const Xr={metaKey:function(){return window.navigator.userAgent.indexOf("Mac")>-1?"cmd":"ctrl"}};const Zr={status:function(t,e=!1){const i={class:"k-status-icon",icon:"status-"+t,title:window.panel.$t("page.status")+": "+window.panel.$t("page.status."+t),disabled:e,size:"xs",style:"--icon-size: 15px"};return e&&(i.title+=` (${window.panel.$t("disabled")})`),i.theme="draft"===t?"negative":"unlisted"===t?"info":"positive",i}},Qr=(t="3/2",e="100%",i=!0)=>{const n=String(t).split("/");if(2!==n.length)return e;const s=Number(n[0]),o=Number(n[1]);let l=100;return 0!==s&&0!==o&&(l=i?l/s*o:l/o*s,l=parseFloat(String(l)).toFixed(2)),l+"%"},ta={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="};function ea(t){return String(t).replace(/[&<>"'`=/]/g,(t=>ta[t]))}function ia(t){return!t||0===String(t).length}function na(t){const e=String(t);return e.charAt(0).toLowerCase()+e.slice(1)}function sa(t="",e=""){const i=new RegExp(`^(${e})+`,"g");return t.replace(i,"")}function oa(t="",e=""){const i=new RegExp(`(${e})+$`,"g");return t.replace(i,"")}function la(t,e={}){const i=(t,e={})=>{const n=e[ea(t.shift())]??"…";return"…"===n||0===t.length?n:i(t,n)},n="[{]{1,2}[\\s]?",s="[\\s]?[}]{1,2}";return(t=t.replace(new RegExp(`${n}(.*?)${s}`,"gi"),((t,n)=>i(n.split("."),e)))).replace(new RegExp(`${n}.*${s}`,"gi"),"…")}function ra(t){const e=String(t);return e.charAt(0).toUpperCase()+e.slice(1)}function aa(){let t,e,i="";for(t=0;t<32;t++)e=16*Math.random()|0,8!=t&&12!=t&&16!=t&&20!=t||(i+="-"),i+=(12==t?4:16==t?3&e|8:e).toString(16);return i}const ua={camelToKebab:function(t){return t.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase()},escapeHTML:ea,hasEmoji:function(t){if("string"!=typeof t)return!1;const e=t.match(/(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\u0023-\u0039]\ufe0f?\u20e3|\u3299|\u3297|\u303d|\u3030|\u24c2|\ud83c[\udd70-\udd71]|\ud83c[\udd7e-\udd7f]|\ud83c\udd8e|\ud83c[\udd91-\udd9a]|\ud83c[\udde6-\uddff]|[\ud83c\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|[\ud83c\ude32-\ude3a]|[\ud83c\ude50-\ude51]|\u203c|\u2049|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|\u00a9|\u00ae|\u2122|\u2139|\ud83c\udc04|[\u2600-\u26FF]|\u2b05|\u2b06|\u2b07|\u2b1b|\u2b1c|\u2b50|\u2b55|\u231a|\u231b|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\ud83c\udccf|\u2934|\u2935|[\u2190-\u21ff])/i);return null!==e&&null!==e.length},isEmpty:ia,lcfirst:na,ltrim:sa,pad:function(t,e=2){t=String(t);let i="";for(;i.length]+)>)/gi,"")},template:la,ucfirst:ra,ucwords:function(t){return String(t).split(/ /g).map((t=>ra(t))).join(" ")},unescapeHTML:function(t){for(const e in ta)t=String(t).replaceAll(ta[e],e);return t},uuid:aa},ca=async(t,e)=>new Promise(((i,n)=>{const s={url:"/",field:"file",method:"POST",filename:t.name,headers:{},attributes:{},complete:()=>{},error:()=>{},success:()=>{},progress:()=>{}},o=Object.assign(s,e),l=new XMLHttpRequest,r=new FormData;r.append(o.field,t,o.filename);for(const t in o.attributes)r.append(t,o.attributes[t]);const a=e=>{if(e.lengthComputable&&o.progress){const i=Math.max(0,Math.min(100,Math.ceil(e.loaded/e.total*100)));o.progress(l,t,i)}};l.upload.addEventListener("loadstart",a),l.upload.addEventListener("progress",a),l.addEventListener("load",(e=>{let s=null;try{s=JSON.parse(e.target.response)}catch(r){s={status:"error",message:"The file could not be uploaded"}}"error"===s.status?(o.error(l,t,s),n(s)):(o.progress(l,t,100),o.success(l,t,s),i(s))})),l.addEventListener("error",(e=>{const i=JSON.parse(e.target.response);o.progress(l,t,100),o.error(l,t,i),n(i)})),l.open(o.method,o.url,!0);for(const t in o.headers)l.setRequestHeader(t,o.headers[t]);l.send(r)}));function da(){var t;return new URL((null==(t=document.querySelector("base"))?void 0:t.href)??window.location.origin)}function pa(t={},e={}){e instanceof URL&&(e=e.search);const i=new URLSearchParams(e);for(const[n,s]of Object.entries(t))null!==s&&i.set(n,s);return i}function ha(t="",e={},i){return(t=ba(t,i)).search=pa(e,t.search),t}function ma(t){return null!==String(t).match(/^https?:\/\//)}function fa(t){return ba(t).origin===window.location.origin}function ga(t){if(t instanceof URL||t instanceof Location)return!0;if("string"!=typeof t)return!1;try{return new URL(t,window.location),!0}catch(e){return!1}}function ka(t,e){return!0===ma(t)?t:(e=e??da(),(e=String(e).replaceAll(/\/$/g,""))+"/"+(t=String(t).replaceAll(/^\//g,"")))}function ba(t,e){return t instanceof URL?t:new URL(ka(t,e))}const va={base:da,buildUrl:ha,buildQuery:pa,isAbsolute:ma,isSameOrigin:fa,isUrl:ga,makeAbsolute:ka,toObject:ba},ya={install(t){t.prototype.$helper={array:Dr,clipboard:Br,clone:xt.clone,color:qr,embed:zr,focus:Kr,isComponent:Jr,isUploadEvent:Gr,debounce:zt,field:Rr,file:Vr,keyboard:Xr,object:xt,page:Zr,pad:ua.pad,ratio:Qr,slug:ua.slug,sort:Lr,string:ua,upload:ca,url:va,uuid:ua.uuid},t.prototype.$esc=ua.escapeHTML}},$a={install(t){t.directive("direction",{inserted(t,e,i){!0!==i.context.disabled?t.dir=window.panel.translation.direction:t.dir=null}})}},wa={install(t){window.panel.redirect=window.panel.redirect.bind(window.panel),window.panel.reload=window.panel.reload.bind(window.panel),window.panel.request=window.panel.request.bind(window.panel),window.panel.search=window.panel.search.bind(window.panel);const e=["api","config","direction","events","language","languages","license","menu","multilang","permissions","search","searches","system","t","translation","url","urls","user","view"];for(const i of e){const e=`$${i}`;t.prototype[e]=window.panel[e]=window.panel[i]}window.panel.$vue=window.panel.app}},xa=/^#?([\da-f]{3}){1,2}$/i,_a=/^#?([\da-f]{4}){1,2}$/i,Sa=/^rgba?\(\s*(\d{1,3})(%?)(?:,|\s)+(\d{1,3})(%?)(?:,|\s)+(\d{1,3})(%?)(?:,|\s|\/)*(\d*(?:\.\d+)?)(%?)\s*\)?$/i,Ca=/^hsla?\(\s*(\d{1,3}\.?\d*)(deg|rad|grad|turn)?(?:,|\s)+(\d{1,3})%(?:,|\s)+(\d{1,3})%(?:,|\s|\/)*(\d*(?:\.\d+)?)(%?)\s*\)?$/i;function Oa(t){return"string"==typeof t&&(xa.test(t)||_a.test(t))}function Aa(t){return yt(t)&&"r"in t&&"g"in t&&"b"in t}function Ma(t){return yt(t)&&"h"in t&&"s"in t&&"l"in t}function ja({h:t,s:e,v:i,a:n}){if(0===i)return{h:t,s:0,l:0,a:n};if(0===e&&1===i)return{h:t,s:1,l:1,a:n};const s=i*(2-e)/2;return{h:t,s:e=i*e/(1-Math.abs(2*s-1)),l:s,a:n}}function Ta({h:t,s:e,l:i,a:n}){const s=e*(i<.5?i:1-i);return{h:t,s:e=0===s?0:2*s/(i+s),v:i+s,a:n}}function Ia(t){if(!0===xa.test(t)||!0===_a.test(t)){"#"===t[0]&&(t=t.slice(1)),3===t.length&&(t=t.split("").reduce(((t,e)=>t+e+e),""));const e=parseInt(t,16);return!0===xa.test(t)?{r:e>>16,g:e>>8&255,b:255&e,a:1}:{r:e>>24&255,g:e>>16&255,b:e>>8&255,a:Math.round((255&e)/255*100)/100}}throw new Error(`unknown hex color: ${t}`)}function Ea({r:t,g:e,b:i,a:n=1}){let s="#"+(1<<24|t<<16|e<<8|i).toString(16).slice(1);return n<1&&(s+=(256|Math.round(255*n)).toString(16).slice(1)),s}function La({h:t,s:e,l:i,a:n}){const s=e*Math.min(i,1-i),o=(e,n=(e+t/30)%12)=>i-s*Math.max(Math.min(n-3,9-n,1),-1);return{r:255*o(0),g:255*o(8),b:255*o(4),a:n}}function Da({r:t,g:e,b:i,a:n}){t/=255,e/=255,i/=255;const s=Math.max(t,e,i),o=s-Math.min(t,e,i),l=1-Math.abs(s+s-o-1);let r=o&&(s==t?(e-i)/o:s==e?2+(i-t)/o:4+(t-e)/o);return r=60*(r<0?r+6:r),{h:r,s:l?o/l:0,l:(s+s-o)/2,a:n}}function Ba(t){return Ea(La(t))}function qa(t){return Da(Ia(t))}function Pa(t,e){return t=Number(t),"grad"===e?t*=.9:"rad"===e?t*=180/Math.PI:"turn"===e&&(t*=360),parseInt(t%360)}function Na(t,e){if(!0===Oa(t))switch("#"!==t[0]&&(t="#"+t),e){case"hex":return t;case"rgb":return Ia(t);case"hsl":return qa(t);case"hsv":return Ta(qa(t))}if(!0===Aa(t))switch(e){case"hex":return Ea(t);case"rgb":return t;case"hsl":return Da(t);case"hsv":return function({r:t,g:e,b:i,a:n}){t/=255,e/=255,i/=255;const s=Math.max(t,e,i),o=s-Math.min(t,e,i);let l=o&&(s==t?(e-i)/o:s==e?2+(i-t)/o:4+(t-e)/o);return l=60*(l<0?l+6:l),{h:l,s:s&&o/s,v:s,a:n}}(t)}if(!0===Ma(t))switch(e){case"hex":return Ba(t);case"rgb":return La(t);case"hsl":return t;case"hsv":return Ta(t)}if(!0===function(t){return yt(t)&&"h"in t&&"s"in t&&"v"in t}(t))switch(e){case"hex":return Ba(ja(t));case"rgb":return function({h:t,s:e,v:i,a:n}){const s=(n,s=(n+t/60)%6)=>i-i*e*Math.max(Math.min(s,4-s,1),0);return{r:255*s(5),g:255*s(3),b:255*s(1),a:n}}(t);case"hsl":return ja(t);case"hsv":return t}throw new Error(`Invalid color conversion: ${JSON.stringify(t)} -> ${e}`)}function za(t){let e;if(!t||"string"!=typeof t)return!1;if(!0===Oa(t))return"#"!==t[0]&&(t="#"+t),t;if(e=t.match(Sa)){const t={r:Number(e[1]),g:Number(e[3]),b:Number(e[5]),a:Number(e[7]||1)};return"%"===e[2]&&(t.r=Math.ceil(2.55*t.r)),"%"===e[4]&&(t.g=Math.ceil(2.55*t.g)),"%"===e[6]&&(t.b=Math.ceil(2.55*t.b)),"%"===e[8]&&(t.a=t.a/100),t}if(e=t.match(Ca)){let[t,i,n,s,o]=e.slice(1);const l={h:Pa(t,i),s:Number(n)/100,l:Number(s)/100,a:Number(o||1)};return"%"===e[6]&&(l.a=l.a/100),l}return null}const Fa={convert:Na,parse:za,parseAs:function(t,e){const i=za(t);return i&&e?Na(i,e):i},toString:function(t,e,i=!0){var n,s;let o=t;if("string"==typeof o&&(o=za(t)),o&&e&&(o=Na(o,e)),!0===Oa(o))return!0!==i&&(5===o.length?o=o.slice(0,4):o.length>7&&(o=o.slice(0,7))),o.toLowerCase();if(!0===Aa(o)){const t=o.r.toFixed(),e=o.g.toFixed(),s=o.b.toFixed(),l=null==(n=o.a)?void 0:n.toFixed(2);return i&&l&&l<1?`rgb(${t} ${e} ${s} / ${l})`:`rgb(${t} ${e} ${s})`}if(!0===Ma(o)){const t=o.h.toFixed(),e=(100*o.s).toFixed(),n=(100*o.l).toFixed(),l=null==(s=o.a)?void 0:s.toFixed(2);return i&&l&&l<1?`hsl(${t} ${e}% ${n}% / ${l})`:`hsl(${t} ${e}% ${n}%)`}throw new Error(`Unsupported color: ${JSON.stringify(t)}`)}};D.extend(B),D.extend(((t,e,i)=>{i.interpret=(t,e="date")=>{const n={date:{"YYYY-MM-DD":!0,"YYYY-MM-D":!0,"YYYY-MM-":!0,"YYYY-MM":!0,"YYYY-M-DD":!0,"YYYY-M-D":!0,"YYYY-M-":!0,"YYYY-M":!0,"YYYY-":!0,YYYYMMDD:!0,"MMM DD YYYY":!1,"MMM D YYYY":!1,"MMM DD YY":!1,"MMM D YY":!1,"MMM YYYY":!0,"MMM DD":!1,"MMM D":!1,"MM YYYY":!0,"M YYYY":!0,"DD MMMM YYYY":!1,"DD MMMM YY":!1,"DD MMMM":!1,"D MMMM YYYY":!1,"D MMMM YY":!1,"D MMMM":!1,"DD MMM YYYY":!1,"D MMM YYYY":!1,"DD MMM YY":!1,"D MMM YY":!1,"DD MMM":!1,"D MMM":!1,"DD MM YYYY":!1,"DD M YYYY":!1,"D MM YYYY":!1,"D M YYYY":!1,"DD MM YY":!1,"D MM YY":!1,"DD M YY":!1,"D M YY":!1,YYYY:!0,MMMM:!0,MMM:!0,"DD MM":!1,"DD M":!1,"D MM":!1,"D M":!1,DD:!1,D:!1},time:{"HH:mm:ss a":!1,"HH:mm:ss":!1,"HH:mm a":!1,"HH:mm":!1,"HH a":!1,HH:!1}};if("string"==typeof t&&""!==t)for(const s in n[e]){const o=i(t,s,n[e][s]);if(!0===o.isValid())return o}return null}})),D.extend(((t,e,i)=>{const n=t=>"date"===t?"YYYY-MM-DD":"time"===t?"HH:mm:ss":"YYYY-MM-DD HH:mm:ss";e.prototype.toISO=function(t="datetime"){return this.format(n(t))},i.iso=function(t,e="datetime"){const s=i(t,n(e));return s&&s.isValid()?s:null}})),D.extend(((t,e)=>{e.prototype.merge=function(t,e="date"){let i=this.clone();if(!t||!t.isValid())return this;if("string"==typeof e){const t={date:["year","month","date"],time:["hour","minute","second"]};if(!1===Object.hasOwn(t,e))throw new Error("Invalid merge unit alias");e=t[e]}for(const n of e)i=i.set(n,t.get(n));return i}})),D.extend(((t,e,i)=>{i.pattern=t=>new class{constructor(t,e){this.dayjs=t,this.pattern=e;const i={year:["YY","YYYY"],month:["M","MM","MMM","MMMM"],day:["D","DD"],hour:["h","hh","H","HH"],minute:["m","mm"],second:["s","ss"],meridiem:["a"]};this.parts=this.pattern.split(/\W/).map(((t,e)=>{const n=this.pattern.indexOf(t);return{index:e,unit:Object.keys(i)[Object.values(i).findIndex((e=>e.includes(t)))],start:n,end:n+(t.length-1)}}))}at(t,e=t){const i=this.parts.filter((i=>i.start<=t&&i.end>=e-1));return i[0]?i[0]:this.parts.filter((e=>e.start<=t)).pop()}format(t){return t&&t.isValid()?t.format(this.pattern):null}}(i,t)})),D.extend(((t,e)=>{e.prototype.round=function(t="date",e=1){const i=["second","minute","hour","date","month","year"];if("day"===t&&(t="date"),!1===i.includes(t))throw new Error("Invalid rounding unit");if(["date","month","year"].includes(t)&&1!==e||"hour"===t&&24%e!=0||["second","minute"].includes(t)&&60%e!=0)throw"Invalid rounding size for "+t;let n=this.clone();const s=i.indexOf(t),o=i.slice(0,s),l=o.pop();for(const r of o)n=n.startOf(r);if(l){const e={month:12,date:n.daysInMonth(),hour:24,minute:60,second:60}[l];Math.round(n.get(l)/e)*e===e&&(n=n.add(1,"date"===t?"day":t)),n=n.startOf(t)}return n=n.set(t,Math.round(n.get(t)/e)*e),n}})),D.extend(((t,e,i)=>{e.prototype.validate=function(t,e,n="day"){if(!this.isValid())return!1;if(!t)return!0;t=i.iso(t);const s={min:"isAfter",max:"isBefore"}[e];return this.isSame(t,n)||this[s](t,n)}}));const Ra={install(t){t.prototype.$library={autosize:q,colors:Fa,dayjs:D}}},Ya=()=>({close(){sessionStorage.setItem("kirby$activation$card","true"),this.isOpen=!1},isOpen:"true"!==sessionStorage.getItem("kirby$activation$card"),open(){sessionStorage.removeItem("kirby$activation$card"),this.isOpen=!0}}),Ua=t=>({async changeName(e,i,n){return t.patch(this.url(e,i,"name"),{name:n})},async delete(e,i){return t.delete(this.url(e,i))},async get(e,i,n){let s=await t.get(this.url(e,i),n);return!0===Array.isArray(s.content)&&(s.content={}),s},id:t=>!0===t.startsWith("/@/file/")?t.replace("/@/file/","@"):!0===t.startsWith("file://")?t.replace("file://","@"):t,link(t,e,i){return"/"+this.url(t,e,i)},async update(e,i,n){return t.patch(this.url(e,i),n)},url(t,e,i){let n="files/"+this.id(e);return t&&(n=t+"/"+n),i&&(n+="/"+i),n}}),Ha=t=>({async blueprint(e){return t.get("pages/"+this.id(e)+"/blueprint")},async blueprints(e,i){return t.get("pages/"+this.id(e)+"/blueprints",{section:i})},async changeSlug(e,i){return t.patch("pages/"+this.id(e)+"/slug",{slug:i})},async changeStatus(e,i,n){return t.patch("pages/"+this.id(e)+"/status",{status:i,position:n})},async changeTemplate(e,i){return t.patch("pages/"+this.id(e)+"/template",{template:i})},async changeTitle(e,i){return t.patch("pages/"+this.id(e)+"/title",{title:i})},async children(e,i){return t.post("pages/"+this.id(e)+"/children/search",i)},async create(e,i){return null===e||"/"===e?t.post("site/children",i):t.post("pages/"+this.id(e)+"/children",i)},async delete(e,i){return t.delete("pages/"+this.id(e),i)},async duplicate(e,i,n){return t.post("pages/"+this.id(e)+"/duplicate",{slug:i,children:n.children??!1,files:n.files??!1})},async get(e,i){let n=await t.get("pages/"+this.id(e),i);return!0===Array.isArray(n.content)&&(n.content={}),n},id:t=>!0===t.startsWith("/@/page/")?t.replace("/@/page/","@"):!0===t.startsWith("page://")?t.replace("page://","@"):t.replace(/\//g,"+"),async files(e,i){return t.post("pages/"+this.id(e)+"/files/search",i)},link(t){return"/"+this.url(t)},async preview(t){return(await this.get(this.id(t),{select:"previewUrl"})).previewUrl},async search(e,i){return e?t.post("pages/"+this.id(e)+"/children/search?select=id,title,hasChildren",i):t.post("site/children/search?select=id,title,hasChildren",i)},async update(e,i){return t.patch("pages/"+this.id(e),i)},url(t,e){let i=null===t?"pages":"pages/"+String(t).replace(/\//g,"+");return e&&(i+="/"+e),i}});class Va extends Error{constructor(t,{request:e,response:i,cause:n}){super(i.json.message??t,{cause:n}),this.request=e,this.response=i}state(){return this.response.json}}class Ka extends Va{}class Wa extends Va{state(){return{message:this.message,text:this.response.text}}}const Ja=t=>(window.location.href=ka(t),!1),Ga=async(t,e={})=>{var i;(e={cache:"no-store",credentials:"same-origin",mode:"same-origin",...e}).body=((i=e.body)instanceof HTMLFormElement&&(i=new FormData(i)),i instanceof FormData&&(i=Object.fromEntries(i)),"object"==typeof i?JSON.stringify(i):i),e.headers=((t={},e={})=>{return{"content-type":"application/json","x-csrf":e.csrf??!1,"x-fiber":!0,"x-fiber-globals":(i=e.globals,!!i&&(!1===Array.isArray(i)?String(i):i.join(","))),"x-fiber-referrer":e.referrer??!1,...wt(t)};var i})(e.headers,e),e.url=ha(t,e.query);const n=new Request(e.url,e);return!1===fa(n.url)?Ja(n.url):await Xa(n,await fetch(n))},Xa=async(t,e)=>{var i;if(!1===e.headers.get("Content-Type").includes("application/json"))return Ja(e.url);try{e.text=await e.text(),e.json=JSON.parse(e.text)}catch(n){throw new Wa("Invalid JSON response",{cause:n,request:t,response:e})}if(401===e.status)throw new Ka("Unauthenticated",{request:t,response:e});if("error"===(null==(i=e.json)?void 0:i.status))throw e.json;if(!1===e.ok)throw new Va(`The request to ${e.url} failed`,{request:t,response:e});return{request:t,response:e}},Za=t=>({blueprint:async e=>t.get("users/"+e+"/blueprint"),blueprints:async(e,i)=>t.get("users/"+e+"/blueprints",{section:i}),changeEmail:async(e,i)=>t.patch("users/"+e+"/email",{email:i}),changeLanguage:async(e,i)=>t.patch("users/"+e+"/language",{language:i}),changeName:async(e,i)=>t.patch("users/"+e+"/name",{name:i}),changePassword:async(e,i)=>t.patch("users/"+e+"/password",{password:i}),changeRole:async(e,i)=>t.patch("users/"+e+"/role",{role:i}),create:async e=>t.post("users",e),delete:async e=>t.delete("users/"+e),deleteAvatar:async e=>t.delete("users/"+e+"/avatar"),link(t,e){return"/"+this.url(t,e)},async list(e){return t.post(this.url(null,"search"),e)},get:async(e,i)=>t.get("users/"+e,i),async roles(e){return(await t.get(this.url(e,"roles"))).data.map((t=>({info:t.description??`(${window.panel.$t("role.description.placeholder")})`,text:t.title,value:t.name})))},search:async e=>t.post("users/search",e),update:async(e,i)=>t.patch("users/"+e,i),url(t,e){let i=t?"users/"+t:"users";return e&&(i+="/"+e),i}}),Qa=t=>{const e={csrf:t.system.csrf,endpoint:oa(t.urls.api,"/"),methodOverwrite:!0,ping:null,requests:[],running:0},i=()=>{clearInterval(e.ping),e.ping=setInterval(e.auth.ping,3e5)};return e.request=async(n,s={},o=!1)=>{const l=n+"/"+JSON.stringify(s);e.requests.push(l),!1===o&&(t.isLoading=!0),e.language=t.language.code;try{return await(t=>async(e,i={})=>{i={cache:"no-store",credentials:"same-origin",mode:"same-origin",headers:{"content-type":"application/json","x-csrf":t.csrf,"x-language":t.language,...wt(i.headers??{})},...i},t.methodOverwrite&&"GET"!==i.method&&"POST"!==i.method&&(i.headers["x-http-method-override"]=i.method,i.method="POST"),i.url=oa(t.endpoint,"/")+"/"+sa(e,"/");const n=new Request(i.url,i),{response:s}=await Xa(n,await fetch(n));let o=s.json;return o.data&&"model"===o.type&&(o=o.data),o})(e)(n,s)}finally{i(),e.requests=e.requests.filter((t=>t!==l)),0===e.requests.length&&(t.isLoading=!1)}},e.auth=(t=>({async login(e){const i={long:e.remember??!1,email:e.email,password:e.password};return t.post("auth/login",i)},logout:async()=>t.post("auth/logout"),ping:async()=>t.post("auth/ping"),user:async e=>t.get("auth",e),verifyCode:async e=>t.post("auth/code",{code:e})}))(e),e.delete=(t=>async(e,i,n,s=!1)=>t.post(e,i,n,"DELETE",s))(e),e.files=Ua(e),e.get=(t=>async(e,i,n,s=!1)=>(i&&(e+="?"+Object.keys(i).filter((t=>void 0!==i[t]&&null!==i[t])).map((t=>t+"="+i[t])).join("&")),t.request(e,Object.assign(n??{},{method:"GET"}),s)))(e),e.languages=(t=>({create:async e=>t.post("languages",e),delete:async e=>t.delete("languages/"+e),get:async e=>t.get("languages/"+e),list:async()=>t.get("languages"),update:async(e,i)=>t.patch("languages/"+e,i)}))(e),e.pages=Ha(e),e.patch=(t=>async(e,i,n,s=!1)=>t.post(e,i,n,"PATCH",s))(e),e.post=(t=>async(e,i,n,s="POST",o=!1)=>t.request(e,Object.assign(n??{},{method:s,body:JSON.stringify(i)}),o))(e),e.roles=(t=>({list:async e=>t.get("roles",e),get:async e=>t.get("roles/"+e)}))(e),e.system=(t=>({get:async(e={view:"panel"})=>t.get("system",e),install:async e=>(await t.post("system/install",e)).user,register:async e=>t.post("system/register",e)}))(e),e.site=(t=>({blueprint:async()=>t.get("site/blueprint"),blueprints:async()=>t.get("site/blueprints"),changeTitle:async e=>t.patch("site/title",{title:e}),children:async e=>t.post("site/children/search",e),get:async(e={view:"panel"})=>t.get("site",e),update:async e=>t.post("site",e)}))(e),e.translations=(t=>({list:async()=>t.get("translations"),get:async e=>t.get("translations/"+e)}))(e),e.users=Za(e),i(),e},tu=()=>({addEventListener(t,e){"function"==typeof e&&(this.on[t]=e)},addEventListeners(t){if(!1!==yt(t))for(const e in t)this.addEventListener(e,t[e])},emit(t,...e){return this.hasEventListener(t)?this.on[t](...e):()=>{}},hasEventListener(t){return"function"==typeof this.on[t]},listeners(){return this.on},on:{}}),eu=(t,e={})=>({...e,key:()=>t,defaults:()=>e,reset(){return this.set(this.defaults())},set(t){this.validateState(t);for(const e in this.defaults())this[e]=t[e]??this.defaults()[e];return this.state()},state(){const t={};for(const e in this.defaults())t[e]=this[e]??this.defaults()[e];return t},validateState(t){if(!1===yt(t))throw new Error(`Invalid ${this.key()} state`);return!0}}),iu=(t,e,i)=>{const n=eu(e,i);return{...n,...tu(),async load(e,i={}){return!0!==i.silent&&(this.isLoading=!0),await t.open(e,i),this.isLoading=!1,this.addEventListeners(i.on),this.state()},async open(t,e={}){return"function"==typeof e&&(e={on:{submit:e}}),!0===ga(t)?this.load(t,e):(this.set(t),this.addEventListeners(e.on),this.emit("open",t,e),this.state())},async post(e,i={}){var n;if(!this.path)throw new Error(`The ${this.key()} cannot be posted`);this.isLoading=!0,e=e??(null==(n=this.props)?void 0:n.value)??{};try{return await t.post(this.path,e,i)}catch(s){t.error(s)}finally{this.isLoading=!1}return!1},async refresh(e={}){e.url=e.url??this.url();const i=(await t.get(e.url,e))["$"+this.key()];if(i&&i.component===this.component)return this.props=i.props,this.state()},async reload(t={}){if(!this.path)return!1;this.open(this.url(),t)},set(t){return n.set.call(this,t),this.on={},this.addEventListeners(t.on??{}),this.state()},url(){return t.url(this.path,this.query)}}},nu=(t,e,i)=>{const n=iu(t,e,i);return{...n,async cancel(){this.isOpen&&this.emit("cancel"),this.close()},async close(){!1!==this.isOpen&&(this.isOpen=!1,this.emit("close"),this.reset(),0===t.overlays().length&&(document.documentElement.removeAttribute("data-overlay"),document.documentElement.style.removeProperty("--scroll-top")))},focus(t){Kr(`.k-${this.key()}-portal`,t)},input(t){!1!==this.isOpen&&(Vue.set(this.props,"value",t),this.emit("input",t))},isOpen:!1,listeners(){return{...this.on,cancel:this.cancel.bind(this),close:this.close.bind(this),input:this.input.bind(this),submit:this.submit.bind(this),success:this.success.bind(this)}},async open(e,i){return!1===this.isOpen&&t.notification.close(),document.documentElement.setAttribute("data-overlay","true"),document.documentElement.style.setProperty("--scroll-top",window.scrollY+"px"),await n.open.call(this,e,i),this.isOpen=!0,this.state()},async submit(t,e={}){if(t=t??this.props.value,this.hasEventListener("submit"))return this.emit("submit",t,e);if(!this.path)return this.close();const i=await this.post(t,e);return!1===yt(i)?i:this.success(i["$"+this.key()]??{})},success(e){return this.hasEventListener("success")?this.emit("success",e):("string"==typeof e&&t.notification.success(e),this.close(),this.successNotification(e),this.successEvents(e),this.successDispatch(e),e.route||e.redirect?this.successRedirect(e):t.view.reload(e.reload),e)},successDispatch(e){if(!1!==yt(e.dispatch))for(const i in e.dispatch){const n=e.dispatch[i];t.app.$store.dispatch(i,!0===Array.isArray(n)?[...n]:n)}},successEvents(e){if(e.event){const i=Array.wrap(e.event);for(const n of i)"string"==typeof n&&t.events.emit(n,e)}!1!==e.emit&&t.events.emit("success",e)},successNotification(e){e.message&&t.notification.success(e.message)},successRedirect(e){const i=e.route??e.redirect;return!!i&&("string"==typeof i?t.open(i):t.open(i.url,i.options))},get value(){var t;return null==(t=this.props)?void 0:t.value}}},su=t=>{t.events.on("dialog.save",(e=>{var i;null==(i=null==e?void 0:e.preventDefault)||i.call(e),t.dialog.submit()}));const e=nu(t,"dialog",{component:null,isLoading:!1,on:{},path:null,props:{},query:{},referrer:null,timestamp:null,legacy:!1,ref:null});return{...e,async close(){this.ref&&(this.ref.visible=!1),e.close.call(this)},async open(t,i={}){return t instanceof window.Vue?this.openComponent(t):("string"==typeof t&&(t=`/dialogs/${t}`),e.open.call(this,t,i))},async openComponent(i){t.deprecated("Dialog components should no longer be used in your templates");const n=await e.open.call(this,{component:i.$options._componentTag,legacy:!0,props:{...i.$attrs,...i.$props},ref:i}),s=this.listeners();for(const t in s)i.$on(t,s[t]);return i.visible=!0,n}}},ou=()=>({...eu("drag",{type:null,data:{}}),get isDragging(){return null!==this.type},start(t,e){this.type=t,this.data=e},stop(){this.type=null,this.data={}}}),lu=()=>({add(t){if(!t.id)throw new Error("The state needs an ID");!0!==this.has(t.id)&&this.milestones.push(t)},at(t){return this.milestones.at(t)},get(t=null){return null===t?this.milestones:this.milestones.find((e=>e.id===t))},goto(t){const e=this.index(t);if(-1!==e)return this.milestones=this.milestones.slice(0,e+1),this.milestones[e]},has(t){return void 0!==this.get(t)},index(t){return this.milestones.findIndex((e=>e.id===t))},isEmpty(){return 0===this.milestones.length},last(){return this.milestones.at(-1)},milestones:[],remove(t=null){return null===t?this.removeLast():this.milestones=this.milestones.filter((e=>e.id!==t))},removeLast(){return this.milestones=this.milestones.slice(0,-1)},replace(t,e){-1===t&&(t=this.milestones.length-1),Vue.set(this.milestones,t,e)}}),ru=t=>{const e=nu(t,"drawer",{component:null,isLoading:!1,on:{},path:null,props:{},query:{},referrer:null,timestamp:null,id:null});return t.events.on("drawer.save",(e=>{e.preventDefault(),t.drawer.submit()})),{...e,get breadcrumb(){return this.history.milestones},async close(t){if(!1!==this.isOpen&&(void 0===t||t===this.id)){if(this.history.removeLast(),!0!==this.history.isEmpty())return this.open(this.history.last());e.close.call(this)}},goTo(t){const e=this.history.goto(t);void 0!==e&&this.open(e)},history:lu(),get icon(){return this.props.icon??"box"},input(t){Vue.set(this.props,"value",t),this.emit("input",this.props.value)},listeners(){return{...this.on,cancel:this.cancel.bind(this),close:this.close.bind(this),crumb:this.goTo.bind(this),input:this.input.bind(this),submit:this.submit.bind(this),success:this.success.bind(this),tab:this.tab.bind(this)}},async open(t,i={}){"string"==typeof t&&(t=`/drawers/${t}`),await e.open.call(this,t,i),this.tab(t.tab);const n=this.state();return!0===t.replace?this.history.replace(-1,n):this.history.add(n),this.focus(),n},set(t){return e.set.call(this,t),this.id=this.id??aa(),this.state()},tab(t){const e=this.props.tabs??{};if(!(t=t??Object.keys(e??{})[0]))return!1;Vue.set(this.props,"fields",e[t].fields),Vue.set(this.props,"tab",t),this.emit("tab",t),setTimeout((()=>{this.focus()}))}}},au=t=>{const e=iu(t,"dropdown",{component:null,isLoading:!1,on:{},path:null,props:{},query:{},referrer:null,timestamp:null});return{...e,close(){this.emit("close"),this.reset()},open(t,i={}){return"string"==typeof t&&(t=`/dropdowns/${t}`),e.open.call(this,t,i)},openAsync(t,e={}){return async i=>{await this.open(t,e);const n=this.options();if(0===n.length)throw Error("The dropdown is empty");i(n)}},options(){return!1===Array.isArray(this.props.options)?[]:this.props.options.map((e=>e.dialog?(e.click=()=>{const i="string"==typeof e.dialog?e.dialog:e.dialog.url,n="object"==typeof e.dialog?e.dialog:{};return t.app.$dialog(i,n)},e):e))},set(t){return t.options&&(t.props={options:t.options}),e.set.call(this,t)}}},uu=t=>{const e=P();e.on("online",(()=>{t.isOffline=!1})),e.on("offline",(()=>{t.isOffline=!0})),e.on("keydown.cmd.s",(i=>{e.emit(t.context+".save",i)})),e.on("keydown.cmd.shift.f",(()=>t.search())),e.on("keydown.cmd./",(()=>t.search()));const i={document:{blur:!0,click:!1,copy:!0,focus:!0,paste:!0},window:{dragenter:!1,dragexit:!1,dragleave:!1,dragover:!1,drop:!1,keydown:!1,keyup:!1,offline:!1,online:!1,popstate:!1}};return{blur(t){this.emit("blur",t)},click(t){this.emit("click",t)},copy(t){this.emit("copy",t)},dragenter(t){this.entered=t.target,this.prevent(t),this.emit("dragenter",t)},dragexit(t){this.prevent(t),this.entered=null,this.emit("dragexit",t)},dragleave(t){this.prevent(t),this.entered===t.target&&(this.entered=null,this.emit("dragleave",t))},dragover(t){this.prevent(t),this.emit("dragover",t)},drop(t){this.prevent(t),this.entered=null,this.emit("drop",t)},emit:e.emit,entered:null,focus(t){this.emit("focus",t)},keychain(t,e){let i=[t];(e.metaKey||e.ctrlKey)&&i.push("cmd"),!0===e.altKey&&i.push("alt"),!0===e.shiftKey&&i.push("shift");let n=e.key?na(e.key):null;const s={escape:"esc",arrowUp:"up",arrowDown:"down",arrowLeft:"left",arrowRight:"right"};return s[n]&&(n=s[n]),n&&!1===["alt","control","shift","meta"].includes(n)&&i.push(n),i.join(".")},keydown(t){this.emit(this.keychain("keydown",t),t),this.emit("keydown",t)},keyup(t){this.emit(this.keychain("keyup",t),t),this.emit("keyup",t)},off:e.off,offline(t){this.emit("offline",t)},on:e.on,online(t){this.emit("online",t)},paste(t){this.emit("paste",t)},popstate(t){this.emit("popstate",t)},prevent(t){t.stopPropagation(),t.preventDefault()},subscribe(){for(const t in i.document)document.addEventListener(t,this[t].bind(this),i.document[t]);for(const t in i.window)window.addEventListener(t,this[t].bind(this),i.window[t])},unsubscribe(){for(const t in i.document)document.removeEventListener(t,this[t]);for(const t in i.window)window.removeEventListener(t,this[t])},$on(...t){window.panel.deprecated("`events.$on` will be removed in a future version. Use `events.on` instead."),e.on(...t)},$emit(...t){window.panel.deprecated("`events.$emit` will be removed in a future version. Use `events.emit` instead."),e.emit(...t)},$off(...t){window.panel.deprecated("`events.$off` will be removed in a future version. Use `events.off` instead."),e.off(...t)}}},cu={interval:null,start(t,e){this.stop(),t&&(this.interval=setInterval(e,t))},stop(){clearInterval(this.interval),this.interval=null}},du=(t={})=>({...eu("notification",{context:null,details:null,icon:null,isOpen:!1,message:null,timeout:null,type:null}),close(){return this.timer.stop(),this.reset(),this.state()},deprecated(t){console.warn("Deprecated: "+t)},error(e){if(e instanceof Ka&&t.user.id)return t.redirect("logout");if(e instanceof Wa)return this.fatal(e);if(e instanceof Va){const t=Object.values(e.response.json).find((t=>"string"==typeof(null==t?void 0:t.error)));t&&(e.message=t.error)}return"string"==typeof e&&(e={message:e,type:"error"}),e={message:e.message??"Something went wrong",details:e.details??{}},"view"===t.context&&t.dialog.open({component:"k-error-dialog",props:e,type:"error"}),this.open({message:e.message,type:"error",icon:"alert"})},get isFatal(){return"fatal"===this.type},fatal(t){return"string"==typeof t?this.open({message:t,type:"fatal"}):t instanceof Wa?this.open({message:t.response.text,type:"fatal"}):this.open({message:t.message??"Something went wrong",type:"fatal"})},open(e){return this.timer.stop(),"string"==typeof e?this.success(e):(this.set({context:t.context,...e}),this.isOpen=!0,this.timer.start(this.timeout,(()=>this.close())),this.state())},success(t){return t||(t={}),"string"==typeof t&&(t={message:t}),this.open({timeout:4e3,type:"success",icon:"check",...t})},get theme(){return"error"===this.type?"negative":"positive"},timer:cu}),pu=()=>({...eu("language",{code:null,default:!1,direction:"ltr",name:null,rules:null}),get isDefault(){return this.default}}),hu=(t,e,i)=>{if(!i.template&&!i.render&&!i.extends)throw new Error(`Neither template nor render method provided. Nor extending a component when loading plugin component "${e}". The component has not been registered.`);return(i=mu(t,e,i)).template&&(i.render=null),i=fu(i),!0===Jr(e)&&window.console.warn(`Plugin is replacing "${e}"`),t.component(e,i),i},mu=(t,e,i)=>"string"!=typeof(null==i?void 0:i.extends)?i:!1===Jr(i.extends)?(window.console.warn(`Problem with plugin trying to register component "${e}": cannot extend non-existent component "${i.extends}"`),i.extends=null,i):(i.extends=t.options.components[i.extends].extend({options:i,components:{...t.options.components,...i.components??{}}}),i),fu=t=>{if(!1===Array.isArray(t.mixins))return t;const e={dialog:Lt,drawer:ge,section:Yl};return t.mixins=t.mixins.map((t=>"string"==typeof t?e[t]:t)),t},gu=(t,e={})=>((e={components:{},created:[],icons:{},login:null,textareaButtons:{},use:[],thirdParty:{},writerMarks:{},writerNodes:{},...e}).use=((t,e)=>{if(!1===Array.isArray(e))return[];for(const i of e)t.use(i);return e})(t,e.use),e.components=((t,e)=>{if(!1===yt(e))return;const i={};for(const[s,o]of Object.entries(e))try{i[s]=hu(t,s,o)}catch(n){window.console.warn(n.message)}return i})(t,e.components),e),ku=t=>{var e;const i=eu("menu",{entries:[],hover:!1,isOpen:!1}),n=null==(e=window.matchMedia)?void 0:e.call(window,"(max-width: 60rem)"),s={...i,blur(t){if(!1===n.matches)return!1;const e=document.querySelector(".k-panel-menu");!1===document.querySelector(".k-panel-menu-proxy").contains(t.target)&&!1===e.contains(t.target)&&this.close()},close(){this.isOpen=!1,!1===n.matches&&localStorage.setItem("kirby$menu",!0)},escape(){if(!1===n.matches)return!1;this.close()},open(){this.isOpen=!0,!1===n.matches&&localStorage.removeItem("kirby$menu")},resize(){if(n.matches)return this.close();null!==localStorage.getItem("kirby$menu")?this.isOpen=!1:this.isOpen=!0},set(t){return this.entries=t,this.resize(),this.state()},toggle(){this.isOpen?this.close():this.open()}};return t.events.on("keydown.esc",s.escape.bind(s)),t.events.on("click",s.blur.bind(s)),null==n||n.addEventListener("change",s.resize.bind(s)),s},bu=()=>{const t=eu("translation",{code:null,data:{},direction:"ltr",name:null});return{...t,set(e){return t.set.call(this,e),document.documentElement.lang=this.code,document.body.dir=this.direction,this.state()},translate(t,e,i=null){if("string"!=typeof t)return;const n=this.data[t]??i;return"string"!=typeof n?n:la(n,e)}}};const vu=t=>{const e=eu("upload",{accept:"*",attributes:{},files:[],max:null,multiple:!0,replacing:null,url:null});return{...e,...tu(),input:null,cancel(){this.emit("cancel"),this.completed.length>0&&(this.emit("complete",this.completed),t.view.reload()),this.reset()},get completed(){return this.files.filter((t=>t.completed)).map((t=>t.model))},done(){t.dialog.close(),this.completed.length>0&&(this.emit("done",this.completed),!1===t.drawer.isOpen&&(t.notification.success({context:"view"}),t.view.reload())),this.reset()},findDuplicate(t){return this.files.findLastIndex((e=>e.src.name===t.src.name&&e.src.type===t.src.type&&e.src.size===t.src.size&&e.src.lastModified===t.src.lastModified))},hasUniqueName(t){return this.files.filter((e=>e.name===t.name&&e.extension===t.extension)).length<2},file(t){const e=URL.createObjectURL(t);return{completed:!1,error:null,extension:Yr(t.name),filename:t.name,id:aa(),model:null,name:Ur(t.name),niceSize:Hr(t.size),progress:0,size:t.size,src:t,type:t.type,url:e}},open(e,i){e instanceof FileList?(this.set(i),this.select(e)):this.set(e);const n={component:"k-upload-dialog",on:{cancel:()=>this.cancel(),submit:async()=>{t.dialog.isLoading=!0,await this.submit(),t.dialog.isLoading=!1}}};this.replacing&&(n.component="k-upload-replace-dialog",n.props={original:this.replacing}),t.dialog.open(n)},pick(t){this.set(t),this.input=document.createElement("input"),this.input.type="file",this.input.classList.add("sr-only"),this.input.value=null,this.input.accept=this.accept,this.input.multiple=this.multiple,this.input.click(),this.input.addEventListener("change",(e=>{!0===(null==t?void 0:t.immediate)?(this.set(t),this.select(e.target.files),this.submit()):this.open(e.target.files,t),this.input.remove()}))},remove(t){this.files=this.files.filter((e=>e.id!==t))},replace(e,i){this.pick({...i,url:t.urls.api+"/"+e.link,accept:"."+e.extension+","+e.mime,multiple:!1,replacing:e})},reset(){e.reset.call(this),this.files.splice(0)},select(t,e){if(this.set(e),t instanceof Event&&(t=t.target.files),t instanceof FileList==!1)throw new Error("Please provide a FileList");t=(t=[...t]).map((t=>this.file(t))),this.files=[...this.files,...t],this.files=this.files.filter(((t,e)=>this.findDuplicate(t)===e)),null!==this.max&&(this.files=this.files.slice(-1*this.max)),this.emit("select",this.files)},set(t){if(t)return e.set.call(this,t),this.on={},this.addEventListeners(t.on??{}),1===this.max&&(this.multiple=!1),!1===this.multiple&&(this.max=1),this.state()},async submit(){var e;if(!this.url)throw new Error("The upload URL is missing");const i=[];for(const n of this.files)!0!==n.completed&&(!1!==this.hasUniqueName(n)?(n.error=null,n.progress=0,i.push((async()=>await this.upload(n))),void 0!==(null==(e=this.attributes)?void 0:e.sort)&&this.attributes.sort++):n.error=t.t("error.file.name.unique"));if(await async function(t,e=20){let i=0,n=0;return new Promise((s=>{const o=e=>n=>{t[e]=n,i--,l()},l=()=>{if(i{e.progress=n}});e.completed=!0,e.model=i.data}catch(i){t.error(i,!1),e.error=i.message,e.progress=0}}}},yu=t=>{const e=iu(t,"view",{component:null,isLoading:!1,on:{},path:null,props:{},query:{},referrer:null,timestamp:null,breadcrumb:[],breadcrumbLabel:null,icon:null,id:null,link:null,search:"pages",title:null});return{...e,set(i){e.set.call(this,i),t.title=this.title;const n=this.url().toString();window.location.toString()!==n&&(window.history.pushState(null,null,n),window.scrollTo(0,0))},async submit(){throw new Error("Not yet implemented")}}},$u={config:{},languages:[],license:"missing",multilang:!1,permissions:{},searches:{},urls:{}},wu=["dialog","drawer"],xu=["dropdown","language","menu","notification","system","translation","user"],_u={create(t={}){return this.isLoading=!1,this.isOffline=!1,this.activation=Ya(),this.drag=ou(),this.events=uu(this),this.upload=vu(this),this.language=pu(),this.menu=ku(this),this.notification=du(this),this.system=eu("system",{ascii:{},csrf:null,isLocal:null,locales:{},slugs:[],title:null}),this.translation=bu(),this.user=eu("user",{email:null,id:null,language:null,role:null,username:null}),this.dropdown=au(this),this.view=yu(this),this.drawer=ru(this),this.dialog=su(this),this.redirect=Ja,this.reload=this.view.reload.bind(this.view),this.t=this.translation.translate.bind(this.translation),this.plugins=gu(window.Vue,t),this.set(window.fiber),this.api=Qa(this),Vue.reactive(this)},get context(){return this.dialog.isOpen?"dialog":this.drawer.isOpen?"drawer":"view"},get debug(){return!0===this.config.debug},deprecated(t){this.notification.deprecated(t)},get direction(){return this.translation.direction},error(t,e=!0){if(!0===this.debug&&console.error(t),!0===e)return this.notification.error(t)},async get(t,e={}){const{response:i}=await this.request(t,{method:"GET",...e});return(null==i?void 0:i.json)??{}},async open(t,e={}){try{if(!1===ga(t))this.set(t);else{this.isLoading=!0;const i=await this.get(t,e);this.set(i),this.isLoading=!1}return this.state()}catch(i){return this.error(i)}},overlays(){const t=[];return!0===this.drawer.isOpen&&t.push("drawer"),!0===this.dialog.isOpen&&t.push("dialog"),t},async post(t,e={},i={}){const{response:n}=await this.request(t,{method:"POST",body:e,...i});return n.json},async request(t,e={}){return Ga(t,{referrer:this.view.path,csrf:this.system.csrf,...e})},async search(t,e,i){if(!t&&!e)return this.menu.escape(),this.dialog.open({component:"k-search-dialog"});const{$search:n}=await this.get(`/search/${t}`,{query:{query:e,...i}});return n},set(t={}){t=Object.fromEntries(Object.entries(t).map((([t,e])=>[t.replace("$",""),e])));for(const e in $u){const i=t[e]??this[e]??$u[e];typeof i==typeof $u[e]&&(this[e]=i)}for(const e of xu)(yt(t[e])||Array.isArray(t[e]))&&this[e].set(t[e]);for(const e of wu)!0===yt(t[e])?this[e].open(t[e]):void 0!==t[e]&&this[e].close();!0===yt(t.dropdown)?this.dropdown.open(t.dropdown):void 0!==t.dropdown&&this.dropdown.close(),!0===yt(t.view)&&this.view.open(t.view)},state(){const t={};for(const e in $u)t[e]=this[e]??$u[e];for(const e of xu)t[e]=this[e].state();for(const e of wu)t[e]=this[e].state();return t.dropdown=this.dropdown.state(),t.view=this.view.state(),t},get title(){return document.title},set title(t){!1===ia(this.system.title)&&(t+=" | "+this.system.title),document.title=t},url:(t="",e={},i)=>ha(t,e,i)},Su=(t,e)=>{localStorage.setItem("kirby$content$"+t,JSON.stringify(e))},Cu={namespaced:!0,state:{current:null,models:{},status:{enabled:!0}},getters:{exists:t=>e=>Object.hasOwn(t.models,e),hasChanges:(t,e)=>t=>$t(e.model(t).changes)>0,isCurrent:t=>e=>t.current===e,id:t=>e=>(e=e??t.current)+"?language="+window.panel.language.code,model:(t,e)=>i=>(i=i??t.current,!0===e.exists(i)?t.models[i]:{api:null,originals:{},values:{},changes:{}}),originals:(t,e)=>t=>vt(e.model(t).originals),values:(t,e)=>t=>({...e.originals(t),...e.changes(t)}),changes:(t,e)=>t=>vt(e.model(t).changes)},mutations:{CLEAR(t){for(const e in t.models)t.models[e].changes={};for(const e in localStorage)e.startsWith("kirby$content$")&&localStorage.removeItem(e)},CREATE(t,[e,i]){if(!i)return!1;let n=t.models[e]?t.models[e].changes:i.changes;Vue.set(t.models,e,{api:i.api,originals:i.originals,changes:n??{}})},CURRENT(t,e){t.current=e},MOVE(t,[e,i]){const n=vt(t.models[e]);Vue.del(t.models,e),Vue.set(t.models,i,n);const s=localStorage.getItem("kirby$content$"+e);localStorage.removeItem("kirby$content$"+e),localStorage.setItem("kirby$content$"+i,s)},REMOVE(t,e){Vue.del(t.models,e),localStorage.removeItem("kirby$content$"+e)},REVERT(t,e){t.models[e]&&(Vue.set(t.models[e],"changes",{}),localStorage.removeItem("kirby$content$"+e))},STATUS(t,e){Vue.set(t.status,"enabled",e)},UPDATE(t,[e,i,n]){if(!t.models[e])return!1;void 0===n&&(n=null),n=vt(n);const s=JSON.stringify(n);JSON.stringify(t.models[e].originals[i]??null)==s?Vue.del(t.models[e].changes,i):Vue.set(t.models[e].changes,i,n),Su(e,{api:t.models[e].api,originals:t.models[e].originals,changes:t.models[e].changes})}},actions:{init(t){for(const i in localStorage)if(i.startsWith("kirby$content$")){const e=i.split("kirby$content$")[1],n=localStorage.getItem("kirby$content$"+e);t.commit("CREATE",[e,JSON.parse(n)])}else if(i.startsWith("kirby$form$")){const n=i.split("kirby$form$")[1],s=localStorage.getItem("kirby$form$"+n);let o=null;try{o=JSON.parse(s)}catch(e){}if(!o||!o.api)return localStorage.removeItem("kirby$form$"+n),!1;const l={api:o.api,originals:o.originals,changes:o.values};t.commit("CREATE",[n,l]),Su(n,l),localStorage.removeItem("kirby$form$"+n)}},clear(t){t.commit("CLEAR")},create(t,e){const i=vt(e.content);if(Array.isArray(e.ignore))for(const s of e.ignore)delete i[s];e.id=t.getters.id(e.id);const n={api:e.api,originals:i,changes:{}};t.commit("CREATE",[e.id,n]),t.dispatch("current",e.id)},current(t,e){t.commit("CURRENT",e)},disable(t){t.commit("STATUS",!1)},enable(t){t.commit("STATUS",!0)},move(t,[e,i]){e=t.getters.id(e),i=t.getters.id(i),t.commit("MOVE",[e,i])},remove(t,e){t.commit("REMOVE",e),t.getters.isCurrent(e)&&t.commit("CURRENT",null)},revert(t,e){e=e??t.state.current,t.commit("REVERT",e)},async save(t,e){if(e=e??t.state.current,t.getters.isCurrent(e)&&!1===t.state.status.enabled)return!1;t.dispatch("disable");const i=t.getters.model(e),n={...i.originals,...i.changes};try{await window.panel.api.patch(i.api,n),t.commit("CREATE",[e,{...i,originals:n}]),t.dispatch("revert",e)}finally{t.dispatch("enable")}},update(t,[e,i,n]){if(n=n??t.state.current,null===e)for(const s in i)t.commit("UPDATE",[n,s,i[s]]);else t.commit("UPDATE",[n,e,i])}}},Ou={namespaced:!0,actions:{close(t,e){window.panel.deprecated("`$store.drawer` will be removed in a future version. Use `$panel.drawer` instead."),window.panel.drawer.close(e)},goto(t,e){window.panel.deprecated("`$store.drawer` will be removed in a future version. Use `$panel.drawer` instead."),window.panel.drawer.goto(e)},open(t,e){window.panel.deprecated("`$store.drawer` will be removed in a future version. Use `$panel.drawer` instead."),window.panel.drawer.goto(e)}}},Au={namespaced:!0,actions:{close(){window.panel.deprecated("`$store.notification` will be removed in a future version. Use `$panel.notification` instead."),window.panel.notification.close()},deprecated(t,e){window.panel.deprecated("`$store.notification.deprecated` will be removed in a future version. Use `$panel.deprecated` instead."),window.panel.notification.deprecated(e)},error(t,e){window.panel.deprecated("`$store.notification` will be removed in a future version. Use `$panel.notification` instead."),window.panel.notification.error(e)},open(t,e){window.panel.deprecated("`$store.notification` will be removed in a future version. Use `$panel.notification` instead."),window.panel.notification.open(e)},success(t,e){window.panel.deprecated("`$store.notification` will be removed in a future version. Use `$panel.notification` instead."),window.panel.notification.success(e)}}};Vue.use(N);const Mu=new N.Store({strict:!1,actions:{dialog(t,e){window.panel.deprecated("`$store.dialog` will be removed in a future version. Use `$panel.dialog.open()` instead."),window.panel.dialog.open(e)},drag(t,e){window.panel.deprecated("`$store.drag` will be removed in a future version. Use `$panel.drag.start(type, data)` instead."),window.panel.drag.start(...e)},fatal(t,e){window.panel.deprecated("`$store.fatal` will be removed in a future version. Use `$panel.notification.fatal()` instead."),window.panel.notification.fatal(e)},isLoading(t,e){window.panel.deprecated("`$store.isLoading` will be removed in a future version. Use `$panel.isLoading` instead."),window.panel.isLoading=e},navigate(){window.panel.deprecated("`$store.navigate` will be removed in a future version."),window.panel.dialog.close(),window.panel.drawer.close()}},modules:{content:Cu,drawers:Ou,notification:Au}});Vue.config.productionTip=!1,Vue.config.devtools=!0,Vue.use(ya),Vue.use(Ra),Vue.use(z),Vue.use(Ir),window.panel=Vue.prototype.$panel=_u.create(window.panel.plugins),Vue.prototype.$dialog=window.panel.dialog.open.bind(window.panel.dialog),Vue.prototype.$drawer=window.panel.drawer.open.bind(window.panel.drawer),Vue.prototype.$dropdown=window.panel.dropdown.openAsync.bind(window.panel.dropdown),Vue.prototype.$go=window.panel.view.open.bind(window.panel.view),Vue.prototype.$reload=window.panel.reload.bind(window.panel),window.panel.app=new Vue({store:Mu,render:()=>Vue.h(Y)}),Vue.use($a),Vue.use(Er),Vue.use(wa),!1===CSS.supports("container","foo / inline-size")&&R((()=>import("./container-query-polyfill.modern.min.js")),[],import.meta.url),window.panel.app.$mount("#app");export{R as _,ut as n}; diff --git a/kirby/panel/dist/js/plugins.js b/kirby/panel/dist/js/plugins.js index 4cf1612..699037f 100644 --- a/kirby/panel/dist/js/plugins.js +++ b/kirby/panel/dist/js/plugins.js @@ -1,75 +1,91 @@ -window.panel = window.panel || {}; +window.panel = window.panel ?? {}; window.panel.plugins = { - components: {}, - created: [], - icons: {}, - routes: [], - use: [], - views: {}, - thirdParty: {} + components: {}, + created: [], + icons: {}, + routes: [], + textareaButtons: {}, + thirdParty: {}, + use: [], + views: {}, + writerMarks: {}, + writerNodes: {} }; -window.panel.plugin = function (plugin, parts) { - // Blocks - resolve(parts, "blocks", function (name, options) { - if (typeof options === "string") { - options = { template: options }; - } +window.panel.plugin = function (plugin, extensions) { + // Blocks + resolve(extensions, "blocks", (name, options) => { + if (typeof options === "string") { + options = { template: options }; + } - window.panel.plugins.components[`k-block-type-${name}`] = { - extends: "k-block-type", - ...options - }; - }); + window.panel.plugins.components[`k-block-type-${name}`] = { + extends: "k-block-type", + ...options + }; + }); - // Components - resolve(parts, "components", function (name, options) { - window.panel.plugins.components[name] = options; - }); + // Components + resolve(extensions, "components", (name, options) => { + window.panel.plugins.components[name] = options; + }); - // Fields - resolve(parts, "fields", function (name, options) { - window.panel.plugins.components[`k-${name}-field`] = options; - }); + // Fields + resolve(extensions, "fields", (name, options) => { + window.panel.plugins.components[`k-${name}-field`] = options; + }); - // Icons - resolve(parts, "icons", function (name, options) { - window.panel.plugins.icons[name] = options; - }); + // Icons + resolve(extensions, "icons", (name, options) => { + window.panel.plugins.icons[name] = options; + }); - // Sections - resolve(parts, "sections", function (name, options) { - window.panel.plugins.components[`k-${name}-section`] = { - ...options, - mixins: ["section", ...(options.mixins || [])] - }; - }); + // Sections + resolve(extensions, "sections", (name, options) => { + window.panel.plugins.components[`k-${name}-section`] = { + ...options, + mixins: ["section", ...(options.mixins ?? [])] + }; + }); - // `Vue.use` - resolve(parts, "use", function (name, options) { - window.panel.plugins.use.push(options); - }); + // `Vue.use` + resolve(extensions, "use", (name, options) => { + window.panel.plugins.use.push(options); + }); - // Vue `created` callback - if (parts["created"]) { - window.panel.plugins.created.push(parts["created"]); - } + // Vue `created` callback + if (extensions["created"]) { + window.panel.plugins.created.push(extensions["created"]); + } - // Login - if (parts.login) { - window.panel.plugins.login = parts.login; - } + // Login + if (extensions.login) { + window.panel.plugins.login = extensions.login; + } - // Third-party plugins - resolve(parts, "thirdParty", function (name, options) { - window.panel.plugins.thirdParty[name] = options; - }); + // Textarea custom toolbar buttons + resolve(extensions, "textareaButtons", (name, options) => { + window.panel.plugins.textareaButtons[name] = options; + }); + + // Third-party plugins + resolve(extensions, "thirdParty", (name, options) => { + window.panel.plugins.thirdParty[name] = options; + }); + + // Writer custom marks + resolve(extensions, "writerMarks", (name, options) => { + window.panel.plugins.writerMarks[name] = options; + }); + + // Writer custom nodes + resolve(extensions, "writerNodes", function (name, options) { + window.panel.plugins.writerNodes[name] = options; + }); }; -function resolve(object, type, callback) { - if (object[type]) { - for (const [name, options] of Object.entries(object[type])) { - callback(name, options); - } - } -} +const resolve = (extensions, type, callback) => { + for (const [name, options] of Object.entries(extensions[type] ?? {})) { + callback(name, options); + } +}; diff --git a/kirby/panel/dist/js/vendor.js b/kirby/panel/dist/js/vendor.js deleted file mode 100644 index c7b159f..0000000 --- a/kirby/panel/dist/js/vendor.js +++ /dev/null @@ -1,6 +0,0 @@ -function e(e){this.content=e}function t(e,n,r){for(let i=0;;i++){if(i==e.childCount||i==n.childCount)return e.childCount==n.childCount?null:r;let o=e.child(i),s=n.child(i);if(o!=s){if(!o.sameMarkup(s))return r;if(o.isText&&o.text!=s.text){for(let e=0;o.text[e]==s.text[e];e++)r++;return r}if(o.content.size||s.content.size){let e=t(o.content,s.content,r+1);if(null!=e)return e}r+=o.nodeSize}else r+=o.nodeSize}}function n(e,t,r,i){for(let o=e.childCount,s=t.childCount;;){if(0==o||0==s)return o==s?null:{a:r,b:i};let l=e.child(--o),a=t.child(--s),c=l.nodeSize;if(l!=a){if(!l.sameMarkup(a))return{a:r,b:i};if(l.isText&&l.text!=a.text){let e=0,t=Math.min(l.text.length,a.text.length);for(;e>1}},e.from=function(t){if(t instanceof e)return t;var n=[];if(t)for(var r in t)n.push(r,t[r]);return new e(n)};class r{constructor(e,t){if(this.content=e,this.size=t||0,null==t)for(let n=0;ne&&!1!==n(l,r+s,i||null,o)&&l.content.size){let i=s+1;l.nodesBetween(Math.max(0,e-i),Math.min(l.content.size,t-i),n,r+i)}s=a}}descendants(e){this.nodesBetween(0,this.size,e)}textBetween(e,t,n,r){let i="",o=!0;return this.nodesBetween(e,t,((s,l)=>{s.isText?(i+=s.text.slice(Math.max(e,l)-l,t-l),o=!n):s.isLeaf?(r?i+="function"==typeof r?r(s):r:s.type.spec.leafText&&(i+=s.type.spec.leafText(s)),o=!n):!o&&s.isBlock&&(i+=n,o=!0)}),0),i}append(e){if(!e.size)return this;if(!this.size)return e;let t=this.lastChild,n=e.firstChild,i=this.content.slice(),o=0;for(t.isText&&t.sameMarkup(n)&&(i[i.length-1]=t.withText(t.text+n.text),o=1);oe)for(let r=0,o=0;oe&&((ot)&&(s=s.isText?s.cut(Math.max(0,e-o),Math.min(s.text.length,t-o)):s.cut(Math.max(0,e-o-1),Math.min(s.content.size,t-o-1))),n.push(s),i+=s.nodeSize),o=l}return new r(n,i)}cutByIndex(e,t){return e==t?r.empty:0==e&&t==this.content.length?this:new r(this.content.slice(e,t))}replaceChild(e,t){let n=this.content[e];if(n==t)return this;let i=this.content.slice(),o=this.size+t.nodeSize-n.nodeSize;return i[e]=t,new r(i,o)}addToStart(e){return new r([e].concat(this.content),this.size+e.nodeSize)}addToEnd(e){return new r(this.content.concat(e),this.size+e.nodeSize)}eq(e){if(this.content.length!=e.content.length)return!1;for(let t=0;tthis.size||e<0)throw new RangeError(`Position ${e} outside of fragment (${this})`);for(let n=0,r=0;;n++){let i=r+this.child(n).nodeSize;if(i>=e)return i==e||t>0?o(n+1,i):o(n,r);r=i}}toString(){return"<"+this.toStringInner()+">"}toStringInner(){return this.content.join(", ")}toJSON(){return this.content.length?this.content.map((e=>e.toJSON())):null}static fromJSON(e,t){if(!t)return r.empty;if(!Array.isArray(t))throw new RangeError("Invalid input for Fragment.fromJSON");return new r(t.map(e.nodeFromJSON))}static fromArray(e){if(!e.length)return r.empty;let t,n=0;for(let r=0;rthis.type.rank&&(t||(t=e.slice(0,r)),t.push(this),n=!0),t&&t.push(i)}}return t||(t=e.slice()),n||t.push(this),t}removeFromSet(e){for(let t=0;te.type.rank-t.type.rank)),t}}l.none=[];class a extends Error{}class c{constructor(e,t,n){this.content=e,this.openStart=t,this.openEnd=n}get size(){return this.content.size-this.openStart-this.openEnd}insertAt(e,t){let n=d(this.content,e+this.openStart,t);return n&&new c(n,this.openStart,this.openEnd)}removeBetween(e,t){return new c(h(this.content,e+this.openStart,t+this.openStart),this.openStart,this.openEnd)}eq(e){return this.content.eq(e.content)&&this.openStart==e.openStart&&this.openEnd==e.openEnd}toString(){return this.content+"("+this.openStart+","+this.openEnd+")"}toJSON(){if(!this.content.size)return null;let e={content:this.content.toJSON()};return this.openStart>0&&(e.openStart=this.openStart),this.openEnd>0&&(e.openEnd=this.openEnd),e}static fromJSON(e,t){if(!t)return c.empty;let n=t.openStart||0,i=t.openEnd||0;if("number"!=typeof n||"number"!=typeof i)throw new RangeError("Invalid input for Slice.fromJSON");return new c(r.fromJSON(e,t.content),n,i)}static maxOpen(e,t=!0){let n=0,r=0;for(let i=e.firstChild;i&&!i.isLeaf&&(t||!i.type.spec.isolating);i=i.firstChild)n++;for(let i=e.lastChild;i&&!i.isLeaf&&(t||!i.type.spec.isolating);i=i.lastChild)r++;return new c(e,n,r)}}function h(e,t,n){let{index:r,offset:i}=e.findIndex(t),o=e.maybeChild(r),{index:s,offset:l}=e.findIndex(n);if(i==t||o.isText){if(l!=n&&!e.child(s).isText)throw new RangeError("Removing non-flat range");return e.cut(0,t).append(e.cut(n))}if(r!=s)throw new RangeError("Removing non-flat range");return e.replaceChild(r,o.copy(h(o.content,t-i-1,n-i-1)))}function d(e,t,n,r){let{index:i,offset:o}=e.findIndex(t),s=e.maybeChild(i);if(o==t||s.isText)return r&&!r.canReplace(i,i,n)?null:e.cut(0,t).append(n).append(e.cut(t));let l=d(s.content,t-o-1,n);return l&&e.replaceChild(i,s.copy(l))}function u(e,t,n){if(n.openStart>e.depth)throw new a("Inserted content deeper than insertion position");if(e.depth-n.openStart!=t.depth-n.openEnd)throw new a("Inconsistent open depths");return f(e,t,n,0)}function f(e,t,n,i){let o=e.index(i),s=e.node(i);if(o==t.index(i)&&i=0;o--)i=t.node(o).copy(r.from(i));return{start:i.resolveNoCache(e.openStart+n),end:i.resolveNoCache(i.content.size-e.openEnd-n)}}(n,e);return v(s,w(e,o,l,t,i))}{let r=e.parent,i=r.content;return v(r,i.cut(0,e.parentOffset).append(n.content).append(i.cut(t.parentOffset)))}}return v(s,b(e,t,i))}function p(e,t){if(!t.type.compatibleContent(e.type))throw new a("Cannot join "+t.type.name+" onto "+e.type.name)}function m(e,t,n){let r=e.node(n);return p(r,t.node(n)),r}function g(e,t){let n=t.length-1;n>=0&&e.isText&&e.sameMarkup(t[n])?t[n]=e.withText(t[n].text+e.text):t.push(e)}function y(e,t,n,r){let i=(t||e).node(n),o=0,s=t?t.index(n):i.childCount;e&&(o=e.index(n),e.depth>n?o++:e.textOffset&&(g(e.nodeAfter,r),o++));for(let l=o;lo&&m(e,t,o+1),l=i.depth>o&&m(n,i,o+1),a=[];return y(null,e,o,a),s&&l&&t.index(o)==n.index(o)?(p(s,l),g(v(s,w(e,t,n,i,o+1)),a)):(s&&g(v(s,b(e,t,o+1)),a),y(t,n,o,a),l&&g(v(l,b(n,i,o+1)),a)),y(i,null,o,a),new r(a)}function b(e,t,n){let i=[];if(y(null,e,n,i),e.depth>n){g(v(m(e,t,n+1),b(e,t,n+1)),i)}return y(t,null,n,i),new r(i)}c.empty=new c(r.empty,0,0);class x{constructor(e,t,n){this.pos=e,this.path=t,this.parentOffset=n,this.depth=t.length/3-1}resolveDepth(e){return null==e?this.depth:e<0?this.depth+e:e}get parent(){return this.node(this.depth)}get doc(){return this.node(0)}node(e){return this.path[3*this.resolveDepth(e)]}index(e){return this.path[3*this.resolveDepth(e)+1]}indexAfter(e){return e=this.resolveDepth(e),this.index(e)+(e!=this.depth||this.textOffset?1:0)}start(e){return 0==(e=this.resolveDepth(e))?0:this.path[3*e-1]+1}end(e){return e=this.resolveDepth(e),this.start(e)+this.node(e).content.size}before(e){if(!(e=this.resolveDepth(e)))throw new RangeError("There is no position before the top-level node");return e==this.depth+1?this.pos:this.path[3*e-1]}after(e){if(!(e=this.resolveDepth(e)))throw new RangeError("There is no position after the top-level node");return e==this.depth+1?this.pos:this.path[3*e-1]+this.path[3*e].nodeSize}get textOffset(){return this.pos-this.path[this.path.length-1]}get nodeAfter(){let e=this.parent,t=this.index(this.depth);if(t==e.childCount)return null;let n=this.pos-this.path[this.path.length-1],r=e.child(t);return n?e.child(t).cut(n):r}get nodeBefore(){let e=this.index(this.depth),t=this.pos-this.path[this.path.length-1];return t?this.parent.child(e).cut(0,t):0==e?null:this.parent.child(e-1)}posAtIndex(e,t){t=this.resolveDepth(t);let n=this.path[3*t],r=0==t?0:this.path[3*t-1]+1;for(let i=0;i0;t--)if(this.start(t)<=e&&this.end(t)>=e)return t;return 0}blockRange(e=this,t){if(e.pos=0;n--)if(e.pos<=this.end(n)&&(!t||t(this.node(n))))return new O(this,e,n);return null}sameParent(e){return this.pos-this.parentOffset==e.pos-e.parentOffset}max(e){return e.pos>this.pos?e:this}min(e){return e.pos=0&&t<=e.content.size))throw new RangeError("Position "+t+" out of range");let n=[],r=0,i=t;for(let o=e;;){let{index:e,offset:t}=o.content.findIndex(i),s=i-t;if(n.push(o,e,r+t),!s)break;if(o=o.child(e),o.isText)break;i=s-1,r+=t+1}return new x(t,n,i)}static resolveCached(e,t){for(let r=0;re&&this.nodesBetween(e,t,(e=>(n.isInSet(e.marks)&&(r=!0),!r))),r}get isBlock(){return this.type.isBlock}get isTextblock(){return this.type.isTextblock}get inlineContent(){return this.type.inlineContent}get isInline(){return this.type.isInline}get isText(){return this.type.isText}get isLeaf(){return this.type.isLeaf}get isAtom(){return this.type.isAtom}toString(){if(this.type.spec.toDebugString)return this.type.spec.toDebugString(this);let e=this.type.name;return this.content.size&&(e+="("+this.content.toStringInner()+")"),T(this.marks,e)}contentMatchAt(e){let t=this.type.contentMatch.matchFragment(this.content,0,e);if(!t)throw new Error("Called contentMatchAt on a node with invalid content");return t}canReplace(e,t,n=r.empty,i=0,o=n.childCount){let s=this.contentMatchAt(e).matchFragment(n,i,o),l=s&&s.matchFragment(this.content,t);if(!l||!l.validEnd)return!1;for(let r=i;re.type.name))}`);this.content.forEach((e=>e.check()))}toJSON(){let e={type:this.type.name};for(let t in this.attrs){e.attrs=this.attrs;break}return this.content.size&&(e.content=this.content.toJSON()),this.marks.length&&(e.marks=this.marks.map((e=>e.toJSON()))),e}static fromJSON(e,t){if(!t)throw new RangeError("Invalid input for Node.fromJSON");let n=null;if(t.marks){if(!Array.isArray(t.marks))throw new RangeError("Invalid mark data for Node.fromJSON");n=t.marks.map(e.markFromJSON)}if("text"==t.type){if("string"!=typeof t.text)throw new RangeError("Invalid text node in JSON");return e.text(t.text,n)}let i=r.fromJSON(e,t.content);return e.nodeType(t.type).create(t.attrs,i,n)}}N.prototype.text=void 0;class D extends N{constructor(e,t,n,r){if(super(e,t,null,r),!n)throw new RangeError("Empty text nodes are not allowed");this.text=n}toString(){return this.type.spec.toDebugString?this.type.spec.toDebugString(this):T(this.marks,JSON.stringify(this.text))}get textContent(){return this.text}textBetween(e,t){return this.text.slice(e,t)}get nodeSize(){return this.text.length}mark(e){return e==this.marks?this:new D(this.type,this.attrs,this.text,e)}withText(e){return e==this.text?this:new D(this.type,this.attrs,e,this.marks)}cut(e=0,t=this.text.length){return 0==e&&t==this.text.length?this:this.withText(this.text.slice(e,t))}eq(e){return this.sameMarkup(e)&&this.text==e.text}toJSON(){let e=super.toJSON();return e.text=this.text,e}}function T(e,t){for(let n=e.length-1;n>=0;n--)t=e[n].type.name+"("+t+")";return t}class E{constructor(e){this.validEnd=e,this.next=[],this.wrapCache=[]}static parse(e,t){let n=new A(e,t);if(null==n.next)return E.empty;let r=$(n);n.next&&n.err("Unexpected trailing text");let i=function(e){let t=Object.create(null);return n(B(e,0));function n(r){let i=[];r.forEach((t=>{e[t].forEach((({term:t,to:n})=>{if(!t)return;let r;for(let e=0;e{r||i.push([t,r=[]]),-1==r.indexOf(e)&&r.push(e)}))}))}));let o=t[r.join(",")]=new E(r.indexOf(e.length-1)>-1);for(let e=0;ee.to=t))}function o(e,t){if("choice"==e.type)return e.exprs.reduce(((e,n)=>e.concat(o(n,t))),[]);if("seq"!=e.type){if("star"==e.type){let s=n();return r(t,s),i(o(e.expr,s),s),[r(s)]}if("plus"==e.type){let s=n();return i(o(e.expr,t),s),i(o(e.expr,s),s),[r(s)]}if("opt"==e.type)return[r(t)].concat(o(e.expr,t));if("range"==e.type){let s=t;for(let t=0;te.createAndFill())));for(let e=0;e=this.next.length)throw new RangeError(`There's no ${e}th edge in this content match`);return this.next[e]}toString(){let e=[];return function t(n){e.push(n);for(let r=0;r{let r=n+(t.validEnd?"*":" ")+" ";for(let i=0;i"+e.indexOf(t.next[i].next);return r})).join("\n")}}E.empty=new E(!0);class A{constructor(e,t){this.string=e,this.nodeTypes=t,this.inline=null,this.pos=0,this.tokens=e.split(/\s*(?=\b|\W|$)/),""==this.tokens[this.tokens.length-1]&&this.tokens.pop(),""==this.tokens[0]&&this.tokens.shift()}get next(){return this.tokens[this.pos]}eat(e){return this.next==e&&(this.pos++||!0)}err(e){throw new SyntaxError(e+" (in content expression '"+this.string+"')")}}function $(e){let t=[];do{t.push(P(e))}while(e.eat("|"));return 1==t.length?t[0]:{type:"choice",exprs:t}}function P(e){let t=[];do{t.push(I(e))}while(e.next&&")"!=e.next&&"|"!=e.next);return 1==t.length?t[0]:{type:"seq",exprs:t}}function I(e){let t=function(e){if(e.eat("(")){let t=$(e);return e.eat(")")||e.err("Missing closing paren"),t}if(!/\W/.test(e.next)){let t=function(e,t){let n=e.nodeTypes,r=n[t];if(r)return[r];let i=[];for(let o in n){let e=n[o];e.groups.indexOf(t)>-1&&i.push(e)}0==i.length&&e.err("No node type or group '"+t+"' found");return i}(e,e.next).map((t=>(null==e.inline?e.inline=t.isInline:e.inline!=t.isInline&&e.err("Mixing inline and block content"),{type:"name",value:t})));return e.pos++,1==t.length?t[0]:{type:"choice",exprs:t}}e.err("Unexpected token '"+e.next+"'")}(e);for(;;)if(e.eat("+"))t={type:"plus",expr:t};else if(e.eat("*"))t={type:"star",expr:t};else if(e.eat("?"))t={type:"opt",expr:t};else{if(!e.eat("{"))break;t=R(e,t)}return t}function z(e){/\D/.test(e.next)&&e.err("Expected number, got '"+e.next+"'");let t=Number(e.next);return e.pos++,t}function R(e,t){let n=z(e),r=n;return e.eat(",")&&(r="}"!=e.next?z(e):-1),e.eat("}")||e.err("Unclosed braced range"),{type:"range",min:n,max:r,expr:t}}function _(e,t){return t-e}function B(e,t){let n=[];return function t(r){let i=e[r];if(1==i.length&&!i[0].term)return t(i[0].to);n.push(r);for(let e=0;e-1}allowsMarks(e){if(null==this.markSet)return!0;for(let t=0;tr[t]=new e(t,n,i)));let i=n.spec.topNode||"doc";if(!r[i])throw new RangeError("Schema is missing its top node type ('"+i+"')");if(!r.text)throw new RangeError("Every schema needs a 'text' type");for(let e in r.text.attrs)throw new RangeError("The text node type should not have attributes");return r}};class q{constructor(e){this.hasDefault=Object.prototype.hasOwnProperty.call(e,"default"),this.default=e.default}get isRequired(){return!this.hasDefault}}class W{constructor(e,t,n,r){this.name=e,this.rank=t,this.schema=n,this.spec=r,this.attrs=F(r.attrs),this.excluded=null;let i=V(this.attrs);this.instance=i?new l(this,i):null}create(e=null){return!e&&this.instance?this.instance:new l(this,j(this.attrs,e))}static compile(e,t){let n=Object.create(null),r=0;return e.forEach(((e,i)=>n[e]=new W(e,r++,t,i))),n}removeFromSet(e){for(var t=0;t-1}}class J{constructor(t){this.cached=Object.create(null);let n=this.spec={};for(let e in t)n[e]=t[e];n.nodes=e.from(t.nodes),n.marks=e.from(t.marks||{}),this.nodes=L.compile(this.spec.nodes,this),this.marks=W.compile(this.spec.marks,this);let r=Object.create(null);for(let e in this.nodes){if(e in this.marks)throw new RangeError(e+" can not be both a node and a mark");let t=this.nodes[e],n=t.spec.content||"",i=t.spec.marks;t.contentMatch=r[n]||(r[n]=E.parse(n,this.nodes)),t.inlineContent=t.contentMatch.inlineContent,t.markSet="_"==i?null:i?K(this,i.split(" ")):""!=i&&t.inlineContent?null:[]}for(let e in this.marks){let t=this.marks[e],n=t.spec.excludes;t.excluded=null==n?[t]:""==n?[]:K(this,n.split(" "))}this.nodeFromJSON=this.nodeFromJSON.bind(this),this.markFromJSON=this.markFromJSON.bind(this),this.topNodeType=this.nodes[this.spec.topNode||"doc"],this.cached.wrappings=Object.create(null)}node(e,t=null,n,r){if("string"==typeof e)e=this.nodeType(e);else{if(!(e instanceof L))throw new RangeError("Invalid node type: "+e);if(e.schema!=this)throw new RangeError("Node type from different schema used ("+e.name+")")}return e.createChecked(t,n,r)}text(e,t){let n=this.nodes.text;return new D(n,n.defaultAttrs,e,l.setFrom(t))}mark(e,t){return"string"==typeof e&&(e=this.marks[e]),e.create(t)}nodeFromJSON(e){return N.fromJSON(this,e)}markFromJSON(e){return l.fromJSON(this,e)}nodeType(e){let t=this.nodes[e];if(!t)throw new RangeError("Unknown node type: "+e);return t}}function K(e,t){let n=[];for(let r=0;r-1)&&n.push(s=r)}if(!s)throw new SyntaxError("Unknown mark type: '"+t[r]+"'")}return n}class H{constructor(e,t){this.schema=e,this.rules=t,this.tags=[],this.styles=[],t.forEach((e=>{e.tag?this.tags.push(e):e.style&&this.styles.push(e)})),this.normalizeLists=!this.tags.some((t=>{if(!/^(ul|ol)\b/.test(t.tag)||!t.node)return!1;let n=e.nodes[t.node];return n.contentMatch.matchType(n)}))}parse(e,t={}){let n=new ne(this,t,!1);return n.addAll(e,t.from,t.to),n.finish()}parseSlice(e,t={}){let n=new ne(this,t,!0);return n.addAll(e,t.from,t.to),c.maxOpen(n.finish())}matchTag(e,t,n){for(let r=n?this.tags.indexOf(n)+1:0;re.length&&(61!=o.charCodeAt(e.length)||o.slice(e.length+1)!=t))){if(r.getAttrs){let e=r.getAttrs(t);if(!1===e)continue;r.attrs=e||void 0}return r}}}static schemaRules(e){let t=[];function n(e){let n=null==e.priority?50:e.priority,r=0;for(;r{n(e=ie(e)),e.mark||e.ignore||e.clearMark||(e.mark=r)}))}for(let r in e.nodes){let t=e.nodes[r].spec.parseDOM;t&&t.forEach((e=>{n(e=ie(e)),e.node||e.ignore||e.mark||(e.node=r)}))}return t}static fromSchema(e){return e.cached.domParser||(e.cached.domParser=new H(e,H.schemaRules(e)))}}const Y={address:!0,article:!0,aside:!0,blockquote:!0,canvas:!0,dd:!0,div:!0,dl:!0,fieldset:!0,figcaption:!0,figure:!0,footer:!0,form:!0,h1:!0,h2:!0,h3:!0,h4:!0,h5:!0,h6:!0,header:!0,hgroup:!0,hr:!0,li:!0,noscript:!0,ol:!0,output:!0,p:!0,pre:!0,section:!0,table:!0,tfoot:!0,ul:!0},U={head:!0,noscript:!0,object:!0,script:!0,style:!0,title:!0},G={ol:!0,ul:!0},Z=1,X=2,Q=4;function ee(e,t,n){return null!=t?(t?Z:0)|("full"===t?X:0):e&&"pre"==e.whitespace?Z|X:n&~Q}class te{constructor(e,t,n,r,i,o,s){this.type=e,this.attrs=t,this.marks=n,this.pendingMarks=r,this.solid=i,this.options=s,this.content=[],this.activeMarks=l.none,this.stashMarks=[],this.match=o||(s&Q?null:e.contentMatch)}findWrapping(e){if(!this.match){if(!this.type)return[];let t=this.type.contentMatch.fillBefore(r.from(e));if(!t){let t,n=this.type.contentMatch;return(t=n.findWrapping(e.type))?(this.match=n,t):null}this.match=this.type.contentMatch.matchFragment(t)}return this.match.findWrapping(e.type)}finish(e){if(!(this.options&Z)){let e,t=this.content[this.content.length-1];if(t&&t.isText&&(e=/[ \t\r\n\u000c]+$/.exec(t.text))){let n=t;t.text.length==e[0].length?this.content.pop():this.content[this.content.length-1]=n.withText(n.text.slice(0,n.text.length-e[0].length))}}let t=r.from(this.content);return!e&&this.match&&(t=t.append(this.match.fillBefore(r.empty,!0))),this.type?this.type.create(this.attrs,t,this.marks):t}popFromStashMark(e){for(let t=this.stashMarks.length-1;t>=0;t--)if(e.eq(this.stashMarks[t]))return this.stashMarks.splice(t,1)[0]}applyPending(e){for(let t=0,n=this.pendingMarks;t{o.clearMark(e)&&(n=e.addToSet(n))})):t=this.parser.schema.marks[o.mark].create(o.attrs).addToSet(t),!1!==o.consuming)break;i=o}return[t,n]}addElementByRule(e,t,n){let r,i,o;if(t.node)i=this.parser.schema.nodes[t.node],i.isLeaf?this.insertNode(i.create(t.attrs))||this.leafFallback(e):r=this.enter(i,t.attrs||null,t.preserveWhitespace);else{o=this.parser.schema.marks[t.mark].create(t.attrs),this.addPendingMark(o)}let s=this.top;if(i&&i.isLeaf)this.findInside(e);else if(n)this.addElement(e,n);else if(t.getContent)this.findInside(e),t.getContent(e,this.parser.schema).forEach((e=>this.insertNode(e)));else{let n=e;"string"==typeof t.contentElement?n=e.querySelector(t.contentElement):"function"==typeof t.contentElement?n=t.contentElement(e):t.contentElement&&(n=t.contentElement),this.findAround(e,n,!0),this.addAll(n)}r&&this.sync(s)&&this.open--,o&&this.removePendingMark(o,s)}addAll(e,t,n){let r=t||0;for(let i=t?e.childNodes[t]:e.firstChild,o=null==n?null:e.childNodes[n];i!=o;i=i.nextSibling,++r)this.findAtPoint(e,r),this.addDOM(i);this.findAtPoint(e,r)}findPlace(e){let t,n;for(let r=this.open;r>=0;r--){let i=this.nodes[r],o=i.findWrapping(e);if(o&&(!t||t.length>o.length)&&(t=o,n=i,!o.length))break;if(i.solid)break}if(!t)return!1;this.sync(n);for(let r=0;rthis.open){for(;t>this.open;t--)this.nodes[t-1].content.push(this.nodes[t].finish(e));this.nodes.length=this.open+1}}finish(){return this.open=0,this.closeExtra(this.isOpen),this.nodes[0].finish(this.isOpen||this.options.topOpen)}sync(e){for(let t=this.open;t>=0;t--)if(this.nodes[t]==e)return this.open=t,!0;return!1}get currentPos(){this.closeExtra();let e=0;for(let t=this.open;t>=0;t--){let n=this.nodes[t].content;for(let t=n.length-1;t>=0;t--)e+=n[t].nodeSize;t&&e++}return e}findAtPoint(e,t){if(this.find)for(let n=0;n-1)return e.split(/\s*\|\s*/).some(this.matchesContext,this);let t=e.split("/"),n=this.options.context,r=!(this.isOpen||n&&n.parent.type!=this.nodes[0].type),i=-(n?n.depth+1:0)+(r?0:1),o=(e,s)=>{for(;e>=0;e--){let l=t[e];if(""==l){if(e==t.length-1||0==e)continue;for(;s>=i;s--)if(o(e-1,s))return!0;return!1}{let e=s>0||0==s&&r?this.nodes[s].type:n&&s>=i?n.node(s-i).type:null;if(!e||e.name!=l&&-1==e.groups.indexOf(l))return!1;s--}}return!0};return o(t.length-1,this.open)}textblockFromContext(){let e=this.options.context;if(e)for(let t=e.depth;t>=0;t--){let n=e.node(t).contentMatchAt(e.indexAfter(t)).defaultType;if(n&&n.isTextblock&&n.defaultAttrs)return n}for(let t in this.parser.schema.nodes){let e=this.parser.schema.nodes[t];if(e.isTextblock&&e.defaultAttrs)return e}}addPendingMark(e){let t=function(e,t){for(let n=0;n=0;n--){let r=this.nodes[n];if(r.pendingMarks.lastIndexOf(e)>-1)r.pendingMarks=e.removeFromSet(r.pendingMarks);else{r.activeMarks=e.removeFromSet(r.activeMarks);let t=r.popFromStashMark(e);t&&r.type&&r.type.allowsMarkType(t.type)&&(r.activeMarks=t.addToSet(r.activeMarks))}if(r==t)break}}}function re(e,t){return(e.matches||e.msMatchesSelector||e.webkitMatchesSelector||e.mozMatchesSelector).call(e,t)}function ie(e){let t={};for(let n in e)t[n]=e[n];return t}function oe(e,t){let n=t.schema.nodes;for(let r in n){let i=n[r];if(!i.allowsMarkType(e))continue;let o=[],s=e=>{o.push(e);for(let n=0;n{if(i.length||e.marks.length){let n=0,o=0;for(;n=0;r--){let i=this.serializeMark(e.marks[r],e.isInline,t);i&&((i.contentDOM||i.dom).appendChild(n),n=i.dom)}return n}serializeMark(e,t,n={}){let r=this.marks[e.type.name];return r&&se.renderSpec(ae(n),r(e,t))}static renderSpec(e,t,n=null){if("string"==typeof t)return{dom:e.createTextNode(t)};if(null!=t.nodeType)return{dom:t};if(t.dom&&null!=t.dom.nodeType)return t;let r,i=t[0],o=i.indexOf(" ");o>0&&(n=i.slice(0,o),i=i.slice(o+1));let s=n?e.createElementNS(n,i):e.createElement(i),l=t[1],a=1;if(l&&"object"==typeof l&&null==l.nodeType&&!Array.isArray(l)){a=2;for(let e in l)if(null!=l[e]){let t=e.indexOf(" ");t>0?s.setAttributeNS(e.slice(0,t),e.slice(t+1),l[e]):s.setAttribute(e,l[e])}}for(let c=a;ca)throw new RangeError("Content hole must be the only child of its parent node");return{dom:s,contentDOM:s}}{let{dom:t,contentDOM:o}=se.renderSpec(e,i,n);if(s.appendChild(t),o){if(r)throw new RangeError("Multiple content holes");r=o}}}return{dom:s,contentDOM:r}}static fromSchema(e){return e.cached.domSerializer||(e.cached.domSerializer=new se(this.nodesFromSchema(e),this.marksFromSchema(e)))}static nodesFromSchema(e){let t=le(e.nodes);return t.text||(t.text=e=>e.text),t}static marksFromSchema(e){return le(e.marks)}}function le(e){let t={};for(let n in e){let r=e[n].spec.toDOM;r&&(t[n]=r)}return t}function ae(e){return e.document||window.document}const ce=Math.pow(2,16);function he(e){return 65535&e}class de{constructor(e,t,n){this.pos=e,this.delInfo=t,this.recover=n}get deleted(){return(8&this.delInfo)>0}get deletedBefore(){return(5&this.delInfo)>0}get deletedAfter(){return(6&this.delInfo)>0}get deletedAcross(){return(4&this.delInfo)>0}}class ue{constructor(e,t=!1){if(this.ranges=e,this.inverted=t,!e.length&&ue.empty)return ue.empty}recover(e){let t=0,n=he(e);if(!this.inverted)for(let r=0;re)break;let a=this.ranges[s+i],c=this.ranges[s+o],h=l+a;if(e<=h){let i=l+r+((a?e==l?-1:e==h?1:t:t)<0?0:c);if(n)return i;let o=e==(t<0?l:h)?null:s/3+(e-l)*ce,d=e==l?2:e==h?1:4;return(t<0?e!=l:e!=h)&&(d|=8),new de(i,d,o)}r+=c-a}return n?e+r:new de(e+r,0,null)}touches(e,t){let n=0,r=he(t),i=this.inverted?2:1,o=this.inverted?1:2;for(let s=0;se)break;let l=this.ranges[s+i];if(e<=t+l&&s==3*r)return!0;n+=this.ranges[s+o]-l}return!1}forEach(e){let t=this.inverted?2:1,n=this.inverted?1:2;for(let r=0,i=0;r=0;t--){let r=e.getMirror(t);this.appendMap(e.maps[t].invert(),null!=r&&r>t?n-r-1:void 0)}}invert(){let e=new fe;return e.appendMappingInverted(this),e}map(e,t=1){if(this.mirror)return this._map(e,t,!0);for(let n=this.from;ni&&te.isAtom&&t.type.allowsMarkType(this.mark.type)?e.mark(this.mark.addToSet(e.marks)):e),r),t.openStart,t.openEnd);return ge.fromReplace(e,this.from,this.to,i)}invert(){return new we(this.from,this.to,this.mark)}map(e){let t=e.mapResult(this.from,1),n=e.mapResult(this.to,-1);return t.deleted&&n.deleted||t.pos>=n.pos?null:new ve(t.pos,n.pos,this.mark)}merge(e){return e instanceof ve&&e.mark.eq(this.mark)&&this.from<=e.to&&this.to>=e.from?new ve(Math.min(this.from,e.from),Math.max(this.to,e.to),this.mark):null}toJSON(){return{stepType:"addMark",mark:this.mark.toJSON(),from:this.from,to:this.to}}static fromJSON(e,t){if("number"!=typeof t.from||"number"!=typeof t.to)throw new RangeError("Invalid input for AddMarkStep.fromJSON");return new ve(t.from,t.to,e.markFromJSON(t.mark))}}me.jsonID("addMark",ve);class we extends me{constructor(e,t,n){super(),this.from=e,this.to=t,this.mark=n}apply(e){let t=e.slice(this.from,this.to),n=new c(ye(t.content,(e=>e.mark(this.mark.removeFromSet(e.marks))),e),t.openStart,t.openEnd);return ge.fromReplace(e,this.from,this.to,n)}invert(){return new ve(this.from,this.to,this.mark)}map(e){let t=e.mapResult(this.from,1),n=e.mapResult(this.to,-1);return t.deleted&&n.deleted||t.pos>=n.pos?null:new we(t.pos,n.pos,this.mark)}merge(e){return e instanceof we&&e.mark.eq(this.mark)&&this.from<=e.to&&this.to>=e.from?new we(Math.min(this.from,e.from),Math.max(this.to,e.to),this.mark):null}toJSON(){return{stepType:"removeMark",mark:this.mark.toJSON(),from:this.from,to:this.to}}static fromJSON(e,t){if("number"!=typeof t.from||"number"!=typeof t.to)throw new RangeError("Invalid input for RemoveMarkStep.fromJSON");return new we(t.from,t.to,e.markFromJSON(t.mark))}}me.jsonID("removeMark",we);class be extends me{constructor(e,t){super(),this.pos=e,this.mark=t}apply(e){let t=e.nodeAt(this.pos);if(!t)return ge.fail("No node at mark step's position");let n=t.type.create(t.attrs,null,this.mark.addToSet(t.marks));return ge.fromReplace(e,this.pos,this.pos+1,new c(r.from(n),0,t.isLeaf?0:1))}invert(e){let t=e.nodeAt(this.pos);if(t){let e=this.mark.addToSet(t.marks);if(e.length==t.marks.length){for(let n=0;nn.pos?null:new ke(t.pos,n.pos,r,i,this.slice,this.insert,this.structure)}toJSON(){let e={stepType:"replaceAround",from:this.from,to:this.to,gapFrom:this.gapFrom,gapTo:this.gapTo,insert:this.insert};return this.slice.size&&(e.slice=this.slice.toJSON()),this.structure&&(e.structure=!0),e}static fromJSON(e,t){if("number"!=typeof t.from||"number"!=typeof t.to||"number"!=typeof t.gapFrom||"number"!=typeof t.gapTo||"number"!=typeof t.insert)throw new RangeError("Invalid input for ReplaceAroundStep.fromJSON");return new ke(t.from,t.to,t.gapFrom,t.gapTo,c.fromJSON(e,t.slice),t.insert,!!t.structure)}}function Me(e,t,n){let r=e.resolve(t),i=n-t,o=r.depth;for(;i>0&&o>0&&r.indexAfter(o)==r.node(o).childCount;)o--,i--;if(i>0){let e=r.node(o).maybeChild(r.indexAfter(o));for(;i>0;){if(!e||e.isLeaf)return!0;e=e.firstChild,i--}}return!1}function Oe(e,t,n){return(0==t||e.canReplace(t,e.childCount))&&(n==e.childCount||e.canReplace(0,n))}function Ce(e){let t=e.parent.content.cutByIndex(e.startIndex,e.endIndex);for(let n=e.depth;;--n){let r=e.$from.node(n),i=e.$from.index(n),o=e.$to.indexAfter(n);if(no;c--,h--){let e=i.node(c),t=i.index(c);if(e.type.spec.isolating)return!1;let n=e.content.cutByIndex(t,e.childCount),o=r&&r[h]||e;if(o!=e&&(n=n.replaceChild(0,o.type.create(o.attrs))),!e.canReplace(t+1,e.childCount)||!o.type.validContent(n))return!1}let l=i.indexAfter(o),a=r&&r[0];return i.node(o).canReplaceWith(l,l,a?a.type:i.node(o+1).type)}function Ee(e,t){let n=e.resolve(t),r=n.index();return i=n.nodeBefore,o=n.nodeAfter,!(!i||!o||i.isLeaf||!i.canAppend(o))&&n.parent.canReplace(r,r+1);var i,o}function Ae(e,t,n=t,r=c.empty){if(t==n&&!r.size)return null;let i=e.resolve(t),o=e.resolve(n);return $e(i,o,r)?new Se(t,n,r):new Pe(i,o,r).fit()}function $e(e,t,n){return!n.openStart&&!n.openEnd&&e.start()==t.start()&&e.parent.canReplace(e.index(),t.index(),n.content)}me.jsonID("replaceAround",ke);class Pe{constructor(e,t,n){this.$from=e,this.$to=t,this.unplaced=n,this.frontier=[],this.placed=r.empty;for(let r=0;r<=e.depth;r++){let t=e.node(r);this.frontier.push({type:t.type,match:t.contentMatchAt(e.indexAfter(r))})}for(let i=e.depth;i>0;i--)this.placed=r.from(e.node(i).copy(this.placed))}get depth(){return this.frontier.length-1}fit(){for(;this.unplaced.size;){let e=this.findFittable();e?this.placeNodes(e):this.openMore()||this.dropNode()}let e=this.mustMoveInline(),t=this.placed.size-this.depth-this.$from.depth,n=this.$from,r=this.close(e<0?this.$to:n.doc.resolve(e));if(!r)return null;let i=this.placed,o=n.depth,s=r.depth;for(;o&&s&&1==i.childCount;)i=i.firstChild.content,o--,s--;let l=new c(i,o,s);return e>-1?new ke(n.pos,e,this.$to.pos,this.$to.end(),l,t):l.size||n.pos!=this.$to.pos?new Se(n.pos,r.pos,l):null}findFittable(){for(let e=1;e<=2;e++)for(let t=this.unplaced.openStart;t>=0;t--){let n,i=null;t?(i=Re(this.unplaced.content,t-1).firstChild,n=i.content):n=this.unplaced.content;let o=n.firstChild;for(let s=this.depth;s>=0;s--){let n,{type:l,match:a}=this.frontier[s],c=null;if(1==e&&(o?a.matchType(o.type)||(c=a.fillBefore(r.from(o),!1)):i&&l.compatibleContent(i.type)))return{sliceDepth:t,frontierDepth:s,parent:i,inject:c};if(2==e&&o&&(n=a.findWrapping(o.type)))return{sliceDepth:t,frontierDepth:s,parent:i,wrap:n};if(i&&a.matchType(i.type))break}}}openMore(){let{content:e,openStart:t,openEnd:n}=this.unplaced,r=Re(e,t);return!(!r.childCount||r.firstChild.isLeaf)&&(this.unplaced=new c(e,t+1,Math.max(n,r.size+t>=e.size-n?t+1:0)),!0)}dropNode(){let{content:e,openStart:t,openEnd:n}=this.unplaced,r=Re(e,t);if(r.childCount<=1&&t>0){let i=e.size-t<=t+r.size;this.unplaced=new c(Ie(e,t-1,1),t-1,i?t-1:n)}else this.unplaced=new c(Ie(e,t,1),t,n)}placeNodes({sliceDepth:e,frontierDepth:t,parent:n,inject:i,wrap:o}){for(;this.depth>t;)this.closeFrontierNode();if(o)for(let r=0;r1||0==a||e.content.size)&&(u=t,d.push(_e(e.mark(f.allowedMarks(e.marks)),1==h?a:0,h==l.childCount?p:-1)))}let m=h==l.childCount;m||(p=-1),this.placed=ze(this.placed,t,r.from(d)),this.frontier[t].match=u,m&&p<0&&n&&n.type==this.frontier[this.depth].type&&this.frontier.length>1&&this.closeFrontierNode();for(let r=0,c=l;r1&&r==this.$to.end(--n);)++r;return r}findCloseLevel(e){e:for(let t=Math.min(this.depth,e.depth);t>=0;t--){let{match:n,type:r}=this.frontier[t],i=t=0;n--){let{match:t,type:r}=this.frontier[n],i=Be(e,n,r,t,!0);if(!i||i.childCount)continue e}return{depth:t,fit:o,move:i?e.doc.resolve(e.after(t+1)):e}}}}close(e){let t=this.findCloseLevel(e);if(!t)return null;for(;this.depth>t.depth;)this.closeFrontierNode();t.fit.childCount&&(this.placed=ze(this.placed,t.depth,t.fit)),e=t.move;for(let n=t.depth+1;n<=e.depth;n++){let t=e.node(n),r=t.type.contentMatch.fillBefore(t.content,!0,e.index(n));this.openFrontierNode(t.type,t.attrs,r)}return e}openFrontierNode(e,t=null,n){let i=this.frontier[this.depth];i.match=i.match.matchType(e),this.placed=ze(this.placed,this.depth,r.from(e.create(t,n))),this.frontier.push({type:e,match:e.contentMatch})}closeFrontierNode(){let e=this.frontier.pop().match.fillBefore(r.empty,!0);e.childCount&&(this.placed=ze(this.placed,this.frontier.length,e))}}function Ie(e,t,n){return 0==t?e.cutByIndex(n,e.childCount):e.replaceChild(0,e.firstChild.copy(Ie(e.firstChild.content,t-1,n)))}function ze(e,t,n){return 0==t?e.append(n):e.replaceChild(e.childCount-1,e.lastChild.copy(ze(e.lastChild.content,t-1,n)))}function Re(e,t){for(let n=0;n1&&(i=i.replaceChild(0,_e(i.firstChild,t-1,1==i.childCount?n-1:0))),t>0&&(i=e.type.contentMatch.fillBefore(i).append(i),n<=0&&(i=i.append(e.type.contentMatch.matchFragment(i).fillBefore(r.empty,!0)))),e.copy(i)}function Be(e,t,n,r,i){let o=e.node(t),s=i?e.indexAfter(t):e.index(t);if(s==o.childCount&&!n.compatibleContent(o.type))return null;let l=r.fillBefore(o.content,!0,s);return l&&!function(e,t,n){for(let r=n;ri){let t=o.contentMatchAt(0),n=t.fillBefore(e).append(e);e=n.append(t.matchFragment(n).fillBefore(r.empty,!0))}return e}function Fe(e,t){let n=[];for(let r=Math.min(e.depth,t.depth);r>=0;r--){let i=e.start(r);if(it.pos+(t.depth-r)||e.node(r).type.spec.isolating||t.node(r).type.spec.isolating)break;(i==t.start(r)||r==e.depth&&r==t.depth&&e.parent.inlineContent&&t.parent.inlineContent&&r&&t.start(r-1)==i-1)&&n.push(r)}return n}class Le extends me{constructor(e,t,n){super(),this.pos=e,this.attr=t,this.value=n}apply(e){let t=e.nodeAt(this.pos);if(!t)return ge.fail("No node at attribute step's position");let n=Object.create(null);for(let r in t.attrs)n[r]=t.attrs[r];n[this.attr]=this.value;let i=t.type.create(n,null,t.marks);return ge.fromReplace(e,this.pos,this.pos+1,new c(r.from(i),0,t.isLeaf?0:1))}getMap(){return ue.empty}invert(e){return new Le(this.pos,this.attr,e.nodeAt(this.pos).attrs[this.attr])}map(e){let t=e.mapResult(this.pos,1);return t.deletedAfter?null:new Le(t.pos,this.attr,this.value)}toJSON(){return{stepType:"attr",pos:this.pos,attr:this.attr,value:this.value}}static fromJSON(e,t){if("number"!=typeof t.pos||"string"!=typeof t.attr)throw new RangeError("Invalid input for AttrStep.fromJSON");return new Le(t.pos,t.attr,t.value)}}me.jsonID("attr",Le);let qe=class extends Error{};qe=function e(t){let n=Error.call(this,t);return n.__proto__=e.prototype,n},(qe.prototype=Object.create(Error.prototype)).constructor=qe,qe.prototype.name="TransformError";class We{constructor(e){this.doc=e,this.steps=[],this.docs=[],this.mapping=new fe}get before(){return this.docs.length?this.docs[0]:this.doc}step(e){let t=this.maybeStep(e);if(t.failed)throw new qe(t.failed);return this}maybeStep(e){let t=e.apply(this.doc);return t.failed||this.addStep(e,t.doc),t}get docChanged(){return this.steps.length>0}addStep(e,t){this.docs.push(this.doc),this.steps.push(e),this.mapping.appendMap(e.getMap()),this.doc=t}replace(e,t=e,n=c.empty){let r=Ae(this.doc,e,t,n);return r&&this.step(r),this}replaceWith(e,t,n){return this.replace(e,t,new c(r.from(n),0,0))}delete(e,t){return this.replace(e,t,c.empty)}insert(e,t){return this.replaceWith(e,e,t)}replaceRange(e,t,n){return function(e,t,n,r){if(!r.size)return e.deleteRange(t,n);let i=e.doc.resolve(t),o=e.doc.resolve(n);if($e(i,o,r))return e.step(new Se(t,n,r));let s=Fe(i,e.doc.resolve(n));0==s[s.length-1]&&s.pop();let l=-(i.depth+1);s.unshift(l);for(let c=i.depth,f=i.pos-1;c>0;c--,f--){let e=i.node(c).type.spec;if(e.defining||e.definingAsContext||e.isolating)break;s.indexOf(c)>-1?l=c:i.before(c)==f&&s.splice(1,0,-c)}let a=s.indexOf(l),h=[],d=r.openStart;for(let c=r.content,f=0;;f++){let e=c.firstChild;if(h.push(e),f==r.openStart)break;c=e.content}for(let c=d-1;c>=0;c--){let e=h[c].type,t=Ve(e);if(t&&i.node(a).type!=e)d=c;else if(t||!e.isTextblock)break}for(let f=r.openStart;f>=0;f--){let t=(f+d+1)%(r.openStart+1),l=h[t];if(l)for(let h=0;h=0&&(e.replace(t,n,r),!(e.steps.length>u));c--){let e=s[c];e<0||(t=i.before(e),n=o.after(e))}}(this,e,t,n),this}replaceRangeWith(e,t,n){return function(e,t,n,i){if(!i.isInline&&t==n&&e.doc.resolve(t).parent.content.size){let r=function(e,t,n){let r=e.resolve(t);if(r.parent.canReplaceWith(r.index(),r.index(),n))return t;if(0==r.parentOffset)for(let i=r.depth-1;i>=0;i--){let e=r.index(i);if(r.node(i).canReplaceWith(e,e,n))return r.before(i+1);if(e>0)return null}if(r.parentOffset==r.parent.content.size)for(let i=r.depth-1;i>=0;i--){let e=r.indexAfter(i);if(r.node(i).canReplaceWith(e,e,n))return r.after(i+1);if(e0&&(n||r.node(t-1).canReplace(r.index(t-1),i.indexAfter(t-1))))return e.delete(r.before(t),i.after(t))}for(let s=1;s<=r.depth&&s<=i.depth;s++)if(t-r.start(s)==r.depth-s&&n>r.end(s)&&i.end(s)-n!=i.depth-s)return e.delete(r.before(s),n);e.delete(t,n)}(this,e,t),this}lift(e,t){return function(e,t,n){let{$from:i,$to:o,depth:s}=t,l=i.before(s+1),a=o.after(s+1),h=l,d=a,u=r.empty,f=0;for(let c=s,g=!1;c>n;c--)g||i.index(c)>0?(g=!0,u=r.from(i.node(c).copy(u)),f++):h--;let p=r.empty,m=0;for(let c=s,g=!1;c>n;c--)g||o.after(c+1)=0;l--){if(i.size){let e=n[l].type.contentMatch.matchFragment(i);if(!e||!e.validEnd)throw new RangeError("Wrapper type given to Transform.wrap does not form valid content of its parent wrapper")}i=r.from(n[l].type.create(n[l].attrs,i))}let o=t.start,s=t.end;e.step(new ke(o,s,o,s,new c(i,0,0),n.length,!0))}(this,e,t),this}setBlockType(e,t=e,n,i=null){return function(e,t,n,i,o){if(!i.isTextblock)throw new RangeError("Type given to setBlockType should be a textblock");let s=e.steps.length;e.doc.nodesBetween(t,n,((t,n)=>{if(t.isTextblock&&!t.hasMarkup(i,o)&&function(e,t,n){let r=e.resolve(t),i=r.index();return r.parent.canReplaceWith(i,i+1,n)}(e.doc,e.mapping.slice(s).map(n),i)){e.clearIncompatible(e.mapping.slice(s).map(n,1),i);let l=e.mapping.slice(s),a=l.map(n,1),h=l.map(n+t.nodeSize,1);return e.step(new ke(a,h,a+1,h-1,new c(r.from(i.create(o,null,t.marks)),0,0),1,!0)),!1}}))}(this,e,t,n,i),this}setNodeMarkup(e,t,n=null,i=[]){return function(e,t,n,i,o){let s=e.doc.nodeAt(t);if(!s)throw new RangeError("No node at given position");n||(n=s.type);let l=n.create(i,null,o||s.marks);if(s.isLeaf)return e.replaceWith(t,t+s.nodeSize,l);if(!n.validContent(s.content))throw new RangeError("Invalid content for node type "+n.name);e.step(new ke(t,t+s.nodeSize,t+1,t+s.nodeSize-1,new c(r.from(l),0,0),1,!0))}(this,e,t,n,i),this}setNodeAttribute(e,t,n){return this.step(new Le(e,t,n)),this}addNodeMark(e,t){return this.step(new be(e,t)),this}removeNodeMark(e,t){if(!(t instanceof l)){let n=this.doc.nodeAt(e);if(!n)throw new RangeError("No node at position "+e);if(!(t=t.isInSet(n.marks)))return this}return this.step(new xe(e,t)),this}split(e,t=1,n){return function(e,t,n=1,i){let o=e.doc.resolve(t),s=r.empty,l=r.empty;for(let a=o.depth,c=o.depth-n,h=n-1;a>c;a--,h--){s=r.from(o.node(a).copy(s));let e=i&&i[h];l=r.from(e?e.type.create(e.attrs,l):o.node(a).copy(l))}e.step(new Se(t,t,new c(s.append(l),n,n),!0))}(this,e,t,n),this}addMark(e,t,n){return function(e,t,n,r){let i,o,s=[],l=[];e.doc.nodesBetween(t,n,((e,a,c)=>{if(!e.isInline)return;let h=e.marks;if(!r.isInSet(h)&&c.type.allowsMarkType(r.type)){let c=Math.max(a,t),d=Math.min(a+e.nodeSize,n),u=r.addToSet(h);for(let e=0;ee.step(t))),l.forEach((t=>e.step(t)))}(this,e,t,n),this}removeMark(e,t,n){return function(e,t,n,r){let i=[],o=0;e.doc.nodesBetween(t,n,((e,s)=>{if(!e.isInline)return;o++;let l=null;if(r instanceof W){let t,n=e.marks;for(;t=r.isInSet(n);)(l||(l=[])).push(t),n=t.removeFromSet(n)}else r?r.isInSet(e.marks)&&(l=[r]):l=e.marks;if(l&&l.length){let r=Math.min(s+e.nodeSize,n);for(let e=0;ee.step(new we(t.from,t.to,t.style))))}(this,e,t,n),this}clearIncompatible(e,t,n){return function(e,t,n,i=n.contentMatch){let o=e.doc.nodeAt(t),s=[],l=t+1;for(let r=0;r=0;r--)e.step(s[r])}(this,e,t,n),this}}const Je=Object.create(null);class Ke{constructor(e,t,n){this.$anchor=e,this.$head=t,this.ranges=n||[new He(e.min(t),e.max(t))]}get anchor(){return this.$anchor.pos}get head(){return this.$head.pos}get from(){return this.$from.pos}get to(){return this.$to.pos}get $from(){return this.ranges[0].$from}get $to(){return this.ranges[0].$to}get empty(){let e=this.ranges;for(let t=0;t=0;i--){let r=t<0?nt(e.node(0),e.node(i),e.before(i+1),e.index(i),t,n):nt(e.node(0),e.node(i),e.after(i+1),e.index(i)+1,t,n);if(r)return r}return null}static near(e,t=1){return this.findFrom(e,t)||this.findFrom(e,-t)||new et(e.node(0))}static atStart(e){return nt(e,e,0,0,1)||new et(e)}static atEnd(e){return nt(e,e,e.content.size,e.childCount,-1)||new et(e)}static fromJSON(e,t){if(!t||!t.type)throw new RangeError("Invalid input for Selection.fromJSON");let n=Je[t.type];if(!n)throw new RangeError(`No selection type ${t.type} defined`);return n.fromJSON(e,t)}static jsonID(e,t){if(e in Je)throw new RangeError("Duplicate use of selection JSON ID "+e);return Je[e]=t,t.prototype.jsonID=e,t}getBookmark(){return Ge.between(this.$anchor,this.$head).getBookmark()}}Ke.prototype.visible=!0;class He{constructor(e,t){this.$from=e,this.$to=t}}let Ye=!1;function Ue(e){Ye||e.parent.inlineContent||(Ye=!0,console.warn("TextSelection endpoint not pointing into a node with inline content ("+e.parent.type.name+")"))}class Ge extends Ke{constructor(e,t=e){Ue(e),Ue(t),super(e,t)}get $cursor(){return this.$anchor.pos==this.$head.pos?this.$head:null}map(e,t){let n=e.resolve(t.map(this.head));if(!n.parent.inlineContent)return Ke.near(n);let r=e.resolve(t.map(this.anchor));return new Ge(r.parent.inlineContent?r:n,n)}replace(e,t=c.empty){if(super.replace(e,t),t==c.empty){let t=this.$from.marksAcross(this.$to);t&&e.ensureMarks(t)}}eq(e){return e instanceof Ge&&e.anchor==this.anchor&&e.head==this.head}getBookmark(){return new Ze(this.anchor,this.head)}toJSON(){return{type:"text",anchor:this.anchor,head:this.head}}static fromJSON(e,t){if("number"!=typeof t.anchor||"number"!=typeof t.head)throw new RangeError("Invalid input for TextSelection.fromJSON");return new Ge(e.resolve(t.anchor),e.resolve(t.head))}static create(e,t,n=t){let r=e.resolve(t);return new this(r,n==t?r:e.resolve(n))}static between(e,t,n){let r=e.pos-t.pos;if(n&&!r||(n=r>=0?1:-1),!t.parent.inlineContent){let e=Ke.findFrom(t,n,!0)||Ke.findFrom(t,-n,!0);if(!e)return Ke.near(t,n);t=e.$head}return e.parent.inlineContent||(0==r||(e=(Ke.findFrom(e,-n,!0)||Ke.findFrom(e,n,!0)).$anchor).posnew et(e)};function nt(e,t,n,r,i,o=!1){if(t.inlineContent)return Ge.create(e,n);for(let s=r-(i>0?0:1);i>0?s=0;s+=i){let r=t.child(s);if(r.isAtom){if(!o&&Xe.isSelectable(r))return Xe.create(e,n-(i<0?r.nodeSize:0))}else{let t=nt(e,r,n+i,i<0?r.childCount:0,i,o);if(t)return t}n+=r.nodeSize*i}return null}function rt(e,t,n){let r=e.steps.length-1;if(r{null==i&&(i=r)})),e.setSelection(Ke.near(e.doc.resolve(i),n)))}class it extends We{constructor(e){super(e.doc),this.curSelectionFor=0,this.updated=0,this.meta=Object.create(null),this.time=Date.now(),this.curSelection=e.selection,this.storedMarks=e.storedMarks}get selection(){return this.curSelectionFor0}setStoredMarks(e){return this.storedMarks=e,this.updated|=2,this}ensureMarks(e){return l.sameSet(this.storedMarks||this.selection.$from.marks(),e)||this.setStoredMarks(e),this}addStoredMark(e){return this.ensureMarks(e.addToSet(this.storedMarks||this.selection.$head.marks()))}removeStoredMark(e){return this.ensureMarks(e.removeFromSet(this.storedMarks||this.selection.$head.marks()))}get storedMarksSet(){return(2&this.updated)>0}addStep(e,t){super.addStep(e,t),this.updated=-3&this.updated,this.storedMarks=null}setTime(e){return this.time=e,this}replaceSelection(e){return this.selection.replace(this,e),this}replaceSelectionWith(e,t=!0){let n=this.selection;return t&&(e=e.mark(this.storedMarks||(n.empty?n.$from.marks():n.$from.marksAcross(n.$to)||l.none))),n.replaceWith(this,e),this}deleteSelection(){return this.selection.replace(this),this}insertText(e,t,n){let r=this.doc.type.schema;if(null==t)return e?this.replaceSelectionWith(r.text(e),!0):this.deleteSelection();{if(null==n&&(n=t),n=null==n?t:n,!e)return this.deleteRange(t,n);let i=this.storedMarks;if(!i){let e=this.doc.resolve(t);i=n==t?e.marks():e.marksAcross(this.doc.resolve(n))}return this.replaceRangeWith(t,n,r.text(e,i)),this.selection.empty||this.setSelection(Ke.near(this.selection.$to)),this}}setMeta(e,t){return this.meta["string"==typeof e?e:e.key]=t,this}getMeta(e){return this.meta["string"==typeof e?e:e.key]}get isGeneric(){for(let e in this.meta)return!1;return!0}scrollIntoView(){return this.updated|=4,this}get scrolledIntoView(){return(4&this.updated)>0}}function ot(e,t){return t&&e?e.bind(t):e}class st{constructor(e,t,n){this.name=e,this.init=ot(t.init,n),this.apply=ot(t.apply,n)}}const lt=[new st("doc",{init:e=>e.doc||e.schema.topNodeType.createAndFill(),apply:e=>e.doc}),new st("selection",{init:(e,t)=>e.selection||Ke.atStart(t.doc),apply:e=>e.selection}),new st("storedMarks",{init:e=>e.storedMarks||null,apply:(e,t,n,r)=>r.selection.$cursor?e.storedMarks:null}),new st("scrollToSelection",{init:()=>0,apply:(e,t)=>e.scrolledIntoView?t+1:t})];class at{constructor(e,t){this.schema=e,this.plugins=[],this.pluginsByKey=Object.create(null),this.fields=lt.slice(),t&&t.forEach((e=>{if(this.pluginsByKey[e.key])throw new RangeError("Adding different instances of a keyed plugin ("+e.key+")");this.plugins.push(e),this.pluginsByKey[e.key]=e,e.spec.state&&this.fields.push(new st(e.key,e.spec.state,e))}))}}class ct{constructor(e){this.config=e}get schema(){return this.config.schema}get plugins(){return this.config.plugins}apply(e){return this.applyTransaction(e).state}filterTransaction(e,t=-1){for(let n=0;ne.toJSON()))),e&&"object"==typeof e)for(let n in e){if("doc"==n||"selection"==n)throw new RangeError("The JSON fields `doc` and `selection` are reserved");let r=e[n],i=r.spec.state;i&&i.toJSON&&(t[n]=i.toJSON.call(r,this[r.key]))}return t}static fromJSON(e,t,n){if(!t)throw new RangeError("Invalid input for EditorState.fromJSON");if(!e.schema)throw new RangeError("Required config field 'schema' missing");let r=new at(e.schema,e.plugins),i=new ct(r);return r.fields.forEach((r=>{if("doc"==r.name)i.doc=N.fromJSON(e.schema,t.doc);else if("selection"==r.name)i.selection=Ke.fromJSON(i.doc,t.selection);else if("storedMarks"==r.name)t.storedMarks&&(i.storedMarks=t.storedMarks.map(e.schema.markFromJSON));else{if(n)for(let o in n){let s=n[o],l=s.spec.state;if(s.key==r.name&&l&&l.fromJSON&&Object.prototype.hasOwnProperty.call(t,o))return void(i[r.name]=l.fromJSON.call(s,e,t[o],i))}i[r.name]=r.init(e,i)}})),i}}function ht(e,t,n){for(let r in e){let i=e[r];i instanceof Function?i=i.bind(t):"handleDOMEvents"==r&&(i=ht(i,t,{})),n[r]=i}return n}class dt{constructor(e){this.spec=e,this.props={},e.props&&ht(e.props,this,this.props),this.key=e.key?e.key.key:ft("plugin")}getState(e){return e[this.key]}}const ut=Object.create(null);function ft(e){return e in ut?e+"$"+ ++ut[e]:(ut[e]=0,e+"$")}class pt{constructor(e="key"){this.key=ft(e)}get(e){return e.config.pluginsByKey[this.key]}getState(e){return e[this.key]}}const mt=function(e){for(var t=0;;t++)if(!(e=e.previousSibling))return t},gt=function(e){let t=e.assignedSlot||e.parentNode;return t&&11==t.nodeType?t.host:t};let yt=null;const vt=function(e,t,n){let r=yt||(yt=document.createRange());return r.setEnd(e,null==n?e.nodeValue.length:n),r.setStart(e,t||0),r},wt=function(e,t,n,r){return n&&(xt(e,t,n,r,-1)||xt(e,t,n,r,1))},bt=/^(img|br|input|textarea|hr)$/i;function xt(e,t,n,r,i){for(;;){if(e==n&&t==r)return!0;if(t==(i<0?0:St(e))){let n=e.parentNode;if(!n||1!=n.nodeType||kt(e)||bt.test(e.nodeName)||"false"==e.contentEditable)return!1;t=mt(e)+(i<0?0:1),e=n}else{if(1!=e.nodeType)return!1;if("false"==(e=e.childNodes[t+(i<0?-1:0)]).contentEditable)return!1;t=i<0?St(e):0}}}function St(e){return 3==e.nodeType?e.nodeValue.length:e.childNodes.length}function kt(e){let t;for(let n=e;n&&!(t=n.pmViewDesc);n=n.parentNode);return t&&t.node&&t.node.isBlock&&(t.dom==e||t.contentDOM==e)}const Mt=function(e){return e.focusNode&&wt(e.focusNode,e.focusOffset,e.anchorNode,e.anchorOffset)};function Ot(e,t){let n=document.createEvent("Event");return n.initEvent("keydown",!0,!0),n.keyCode=e,n.key=n.code=t,n}const Ct="undefined"!=typeof navigator?navigator:null,Nt="undefined"!=typeof document?document:null,Dt=Ct&&Ct.userAgent||"",Tt=/Edge\/(\d+)/.exec(Dt),Et=/MSIE \d/.exec(Dt),At=/Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(Dt),$t=!!(Et||At||Tt),Pt=Et?document.documentMode:At?+At[1]:Tt?+Tt[1]:0,It=!$t&&/gecko\/(\d+)/i.test(Dt);It&&(/Firefox\/(\d+)/.exec(Dt)||[0,0])[1];const zt=!$t&&/Chrome\/(\d+)/.exec(Dt),Rt=!!zt,_t=zt?+zt[1]:0,Bt=!$t&&!!Ct&&/Apple Computer/.test(Ct.vendor),Vt=Bt&&(/Mobile\/\w+/.test(Dt)||!!Ct&&Ct.maxTouchPoints>2),jt=Vt||!!Ct&&/Mac/.test(Ct.platform),Ft=/Android \d/.test(Dt),Lt=!!Nt&&"webkitFontSmoothing"in Nt.documentElement.style,qt=Lt?+(/\bAppleWebKit\/(\d+)/.exec(navigator.userAgent)||[0,0])[1]:0;function Wt(e){return{left:0,right:e.documentElement.clientWidth,top:0,bottom:e.documentElement.clientHeight}}function Jt(e,t){return"number"==typeof e?e:e[t]}function Kt(e){let t=e.getBoundingClientRect(),n=t.width/e.offsetWidth||1,r=t.height/e.offsetHeight||1;return{left:t.left,right:t.left+e.clientWidth*n,top:t.top,bottom:t.top+e.clientHeight*r}}function Ht(e,t,n){let r=e.someProp("scrollThreshold")||0,i=e.someProp("scrollMargin")||5,o=e.dom.ownerDocument;for(let s=n||e.dom;s;s=gt(s)){if(1!=s.nodeType)continue;let e=s,n=e==o.body,l=n?Wt(o):Kt(e),a=0,c=0;if(t.topl.bottom-Jt(r,"bottom")&&(c=t.bottom-l.bottom+Jt(i,"bottom")),t.leftl.right-Jt(r,"right")&&(a=t.right-l.right+Jt(i,"right")),a||c)if(n)o.defaultView.scrollBy(a,c);else{let n=e.scrollLeft,r=e.scrollTop;c&&(e.scrollTop+=c),a&&(e.scrollLeft+=a);let i=e.scrollLeft-n,o=e.scrollTop-r;t={left:t.left-i,top:t.top-o,right:t.right-i,bottom:t.bottom-o}}if(n)break}}function Yt(e){let t=[],n=e.ownerDocument;for(let r=e;r&&(t.push({dom:r,top:r.scrollTop,left:r.scrollLeft}),e!=n);r=gt(r));return t}function Ut(e,t){for(let n=0;n=l){s=Math.max(d.bottom,s),l=Math.min(d.top,l);let e=d.left>t.left?d.left-t.left:d.right=(d.left+d.right)/2?1:0));continue}}!n&&(t.left>=d.right&&t.top>=d.top||t.left>=d.left&&t.top>=d.bottom)&&(o=c+1)}}return n&&3==n.nodeType?function(e,t){let n=e.nodeValue.length,r=document.createRange();for(let i=0;i=(n.left+n.right)/2?1:0)}}return{node:e,offset:0}}(n,r):!n||i&&1==n.nodeType?{node:e,offset:o}:Zt(n,r)}function Xt(e,t){return e.left>=t.left-1&&e.left<=t.right+1&&e.top>=t.top-1&&e.top<=t.bottom+1}function Qt(e,t,n){let r=e.childNodes.length;if(r&&n.topt.top&&i++}n==e.dom&&i==n.childNodes.length-1&&1==n.lastChild.nodeType&&t.top>n.lastChild.getBoundingClientRect().bottom?o=e.state.doc.content.size:0!=i&&1==n.nodeType&&"BR"==n.childNodes[i-1].nodeName||(o=function(e,t,n,r){let i=-1;for(let o=t,s=!1;o!=e.dom;){let t=e.docView.nearestDesc(o,!0);if(!t)return null;if(1==t.dom.nodeType&&(t.node.isBlock&&t.parent&&!s||!t.contentDOM)){let e=t.dom.getBoundingClientRect();if(t.node.isBlock&&t.parent&&!s&&(s=!0,e.left>r.left||e.top>r.top?i=t.posBefore:(e.right-1?i:e.docView.posFromDOM(t,n,-1)}(e,n,i,t))}null==o&&(o=function(e,t,n){let{node:r,offset:i}=Zt(t,n),o=-1;if(1==r.nodeType&&!r.firstChild){let e=r.getBoundingClientRect();o=e.left!=e.right&&n.left>(e.left+e.right)/2?1:-1}return e.docView.posFromDOM(r,i,o)}(e,s,t));let l=e.docView.nearestDesc(s,!0);return{pos:o,inside:l?l.posAtStart-l.border:-1}}function tn(e,t){let n=e.getClientRects();return n.length?n[t<0?0:n.length-1]:e.getBoundingClientRect()}const nn=/[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;function rn(e,t,n){let{node:r,offset:i,atom:o}=e.docView.domFromPos(t,n<0?-1:1),s=Lt||It;if(3==r.nodeType){if(!s||!nn.test(r.nodeValue)&&(n<0?i:i!=r.nodeValue.length)){let e=i,t=i,o=n<0?1:-1;return n<0&&!i?(t++,o=-1):n>=0&&i==r.nodeValue.length?(e--,o=1):n<0?e--:t++,on(tn(vt(r,e,t),1),o<0)}{let e=tn(vt(r,i,i),n);if(It&&i&&/\s/.test(r.nodeValue[i-1])&&i=0)}if(null==o&&i&&(n<0||i==St(r))){let e=r.childNodes[i-1],t=3==e.nodeType?vt(e,St(e)-(s?0:1)):1!=e.nodeType||"BR"==e.nodeName&&e.nextSibling?null:e;if(t)return on(tn(t,1),!1)}if(null==o&&i=0)}function on(e,t){if(0==e.width)return e;let n=t?e.left:e.right;return{top:e.top,bottom:e.bottom,left:n,right:n}}function sn(e,t){if(0==e.height)return e;let n=t?e.top:e.bottom;return{top:n,bottom:n,left:e.left,right:e.right}}function ln(e,t,n){let r=e.state,i=e.root.activeElement;r!=t&&e.updateState(t),i!=e.dom&&e.focus();try{return n()}finally{r!=t&&e.updateState(r),i!=e.dom&&i&&i.focus()}}const an=/[\u0590-\u08ac]/;let cn=null,hn=null,dn=!1;function un(e,t,n){return cn==t&&hn==n?dn:(cn=t,hn=n,dn="up"==n||"down"==n?function(e,t,n){let r=t.selection,i="up"==n?r.$from:r.$to;return ln(e,t,(()=>{let{node:t}=e.docView.domFromPos(i.pos,"up"==n?-1:1);for(;;){let n=e.docView.nearestDesc(t,!0);if(!n)break;if(n.node.isBlock){t=n.contentDOM||n.dom;break}t=n.dom.parentNode}let r=rn(e,i.pos,1);for(let e=t.firstChild;e;e=e.nextSibling){let t;if(1==e.nodeType)t=e.getClientRects();else{if(3!=e.nodeType)continue;t=vt(e,0,e.nodeValue.length).getClientRects()}for(let e=0;ei.top+1&&("up"==n?r.top-i.top>2*(i.bottom-r.top):i.bottom-r.bottom>2*(r.bottom-i.top)))return!1}}return!0}))}(e,t,n):function(e,t,n){let{$head:r}=t.selection;if(!r.parent.isTextblock)return!1;let i=r.parentOffset,o=!i,s=i==r.parent.content.size,l=e.domSelection();return an.test(r.parent.textContent)&&l.modify?ln(e,t,(()=>{let{focusNode:t,focusOffset:i,anchorNode:o,anchorOffset:s}=e.domSelectionRange(),a=l.caretBidiLevel;l.modify("move",n,"character");let c=r.depth?e.docView.domAfterPos(r.before()):e.dom,{focusNode:h,focusOffset:d}=e.domSelectionRange(),u=h&&!c.contains(1==h.nodeType?h:h.parentNode)||t==h&&i==d;try{l.collapse(o,s),t&&(t!=o||i!=s)&&l.extend&&l.extend(t,i)}catch(f){}return null!=a&&(l.caretBidiLevel=a),u})):"left"==n||"backward"==n?o:s}(e,t,n))}class fn{constructor(e,t,n,r){this.parent=e,this.children=t,this.dom=n,this.contentDOM=r,this.dirty=0,n.pmViewDesc=this}matchesWidget(e){return!1}matchesMark(e){return!1}matchesNode(e,t,n){return!1}matchesHack(e){return!1}parseRule(){return null}stopEvent(e){return!1}get size(){let e=0;for(let t=0;tmt(this.contentDOM);else if(this.contentDOM&&this.contentDOM!=this.dom&&this.dom.contains(this.contentDOM))r=2&e.compareDocumentPosition(this.contentDOM);else if(this.dom.firstChild){if(0==t)for(let t=e;;t=t.parentNode){if(t==this.dom){r=!1;break}if(t.previousSibling)break}if(null==r&&t==e.childNodes.length)for(let t=e;;t=t.parentNode){if(t==this.dom){r=!0;break}if(t.nextSibling)break}}return(null==r?n>0:r)?this.posAtEnd:this.posAtStart}nearestDesc(e,t=!1){for(let n=!0,r=e;r;r=r.parentNode){let i,o=this.getDesc(r);if(o&&(!t||o.node)){if(!n||!(i=o.nodeDOM)||(1==i.nodeType?i.contains(1==e.nodeType?e:e.parentNode):i==e))return o;n=!1}}}getDesc(e){let t=e.pmViewDesc;for(let n=t;n;n=n.parent)if(n==this)return t}posFromDOM(e,t,n){for(let r=e;r;r=r.parentNode){let i=this.getDesc(r);if(i)return i.localPosFromDOM(e,t,n)}return-1}descAt(e){for(let t=0,n=0;te||t instanceof bn){r=e-i;break}i=o}if(r)return this.children[n].domFromPos(r-this.children[n].border,t);for(let i;n&&!(i=this.children[n-1]).size&&i instanceof pn&&i.side>=0;n--);if(t<=0){let e,r=!0;for(;e=n?this.children[n-1]:null,e&&e.dom.parentNode!=this.contentDOM;n--,r=!1);return e&&t&&r&&!e.border&&!e.domAtom?e.domFromPos(e.size,t):{node:this.contentDOM,offset:e?mt(e.dom)+1:0}}{let e,r=!0;for(;e=n=i&&t<=l-n.border&&n.node&&n.contentDOM&&this.contentDOM.contains(n.contentDOM))return n.parseRange(e,t,i);e=o;for(let t=s;t>0;t--){let n=this.children[t-1];if(n.size&&n.dom.parentNode==this.contentDOM&&!n.emptyChildAt(1)){r=mt(n.dom)+1;break}e-=n.size}-1==r&&(r=0)}if(r>-1&&(l>t||s==this.children.length-1)){t=l;for(let e=s+1;ef&&ot){let e=s;s=l,l=e}let n=document.createRange();n.setEnd(l.node,l.offset),n.setStart(s.node,s.offset),a.removeAllRanges(),a.addRange(n)}}ignoreMutation(e){return!this.contentDOM&&"selection"!=e.type}get contentLost(){return this.contentDOM&&this.contentDOM!=this.dom&&!this.dom.contains(this.contentDOM)}markDirty(e,t){for(let n=0,r=0;r=n:en){let r=n+i.border,s=o-i.border;if(e>=r&&t<=s)return this.dirty=e==n||t==o?2:1,void(e!=r||t!=s||!i.contentLost&&i.dom.parentNode==this.contentDOM?i.markDirty(e-r,t-r):i.dirty=3);i.dirty=i.dom!=i.contentDOM||i.dom.parentNode!=this.contentDOM||i.children.length?3:2}n=o}this.dirty=2}markParentsDirty(){let e=1;for(let t=this.parent;t;t=t.parent,e++){let n=1==e?2:1;t.dirtyi?i.parent?i.parent.posBeforeChild(i):void 0:r))),!t.type.spec.raw){if(1!=o.nodeType){let e=document.createElement("span");e.appendChild(o),o=e}o.contentEditable="false",o.classList.add("ProseMirror-widget")}super(e,[],o,null),this.widget=t,this.widget=t,i=this}matchesWidget(e){return 0==this.dirty&&e.type.eq(this.widget.type)}parseRule(){return{ignore:!0}}stopEvent(e){let t=this.widget.spec.stopEvent;return!!t&&t(e)}ignoreMutation(e){return"selection"!=e.type||this.widget.spec.ignoreSelection}destroy(){this.widget.type.destroy(this.dom),super.destroy()}get domAtom(){return!0}get side(){return this.widget.type.side}}class mn extends fn{constructor(e,t,n,r){super(e,[],t,null),this.textDOM=n,this.text=r}get size(){return this.text.length}localPosFromDOM(e,t){return e!=this.textDOM?this.posAtStart+(t?this.size:0):this.posAtStart+t}domFromPos(e){return{node:this.textDOM,offset:e}}ignoreMutation(e){return"characterData"===e.type&&e.target.nodeValue==e.oldValue}}class gn extends fn{constructor(e,t,n,r){super(e,[],n,r),this.mark=t}static create(e,t,n,r){let i=r.nodeViews[t.type.name],o=i&&i(t,r,n);return o&&o.dom||(o=se.renderSpec(document,t.type.spec.toDOM(t,n))),new gn(e,t,o.dom,o.contentDOM||o.dom)}parseRule(){return 3&this.dirty||this.mark.type.spec.reparseInView?null:{mark:this.mark.type.name,attrs:this.mark.attrs,contentElement:this.contentDOM||void 0}}matchesMark(e){return 3!=this.dirty&&this.mark.eq(e)}markDirty(e,t){if(super.markDirty(e,t),0!=this.dirty){let e=this.parent;for(;!e.node;)e=e.parent;e.dirty0&&(i=Pn(i,0,e,n));for(let s=0;ss?s.parent?s.parent.posBeforeChild(s):void 0:o),n,r),c=a&&a.dom,h=a&&a.contentDOM;if(t.isText)if(c){if(3!=c.nodeType)throw new RangeError("Text must be rendered as a DOM text node")}else c=document.createTextNode(t.text);else c||({dom:c,contentDOM:h}=se.renderSpec(document,t.type.spec.toDOM(t)));h||t.isText||"BR"==c.nodeName||(c.hasAttribute("contenteditable")||(c.contentEditable="false"),t.type.spec.draggable&&(c.draggable=!0));let d=c;return c=Dn(c,n,t),a?s=new xn(e,t,n,r,c,h||null,d,a,i,o+1):t.isText?new wn(e,t,n,r,c,d,i):new yn(e,t,n,r,c,h||null,d,i,o+1)}parseRule(){if(this.node.type.spec.reparseInView)return null;let e={node:this.node.type.name,attrs:this.node.attrs};if("pre"==this.node.type.whitespace&&(e.preserveWhitespace="full"),this.contentDOM)if(this.contentLost){for(let t=this.children.length-1;t>=0;t--){let n=this.children[t];if(this.dom.contains(n.dom.parentNode)){e.contentElement=n.dom.parentNode;break}}e.contentElement||(e.getContent=()=>r.empty)}else e.contentElement=this.contentDOM;else e.getContent=()=>this.node.content;return e}matchesNode(e,t,n){return 0==this.dirty&&e.eq(this.node)&&Tn(t,this.outerDeco)&&n.eq(this.innerDeco)}get size(){return this.node.nodeSize}get border(){return this.node.isLeaf?0:1}updateChildren(e,t){let n=this.node.inlineContent,r=t,i=e.composing?this.localCompositionInfo(e,t):null,o=i&&i.pos>-1?i:null,s=i&&i.pos<0,a=new An(this,o&&o.node,e);!function(e,t,n,r){let i=t.locals(e),o=0;if(0==i.length){for(let n=0;no;)l.push(i[s++]);let u=o+h.nodeSize;if(h.isText){let e=u;s!e.inline)):l.slice(),t.forChild(o,h),d),o=u}}(this.node,this.innerDeco,((t,i,o)=>{t.spec.marks?a.syncToMarks(t.spec.marks,n,e):t.type.side>=0&&!o&&a.syncToMarks(i==this.node.childCount?l.none:this.node.child(i).marks,n,e),a.placeWidget(t,e,r)}),((t,o,l,c)=>{let h;a.syncToMarks(t.marks,n,e),a.findNodeMatch(t,o,l,c)||s&&e.state.selection.from>r&&e.state.selection.to-1&&a.updateNodeAt(t,o,l,h,e)||a.updateNextNode(t,o,l,e,c)||a.addNode(t,o,l,e,r),r+=t.nodeSize})),a.syncToMarks([],n,e),this.node.isTextblock&&a.addTextblockHacks(),a.destroyRest(),(a.changed||2==this.dirty)&&(o&&this.protectLocalComposition(e,o),Sn(this.contentDOM,this.children,e),Vt&&function(e){if("UL"==e.nodeName||"OL"==e.nodeName){let t=e.style.cssText;e.style.cssText=t+"; list-style: square !important",window.getComputedStyle(e).listStyle,e.style.cssText=t}}(this.dom))}localCompositionInfo(e,t){let{from:n,to:r}=e.state.selection;if(!(e.state.selection instanceof Ge)||nt+this.node.content.size)return null;let i=e.domSelectionRange(),o=function(e,t){for(;;){if(3==e.nodeType)return e;if(1==e.nodeType&&t>0){if(e.childNodes.length>t&&3==e.childNodes[t].nodeType)return e.childNodes[t];t=St(e=e.childNodes[t-1])}else{if(!(1==e.nodeType&&t=n){let e=l=0&&e+t.length+l>=n)return l+e;if(n==r&&a.length>=r+t.length-l&&a.slice(r-l,r-l+t.length)==t)return r}}return-1}(this.node.content,e,n-t,r-t);return i<0?null:{node:o,pos:i,text:e}}return{node:o,pos:-1,text:""}}protectLocalComposition(e,{node:t,pos:n,text:r}){if(this.getDesc(t))return;let i=t;for(;i.parentNode!=this.contentDOM;i=i.parentNode){for(;i.previousSibling;)i.parentNode.removeChild(i.previousSibling);for(;i.nextSibling;)i.parentNode.removeChild(i.nextSibling);i.pmViewDesc&&(i.pmViewDesc=void 0)}let o=new mn(this,i,t,r);e.input.compositionNodes.push(o),this.children=Pn(this.children,n,n+r.length,e,o)}update(e,t,n,r){return!(3==this.dirty||!e.sameMarkup(this.node))&&(this.updateInner(e,t,n,r),!0)}updateInner(e,t,n,r){this.updateOuterDeco(t),this.node=e,this.innerDeco=n,this.contentDOM&&this.updateChildren(r,this.posAtStart),this.dirty=0}updateOuterDeco(e){if(Tn(e,this.outerDeco))return;let t=1!=this.nodeDOM.nodeType,n=this.dom;this.dom=Cn(this.dom,this.nodeDOM,On(this.outerDeco,this.node,t),On(e,this.node,t)),this.dom!=n&&(n.pmViewDesc=void 0,this.dom.pmViewDesc=this),this.outerDeco=e}selectNode(){1==this.nodeDOM.nodeType&&this.nodeDOM.classList.add("ProseMirror-selectednode"),!this.contentDOM&&this.node.type.spec.draggable||(this.dom.draggable=!0)}deselectNode(){1==this.nodeDOM.nodeType&&this.nodeDOM.classList.remove("ProseMirror-selectednode"),!this.contentDOM&&this.node.type.spec.draggable||this.dom.removeAttribute("draggable")}get domAtom(){return this.node.isAtom}}function vn(e,t,n,r,i){return Dn(r,t,e),new yn(void 0,e,t,n,r,r,r,i,0)}class wn extends yn{constructor(e,t,n,r,i,o,s){super(e,t,n,r,i,null,o,s,0)}parseRule(){let e=this.nodeDOM.parentNode;for(;e&&e!=this.dom&&!e.pmIsDeco;)e=e.parentNode;return{skip:e||!0}}update(e,t,n,r){return!(3==this.dirty||0!=this.dirty&&!this.inParent()||!e.sameMarkup(this.node))&&(this.updateOuterDeco(t),0==this.dirty&&e.text==this.node.text||e.text==this.nodeDOM.nodeValue||(this.nodeDOM.nodeValue=e.text,r.trackWrites==this.nodeDOM&&(r.trackWrites=null)),this.node=e,this.dirty=0,!0)}inParent(){let e=this.parent.contentDOM;for(let t=this.nodeDOM;t;t=t.parentNode)if(t==e)return!0;return!1}domFromPos(e){return{node:this.nodeDOM,offset:e}}localPosFromDOM(e,t,n){return e==this.nodeDOM?this.posAtStart+Math.min(t,this.node.text.length):super.localPosFromDOM(e,t,n)}ignoreMutation(e){return"characterData"!=e.type&&"selection"!=e.type}slice(e,t,n){let r=this.node.cut(e,t),i=document.createTextNode(r.text);return new wn(this.parent,r,this.outerDeco,this.innerDeco,i,i,n)}markDirty(e,t){super.markDirty(e,t),this.dom==this.nodeDOM||0!=e&&t!=this.nodeDOM.nodeValue.length||(this.dirty=3)}get domAtom(){return!1}}class bn extends fn{parseRule(){return{ignore:!0}}matchesHack(e){return 0==this.dirty&&this.dom.nodeName==e}get domAtom(){return!0}get ignoreForCoords(){return"IMG"==this.dom.nodeName}}class xn extends yn{constructor(e,t,n,r,i,o,s,l,a,c){super(e,t,n,r,i,o,s,a,c),this.spec=l}update(e,t,n,r){if(3==this.dirty)return!1;if(this.spec.update){let i=this.spec.update(e,t,n);return i&&this.updateInner(e,t,n,r),i}return!(!this.contentDOM&&!e.isLeaf)&&super.update(e,t,n,r)}selectNode(){this.spec.selectNode?this.spec.selectNode():super.selectNode()}deselectNode(){this.spec.deselectNode?this.spec.deselectNode():super.deselectNode()}setSelection(e,t,n,r){this.spec.setSelection?this.spec.setSelection(e,t,n):super.setSelection(e,t,n,r)}destroy(){this.spec.destroy&&this.spec.destroy(),super.destroy()}stopEvent(e){return!!this.spec.stopEvent&&this.spec.stopEvent(e)}ignoreMutation(e){return this.spec.ignoreMutation?this.spec.ignoreMutation(e):super.ignoreMutation(e)}}function Sn(e,t,n){let r=e.firstChild,i=!1;for(let o=0;o0;){let l;for(;;)if(r){let e=n.children[r-1];if(!(e instanceof gn)){l=e,r--;break}n=e,r=e.children.length}else{if(n==t)break e;r=n.parent.children.indexOf(n),n=n.parent}let a=l.node;if(a){if(a!=e.child(i-1))break;--i,o.set(l,i),s.push(l)}}return{index:i,matched:o,matches:s.reverse()}}(e.node.content,e)}destroyBetween(e,t){if(e!=t){for(let n=e;n>1,o=Math.min(i,e.length);for(;r-1)r>this.index&&(this.changed=!0,this.destroyBetween(this.index,r)),this.top=this.top.children[this.index];else{let r=gn.create(this.top,e[i],t,n);this.top.children.splice(this.index,0,r),this.top=r,this.changed=!0}this.index=0,i++}}findNodeMatch(e,t,n,r){let i,o=-1;if(r>=this.preMatch.index&&(i=this.preMatch.matches[r-this.preMatch.index]).parent==this.top&&i.matchesNode(e,t,n))o=this.top.children.indexOf(i,this.index);else for(let s=this.index,l=Math.min(this.top.children.length,s+5);s=n||h<=t?o.push(a):(cn&&o.push(a.slice(n-c,a.size,r)))}return o}function In(e,t=null){let n=e.domSelectionRange(),r=e.state.doc;if(!n.focusNode)return null;let i=e.docView.nearestDesc(n.focusNode),o=i&&0==i.size,s=e.docView.posFromDOM(n.focusNode,n.focusOffset,1);if(s<0)return null;let l,a,c=r.resolve(s);if(Mt(n)){for(l=c;i&&!i.node;)i=i.parent;let e=i.node;if(i&&e.isAtom&&Xe.isSelectable(e)&&i.parent&&(!e.isInline||!function(e,t,n){for(let r=0==t,i=t==St(e);r||i;){if(e==n)return!0;let t=mt(e);if(!(e=e.parentNode))return!1;r=r&&0==t,i=i&&t==St(e)}}(n.focusNode,n.focusOffset,i.dom))){let e=i.posBefore;a=new Xe(s==e?c:r.resolve(e))}}else{let t=e.docView.posFromDOM(n.anchorNode,n.anchorOffset,1);if(t<0)return null;l=r.resolve(t)}if(!a){a=qn(e,l,c,"pointer"==t||e.state.selection.head{n.anchorNode==r&&n.anchorOffset==i||(t.removeEventListener("selectionchange",e.input.hideSelectionGuard),setTimeout((()=>{zn(e)&&!e.state.selection.visible||e.dom.classList.remove("ProseMirror-hideselection")}),20))})}(e))}e.domObserver.setCurSelection(),e.domObserver.connectSelection()}}const _n=Bt||Rt&&_t<63;function Bn(e,t){let{node:n,offset:r}=e.docView.domFromPos(t,0),i=rr(e,t,n)))||Ge.between(t,n,r)}function Wn(e){return!(e.editable&&!e.hasFocus())&&Jn(e)}function Jn(e){let t=e.domSelectionRange();if(!t.anchorNode)return!1;try{return e.dom.contains(3==t.anchorNode.nodeType?t.anchorNode.parentNode:t.anchorNode)&&(e.editable||e.dom.contains(3==t.focusNode.nodeType?t.focusNode.parentNode:t.focusNode))}catch(n){return!1}}function Kn(e,t){let{$anchor:n,$head:r}=e.selection,i=t>0?n.max(r):n.min(r),o=i.parent.inlineContent?i.depth?e.doc.resolve(t>0?i.after():i.before()):null:i;return o&&Ke.findFrom(o,t)}function Hn(e,t){return e.dispatch(e.state.tr.setSelection(t).scrollIntoView()),!0}function Yn(e,t,n){let r=e.state.selection;if(!(r instanceof Ge)){if(r instanceof Xe&&r.node.isInline)return Hn(e,new Ge(t>0?r.$to:r.$from));{let n=Kn(e.state,t);return!!n&&Hn(e,n)}}if(!r.empty||n.indexOf("s")>-1)return!1;if(e.endOfTextblock(t>0?"right":"left")){let n=Kn(e.state,t);return!!(n&&n instanceof Xe)&&Hn(e,n)}if(!(jt&&n.indexOf("m")>-1)){let n,i=r.$head,o=i.textOffset?null:t<0?i.nodeBefore:i.nodeAfter;if(!o||o.isText)return!1;let s=t<0?i.pos-o.nodeSize:i.pos;return!!(o.isAtom||(n=e.docView.descAt(s))&&!n.contentDOM)&&(Xe.isSelectable(o)?Hn(e,new Xe(t<0?e.state.doc.resolve(i.pos-o.nodeSize):i)):!!Lt&&Hn(e,new Ge(e.state.doc.resolve(t<0?s:s+o.nodeSize))))}}function Un(e){return 3==e.nodeType?e.nodeValue.length:e.childNodes.length}function Gn(e){let t=e.pmViewDesc;return t&&0==t.size&&(e.nextSibling||"BR"!=e.nodeName)}function Zn(e){let t=e.domSelectionRange(),n=t.focusNode,r=t.focusOffset;if(!n)return;let i,o,s=!1;for(It&&1==n.nodeType&&r0){if(1!=n.nodeType)break;{let e=n.childNodes[r-1];if(Gn(e))i=n,o=--r;else{if(3!=e.nodeType)break;n=e,r=n.nodeValue.length}}}else{if(Qn(n))break;{let t=n.previousSibling;for(;t&&Gn(t);)i=n.parentNode,o=mt(t),t=t.previousSibling;if(t)n=t,r=Un(n);else{if(n=n.parentNode,n==e.dom)break;r=0}}}s?er(e,n,r):i&&er(e,i,o)}function Xn(e){let t=e.domSelectionRange(),n=t.focusNode,r=t.focusOffset;if(!n)return;let i,o,s=Un(n);for(;;)if(r{e.state==i&&Rn(e)}),50)}function tr(e,t,n){let r=e.state.selection;if(r instanceof Ge&&!r.empty||n.indexOf("s")>-1)return!1;if(jt&&n.indexOf("m")>-1)return!1;let{$from:i,$to:o}=r;if(!i.parent.inlineContent||e.endOfTextblock(t<0?"up":"down")){let n=Kn(e.state,t);if(n&&n instanceof Xe)return Hn(e,n)}if(!i.parent.inlineContent){let n=t<0?i:o,s=r instanceof et?Ke.near(n,t):Ke.findFrom(n,t);return!!s&&Hn(e,s)}return!1}function nr(e,t){if(!(e.state.selection instanceof Ge))return!0;let{$head:n,$anchor:r,empty:i}=e.state.selection;if(!n.sameParent(r))return!0;if(!i)return!1;if(e.endOfTextblock(t>0?"forward":"backward"))return!0;let o=!n.textOffset&&(t<0?n.nodeBefore:n.nodeAfter);if(o&&!o.isText){let r=e.state.tr;return t<0?r.delete(n.pos-o.nodeSize,n.pos):r.delete(n.pos,n.pos+o.nodeSize),e.dispatch(r),!0}return!1}function rr(e,t,n){e.domObserver.stop(),t.contentEditable=n,e.domObserver.start()}function ir(e,t){let n=t.keyCode,r=function(e){let t="";return e.ctrlKey&&(t+="c"),e.metaKey&&(t+="m"),e.altKey&&(t+="a"),e.shiftKey&&(t+="s"),t}(t);return 8==n||jt&&72==n&&"c"==r?nr(e,-1)||Zn(e):46==n||jt&&68==n&&"c"==r?nr(e,1)||Xn(e):13==n||27==n||(37==n||jt&&66==n&&"c"==r?Yn(e,-1,r)||Zn(e):39==n||jt&&70==n&&"c"==r?Yn(e,1,r)||Xn(e):38==n||jt&&80==n&&"c"==r?tr(e,-1,r)||Zn(e):40==n||jt&&78==n&&"c"==r?function(e){if(!Bt||e.state.selection.$head.parentOffset>0)return!1;let{focusNode:t,focusOffset:n}=e.domSelectionRange();if(t&&1==t.nodeType&&0==n&&t.firstChild&&"false"==t.firstChild.contentEditable){let n=t.firstChild;rr(e,n,"true"),setTimeout((()=>rr(e,n,"false")),20)}return!1}(e)||tr(e,1,r)||Xn(e):r==(jt?"m":"c")&&(66==n||73==n||89==n||90==n))}function or(e,t){e.someProp("transformCopied",(n=>{t=n(t,e)}));let n=[],{content:r,openStart:i,openEnd:o}=t;for(;i>1&&o>1&&1==r.childCount&&1==r.firstChild.childCount;){i--,o--;let e=r.firstChild;n.push(e.type.name,e.attrs!=e.type.defaultAttrs?e.attrs:null),r=e.content}let s=e.someProp("clipboardSerializer")||se.fromSchema(e.state.schema),l=mr(),a=l.createElement("div");a.appendChild(s.serializeFragment(r,{document:l}));let c,h=a.firstChild,d=0;for(;h&&1==h.nodeType&&(c=fr[h.nodeName.toLowerCase()]);){for(let e=c.length-1;e>=0;e--){let t=l.createElement(c[e]);for(;a.firstChild;)t.appendChild(a.firstChild);a.appendChild(t),d++}h=a.firstChild}return h&&1==h.nodeType&&h.setAttribute("data-pm-slice",`${i} ${o}${d?` -${d}`:""} ${JSON.stringify(n)}`),{dom:a,text:e.someProp("clipboardTextSerializer",(n=>n(t,e)))||t.content.textBetween(0,t.content.size,"\n\n")}}function sr(e,t,n,i,o){let s,l,a=o.parent.type.spec.code;if(!n&&!t)return null;let h=t&&(i||a||!n);if(h){if(e.someProp("transformPastedText",(n=>{t=n(t,a||i,e)})),a)return t?new c(r.from(e.state.schema.text(t.replace(/\r\n?/g,"\n"))),0,0):c.empty;let n=e.someProp("clipboardTextParser",(n=>n(t,o,i,e)));if(n)l=n;else{let n=o.marks(),{schema:r}=e.state,i=se.fromSchema(r);s=document.createElement("div"),t.split(/(?:\r\n?|\n)+/).forEach((e=>{let t=s.appendChild(document.createElement("p"));e&&t.appendChild(i.serializeNode(r.text(e,n)))}))}}else e.someProp("transformPastedHTML",(t=>{n=t(n,e)})),s=function(e){let t=/^(\s*]*>)*/.exec(e);t&&(e=e.slice(t[0].length));let n,r=mr().createElement("div"),i=/<([a-z][^>\s]+)/i.exec(e);(n=i&&fr[i[1].toLowerCase()])&&(e=n.map((e=>"<"+e+">")).join("")+e+n.map((e=>"")).reverse().join(""));if(r.innerHTML=e,n)for(let o=0;o0;r--){let e=s.firstChild;for(;e&&1!=e.nodeType;)e=e.nextSibling;if(!e)break;s=e}if(!l){let t=e.someProp("clipboardParser")||e.someProp("domParser")||H.fromSchema(e.state.schema);l=t.parseSlice(s,{preserveWhitespace:!(!h&&!u),context:o,ruleFromNode:e=>"BR"!=e.nodeName||e.nextSibling||!e.parentNode||lr.test(e.parentNode.nodeName)?null:{ignore:!0}})}if(u)l=function(e,t){if(!e.size)return e;let n,i=e.content.firstChild.type.schema;try{n=JSON.parse(t)}catch(a){return e}let{content:o,openStart:s,openEnd:l}=e;for(let c=n.length-2;c>=0;c-=2){let e=i.nodes[n[c]];if(!e||e.hasRequiredAttrs())break;o=r.from(e.create(n[c+1],o)),s++,l++}return new c(o,s,l)}(ur(l,+u[1],+u[2]),u[4]);else if(l=c.maxOpen(function(e,t){if(e.childCount<2)return e;for(let n=t.depth;n>=0;n--){let i,o=t.node(n).contentMatchAt(t.index(n)),s=[];if(e.forEach((e=>{if(!s)return;let t,n=o.findWrapping(e.type);if(!n)return s=null;if(t=s.length&&i.length&&cr(n,i,e,s[s.length-1],0))s[s.length-1]=t;else{s.length&&(s[s.length-1]=hr(s[s.length-1],i.length));let t=ar(e,n);s.push(t),o=o.matchType(t.type),i=n}})),s)return r.from(s)}return e}(l.content,o),!0),l.openStart||l.openEnd){let e=0,t=0;for(let n=l.content.firstChild;e{l=t(l,e)})),l}const lr=/^(a|abbr|acronym|b|cite|code|del|em|i|ins|kbd|label|output|q|ruby|s|samp|span|strong|sub|sup|time|u|tt|var)$/i;function ar(e,t,n=0){for(let i=t.length-1;i>=n;i--)e=t[i].create(null,r.from(e));return e}function cr(e,t,n,i,o){if(o=n&&(a=t<0?l.contentMatchAt(0).fillBefore(a,e.childCount>1||s<=o).append(a):a.append(l.contentMatchAt(l.childCount).fillBefore(r.empty,!0))),e.replaceChild(t<0?0:e.childCount-1,l.copy(a))}function ur(e,t,n){return t{for(let n in t)e.input.eventHandlers[n]||e.dom.addEventListener(n,e.input.eventHandlers[n]=t=>Sr(e,t))}))}function Sr(e,t){return e.someProp("handleDOMEvents",(n=>{let r=n[t.type];return!!r&&(r(e,t)||t.defaultPrevented)}))}function kr(e,t){if(!t.bubbles)return!0;if(t.defaultPrevented)return!1;for(let n=t.target;n!=e.dom;n=n.parentNode)if(!n||11==n.nodeType||n.pmViewDesc&&n.pmViewDesc.stopEvent(t))return!1;return!0}function Mr(e){return{left:e.clientX,top:e.clientY}}function Or(e,t,n,r,i){if(-1==r)return!1;let o=e.state.doc.resolve(r);for(let s=o.depth+1;s>0;s--)if(e.someProp(t,(t=>s>o.depth?t(e,n,o.nodeAfter,o.before(s),i,!0):t(e,n,o.node(s),o.before(s),i,!1))))return!0;return!1}function Cr(e,t,n){e.focused||e.focus();let r=e.state.tr.setSelection(t);"pointer"==n&&r.setMeta("pointer",!0),e.dispatch(r)}function Nr(e,t,n,r,i){return Or(e,"handleClickOn",t,n,r)||e.someProp("handleClick",(n=>n(e,t,r)))||(i?function(e,t){if(-1==t)return!1;let n,r,i=e.state.selection;i instanceof Xe&&(n=i.node);let o=e.state.doc.resolve(t);for(let s=o.depth+1;s>0;s--){let e=s>o.depth?o.nodeAfter:o.node(s);if(Xe.isSelectable(e)){r=n&&i.$from.depth>0&&s>=i.$from.depth&&o.before(i.$from.depth+1)==i.$from.pos?o.before(i.$from.depth):o.before(s);break}}return null!=r&&(Cr(e,Xe.create(e.state.doc,r),"pointer"),!0)}(e,n):function(e,t){if(-1==t)return!1;let n=e.state.doc.resolve(t),r=n.nodeAfter;return!!(r&&r.isAtom&&Xe.isSelectable(r))&&(Cr(e,new Xe(n),"pointer"),!0)}(e,n))}function Dr(e,t,n,r){return Or(e,"handleDoubleClickOn",t,n,r)||e.someProp("handleDoubleClick",(n=>n(e,t,r)))}function Tr(e,t,n,r){return Or(e,"handleTripleClickOn",t,n,r)||e.someProp("handleTripleClick",(n=>n(e,t,r)))||function(e,t,n){if(0!=n.button)return!1;let r=e.state.doc;if(-1==t)return!!r.inlineContent&&(Cr(e,Ge.create(r,0,r.content.size),"pointer"),!0);let i=r.resolve(t);for(let o=i.depth+1;o>0;o--){let t=o>i.depth?i.nodeAfter:i.node(o),n=i.before(o);if(t.inlineContent)Cr(e,Ge.create(r,n+1,n+1+t.content.size),"pointer");else{if(!Xe.isSelectable(t))continue;Cr(e,Xe.create(r,n),"pointer")}return!0}}(e,n,r)}function Er(e){return _r(e)}yr.keydown=(e,t)=>{let n=t;if(e.input.shiftKey=16==n.keyCode||n.shiftKey,!Pr(e,n)&&(e.input.lastKeyCode=n.keyCode,e.input.lastKeyCodeTime=Date.now(),!Ft||!Rt||13!=n.keyCode))if(229!=n.keyCode&&e.domObserver.forceFlush(),!Vt||13!=n.keyCode||n.ctrlKey||n.altKey||n.metaKey)e.someProp("handleKeyDown",(t=>t(e,n)))||ir(e,n)?n.preventDefault():br(e,"key");else{let t=Date.now();e.input.lastIOSEnter=t,e.input.lastIOSEnterFallbackTimeout=setTimeout((()=>{e.input.lastIOSEnter==t&&(e.someProp("handleKeyDown",(t=>t(e,Ot(13,"Enter")))),e.input.lastIOSEnter=0)}),200)}},yr.keyup=(e,t)=>{16==t.keyCode&&(e.input.shiftKey=!1)},yr.keypress=(e,t)=>{let n=t;if(Pr(e,n)||!n.charCode||n.ctrlKey&&!n.altKey||jt&&n.metaKey)return;if(e.someProp("handleKeyPress",(t=>t(e,n))))return void n.preventDefault();let r=e.state.selection;if(!(r instanceof Ge&&r.$from.sameParent(r.$to))){let t=String.fromCharCode(n.charCode);/[\r\n]/.test(t)||e.someProp("handleTextInput",(n=>n(e,r.$from.pos,r.$to.pos,t)))||e.dispatch(e.state.tr.insertText(t).scrollIntoView()),n.preventDefault()}};const Ar=jt?"metaKey":"ctrlKey";gr.mousedown=(e,t)=>{let n=t;e.input.shiftKey=n.shiftKey;let r=Er(e),i=Date.now(),o="singleClick";i-e.input.lastClick.time<500&&function(e,t){let n=t.x-e.clientX,r=t.y-e.clientY;return n*n+r*r<100}(n,e.input.lastClick)&&!n[Ar]&&("singleClick"==e.input.lastClick.type?o="doubleClick":"doubleClick"==e.input.lastClick.type&&(o="tripleClick")),e.input.lastClick={time:i,x:n.clientX,y:n.clientY,type:o};let s=e.posAtCoords(Mr(n));s&&("singleClick"==o?(e.input.mouseDown&&e.input.mouseDown.done(),e.input.mouseDown=new $r(e,s,n,!!r)):("doubleClick"==o?Dr:Tr)(e,s.pos,s.inside,n)?n.preventDefault():br(e,"pointer"))};class $r{constructor(e,t,n,r){let i,o;if(this.view=e,this.pos=t,this.event=n,this.flushed=r,this.delayedSelectionSync=!1,this.mightDrag=null,this.startDoc=e.state.doc,this.selectNode=!!n[Ar],this.allowDefault=n.shiftKey,t.inside>-1)i=e.state.doc.nodeAt(t.inside),o=t.inside;else{let n=e.state.doc.resolve(t.pos);i=n.parent,o=n.depth?n.before():0}const s=r?null:n.target,l=s?e.docView.nearestDesc(s,!0):null;this.target=l?l.dom:null;let{selection:a}=e.state;(0==n.button&&i.type.spec.draggable&&!1!==i.type.spec.selectable||a instanceof Xe&&a.from<=o&&a.to>o)&&(this.mightDrag={node:i,pos:o,addAttr:!(!this.target||this.target.draggable),setUneditable:!(!this.target||!It||this.target.hasAttribute("contentEditable"))}),this.target&&this.mightDrag&&(this.mightDrag.addAttr||this.mightDrag.setUneditable)&&(this.view.domObserver.stop(),this.mightDrag.addAttr&&(this.target.draggable=!0),this.mightDrag.setUneditable&&setTimeout((()=>{this.view.input.mouseDown==this&&this.target.setAttribute("contentEditable","false")}),20),this.view.domObserver.start()),e.root.addEventListener("mouseup",this.up=this.up.bind(this)),e.root.addEventListener("mousemove",this.move=this.move.bind(this)),br(e,"pointer")}done(){this.view.root.removeEventListener("mouseup",this.up),this.view.root.removeEventListener("mousemove",this.move),this.mightDrag&&this.target&&(this.view.domObserver.stop(),this.mightDrag.addAttr&&this.target.removeAttribute("draggable"),this.mightDrag.setUneditable&&this.target.removeAttribute("contentEditable"),this.view.domObserver.start()),this.delayedSelectionSync&&setTimeout((()=>Rn(this.view))),this.view.input.mouseDown=null}up(e){if(this.done(),!this.view.dom.contains(e.target))return;let t=this.pos;this.view.state.doc!=this.startDoc&&(t=this.view.posAtCoords(Mr(e))),this.updateAllowDefault(e),this.allowDefault||!t?br(this.view,"pointer"):Nr(this.view,t.pos,t.inside,e,this.selectNode)?e.preventDefault():0==e.button&&(this.flushed||Bt&&this.mightDrag&&!this.mightDrag.node.isAtom||Rt&&!this.view.state.selection.visible&&Math.min(Math.abs(t.pos-this.view.state.selection.from),Math.abs(t.pos-this.view.state.selection.to))<=2)?(Cr(this.view,Ke.near(this.view.state.doc.resolve(t.pos)),"pointer"),e.preventDefault()):br(this.view,"pointer")}move(e){this.updateAllowDefault(e),br(this.view,"pointer"),0==e.buttons&&this.done()}updateAllowDefault(e){!this.allowDefault&&(Math.abs(this.event.x-e.clientX)>4||Math.abs(this.event.y-e.clientY)>4)&&(this.allowDefault=!0)}}function Pr(e,t){return!!e.composing||!!(Bt&&Math.abs(t.timeStamp-e.input.compositionEndedAt)<500)&&(e.input.compositionEndedAt=-2e8,!0)}gr.touchstart=e=>{e.input.lastTouch=Date.now(),Er(e),br(e,"pointer")},gr.touchmove=e=>{e.input.lastTouch=Date.now(),br(e,"pointer")},gr.contextmenu=e=>Er(e);const Ir=Ft?5e3:-1;function zr(e,t){clearTimeout(e.input.composingTimeout),t>-1&&(e.input.composingTimeout=setTimeout((()=>_r(e)),t))}function Rr(e){for(e.composing&&(e.input.composing=!1,e.input.compositionEndedAt=function(){let e=document.createEvent("Event");return e.initEvent("event",!0,!0),e.timeStamp}());e.input.compositionNodes.length>0;)e.input.compositionNodes.pop().markParentsDirty()}function _r(e,t=!1){if(!(Ft&&e.domObserver.flushingSoon>=0)){if(e.domObserver.forceFlush(),Rr(e),t||e.docView&&e.docView.dirty){let t=In(e);return t&&!t.eq(e.state.selection)?e.dispatch(e.state.tr.setSelection(t)):e.updateState(e.state),!0}return!1}}yr.compositionstart=yr.compositionupdate=e=>{if(!e.composing){e.domObserver.flush();let{state:t}=e,n=t.selection.$from;if(t.selection.empty&&(t.storedMarks||!n.textOffset&&n.parentOffset&&n.nodeBefore.marks.some((e=>!1===e.type.spec.inclusive))))e.markCursor=e.state.storedMarks||n.marks(),_r(e,!0),e.markCursor=null;else if(_r(e),It&&t.selection.empty&&n.parentOffset&&!n.textOffset&&n.nodeBefore.marks.length){let t=e.domSelectionRange();for(let n=t.focusNode,r=t.focusOffset;n&&1==n.nodeType&&0!=r;){let t=r<0?n.lastChild:n.childNodes[r-1];if(!t)break;if(3==t.nodeType){e.domSelection().collapse(t,t.nodeValue.length);break}n=t,r=-1}}e.input.composing=!0}zr(e,Ir)},yr.compositionend=(e,t)=>{e.composing&&(e.input.composing=!1,e.input.compositionEndedAt=t.timeStamp,zr(e,20))};const Br=$t&&Pt<15||Vt&&qt<604;function Vr(e,t,n,r,i){let o=sr(e,t,n,r,e.state.selection.$from);if(e.someProp("handlePaste",(t=>t(e,i,o||c.empty))))return!0;if(!o)return!1;let s=function(e){return 0==e.openStart&&0==e.openEnd&&1==e.content.childCount?e.content.firstChild:null}(o),l=s?e.state.tr.replaceSelectionWith(s,e.input.shiftKey):e.state.tr.replaceSelection(o);return e.dispatch(l.scrollIntoView().setMeta("paste",!0).setMeta("uiEvent","paste")),!0}gr.copy=yr.cut=(e,t)=>{let n=t,r=e.state.selection,i="cut"==n.type;if(r.empty)return;let o=Br?null:n.clipboardData,s=r.content(),{dom:l,text:a}=or(e,s);o?(n.preventDefault(),o.clearData(),o.setData("text/html",l.innerHTML),o.setData("text/plain",a)):function(e,t){if(!e.dom.parentNode)return;let n=e.dom.parentNode.appendChild(document.createElement("div"));n.appendChild(t),n.style.cssText="position: fixed; left: -10000px; top: 10px";let r=getSelection(),i=document.createRange();i.selectNodeContents(t),e.dom.blur(),r.removeAllRanges(),r.addRange(i),setTimeout((()=>{n.parentNode&&n.parentNode.removeChild(n),e.focus()}),50)}(e,l),i&&e.dispatch(e.state.tr.deleteSelection().scrollIntoView().setMeta("uiEvent","cut"))},yr.paste=(e,t)=>{let n=t;if(e.composing&&!Ft)return;let r=Br?null:n.clipboardData;r&&Vr(e,r.getData("text/plain"),r.getData("text/html"),e.input.shiftKey,n)?n.preventDefault():function(e,t){if(!e.dom.parentNode)return;let n=e.input.shiftKey||e.state.selection.$from.parent.type.spec.code,r=e.dom.parentNode.appendChild(document.createElement(n?"textarea":"div"));n||(r.contentEditable="true"),r.style.cssText="position: fixed; left: -10000px; top: 10px",r.focus(),setTimeout((()=>{e.focus(),r.parentNode&&r.parentNode.removeChild(r),n?Vr(e,r.value,null,e.input.shiftKey,t):Vr(e,r.textContent,r.innerHTML,e.input.shiftKey,t)}),50)}(e,n)};class jr{constructor(e,t){this.slice=e,this.move=t}}const Fr=jt?"altKey":"ctrlKey";gr.dragstart=(e,t)=>{let n=t,r=e.input.mouseDown;if(r&&r.done(),!n.dataTransfer)return;let i=e.state.selection,o=i.empty?null:e.posAtCoords(Mr(n));if(o&&o.pos>=i.from&&o.pos<=(i instanceof Xe?i.to-1:i.to));else if(r&&r.mightDrag)e.dispatch(e.state.tr.setSelection(Xe.create(e.state.doc,r.mightDrag.pos)));else if(n.target&&1==n.target.nodeType){let t=e.docView.nearestDesc(n.target,!0);t&&t.node.type.spec.draggable&&t!=e.docView&&e.dispatch(e.state.tr.setSelection(Xe.create(e.state.doc,t.posBefore)))}let s=e.state.selection.content(),{dom:l,text:a}=or(e,s);n.dataTransfer.clearData(),n.dataTransfer.setData(Br?"Text":"text/html",l.innerHTML),n.dataTransfer.effectAllowed="copyMove",Br||n.dataTransfer.setData("text/plain",a),e.dragging=new jr(s,!n[Fr])},gr.dragend=e=>{let t=e.dragging;window.setTimeout((()=>{e.dragging==t&&(e.dragging=null)}),50)},yr.dragover=yr.dragenter=(e,t)=>t.preventDefault(),yr.drop=(e,t)=>{let n=t,r=e.dragging;if(e.dragging=null,!n.dataTransfer)return;let i=e.posAtCoords(Mr(n));if(!i)return;let o=e.state.doc.resolve(i.pos),s=r&&r.slice;s?e.someProp("transformPasted",(t=>{s=t(s,e)})):s=sr(e,n.dataTransfer.getData(Br?"Text":"text/plain"),Br?null:n.dataTransfer.getData("text/html"),!1,o);let l=!(!r||n[Fr]);if(e.someProp("handleDrop",(t=>t(e,n,s||c.empty,l))))return void n.preventDefault();if(!s)return;n.preventDefault();let a=s?function(e,t,n){let r=e.resolve(t);if(!n.content.size)return t;let i=n.content;for(let o=0;o=0;e--){let t=e==r.depth?0:r.pos<=(r.start(e+1)+r.end(e+1))/2?-1:1,n=r.index(e)+(t>0?1:0),s=r.node(e),l=!1;if(1==o)l=s.canReplace(n,n,i);else{let e=s.contentMatchAt(n).findWrapping(i.firstChild.type);l=e&&s.canReplaceWith(n,n,e[0])}if(l)return 0==t?r.pos:t<0?r.before(e+1):r.after(e+1)}return null}(e.state.doc,o.pos,s):o.pos;null==a&&(a=o.pos);let h=e.state.tr;l&&h.deleteSelection();let d=h.mapping.map(a),u=0==s.openStart&&0==s.openEnd&&1==s.content.childCount,f=h.doc;if(u?h.replaceRangeWith(d,d,s.content.firstChild):h.replaceRange(d,d,s),h.doc.eq(f))return;let p=h.doc.resolve(d);if(u&&Xe.isSelectable(s.content.firstChild)&&p.nodeAfter&&p.nodeAfter.sameMarkup(s.content.firstChild))h.setSelection(new Xe(p));else{let t=h.mapping.map(a);h.mapping.maps[h.mapping.maps.length-1].forEach(((e,n,r,i)=>t=i)),h.setSelection(qn(e,p,h.doc.resolve(t)))}e.focus(),e.dispatch(h.setMeta("uiEvent","drop"))},gr.focus=e=>{e.input.lastFocus=Date.now(),e.focused||(e.domObserver.stop(),e.dom.classList.add("ProseMirror-focused"),e.domObserver.start(),e.focused=!0,setTimeout((()=>{e.docView&&e.hasFocus()&&!e.domObserver.currentSelection.eq(e.domSelectionRange())&&Rn(e)}),20))},gr.blur=(e,t)=>{let n=t;e.focused&&(e.domObserver.stop(),e.dom.classList.remove("ProseMirror-focused"),e.domObserver.start(),n.relatedTarget&&e.dom.contains(n.relatedTarget)&&e.domObserver.currentSelection.clear(),e.focused=!1)},gr.beforeinput=(e,t)=>{if(Rt&&Ft&&"deleteContentBackward"==t.inputType){e.domObserver.flushSoon();let{domChangeCount:t}=e.input;setTimeout((()=>{if(e.input.domChangeCount!=t)return;if(e.dom.blur(),e.focus(),e.someProp("handleKeyDown",(t=>t(e,Ot(8,"Backspace")))))return;let{$cursor:n}=e.state.selection;n&&n.pos>0&&e.dispatch(e.state.tr.delete(n.pos-1,n.pos).scrollIntoView())}),50)}};for(let os in yr)gr[os]=yr[os];function Lr(e,t){if(e==t)return!0;for(let n in e)if(e[n]!==t[n])return!1;for(let n in t)if(!(n in e))return!1;return!0}class qr{constructor(e,t){this.toDOM=e,this.spec=t||Yr,this.side=this.spec.side||0}map(e,t,n,r){let{pos:i,deleted:o}=e.mapResult(t.from+r,this.side<0?-1:1);return o?null:new Kr(i-n,i-n,this)}valid(){return!0}eq(e){return this==e||e instanceof qr&&(this.spec.key&&this.spec.key==e.spec.key||this.toDOM==e.toDOM&&Lr(this.spec,e.spec))}destroy(e){this.spec.destroy&&this.spec.destroy(e)}}class Wr{constructor(e,t){this.attrs=e,this.spec=t||Yr}map(e,t,n,r){let i=e.map(t.from+r,this.spec.inclusiveStart?-1:1)-n,o=e.map(t.to+r,this.spec.inclusiveEnd?1:-1)-n;return i>=o?null:new Kr(i,o,this)}valid(e,t){return t.from=e&&(!i||i(s.spec))&&n.push(s.copy(s.from+r,s.to+r))}for(let o=0;oe){let s=this.children[o]+1;this.children[o+2].findInner(e-s,t-s,n,r+s,i)}}map(e,t,n){return this==Gr||0==e.maps.length?this:this.mapInner(e,t,0,0,n||Yr)}mapInner(e,t,n,r,i){let o;for(let s=0;s{let s=o-r-(n-t);for(let a=0;ao+h-e)continue;let c=l[a]+h-e;n>=c?l[a+1]=t<=c?-2:-1:r>=i&&s&&(l[a]+=s,l[a+1]+=s)}e+=s})),h=n.maps[c].map(h,-1)}let a=!1;for(let c=0;c=r.content.size){a=!0;continue}let d=n.map(e[c+1]+o,-1)-i,{index:u,offset:f}=r.content.findIndex(h),p=r.maybeChild(u);if(p&&f==h&&f+p.nodeSize==d){let r=l[c+2].mapInner(n,p,t+1,e[c]+o+1,s);r!=Gr?(l[c]=h,l[c+1]=d,l[c+2]=r):(l[c+1]=-2,a=!0)}else a=!0}if(a){let a=function(e,t,n,r,i,o,s){function l(e,t){for(let o=0;o{let s,l=o+n;if(s=Qr(t,e,l)){for(r||(r=this.children.slice());io&&t.to=e){this.children[s]==e&&(n=this.children[s+2]);break}let i=e+1,o=i+t.content.size;for(let s=0;si&&e.type instanceof Wr){let t=Math.max(i,e.from)-i,n=Math.min(o,e.to)-i;tn.map(e,t,Yr)));return Zr.from(n)}forChild(e,t){if(t.isLeaf)return Ur.empty;let n=[];for(let r=0;re instanceof Ur))?e:e.reduce(((e,t)=>e.concat(t instanceof Ur?t:t.members)),[]))}}}function Xr(e,t){if(!t||!e.length)return e;let n=[];for(let r=0;rn&&o.to{let l=Qr(e,t,s+n);if(l){o=!0;let e=ti(l,t,n+s+1,r);e!=Gr&&i.push(s,s+t.nodeSize,e)}}));let s=Xr(o?ei(e):e,-n).sort(ni);for(let l=0;l0;)t++;e.splice(t,0,n)}function oi(e){let t=[];return e.someProp("decorations",(n=>{let r=n(e.state);r&&r!=Gr&&t.push(r)})),e.cursorWrapper&&t.push(Ur.create(e.state.doc,[e.cursorWrapper.deco])),Zr.from(t)}const si={childList:!0,characterData:!0,characterDataOldValue:!0,attributes:!0,attributeOldValue:!0,subtree:!0},li=$t&&Pt<=11;class ai{constructor(){this.anchorNode=null,this.anchorOffset=0,this.focusNode=null,this.focusOffset=0}set(e){this.anchorNode=e.anchorNode,this.anchorOffset=e.anchorOffset,this.focusNode=e.focusNode,this.focusOffset=e.focusOffset}clear(){this.anchorNode=this.focusNode=null}eq(e){return e.anchorNode==this.anchorNode&&e.anchorOffset==this.anchorOffset&&e.focusNode==this.focusNode&&e.focusOffset==this.focusOffset}}class ci{constructor(e,t){this.view=e,this.handleDOMChange=t,this.queue=[],this.flushingSoon=-1,this.observer=null,this.currentSelection=new ai,this.onCharData=null,this.suppressingSelectionUpdates=!1,this.observer=window.MutationObserver&&new window.MutationObserver((e=>{for(let t=0;t"childList"==e.type&&e.removedNodes.length||"characterData"==e.type&&e.oldValue.length>e.target.nodeValue.length))?this.flushSoon():this.flush()})),li&&(this.onCharData=e=>{this.queue.push({target:e.target,type:"characterData",oldValue:e.prevValue}),this.flushSoon()}),this.onSelectionChange=this.onSelectionChange.bind(this)}flushSoon(){this.flushingSoon<0&&(this.flushingSoon=window.setTimeout((()=>{this.flushingSoon=-1,this.flush()}),20))}forceFlush(){this.flushingSoon>-1&&(window.clearTimeout(this.flushingSoon),this.flushingSoon=-1,this.flush())}start(){this.observer&&(this.observer.takeRecords(),this.observer.observe(this.view.dom,si)),this.onCharData&&this.view.dom.addEventListener("DOMCharacterDataModified",this.onCharData),this.connectSelection()}stop(){if(this.observer){let e=this.observer.takeRecords();if(e.length){for(let t=0;tthis.flush()),20)}this.observer.disconnect()}this.onCharData&&this.view.dom.removeEventListener("DOMCharacterDataModified",this.onCharData),this.disconnectSelection()}connectSelection(){this.view.dom.ownerDocument.addEventListener("selectionchange",this.onSelectionChange)}disconnectSelection(){this.view.dom.ownerDocument.removeEventListener("selectionchange",this.onSelectionChange)}suppressSelectionUpdates(){this.suppressingSelectionUpdates=!0,setTimeout((()=>this.suppressingSelectionUpdates=!1),50)}onSelectionChange(){if(Wn(this.view)){if(this.suppressingSelectionUpdates)return Rn(this.view);if($t&&Pt<=11&&!this.view.state.selection.empty){let e=this.view.domSelectionRange();if(e.focusNode&&wt(e.focusNode,e.focusOffset,e.anchorNode,e.anchorOffset))return this.flushSoon()}this.flush()}}setCurSelection(){this.currentSelection.set(this.view.domSelectionRange())}ignoreSelectionChange(e){if(!e.focusNode)return!0;let t,n=new Set;for(let i=e.focusNode;i;i=gt(i))n.add(i);for(let i=e.anchorNode;i;i=gt(i))if(n.has(i)){t=i;break}let r=t&&this.view.docView.nearestDesc(t);return r&&r.ignoreMutation({type:"selection",target:3==t.nodeType?t.parentNode:t})?(this.setCurSelection(),!0):void 0}flush(){let{view:e}=this;if(!e.docView||this.flushingSoon>-1)return;let t=this.observer?this.observer.takeRecords():[];this.queue.length&&(t=this.queue.concat(t),this.queue.length=0);let n=e.domSelectionRange(),r=!this.suppressingSelectionUpdates&&!this.currentSelection.eq(n)&&Wn(e)&&!this.ignoreSelectionChange(n),i=-1,o=-1,s=!1,l=[];if(e.editable)for(let c=0;c1){let e=l.filter((e=>"BR"==e.nodeName));if(2==e.length){let t=e[0],n=e[1];t.parentNode&&t.parentNode.parentNode==n.parentNode?n.remove():t.remove()}}let a=null;i<0&&r&&e.input.lastFocus>Date.now()-200&&Math.max(e.input.lastTouch,e.input.lastClick.time)-1||r)&&(i>-1&&(e.docView.markDirty(i,o),function(e){if(hi.has(e))return;if(hi.set(e,null),-1!==["normal","nowrap","pre-line"].indexOf(getComputedStyle(e.dom).whiteSpace)){if(e.requiresGeckoHackNode=It,di)return;console.warn("ProseMirror expects the CSS white-space property to be set, preferably to 'pre-wrap'. It is recommended to load style/prosemirror.css from the prosemirror-view package."),di=!0}}(e)),this.handleDOMChange(i,o,s,l),e.docView&&e.docView.dirty?e.updateState(e.state):this.currentSelection.eq(n)||Rn(e),this.currentSelection.set(n))}registerMutation(e,t){if(t.indexOf(e.target)>-1)return null;let n=this.view.docView.nearestDesc(e.target);if("attributes"==e.type&&(n==this.view.docView||"contenteditable"==e.attributeName||"style"==e.attributeName&&!e.oldValue&&!e.target.getAttribute("style")))return null;if(!n||n.ignoreMutation(e))return null;if("childList"==e.type){for(let n=0;nDate.now()-50?e.input.lastSelectionOrigin:null,n=In(e,t);if(n&&!e.state.selection.eq(n)){if(Rt&&Ft&&13===e.input.lastKeyCode&&Date.now()-100t(e,Ot(13,"Enter")))))return;let r=e.state.tr.setSelection(n);"pointer"==t?r.setMeta("pointer",!0):"key"==t&&r.scrollIntoView(),e.dispatch(r)}return}let s=e.state.doc.resolve(t),l=s.sharedDepth(n);t=s.before(l+1),n=e.state.doc.resolve(n).after(l+1);let a,c,h=e.state.selection,d=function(e,t,n){let r,{node:i,fromOffset:o,toOffset:s,from:l,to:a}=e.docView.parseRange(t,n),c=e.domSelectionRange(),h=c.anchorNode;if(h&&e.dom.contains(1==h.nodeType?h:h.parentNode)&&(r=[{node:h,offset:c.anchorOffset}],Mt(c)||r.push({node:c.focusNode,offset:c.focusOffset})),Rt&&8===e.input.lastKeyCode)for(let g=s;g>o;g--){let e=i.childNodes[g-1],t=e.pmViewDesc;if("BR"==e.nodeName&&!t){s=g;break}if(!t||t.size)break}let d=e.state.doc,u=e.someProp("domParser")||H.fromSchema(e.state.schema),f=d.resolve(l),p=null,m=u.parse(i,{topNode:f.parent,topMatch:f.parent.contentMatchAt(f.index()),topOpen:!0,from:o,to:s,preserveWhitespace:"pre"!=f.parent.type.whitespace||"full",findPositions:r,ruleFromNode:ui,context:f});if(r&&null!=r[0].pos){let e=r[0].pos,t=r[1]&&r[1].pos;null==t&&(t=e),p={anchor:e+l,head:t+l}}return{doc:m,sel:p,from:l,to:a}}(e,t,n),u=e.state.doc,f=u.slice(d.from,d.to);8===e.input.lastKeyCode&&Date.now()-100=s?o-r:0,l=o+(l-s),s=o}else if(l=l?o-r:0,s=o+(s-l),l=o}return{start:o,endA:s,endB:l}}(f.content,d.doc.content,d.from,a,c);if((Vt&&e.input.lastIOSEnter>Date.now()-225||Ft)&&o.some((e=>1==e.nodeType&&!fi.test(e.nodeName)))&&(!p||p.endA>=p.endB)&&e.someProp("handleKeyDown",(t=>t(e,Ot(13,"Enter")))))return void(e.input.lastIOSEnter=0);if(!p){if(!(i&&h instanceof Ge&&!h.empty&&h.$head.sameParent(h.$anchor))||e.composing||d.sel&&d.sel.anchor!=d.sel.head){if(d.sel){let t=mi(e,e.state.doc,d.sel);t&&!t.eq(e.state.selection)&&e.dispatch(e.state.tr.setSelection(t))}return}p={start:h.from,endA:h.to,endB:h.to}}if(Rt&&e.cursorWrapper&&d.sel&&d.sel.anchor==e.cursorWrapper.deco.from&&d.sel.head==d.sel.anchor){let e=p.endB-p.start;d.sel={anchor:d.sel.anchor+e,head:d.sel.anchor+e}}e.input.domChangeCount++,e.state.selection.frome.state.selection.from&&p.start<=e.state.selection.from+2&&e.state.selection.from>=d.from?p.start=e.state.selection.from:p.endA=e.state.selection.to-2&&e.state.selection.to<=d.to&&(p.endB+=e.state.selection.to-p.endA,p.endA=e.state.selection.to)),$t&&Pt<=11&&p.endB==p.start+1&&p.endA==p.start&&p.start>d.from&&"  "==d.doc.textBetween(p.start-d.from-1,p.start-d.from+1)&&(p.start--,p.endA--,p.endB--);let m,g=d.doc.resolveNoCache(p.start-d.from),y=d.doc.resolveNoCache(p.endB-d.from),v=u.resolve(p.start),w=g.sameParent(y)&&g.parent.inlineContent&&v.end()>=p.endA;if((Vt&&e.input.lastIOSEnter>Date.now()-225&&(!w||o.some((e=>"DIV"==e.nodeName||"P"==e.nodeName)))||!w&&g.post(e,Ot(13,"Enter")))))return void(e.input.lastIOSEnter=0);if(e.state.selection.anchor>p.start&&function(e,t,n,r,i){if(!r.parent.isTextblock||n-t<=i.pos-r.pos||gi(r,!0,!1)n||gi(s,!0,!1)t(e,Ot(8,"Backspace")))))return void(Ft&&Rt&&e.domObserver.suppressSelectionUpdates());Rt&&Ft&&p.endB==p.start&&(e.input.lastAndroidDelete=Date.now()),Ft&&!w&&g.start()!=y.start()&&0==y.parentOffset&&g.depth==y.depth&&d.sel&&d.sel.anchor==d.sel.head&&d.sel.head==p.endA&&(p.endB-=2,y=d.doc.resolveNoCache(p.endB-d.from),setTimeout((()=>{e.someProp("handleKeyDown",(function(t){return t(e,Ot(13,"Enter"))}))}),20));let b,x,S,k=p.start,M=p.endA;if(w)if(g.pos==y.pos)$t&&Pt<=11&&0==g.parentOffset&&(e.domObserver.suppressSelectionUpdates(),setTimeout((()=>Rn(e)),20)),b=e.state.tr.delete(k,M),x=u.resolve(p.start).marksAcross(u.resolve(p.endA));else if(p.endA==p.endB&&(S=function(e,t){let n,i,o,s=e.firstChild.marks,l=t.firstChild.marks,a=s,c=l;for(let r=0;re.mark(i.addToSet(e.marks));else{if(0!=a.length||1!=c.length)return null;i=c[0],n="remove",o=e=>e.mark(i.removeFromSet(e.marks))}let h=[];for(let r=0;rn(e,k,M,t))))return;b=e.state.tr.insertText(t,k,M)}if(b||(b=e.state.tr.replace(k,M,d.doc.slice(p.start-d.from,p.endB-d.from))),d.sel){let t=mi(e,b.doc,d.sel);t&&!(Rt&&Ft&&e.composing&&t.empty&&(p.start!=p.endB||e.input.lastAndroidDeletet.content.size?null:qn(e,t.resolve(n.anchor),t.resolve(n.head))}function gi(e,t,n){let r=e.depth,i=t?e.end():e.pos;for(;r>0&&(t||e.indexAfter(r)==e.node(r).childCount);)r--,i++,t=!1;if(n){let t=e.node(r).maybeChild(e.indexAfter(r));for(;t&&!t.isLeaf;)t=t.firstChild,i++}return i}class yi{constructor(e,t){this._root=null,this.focused=!1,this.trackWrites=null,this.mounted=!1,this.markCursor=null,this.cursorWrapper=null,this.lastSelectedViewDesc=void 0,this.input=new wr,this.prevDirectPlugins=[],this.pluginViews=[],this.requiresGeckoHackNode=!1,this.dragging=null,this._props=t,this.state=t.state,this.directPlugins=t.plugins||[],this.directPlugins.forEach(Si),this.dispatch=this.dispatch.bind(this),this.dom=e&&e.mount||document.createElement("div"),e&&(e.appendChild?e.appendChild(this.dom):"function"==typeof e?e(this.dom):e.mount&&(this.mounted=!0)),this.editable=bi(this),wi(this),this.nodeViews=xi(this),this.docView=vn(this.state.doc,vi(this),oi(this),this.dom,this),this.domObserver=new ci(this,((e,t,n,r)=>pi(this,e,t,n,r))),this.domObserver.start(),function(e){for(let t in gr){let n=gr[t];e.dom.addEventListener(t,e.input.eventHandlers[t]=t=>{!kr(e,t)||Sr(e,t)||!e.editable&&t.type in yr||n(e,t)},vr[t]?{passive:!0}:void 0)}Bt&&e.dom.addEventListener("input",(()=>null)),xr(e)}(this),this.updatePluginViews()}get composing(){return this.input.composing}get props(){if(this._props.state!=this.state){let e=this._props;this._props={};for(let t in e)this._props[t]=e[t];this._props.state=this.state}return this._props}update(e){e.handleDOMEvents!=this._props.handleDOMEvents&&xr(this);let t=this._props;this._props=e,e.plugins&&(e.plugins.forEach(Si),this.directPlugins=e.plugins),this.updateStateInner(e.state,t)}setProps(e){let t={};for(let n in this._props)t[n]=this._props[n];t.state=this.state;for(let n in e)t[n]=e[n];this.update(t)}updateState(e){this.updateStateInner(e,this._props)}updateStateInner(e,t){let n=this.state,r=!1,i=!1;e.storedMarks&&this.composing&&(Rr(this),i=!0),this.state=e;let o=n.plugins!=e.plugins||this._props.plugins!=t.plugins;if(o||this._props.plugins!=t.plugins||this._props.nodeViews!=t.nodeViews){let e=xi(this);(function(e,t){let n=0,r=0;for(let i in e){if(e[i]!=t[i])return!0;n++}for(let i in t)r++;return n!=r})(e,this.nodeViews)&&(this.nodeViews=e,r=!0)}(o||t.handleDOMEvents!=this._props.handleDOMEvents)&&xr(this),this.editable=bi(this),wi(this);let s=oi(this),l=vi(this),a=n.plugins==e.plugins||n.doc.eq(e.doc)?e.scrollToSelection>n.scrollToSelection?"to selection":"preserve":"reset",c=r||!this.docView.matchesNode(e.doc,l,s);!c&&e.selection.eq(n.selection)||(i=!0);let h="preserve"==a&&i&&null==this.dom.style.overflowAnchor&&function(e){let t,n,r=e.dom.getBoundingClientRect(),i=Math.max(0,r.top);for(let o=(r.left+r.right)/2,s=i+1;s=i-20){t=r,n=l.top;break}}return{refDOM:t,refTop:n,stack:Yt(e.dom)}}(this);if(i){this.domObserver.stop();let t=c&&($t||Rt)&&!this.composing&&!n.selection.empty&&!e.selection.empty&&function(e,t){let n=Math.min(e.$anchor.sharedDepth(e.head),t.$anchor.sharedDepth(t.head));return e.$anchor.start(n)!=t.$anchor.start(n)}(n.selection,e.selection);if(c){let n=Rt?this.trackWrites=this.domSelectionRange().focusNode:null;!r&&this.docView.update(e.doc,l,s,this)||(this.docView.updateOuterDeco([]),this.docView.destroy(),this.docView=vn(e.doc,l,s,this.dom,this)),n&&!this.trackWrites&&(t=!0)}t||!(this.input.mouseDown&&this.domObserver.currentSelection.eq(this.domSelectionRange())&&function(e){let t=e.docView.domFromPos(e.state.selection.anchor,0),n=e.domSelectionRange();return wt(t.node,t.offset,n.anchorNode,n.anchorOffset)}(this))?Rn(this,t):(Fn(this,e.selection),this.domObserver.setCurSelection()),this.domObserver.start()}this.updatePluginViews(n),"reset"==a?this.dom.scrollTop=0:"to selection"==a?this.scrollToSelection():h&&function({refDOM:e,refTop:t,stack:n}){let r=e?e.getBoundingClientRect().top:0;Ut(n,0==r?0:r-t)}(h)}scrollToSelection(){let e=this.domSelectionRange().focusNode;if(this.someProp("handleScrollToSelection",(e=>e(this))));else if(this.state.selection instanceof Xe){let t=this.docView.domAfterPos(this.state.selection.from);1==t.nodeType&&Ht(this,t.getBoundingClientRect(),e)}else Ht(this,this.coordsAtPos(this.state.selection.head,1),e)}destroyPluginViews(){let e;for(;e=this.pluginViews.pop();)e.destroy&&e.destroy()}updatePluginViews(e){if(e&&e.plugins==this.state.plugins&&this.directPlugins==this.prevDirectPlugins)for(let t=0;tt.ownerDocument.getSelection()),this._root=t;return e||document}posAtCoords(e){return en(this,e)}coordsAtPos(e,t=1){return rn(this,e,t)}domAtPos(e,t=0){return this.docView.domFromPos(e,t)}nodeDOM(e){let t=this.docView.descAt(e);return t?t.nodeDOM:null}posAtDOM(e,t,n=-1){let r=this.docView.posFromDOM(e,t,n);if(null==r)throw new RangeError("DOM position not inside the editor");return r}endOfTextblock(e,t){return un(this,t||this.state,e)}pasteHTML(e,t){return Vr(this,"",e,!1,t||new ClipboardEvent("paste"))}pasteText(e,t){return Vr(this,e,null,!0,t||new ClipboardEvent("paste"))}destroy(){this.docView&&(!function(e){e.domObserver.stop();for(let t in e.input.eventHandlers)e.dom.removeEventListener(t,e.input.eventHandlers[t]);clearTimeout(e.input.composingTimeout),clearTimeout(e.input.lastIOSEnterFallbackTimeout)}(this),this.destroyPluginViews(),this.mounted?(this.docView.update(this.state.doc,[],oi(this),this),this.dom.textContent=""):this.dom.parentNode&&this.dom.parentNode.removeChild(this.dom),this.docView.destroy(),this.docView=null)}get isDestroyed(){return null==this.docView}dispatchEvent(e){return function(e,t){Sr(e,t)||!gr[t.type]||!e.editable&&t.type in yr||gr[t.type](e,t)}(this,e)}dispatch(e){let t=this._props.dispatchTransaction;t?t.call(this,e):this.updateState(this.state.apply(e))}domSelectionRange(){return Bt&&11===this.root.nodeType&&function(e){let t=e.activeElement;for(;t&&t.shadowRoot;)t=t.shadowRoot.activeElement;return t}(this.dom.ownerDocument)==this.dom?function(e){let t;function n(e){e.preventDefault(),e.stopImmediatePropagation(),t=e.getTargetRanges()[0]}e.dom.addEventListener("beforeinput",n,!0),document.execCommand("indent"),e.dom.removeEventListener("beforeinput",n,!0);let r=t.startContainer,i=t.startOffset,o=t.endContainer,s=t.endOffset,l=e.domAtPos(e.state.selection.anchor);return wt(l.node,l.offset,o,s)&&([r,i,o,s]=[o,s,r,i]),{anchorNode:r,anchorOffset:i,focusNode:o,focusOffset:s}}(this):this.domSelection()}domSelection(){return this.root.getSelection()}}function vi(e){let t=Object.create(null);return t.class="ProseMirror",t.contenteditable=String(e.editable),t.translate="no",e.someProp("attributes",(n=>{if("function"==typeof n&&(n=n(e.state)),n)for(let e in n)"class"==e&&(t.class+=" "+n[e]),"style"==e?t.style=(t.style?t.style+";":"")+n[e]:t[e]||"contenteditable"==e||"nodeName"==e||(t[e]=String(n[e]))})),[Kr.node(0,e.state.doc.content.size,t)]}function wi(e){if(e.markCursor){let t=document.createElement("img");t.className="ProseMirror-separator",t.setAttribute("mark-placeholder","true"),t.setAttribute("alt",""),e.cursorWrapper={dom:t,deco:Kr.widget(e.state.selection.head,t,{raw:!0,marks:e.markCursor})}}else e.cursorWrapper=null}function bi(e){return!e.someProp("editable",(t=>!1===t(e.state)))}function xi(e){let t=Object.create(null);function n(e){for(let n in e)Object.prototype.hasOwnProperty.call(t,n)||(t[n]=e[n])}return e.someProp("nodeViews",n),e.someProp("markViews",n),t}function Si(e){if(e.spec.state||e.spec.filterTransaction||e.spec.appendTransaction)throw new RangeError("Plugins passed directly to the view must not have a state component")}for(var ki={8:"Backspace",9:"Tab",10:"Enter",12:"NumLock",13:"Enter",16:"Shift",17:"Control",18:"Alt",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",44:"PrintScreen",45:"Insert",46:"Delete",59:";",61:"=",91:"Meta",92:"Meta",106:"*",107:"+",108:",",109:"-",110:".",111:"/",144:"NumLock",145:"ScrollLock",160:"Shift",161:"Shift",162:"Control",163:"Control",164:"Alt",165:"Alt",173:"-",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},Mi={48:")",49:"!",50:"@",51:"#",52:"$",53:"%",54:"^",55:"&",56:"*",57:"(",59:":",61:"+",173:"_",186:":",187:"+",188:"<",189:"_",190:">",191:"?",192:"~",219:"{",220:"|",221:"}",222:'"'},Oi="undefined"!=typeof navigator&&/Chrome\/(\d+)/.exec(navigator.userAgent),Ci="undefined"!=typeof navigator&&/Mac/.test(navigator.platform),Ni="undefined"!=typeof navigator&&/MSIE \d|Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent),Di=Ci||Oi&&+Oi[1]<57,Ti=0;Ti<10;Ti++)ki[48+Ti]=ki[96+Ti]=String(Ti);for(Ti=1;Ti<=24;Ti++)ki[Ti+111]="F"+Ti;for(Ti=65;Ti<=90;Ti++)ki[Ti]=String.fromCharCode(Ti+32),Mi[Ti]=String.fromCharCode(Ti);for(var Ei in ki)Mi.hasOwnProperty(Ei)||(Mi[Ei]=ki[Ei]);const Ai="undefined"!=typeof navigator&&/Mac|iP(hone|[oa]d)/.test(navigator.platform);function $i(e){let t,n,r,i,o=e.split(/-(?!$)/),s=o[o.length-1];"Space"==s&&(s=" ");for(let l=0;l127)&&(r=ki[n.keyCode])&&r!=i){let i=t[Pi(r,n)];if(i&&i(e.state,e.dispatch,e))return!0}}return!1}}const Ri=(e,t)=>!e.selection.empty&&(t&&t(e.tr.deleteSelection().scrollIntoView()),!0);function _i(e,t,n=!1){for(let r=e;r;r="start"==t?r.firstChild:r.lastChild){if(r.isTextblock)return!0;if(n&&1!=r.childCount)return!1}return!1}function Bi(e){if(!e.parent.type.spec.isolating)for(let t=e.depth-1;t>=0;t--){if(e.index(t)>0)return e.doc.resolve(e.before(t+1));if(e.node(t).type.spec.isolating)break}return null}function Vi(e){if(!e.parent.type.spec.isolating)for(let t=e.depth-1;t>=0;t--){let n=e.node(t);if(e.index(t)+1{let{$head:n,$anchor:r}=e.selection;if(!n.parent.type.spec.code||!n.sameParent(r))return!1;let i=n.node(-1),o=n.indexAfter(-1),s=ji(i.contentMatchAt(o));if(!s||!i.canReplaceWith(o,o,s))return!1;if(t){let r=n.after(),i=e.tr.replaceWith(r,r,s.createAndFill());i.setSelection(Ke.near(i.doc.resolve(r),1)),t(i.scrollIntoView())}return!0};const Li=(e,t)=>{let{$from:n,$to:r}=e.selection;if(e.selection instanceof Xe&&e.selection.node.isBlock)return!(!n.parentOffset||!Te(e.doc,n.pos)||(t&&t(e.tr.split(n.pos).scrollIntoView()),0));if(!n.parent.isBlock)return!1;if(t){let i=r.parentOffset==r.parent.content.size,o=e.tr;(e.selection instanceof Ge||e.selection instanceof et)&&o.deleteSelection();let s=0==n.depth?null:ji(n.node(-1).contentMatchAt(n.indexAfter(-1))),l=qi&&qi(r.parent,i),a=l?[l]:i&&s?[{type:s}]:void 0,c=Te(o.doc,o.mapping.map(n.pos),1,a);if(a||c||!Te(o.doc,o.mapping.map(n.pos),1,s?[{type:s}]:void 0)||(s&&(a=[{type:s}]),c=!0),c&&(o.split(o.mapping.map(n.pos),1,a),!i&&!n.parentOffset&&n.parent.type!=s)){let e=o.mapping.map(n.before()),t=o.doc.resolve(e);s&&n.node(-1).canReplaceWith(t.index(),t.index()+1,s)&&o.setNodeMarkup(o.mapping.map(n.before()),s)}t(o.scrollIntoView())}return!0};var qi;function Wi(e,t,n){let i,o,s=t.nodeBefore,l=t.nodeAfter;if(s.type.spec.isolating||l.type.spec.isolating)return!1;if(function(e,t,n){let r=t.nodeBefore,i=t.nodeAfter,o=t.index();return!(!(r&&i&&r.type.compatibleContent(i.type))||(!r.content.size&&t.parent.canReplace(o-1,o)?(n&&n(e.tr.delete(t.pos-r.nodeSize,t.pos).scrollIntoView()),0):!t.parent.canReplace(o,o+1)||!i.isTextblock&&!Ee(e.doc,t.pos)||(n&&n(e.tr.clearIncompatible(t.pos,r.type,r.contentMatchAt(r.childCount)).join(t.pos).scrollIntoView()),0)))}(e,t,n))return!0;let a=t.parent.canReplace(t.index(),t.index()+1);if(a&&(i=(o=s.contentMatchAt(s.childCount)).findWrapping(l.type))&&o.matchType(i[0]||l.type).validEnd){if(n){let o=t.pos+l.nodeSize,a=r.empty;for(let e=i.length-1;e>=0;e--)a=r.from(i[e].create(null,a));a=r.from(s.copy(a));let h=e.tr.step(new ke(t.pos-1,o,t.pos,o,new c(a,1,0),i.length,!0)),d=o+2*i.length;Ee(h.doc,d)&&h.join(d),n(h.scrollIntoView())}return!0}let h=Ke.findFrom(t,1),d=h&&h.$from.blockRange(h.$to),u=d&&Ce(d);if(null!=u&&u>=t.depth)return n&&n(e.tr.lift(d,u).scrollIntoView()),!0;if(a&&_i(l,"start",!0)&&_i(s,"end")){let i=s,o=[];for(;o.push(i),!i.isTextblock;)i=i.lastChild;let a=l,h=1;for(;!a.isTextblock;a=a.firstChild)h++;if(i.canReplace(i.childCount,i.childCount,a.content)){if(n){let i=r.empty;for(let e=o.length-1;e>=0;e--)i=r.from(o[e].copy(i));n(e.tr.step(new ke(t.pos-o.length,t.pos+l.nodeSize,t.pos+h,t.pos+l.nodeSize-h,new c(i,o.length,0),0,!0)).scrollIntoView())}return!0}}return!1}function Ji(e){return function(t,n){let r=t.selection,i=e<0?r.$from:r.$to,o=i.depth;for(;i.node(o).isInline;){if(!o)return!1;o--}return!!i.node(o).isTextblock&&(n&&n(t.tr.setSelection(Ge.create(t.doc,e<0?i.start(o):i.end(o)))),!0)}}const Ki=Ji(-1),Hi=Ji(1);function Yi(e,t=null){return function(n,r){let i=!1;for(let o=0;o{if(i)return!1;if(r.isTextblock&&!r.hasMarkup(e,t))if(r.type==e)i=!0;else{let t=n.doc.resolve(o),r=t.index();i=t.parent.canReplaceWith(r,r+1,e)}}))}if(!i)return!1;if(r){let i=n.tr;for(let r=0;r{if(s)return!1;s=e.inlineContent&&e.type.allowsMarkType(n)})),s)return!0}return!1}(n.doc,s,e))return!1;if(r)if(o)e.isInSet(n.storedMarks||o.marks())?r(n.tr.removeStoredMark(e)):r(n.tr.addStoredMark(e.create(t)));else{let i=!1,o=n.tr;for(let t=0;!i&&t{let r=function(e,t){let{$cursor:n}=e.selection;return!n||(t?!t.endOfTextblock("backward",e):n.parentOffset>0)?null:n}(e,n);if(!r)return!1;let i=Bi(r);if(!i){let n=r.blockRange(),i=n&&Ce(n);return null!=i&&(t&&t(e.tr.lift(n,i).scrollIntoView()),!0)}let o=i.nodeBefore;if(!o.type.spec.isolating&&Wi(e,i,t))return!0;if(0==r.parent.content.size&&(_i(o,"end")||Xe.isSelectable(o))){let n=Ae(e.doc,r.before(),r.after(),c.empty);if(n&&n.slice.size{let{$head:r,empty:i}=e.selection,o=r;if(!i)return!1;if(r.parent.isTextblock){if(n?!n.endOfTextblock("backward",e):r.parentOffset>0)return!1;o=Bi(r)}let s=o&&o.nodeBefore;return!(!s||!Xe.isSelectable(s))&&(t&&t(e.tr.setSelection(Xe.create(e.doc,o.pos-s.nodeSize)).scrollIntoView()),!0)})),Xi=Gi(Ri,((e,t,n)=>{let r=function(e,t){let{$cursor:n}=e.selection;return!n||(t?!t.endOfTextblock("forward",e):n.parentOffset{let{$head:r,empty:i}=e.selection,o=r;if(!i)return!1;if(r.parent.isTextblock){if(n?!n.endOfTextblock("forward",e):r.parentOffset{let{$head:n,$anchor:r}=e.selection;return!(!n.parent.type.spec.code||!n.sameParent(r))&&(t&&t(e.tr.insertText("\n").scrollIntoView()),!0)}),((e,t)=>{let n=e.selection,{$from:r,$to:i}=n;if(n instanceof et||r.parent.inlineContent||i.parent.inlineContent)return!1;let o=ji(i.parent.contentMatchAt(i.indexAfter()));if(!o||!o.isTextblock)return!1;if(t){let n=(!r.parentOffset&&i.index(){let{$cursor:n}=e.selection;if(!n||n.parent.content.size)return!1;if(n.depth>1&&n.after()!=n.end(-1)){let r=n.before();if(Te(e.doc,r))return t&&t(e.tr.split(r).scrollIntoView()),!0}let r=n.blockRange(),i=r&&Ce(r);return null!=i&&(t&&t(e.tr.lift(r,i).scrollIntoView()),!0)}),Li),"Mod-Enter":Fi,Backspace:Zi,"Mod-Backspace":Zi,"Shift-Backspace":Zi,Delete:Xi,"Mod-Delete":Xi,"Mod-a":(e,t)=>(t&&t(e.tr.setSelection(new et(e.doc))),!0)},eo={"Ctrl-h":Qi.Backspace,"Alt-Backspace":Qi["Mod-Backspace"],"Ctrl-d":Qi.Delete,"Ctrl-Alt-Backspace":Qi["Mod-Delete"],"Alt-Delete":Qi["Mod-Delete"],"Alt-d":Qi["Mod-Delete"],"Ctrl-a":Ki,"Ctrl-e":Hi};for(let os in Qi)eo[os]=Qi[os];const to=("undefined"!=typeof navigator?/Mac|iP(hone|[oa]d)/.test(navigator.platform):!("undefined"==typeof os||!os.platform)&&"darwin"==os.platform())?eo:Qi;class no{constructor(e,t){var n;this.match=e,this.match=e,this.handler="string"==typeof t?(n=t,function(e,t,r,i){let o=n;if(t[1]){let e=t[0].lastIndexOf(t[1]);o+=t[0].slice(e+t[1].length);let n=(r+=e)-i;n>0&&(o=t[0].slice(e-n,e)+o,r=i)}return e.tr.insertText(o,r,i)}):t}}const ro=500;function io({rules:e}){let t=new dt({state:{init:()=>null,apply(e,t){let n=e.getMeta(this);return n||(e.selectionSet||e.docChanged?null:t)}},props:{handleTextInput:(n,r,i,o)=>oo(n,r,i,o,e,t),handleDOMEvents:{compositionend:n=>{setTimeout((()=>{let{$cursor:r}=n.state.selection;r&&oo(n,r.pos,r.pos,"",e,t)}))}}},isInputRules:!0});return t}function oo(e,t,n,r,i,o){if(e.composing)return!1;let s=e.state,l=s.doc.resolve(t);if(l.parent.type.spec.code)return!1;let a=l.parent.textBetween(Math.max(0,l.parentOffset-ro),l.parentOffset,null,"")+r;for(let c=0;c{let n=e.plugins;for(let r=0;r=0;e--)n.step(r.steps[e].invert(r.docs[e]));if(i.text){let t=n.doc.resolve(i.from).marks();n.replaceWith(i.from,i.to,e.schema.text(i.text,t))}else n.delete(i.from,i.to);t(n)}return!0}}return!1};function lo(e,t,n=null,r){return new no(e,((e,i,o,s)=>{let l=n instanceof Function?n(i):n,a=e.tr.delete(o,s),c=a.doc.resolve(o).blockRange(),h=c&&Ne(c,t,l);if(!h)return null;a.wrap(c,h);let d=a.doc.resolve(o-1).nodeBefore;return d&&d.type==t&&Ee(a.doc,o-1)&&(!r||r(i,d))&&a.join(o-1),a}))}function ao(e,t,n=null){return new no(e,((e,r,i,o)=>{let s=e.doc.resolve(i),l=n instanceof Function?n(r):n;return s.node(-1).canReplaceWith(s.index(-1),s.indexAfter(-1),t)?e.tr.delete(i,o).setBlockType(i,i,t,l):null}))}const co=["ol",0],ho=["ul",0],uo=["li",0],fo={attrs:{order:{default:1}},parseDOM:[{tag:"ol",getAttrs:e=>({order:e.hasAttribute("start")?+e.getAttribute("start"):1})}],toDOM:e=>1==e.attrs.order?co:["ol",{start:e.attrs.order},0]},po={parseDOM:[{tag:"ul"}],toDOM:()=>ho},mo={parseDOM:[{tag:"li"}],toDOM:()=>uo,defining:!0};function go(e,t){let n={};for(let r in e)n[r]=e[r];for(let r in t)n[r]=t[r];return n}function yo(e,t,n){return e.append({ordered_list:go(fo,{content:"list_item+",group:n}),bullet_list:go(po,{content:"list_item+",group:n}),list_item:go(mo,{content:t})})}function vo(e,t=null){return function(n,i){let{$from:o,$to:s}=n.selection,l=o.blockRange(s),a=!1,h=l;if(!l)return!1;if(l.depth>=2&&o.node(l.depth-1).type.compatibleContent(e)&&0==l.startIndex){if(0==o.index(l.depth-1))return!1;let e=n.doc.resolve(l.start-2);h=new O(e,e,l.depth),l.endIndex=0;c--)s=r.from(n[c].type.create(n[c].attrs,s));e.step(new ke(t.start-(i?2:0),t.end,t.start,t.end,new c(s,0,0),n.length,!0));let l=0;for(let r=0;r=i.depth-3;e--)o=r.from(i.node(e).copy(o));let l=i.indexAfter(-1){if(d>-1)return!1;e.isTextblock&&0==e.content.size&&(d=t+1)})),d>-1&&h.setSelection(Ke.near(h.doc.resolve(d))),n(h.scrollIntoView())}return!0}let a=o.pos==i.end()?l.contentMatchAt(0).defaultType:null,h=t.tr.delete(i.pos,o.pos),d=a?[null,{type:a}]:void 0;return!!Te(h.doc,i.pos,2,d)&&(n&&n(h.split(i.pos,2,d).scrollIntoView()),!0)}}function bo(e){return function(t,n){let{$from:i,$to:o}=t.selection,s=i.blockRange(o,(t=>t.childCount>0&&t.firstChild.type==e));return!!s&&(!n||(i.node(s.depth-1).type==e?function(e,t,n,i){let o=e.tr,s=i.end,l=i.$to.end(i.depth);sm;c--)r-=o.child(c).nodeSize,i.delete(r-1,r+1);let s=i.doc.resolve(n.start),l=s.nodeAfter;if(i.mapping.map(n.end)!=n.start+s.nodeAfter.nodeSize)return!1;let a=0==n.startIndex,h=n.endIndex==o.childCount,d=s.node(-1),u=s.index(-1);if(!d.canReplace(u+(a?0:1),u+1,l.content.append(h?r.empty:r.from(o))))return!1;let f=s.pos,p=f+l.nodeSize;return i.step(new ke(f-(a?1:0),p+(h?1:0),f+1,p-1,new c((a?r.empty:r.from(o.copy(r.empty))).append(h?r.empty:r.from(o.copy(r.empty))),a?0:1,h?0:1),a?0:1)),t(i.scrollIntoView()),!0}(t,n,s)))}}function xo(e){return function(t,n){let{$from:i,$to:o}=t.selection,s=i.blockRange(o,(t=>t.childCount>0&&t.firstChild.type==e));if(!s)return!1;let l=s.startIndex;if(0==l)return!1;let a=s.parent,h=a.child(l-1);if(h.type!=e)return!1;if(n){let i=h.lastChild&&h.lastChild.type==a.type,o=r.from(i?e.create():null),l=new c(r.from(e.create(null,r.from(a.type.create(null,o)))),i?3:1,0),d=s.start,u=s.end;n(t.tr.step(new ke(d-(i?3:1),u,d,u,l,1,!0)).scrollIntoView())}return!0}}var So=200,ko=function(){};ko.prototype.append=function(e){return e.length?(e=ko.from(e),!this.length&&e||e.length=t?ko.empty:this.sliceInner(Math.max(0,e),Math.min(this.length,t))},ko.prototype.get=function(e){if(!(e<0||e>=this.length))return this.getInner(e)},ko.prototype.forEach=function(e,t,n){void 0===t&&(t=0),void 0===n&&(n=this.length),t<=n?this.forEachInner(e,t,n,0):this.forEachInvertedInner(e,t,n,0)},ko.prototype.map=function(e,t,n){void 0===t&&(t=0),void 0===n&&(n=this.length);var r=[];return this.forEach((function(t,n){return r.push(e(t,n))}),t,n),r},ko.from=function(e){return e instanceof ko?e:e&&e.length?new Mo(e):ko.empty};var Mo=function(e){function t(t){e.call(this),this.values=t}e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t;var n={length:{configurable:!0},depth:{configurable:!0}};return t.prototype.flatten=function(){return this.values},t.prototype.sliceInner=function(e,n){return 0==e&&n==this.length?this:new t(this.values.slice(e,n))},t.prototype.getInner=function(e){return this.values[e]},t.prototype.forEachInner=function(e,t,n,r){for(var i=t;i=n;i--)if(!1===e(this.values[i],r+i))return!1},t.prototype.leafAppend=function(e){if(this.length+e.length<=So)return new t(this.values.concat(e.flatten()))},t.prototype.leafPrepend=function(e){if(this.length+e.length<=So)return new t(e.flatten().concat(this.values))},n.length.get=function(){return this.values.length},n.depth.get=function(){return 0},Object.defineProperties(t.prototype,n),t}(ko);ko.empty=new Mo([]);var Oo=function(e){function t(t,n){e.call(this),this.left=t,this.right=n,this.length=t.length+n.length,this.depth=Math.max(t.depth,n.depth)+1}return e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t,t.prototype.flatten=function(){return this.left.flatten().concat(this.right.flatten())},t.prototype.getInner=function(e){return ei&&!1===this.right.forEachInner(e,Math.max(t-i,0),Math.min(this.length,n)-i,r+i))&&void 0)},t.prototype.forEachInvertedInner=function(e,t,n,r){var i=this.left.length;return!(t>i&&!1===this.right.forEachInvertedInner(e,t-i,Math.max(n,i)-i,r+i))&&(!(n=n?this.right.slice(e-n,t-n):this.left.slice(e,n).append(this.right.slice(0,t-n))},t.prototype.leafAppend=function(e){var n=this.right.leafAppend(e);if(n)return new t(this.left,n)},t.prototype.leafPrepend=function(e){var n=this.left.leafPrepend(e);if(n)return new t(n,this.right)},t.prototype.appendInner=function(e){return this.left.depth>=Math.max(this.right.depth,e.depth)+1?new t(this.left,new t(this.right,e)):new t(this,e)},t}(ko),Co=ko;class No{constructor(e,t){this.items=e,this.eventCount=t}popEvent(e,t){if(0==this.eventCount)return null;let n,r,i=this.items.length;for(;;i--){if(this.items.get(i-1).selection){--i;break}}t&&(n=this.remapping(i,this.items.length),r=n.maps.length);let o,s,l=e.tr,a=[],c=[];return this.items.forEach(((e,t)=>{if(!e.step)return n||(n=this.remapping(i,t+1),r=n.maps.length),r--,void c.push(e);if(n){c.push(new Do(e.map));let t,i=e.step.map(n.slice(r));i&&l.maybeStep(i).doc&&(t=l.mapping.maps[l.mapping.maps.length-1],a.push(new Do(t,void 0,void 0,a.length+c.length))),r--,t&&n.appendMap(t,r)}else l.maybeStep(e.step);return e.selection?(o=n?e.selection.map(n.slice(r)):e.selection,s=new No(this.items.slice(0,i).append(c.reverse().concat(a)),this.eventCount-1),!1):void 0}),this.items.length,0),{remaining:s,transform:l,selection:o}}addTransform(e,t,n,r){let i=[],o=this.eventCount,s=this.items,l=!r&&s.length?s.get(s.length-1):null;for(let c=0;cEo&&(s=function(e,t){let n;return e.forEach(((e,r)=>{if(e.selection&&0==t--)return n=r,!1})),e.slice(n)}(s,a),o-=a),new No(s.append(i),o)}remapping(e,t){let n=new fe;return this.items.forEach(((t,r)=>{let i=null!=t.mirrorOffset&&r-t.mirrorOffset>=e?n.maps.length-t.mirrorOffset:void 0;n.appendMap(t.map,i)}),e,t),n}addMaps(e){return 0==this.eventCount?this:new No(this.items.append(e.map((e=>new Do(e)))),this.eventCount)}rebased(e,t){if(!this.eventCount)return this;let n=[],r=Math.max(0,this.items.length-t),i=e.mapping,o=e.steps.length,s=this.eventCount;this.items.forEach((e=>{e.selection&&s--}),r);let l=t;this.items.forEach((t=>{let r=i.getMirror(--l);if(null==r)return;o=Math.min(o,r);let a=i.maps[r];if(t.step){let o=e.steps[r].invert(e.docs[r]),c=t.selection&&t.selection.map(i.slice(l+1,r));c&&s++,n.push(new Do(a,o,c))}else n.push(new Do(a))}),r);let a=[];for(let d=t;d500&&(h=h.compress(this.items.length-n.length)),h}emptyItemCount(){let e=0;return this.items.forEach((t=>{t.step||e++})),e}compress(e=this.items.length){let t=this.remapping(0,e),n=t.maps.length,r=[],i=0;return this.items.forEach(((o,s)=>{if(s>=e)r.push(o),o.selection&&i++;else if(o.step){let e=o.step.map(t.slice(n)),s=e&&e.getMap();if(n--,s&&t.appendMap(s,n),e){let l=o.selection&&o.selection.map(t.slice(n));l&&i++;let a,c=new Do(s.invert(),e,l),h=r.length-1;(a=r.length&&r[h].merge(c))?r[h]=a:r.push(c)}}else o.map&&n--}),this.items.length,0),new No(Co.from(r.reverse()),i)}}No.empty=new No(Co.empty,0);class Do{constructor(e,t,n,r){this.map=e,this.step=t,this.selection=n,this.mirrorOffset=r}merge(e){if(this.step&&e.step&&!e.selection){let t=e.step.merge(this.step);if(t)return new Do(t.getMap().invert(),t,this.selection)}}}class To{constructor(e,t,n,r){this.done=e,this.undone=t,this.prevRanges=n,this.prevTime=r}}const Eo=20;function Ao(e){let t=[];return e.forEach(((e,n,r,i)=>t.push(r,i))),t}function $o(e,t){if(!e)return null;let n=[];for(let r=0;rnew To(No.empty,No.empty,null,0),apply:(t,n,r)=>function(e,t,n,r){let i,o=n.getMeta(_o);if(o)return o.historyState;n.getMeta(Bo)&&(e=new To(e.done,e.undone,null,0));let s=n.getMeta("appendedTransaction");if(0==n.steps.length)return e;if(s&&s.getMeta(_o))return s.getMeta(_o).redo?new To(e.done.addTransform(n,void 0,r,Ro(t)),e.undone,Ao(n.mapping.maps[n.steps.length-1]),e.prevTime):new To(e.done,e.undone.addTransform(n,void 0,r,Ro(t)),null,e.prevTime);if(!1===n.getMeta("addToHistory")||s&&!1===s.getMeta("addToHistory"))return(i=n.getMeta("rebased"))?new To(e.done.rebased(n,i),e.undone.rebased(n,i),$o(e.prevRanges,n.mapping),e.prevTime):new To(e.done.addMaps(n.mapping.maps),e.undone.addMaps(n.mapping.maps),$o(e.prevRanges,n.mapping),e.prevTime);{let i=0==e.prevTime||!s&&(e.prevTime<(n.time||0)-r.newGroupDelay||!function(e,t){if(!t)return!1;if(!e.docChanged)return!0;let n=!1;return e.mapping.maps[0].forEach(((e,r)=>{for(let i=0;i=t[i]&&(n=!0)})),n}(n,e.prevRanges)),o=s?$o(e.prevRanges,n.mapping):Ao(n.mapping.maps[n.steps.length-1]);return new To(e.done.addTransform(n,i?t.selection.getBookmark():void 0,r,Ro(t)),No.empty,o,n.time)}}(n,r,t,e)},config:e,props:{handleDOMEvents:{beforeinput(e,t){let n=t.inputType,r="historyUndo"==n?jo:"historyRedo"==n?Fo:null;return!!r&&(t.preventDefault(),r(e.state,e.dispatch))}}}})}const jo=(e,t)=>{let n=_o.getState(e);return!(!n||0==n.done.eventCount)&&(t&&Po(n,e,t,!1),!0)},Fo=(e,t)=>{let n=_o.getState(e);return!(!n||0==n.undone.eventCount)&&(t&&Po(n,e,t,!0),!0)};function Lo(e){let t=_o.getState(e);return t?t.done.eventCount:0}function qo(e){let t=_o.getState(e);return t?t.undone.eventCount:0}"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self&&self;function Wo(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var Jo={},Ko={},Ho={},Yo={},Uo={};function Go(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function Zo(e){for(var t=1;t=+n}))};var ms={};Object.defineProperty(ms,"__esModule",{value:!0}),ms.default=void 0;var gs=(0,Ho.regex)("email",/^(?:[A-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[A-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9]{2,}(?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$/i);ms.default=gs;var ys={};Object.defineProperty(ys,"__esModule",{value:!0}),ys.default=void 0;var vs=Ho,ws=(0,vs.withParams)({type:"ipAddress"},(function(e){if(!(0,vs.req)(e))return!0;if("string"!=typeof e)return!1;var t=e.split(".");return 4===t.length&&t.every(bs)}));ys.default=ws;var bs=function(e){if(e.length>3||0===e.length)return!1;if("0"===e[0]&&"0"!==e)return!1;if(!e.match(/^\d+$/))return!1;var t=0|+e;return t>=0&&t<=255},xs={};Object.defineProperty(xs,"__esModule",{value:!0}),xs.default=void 0;var Ss=Ho;xs.default=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:":";return(0,Ss.withParams)({type:"macAddress"},(function(t){if(!(0,Ss.req)(t))return!0;if("string"!=typeof t)return!1;var n="string"==typeof e&&""!==e?t.split(e):12===t.length||16===t.length?t.match(/.{2}/g):null;return null!==n&&(6===n.length||8===n.length)&&n.every(ks)}))};var ks=function(e){return e.toLowerCase().match(/^[0-9a-f]{2}$/)},Ms={};Object.defineProperty(Ms,"__esModule",{value:!0}),Ms.default=void 0;var Os=Ho;Ms.default=function(e){return(0,Os.withParams)({type:"maxLength",max:e},(function(t){return!(0,Os.req)(t)||(0,Os.len)(t)<=e}))};var Cs={};Object.defineProperty(Cs,"__esModule",{value:!0}),Cs.default=void 0;var Ns=Ho;Cs.default=function(e){return(0,Ns.withParams)({type:"minLength",min:e},(function(t){return!(0,Ns.req)(t)||(0,Ns.len)(t)>=e}))};var Ds={};Object.defineProperty(Ds,"__esModule",{value:!0}),Ds.default=void 0;var Ts=Ho,Es=(0,Ts.withParams)({type:"required"},(function(e){return(0,Ts.req)("string"==typeof e?e.trim():e)}));Ds.default=Es;var As={};Object.defineProperty(As,"__esModule",{value:!0}),As.default=void 0;var $s=Ho;As.default=function(e){return(0,$s.withParams)({type:"requiredIf",prop:e},(function(t,n){return!(0,$s.ref)(e,this,n)||(0,$s.req)(t)}))};var Ps={};Object.defineProperty(Ps,"__esModule",{value:!0}),Ps.default=void 0;var Is=Ho;Ps.default=function(e){return(0,Is.withParams)({type:"requiredUnless",prop:e},(function(t,n){return!!(0,Is.ref)(e,this,n)||(0,Is.req)(t)}))};var zs={};Object.defineProperty(zs,"__esModule",{value:!0}),zs.default=void 0;var Rs=Ho;zs.default=function(e){return(0,Rs.withParams)({type:"sameAs",eq:e},(function(t,n){return t===(0,Rs.ref)(e,this,n)}))};var _s={};Object.defineProperty(_s,"__esModule",{value:!0}),_s.default=void 0;var Bs=(0,Ho.regex)("url",/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff]\.)+(?:[a-z\u00a1-\uffff]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S*)?$/i);_s.default=Bs;var Vs={};Object.defineProperty(Vs,"__esModule",{value:!0}),Vs.default=void 0;var js=Ho;Vs.default=function(){for(var e=arguments.length,t=new Array(e),n=0;n0&&t.reduce((function(t,n){return t||n.apply(e,r)}),!1)}))};var Fs={};Object.defineProperty(Fs,"__esModule",{value:!0}),Fs.default=void 0;var Ls=Ho;Fs.default=function(){for(var e=arguments.length,t=new Array(e),n=0;n0&&t.reduce((function(t,n){return t&&n.apply(e,r)}),!0)}))};var qs={};Object.defineProperty(qs,"__esModule",{value:!0}),qs.default=void 0;var Ws=Ho;qs.default=function(e){return(0,Ws.withParams)({type:"not"},(function(t,n){return!(0,Ws.req)(t)||!e.call(this,t,n)}))};var Js={};Object.defineProperty(Js,"__esModule",{value:!0}),Js.default=void 0;var Ks=Ho;Js.default=function(e){return(0,Ks.withParams)({type:"minValue",min:e},(function(t){return!(0,Ks.req)(t)||(!/\s/.test(t)||t instanceof Date)&&+t>=+e}))};var Hs={};Object.defineProperty(Hs,"__esModule",{value:!0}),Hs.default=void 0;var Ys=Ho;Hs.default=function(e){return(0,Ys.withParams)({type:"maxValue",max:e},(function(t){return!(0,Ys.req)(t)||(!/\s/.test(t)||t instanceof Date)&&+t<=+e}))};var Us={};Object.defineProperty(Us,"__esModule",{value:!0}),Us.default=void 0;var Gs=(0,Ho.regex)("integer",/(^[0-9]*$)|(^-[0-9]+$)/);Us.default=Gs;var Zs={};Object.defineProperty(Zs,"__esModule",{value:!0}),Zs.default=void 0;var Xs=(0,Ho.regex)("decimal",/^[-]?\d*(\.\d+)?$/);Zs.default=Xs,function(e){function t(e){return(t="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})(e)}Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"alpha",{enumerable:!0,get:function(){return n.default}}),Object.defineProperty(e,"alphaNum",{enumerable:!0,get:function(){return r.default}}),Object.defineProperty(e,"and",{enumerable:!0,get:function(){return y.default}}),Object.defineProperty(e,"between",{enumerable:!0,get:function(){return o.default}}),Object.defineProperty(e,"decimal",{enumerable:!0,get:function(){return S.default}}),Object.defineProperty(e,"email",{enumerable:!0,get:function(){return s.default}}),e.helpers=void 0,Object.defineProperty(e,"integer",{enumerable:!0,get:function(){return x.default}}),Object.defineProperty(e,"ipAddress",{enumerable:!0,get:function(){return l.default}}),Object.defineProperty(e,"macAddress",{enumerable:!0,get:function(){return a.default}}),Object.defineProperty(e,"maxLength",{enumerable:!0,get:function(){return c.default}}),Object.defineProperty(e,"maxValue",{enumerable:!0,get:function(){return b.default}}),Object.defineProperty(e,"minLength",{enumerable:!0,get:function(){return h.default}}),Object.defineProperty(e,"minValue",{enumerable:!0,get:function(){return w.default}}),Object.defineProperty(e,"not",{enumerable:!0,get:function(){return v.default}}),Object.defineProperty(e,"numeric",{enumerable:!0,get:function(){return i.default}}),Object.defineProperty(e,"or",{enumerable:!0,get:function(){return g.default}}),Object.defineProperty(e,"required",{enumerable:!0,get:function(){return d.default}}),Object.defineProperty(e,"requiredIf",{enumerable:!0,get:function(){return u.default}}),Object.defineProperty(e,"requiredUnless",{enumerable:!0,get:function(){return f.default}}),Object.defineProperty(e,"sameAs",{enumerable:!0,get:function(){return p.default}}),Object.defineProperty(e,"url",{enumerable:!0,get:function(){return m.default}});var n=O(Ko),r=O(cs),i=O(ds),o=O(fs),s=O(ms),l=O(ys),a=O(xs),c=O(Ms),h=O(Cs),d=O(Ds),u=O(As),f=O(Ps),p=O(zs),m=O(_s),g=O(Vs),y=O(Fs),v=O(qs),w=O(Js),b=O(Hs),x=O(Us),S=O(Zs),k=function(e,n){if(!n&&e&&e.__esModule)return e;if(null===e||"object"!==t(e)&&"function"!=typeof e)return{default:e};var r=M(n);if(r&&r.has(e))return r.get(e);var i={},o=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var s in e)if("default"!==s&&Object.prototype.hasOwnProperty.call(e,s)){var l=o?Object.getOwnPropertyDescriptor(e,s):null;l&&(l.get||l.set)?Object.defineProperty(i,s,l):i[s]=e[s]}i.default=e,r&&r.set(e,i);return i}(Ho);function M(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,n=new WeakMap;return(M=function(e){return e?n:t})(e)}function O(e){return e&&e.__esModule?e:{default:e}}e.helpers=k}(Jo);function Qs(e){return(Qs="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})(e)}var el={selector:"vue-portal-target-".concat(((e=21)=>{let t="",n=e;for(;n--;)t+="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict"[64*Math.random()|0];return t})())},tl="undefined"!=typeof window&&void 0!==("undefined"==typeof document?"undefined":Qs(document)),nl=Vue.extend({abstract:!0,name:"PortalOutlet",props:["nodes","tag"],data:function(e){return{updatedNodes:e.nodes}},render:function(e){var t=this.updatedNodes&&this.updatedNodes();return t?1!==t.length||t[0].text?e(this.tag||"DIV",t):t:e()},destroyed:function(){var e=this.$el;e&&e.parentNode.removeChild(e)}}),rl=Vue.extend({name:"VueSimplePortal",props:{disabled:{type:Boolean},prepend:{type:Boolean},selector:{type:String,default:function(){return"#".concat(el.selector)}},tag:{type:String,default:"DIV"}},render:function(e){if(this.disabled){var t=this.$scopedSlots&&this.$scopedSlots.default();return t?t.length<2&&!t[0].text?t:e(this.tag,t):e()}return e()},created:function(){this.getTargetEl()||this.insertTargetEl()},updated:function(){var e=this;this.$nextTick((function(){e.disabled||e.slotFn===e.$scopedSlots.default||(e.container.updatedNodes=e.$scopedSlots.default),e.slotFn=e.$scopedSlots.default}))},beforeDestroy:function(){this.unmount()},watch:{disabled:{immediate:!0,handler:function(e){e?this.unmount():this.$nextTick(this.mount)}}},methods:{getTargetEl:function(){if(tl)return document.querySelector(this.selector)},insertTargetEl:function(){if(tl){var e=document.querySelector("body"),t=document.createElement(this.tag);t.id=this.selector.substring(1),e.appendChild(t)}},mount:function(){if(tl){var e=this.getTargetEl(),t=document.createElement("DIV");this.prepend&&e.firstChild?e.insertBefore(t,e.firstChild):e.appendChild(t),this.container=new nl({el:t,parent:this,propsData:{tag:this.tag,nodes:this.$scopedSlots.default}})}},unmount:function(){this.container&&(this.container.$destroy(),delete this.container)}}});function il(e){var t,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};e.component(n.name||"portal",rl),n.defaultSelector&&(t=n.defaultSelector,el.selector=t)}"undefined"!=typeof window&&window.Vue&&window.Vue===Vue&&Vue.use(il) -/*! - * vuex v3.6.2 - * (c) 2021 Evan You - * @license MIT - */;var ol=("undefined"!=typeof window?window:"undefined"!=typeof global?global:{}).__VUE_DEVTOOLS_GLOBAL_HOOK__;function sl(e,t){if(void 0===t&&(t=[]),null===e||"object"!=typeof e)return e;var n,r=(n=function(t){return t.original===e},t.filter(n)[0]);if(r)return r.copy;var i=Array.isArray(e)?[]:{};return t.push({original:e,copy:i}),Object.keys(e).forEach((function(n){i[n]=sl(e[n],t)})),i}function ll(e,t){Object.keys(e).forEach((function(n){return t(e[n],n)}))}function al(e){return null!==e&&"object"==typeof e}var cl=function(e,t){this.runtime=t,this._children=Object.create(null),this._rawModule=e;var n=e.state;this.state=("function"==typeof n?n():n)||{}},hl={namespaced:{configurable:!0}};hl.namespaced.get=function(){return!!this._rawModule.namespaced},cl.prototype.addChild=function(e,t){this._children[e]=t},cl.prototype.removeChild=function(e){delete this._children[e]},cl.prototype.getChild=function(e){return this._children[e]},cl.prototype.hasChild=function(e){return e in this._children},cl.prototype.update=function(e){this._rawModule.namespaced=e.namespaced,e.actions&&(this._rawModule.actions=e.actions),e.mutations&&(this._rawModule.mutations=e.mutations),e.getters&&(this._rawModule.getters=e.getters)},cl.prototype.forEachChild=function(e){ll(this._children,e)},cl.prototype.forEachGetter=function(e){this._rawModule.getters&&ll(this._rawModule.getters,e)},cl.prototype.forEachAction=function(e){this._rawModule.actions&&ll(this._rawModule.actions,e)},cl.prototype.forEachMutation=function(e){this._rawModule.mutations&&ll(this._rawModule.mutations,e)},Object.defineProperties(cl.prototype,hl);var dl,ul=function(e){this.register([],e,!1)};function fl(e,t,n){if(t.update(n),n.modules)for(var r in n.modules){if(!t.getChild(r))return;fl(e.concat(r),t.getChild(r),n.modules[r])}}ul.prototype.get=function(e){return e.reduce((function(e,t){return e.getChild(t)}),this.root)},ul.prototype.getNamespace=function(e){var t=this.root;return e.reduce((function(e,n){return e+((t=t.getChild(n)).namespaced?n+"/":"")}),"")},ul.prototype.update=function(e){fl([],this.root,e)},ul.prototype.register=function(e,t,n){var r=this;void 0===n&&(n=!0);var i=new cl(t,n);0===e.length?this.root=i:this.get(e.slice(0,-1)).addChild(e[e.length-1],i);t.modules&&ll(t.modules,(function(t,i){r.register(e.concat(i),t,n)}))},ul.prototype.unregister=function(e){var t=this.get(e.slice(0,-1)),n=e[e.length-1],r=t.getChild(n);r&&r.runtime&&t.removeChild(n)},ul.prototype.isRegistered=function(e){var t=this.get(e.slice(0,-1)),n=e[e.length-1];return!!t&&t.hasChild(n)};var pl=function(e){var t=this;void 0===e&&(e={}),!dl&&"undefined"!=typeof window&&window.Vue&&Sl(window.Vue);var n=e.plugins;void 0===n&&(n=[]);var r=e.strict;void 0===r&&(r=!1),this._committing=!1,this._actions=Object.create(null),this._actionSubscribers=[],this._mutations=Object.create(null),this._wrappedGetters=Object.create(null),this._modules=new ul(e),this._modulesNamespaceMap=Object.create(null),this._subscribers=[],this._watcherVM=new dl,this._makeLocalGettersCache=Object.create(null);var i=this,o=this.dispatch,s=this.commit;this.dispatch=function(e,t){return o.call(i,e,t)},this.commit=function(e,t,n){return s.call(i,e,t,n)},this.strict=r;var l=this._modules.root.state;wl(this,l,[],this._modules.root),vl(this,l),n.forEach((function(e){return e(t)})),(void 0!==e.devtools?e.devtools:dl.config.devtools)&&function(e){ol&&(e._devtoolHook=ol,ol.emit("vuex:init",e),ol.on("vuex:travel-to-state",(function(t){e.replaceState(t)})),e.subscribe((function(e,t){ol.emit("vuex:mutation",e,t)}),{prepend:!0}),e.subscribeAction((function(e,t){ol.emit("vuex:action",e,t)}),{prepend:!0}))}(this)},ml={state:{configurable:!0}};function gl(e,t,n){return t.indexOf(e)<0&&(n&&n.prepend?t.unshift(e):t.push(e)),function(){var n=t.indexOf(e);n>-1&&t.splice(n,1)}}function yl(e,t){e._actions=Object.create(null),e._mutations=Object.create(null),e._wrappedGetters=Object.create(null),e._modulesNamespaceMap=Object.create(null);var n=e.state;wl(e,n,[],e._modules.root,!0),vl(e,n,t)}function vl(e,t,n){var r=e._vm;e.getters={},e._makeLocalGettersCache=Object.create(null);var i=e._wrappedGetters,o={};ll(i,(function(t,n){o[n]=function(e,t){return function(){return e(t)}}(t,e),Object.defineProperty(e.getters,n,{get:function(){return e._vm[n]},enumerable:!0})}));var s=dl.config.silent;dl.config.silent=!0,e._vm=new dl({data:{$$state:t},computed:o}),dl.config.silent=s,e.strict&&function(e){e._vm.$watch((function(){return this._data.$$state}),(function(){}),{deep:!0,sync:!0})}(e),r&&(n&&e._withCommit((function(){r._data.$$state=null})),dl.nextTick((function(){return r.$destroy()})))}function wl(e,t,n,r,i){var o=!n.length,s=e._modules.getNamespace(n);if(r.namespaced&&(e._modulesNamespaceMap[s],e._modulesNamespaceMap[s]=r),!o&&!i){var l=bl(t,n.slice(0,-1)),a=n[n.length-1];e._withCommit((function(){dl.set(l,a,r.state)}))}var c=r.context=function(e,t,n){var r=""===t,i={dispatch:r?e.dispatch:function(n,r,i){var o=xl(n,r,i),s=o.payload,l=o.options,a=o.type;return l&&l.root||(a=t+a),e.dispatch(a,s)},commit:r?e.commit:function(n,r,i){var o=xl(n,r,i),s=o.payload,l=o.options,a=o.type;l&&l.root||(a=t+a),e.commit(a,s,l)}};return Object.defineProperties(i,{getters:{get:r?function(){return e.getters}:function(){return function(e,t){if(!e._makeLocalGettersCache[t]){var n={},r=t.length;Object.keys(e.getters).forEach((function(i){if(i.slice(0,r)===t){var o=i.slice(r);Object.defineProperty(n,o,{get:function(){return e.getters[i]},enumerable:!0})}})),e._makeLocalGettersCache[t]=n}return e._makeLocalGettersCache[t]}(e,t)}},state:{get:function(){return bl(e.state,n)}}}),i}(e,s,n);r.forEachMutation((function(t,n){!function(e,t,n,r){var i=e._mutations[t]||(e._mutations[t]=[]);i.push((function(t){n.call(e,r.state,t)}))}(e,s+n,t,c)})),r.forEachAction((function(t,n){var r=t.root?n:s+n,i=t.handler||t;!function(e,t,n,r){var i=e._actions[t]||(e._actions[t]=[]);i.push((function(t){var i,o=n.call(e,{dispatch:r.dispatch,commit:r.commit,getters:r.getters,state:r.state,rootGetters:e.getters,rootState:e.state},t);return(i=o)&&"function"==typeof i.then||(o=Promise.resolve(o)),e._devtoolHook?o.catch((function(t){throw e._devtoolHook.emit("vuex:error",t),t})):o}))}(e,r,i,c)})),r.forEachGetter((function(t,n){!function(e,t,n,r){if(e._wrappedGetters[t])return;e._wrappedGetters[t]=function(e){return n(r.state,r.getters,e.state,e.getters)}}(e,s+n,t,c)})),r.forEachChild((function(r,o){wl(e,t,n.concat(o),r,i)}))}function bl(e,t){return t.reduce((function(e,t){return e[t]}),e)}function xl(e,t,n){return al(e)&&e.type&&(n=t,t=e,e=e.type),{type:e,payload:t,options:n}}function Sl(e){dl&&e===dl||function(e){if(Number(e.version.split(".")[0])>=2)e.mixin({beforeCreate:n});else{var t=e.prototype._init;e.prototype._init=function(e){void 0===e&&(e={}),e.init=e.init?[n].concat(e.init):n,t.call(this,e)}}function n(){var e=this.$options;e.store?this.$store="function"==typeof e.store?e.store():e.store:e.parent&&e.parent.$store&&(this.$store=e.parent.$store)}}(dl=e)}ml.state.get=function(){return this._vm._data.$$state},ml.state.set=function(e){},pl.prototype.commit=function(e,t,n){var r=this,i=xl(e,t,n),o=i.type,s=i.payload,l={type:o,payload:s},a=this._mutations[o];a&&(this._withCommit((function(){a.forEach((function(e){e(s)}))})),this._subscribers.slice().forEach((function(e){return e(l,r.state)})))},pl.prototype.dispatch=function(e,t){var n=this,r=xl(e,t),i=r.type,o=r.payload,s={type:i,payload:o},l=this._actions[i];if(l){try{this._actionSubscribers.slice().filter((function(e){return e.before})).forEach((function(e){return e.before(s,n.state)}))}catch(c){}var a=l.length>1?Promise.all(l.map((function(e){return e(o)}))):l[0](o);return new Promise((function(e,t){a.then((function(t){try{n._actionSubscribers.filter((function(e){return e.after})).forEach((function(e){return e.after(s,n.state)}))}catch(c){}e(t)}),(function(e){try{n._actionSubscribers.filter((function(e){return e.error})).forEach((function(t){return t.error(s,n.state,e)}))}catch(c){}t(e)}))}))}},pl.prototype.subscribe=function(e,t){return gl(e,this._subscribers,t)},pl.prototype.subscribeAction=function(e,t){return gl("function"==typeof e?{before:e}:e,this._actionSubscribers,t)},pl.prototype.watch=function(e,t,n){var r=this;return this._watcherVM.$watch((function(){return e(r.state,r.getters)}),t,n)},pl.prototype.replaceState=function(e){var t=this;this._withCommit((function(){t._vm._data.$$state=e}))},pl.prototype.registerModule=function(e,t,n){void 0===n&&(n={}),"string"==typeof e&&(e=[e]),this._modules.register(e,t),wl(this,this.state,e,this._modules.get(e),n.preserveState),vl(this,this.state)},pl.prototype.unregisterModule=function(e){var t=this;"string"==typeof e&&(e=[e]),this._modules.unregister(e),this._withCommit((function(){var n=bl(t.state,e.slice(0,-1));dl.delete(n,e[e.length-1])})),yl(this)},pl.prototype.hasModule=function(e){return"string"==typeof e&&(e=[e]),this._modules.isRegistered(e)},pl.prototype.hotUpdate=function(e){this._modules.update(e),yl(this,!0)},pl.prototype._withCommit=function(e){var t=this._committing;this._committing=!0,e(),this._committing=t},Object.defineProperties(pl.prototype,ml);var kl=Dl((function(e,t){var n={};return Nl(t).forEach((function(t){var r=t.key,i=t.val;n[r]=function(){var t=this.$store.state,n=this.$store.getters;if(e){var r=Tl(this.$store,"mapState",e);if(!r)return;t=r.context.state,n=r.context.getters}return"function"==typeof i?i.call(this,t,n):t[i]},n[r].vuex=!0})),n})),Ml=Dl((function(e,t){var n={};return Nl(t).forEach((function(t){var r=t.key,i=t.val;n[r]=function(){for(var t=[],n=arguments.length;n--;)t[n]=arguments[n];var r=this.$store.commit;if(e){var o=Tl(this.$store,"mapMutations",e);if(!o)return;r=o.context.commit}return"function"==typeof i?i.apply(this,[r].concat(t)):r.apply(this.$store,[i].concat(t))}})),n})),Ol=Dl((function(e,t){var n={};return Nl(t).forEach((function(t){var r=t.key,i=t.val;i=e+i,n[r]=function(){if(!e||Tl(this.$store,"mapGetters",e))return this.$store.getters[i]},n[r].vuex=!0})),n})),Cl=Dl((function(e,t){var n={};return Nl(t).forEach((function(t){var r=t.key,i=t.val;n[r]=function(){for(var t=[],n=arguments.length;n--;)t[n]=arguments[n];var r=this.$store.dispatch;if(e){var o=Tl(this.$store,"mapActions",e);if(!o)return;r=o.context.dispatch}return"function"==typeof i?i.apply(this,[r].concat(t)):r.apply(this.$store,[i].concat(t))}})),n}));function Nl(e){return function(e){return Array.isArray(e)||al(e)}(e)?Array.isArray(e)?e.map((function(e){return{key:e,val:e}})):Object.keys(e).map((function(t){return{key:t,val:e[t]}})):[]}function Dl(e){return function(t,n){return"string"!=typeof t?(n=t,t=""):"/"!==t.charAt(t.length-1)&&(t+="/"),e(t,n)}}function Tl(e,t,n){return e._modulesNamespaceMap[n]}function El(e,t,n){var r=n?e.groupCollapsed:e.group;try{r.call(e,t)}catch(i){e.log(t)}}function Al(e){try{e.groupEnd()}catch(t){e.log("—— log end ——")}}function $l(){var e=new Date;return" @ "+Pl(e.getHours(),2)+":"+Pl(e.getMinutes(),2)+":"+Pl(e.getSeconds(),2)+"."+Pl(e.getMilliseconds(),3)}function Pl(e,t){return n="0",r=t-e.toString().length,new Array(r+1).join(n)+e;var n,r}const Il={Store:pl,install:Sl,version:"3.6.2",mapState:kl,mapMutations:Ml,mapGetters:Ol,mapActions:Cl,createNamespacedHelpers:function(e){return{mapState:kl.bind(null,e),mapGetters:Ol.bind(null,e),mapMutations:Ml.bind(null,e),mapActions:Cl.bind(null,e)}},createLogger:function(e){void 0===e&&(e={});var t=e.collapsed;void 0===t&&(t=!0);var n=e.filter;void 0===n&&(n=function(e,t,n){return!0});var r=e.transformer;void 0===r&&(r=function(e){return e});var i=e.mutationTransformer;void 0===i&&(i=function(e){return e});var o=e.actionFilter;void 0===o&&(o=function(e,t){return!0});var s=e.actionTransformer;void 0===s&&(s=function(e){return e});var l=e.logMutations;void 0===l&&(l=!0);var a=e.logActions;void 0===a&&(a=!0);var c=e.logger;return void 0===c&&(c=console),function(e){var h=sl(e.state);void 0!==c&&(l&&e.subscribe((function(e,o){var s=sl(o);if(n(e,h,s)){var l=$l(),a=i(e),d="mutation "+e.type+l;El(c,d,t),c.log("%c prev state","color: #9E9E9E; font-weight: bold",r(h)),c.log("%c mutation","color: #03A9F4; font-weight: bold",a),c.log("%c next state","color: #4CAF50; font-weight: bold",r(s)),Al(c)}h=s})),a&&e.subscribeAction((function(e,n){if(o(e,n)){var r=$l(),i=s(e),l="action "+e.type+r;El(c,l,t),c.log("%c action","color: #03A9F4; font-weight: bold",i),Al(c)}})))}}};function zl(e){return{all:e=e||new Map,on:function(t,n){var r=e.get(t);r?r.push(n):e.set(t,[n])},off:function(t,n){var r=e.get(t);r&&(n?r.splice(r.indexOf(n)>>>0,1):e.set(t,[]))},emit:function(t,n){var r=e.get(t);r&&r.slice().map((function(e){e(n)})),(r=e.get("*"))&&r.slice().map((function(e){e(t,n)}))}}}var Rl,_l,Bl="function"==typeof Map?new Map:(Rl=[],_l=[],{has:function(e){return Rl.indexOf(e)>-1},get:function(e){return _l[Rl.indexOf(e)]},set:function(e,t){-1===Rl.indexOf(e)&&(Rl.push(e),_l.push(t))},delete:function(e){var t=Rl.indexOf(e);t>-1&&(Rl.splice(t,1),_l.splice(t,1))}}),Vl=function(e){return new Event(e,{bubbles:!0})};try{new Event("test")}catch(oa){Vl=function(e){var t=document.createEvent("Event");return t.initEvent(e,!0,!1),t}}function jl(e){var t=Bl.get(e);t&&t.destroy()}function Fl(e){var t=Bl.get(e);t&&t.update()}var Ll=null;"undefined"==typeof window||"function"!=typeof window.getComputedStyle?((Ll=function(e){return e}).destroy=function(e){return e},Ll.update=function(e){return e}):((Ll=function(e,t){return e&&Array.prototype.forEach.call(e.length?e:[e],(function(e){return function(e){if(e&&e.nodeName&&"TEXTAREA"===e.nodeName&&!Bl.has(e)){var t,n=null,r=null,i=null,o=function(){e.clientWidth!==r&&c()},s=function(t){window.removeEventListener("resize",o,!1),e.removeEventListener("input",c,!1),e.removeEventListener("keyup",c,!1),e.removeEventListener("autosize:destroy",s,!1),e.removeEventListener("autosize:update",c,!1),Object.keys(t).forEach((function(n){e.style[n]=t[n]})),Bl.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",s,!1),"onpropertychange"in e&&"oninput"in e&&e.addEventListener("keyup",c,!1),window.addEventListener("resize",o,!1),e.addEventListener("input",c,!1),e.addEventListener("autosize:update",c,!1),e.style.overflowX="hidden",e.style.wordWrap="break-word",Bl.set(e,{destroy:s,update:c}),"vertical"===(t=window.getComputedStyle(e,null)).resize?e.style.resize="none":"both"===t.resize&&(e.style.resize="horizontal"),n="content-box"===t.boxSizing?-(parseFloat(t.paddingTop)+parseFloat(t.paddingBottom)):parseFloat(t.borderTopWidth)+parseFloat(t.borderBottomWidth),isNaN(n)&&(n=0),c()}function l(t){var n=e.style.width;e.style.width="0px",e.style.width=n,e.style.overflowY=t}function a(){if(0!==e.scrollHeight){var t=function(e){for(var t=[];e&&e.parentNode&&e.parentNode instanceof Element;)e.parentNode.scrollTop&&(e.parentNode.style.scrollBehavior="auto",t.push([e.parentNode,e.parentNode.scrollTop])),e=e.parentNode;return function(){return t.forEach((function(e){var t=e[0];t.scrollTop=e[1],t.style.scrollBehavior=null}))}}(e);e.style.height="",e.style.height=e.scrollHeight+n+"px",r=e.clientWidth,t()}}function c(){a();var t=Math.round(parseFloat(e.style.height)),n=window.getComputedStyle(e,null),r="content-box"===n.boxSizing?Math.round(parseFloat(n.height)):e.offsetHeight;if(r=t?e:""+Array(t+1-r.length).join(n)+e},v={s:y,z:function(e){var t=-e.utcOffset(),n=Math.abs(t),r=Math.floor(n/60),i=n%60;return(t<=0?"+":"-")+y(r,2,"0")+":"+y(i,2,"0")},m:function e(t,n){if(t.date()1)return e(s[0])}else{var l=t.name;b[l]=t,i=l}return!r&&i&&(w=i),i||!r&&w},k=function(e,t){if(x(e))return e.clone();var n="object"==typeof t?t:{};return n.date=e,n.args=arguments,new O(n)},M=v;M.l=S,M.i=x,M.w=function(e,t){return k(e,{locale:t.$L,utc:t.$u,x:t.$x,$offset:t.$offset})};var O=function(){function g(e){this.$L=S(e.locale,null,!0),this.parse(e)}var y=g.prototype;return y.parse=function(e){this.$d=function(e){var t=e.date,n=e.utc;if(null===t)return new Date(NaN);if(M.u(t))return new Date;if(t instanceof Date)return new Date(t);if("string"==typeof t&&!/Z$/i.test(t)){var r=t.match(p);if(r){var i=r[2]-1||0,o=(r[7]||"0").substring(0,3);return n?new Date(Date.UTC(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,o)):new Date(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,o)}}return new Date(t)}(e),this.$x=e.x||{},this.init()},y.init=function(){var e=this.$d;this.$y=e.getFullYear(),this.$M=e.getMonth(),this.$D=e.getDate(),this.$W=e.getDay(),this.$H=e.getHours(),this.$m=e.getMinutes(),this.$s=e.getSeconds(),this.$ms=e.getMilliseconds()},y.$utils=function(){return M},y.isValid=function(){return!(this.$d.toString()===f)},y.isSame=function(e,t){var n=k(e);return this.startOf(t)<=n&&n<=this.endOf(t)},y.isAfter=function(e,t){return k(e)68?1900:2e3)},l=function(e){return function(t){this[e]=+t}},a=[/[+-]\d\d:?(\d\d)?|Z/,function(e){(this.zone||(this.zone={})).offset=function(e){if(!e)return 0;if("Z"===e)return 0;var t=e.match(/([+-]|\d\d)/g),n=60*t[1]+(+t[2]||0);return 0===n?0:"+"===t[0]?-n:n}(e)}],c=function(e){var t=o[e];return t&&(t.indexOf?t:t.s.concat(t.f))},h=function(e,t){var n,r=o.meridiem;if(r){for(var i=1;i<=24;i+=1)if(e.indexOf(r(i,0,t))>-1){n=i>12;break}}else n=e===(t?"pm":"PM");return n},d={A:[i,function(e){this.afternoon=h(e,!1)}],a:[i,function(e){this.afternoon=h(e,!0)}],S:[/\d/,function(e){this.milliseconds=100*+e}],SS:[n,function(e){this.milliseconds=10*+e}],SSS:[/\d{3}/,function(e){this.milliseconds=+e}],s:[r,l("seconds")],ss:[r,l("seconds")],m:[r,l("minutes")],mm:[r,l("minutes")],H:[r,l("hours")],h:[r,l("hours")],HH:[r,l("hours")],hh:[r,l("hours")],D:[r,l("day")],DD:[n,l("day")],Do:[i,function(e){var t=o.ordinal,n=e.match(/\d+/);if(this.day=n[0],t)for(var r=1;r<=31;r+=1)t(r).replace(/\[|\]/g,"")===e&&(this.day=r)}],M:[r,l("month")],MM:[n,l("month")],MMM:[i,function(e){var t=c("months"),n=(c("monthsShort")||t.map((function(e){return e.slice(0,3)}))).indexOf(e)+1;if(n<1)throw new Error;this.month=n%12||n}],MMMM:[i,function(e){var t=c("months").indexOf(e)+1;if(t<1)throw new Error;this.month=t%12||t}],Y:[/[+-]?\d+/,l("year")],YY:[n,function(e){this.year=s(e)}],YYYY:[/\d{4}/,l("year")],Z:a,ZZ:a};function u(n){var r,i;r=n,i=o&&o.formats;for(var s=(n=r.replace(/(\[[^\]]+])|(LTS?|l{1,4}|L{1,4})/g,(function(t,n,r){var o=r&&r.toUpperCase();return n||i[r]||e[r]||i[o].replace(/(\[[^\]]+])|(MMMM|MM|DD|dddd)/g,(function(e,t,n){return t||n.slice(1)}))}))).match(t),l=s.length,a=0;a-1)return new Date(("X"===t?1e3:1)*e);var r=u(t)(e),i=r.year,o=r.month,s=r.day,l=r.hours,a=r.minutes,c=r.seconds,h=r.milliseconds,d=r.zone,f=new Date,p=s||(i||o?1:f.getDate()),m=i||f.getFullYear(),g=0;i&&!o||(g=o>0?o-1:f.getMonth());var y=l||0,v=a||0,w=c||0,b=h||0;return d?new Date(Date.UTC(m,g,p,y,v,w,b+60*d.offset*1e3)):n?new Date(Date.UTC(m,g,p,y,v,w,b)):new Date(m,g,p,y,v,w,b)}catch(x){return new Date("")}}(t,l,r),this.init(),d&&!0!==d&&(this.$L=this.locale(d).$L),h&&t!=this.format(l)&&(this.$d=new Date("")),o={}}else if(l instanceof Array)for(var f=l.length,p=1;p<=f;p+=1){s[1]=l[p-1];var m=n.apply(this,s);if(m.isValid()){this.$d=m.$d,this.$L=m.$L,this.init();break}p===f&&(this.$d=new Date(""))}else i.call(this,e)}}}());var Yl={},Ul={};function Gl(e){return null==e}function Zl(e){return null!=e}function Xl(e,t){return t.tag===e.tag&&t.key===e.key}function Ql(e){var t=e.tag;e.vm=new t({data:e.args})}function ea(e,t,n){var r,i,o={};for(r=t;r<=n;++r)Zl(i=e[r].key)&&(o[i]=r);return o}function ta(e,t,n){for(;t<=n;++t)Ql(e[t])}function na(e,t,n){for(;t<=n;++t){var r=e[t];Zl(r)&&(r.vm.$destroy(),r.vm=null)}}function ra(e,t){e!==t&&(t.vm=e.vm,function(e){for(var t=Object.keys(e.args),n=0;nl?ta(t,s,h):s>h&&na(e,o,l)}(e,t):Zl(t)?ta(t,0,t.length-1):Zl(e)&&na(e,0,e.length-1)},function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.Vuelidate=O,e.validationMixin=e.default=void 0,Object.defineProperty(e,"withParams",{enumerable:!0,get:function(){return n.withParams}});var t=Ul,n=Uo;function r(e){return function(e){if(Array.isArray(e))return i(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,t){if(!e)return;if("string"==typeof e)return i(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return i(e,t)}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function i(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n1?l:l.$sub[0]:null}}},computed:{run:function(){var e=this,t=this.lazyParentModel();if(Array.isArray(t)&&t.__ob__){var n=t.__ob__.dep;n.depend();var r=n.constructor.target;if(!this._indirectWatcher){var i=r.constructor;this._indirectWatcher=new i(this,(function(){return e.runRule(t)}),null,{lazy:!0})}var o=this.getModel();if(!this._indirectWatcher.dirty&&this._lastModel===o)return this._indirectWatcher.depend(),r.value;this._lastModel=o,this._indirectWatcher.evaluate(),this._indirectWatcher.depend()}else this._indirectWatcher&&(this._indirectWatcher.teardown(),this._indirectWatcher=null);return this._indirectWatcher?this._indirectWatcher.value:this.runRule(t)},$params:function(){return this.run.params},proxy:function(){var e=this.run.output;return e[p]?!!e.v:!!e},$pending:function(){var e=this.run.output;return!!e[p]&&e.p}},destroyed:function(){this._indirectWatcher&&(this._indirectWatcher.teardown(),this._indirectWatcher=null)}}),l=i.extend({data:function(){return{dirty:!1,validations:null,lazyModel:null,model:null,prop:null,lazyParentModel:null,rootModel:null}},methods:s(s({},y),{},{refProxy:function(e){return this.getRef(e).proxy},getRef:function(e){return this.refs[e]},isNested:function(e){return"function"!=typeof this.validations[e]}}),computed:s(s({},m),{},{nestedKeys:function(){return this.keys.filter(this.isNested)},ruleKeys:function(){var e=this;return this.keys.filter((function(t){return!e.isNested(t)}))},keys:function(){return Object.keys(this.validations).filter((function(e){return"$params"!==e}))},proxy:function(){var e=this,t=h(this.keys,(function(t){return{enumerable:!0,configurable:!0,get:function(){return e.refProxy(t)}}})),n=h(v,(function(t){return{enumerable:!0,configurable:!0,get:function(){return e[t]}}})),r=h(w,(function(t){return{enumerable:!1,configurable:!0,get:function(){return e[t]}}})),i=this.hasIter()?{$iter:{enumerable:!0,value:Object.defineProperties({},s({},t))}}:{};return Object.defineProperties({},s(s(s(s({},t),i),{},{$model:{enumerable:!0,get:function(){var t=e.lazyParentModel();return null!=t?t[e.prop]:null},set:function(t){var n=e.lazyParentModel();null!=n&&(n[e.prop]=t,e.$touch())}}},n),r))},children:function(){var e=this;return[].concat(r(this.nestedKeys.map((function(t){return x(e,t)}))),r(this.ruleKeys.map((function(t){return S(e,t)})))).filter(Boolean)}})}),a=l.extend({methods:{isNested:function(e){return void 0!==this.validations[e]()},getRef:function(e){var t=this;return{get proxy(){return t.validations[e]()||!1}}}}}),g=l.extend({computed:{keys:function(){var e=this.getModel();return u(e)?Object.keys(e):[]},tracker:function(){var e=this,t=this.validations.$trackBy;return t?function(n){return"".concat(f(e.rootModel,e.getModelKey(n),t))}:function(e){return"".concat(e)}},getModelLazy:function(){var e=this;return function(){return e.getModel()}},children:function(){var e=this,n=this.validations,r=this.getModel(),i=s({},n);delete i.$trackBy;var o={};return this.keys.map((function(n){var s=e.tracker(n);return o.hasOwnProperty(s)?null:(o[s]=!0,(0,t.h)(l,s,{validations:i,prop:n,lazyParentModel:e.getModelLazy,model:r[n],rootModel:e.rootModel}))})).filter(Boolean)}},methods:{isNested:function(){return!0},getRef:function(e){return this.refs[this.tracker(e)]},hasIter:function(){return!0}}}),x=function(e,n){if("$each"===n)return(0,t.h)(g,n,{validations:e.validations[n],lazyParentModel:e.lazyParentModel,prop:n,lazyModel:e.getModel,rootModel:e.rootModel});var r=e.validations[n];if(Array.isArray(r)){var i=e.rootModel,o=h(r,(function(e){return function(){return f(i,i.$v,e)}}),(function(e){return Array.isArray(e)?e.join("."):e}));return(0,t.h)(a,n,{validations:o,lazyParentModel:c,prop:n,lazyModel:c,rootModel:i})}return(0,t.h)(l,n,{validations:r,lazyParentModel:e.getModel,prop:n,lazyModel:e.getModelKey,rootModel:e.rootModel})},S=function(e,n){return(0,t.h)(o,n,{rule:e.validations[n],lazyParentModel:e.lazyParentModel,lazyModel:e.getModel,rootModel:e.rootModel})};return b={VBase:i,Validation:l}},S=null;var k=function(e,n){var r=function(e){if(S)return S;for(var t=e.constructor;t.super;)t=t.super;return S=t,t}(e),i=x(r),o=i.Validation;return new(0,i.VBase)({computed:{children:function(){var r="function"==typeof n?n.call(e):n;return[(0,t.h)(o,"$v",{validations:r,lazyParentModel:c,prop:"$v",model:e,rootModel:e})]}}})},M={data:function(){var e=this.$options.validations;return e&&(this._vuelidate=k(this,e)),{}},beforeCreate:function(){var e=this.$options;e.validations&&(e.computed||(e.computed={}),e.computed.$v||(e.computed.$v=function(){return this._vuelidate?this._vuelidate.refs.$v.proxy:null}))},beforeDestroy:function(){this._vuelidate&&(this._vuelidate.$destroy(),this._vuelidate=null)}};function O(e){e.mixin(M)}e.validationMixin=M;var C=O;e.default=C}(Yl);const ia=Wo(Yl);export{Jl as A,Hl as B,ql as C,H as D,ct as E,r as F,ia as G,no as I,Xe as N,dt as P,c as S,Ge as T,Il as V,lo as a,ao as b,Gi as c,yo as d,Fi as e,wo as f,xo as g,J as h,io as i,yi as j,Ii as k,bo as l,se as m,to as n,jo as o,Lo as p,qo as q,Fo as r,Yi as s,Ui as t,so as u,Vo as v,vo as w,Jo as x,il as y,zl as z}; diff --git a/kirby/panel/dist/js/vendor.min.js b/kirby/panel/dist/js/vendor.min.js new file mode 100644 index 0000000..a9efa1f --- /dev/null +++ b/kirby/panel/dist/js/vendor.min.js @@ -0,0 +1,16 @@ +var t="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function e(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var n={},r={},i={},o={},s={};function l(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(t);e&&(r=r.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,r)}return n}function a(t){for(var e=1;e=+n}))};var O={};Object.defineProperty(O,"__esModule",{value:!0}),O.default=void 0;var C=(0,i.regex)("email",/^(?:[A-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[A-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9]{2,}(?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$/i);O.default=C;var D={};Object.defineProperty(D,"__esModule",{value:!0}),D.default=void 0;var N=i,T=(0,N.withParams)({type:"ipAddress"},(function(t){if(!(0,N.req)(t))return!0;if("string"!=typeof t)return!1;var e=t.split(".");return 4===e.length&&e.every(A)}));D.default=T;var A=function(t){if(t.length>3||0===t.length)return!1;if("0"===t[0]&&"0"!==t)return!1;if(!t.match(/^\d+$/))return!1;var e=0|+t;return e>=0&&e<=255},E={};Object.defineProperty(E,"__esModule",{value:!0}),E.default=void 0;var $=i;E.default=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:":";return(0,$.withParams)({type:"macAddress"},(function(e){if(!(0,$.req)(e))return!0;if("string"!=typeof e)return!1;var n="string"==typeof t&&""!==t?e.split(t):12===e.length||16===e.length?e.match(/.{2}/g):null;return null!==n&&(6===n.length||8===n.length)&&n.every(P)}))};var P=function(t){return t.toLowerCase().match(/^[0-9a-f]{2}$/)},I={};Object.defineProperty(I,"__esModule",{value:!0}),I.default=void 0;var R=i;I.default=function(t){return(0,R.withParams)({type:"maxLength",max:t},(function(e){return!(0,R.req)(e)||(0,R.len)(e)<=t}))};var z={};Object.defineProperty(z,"__esModule",{value:!0}),z.default=void 0;var _=i;z.default=function(t){return(0,_.withParams)({type:"minLength",min:t},(function(e){return!(0,_.req)(e)||(0,_.len)(e)>=t}))};var B={};Object.defineProperty(B,"__esModule",{value:!0}),B.default=void 0;var j=i,V=(0,j.withParams)({type:"required"},(function(t){return(0,j.req)("string"==typeof t?t.trim():t)}));B.default=V;var F={};Object.defineProperty(F,"__esModule",{value:!0}),F.default=void 0;var L=i;F.default=function(t){return(0,L.withParams)({type:"requiredIf",prop:t},(function(e,n){return!(0,L.ref)(t,this,n)||(0,L.req)(e)}))};var W={};Object.defineProperty(W,"__esModule",{value:!0}),W.default=void 0;var q=i;W.default=function(t){return(0,q.withParams)({type:"requiredUnless",prop:t},(function(e,n){return!!(0,q.ref)(t,this,n)||(0,q.req)(e)}))};var J={};Object.defineProperty(J,"__esModule",{value:!0}),J.default=void 0;var K=i;J.default=function(t){return(0,K.withParams)({type:"sameAs",eq:t},(function(e,n){return e===(0,K.ref)(t,this,n)}))};var H={};Object.defineProperty(H,"__esModule",{value:!0}),H.default=void 0;var Y=(0,i.regex)("url",/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff]\.)+(?:[a-z\u00a1-\uffff]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S*)?$/i);H.default=Y;var U={};Object.defineProperty(U,"__esModule",{value:!0}),U.default=void 0;var G=i;U.default=function(){for(var t=arguments.length,e=new Array(t),n=0;n0&&e.reduce((function(e,n){return e||n.apply(t,r)}),!1)}))};var Z={};Object.defineProperty(Z,"__esModule",{value:!0}),Z.default=void 0;var X=i;Z.default=function(){for(var t=arguments.length,e=new Array(t),n=0;n0&&e.reduce((function(e,n){return e&&n.apply(t,r)}),!0)}))};var Q={};Object.defineProperty(Q,"__esModule",{value:!0}),Q.default=void 0;var tt=i;Q.default=function(t){return(0,tt.withParams)({type:"not"},(function(e,n){return!(0,tt.req)(e)||!t.call(this,e,n)}))};var et={};Object.defineProperty(et,"__esModule",{value:!0}),et.default=void 0;var nt=i;et.default=function(t){return(0,nt.withParams)({type:"minValue",min:t},(function(e){return!(0,nt.req)(e)||(!/\s/.test(e)||e instanceof Date)&&+e>=+t}))};var rt={};Object.defineProperty(rt,"__esModule",{value:!0}),rt.default=void 0;var it=i;rt.default=function(t){return(0,it.withParams)({type:"maxValue",max:t},(function(e){return!(0,it.req)(e)||(!/\s/.test(e)||e instanceof Date)&&+e<=+t}))};var ot={};Object.defineProperty(ot,"__esModule",{value:!0}),ot.default=void 0;var st=(0,i.regex)("integer",/(^[0-9]*$)|(^-[0-9]+$)/);ot.default=st;var lt={};Object.defineProperty(lt,"__esModule",{value:!0}),lt.default=void 0;var at=(0,i.regex)("decimal",/^[-]?\d*(\.\d+)?$/);function ct(t){this.content=t}function ht(t,e,n){for(let r=0;;r++){if(r==t.childCount||r==e.childCount)return t.childCount==e.childCount?null:n;let i=t.child(r),o=e.child(r);if(i!=o){if(!i.sameMarkup(o))return n;if(i.isText&&i.text!=o.text){for(let t=0;i.text[t]==o.text[t];t++)n++;return n}if(i.content.size||o.content.size){let t=ht(i.content,o.content,n+1);if(null!=t)return t}n+=i.nodeSize}else n+=i.nodeSize}}function ut(t,e,n,r){for(let i=t.childCount,o=e.childCount;;){if(0==i||0==o)return i==o?null:{a:n,b:r};let s=t.child(--i),l=e.child(--o),a=s.nodeSize;if(s!=l){if(!s.sameMarkup(l))return{a:n,b:r};if(s.isText&&s.text!=l.text){let t=0,e=Math.min(s.text.length,l.text.length);for(;t>1}},ct.from=function(t){if(t instanceof ct)return t;var e=[];if(t)for(var n in t)e.push(n,t[n]);return new ct(e)};class dt{constructor(t,e){if(this.content=t,this.size=e||0,null==e)for(let n=0;nt&&!1!==n(l,r+s,i||null,o)&&l.content.size){let i=s+1;l.nodesBetween(Math.max(0,t-i),Math.min(l.content.size,e-i),n,r+i)}s=a}}descendants(t){this.nodesBetween(0,this.size,t)}textBetween(t,e,n,r){let i="",o=!0;return this.nodesBetween(t,e,((s,l)=>{s.isText?(i+=s.text.slice(Math.max(t,l)-l,e-l),o=!n):s.isLeaf?(r?i+="function"==typeof r?r(s):r:s.type.spec.leafText&&(i+=s.type.spec.leafText(s)),o=!n):!o&&s.isBlock&&(i+=n,o=!0)}),0),i}append(t){if(!t.size)return this;if(!this.size)return t;let e=this.lastChild,n=t.firstChild,r=this.content.slice(),i=0;for(e.isText&&e.sameMarkup(n)&&(r[r.length-1]=e.withText(e.text+n.text),i=1);it)for(let i=0,o=0;ot&&((oe)&&(s=s.isText?s.cut(Math.max(0,t-o),Math.min(s.text.length,e-o)):s.cut(Math.max(0,t-o-1),Math.min(s.content.size,e-o-1))),n.push(s),r+=s.nodeSize),o=l}return new dt(n,r)}cutByIndex(t,e){return t==e?dt.empty:0==t&&e==this.content.length?this:new dt(this.content.slice(t,e))}replaceChild(t,e){let n=this.content[t];if(n==e)return this;let r=this.content.slice(),i=this.size+e.nodeSize-n.nodeSize;return r[t]=e,new dt(r,i)}addToStart(t){return new dt([t].concat(this.content),this.size+t.nodeSize)}addToEnd(t){return new dt(this.content.concat(t),this.size+t.nodeSize)}eq(t){if(this.content.length!=t.content.length)return!1;for(let e=0;ethis.size||t<0)throw new RangeError(`Position ${t} outside of fragment (${this})`);for(let n=0,r=0;;n++){let i=r+this.child(n).nodeSize;if(i>=t)return i==t||e>0?pt(n+1,i):pt(n,r);r=i}}toString(){return"<"+this.toStringInner()+">"}toStringInner(){return this.content.join(", ")}toJSON(){return this.content.length?this.content.map((t=>t.toJSON())):null}static fromJSON(t,e){if(!e)return dt.empty;if(!Array.isArray(e))throw new RangeError("Invalid input for Fragment.fromJSON");return new dt(e.map(t.nodeFromJSON))}static fromArray(t){if(!t.length)return dt.empty;let e,n=0;for(let r=0;rthis.type.rank&&(e||(e=t.slice(0,r)),e.push(this),n=!0),e&&e.push(i)}}return e||(e=t.slice()),n||e.push(this),e}removeFromSet(t){for(let e=0;et.type.rank-e.type.rank)),e}}gt.none=[];class yt extends Error{}class vt{constructor(t,e,n){this.content=t,this.openStart=e,this.openEnd=n}get size(){return this.content.size-this.openStart-this.openEnd}insertAt(t,e){let n=bt(this.content,t+this.openStart,e);return n&&new vt(n,this.openStart,this.openEnd)}removeBetween(t,e){return new vt(wt(this.content,t+this.openStart,e+this.openStart),this.openStart,this.openEnd)}eq(t){return this.content.eq(t.content)&&this.openStart==t.openStart&&this.openEnd==t.openEnd}toString(){return this.content+"("+this.openStart+","+this.openEnd+")"}toJSON(){if(!this.content.size)return null;let t={content:this.content.toJSON()};return this.openStart>0&&(t.openStart=this.openStart),this.openEnd>0&&(t.openEnd=this.openEnd),t}static fromJSON(t,e){if(!e)return vt.empty;let n=e.openStart||0,r=e.openEnd||0;if("number"!=typeof n||"number"!=typeof r)throw new RangeError("Invalid input for Slice.fromJSON");return new vt(dt.fromJSON(t,e.content),n,r)}static maxOpen(t,e=!0){let n=0,r=0;for(let i=t.firstChild;i&&!i.isLeaf&&(e||!i.type.spec.isolating);i=i.firstChild)n++;for(let i=t.lastChild;i&&!i.isLeaf&&(e||!i.type.spec.isolating);i=i.lastChild)r++;return new vt(t,n,r)}}function wt(t,e,n){let{index:r,offset:i}=t.findIndex(e),o=t.maybeChild(r),{index:s,offset:l}=t.findIndex(n);if(i==e||o.isText){if(l!=n&&!t.child(s).isText)throw new RangeError("Removing non-flat range");return t.cut(0,e).append(t.cut(n))}if(r!=s)throw new RangeError("Removing non-flat range");return t.replaceChild(r,o.copy(wt(o.content,e-i-1,n-i-1)))}function bt(t,e,n,r){let{index:i,offset:o}=t.findIndex(e),s=t.maybeChild(i);if(o==e||s.isText)return r&&!r.canReplace(i,i,n)?null:t.cut(0,e).append(n).append(t.cut(e));let l=bt(s.content,e-o-1,n);return l&&t.replaceChild(i,s.copy(l))}function xt(t,e,n){if(n.openStart>t.depth)throw new yt("Inserted content deeper than insertion position");if(t.depth-n.openStart!=e.depth-n.openEnd)throw new yt("Inconsistent open depths");return St(t,e,n,0)}function St(t,e,n,r){let i=t.index(r),o=t.node(r);if(i==e.index(r)&&r=0;i--)r=e.node(i).copy(dt.from(r));return{start:r.resolveNoCache(t.openStart+n),end:r.resolveNoCache(r.content.size-t.openEnd-n)}}(n,t);return Dt(o,Nt(t,i,s,e,r))}{let r=t.parent,i=r.content;return Dt(r,i.cut(0,t.parentOffset).append(n.content).append(i.cut(e.parentOffset)))}}return Dt(o,Tt(t,e,r))}function kt(t,e){if(!e.type.compatibleContent(t.type))throw new yt("Cannot join "+e.type.name+" onto "+t.type.name)}function Mt(t,e,n){let r=t.node(n);return kt(r,e.node(n)),r}function Ot(t,e){let n=e.length-1;n>=0&&t.isText&&t.sameMarkup(e[n])?e[n]=t.withText(e[n].text+t.text):e.push(t)}function Ct(t,e,n,r){let i=(e||t).node(n),o=0,s=e?e.index(n):i.childCount;t&&(o=t.index(n),t.depth>n?o++:t.textOffset&&(Ot(t.nodeAfter,r),o++));for(let l=o;li&&Mt(t,e,i+1),s=r.depth>i&&Mt(n,r,i+1),l=[];return Ct(null,t,i,l),o&&s&&e.index(i)==n.index(i)?(kt(o,s),Ot(Dt(o,Nt(t,e,n,r,i+1)),l)):(o&&Ot(Dt(o,Tt(t,e,i+1)),l),Ct(e,n,i,l),s&&Ot(Dt(s,Tt(n,r,i+1)),l)),Ct(r,null,i,l),new dt(l)}function Tt(t,e,n){let r=[];if(Ct(null,t,n,r),t.depth>n){Ot(Dt(Mt(t,e,n+1),Tt(t,e,n+1)),r)}return Ct(e,null,n,r),new dt(r)}vt.empty=new vt(dt.empty,0,0);class At{constructor(t,e,n){this.pos=t,this.path=e,this.parentOffset=n,this.depth=e.length/3-1}resolveDepth(t){return null==t?this.depth:t<0?this.depth+t:t}get parent(){return this.node(this.depth)}get doc(){return this.node(0)}node(t){return this.path[3*this.resolveDepth(t)]}index(t){return this.path[3*this.resolveDepth(t)+1]}indexAfter(t){return t=this.resolveDepth(t),this.index(t)+(t!=this.depth||this.textOffset?1:0)}start(t){return 0==(t=this.resolveDepth(t))?0:this.path[3*t-1]+1}end(t){return t=this.resolveDepth(t),this.start(t)+this.node(t).content.size}before(t){if(!(t=this.resolveDepth(t)))throw new RangeError("There is no position before the top-level node");return t==this.depth+1?this.pos:this.path[3*t-1]}after(t){if(!(t=this.resolveDepth(t)))throw new RangeError("There is no position after the top-level node");return t==this.depth+1?this.pos:this.path[3*t-1]+this.path[3*t].nodeSize}get textOffset(){return this.pos-this.path[this.path.length-1]}get nodeAfter(){let t=this.parent,e=this.index(this.depth);if(e==t.childCount)return null;let n=this.pos-this.path[this.path.length-1],r=t.child(e);return n?t.child(e).cut(n):r}get nodeBefore(){let t=this.index(this.depth),e=this.pos-this.path[this.path.length-1];return e?this.parent.child(t).cut(0,e):0==t?null:this.parent.child(t-1)}posAtIndex(t,e){e=this.resolveDepth(e);let n=this.path[3*e],r=0==e?0:this.path[3*e-1]+1;for(let i=0;i0;e--)if(this.start(e)<=t&&this.end(e)>=t)return e;return 0}blockRange(t=this,e){if(t.pos=0;n--)if(t.pos<=this.end(n)&&(!e||e(this.node(n))))return new It(this,t,n);return null}sameParent(t){return this.pos-this.parentOffset==t.pos-t.parentOffset}max(t){return t.pos>this.pos?t:this}min(t){return t.pos=0&&e<=t.content.size))throw new RangeError("Position "+e+" out of range");let n=[],r=0,i=e;for(let o=t;;){let{index:t,offset:e}=o.content.findIndex(i),s=i-e;if(n.push(o,t,r+e),!s)break;if(o=o.child(t),o.isText)break;i=s-1,r+=e+1}return new At(e,n,i)}static resolveCached(t,e){for(let r=0;rt&&this.nodesBetween(t,e,(t=>(n.isInSet(t.marks)&&(r=!0),!r))),r}get isBlock(){return this.type.isBlock}get isTextblock(){return this.type.isTextblock}get inlineContent(){return this.type.inlineContent}get isInline(){return this.type.isInline}get isText(){return this.type.isText}get isLeaf(){return this.type.isLeaf}get isAtom(){return this.type.isAtom}toString(){if(this.type.spec.toDebugString)return this.type.spec.toDebugString(this);let t=this.type.name;return this.content.size&&(t+="("+this.content.toStringInner()+")"),Bt(this.marks,t)}contentMatchAt(t){let e=this.type.contentMatch.matchFragment(this.content,0,t);if(!e)throw new Error("Called contentMatchAt on a node with invalid content");return e}canReplace(t,e,n=dt.empty,r=0,i=n.childCount){let o=this.contentMatchAt(t).matchFragment(n,r,i),s=o&&o.matchFragment(this.content,e);if(!s||!s.validEnd)return!1;for(let l=r;lt.type.name))}`);this.content.forEach((t=>t.check()))}toJSON(){let t={type:this.type.name};for(let e in this.attrs){t.attrs=this.attrs;break}return this.content.size&&(t.content=this.content.toJSON()),this.marks.length&&(t.marks=this.marks.map((t=>t.toJSON()))),t}static fromJSON(t,e){if(!e)throw new RangeError("Invalid input for Node.fromJSON");let n=null;if(e.marks){if(!Array.isArray(e.marks))throw new RangeError("Invalid mark data for Node.fromJSON");n=e.marks.map(t.markFromJSON)}if("text"==e.type){if("string"!=typeof e.text)throw new RangeError("Invalid text node in JSON");return t.text(e.text,n)}let r=dt.fromJSON(t,e.content);return t.nodeType(e.type).create(e.attrs,r,n)}}zt.prototype.text=void 0;class _t extends zt{constructor(t,e,n,r){if(super(t,e,null,r),!n)throw new RangeError("Empty text nodes are not allowed");this.text=n}toString(){return this.type.spec.toDebugString?this.type.spec.toDebugString(this):Bt(this.marks,JSON.stringify(this.text))}get textContent(){return this.text}textBetween(t,e){return this.text.slice(t,e)}get nodeSize(){return this.text.length}mark(t){return t==this.marks?this:new _t(this.type,this.attrs,this.text,t)}withText(t){return t==this.text?this:new _t(this.type,this.attrs,t,this.marks)}cut(t=0,e=this.text.length){return 0==t&&e==this.text.length?this:this.withText(this.text.slice(t,e))}eq(t){return this.sameMarkup(t)&&this.text==t.text}toJSON(){let t=super.toJSON();return t.text=this.text,t}}function Bt(t,e){for(let n=t.length-1;n>=0;n--)e=t[n].type.name+"("+e+")";return e}class jt{constructor(t){this.validEnd=t,this.next=[],this.wrapCache=[]}static parse(t,e){let n=new Vt(t,e);if(null==n.next)return jt.empty;let r=Ft(n);n.next&&n.err("Unexpected trailing text");let i=function(t){let e=Object.create(null);return n(Ht(t,0));function n(r){let i=[];r.forEach((e=>{t[e].forEach((({term:e,to:n})=>{if(!e)return;let r;for(let t=0;t{r||i.push([e,r=[]]),-1==r.indexOf(t)&&r.push(t)}))}))}));let o=e[r.join(",")]=new jt(r.indexOf(t.length-1)>-1);for(let t=0;tt.to=e))}function o(t,e){if("choice"==t.type)return t.exprs.reduce(((t,n)=>t.concat(o(n,e))),[]);if("seq"!=t.type){if("star"==t.type){let s=n();return r(e,s),i(o(t.expr,s),s),[r(s)]}if("plus"==t.type){let s=n();return i(o(t.expr,e),s),i(o(t.expr,s),s),[r(s)]}if("opt"==t.type)return[r(e)].concat(o(t.expr,e));if("range"==t.type){let s=e;for(let e=0;et.createAndFill())));for(let t=0;t=this.next.length)throw new RangeError(`There's no ${t}th edge in this content match`);return this.next[t]}toString(){let t=[];return function e(n){t.push(n);for(let r=0;r{let r=n+(e.validEnd?"*":" ")+" ";for(let i=0;i"+t.indexOf(e.next[i].next);return r})).join("\n")}}jt.empty=new jt(!0);class Vt{constructor(t,e){this.string=t,this.nodeTypes=e,this.inline=null,this.pos=0,this.tokens=t.split(/\s*(?=\b|\W|$)/),""==this.tokens[this.tokens.length-1]&&this.tokens.pop(),""==this.tokens[0]&&this.tokens.shift()}get next(){return this.tokens[this.pos]}eat(t){return this.next==t&&(this.pos++||!0)}err(t){throw new SyntaxError(t+" (in content expression '"+this.string+"')")}}function Ft(t){let e=[];do{e.push(Lt(t))}while(t.eat("|"));return 1==e.length?e[0]:{type:"choice",exprs:e}}function Lt(t){let e=[];do{e.push(Wt(t))}while(t.next&&")"!=t.next&&"|"!=t.next);return 1==e.length?e[0]:{type:"seq",exprs:e}}function Wt(t){let e=function(t){if(t.eat("(")){let e=Ft(t);return t.eat(")")||t.err("Missing closing paren"),e}if(!/\W/.test(t.next)){let e=function(t,e){let n=t.nodeTypes,r=n[e];if(r)return[r];let i=[];for(let o in n){let t=n[o];t.groups.indexOf(e)>-1&&i.push(t)}0==i.length&&t.err("No node type or group '"+e+"' found");return i}(t,t.next).map((e=>(null==t.inline?t.inline=e.isInline:t.inline!=e.isInline&&t.err("Mixing inline and block content"),{type:"name",value:e})));return t.pos++,1==e.length?e[0]:{type:"choice",exprs:e}}t.err("Unexpected token '"+t.next+"'")}(t);for(;;)if(t.eat("+"))e={type:"plus",expr:e};else if(t.eat("*"))e={type:"star",expr:e};else if(t.eat("?"))e={type:"opt",expr:e};else{if(!t.eat("{"))break;e=Jt(t,e)}return e}function qt(t){/\D/.test(t.next)&&t.err("Expected number, got '"+t.next+"'");let e=Number(t.next);return t.pos++,e}function Jt(t,e){let n=qt(t),r=n;return t.eat(",")&&(r="}"!=t.next?qt(t):-1),t.eat("}")||t.err("Unclosed braced range"),{type:"range",min:n,max:r,expr:e}}function Kt(t,e){return e-t}function Ht(t,e){let n=[];return function e(r){let i=t[r];if(1==i.length&&!i[0].term)return e(i[0].to);n.push(r);for(let t=0;t-1}allowsMarks(t){if(null==this.markSet)return!0;for(let e=0;er[e]=new t(e,n,i)));let i=n.spec.topNode||"doc";if(!r[i])throw new RangeError("Schema is missing its top node type ('"+i+"')");if(!r.text)throw new RangeError("Every schema needs a 'text' type");for(let t in r.text.attrs)throw new RangeError("The text node type should not have attributes");return r}};class Xt{constructor(t){this.hasDefault=Object.prototype.hasOwnProperty.call(t,"default"),this.default=t.default}get isRequired(){return!this.hasDefault}}class Qt{constructor(t,e,n,r){this.name=t,this.rank=e,this.schema=n,this.spec=r,this.attrs=Gt(r.attrs),this.excluded=null;let i=Yt(this.attrs);this.instance=i?new gt(this,i):null}create(t=null){return!t&&this.instance?this.instance:new gt(this,Ut(this.attrs,t))}static compile(t,e){let n=Object.create(null),r=0;return t.forEach(((t,i)=>n[t]=new Qt(t,r++,e,i))),n}removeFromSet(t){for(var e=0;e-1}}class te{constructor(t){this.cached=Object.create(null);let e=this.spec={};for(let r in t)e[r]=t[r];e.nodes=ct.from(t.nodes),e.marks=ct.from(t.marks||{}),this.nodes=Zt.compile(this.spec.nodes,this),this.marks=Qt.compile(this.spec.marks,this);let n=Object.create(null);for(let r in this.nodes){if(r in this.marks)throw new RangeError(r+" can not be both a node and a mark");let t=this.nodes[r],e=t.spec.content||"",i=t.spec.marks;t.contentMatch=n[e]||(n[e]=jt.parse(e,this.nodes)),t.inlineContent=t.contentMatch.inlineContent,t.markSet="_"==i?null:i?ee(this,i.split(" ")):""!=i&&t.inlineContent?null:[]}for(let r in this.marks){let t=this.marks[r],e=t.spec.excludes;t.excluded=null==e?[t]:""==e?[]:ee(this,e.split(" "))}this.nodeFromJSON=this.nodeFromJSON.bind(this),this.markFromJSON=this.markFromJSON.bind(this),this.topNodeType=this.nodes[this.spec.topNode||"doc"],this.cached.wrappings=Object.create(null)}node(t,e=null,n,r){if("string"==typeof t)t=this.nodeType(t);else{if(!(t instanceof Zt))throw new RangeError("Invalid node type: "+t);if(t.schema!=this)throw new RangeError("Node type from different schema used ("+t.name+")")}return t.createChecked(e,n,r)}text(t,e){let n=this.nodes.text;return new _t(n,n.defaultAttrs,t,gt.setFrom(e))}mark(t,e){return"string"==typeof t&&(t=this.marks[t]),t.create(e)}nodeFromJSON(t){return zt.fromJSON(this,t)}markFromJSON(t){return gt.fromJSON(this,t)}nodeType(t){let e=this.nodes[t];if(!e)throw new RangeError("Unknown node type: "+t);return e}}function ee(t,e){let n=[];for(let r=0;r-1)&&n.push(s=r)}if(!s)throw new SyntaxError("Unknown mark type: '"+e[r]+"'")}return n}class ne{constructor(t,e){this.schema=t,this.rules=e,this.tags=[],this.styles=[],e.forEach((t=>{t.tag?this.tags.push(t):t.style&&this.styles.push(t)})),this.normalizeLists=!this.tags.some((e=>{if(!/^(ul|ol)\b/.test(e.tag)||!e.node)return!1;let n=t.nodes[e.node];return n.contentMatch.matchType(n)}))}parse(t,e={}){let n=new ae(this,e,!1);return n.addAll(t,e.from,e.to),n.finish()}parseSlice(t,e={}){let n=new ae(this,e,!0);return n.addAll(t,e.from,e.to),vt.maxOpen(n.finish())}matchTag(t,e,n){for(let r=n?this.tags.indexOf(n)+1:0;rt.length&&(61!=o.charCodeAt(t.length)||o.slice(t.length+1)!=e))){if(r.getAttrs){let t=r.getAttrs(e);if(!1===t)continue;r.attrs=t||void 0}return r}}}static schemaRules(t){let e=[];function n(t){let n=null==t.priority?50:t.priority,r=0;for(;r{n(t=he(t)),t.mark||t.ignore||t.clearMark||(t.mark=r)}))}for(let r in t.nodes){let e=t.nodes[r].spec.parseDOM;e&&e.forEach((t=>{n(t=he(t)),t.node||t.ignore||t.mark||(t.node=r)}))}return e}static fromSchema(t){return t.cached.domParser||(t.cached.domParser=new ne(t,ne.schemaRules(t)))}}const re={address:!0,article:!0,aside:!0,blockquote:!0,canvas:!0,dd:!0,div:!0,dl:!0,fieldset:!0,figcaption:!0,figure:!0,footer:!0,form:!0,h1:!0,h2:!0,h3:!0,h4:!0,h5:!0,h6:!0,header:!0,hgroup:!0,hr:!0,li:!0,noscript:!0,ol:!0,output:!0,p:!0,pre:!0,section:!0,table:!0,tfoot:!0,ul:!0},ie={head:!0,noscript:!0,object:!0,script:!0,style:!0,title:!0},oe={ol:!0,ul:!0};function se(t,e,n){return null!=e?(e?1:0)|("full"===e?2:0):t&&"pre"==t.whitespace?3:-5&n}class le{constructor(t,e,n,r,i,o,s){this.type=t,this.attrs=e,this.marks=n,this.pendingMarks=r,this.solid=i,this.options=s,this.content=[],this.activeMarks=gt.none,this.stashMarks=[],this.match=o||(4&s?null:t.contentMatch)}findWrapping(t){if(!this.match){if(!this.type)return[];let e=this.type.contentMatch.fillBefore(dt.from(t));if(!e){let e,n=this.type.contentMatch;return(e=n.findWrapping(t.type))?(this.match=n,e):null}this.match=this.type.contentMatch.matchFragment(e)}return this.match.findWrapping(t.type)}finish(t){if(!(1&this.options)){let t,e=this.content[this.content.length-1];if(e&&e.isText&&(t=/[ \t\r\n\u000c]+$/.exec(e.text))){let n=e;e.text.length==t[0].length?this.content.pop():this.content[this.content.length-1]=n.withText(n.text.slice(0,n.text.length-t[0].length))}}let e=dt.from(this.content);return!t&&this.match&&(e=e.append(this.match.fillBefore(dt.empty,!0))),this.type?this.type.create(this.attrs,e,this.marks):e}popFromStashMark(t){for(let e=this.stashMarks.length-1;e>=0;e--)if(t.eq(this.stashMarks[e]))return this.stashMarks.splice(e,1)[0]}applyPending(t){for(let e=0,n=this.pendingMarks;ethis.addAll(t))),e&&this.sync(n),this.needsBlock=o}else this.withStyleRules(t,(()=>{this.addElementByRule(t,i,!1===i.consuming?n:void 0)}))}leafFallback(t){"BR"==t.nodeName&&this.top.type&&this.top.type.inlineContent&&this.addTextNode(t.ownerDocument.createTextNode("\n"))}ignoreFallback(t){"BR"!=t.nodeName||this.top.type&&this.top.type.inlineContent||this.findPlace(this.parser.schema.text("-"))}readStyles(t){let e=gt.none,n=gt.none;for(let r=0;r{o.clearMark(t)&&(n=t.addToSet(n))})):e=this.parser.schema.marks[o.mark].create(o.attrs).addToSet(e),!1!==o.consuming)break;i=o}return[e,n]}addElementByRule(t,e,n){let r,i,o;if(e.node)i=this.parser.schema.nodes[e.node],i.isLeaf?this.insertNode(i.create(e.attrs))||this.leafFallback(t):r=this.enter(i,e.attrs||null,e.preserveWhitespace);else{o=this.parser.schema.marks[e.mark].create(e.attrs),this.addPendingMark(o)}let s=this.top;if(i&&i.isLeaf)this.findInside(t);else if(n)this.addElement(t,n);else if(e.getContent)this.findInside(t),e.getContent(t,this.parser.schema).forEach((t=>this.insertNode(t)));else{let n=t;"string"==typeof e.contentElement?n=t.querySelector(e.contentElement):"function"==typeof e.contentElement?n=e.contentElement(t):e.contentElement&&(n=e.contentElement),this.findAround(t,n,!0),this.addAll(n)}r&&this.sync(s)&&this.open--,o&&this.removePendingMark(o,s)}addAll(t,e,n){let r=e||0;for(let i=e?t.childNodes[e]:t.firstChild,o=null==n?null:t.childNodes[n];i!=o;i=i.nextSibling,++r)this.findAtPoint(t,r),this.addDOM(i);this.findAtPoint(t,r)}findPlace(t){let e,n;for(let r=this.open;r>=0;r--){let i=this.nodes[r],o=i.findWrapping(t);if(o&&(!e||e.length>o.length)&&(e=o,n=i,!o.length))break;if(i.solid)break}if(!e)return!1;this.sync(n);for(let r=0;rthis.open){for(;e>this.open;e--)this.nodes[e-1].content.push(this.nodes[e].finish(t));this.nodes.length=this.open+1}}finish(){return this.open=0,this.closeExtra(this.isOpen),this.nodes[0].finish(this.isOpen||this.options.topOpen)}sync(t){for(let e=this.open;e>=0;e--)if(this.nodes[e]==t)return this.open=e,!0;return!1}get currentPos(){this.closeExtra();let t=0;for(let e=this.open;e>=0;e--){let n=this.nodes[e].content;for(let e=n.length-1;e>=0;e--)t+=n[e].nodeSize;e&&t++}return t}findAtPoint(t,e){if(this.find)for(let n=0;n-1)return t.split(/\s*\|\s*/).some(this.matchesContext,this);let e=t.split("/"),n=this.options.context,r=!(this.isOpen||n&&n.parent.type!=this.nodes[0].type),i=-(n?n.depth+1:0)+(r?0:1),o=(t,s)=>{for(;t>=0;t--){let l=e[t];if(""==l){if(t==e.length-1||0==t)continue;for(;s>=i;s--)if(o(t-1,s))return!0;return!1}{let t=s>0||0==s&&r?this.nodes[s].type:n&&s>=i?n.node(s-i).type:null;if(!t||t.name!=l&&-1==t.groups.indexOf(l))return!1;s--}}return!0};return o(e.length-1,this.open)}textblockFromContext(){let t=this.options.context;if(t)for(let e=t.depth;e>=0;e--){let n=t.node(e).contentMatchAt(t.indexAfter(e)).defaultType;if(n&&n.isTextblock&&n.defaultAttrs)return n}for(let e in this.parser.schema.nodes){let t=this.parser.schema.nodes[e];if(t.isTextblock&&t.defaultAttrs)return t}}addPendingMark(t){let e=function(t,e){for(let n=0;n=0;n--){let r=this.nodes[n];if(r.pendingMarks.lastIndexOf(t)>-1)r.pendingMarks=t.removeFromSet(r.pendingMarks);else{r.activeMarks=t.removeFromSet(r.activeMarks);let e=r.popFromStashMark(t);e&&r.type&&r.type.allowsMarkType(e.type)&&(r.activeMarks=e.addToSet(r.activeMarks))}if(r==e)break}}}function ce(t,e){return(t.matches||t.msMatchesSelector||t.webkitMatchesSelector||t.mozMatchesSelector).call(t,e)}function he(t){let e={};for(let n in t)e[n]=t[n];return e}function ue(t,e){let n=e.schema.nodes;for(let r in n){let i=n[r];if(!i.allowsMarkType(t))continue;let o=[],s=t=>{o.push(t);for(let n=0;n{if(i.length||t.marks.length){let n=0,o=0;for(;n=0;r--){let i=this.serializeMark(t.marks[r],t.isInline,e);i&&((i.contentDOM||i.dom).appendChild(n),n=i.dom)}return n}serializeMark(t,e,n={}){let r=this.marks[t.type.name];return r&&de.renderSpec(pe(n),r(t,e))}static renderSpec(t,e,n=null){if("string"==typeof e)return{dom:t.createTextNode(e)};if(null!=e.nodeType)return{dom:e};if(e.dom&&null!=e.dom.nodeType)return e;let r,i=e[0],o=i.indexOf(" ");o>0&&(n=i.slice(0,o),i=i.slice(o+1));let s=n?t.createElementNS(n,i):t.createElement(i),l=e[1],a=1;if(l&&"object"==typeof l&&null==l.nodeType&&!Array.isArray(l)){a=2;for(let t in l)if(null!=l[t]){let e=t.indexOf(" ");e>0?s.setAttributeNS(t.slice(0,e),t.slice(e+1),l[t]):s.setAttribute(t,l[t])}}for(let c=a;ca)throw new RangeError("Content hole must be the only child of its parent node");return{dom:s,contentDOM:s}}{let{dom:e,contentDOM:o}=de.renderSpec(t,i,n);if(s.appendChild(e),o){if(r)throw new RangeError("Multiple content holes");r=o}}}return{dom:s,contentDOM:r}}static fromSchema(t){return t.cached.domSerializer||(t.cached.domSerializer=new de(this.nodesFromSchema(t),this.marksFromSchema(t)))}static nodesFromSchema(t){let e=fe(t.nodes);return e.text||(e.text=t=>t.text),e}static marksFromSchema(t){return fe(t.marks)}}function fe(t){let e={};for(let n in t){let r=t[n].spec.toDOM;r&&(e[n]=r)}return e}function pe(t){return t.document||window.document}const me=Math.pow(2,16);function ge(t){return 65535&t}class ye{constructor(t,e,n){this.pos=t,this.delInfo=e,this.recover=n}get deleted(){return(8&this.delInfo)>0}get deletedBefore(){return(5&this.delInfo)>0}get deletedAfter(){return(6&this.delInfo)>0}get deletedAcross(){return(4&this.delInfo)>0}}class ve{constructor(t,e=!1){if(this.ranges=t,this.inverted=e,!t.length&&ve.empty)return ve.empty}recover(t){let e=0,n=ge(t);if(!this.inverted)for(let r=0;rt)break;let a=this.ranges[s+i],c=this.ranges[s+o],h=l+a;if(t<=h){let i=l+r+((a?t==l?-1:t==h?1:e:e)<0?0:c);if(n)return i;let o=t==(e<0?l:h)?null:s/3+(t-l)*me,u=t==l?2:t==h?1:4;return(e<0?t!=l:t!=h)&&(u|=8),new ye(i,u,o)}r+=c-a}return n?t+r:new ye(t+r,0,null)}touches(t,e){let n=0,r=ge(e),i=this.inverted?2:1,o=this.inverted?1:2;for(let s=0;st)break;let l=this.ranges[s+i];if(t<=e+l&&s==3*r)return!0;n+=this.ranges[s+o]-l}return!1}forEach(t){let e=this.inverted?2:1,n=this.inverted?1:2;for(let r=0,i=0;r=0;e--){let r=t.getMirror(e);this.appendMap(t.maps[e].invert(),null!=r&&r>e?n-r-1:void 0)}}invert(){let t=new we;return t.appendMappingInverted(this),t}map(t,e=1){if(this.mirror)return this._map(t,e,!0);for(let n=this.from;ni&&et.isAtom&&e.type.allowsMarkType(this.mark.type)?t.mark(this.mark.addToSet(t.marks)):t),r),e.openStart,e.openEnd);return Se.fromReplace(t,this.from,this.to,i)}invert(){return new Oe(this.from,this.to,this.mark)}map(t){let e=t.mapResult(this.from,1),n=t.mapResult(this.to,-1);return e.deleted&&n.deleted||e.pos>=n.pos?null:new Me(e.pos,n.pos,this.mark)}merge(t){return t instanceof Me&&t.mark.eq(this.mark)&&this.from<=t.to&&this.to>=t.from?new Me(Math.min(this.from,t.from),Math.max(this.to,t.to),this.mark):null}toJSON(){return{stepType:"addMark",mark:this.mark.toJSON(),from:this.from,to:this.to}}static fromJSON(t,e){if("number"!=typeof e.from||"number"!=typeof e.to)throw new RangeError("Invalid input for AddMarkStep.fromJSON");return new Me(e.from,e.to,t.markFromJSON(e.mark))}}xe.jsonID("addMark",Me);class Oe extends xe{constructor(t,e,n){super(),this.from=t,this.to=e,this.mark=n}apply(t){let e=t.slice(this.from,this.to),n=new vt(ke(e.content,(t=>t.mark(this.mark.removeFromSet(t.marks))),t),e.openStart,e.openEnd);return Se.fromReplace(t,this.from,this.to,n)}invert(){return new Me(this.from,this.to,this.mark)}map(t){let e=t.mapResult(this.from,1),n=t.mapResult(this.to,-1);return e.deleted&&n.deleted||e.pos>=n.pos?null:new Oe(e.pos,n.pos,this.mark)}merge(t){return t instanceof Oe&&t.mark.eq(this.mark)&&this.from<=t.to&&this.to>=t.from?new Oe(Math.min(this.from,t.from),Math.max(this.to,t.to),this.mark):null}toJSON(){return{stepType:"removeMark",mark:this.mark.toJSON(),from:this.from,to:this.to}}static fromJSON(t,e){if("number"!=typeof e.from||"number"!=typeof e.to)throw new RangeError("Invalid input for RemoveMarkStep.fromJSON");return new Oe(e.from,e.to,t.markFromJSON(e.mark))}}xe.jsonID("removeMark",Oe);class Ce extends xe{constructor(t,e){super(),this.pos=t,this.mark=e}apply(t){let e=t.nodeAt(this.pos);if(!e)return Se.fail("No node at mark step's position");let n=e.type.create(e.attrs,null,this.mark.addToSet(e.marks));return Se.fromReplace(t,this.pos,this.pos+1,new vt(dt.from(n),0,e.isLeaf?0:1))}invert(t){let e=t.nodeAt(this.pos);if(e){let t=this.mark.addToSet(e.marks);if(t.length==e.marks.length){for(let n=0;nn.pos?null:new Te(e.pos,n.pos,r,i,this.slice,this.insert,this.structure)}toJSON(){let t={stepType:"replaceAround",from:this.from,to:this.to,gapFrom:this.gapFrom,gapTo:this.gapTo,insert:this.insert};return this.slice.size&&(t.slice=this.slice.toJSON()),this.structure&&(t.structure=!0),t}static fromJSON(t,e){if("number"!=typeof e.from||"number"!=typeof e.to||"number"!=typeof e.gapFrom||"number"!=typeof e.gapTo||"number"!=typeof e.insert)throw new RangeError("Invalid input for ReplaceAroundStep.fromJSON");return new Te(e.from,e.to,e.gapFrom,e.gapTo,vt.fromJSON(t,e.slice),e.insert,!!e.structure)}}function Ae(t,e,n){let r=t.resolve(e),i=n-e,o=r.depth;for(;i>0&&o>0&&r.indexAfter(o)==r.node(o).childCount;)o--,i--;if(i>0){let t=r.node(o).maybeChild(r.indexAfter(o));for(;i>0;){if(!t||t.isLeaf)return!0;t=t.firstChild,i--}}return!1}function Ee(t,e,n){return(0==e||t.canReplace(e,t.childCount))&&(n==t.childCount||t.canReplace(0,n))}function $e(t){let e=t.parent.content.cutByIndex(t.startIndex,t.endIndex);for(let n=t.depth;;--n){let r=t.$from.node(n),i=t.$from.index(n),o=t.$to.indexAfter(n);if(no;c--,h--){let t=i.node(c),e=i.index(c);if(t.type.spec.isolating)return!1;let n=t.content.cutByIndex(e,t.childCount),o=r&&r[h+1];o&&(n=n.replaceChild(0,o.type.create(o.attrs)));let s=r&&r[h]||t;if(!t.canReplace(e+1,t.childCount)||!s.type.validContent(n))return!1}let l=i.indexAfter(o),a=r&&r[0];return i.node(o).canReplaceWith(l,l,a?a.type:i.node(o+1).type)}function ze(t,e){let n=t.resolve(e),r=n.index();return i=n.nodeBefore,o=n.nodeAfter,!(!i||!o||i.isLeaf||!i.canAppend(o))&&n.parent.canReplace(r,r+1);var i,o}function _e(t,e,n=e,r=vt.empty){if(e==n&&!r.size)return null;let i=t.resolve(e),o=t.resolve(n);return Be(i,o,r)?new Ne(e,n,r):new je(i,o,r).fit()}function Be(t,e,n){return!n.openStart&&!n.openEnd&&t.start()==e.start()&&t.parent.canReplace(t.index(),e.index(),n.content)}xe.jsonID("replaceAround",Te);class je{constructor(t,e,n){this.$from=t,this.$to=e,this.unplaced=n,this.frontier=[],this.placed=dt.empty;for(let r=0;r<=t.depth;r++){let e=t.node(r);this.frontier.push({type:e.type,match:e.contentMatchAt(t.indexAfter(r))})}for(let r=t.depth;r>0;r--)this.placed=dt.from(t.node(r).copy(this.placed))}get depth(){return this.frontier.length-1}fit(){for(;this.unplaced.size;){let t=this.findFittable();t?this.placeNodes(t):this.openMore()||this.dropNode()}let t=this.mustMoveInline(),e=this.placed.size-this.depth-this.$from.depth,n=this.$from,r=this.close(t<0?this.$to:n.doc.resolve(t));if(!r)return null;let i=this.placed,o=n.depth,s=r.depth;for(;o&&s&&1==i.childCount;)i=i.firstChild.content,o--,s--;let l=new vt(i,o,s);return t>-1?new Te(n.pos,t,this.$to.pos,this.$to.end(),l,e):l.size||n.pos!=this.$to.pos?new Ne(n.pos,r.pos,l):null}findFittable(){let t=this.unplaced.openStart;for(let e=this.unplaced.content,n=0,r=this.unplaced.openEnd;n1&&(r=0),i.type.spec.isolating&&r<=n){t=n;break}e=i.content}for(let e=1;e<=2;e++)for(let n=1==e?t:this.unplaced.openStart;n>=0;n--){let t,r=null;n?(r=Le(this.unplaced.content,n-1).firstChild,t=r.content):t=this.unplaced.content;let i=t.firstChild;for(let o=this.depth;o>=0;o--){let t,{type:s,match:l}=this.frontier[o],a=null;if(1==e&&(i?l.matchType(i.type)||(a=l.fillBefore(dt.from(i),!1)):r&&s.compatibleContent(r.type)))return{sliceDepth:n,frontierDepth:o,parent:r,inject:a};if(2==e&&i&&(t=l.findWrapping(i.type)))return{sliceDepth:n,frontierDepth:o,parent:r,wrap:t};if(r&&l.matchType(r.type))break}}}openMore(){let{content:t,openStart:e,openEnd:n}=this.unplaced,r=Le(t,e);return!(!r.childCount||r.firstChild.isLeaf)&&(this.unplaced=new vt(t,e+1,Math.max(n,r.size+e>=t.size-n?e+1:0)),!0)}dropNode(){let{content:t,openStart:e,openEnd:n}=this.unplaced,r=Le(t,e);if(r.childCount<=1&&e>0){let i=t.size-e<=e+r.size;this.unplaced=new vt(Ve(t,e-1,1),e-1,i?e-1:n)}else this.unplaced=new vt(Ve(t,e,1),e,n)}placeNodes({sliceDepth:t,frontierDepth:e,parent:n,inject:r,wrap:i}){for(;this.depth>e;)this.closeFrontierNode();if(i)for(let p=0;p1||0==l||t.content.size)&&(h=e,c.push(We(t.mark(u.allowedMarks(t.marks)),1==a?l:0,a==s.childCount?d:-1)))}let f=a==s.childCount;f||(d=-1),this.placed=Fe(this.placed,e,dt.from(c)),this.frontier[e].match=h,f&&d<0&&n&&n.type==this.frontier[this.depth].type&&this.frontier.length>1&&this.closeFrontierNode();for(let p=0,m=s;p1&&r==this.$to.end(--n);)++r;return r}findCloseLevel(t){t:for(let e=Math.min(this.depth,t.depth);e>=0;e--){let{match:n,type:r}=this.frontier[e],i=e=0;n--){let{match:e,type:r}=this.frontier[n],i=qe(t,n,r,e,!0);if(!i||i.childCount)continue t}return{depth:e,fit:o,move:i?t.doc.resolve(t.after(e+1)):t}}}}close(t){let e=this.findCloseLevel(t);if(!e)return null;for(;this.depth>e.depth;)this.closeFrontierNode();e.fit.childCount&&(this.placed=Fe(this.placed,e.depth,e.fit)),t=e.move;for(let n=e.depth+1;n<=t.depth;n++){let e=t.node(n),r=e.type.contentMatch.fillBefore(e.content,!0,t.index(n));this.openFrontierNode(e.type,e.attrs,r)}return t}openFrontierNode(t,e=null,n){let r=this.frontier[this.depth];r.match=r.match.matchType(t),this.placed=Fe(this.placed,this.depth,dt.from(t.create(e,n))),this.frontier.push({type:t,match:t.contentMatch})}closeFrontierNode(){let t=this.frontier.pop().match.fillBefore(dt.empty,!0);t.childCount&&(this.placed=Fe(this.placed,this.frontier.length,t))}}function Ve(t,e,n){return 0==e?t.cutByIndex(n,t.childCount):t.replaceChild(0,t.firstChild.copy(Ve(t.firstChild.content,e-1,n)))}function Fe(t,e,n){return 0==e?t.append(n):t.replaceChild(t.childCount-1,t.lastChild.copy(Fe(t.lastChild.content,e-1,n)))}function Le(t,e){for(let n=0;n1&&(r=r.replaceChild(0,We(r.firstChild,e-1,1==r.childCount?n-1:0))),e>0&&(r=t.type.contentMatch.fillBefore(r).append(r),n<=0&&(r=r.append(t.type.contentMatch.matchFragment(r).fillBefore(dt.empty,!0)))),t.copy(r)}function qe(t,e,n,r,i){let o=t.node(e),s=i?t.indexAfter(e):t.index(e);if(s==o.childCount&&!n.compatibleContent(o.type))return null;let l=r.fillBefore(o.content,!0,s);return l&&!function(t,e,n){for(let r=n;rr){let e=i.contentMatchAt(0),n=e.fillBefore(t).append(t);t=n.append(e.matchFragment(n).fillBefore(dt.empty,!0))}return t}function He(t,e){let n=[];for(let r=Math.min(t.depth,e.depth);r>=0;r--){let i=t.start(r);if(ie.pos+(e.depth-r)||t.node(r).type.spec.isolating||e.node(r).type.spec.isolating)break;(i==e.start(r)||r==t.depth&&r==e.depth&&t.parent.inlineContent&&e.parent.inlineContent&&r&&e.start(r-1)==i-1)&&n.push(r)}return n}class Ye extends xe{constructor(t,e,n){super(),this.pos=t,this.attr=e,this.value=n}apply(t){let e=t.nodeAt(this.pos);if(!e)return Se.fail("No node at attribute step's position");let n=Object.create(null);for(let i in e.attrs)n[i]=e.attrs[i];n[this.attr]=this.value;let r=e.type.create(n,null,e.marks);return Se.fromReplace(t,this.pos,this.pos+1,new vt(dt.from(r),0,e.isLeaf?0:1))}getMap(){return ve.empty}invert(t){return new Ye(this.pos,this.attr,t.nodeAt(this.pos).attrs[this.attr])}map(t){let e=t.mapResult(this.pos,1);return e.deletedAfter?null:new Ye(e.pos,this.attr,this.value)}toJSON(){return{stepType:"attr",pos:this.pos,attr:this.attr,value:this.value}}static fromJSON(t,e){if("number"!=typeof e.pos||"string"!=typeof e.attr)throw new RangeError("Invalid input for AttrStep.fromJSON");return new Ye(e.pos,e.attr,e.value)}}xe.jsonID("attr",Ye);let Ue=class extends Error{};Ue=function t(e){let n=Error.call(this,e);return n.__proto__=t.prototype,n},(Ue.prototype=Object.create(Error.prototype)).constructor=Ue,Ue.prototype.name="TransformError";class Ge{constructor(t){this.doc=t,this.steps=[],this.docs=[],this.mapping=new we}get before(){return this.docs.length?this.docs[0]:this.doc}step(t){let e=this.maybeStep(t);if(e.failed)throw new Ue(e.failed);return this}maybeStep(t){let e=t.apply(this.doc);return e.failed||this.addStep(t,e.doc),e}get docChanged(){return this.steps.length>0}addStep(t,e){this.docs.push(this.doc),this.steps.push(t),this.mapping.appendMap(t.getMap()),this.doc=e}replace(t,e=t,n=vt.empty){let r=_e(this.doc,t,e,n);return r&&this.step(r),this}replaceWith(t,e,n){return this.replace(t,e,new vt(dt.from(n),0,0))}delete(t,e){return this.replace(t,e,vt.empty)}insert(t,e){return this.replaceWith(t,t,e)}replaceRange(t,e,n){return function(t,e,n,r){if(!r.size)return t.deleteRange(e,n);let i=t.doc.resolve(e),o=t.doc.resolve(n);if(Be(i,o,r))return t.step(new Ne(e,n,r));let s=He(i,t.doc.resolve(n));0==s[s.length-1]&&s.pop();let l=-(i.depth+1);s.unshift(l);for(let d=i.depth,f=i.pos-1;d>0;d--,f--){let t=i.node(d).type.spec;if(t.defining||t.definingAsContext||t.isolating)break;s.indexOf(d)>-1?l=d:i.before(d)==f&&s.splice(1,0,-d)}let a=s.indexOf(l),c=[],h=r.openStart;for(let d=r.content,f=0;;f++){let t=d.firstChild;if(c.push(t),f==r.openStart)break;d=t.content}for(let d=h-1;d>=0;d--){let t=c[d].type,e=Je(t);if(e&&i.node(a).type!=t)h=d;else if(e||!t.isTextblock)break}for(let d=r.openStart;d>=0;d--){let e=(d+h+1)%(r.openStart+1),l=c[e];if(l)for(let c=0;c=0&&(t.replace(e,n,r),!(t.steps.length>u));d--){let t=s[d];t<0||(e=i.before(t),n=o.after(t))}}(this,t,e,n),this}replaceRangeWith(t,e,n){return function(t,e,n,r){if(!r.isInline&&e==n&&t.doc.resolve(e).parent.content.size){let i=function(t,e,n){let r=t.resolve(e);if(r.parent.canReplaceWith(r.index(),r.index(),n))return e;if(0==r.parentOffset)for(let i=r.depth-1;i>=0;i--){let t=r.index(i);if(r.node(i).canReplaceWith(t,t,n))return r.before(i+1);if(t>0)return null}if(r.parentOffset==r.parent.content.size)for(let i=r.depth-1;i>=0;i--){let t=r.indexAfter(i);if(r.node(i).canReplaceWith(t,t,n))return r.after(i+1);if(t0&&(n||r.node(e-1).canReplace(r.index(e-1),i.indexAfter(e-1))))return t.delete(r.before(e),i.after(e))}for(let s=1;s<=r.depth&&s<=i.depth;s++)if(e-r.start(s)==r.depth-s&&n>r.end(s)&&i.end(s)-n!=i.depth-s)return t.delete(r.before(s),n);t.delete(e,n)}(this,t,e),this}lift(t,e){return function(t,e,n){let{$from:r,$to:i,depth:o}=e,s=r.before(o+1),l=i.after(o+1),a=s,c=l,h=dt.empty,u=0;for(let p=o,m=!1;p>n;p--)m||r.index(p)>0?(m=!0,h=dt.from(r.node(p).copy(h)),u++):a--;let d=dt.empty,f=0;for(let p=o,m=!1;p>n;p--)m||i.after(p+1)=0;s--){if(r.size){let t=n[s].type.contentMatch.matchFragment(r);if(!t||!t.validEnd)throw new RangeError("Wrapper type given to Transform.wrap does not form valid content of its parent wrapper")}r=dt.from(n[s].type.create(n[s].attrs,r))}let i=e.start,o=e.end;t.step(new Te(i,o,i,o,new vt(r,0,0),n.length,!0))}(this,t,e),this}setBlockType(t,e=t,n,r=null){return function(t,e,n,r,i){if(!r.isTextblock)throw new RangeError("Type given to setBlockType should be a textblock");let o=t.steps.length;t.doc.nodesBetween(e,n,((e,n)=>{if(e.isTextblock&&!e.hasMarkup(r,i)&&function(t,e,n){let r=t.resolve(e),i=r.index();return r.parent.canReplaceWith(i,i+1,n)}(t.doc,t.mapping.slice(o).map(n),r)){t.clearIncompatible(t.mapping.slice(o).map(n,1),r);let s=t.mapping.slice(o),l=s.map(n,1),a=s.map(n+e.nodeSize,1);return t.step(new Te(l,a,l+1,a-1,new vt(dt.from(r.create(i,null,e.marks)),0,0),1,!0)),!1}}))}(this,t,e,n,r),this}setNodeMarkup(t,e,n=null,r){return function(t,e,n,r,i){let o=t.doc.nodeAt(e);if(!o)throw new RangeError("No node at given position");n||(n=o.type);let s=n.create(r,null,i||o.marks);if(o.isLeaf)return t.replaceWith(e,e+o.nodeSize,s);if(!n.validContent(o.content))throw new RangeError("Invalid content for node type "+n.name);t.step(new Te(e,e+o.nodeSize,e+1,e+o.nodeSize-1,new vt(dt.from(s),0,0),1,!0))}(this,t,e,n,r),this}setNodeAttribute(t,e,n){return this.step(new Ye(t,e,n)),this}addNodeMark(t,e){return this.step(new Ce(t,e)),this}removeNodeMark(t,e){if(!(e instanceof gt)){let n=this.doc.nodeAt(t);if(!n)throw new RangeError("No node at position "+t);if(!(e=e.isInSet(n.marks)))return this}return this.step(new De(t,e)),this}split(t,e=1,n){return function(t,e,n=1,r){let i=t.doc.resolve(e),o=dt.empty,s=dt.empty;for(let l=i.depth,a=i.depth-n,c=n-1;l>a;l--,c--){o=dt.from(i.node(l).copy(o));let t=r&&r[c];s=dt.from(t?t.type.create(t.attrs,s):i.node(l).copy(s))}t.step(new Ne(e,e,new vt(o.append(s),n,n),!0))}(this,t,e,n),this}addMark(t,e,n){return function(t,e,n,r){let i,o,s=[],l=[];t.doc.nodesBetween(e,n,((t,a,c)=>{if(!t.isInline)return;let h=t.marks;if(!r.isInSet(h)&&c.type.allowsMarkType(r.type)){let c=Math.max(a,e),u=Math.min(a+t.nodeSize,n),d=r.addToSet(h);for(let t=0;tt.step(e))),l.forEach((e=>t.step(e)))}(this,t,e,n),this}removeMark(t,e,n){return function(t,e,n,r){let i=[],o=0;t.doc.nodesBetween(e,n,((t,s)=>{if(!t.isInline)return;o++;let l=null;if(r instanceof Qt){let e,n=t.marks;for(;e=r.isInSet(n);)(l||(l=[])).push(e),n=e.removeFromSet(n)}else r?r.isInSet(t.marks)&&(l=[r]):l=t.marks;if(l&&l.length){let r=Math.min(s+t.nodeSize,n);for(let t=0;tt.step(new Oe(e.from,e.to,e.style))))}(this,t,e,n),this}clearIncompatible(t,e,n){return function(t,e,n,r=n.contentMatch){let i=t.doc.nodeAt(e),o=[],s=e+1;for(let l=0;l=0;l--)t.step(o[l])}(this,t,e,n),this}}const Ze=Object.create(null);class Xe{constructor(t,e,n){this.$anchor=t,this.$head=e,this.ranges=n||[new Qe(t.min(e),t.max(e))]}get anchor(){return this.$anchor.pos}get head(){return this.$head.pos}get from(){return this.$from.pos}get to(){return this.$to.pos}get $from(){return this.ranges[0].$from}get $to(){return this.ranges[0].$to}get empty(){let t=this.ranges;for(let e=0;e=0;i--){let r=e<0?cn(t.node(0),t.node(i),t.before(i+1),t.index(i),e,n):cn(t.node(0),t.node(i),t.after(i+1),t.index(i)+1,e,n);if(r)return r}return null}static near(t,e=1){return this.findFrom(t,e)||this.findFrom(t,-e)||new ln(t.node(0))}static atStart(t){return cn(t,t,0,0,1)||new ln(t)}static atEnd(t){return cn(t,t,t.content.size,t.childCount,-1)||new ln(t)}static fromJSON(t,e){if(!e||!e.type)throw new RangeError("Invalid input for Selection.fromJSON");let n=Ze[e.type];if(!n)throw new RangeError(`No selection type ${e.type} defined`);return n.fromJSON(t,e)}static jsonID(t,e){if(t in Ze)throw new RangeError("Duplicate use of selection JSON ID "+t);return Ze[t]=e,e.prototype.jsonID=t,e}getBookmark(){return nn.between(this.$anchor,this.$head).getBookmark()}}Xe.prototype.visible=!0;class Qe{constructor(t,e){this.$from=t,this.$to=e}}let tn=!1;function en(t){tn||t.parent.inlineContent||(tn=!0,console.warn("TextSelection endpoint not pointing into a node with inline content ("+t.parent.type.name+")"))}class nn extends Xe{constructor(t,e=t){en(t),en(e),super(t,e)}get $cursor(){return this.$anchor.pos==this.$head.pos?this.$head:null}map(t,e){let n=t.resolve(e.map(this.head));if(!n.parent.inlineContent)return Xe.near(n);let r=t.resolve(e.map(this.anchor));return new nn(r.parent.inlineContent?r:n,n)}replace(t,e=vt.empty){if(super.replace(t,e),e==vt.empty){let e=this.$from.marksAcross(this.$to);e&&t.ensureMarks(e)}}eq(t){return t instanceof nn&&t.anchor==this.anchor&&t.head==this.head}getBookmark(){return new rn(this.anchor,this.head)}toJSON(){return{type:"text",anchor:this.anchor,head:this.head}}static fromJSON(t,e){if("number"!=typeof e.anchor||"number"!=typeof e.head)throw new RangeError("Invalid input for TextSelection.fromJSON");return new nn(t.resolve(e.anchor),t.resolve(e.head))}static create(t,e,n=e){let r=t.resolve(e);return new this(r,n==e?r:t.resolve(n))}static between(t,e,n){let r=t.pos-e.pos;if(n&&!r||(n=r>=0?1:-1),!e.parent.inlineContent){let t=Xe.findFrom(e,n,!0)||Xe.findFrom(e,-n,!0);if(!t)return Xe.near(e,n);e=t.$head}return t.parent.inlineContent||(0==r||(t=(Xe.findFrom(t,-n,!0)||Xe.findFrom(t,n,!0)).$anchor).posnew ln(t)};function cn(t,e,n,r,i,o=!1){if(e.inlineContent)return nn.create(t,n);for(let s=r-(i>0?0:1);i>0?s=0;s+=i){let r=e.child(s);if(r.isAtom){if(!o&&on.isSelectable(r))return on.create(t,n-(i<0?r.nodeSize:0))}else{let e=cn(t,r,n+i,i<0?r.childCount:0,i,o);if(e)return e}n+=r.nodeSize*i}return null}function hn(t,e,n){let r=t.steps.length-1;if(r{null==i&&(i=r)})),t.setSelection(Xe.near(t.doc.resolve(i),n)))}class un extends Ge{constructor(t){super(t.doc),this.curSelectionFor=0,this.updated=0,this.meta=Object.create(null),this.time=Date.now(),this.curSelection=t.selection,this.storedMarks=t.storedMarks}get selection(){return this.curSelectionFor0}setStoredMarks(t){return this.storedMarks=t,this.updated|=2,this}ensureMarks(t){return gt.sameSet(this.storedMarks||this.selection.$from.marks(),t)||this.setStoredMarks(t),this}addStoredMark(t){return this.ensureMarks(t.addToSet(this.storedMarks||this.selection.$head.marks()))}removeStoredMark(t){return this.ensureMarks(t.removeFromSet(this.storedMarks||this.selection.$head.marks()))}get storedMarksSet(){return(2&this.updated)>0}addStep(t,e){super.addStep(t,e),this.updated=-3&this.updated,this.storedMarks=null}setTime(t){return this.time=t,this}replaceSelection(t){return this.selection.replace(this,t),this}replaceSelectionWith(t,e=!0){let n=this.selection;return e&&(t=t.mark(this.storedMarks||(n.empty?n.$from.marks():n.$from.marksAcross(n.$to)||gt.none))),n.replaceWith(this,t),this}deleteSelection(){return this.selection.replace(this),this}insertText(t,e,n){let r=this.doc.type.schema;if(null==e)return t?this.replaceSelectionWith(r.text(t),!0):this.deleteSelection();{if(null==n&&(n=e),n=null==n?e:n,!t)return this.deleteRange(e,n);let i=this.storedMarks;if(!i){let t=this.doc.resolve(e);i=n==e?t.marks():t.marksAcross(this.doc.resolve(n))}return this.replaceRangeWith(e,n,r.text(t,i)),this.selection.empty||this.setSelection(Xe.near(this.selection.$to)),this}}setMeta(t,e){return this.meta["string"==typeof t?t:t.key]=e,this}getMeta(t){return this.meta["string"==typeof t?t:t.key]}get isGeneric(){for(let t in this.meta)return!1;return!0}scrollIntoView(){return this.updated|=4,this}get scrolledIntoView(){return(4&this.updated)>0}}function dn(t,e){return e&&t?t.bind(e):t}class fn{constructor(t,e,n){this.name=t,this.init=dn(e.init,n),this.apply=dn(e.apply,n)}}const pn=[new fn("doc",{init:t=>t.doc||t.schema.topNodeType.createAndFill(),apply:t=>t.doc}),new fn("selection",{init:(t,e)=>t.selection||Xe.atStart(e.doc),apply:t=>t.selection}),new fn("storedMarks",{init:t=>t.storedMarks||null,apply:(t,e,n,r)=>r.selection.$cursor?t.storedMarks:null}),new fn("scrollToSelection",{init:()=>0,apply:(t,e)=>t.scrolledIntoView?e+1:e})];class mn{constructor(t,e){this.schema=t,this.plugins=[],this.pluginsByKey=Object.create(null),this.fields=pn.slice(),e&&e.forEach((t=>{if(this.pluginsByKey[t.key])throw new RangeError("Adding different instances of a keyed plugin ("+t.key+")");this.plugins.push(t),this.pluginsByKey[t.key]=t,t.spec.state&&this.fields.push(new fn(t.key,t.spec.state,t))}))}}class gn{constructor(t){this.config=t}get schema(){return this.config.schema}get plugins(){return this.config.plugins}apply(t){return this.applyTransaction(t).state}filterTransaction(t,e=-1){for(let n=0;nt.toJSON()))),t&&"object"==typeof t)for(let n in t){if("doc"==n||"selection"==n)throw new RangeError("The JSON fields `doc` and `selection` are reserved");let r=t[n],i=r.spec.state;i&&i.toJSON&&(e[n]=i.toJSON.call(r,this[r.key]))}return e}static fromJSON(t,e,n){if(!e)throw new RangeError("Invalid input for EditorState.fromJSON");if(!t.schema)throw new RangeError("Required config field 'schema' missing");let r=new mn(t.schema,t.plugins),i=new gn(r);return r.fields.forEach((r=>{if("doc"==r.name)i.doc=zt.fromJSON(t.schema,e.doc);else if("selection"==r.name)i.selection=Xe.fromJSON(i.doc,e.selection);else if("storedMarks"==r.name)e.storedMarks&&(i.storedMarks=e.storedMarks.map(t.schema.markFromJSON));else{if(n)for(let o in n){let s=n[o],l=s.spec.state;if(s.key==r.name&&l&&l.fromJSON&&Object.prototype.hasOwnProperty.call(e,o))return void(i[r.name]=l.fromJSON.call(s,t,e[o],i))}i[r.name]=r.init(t,i)}})),i}}function yn(t,e,n){for(let r in t){let i=t[r];i instanceof Function?i=i.bind(e):"handleDOMEvents"==r&&(i=yn(i,e,{})),n[r]=i}return n}class vn{constructor(t){this.spec=t,this.props={},t.props&&yn(t.props,this,this.props),this.key=t.key?t.key.key:bn("plugin")}getState(t){return t[this.key]}}const wn=Object.create(null);function bn(t){return t in wn?t+"$"+ ++wn[t]:(wn[t]=0,t+"$")}class xn{constructor(t="key"){this.key=bn(t)}get(t){return t.config.pluginsByKey[this.key]}getState(t){return t[this.key]}}const Sn=function(t){for(var e=0;;e++)if(!(t=t.previousSibling))return e},kn=function(t){let e=t.assignedSlot||t.parentNode;return e&&11==e.nodeType?e.host:e};let Mn=null;const On=function(t,e,n){let r=Mn||(Mn=document.createRange());return r.setEnd(t,null==n?t.nodeValue.length:n),r.setStart(t,e||0),r},Cn=function(t,e,n,r){return n&&(Nn(t,e,n,r,-1)||Nn(t,e,n,r,1))},Dn=/^(img|br|input|textarea|hr)$/i;function Nn(t,e,n,r,i){for(;;){if(t==n&&e==r)return!0;if(e==(i<0?0:Tn(t))){let n=t.parentNode;if(!n||1!=n.nodeType||An(t)||Dn.test(t.nodeName)||"false"==t.contentEditable)return!1;e=Sn(t)+(i<0?0:1),t=n}else{if(1!=t.nodeType)return!1;if("false"==(t=t.childNodes[e+(i<0?-1:0)]).contentEditable)return!1;e=i<0?Tn(t):0}}}function Tn(t){return 3==t.nodeType?t.nodeValue.length:t.childNodes.length}function An(t){let e;for(let n=t;n&&!(e=n.pmViewDesc);n=n.parentNode);return e&&e.node&&e.node.isBlock&&(e.dom==t||e.contentDOM==t)}const En=function(t){return t.focusNode&&Cn(t.focusNode,t.focusOffset,t.anchorNode,t.anchorOffset)};function $n(t,e){let n=document.createEvent("Event");return n.initEvent("keydown",!0,!0),n.keyCode=t,n.key=n.code=e,n}const Pn="undefined"!=typeof navigator?navigator:null,In="undefined"!=typeof document?document:null,Rn=Pn&&Pn.userAgent||"",zn=/Edge\/(\d+)/.exec(Rn),_n=/MSIE \d/.exec(Rn),Bn=/Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(Rn),jn=!!(_n||Bn||zn),Vn=_n?document.documentMode:Bn?+Bn[1]:zn?+zn[1]:0,Fn=!jn&&/gecko\/(\d+)/i.test(Rn);Fn&&(/Firefox\/(\d+)/.exec(Rn)||[0,0])[1];const Ln=!jn&&/Chrome\/(\d+)/.exec(Rn),Wn=!!Ln,qn=Ln?+Ln[1]:0,Jn=!jn&&!!Pn&&/Apple Computer/.test(Pn.vendor),Kn=Jn&&(/Mobile\/\w+/.test(Rn)||!!Pn&&Pn.maxTouchPoints>2),Hn=Kn||!!Pn&&/Mac/.test(Pn.platform),Yn=!!Pn&&/Win/.test(Pn.platform),Un=/Android \d/.test(Rn),Gn=!!In&&"webkitFontSmoothing"in In.documentElement.style,Zn=Gn?+(/\bAppleWebKit\/(\d+)/.exec(navigator.userAgent)||[0,0])[1]:0;function Xn(t){return{left:0,right:t.documentElement.clientWidth,top:0,bottom:t.documentElement.clientHeight}}function Qn(t,e){return"number"==typeof t?t:t[e]}function tr(t){let e=t.getBoundingClientRect(),n=e.width/t.offsetWidth||1,r=e.height/t.offsetHeight||1;return{left:e.left,right:e.left+t.clientWidth*n,top:e.top,bottom:e.top+t.clientHeight*r}}function er(t,e,n){let r=t.someProp("scrollThreshold")||0,i=t.someProp("scrollMargin")||5,o=t.dom.ownerDocument;for(let s=n||t.dom;s;s=kn(s)){if(1!=s.nodeType)continue;let t=s,n=t==o.body,l=n?Xn(o):tr(t),a=0,c=0;if(e.topl.bottom-Qn(r,"bottom")&&(c=e.bottom-e.top>l.bottom-l.top?e.top+Qn(i,"top")-l.top:e.bottom-l.bottom+Qn(i,"bottom")),e.leftl.right-Qn(r,"right")&&(a=e.right-l.right+Qn(i,"right")),a||c)if(n)o.defaultView.scrollBy(a,c);else{let n=t.scrollLeft,r=t.scrollTop;c&&(t.scrollTop+=c),a&&(t.scrollLeft+=a);let i=t.scrollLeft-n,o=t.scrollTop-r;e={left:e.left-i,top:e.top-o,right:e.right-i,bottom:e.bottom-o}}if(n||/^(fixed|sticky)$/.test(getComputedStyle(s).position))break}}function nr(t){let e=[],n=t.ownerDocument;for(let r=t;r&&(e.push({dom:r,top:r.scrollTop,left:r.scrollLeft}),t!=n);r=kn(r));return e}function rr(t,e){for(let n=0;n=c){a=Math.max(f.bottom,a),c=Math.min(f.top,c);let t=f.left>e.left?f.left-e.left:f.right=(f.left+f.right)/2?1:0));continue}}else f.top>e.top&&!i&&f.left<=e.left&&f.right>=e.left&&(i=h,o={left:Math.max(f.left,Math.min(f.right,e.left)),top:f.top});!n&&(e.left>=f.right&&e.top>=f.top||e.left>=f.left&&e.top>=f.bottom)&&(l=u+1)}}return!n&&i&&(n=i,r=o,s=0),n&&3==n.nodeType?function(t,e){let n=t.nodeValue.length,r=document.createRange();for(let i=0;i=(n.left+n.right)/2?1:0)}}return{node:t,offset:0}}(n,r):!n||s&&1==n.nodeType?{node:t,offset:l}:or(n,r)}function sr(t,e){return t.left>=e.left-1&&t.left<=e.right+1&&t.top>=e.top-1&&t.top<=e.bottom+1}function lr(t,e,n){let r=t.childNodes.length;if(r&&n.tope.top&&i++}let r;Gn&&i&&1==n.nodeType&&1==(r=n.childNodes[i-1]).nodeType&&"false"==r.contentEditable&&r.getBoundingClientRect().top>=e.top&&i--,n==t.dom&&i==n.childNodes.length-1&&1==n.lastChild.nodeType&&e.top>n.lastChild.getBoundingClientRect().bottom?s=t.state.doc.content.size:0!=i&&1==n.nodeType&&"BR"==n.childNodes[i-1].nodeName||(s=function(t,e,n,r){let i=-1;for(let o=e,s=!1;o!=t.dom;){let e=t.docView.nearestDesc(o,!0);if(!e)return null;if(1==e.dom.nodeType&&(e.node.isBlock&&e.parent&&!s||!e.contentDOM)){let t=e.dom.getBoundingClientRect();if(e.node.isBlock&&e.parent&&!s&&(s=!0,t.left>r.left||t.top>r.top?i=e.posBefore:(t.right-1?i:t.docView.posFromDOM(e,n,-1)}(t,n,i,e))}null==s&&(s=function(t,e,n){let{node:r,offset:i}=or(e,n),o=-1;if(1==r.nodeType&&!r.firstChild){let t=r.getBoundingClientRect();o=t.left!=t.right&&n.left>(t.left+t.right)/2?1:-1}return t.docView.posFromDOM(r,i,o)}(t,l,e));let a=t.docView.nearestDesc(l,!0);return{pos:s,inside:a?a.posAtStart-a.border:-1}}function cr(t){return t.top=0&&i==r.nodeValue.length?(t--,o=1):n<0?t--:e++,fr(hr(On(r,t,e),o),o<0)}{let t=hr(On(r,i,i),n);if(Fn&&i&&/\s/.test(r.nodeValue[i-1])&&i=0)}if(null==o&&i&&(n<0||i==Tn(r))){let t=r.childNodes[i-1],e=3==t.nodeType?On(t,Tn(t)-(s?0:1)):1!=t.nodeType||"BR"==t.nodeName&&t.nextSibling?null:t;if(e)return fr(hr(e,1),!1)}if(null==o&&i=0)}function fr(t,e){if(0==t.width)return t;let n=e?t.left:t.right;return{top:t.top,bottom:t.bottom,left:n,right:n}}function pr(t,e){if(0==t.height)return t;let n=e?t.top:t.bottom;return{top:n,bottom:n,left:t.left,right:t.right}}function mr(t,e,n){let r=t.state,i=t.root.activeElement;r!=e&&t.updateState(e),i!=t.dom&&t.focus();try{return n()}finally{r!=e&&t.updateState(r),i!=t.dom&&i&&i.focus()}}const gr=/[\u0590-\u08ac]/;let yr=null,vr=null,wr=!1;function br(t,e,n){return yr==e&&vr==n?wr:(yr=e,vr=n,wr="up"==n||"down"==n?function(t,e,n){let r=e.selection,i="up"==n?r.$from:r.$to;return mr(t,e,(()=>{let{node:e}=t.docView.domFromPos(i.pos,"up"==n?-1:1);for(;;){let n=t.docView.nearestDesc(e,!0);if(!n)break;if(n.node.isBlock){e=n.contentDOM||n.dom;break}e=n.dom.parentNode}let r=dr(t,i.pos,1);for(let t=e.firstChild;t;t=t.nextSibling){let e;if(1==t.nodeType)e=t.getClientRects();else{if(3!=t.nodeType)continue;e=On(t,0,t.nodeValue.length).getClientRects()}for(let t=0;ti.top+1&&("up"==n?r.top-i.top>2*(i.bottom-r.top):i.bottom-r.bottom>2*(r.bottom-i.top)))return!1}}return!0}))}(t,e,n):function(t,e,n){let{$head:r}=e.selection;if(!r.parent.isTextblock)return!1;let i=r.parentOffset,o=!i,s=i==r.parent.content.size,l=t.domSelection();return gr.test(r.parent.textContent)&&l.modify?mr(t,e,(()=>{let{focusNode:e,focusOffset:i,anchorNode:o,anchorOffset:s}=t.domSelectionRange(),a=l.caretBidiLevel;l.modify("move",n,"character");let c=r.depth?t.docView.domAfterPos(r.before()):t.dom,{focusNode:h,focusOffset:u}=t.domSelectionRange(),d=h&&!c.contains(1==h.nodeType?h:h.parentNode)||e==h&&i==u;try{l.collapse(o,s),e&&(e!=o||i!=s)&&l.extend&&l.extend(e,i)}catch(f){}return null!=a&&(l.caretBidiLevel=a),d})):"left"==n||"backward"==n?o:s}(t,e,n))}class xr{constructor(t,e,n,r){this.parent=t,this.children=e,this.dom=n,this.contentDOM=r,this.dirty=0,n.pmViewDesc=this}matchesWidget(t){return!1}matchesMark(t){return!1}matchesNode(t,e,n){return!1}matchesHack(t){return!1}parseRule(){return null}stopEvent(t){return!1}get size(){let t=0;for(let e=0;eSn(this.contentDOM);else if(this.contentDOM&&this.contentDOM!=this.dom&&this.dom.contains(this.contentDOM))r=2&t.compareDocumentPosition(this.contentDOM);else if(this.dom.firstChild){if(0==e)for(let e=t;;e=e.parentNode){if(e==this.dom){r=!1;break}if(e.previousSibling)break}if(null==r&&e==t.childNodes.length)for(let e=t;;e=e.parentNode){if(e==this.dom){r=!0;break}if(e.nextSibling)break}}return(null==r?n>0:r)?this.posAtEnd:this.posAtStart}nearestDesc(t,e=!1){for(let n=!0,r=t;r;r=r.parentNode){let i,o=this.getDesc(r);if(o&&(!e||o.node)){if(!n||!(i=o.nodeDOM)||(1==i.nodeType?i.contains(1==t.nodeType?t:t.parentNode):i==t))return o;n=!1}}}getDesc(t){let e=t.pmViewDesc;for(let n=e;n;n=n.parent)if(n==this)return e}posFromDOM(t,e,n){for(let r=t;r;r=r.parentNode){let i=this.getDesc(r);if(i)return i.localPosFromDOM(t,e,n)}return-1}descAt(t){for(let e=0,n=0;et||e instanceof Nr){r=t-i;break}i=o}if(r)return this.children[n].domFromPos(r-this.children[n].border,e);for(let i;n&&!(i=this.children[n-1]).size&&i instanceof Sr&&i.side>=0;n--);if(e<=0){let t,r=!0;for(;t=n?this.children[n-1]:null,t&&t.dom.parentNode!=this.contentDOM;n--,r=!1);return t&&e&&r&&!t.border&&!t.domAtom?t.domFromPos(t.size,e):{node:this.contentDOM,offset:t?Sn(t.dom)+1:0}}{let t,r=!0;for(;t=n=i&&e<=l-n.border&&n.node&&n.contentDOM&&this.contentDOM.contains(n.contentDOM))return n.parseRange(t,e,i);t=o;for(let e=s;e>0;e--){let n=this.children[e-1];if(n.size&&n.dom.parentNode==this.contentDOM&&!n.emptyChildAt(1)){r=Sn(n.dom)+1;break}t-=n.size}-1==r&&(r=0)}if(r>-1&&(l>e||s==this.children.length-1)){e=l;for(let t=s+1;tf&&oe){let t=s;s=l,l=t}let n=document.createRange();n.setEnd(l.node,l.offset),n.setStart(s.node,s.offset),a.removeAllRanges(),a.addRange(n)}}ignoreMutation(t){return!this.contentDOM&&"selection"!=t.type}get contentLost(){return this.contentDOM&&this.contentDOM!=this.dom&&!this.dom.contains(this.contentDOM)}markDirty(t,e){for(let n=0,r=0;r=n:tn){let r=n+i.border,s=o-i.border;if(t>=r&&e<=s)return this.dirty=t==n||e==o?2:1,void(t!=r||e!=s||!i.contentLost&&i.dom.parentNode==this.contentDOM?i.markDirty(t-r,e-r):i.dirty=3);i.dirty=i.dom!=i.contentDOM||i.dom.parentNode!=this.contentDOM||i.children.length?3:2}n=o}this.dirty=2}markParentsDirty(){let t=1;for(let e=this.parent;e;e=e.parent,t++){let n=1==t?2:1;e.dirtyi?i.parent?i.parent.posBeforeChild(i):void 0:r))),!e.type.spec.raw){if(1!=o.nodeType){let t=document.createElement("span");t.appendChild(o),o=t}o.contentEditable="false",o.classList.add("ProseMirror-widget")}super(t,[],o,null),this.widget=e,this.widget=e,i=this}matchesWidget(t){return 0==this.dirty&&t.type.eq(this.widget.type)}parseRule(){return{ignore:!0}}stopEvent(t){let e=this.widget.spec.stopEvent;return!!e&&e(t)}ignoreMutation(t){return"selection"!=t.type||this.widget.spec.ignoreSelection}destroy(){this.widget.type.destroy(this.dom),super.destroy()}get domAtom(){return!0}get side(){return this.widget.type.side}}class kr extends xr{constructor(t,e,n,r){super(t,[],e,null),this.textDOM=n,this.text=r}get size(){return this.text.length}localPosFromDOM(t,e){return t!=this.textDOM?this.posAtStart+(e?this.size:0):this.posAtStart+e}domFromPos(t){return{node:this.textDOM,offset:t}}ignoreMutation(t){return"characterData"===t.type&&t.target.nodeValue==t.oldValue}}class Mr extends xr{constructor(t,e,n,r){super(t,[],n,r),this.mark=e}static create(t,e,n,r){let i=r.nodeViews[e.type.name],o=i&&i(e,r,n);return o&&o.dom||(o=de.renderSpec(document,e.type.spec.toDOM(e,n))),new Mr(t,e,o.dom,o.contentDOM||o.dom)}parseRule(){return 3&this.dirty||this.mark.type.spec.reparseInView?null:{mark:this.mark.type.name,attrs:this.mark.attrs,contentElement:this.contentDOM}}matchesMark(t){return 3!=this.dirty&&this.mark.eq(t)}markDirty(t,e){if(super.markDirty(t,e),0!=this.dirty){let t=this.parent;for(;!t.node;)t=t.parent;t.dirty0&&(i=Fr(i,0,t,n));for(let s=0;ss?s.parent?s.parent.posBeforeChild(s):void 0:o),n,r),c=a&&a.dom,h=a&&a.contentDOM;if(e.isText)if(c){if(3!=c.nodeType)throw new RangeError("Text must be rendered as a DOM text node")}else c=document.createTextNode(e.text);else c||({dom:c,contentDOM:h}=de.renderSpec(document,e.type.spec.toDOM(e)));h||e.isText||"BR"==c.nodeName||(c.hasAttribute("contenteditable")||(c.contentEditable="false"),e.type.spec.draggable&&(c.draggable=!0));let u=c;return c=zr(c,n,e),a?s=new Tr(t,e,n,r,c,h||null,u,a,i,o+1):e.isText?new Dr(t,e,n,r,c,u,i):new Or(t,e,n,r,c,h||null,u,i,o+1)}parseRule(){if(this.node.type.spec.reparseInView)return null;let t={node:this.node.type.name,attrs:this.node.attrs};if("pre"==this.node.type.whitespace&&(t.preserveWhitespace="full"),this.contentDOM)if(this.contentLost){for(let e=this.children.length-1;e>=0;e--){let n=this.children[e];if(this.dom.contains(n.dom.parentNode)){t.contentElement=n.dom.parentNode;break}}t.contentElement||(t.getContent=()=>dt.empty)}else t.contentElement=this.contentDOM;else t.getContent=()=>this.node.content;return t}matchesNode(t,e,n){return 0==this.dirty&&t.eq(this.node)&&_r(e,this.outerDeco)&&n.eq(this.innerDeco)}get size(){return this.node.nodeSize}get border(){return this.node.isLeaf?0:1}updateChildren(t,e){let n=this.node.inlineContent,r=e,i=t.composing?this.localCompositionInfo(t,e):null,o=i&&i.pos>-1?i:null,s=i&&i.pos<0,l=new jr(this,o&&o.node,t);!function(t,e,n,r){let i=e.locals(t),o=0;if(0==i.length){for(let n=0;no;)l.push(i[s++]);let p=o+d.nodeSize;if(d.isText){let t=p;s!t.inline)):l.slice(),e.forChild(o,d),f),o=p}}(this.node,this.innerDeco,((e,i,o)=>{e.spec.marks?l.syncToMarks(e.spec.marks,n,t):e.type.side>=0&&!o&&l.syncToMarks(i==this.node.childCount?gt.none:this.node.child(i).marks,n,t),l.placeWidget(e,t,r)}),((e,o,a,c)=>{let h;l.syncToMarks(e.marks,n,t),l.findNodeMatch(e,o,a,c)||s&&t.state.selection.from>r&&t.state.selection.to-1&&l.updateNodeAt(e,o,a,h,t)||l.updateNextNode(e,o,a,t,c,r)||l.addNode(e,o,a,t,r),r+=e.nodeSize})),l.syncToMarks([],n,t),this.node.isTextblock&&l.addTextblockHacks(),l.destroyRest(),(l.changed||2==this.dirty)&&(o&&this.protectLocalComposition(t,o),Ar(this.contentDOM,this.children,t),Kn&&function(t){if("UL"==t.nodeName||"OL"==t.nodeName){let e=t.style.cssText;t.style.cssText=e+"; list-style: square !important",window.getComputedStyle(t).listStyle,t.style.cssText=e}}(this.dom))}localCompositionInfo(t,e){let{from:n,to:r}=t.state.selection;if(!(t.state.selection instanceof nn)||ne+this.node.content.size)return null;let i=t.domSelectionRange(),o=function(t,e){for(;;){if(3==t.nodeType)return t;if(1==t.nodeType&&e>0){if(t.childNodes.length>e&&3==t.childNodes[e].nodeType)return t.childNodes[e];e=Tn(t=t.childNodes[e-1])}else{if(!(1==t.nodeType&&e=n){if(o>=r&&a.slice(r-e.length-l,r-l)==e)return r-e.length;let t=l=0&&t+e.length+l>=n)return l+t;if(n==r&&a.length>=r+e.length-l&&a.slice(r-l,r-l+e.length)==e)return r}}return-1}(this.node.content,t,n-e,r-e);return i<0?null:{node:o,pos:i,text:t}}return{node:o,pos:-1,text:""}}protectLocalComposition(t,{node:e,pos:n,text:r}){if(this.getDesc(e))return;let i=e;for(;i.parentNode!=this.contentDOM;i=i.parentNode){for(;i.previousSibling;)i.parentNode.removeChild(i.previousSibling);for(;i.nextSibling;)i.parentNode.removeChild(i.nextSibling);i.pmViewDesc&&(i.pmViewDesc=void 0)}let o=new kr(this,i,e,r);t.input.compositionNodes.push(o),this.children=Fr(this.children,n,n+r.length,t,o)}update(t,e,n,r){return!(3==this.dirty||!t.sameMarkup(this.node))&&(this.updateInner(t,e,n,r),!0)}updateInner(t,e,n,r){this.updateOuterDeco(e),this.node=t,this.innerDeco=n,this.contentDOM&&this.updateChildren(r,this.posAtStart),this.dirty=0}updateOuterDeco(t){if(_r(t,this.outerDeco))return;let e=1!=this.nodeDOM.nodeType,n=this.dom;this.dom=Ir(this.dom,this.nodeDOM,Pr(this.outerDeco,this.node,e),Pr(t,this.node,e)),this.dom!=n&&(n.pmViewDesc=void 0,this.dom.pmViewDesc=this),this.outerDeco=t}selectNode(){1==this.nodeDOM.nodeType&&this.nodeDOM.classList.add("ProseMirror-selectednode"),!this.contentDOM&&this.node.type.spec.draggable||(this.dom.draggable=!0)}deselectNode(){1==this.nodeDOM.nodeType&&this.nodeDOM.classList.remove("ProseMirror-selectednode"),!this.contentDOM&&this.node.type.spec.draggable||this.dom.removeAttribute("draggable")}get domAtom(){return this.node.isAtom}}function Cr(t,e,n,r,i){zr(r,e,t);let o=new Or(void 0,t,e,n,r,r,r,i,0);return o.contentDOM&&o.updateChildren(i,0),o}class Dr extends Or{constructor(t,e,n,r,i,o,s){super(t,e,n,r,i,null,o,s,0)}parseRule(){let t=this.nodeDOM.parentNode;for(;t&&t!=this.dom&&!t.pmIsDeco;)t=t.parentNode;return{skip:t||!0}}update(t,e,n,r){return!(3==this.dirty||0!=this.dirty&&!this.inParent()||!t.sameMarkup(this.node))&&(this.updateOuterDeco(e),0==this.dirty&&t.text==this.node.text||t.text==this.nodeDOM.nodeValue||(this.nodeDOM.nodeValue=t.text,r.trackWrites==this.nodeDOM&&(r.trackWrites=null)),this.node=t,this.dirty=0,!0)}inParent(){let t=this.parent.contentDOM;for(let e=this.nodeDOM;e;e=e.parentNode)if(e==t)return!0;return!1}domFromPos(t){return{node:this.nodeDOM,offset:t}}localPosFromDOM(t,e,n){return t==this.nodeDOM?this.posAtStart+Math.min(e,this.node.text.length):super.localPosFromDOM(t,e,n)}ignoreMutation(t){return"characterData"!=t.type&&"selection"!=t.type}slice(t,e,n){let r=this.node.cut(t,e),i=document.createTextNode(r.text);return new Dr(this.parent,r,this.outerDeco,this.innerDeco,i,i,n)}markDirty(t,e){super.markDirty(t,e),this.dom==this.nodeDOM||0!=t&&e!=this.nodeDOM.nodeValue.length||(this.dirty=3)}get domAtom(){return!1}}class Nr extends xr{parseRule(){return{ignore:!0}}matchesHack(t){return 0==this.dirty&&this.dom.nodeName==t}get domAtom(){return!0}get ignoreForCoords(){return"IMG"==this.dom.nodeName}}class Tr extends Or{constructor(t,e,n,r,i,o,s,l,a,c){super(t,e,n,r,i,o,s,a,c),this.spec=l}update(t,e,n,r){if(3==this.dirty)return!1;if(this.spec.update){let i=this.spec.update(t,e,n);return i&&this.updateInner(t,e,n,r),i}return!(!this.contentDOM&&!t.isLeaf)&&super.update(t,e,n,r)}selectNode(){this.spec.selectNode?this.spec.selectNode():super.selectNode()}deselectNode(){this.spec.deselectNode?this.spec.deselectNode():super.deselectNode()}setSelection(t,e,n,r){this.spec.setSelection?this.spec.setSelection(t,e,n):super.setSelection(t,e,n,r)}destroy(){this.spec.destroy&&this.spec.destroy(),super.destroy()}stopEvent(t){return!!this.spec.stopEvent&&this.spec.stopEvent(t)}ignoreMutation(t){return this.spec.ignoreMutation?this.spec.ignoreMutation(t):super.ignoreMutation(t)}}function Ar(t,e,n){let r=t.firstChild,i=!1;for(let o=0;o0;){let l;for(;;)if(r){let t=n.children[r-1];if(!(t instanceof Mr)){l=t,r--;break}n=t,r=t.children.length}else{if(n==e)break t;r=n.parent.children.indexOf(n),n=n.parent}let a=l.node;if(a){if(a!=t.child(i-1))break;--i,o.set(l,i),s.push(l)}}return{index:i,matched:o,matches:s.reverse()}}(t.node.content,t)}destroyBetween(t,e){if(t!=e){for(let n=t;n>1,o=Math.min(i,t.length);for(;r-1)r>this.index&&(this.changed=!0,this.destroyBetween(this.index,r)),this.top=this.top.children[this.index];else{let r=Mr.create(this.top,t[i],e,n);this.top.children.splice(this.index,0,r),this.top=r,this.changed=!0}this.index=0,i++}}findNodeMatch(t,e,n,r){let i,o=-1;if(r>=this.preMatch.index&&(i=this.preMatch.matches[r-this.preMatch.index]).parent==this.top&&i.matchesNode(t,e,n))o=this.top.children.indexOf(i,this.index);else for(let s=this.index,l=Math.min(this.top.children.length,s+5);s=n||h<=e?o.push(a):(cn&&o.push(a.slice(n-c,a.size,r)))}return o}function Lr(t,e=null){let n=t.domSelectionRange(),r=t.state.doc;if(!n.focusNode)return null;let i=t.docView.nearestDesc(n.focusNode),o=i&&0==i.size,s=t.docView.posFromDOM(n.focusNode,n.focusOffset,1);if(s<0)return null;let l,a,c=r.resolve(s);if(En(n)){for(l=c;i&&!i.node;)i=i.parent;let t=i.node;if(i&&t.isAtom&&on.isSelectable(t)&&i.parent&&(!t.isInline||!function(t,e,n){for(let r=0==e,i=e==Tn(t);r||i;){if(t==n)return!0;let e=Sn(t);if(!(t=t.parentNode))return!1;r=r&&0==e,i=i&&e==Tn(t)}}(n.focusNode,n.focusOffset,i.dom))){let t=i.posBefore;a=new on(s==t?c:r.resolve(t))}}else{let e=t.docView.posFromDOM(n.anchorNode,n.anchorOffset,1);if(e<0)return null;l=r.resolve(e)}if(!a){a=Zr(t,l,c,"pointer"==e||t.state.selection.head{n.anchorNode==r&&n.anchorOffset==i||(e.removeEventListener("selectionchange",t.input.hideSelectionGuard),setTimeout((()=>{Wr(t)&&!t.state.selection.visible||t.dom.classList.remove("ProseMirror-hideselection")}),20))})}(t))}t.domObserver.setCurSelection(),t.domObserver.connectSelection()}}const Jr=Jn||Wn&&qn<63;function Kr(t,e){let{node:n,offset:r}=t.docView.domFromPos(e,0),i=rr(t,e,n)))||nn.between(e,n,r)}function Xr(t){return!(t.editable&&!t.hasFocus())&&Qr(t)}function Qr(t){let e=t.domSelectionRange();if(!e.anchorNode)return!1;try{return t.dom.contains(3==e.anchorNode.nodeType?e.anchorNode.parentNode:e.anchorNode)&&(t.editable||t.dom.contains(3==e.focusNode.nodeType?e.focusNode.parentNode:e.focusNode))}catch(n){return!1}}function ti(t,e){let{$anchor:n,$head:r}=t.selection,i=e>0?n.max(r):n.min(r),o=i.parent.inlineContent?i.depth?t.doc.resolve(e>0?i.after():i.before()):null:i;return o&&Xe.findFrom(o,e)}function ei(t,e){return t.dispatch(t.state.tr.setSelection(e).scrollIntoView()),!0}function ni(t,e,n){let r=t.state.selection;if(!(r instanceof nn)){if(r instanceof on&&r.node.isInline)return ei(t,new nn(e>0?r.$to:r.$from));{let n=ti(t.state,e);return!!n&&ei(t,n)}}if(n.indexOf("s")>-1){let{$head:n}=r,i=n.textOffset?null:e<0?n.nodeBefore:n.nodeAfter;if(!i||i.isText||!i.isLeaf)return!1;let o=t.state.doc.resolve(n.pos+i.nodeSize*(e<0?-1:1));return ei(t,new nn(r.$anchor,o))}if(!r.empty)return!1;if(t.endOfTextblock(e>0?"forward":"backward")){let n=ti(t.state,e);return!!(n&&n instanceof on)&&ei(t,n)}if(!(Hn&&n.indexOf("m")>-1)){let n,i=r.$head,o=i.textOffset?null:e<0?i.nodeBefore:i.nodeAfter;if(!o||o.isText)return!1;let s=e<0?i.pos-o.nodeSize:i.pos;return!!(o.isAtom||(n=t.docView.descAt(s))&&!n.contentDOM)&&(on.isSelectable(o)?ei(t,new on(e<0?t.state.doc.resolve(i.pos-o.nodeSize):i)):!!Gn&&ei(t,new nn(t.state.doc.resolve(e<0?s:s+o.nodeSize))))}}function ri(t){return 3==t.nodeType?t.nodeValue.length:t.childNodes.length}function ii(t,e){let n=t.pmViewDesc;return n&&0==n.size&&(e<0||t.nextSibling||"BR"!=t.nodeName)}function oi(t,e){return e<0?function(t){let e=t.domSelectionRange(),n=e.focusNode,r=e.focusOffset;if(!n)return;let i,o,s=!1;Fn&&1==n.nodeType&&r0){if(1!=n.nodeType)break;{let t=n.childNodes[r-1];if(ii(t,-1))i=n,o=--r;else{if(3!=t.nodeType)break;n=t,r=n.nodeValue.length}}}else{if(si(n))break;{let e=n.previousSibling;for(;e&&ii(e,-1);)i=n.parentNode,o=Sn(e),e=e.previousSibling;if(e)n=e,r=ri(n);else{if(n=n.parentNode,n==t.dom)break;r=0}}}s?li(t,n,r):i&&li(t,i,o)}(t):function(t){let e=t.domSelectionRange(),n=e.focusNode,r=e.focusOffset;if(!n)return;let i,o,s=ri(n);for(;;)if(r{t.state==i&&qr(t)}),50)}function ai(t,e){let n=t.state.doc.resolve(e);if(!Wn&&!Yn&&n.parent.inlineContent){let r=t.coordsAtPos(e);if(e>n.start()){let n=t.coordsAtPos(e-1),i=(n.top+n.bottom)/2;if(i>r.top&&i1)return n.leftr.top&&i1)return n.left>r.left?"ltr":"rtl"}}return"rtl"==getComputedStyle(t.dom).direction?"rtl":"ltr"}function ci(t,e,n){let r=t.state.selection;if(r instanceof nn&&!r.empty||n.indexOf("s")>-1)return!1;if(Hn&&n.indexOf("m")>-1)return!1;let{$from:i,$to:o}=r;if(!i.parent.inlineContent||t.endOfTextblock(e<0?"up":"down")){let n=ti(t.state,e);if(n&&n instanceof on)return ei(t,n)}if(!i.parent.inlineContent){let n=e<0?i:o,s=r instanceof ln?Xe.near(n,e):Xe.findFrom(n,e);return!!s&&ei(t,s)}return!1}function hi(t,e){if(!(t.state.selection instanceof nn))return!0;let{$head:n,$anchor:r,empty:i}=t.state.selection;if(!n.sameParent(r))return!0;if(!i)return!1;if(t.endOfTextblock(e>0?"forward":"backward"))return!0;let o=!n.textOffset&&(e<0?n.nodeBefore:n.nodeAfter);if(o&&!o.isText){let r=t.state.tr;return e<0?r.delete(n.pos-o.nodeSize,n.pos):r.delete(n.pos,n.pos+o.nodeSize),t.dispatch(r),!0}return!1}function ui(t,e,n){t.domObserver.stop(),e.contentEditable=n,t.domObserver.start()}function di(t,e){let n=e.keyCode,r=function(t){let e="";return t.ctrlKey&&(e+="c"),t.metaKey&&(e+="m"),t.altKey&&(e+="a"),t.shiftKey&&(e+="s"),e}(e);if(8==n||Hn&&72==n&&"c"==r)return hi(t,-1)||oi(t,-1);if(46==n&&!e.shiftKey||Hn&&68==n&&"c"==r)return hi(t,1)||oi(t,1);if(13==n||27==n)return!0;if(37==n||Hn&&66==n&&"c"==r){let e=37==n?"ltr"==ai(t,t.state.selection.from)?-1:1:-1;return ni(t,e,r)||oi(t,e)}if(39==n||Hn&&70==n&&"c"==r){let e=39==n?"ltr"==ai(t,t.state.selection.from)?1:-1:1;return ni(t,e,r)||oi(t,e)}return 38==n||Hn&&80==n&&"c"==r?ci(t,-1,r)||oi(t,-1):40==n||Hn&&78==n&&"c"==r?function(t){if(!Jn||t.state.selection.$head.parentOffset>0)return!1;let{focusNode:e,focusOffset:n}=t.domSelectionRange();if(e&&1==e.nodeType&&0==n&&e.firstChild&&"false"==e.firstChild.contentEditable){let n=e.firstChild;ui(t,n,"true"),setTimeout((()=>ui(t,n,"false")),20)}return!1}(t)||ci(t,1,r)||oi(t,1):r==(Hn?"m":"c")&&(66==n||73==n||89==n||90==n)}function fi(t,e){t.someProp("transformCopied",(n=>{e=n(e,t)}));let n=[],{content:r,openStart:i,openEnd:o}=e;for(;i>1&&o>1&&1==r.childCount&&1==r.firstChild.childCount;){i--,o--;let t=r.firstChild;n.push(t.type.name,t.attrs!=t.type.defaultAttrs?t.attrs:null),r=t.content}let s=t.someProp("clipboardSerializer")||de.fromSchema(t.state.schema),l=ki(),a=l.createElement("div");a.appendChild(s.serializeFragment(r,{document:l}));let c,h=a.firstChild,u=0;for(;h&&1==h.nodeType&&(c=xi[h.nodeName.toLowerCase()]);){for(let t=c.length-1;t>=0;t--){let e=l.createElement(c[t]);for(;a.firstChild;)e.appendChild(a.firstChild);a.appendChild(e),u++}h=a.firstChild}return h&&1==h.nodeType&&h.setAttribute("data-pm-slice",`${i} ${o}${u?` -${u}`:""} ${JSON.stringify(n)}`),{dom:a,text:t.someProp("clipboardTextSerializer",(n=>n(e,t)))||e.content.textBetween(0,e.content.size,"\n\n")}}function pi(t,e,n,r,i){let o,s,l=i.parent.type.spec.code;if(!n&&!e)return null;let a=e&&(r||l||!n);if(a){if(t.someProp("transformPastedText",(n=>{e=n(e,l||r,t)})),l)return e?new vt(dt.from(t.state.schema.text(e.replace(/\r\n?/g,"\n"))),0,0):vt.empty;let n=t.someProp("clipboardTextParser",(n=>n(e,i,r,t)));if(n)s=n;else{let n=i.marks(),{schema:r}=t.state,s=de.fromSchema(r);o=document.createElement("div"),e.split(/(?:\r\n?|\n)+/).forEach((t=>{let e=o.appendChild(document.createElement("p"));t&&e.appendChild(s.serializeNode(r.text(t,n)))}))}}else t.someProp("transformPastedHTML",(e=>{n=e(n,t)})),o=function(t){let e=/^(\s*]*>)*/.exec(t);e&&(t=t.slice(e[0].length));let n,r=ki().createElement("div"),i=/<([a-z][^>\s]+)/i.exec(t);(n=i&&xi[i[1].toLowerCase()])&&(t=n.map((t=>"<"+t+">")).join("")+t+n.map((t=>"")).reverse().join(""));if(r.innerHTML=t,n)for(let o=0;o0;u--){let t=o.firstChild;for(;t&&1!=t.nodeType;)t=t.nextSibling;if(!t)break;o=t}if(!s){let e=t.someProp("clipboardParser")||t.someProp("domParser")||ne.fromSchema(t.state.schema);s=e.parseSlice(o,{preserveWhitespace:!(!a&&!h),context:i,ruleFromNode:t=>"BR"!=t.nodeName||t.nextSibling||!t.parentNode||mi.test(t.parentNode.nodeName)?null:{ignore:!0}})}if(h)s=function(t,e){if(!t.size)return t;let n,r=t.content.firstChild.type.schema;try{n=JSON.parse(e)}catch(l){return t}let{content:i,openStart:o,openEnd:s}=t;for(let a=n.length-2;a>=0;a-=2){let t=r.nodes[n[a]];if(!t||t.hasRequiredAttrs())break;i=dt.from(t.create(n[a+1],i)),o++,s++}return new vt(i,o,s)}(bi(s,+h[1],+h[2]),h[4]);else if(s=vt.maxOpen(function(t,e){if(t.childCount<2)return t;for(let n=e.depth;n>=0;n--){let r,i=e.node(n).contentMatchAt(e.index(n)),o=[];if(t.forEach((t=>{if(!o)return;let e,n=i.findWrapping(t.type);if(!n)return o=null;if(e=o.length&&r.length&&yi(n,r,t,o[o.length-1],0))o[o.length-1]=e;else{o.length&&(o[o.length-1]=vi(o[o.length-1],r.length));let e=gi(t,n);o.push(e),i=i.matchType(e.type),r=n}})),o)return dt.from(o)}return t}(s.content,i),!0),s.openStart||s.openEnd){let t=0,e=0;for(let n=s.content.firstChild;t{s=e(s,t)})),s}const mi=/^(a|abbr|acronym|b|cite|code|del|em|i|ins|kbd|label|output|q|ruby|s|samp|span|strong|sub|sup|time|u|tt|var)$/i;function gi(t,e,n=0){for(let r=e.length-1;r>=n;r--)t=e[r].create(null,dt.from(t));return t}function yi(t,e,n,r,i){if(i1&&(o=0),i=n&&(l=e<0?s.contentMatchAt(0).fillBefore(l,o<=i).append(l):l.append(s.contentMatchAt(s.childCount).fillBefore(dt.empty,!0))),t.replaceChild(e<0?0:t.childCount-1,s.copy(l))}function bi(t,e,n){return e{for(let n in e)t.input.eventHandlers[n]||t.dom.addEventListener(n,t.input.eventHandlers[n]=e=>Ai(t,e))}))}function Ai(t,e){return t.someProp("handleDOMEvents",(n=>{let r=n[e.type];return!!r&&(r(t,e)||e.defaultPrevented)}))}function Ei(t,e){if(!e.bubbles)return!0;if(e.defaultPrevented)return!1;for(let n=e.target;n!=t.dom;n=n.parentNode)if(!n||11==n.nodeType||n.pmViewDesc&&n.pmViewDesc.stopEvent(e))return!1;return!0}function $i(t){return{left:t.clientX,top:t.clientY}}function Pi(t,e,n,r,i){if(-1==r)return!1;let o=t.state.doc.resolve(r);for(let s=o.depth+1;s>0;s--)if(t.someProp(e,(e=>s>o.depth?e(t,n,o.nodeAfter,o.before(s),i,!0):e(t,n,o.node(s),o.before(s),i,!1))))return!0;return!1}function Ii(t,e,n){t.focused||t.focus();let r=t.state.tr.setSelection(e);"pointer"==n&&r.setMeta("pointer",!0),t.dispatch(r)}function Ri(t,e,n,r,i){return Pi(t,"handleClickOn",e,n,r)||t.someProp("handleClick",(n=>n(t,e,r)))||(i?function(t,e){if(-1==e)return!1;let n,r,i=t.state.selection;i instanceof on&&(n=i.node);let o=t.state.doc.resolve(e);for(let s=o.depth+1;s>0;s--){let t=s>o.depth?o.nodeAfter:o.node(s);if(on.isSelectable(t)){r=n&&i.$from.depth>0&&s>=i.$from.depth&&o.before(i.$from.depth+1)==i.$from.pos?o.before(i.$from.depth):o.before(s);break}}return null!=r&&(Ii(t,on.create(t.state.doc,r),"pointer"),!0)}(t,n):function(t,e){if(-1==e)return!1;let n=t.state.doc.resolve(e),r=n.nodeAfter;return!!(r&&r.isAtom&&on.isSelectable(r))&&(Ii(t,new on(n),"pointer"),!0)}(t,n))}function zi(t,e,n,r){return Pi(t,"handleDoubleClickOn",e,n,r)||t.someProp("handleDoubleClick",(n=>n(t,e,r)))}function _i(t,e,n,r){return Pi(t,"handleTripleClickOn",e,n,r)||t.someProp("handleTripleClick",(n=>n(t,e,r)))||function(t,e,n){if(0!=n.button)return!1;let r=t.state.doc;if(-1==e)return!!r.inlineContent&&(Ii(t,nn.create(r,0,r.content.size),"pointer"),!0);let i=r.resolve(e);for(let o=i.depth+1;o>0;o--){let e=o>i.depth?i.nodeAfter:i.node(o),n=i.before(o);if(e.inlineContent)Ii(t,nn.create(r,n+1,n+1+e.content.size),"pointer");else{if(!on.isSelectable(e))continue;Ii(t,on.create(r,n),"pointer")}return!0}}(t,n,r)}function Bi(t){return Ji(t)}Oi.keydown=(t,e)=>{let n=e;if(t.input.shiftKey=16==n.keyCode||n.shiftKey,!Fi(t,n)&&(t.input.lastKeyCode=n.keyCode,t.input.lastKeyCodeTime=Date.now(),!Un||!Wn||13!=n.keyCode))if(229!=n.keyCode&&t.domObserver.forceFlush(),!Kn||13!=n.keyCode||n.ctrlKey||n.altKey||n.metaKey)t.someProp("handleKeyDown",(e=>e(t,n)))||di(t,n)?n.preventDefault():Ni(t,"key");else{let e=Date.now();t.input.lastIOSEnter=e,t.input.lastIOSEnterFallbackTimeout=setTimeout((()=>{t.input.lastIOSEnter==e&&(t.someProp("handleKeyDown",(e=>e(t,$n(13,"Enter")))),t.input.lastIOSEnter=0)}),200)}},Oi.keyup=(t,e)=>{16==e.keyCode&&(t.input.shiftKey=!1)},Oi.keypress=(t,e)=>{let n=e;if(Fi(t,n)||!n.charCode||n.ctrlKey&&!n.altKey||Hn&&n.metaKey)return;if(t.someProp("handleKeyPress",(e=>e(t,n))))return void n.preventDefault();let r=t.state.selection;if(!(r instanceof nn&&r.$from.sameParent(r.$to))){let e=String.fromCharCode(n.charCode);/[\r\n]/.test(e)||t.someProp("handleTextInput",(n=>n(t,r.$from.pos,r.$to.pos,e)))||t.dispatch(t.state.tr.insertText(e).scrollIntoView()),n.preventDefault()}};const ji=Hn?"metaKey":"ctrlKey";Mi.mousedown=(t,e)=>{let n=e;t.input.shiftKey=n.shiftKey;let r=Bi(t),i=Date.now(),o="singleClick";i-t.input.lastClick.time<500&&function(t,e){let n=e.x-t.clientX,r=e.y-t.clientY;return n*n+r*r<100}(n,t.input.lastClick)&&!n[ji]&&("singleClick"==t.input.lastClick.type?o="doubleClick":"doubleClick"==t.input.lastClick.type&&(o="tripleClick")),t.input.lastClick={time:i,x:n.clientX,y:n.clientY,type:o};let s=t.posAtCoords($i(n));s&&("singleClick"==o?(t.input.mouseDown&&t.input.mouseDown.done(),t.input.mouseDown=new Vi(t,s,n,!!r)):("doubleClick"==o?zi:_i)(t,s.pos,s.inside,n)?n.preventDefault():Ni(t,"pointer"))};class Vi{constructor(t,e,n,r){let i,o;if(this.view=t,this.pos=e,this.event=n,this.flushed=r,this.delayedSelectionSync=!1,this.mightDrag=null,this.startDoc=t.state.doc,this.selectNode=!!n[ji],this.allowDefault=n.shiftKey,e.inside>-1)i=t.state.doc.nodeAt(e.inside),o=e.inside;else{let n=t.state.doc.resolve(e.pos);i=n.parent,o=n.depth?n.before():0}const s=r?null:n.target,l=s?t.docView.nearestDesc(s,!0):null;this.target=l?l.dom:null;let{selection:a}=t.state;(0==n.button&&i.type.spec.draggable&&!1!==i.type.spec.selectable||a instanceof on&&a.from<=o&&a.to>o)&&(this.mightDrag={node:i,pos:o,addAttr:!(!this.target||this.target.draggable),setUneditable:!(!this.target||!Fn||this.target.hasAttribute("contentEditable"))}),this.target&&this.mightDrag&&(this.mightDrag.addAttr||this.mightDrag.setUneditable)&&(this.view.domObserver.stop(),this.mightDrag.addAttr&&(this.target.draggable=!0),this.mightDrag.setUneditable&&setTimeout((()=>{this.view.input.mouseDown==this&&this.target.setAttribute("contentEditable","false")}),20),this.view.domObserver.start()),t.root.addEventListener("mouseup",this.up=this.up.bind(this)),t.root.addEventListener("mousemove",this.move=this.move.bind(this)),Ni(t,"pointer")}done(){this.view.root.removeEventListener("mouseup",this.up),this.view.root.removeEventListener("mousemove",this.move),this.mightDrag&&this.target&&(this.view.domObserver.stop(),this.mightDrag.addAttr&&this.target.removeAttribute("draggable"),this.mightDrag.setUneditable&&this.target.removeAttribute("contentEditable"),this.view.domObserver.start()),this.delayedSelectionSync&&setTimeout((()=>qr(this.view))),this.view.input.mouseDown=null}up(t){if(this.done(),!this.view.dom.contains(t.target))return;let e=this.pos;this.view.state.doc!=this.startDoc&&(e=this.view.posAtCoords($i(t))),this.updateAllowDefault(t),this.allowDefault||!e?Ni(this.view,"pointer"):Ri(this.view,e.pos,e.inside,t,this.selectNode)?t.preventDefault():0==t.button&&(this.flushed||Jn&&this.mightDrag&&!this.mightDrag.node.isAtom||Wn&&!this.view.state.selection.visible&&Math.min(Math.abs(e.pos-this.view.state.selection.from),Math.abs(e.pos-this.view.state.selection.to))<=2)?(Ii(this.view,Xe.near(this.view.state.doc.resolve(e.pos)),"pointer"),t.preventDefault()):Ni(this.view,"pointer")}move(t){this.updateAllowDefault(t),Ni(this.view,"pointer"),0==t.buttons&&this.done()}updateAllowDefault(t){!this.allowDefault&&(Math.abs(this.event.x-t.clientX)>4||Math.abs(this.event.y-t.clientY)>4)&&(this.allowDefault=!0)}}function Fi(t,e){return!!t.composing||!!(Jn&&Math.abs(e.timeStamp-t.input.compositionEndedAt)<500)&&(t.input.compositionEndedAt=-2e8,!0)}Mi.touchstart=t=>{t.input.lastTouch=Date.now(),Bi(t),Ni(t,"pointer")},Mi.touchmove=t=>{t.input.lastTouch=Date.now(),Ni(t,"pointer")},Mi.contextmenu=t=>Bi(t);const Li=Un?5e3:-1;function Wi(t,e){clearTimeout(t.input.composingTimeout),e>-1&&(t.input.composingTimeout=setTimeout((()=>Ji(t)),e))}function qi(t){for(t.composing&&(t.input.composing=!1,t.input.compositionEndedAt=function(){let t=document.createEvent("Event");return t.initEvent("event",!0,!0),t.timeStamp}());t.input.compositionNodes.length>0;)t.input.compositionNodes.pop().markParentsDirty()}function Ji(t,e=!1){if(!(Un&&t.domObserver.flushingSoon>=0)){if(t.domObserver.forceFlush(),qi(t),e||t.docView&&t.docView.dirty){let e=Lr(t);return e&&!e.eq(t.state.selection)?t.dispatch(t.state.tr.setSelection(e)):t.updateState(t.state),!0}return!1}}Oi.compositionstart=Oi.compositionupdate=t=>{if(!t.composing){t.domObserver.flush();let{state:e}=t,n=e.selection.$from;if(e.selection.empty&&(e.storedMarks||!n.textOffset&&n.parentOffset&&n.nodeBefore.marks.some((t=>!1===t.type.spec.inclusive))))t.markCursor=t.state.storedMarks||n.marks(),Ji(t,!0),t.markCursor=null;else if(Ji(t),Fn&&e.selection.empty&&n.parentOffset&&!n.textOffset&&n.nodeBefore.marks.length){let e=t.domSelectionRange();for(let n=e.focusNode,r=e.focusOffset;n&&1==n.nodeType&&0!=r;){let e=r<0?n.lastChild:n.childNodes[r-1];if(!e)break;if(3==e.nodeType){t.domSelection().collapse(e,e.nodeValue.length);break}n=e,r=-1}}t.input.composing=!0}Wi(t,Li)},Oi.compositionend=(t,e)=>{t.composing&&(t.input.composing=!1,t.input.compositionEndedAt=e.timeStamp,t.input.compositionPendingChanges=t.domObserver.pendingRecords().length?t.input.compositionID:0,t.input.compositionPendingChanges&&Promise.resolve().then((()=>t.domObserver.flush())),t.input.compositionID++,Wi(t,20))};const Ki=jn&&Vn<15||Kn&&Zn<604;function Hi(t,e,n,r,i){let o=pi(t,e,n,r,t.state.selection.$from);if(t.someProp("handlePaste",(e=>e(t,i,o||vt.empty))))return!0;if(!o)return!1;let s=function(t){return 0==t.openStart&&0==t.openEnd&&1==t.content.childCount?t.content.firstChild:null}(o),l=s?t.state.tr.replaceSelectionWith(s,r):t.state.tr.replaceSelection(o);return t.dispatch(l.scrollIntoView().setMeta("paste",!0).setMeta("uiEvent","paste")),!0}function Yi(t){let e=t.getData("text/plain")||t.getData("Text");if(e)return e;let n=t.getData("text/uri-list");return n?n.replace(/\r?\n/g," "):""}Mi.copy=Oi.cut=(t,e)=>{let n=e,r=t.state.selection,i="cut"==n.type;if(r.empty)return;let o=Ki?null:n.clipboardData,s=r.content(),{dom:l,text:a}=fi(t,s);o?(n.preventDefault(),o.clearData(),o.setData("text/html",l.innerHTML),o.setData("text/plain",a)):function(t,e){if(!t.dom.parentNode)return;let n=t.dom.parentNode.appendChild(document.createElement("div"));n.appendChild(e),n.style.cssText="position: fixed; left: -10000px; top: 10px";let r=getSelection(),i=document.createRange();i.selectNodeContents(e),t.dom.blur(),r.removeAllRanges(),r.addRange(i),setTimeout((()=>{n.parentNode&&n.parentNode.removeChild(n),t.focus()}),50)}(t,l),i&&t.dispatch(t.state.tr.deleteSelection().scrollIntoView().setMeta("uiEvent","cut"))},Oi.paste=(t,e)=>{let n=e;if(t.composing&&!Un)return;let r=Ki?null:n.clipboardData,i=t.input.shiftKey&&45!=t.input.lastKeyCode;r&&Hi(t,Yi(r),r.getData("text/html"),i,n)?n.preventDefault():function(t,e){if(!t.dom.parentNode)return;let n=t.input.shiftKey||t.state.selection.$from.parent.type.spec.code,r=t.dom.parentNode.appendChild(document.createElement(n?"textarea":"div"));n||(r.contentEditable="true"),r.style.cssText="position: fixed; left: -10000px; top: 10px",r.focus();let i=t.input.shiftKey&&45!=t.input.lastKeyCode;setTimeout((()=>{t.focus(),r.parentNode&&r.parentNode.removeChild(r),n?Hi(t,r.value,null,i,e):Hi(t,r.textContent,r.innerHTML,i,e)}),50)}(t,n)};class Ui{constructor(t,e,n){this.slice=t,this.move=e,this.node=n}}const Gi=Hn?"altKey":"ctrlKey";Mi.dragstart=(t,e)=>{let n=e,r=t.input.mouseDown;if(r&&r.done(),!n.dataTransfer)return;let i,o=t.state.selection,s=o.empty?null:t.posAtCoords($i(n));if(s&&s.pos>=o.from&&s.pos<=(o instanceof on?o.to-1:o.to));else if(r&&r.mightDrag)i=on.create(t.state.doc,r.mightDrag.pos);else if(n.target&&1==n.target.nodeType){let e=t.docView.nearestDesc(n.target,!0);e&&e.node.type.spec.draggable&&e!=t.docView&&(i=on.create(t.state.doc,e.posBefore))}let l=(i||t.state.selection).content(),{dom:a,text:c}=fi(t,l);n.dataTransfer.clearData(),n.dataTransfer.setData(Ki?"Text":"text/html",a.innerHTML),n.dataTransfer.effectAllowed="copyMove",Ki||n.dataTransfer.setData("text/plain",c),t.dragging=new Ui(l,!n[Gi],i)},Mi.dragend=t=>{let e=t.dragging;window.setTimeout((()=>{t.dragging==e&&(t.dragging=null)}),50)},Oi.dragover=Oi.dragenter=(t,e)=>e.preventDefault(),Oi.drop=(t,e)=>{let n=e,r=t.dragging;if(t.dragging=null,!n.dataTransfer)return;let i=t.posAtCoords($i(n));if(!i)return;let o=t.state.doc.resolve(i.pos),s=r&&r.slice;s?t.someProp("transformPasted",(e=>{s=e(s,t)})):s=pi(t,Yi(n.dataTransfer),Ki?null:n.dataTransfer.getData("text/html"),!1,o);let l=!(!r||n[Gi]);if(t.someProp("handleDrop",(e=>e(t,n,s||vt.empty,l))))return void n.preventDefault();if(!s)return;n.preventDefault();let a=s?function(t,e,n){let r=t.resolve(e);if(!n.content.size)return e;let i=n.content;for(let o=0;o=0;t--){let e=t==r.depth?0:r.pos<=(r.start(t+1)+r.end(t+1))/2?-1:1,n=r.index(t)+(e>0?1:0),s=r.node(t),l=!1;if(1==o)l=s.canReplace(n,n,i);else{let t=s.contentMatchAt(n).findWrapping(i.firstChild.type);l=t&&s.canReplaceWith(n,n,t[0])}if(l)return 0==e?r.pos:e<0?r.before(t+1):r.after(t+1)}return null}(t.state.doc,o.pos,s):o.pos;null==a&&(a=o.pos);let c=t.state.tr;if(l){let{node:t}=r;t?t.replace(c):c.deleteSelection()}let h=c.mapping.map(a),u=0==s.openStart&&0==s.openEnd&&1==s.content.childCount,d=c.doc;if(u?c.replaceRangeWith(h,h,s.content.firstChild):c.replaceRange(h,h,s),c.doc.eq(d))return;let f=c.doc.resolve(h);if(u&&on.isSelectable(s.content.firstChild)&&f.nodeAfter&&f.nodeAfter.sameMarkup(s.content.firstChild))c.setSelection(new on(f));else{let e=c.mapping.map(a);c.mapping.maps[c.mapping.maps.length-1].forEach(((t,n,r,i)=>e=i)),c.setSelection(Zr(t,f,c.doc.resolve(e)))}t.focus(),t.dispatch(c.setMeta("uiEvent","drop"))},Mi.focus=t=>{t.input.lastFocus=Date.now(),t.focused||(t.domObserver.stop(),t.dom.classList.add("ProseMirror-focused"),t.domObserver.start(),t.focused=!0,setTimeout((()=>{t.docView&&t.hasFocus()&&!t.domObserver.currentSelection.eq(t.domSelectionRange())&&qr(t)}),20))},Mi.blur=(t,e)=>{let n=e;t.focused&&(t.domObserver.stop(),t.dom.classList.remove("ProseMirror-focused"),t.domObserver.start(),n.relatedTarget&&t.dom.contains(n.relatedTarget)&&t.domObserver.currentSelection.clear(),t.focused=!1)},Mi.beforeinput=(t,e)=>{if(Wn&&Un&&"deleteContentBackward"==e.inputType){t.domObserver.flushSoon();let{domChangeCount:e}=t.input;setTimeout((()=>{if(t.input.domChangeCount!=e)return;if(t.dom.blur(),t.focus(),t.someProp("handleKeyDown",(e=>e(t,$n(8,"Backspace")))))return;let{$cursor:n}=t.state.selection;n&&n.pos>0&&t.dispatch(t.state.tr.delete(n.pos-1,n.pos).scrollIntoView())}),50)}};for(let os in Oi)Mi[os]=Oi[os];function Zi(t,e){if(t==e)return!0;for(let n in t)if(t[n]!==e[n])return!1;for(let n in e)if(!(n in t))return!1;return!0}class Xi{constructor(t,e){this.toDOM=t,this.spec=e||ro,this.side=this.spec.side||0}map(t,e,n,r){let{pos:i,deleted:o}=t.mapResult(e.from+r,this.side<0?-1:1);return o?null:new eo(i-n,i-n,this)}valid(){return!0}eq(t){return this==t||t instanceof Xi&&(this.spec.key&&this.spec.key==t.spec.key||this.toDOM==t.toDOM&&Zi(this.spec,t.spec))}destroy(t){this.spec.destroy&&this.spec.destroy(t)}}class Qi{constructor(t,e){this.attrs=t,this.spec=e||ro}map(t,e,n,r){let i=t.map(e.from+r,this.spec.inclusiveStart?-1:1)-n,o=t.map(e.to+r,this.spec.inclusiveEnd?1:-1)-n;return i>=o?null:new eo(i,o,this)}valid(t,e){return e.from=t&&(!i||i(s.spec))&&n.push(s.copy(s.from+r,s.to+r))}for(let o=0;ot){let s=this.children[o]+1;this.children[o+2].findInner(t-s,e-s,n,r+s,i)}}map(t,e,n){return this==oo||0==t.maps.length?this:this.mapInner(t,e,0,0,n||ro)}mapInner(t,e,n,r,i){let o;for(let s=0;s{let s=o-r-(n-e);for(let a=0;ao+h-t)continue;let c=l[a]+h-t;n>=c?l[a+1]=e<=c?-2:-1:r>=i&&s&&(l[a]+=s,l[a+1]+=s)}t+=s})),h=n.maps[c].map(h,-1)}let a=!1;for(let c=0;c=r.content.size){a=!0;continue}let u=n.map(t[c+1]+o,-1)-i,{index:d,offset:f}=r.content.findIndex(h),p=r.maybeChild(d);if(p&&f==h&&f+p.nodeSize==u){let r=l[c+2].mapInner(n,p,e+1,t[c]+o+1,s);r!=oo?(l[c]=h,l[c+1]=u,l[c+2]=r):(l[c+1]=-2,a=!0)}else a=!0}if(a){let a=function(t,e,n,r,i,o,s){function l(t,e){for(let o=0;o{let s,l=o+n;if(s=ao(e,t,l)){for(r||(r=this.children.slice());io&&e.to=t){this.children[s]==t&&(n=this.children[s+2]);break}let i=t+1,o=i+e.content.size;for(let s=0;si&&t.type instanceof Qi){let e=Math.max(i,t.from)-i,n=Math.min(o,t.to)-i;en.map(t,e,ro)));return so.from(n)}forChild(t,e){if(e.isLeaf)return io.empty;let n=[];for(let r=0;rt instanceof io))?t:t.reduce(((t,e)=>t.concat(e instanceof io?e:e.members)),[]))}}}function lo(t,e){if(!e||!t.length)return t;let n=[];for(let r=0;rn&&o.to{let l=ao(t,e,s+n);if(l){o=!0;let t=ho(l,e,n+s+1,r);t!=oo&&i.push(s,s+e.nodeSize,t)}}));let s=lo(o?co(t):t,-n).sort(uo);for(let l=0;l0;)e++;t.splice(e,0,n)}function mo(t){let e=[];return t.someProp("decorations",(n=>{let r=n(t.state);r&&r!=oo&&e.push(r)})),t.cursorWrapper&&e.push(io.create(t.state.doc,[t.cursorWrapper.deco])),so.from(e)}const go={childList:!0,characterData:!0,characterDataOldValue:!0,attributes:!0,attributeOldValue:!0,subtree:!0},yo=jn&&Vn<=11;class vo{constructor(){this.anchorNode=null,this.anchorOffset=0,this.focusNode=null,this.focusOffset=0}set(t){this.anchorNode=t.anchorNode,this.anchorOffset=t.anchorOffset,this.focusNode=t.focusNode,this.focusOffset=t.focusOffset}clear(){this.anchorNode=this.focusNode=null}eq(t){return t.anchorNode==this.anchorNode&&t.anchorOffset==this.anchorOffset&&t.focusNode==this.focusNode&&t.focusOffset==this.focusOffset}}class wo{constructor(t,e){this.view=t,this.handleDOMChange=e,this.queue=[],this.flushingSoon=-1,this.observer=null,this.currentSelection=new vo,this.onCharData=null,this.suppressingSelectionUpdates=!1,this.observer=window.MutationObserver&&new window.MutationObserver((t=>{for(let e=0;e"childList"==t.type&&t.removedNodes.length||"characterData"==t.type&&t.oldValue.length>t.target.nodeValue.length))?this.flushSoon():this.flush()})),yo&&(this.onCharData=t=>{this.queue.push({target:t.target,type:"characterData",oldValue:t.prevValue}),this.flushSoon()}),this.onSelectionChange=this.onSelectionChange.bind(this)}flushSoon(){this.flushingSoon<0&&(this.flushingSoon=window.setTimeout((()=>{this.flushingSoon=-1,this.flush()}),20))}forceFlush(){this.flushingSoon>-1&&(window.clearTimeout(this.flushingSoon),this.flushingSoon=-1,this.flush())}start(){this.observer&&(this.observer.takeRecords(),this.observer.observe(this.view.dom,go)),this.onCharData&&this.view.dom.addEventListener("DOMCharacterDataModified",this.onCharData),this.connectSelection()}stop(){if(this.observer){let t=this.observer.takeRecords();if(t.length){for(let e=0;ethis.flush()),20)}this.observer.disconnect()}this.onCharData&&this.view.dom.removeEventListener("DOMCharacterDataModified",this.onCharData),this.disconnectSelection()}connectSelection(){this.view.dom.ownerDocument.addEventListener("selectionchange",this.onSelectionChange)}disconnectSelection(){this.view.dom.ownerDocument.removeEventListener("selectionchange",this.onSelectionChange)}suppressSelectionUpdates(){this.suppressingSelectionUpdates=!0,setTimeout((()=>this.suppressingSelectionUpdates=!1),50)}onSelectionChange(){if(Xr(this.view)){if(this.suppressingSelectionUpdates)return qr(this.view);if(jn&&Vn<=11&&!this.view.state.selection.empty){let t=this.view.domSelectionRange();if(t.focusNode&&Cn(t.focusNode,t.focusOffset,t.anchorNode,t.anchorOffset))return this.flushSoon()}this.flush()}}setCurSelection(){this.currentSelection.set(this.view.domSelectionRange())}ignoreSelectionChange(t){if(!t.focusNode)return!0;let e,n=new Set;for(let i=t.focusNode;i;i=kn(i))n.add(i);for(let i=t.anchorNode;i;i=kn(i))if(n.has(i)){e=i;break}let r=e&&this.view.docView.nearestDesc(e);return r&&r.ignoreMutation({type:"selection",target:3==e.nodeType?e.parentNode:e})?(this.setCurSelection(),!0):void 0}pendingRecords(){if(this.observer)for(let t of this.observer.takeRecords())this.queue.push(t);return this.queue}flush(){let{view:t}=this;if(!t.docView||this.flushingSoon>-1)return;let e=this.pendingRecords();e.length&&(this.queue=[]);let n=t.domSelectionRange(),r=!this.suppressingSelectionUpdates&&!this.currentSelection.eq(n)&&Xr(t)&&!this.ignoreSelectionChange(n),i=-1,o=-1,s=!1,l=[];if(t.editable)for(let c=0;c1){let t=l.filter((t=>"BR"==t.nodeName));if(2==t.length){let e=t[0],n=t[1];e.parentNode&&e.parentNode.parentNode==n.parentNode?n.remove():e.remove()}}let a=null;i<0&&r&&t.input.lastFocus>Date.now()-200&&Math.max(t.input.lastTouch,t.input.lastClick.time)-1||r)&&(i>-1&&(t.docView.markDirty(i,o),function(t){if(bo.has(t))return;if(bo.set(t,null),-1!==["normal","nowrap","pre-line"].indexOf(getComputedStyle(t.dom).whiteSpace)){if(t.requiresGeckoHackNode=Fn,xo)return;console.warn("ProseMirror expects the CSS white-space property to be set, preferably to 'pre-wrap'. It is recommended to load style/prosemirror.css from the prosemirror-view package."),xo=!0}}(t)),this.handleDOMChange(i,o,s,l),t.docView&&t.docView.dirty?t.updateState(t.state):this.currentSelection.eq(n)||qr(t),this.currentSelection.set(n))}registerMutation(t,e){if(e.indexOf(t.target)>-1)return null;let n=this.view.docView.nearestDesc(t.target);if("attributes"==t.type&&(n==this.view.docView||"contenteditable"==t.attributeName||"style"==t.attributeName&&!t.oldValue&&!t.target.getAttribute("style")))return null;if(!n||n.ignoreMutation(t))return null;if("childList"==t.type){for(let n=0;nDate.now()-50?t.input.lastSelectionOrigin:null,n=Lr(t,e);if(n&&!t.state.selection.eq(n)){if(Wn&&Un&&13===t.input.lastKeyCode&&Date.now()-100e(t,$n(13,"Enter")))))return;let r=t.state.tr.setSelection(n);"pointer"==e?r.setMeta("pointer",!0):"key"==e&&r.scrollIntoView(),o&&r.setMeta("composition",o),t.dispatch(r)}return}let s=t.state.doc.resolve(e),l=s.sharedDepth(n);e=s.before(l+1),n=t.state.doc.resolve(n).after(l+1);let a,c,h=t.state.selection,u=function(t,e,n){let r,{node:i,fromOffset:o,toOffset:s,from:l,to:a}=t.docView.parseRange(e,n),c=t.domSelectionRange(),h=c.anchorNode;if(h&&t.dom.contains(1==h.nodeType?h:h.parentNode)&&(r=[{node:h,offset:c.anchorOffset}],En(c)||r.push({node:c.focusNode,offset:c.focusOffset})),Wn&&8===t.input.lastKeyCode)for(let g=s;g>o;g--){let t=i.childNodes[g-1],e=t.pmViewDesc;if("BR"==t.nodeName&&!e){s=g;break}if(!e||e.size)break}let u=t.state.doc,d=t.someProp("domParser")||ne.fromSchema(t.state.schema),f=u.resolve(l),p=null,m=d.parse(i,{topNode:f.parent,topMatch:f.parent.contentMatchAt(f.index()),topOpen:!0,from:o,to:s,preserveWhitespace:"pre"!=f.parent.type.whitespace||"full",findPositions:r,ruleFromNode:So,context:f});if(r&&null!=r[0].pos){let t=r[0].pos,e=r[1]&&r[1].pos;null==e&&(e=t),p={anchor:t+l,head:e+l}}return{doc:m,sel:p,from:l,to:a}}(t,e,n),d=t.state.doc,f=d.slice(u.from,u.to);8===t.input.lastKeyCode&&Date.now()-100=s?o-r:0;o-=t,o&&o=l?o-r:0;o-=e,o&&oDate.now()-225||Un)&&i.some((t=>1==t.nodeType&&!ko.test(t.nodeName)))&&(!p||p.endA>=p.endB)&&t.someProp("handleKeyDown",(e=>e(t,$n(13,"Enter")))))return void(t.input.lastIOSEnter=0);if(!p){if(!(r&&h instanceof nn&&!h.empty&&h.$head.sameParent(h.$anchor))||t.composing||u.sel&&u.sel.anchor!=u.sel.head){if(u.sel){let e=Oo(t,t.state.doc,u.sel);if(e&&!e.eq(t.state.selection)){let n=t.state.tr.setSelection(e);o&&n.setMeta("composition",o),t.dispatch(n)}}return}p={start:h.from,endA:h.to,endB:h.to}}if(Wn&&t.cursorWrapper&&u.sel&&u.sel.anchor==t.cursorWrapper.deco.from&&u.sel.head==u.sel.anchor){let t=p.endB-p.start;u.sel={anchor:u.sel.anchor+t,head:u.sel.anchor+t}}t.input.domChangeCount++,t.state.selection.fromt.state.selection.from&&p.start<=t.state.selection.from+2&&t.state.selection.from>=u.from?p.start=t.state.selection.from:p.endA=t.state.selection.to-2&&t.state.selection.to<=u.to&&(p.endB+=t.state.selection.to-p.endA,p.endA=t.state.selection.to)),jn&&Vn<=11&&p.endB==p.start+1&&p.endA==p.start&&p.start>u.from&&"  "==u.doc.textBetween(p.start-u.from-1,p.start-u.from+1)&&(p.start--,p.endA--,p.endB--);let m,g=u.doc.resolveNoCache(p.start-u.from),y=u.doc.resolveNoCache(p.endB-u.from),v=d.resolve(p.start),w=g.sameParent(y)&&g.parent.inlineContent&&v.end()>=p.endA;if((Kn&&t.input.lastIOSEnter>Date.now()-225&&(!w||i.some((t=>"DIV"==t.nodeName||"P"==t.nodeName)))||!w&&g.pose(t,$n(13,"Enter")))))return void(t.input.lastIOSEnter=0);if(t.state.selection.anchor>p.start&&function(t,e,n,r,i){if(!r.parent.isTextblock||n-e<=i.pos-r.pos||Co(r,!0,!1)n||Co(s,!0,!1)e(t,$n(8,"Backspace")))))return void(Un&&Wn&&t.domObserver.suppressSelectionUpdates());Wn&&Un&&p.endB==p.start&&(t.input.lastAndroidDelete=Date.now()),Un&&!w&&g.start()!=y.start()&&0==y.parentOffset&&g.depth==y.depth&&u.sel&&u.sel.anchor==u.sel.head&&u.sel.head==p.endA&&(p.endB-=2,y=u.doc.resolveNoCache(p.endB-u.from),setTimeout((()=>{t.someProp("handleKeyDown",(function(e){return e(t,$n(13,"Enter"))}))}),20));let b,x,S,k=p.start,M=p.endA;if(w)if(g.pos==y.pos)jn&&Vn<=11&&0==g.parentOffset&&(t.domObserver.suppressSelectionUpdates(),setTimeout((()=>qr(t)),20)),b=t.state.tr.delete(k,M),x=d.resolve(p.start).marksAcross(d.resolve(p.endA));else if(p.endA==p.endB&&(S=function(t,e){let n,r,i,o=t.firstChild.marks,s=e.firstChild.marks,l=o,a=s;for(let h=0;ht.mark(r.addToSet(t.marks));else{if(0!=l.length||1!=a.length)return null;r=a[0],n="remove",i=t=>t.mark(r.removeFromSet(t.marks))}let c=[];for(let h=0;hn(t,k,M,e))))return;b=t.state.tr.insertText(e,k,M)}if(b||(b=t.state.tr.replace(k,M,u.doc.slice(p.start-u.from,p.endB-u.from))),u.sel){let e=Oo(t,b.doc,u.sel);e&&!(Wn&&Un&&t.composing&&e.empty&&(p.start!=p.endB||t.input.lastAndroidDeletee.content.size?null:Zr(t,e.resolve(n.anchor),e.resolve(n.head))}function Co(t,e,n){let r=t.depth,i=e?t.end():t.pos;for(;r>0&&(e||t.indexAfter(r)==t.node(r).childCount);)r--,i++,e=!1;if(n){let e=t.node(r).maybeChild(t.indexAfter(r));for(;e&&!e.isLeaf;)e=e.firstChild,i++}return i}function Do(t){if(2!=t.length)return!1;let e=t.charCodeAt(0),n=t.charCodeAt(1);return e>=56320&&e<=57343&&n>=55296&&n<=56319}class No{constructor(t,e){this._root=null,this.focused=!1,this.trackWrites=null,this.mounted=!1,this.markCursor=null,this.cursorWrapper=null,this.lastSelectedViewDesc=void 0,this.input=new Di,this.prevDirectPlugins=[],this.pluginViews=[],this.requiresGeckoHackNode=!1,this.dragging=null,this._props=e,this.state=e.state,this.directPlugins=e.plugins||[],this.directPlugins.forEach(Po),this.dispatch=this.dispatch.bind(this),this.dom=t&&t.mount||document.createElement("div"),t&&(t.appendChild?t.appendChild(this.dom):"function"==typeof t?t(this.dom):t.mount&&(this.mounted=!0)),this.editable=Eo(this),Ao(this),this.nodeViews=$o(this),this.docView=Cr(this.state.doc,To(this),mo(this),this.dom,this),this.domObserver=new wo(this,((t,e,n,r)=>Mo(this,t,e,n,r))),this.domObserver.start(),function(t){for(let e in Mi){let n=Mi[e];t.dom.addEventListener(e,t.input.eventHandlers[e]=e=>{!Ei(t,e)||Ai(t,e)||!t.editable&&e.type in Oi||n(t,e)},Ci[e]?{passive:!0}:void 0)}Jn&&t.dom.addEventListener("input",(()=>null)),Ti(t)}(this),this.updatePluginViews()}get composing(){return this.input.composing}get props(){if(this._props.state!=this.state){let t=this._props;this._props={};for(let e in t)this._props[e]=t[e];this._props.state=this.state}return this._props}update(t){t.handleDOMEvents!=this._props.handleDOMEvents&&Ti(this);let e=this._props;this._props=t,t.plugins&&(t.plugins.forEach(Po),this.directPlugins=t.plugins),this.updateStateInner(t.state,e)}setProps(t){let e={};for(let n in this._props)e[n]=this._props[n];e.state=this.state;for(let n in t)e[n]=t[n];this.update(e)}updateState(t){this.updateStateInner(t,this._props)}updateStateInner(t,e){var n;let r=this.state,i=!1,o=!1;t.storedMarks&&this.composing&&(qi(this),o=!0),this.state=t;let s=r.plugins!=t.plugins||this._props.plugins!=e.plugins;if(s||this._props.plugins!=e.plugins||this._props.nodeViews!=e.nodeViews){let t=$o(this);(function(t,e){let n=0,r=0;for(let i in t){if(t[i]!=e[i])return!0;n++}for(let i in e)r++;return n!=r})(t,this.nodeViews)&&(this.nodeViews=t,i=!0)}(s||e.handleDOMEvents!=this._props.handleDOMEvents)&&Ti(this),this.editable=Eo(this),Ao(this);let l=mo(this),a=To(this),c=r.plugins==t.plugins||r.doc.eq(t.doc)?t.scrollToSelection>r.scrollToSelection?"to selection":"preserve":"reset",h=i||!this.docView.matchesNode(t.doc,a,l);!h&&t.selection.eq(r.selection)||(o=!0);let u="preserve"==c&&o&&null==this.dom.style.overflowAnchor&&function(t){let e,n,r=t.dom.getBoundingClientRect(),i=Math.max(0,r.top);for(let o=(r.left+r.right)/2,s=i+1;s=i-20){e=r,n=l.top;break}}return{refDOM:e,refTop:n,stack:nr(t.dom)}}(this);if(o){this.domObserver.stop();let e=h&&(jn||Wn)&&!this.composing&&!r.selection.empty&&!t.selection.empty&&function(t,e){let n=Math.min(t.$anchor.sharedDepth(t.head),e.$anchor.sharedDepth(e.head));return t.$anchor.start(n)!=e.$anchor.start(n)}(r.selection,t.selection);if(h){let n=Wn?this.trackWrites=this.domSelectionRange().focusNode:null;!i&&this.docView.update(t.doc,a,l,this)||(this.docView.updateOuterDeco([]),this.docView.destroy(),this.docView=Cr(t.doc,a,l,this.dom,this)),n&&!this.trackWrites&&(e=!0)}e||!(this.input.mouseDown&&this.domObserver.currentSelection.eq(this.domSelectionRange())&&function(t){let e=t.docView.domFromPos(t.state.selection.anchor,0),n=t.domSelectionRange();return Cn(e.node,e.offset,n.anchorNode,n.anchorOffset)}(this))?qr(this,e):(Ur(this,t.selection),this.domObserver.setCurSelection()),this.domObserver.start()}this.updatePluginViews(r),(null===(n=this.dragging)||void 0===n?void 0:n.node)&&!r.doc.eq(t.doc)&&this.updateDraggedNode(this.dragging,r),"reset"==c?this.dom.scrollTop=0:"to selection"==c?this.scrollToSelection():u&&function({refDOM:t,refTop:e,stack:n}){let r=t?t.getBoundingClientRect().top:0;rr(n,0==r?0:r-e)}(u)}scrollToSelection(){let t=this.domSelectionRange().focusNode;if(this.someProp("handleScrollToSelection",(t=>t(this))));else if(this.state.selection instanceof on){let e=this.docView.domAfterPos(this.state.selection.from);1==e.nodeType&&er(this,e.getBoundingClientRect(),t)}else er(this,this.coordsAtPos(this.state.selection.head,1),t)}destroyPluginViews(){let t;for(;t=this.pluginViews.pop();)t.destroy&&t.destroy()}updatePluginViews(t){if(t&&t.plugins==this.state.plugins&&this.directPlugins==this.prevDirectPlugins)for(let e=0;e0&&this.state.doc.nodeAt(t))==n.node&&(r=t)}this.dragging=new Ui(t.slice,t.move,r<0?void 0:on.create(this.state.doc,r))}someProp(t,e){let n,r=this._props&&this._props[t];if(null!=r&&(n=e?e(r):r))return n;for(let o=0;oe.ownerDocument.getSelection()),this._root=e;return t||document}updateRoot(){this._root=null}posAtCoords(t){return ar(this,t)}coordsAtPos(t,e=1){return dr(this,t,e)}domAtPos(t,e=0){return this.docView.domFromPos(t,e)}nodeDOM(t){let e=this.docView.descAt(t);return e?e.nodeDOM:null}posAtDOM(t,e,n=-1){let r=this.docView.posFromDOM(t,e,n);if(null==r)throw new RangeError("DOM position not inside the editor");return r}endOfTextblock(t,e){return br(this,e||this.state,t)}pasteHTML(t,e){return Hi(this,"",t,!1,e||new ClipboardEvent("paste"))}pasteText(t,e){return Hi(this,t,null,!0,e||new ClipboardEvent("paste"))}destroy(){this.docView&&(!function(t){t.domObserver.stop();for(let e in t.input.eventHandlers)t.dom.removeEventListener(e,t.input.eventHandlers[e]);clearTimeout(t.input.composingTimeout),clearTimeout(t.input.lastIOSEnterFallbackTimeout)}(this),this.destroyPluginViews(),this.mounted?(this.docView.update(this.state.doc,[],mo(this),this),this.dom.textContent=""):this.dom.parentNode&&this.dom.parentNode.removeChild(this.dom),this.docView.destroy(),this.docView=null)}get isDestroyed(){return null==this.docView}dispatchEvent(t){return function(t,e){Ai(t,e)||!Mi[e.type]||!t.editable&&e.type in Oi||Mi[e.type](t,e)}(this,t)}dispatch(t){let e=this._props.dispatchTransaction;e?e.call(this,t):this.updateState(this.state.apply(t))}domSelectionRange(){return Jn&&11===this.root.nodeType&&function(t){let e=t.activeElement;for(;e&&e.shadowRoot;)e=e.shadowRoot.activeElement;return e}(this.dom.ownerDocument)==this.dom?function(t){let e;function n(t){t.preventDefault(),t.stopImmediatePropagation(),e=t.getTargetRanges()[0]}t.dom.addEventListener("beforeinput",n,!0),document.execCommand("indent"),t.dom.removeEventListener("beforeinput",n,!0);let r=e.startContainer,i=e.startOffset,o=e.endContainer,s=e.endOffset,l=t.domAtPos(t.state.selection.anchor);return Cn(l.node,l.offset,o,s)&&([r,i,o,s]=[o,s,r,i]),{anchorNode:r,anchorOffset:i,focusNode:o,focusOffset:s}}(this):this.domSelection()}domSelection(){return this.root.getSelection()}}function To(t){let e=Object.create(null);return e.class="ProseMirror",e.contenteditable=String(t.editable),t.someProp("attributes",(n=>{if("function"==typeof n&&(n=n(t.state)),n)for(let t in n)"class"==t?e.class+=" "+n[t]:"style"==t?e.style=(e.style?e.style+";":"")+n[t]:e[t]||"contenteditable"==t||"nodeName"==t||(e[t]=String(n[t]))})),e.translate||(e.translate="no"),[eo.node(0,t.state.doc.content.size,e)]}function Ao(t){if(t.markCursor){let e=document.createElement("img");e.className="ProseMirror-separator",e.setAttribute("mark-placeholder","true"),e.setAttribute("alt",""),t.cursorWrapper={dom:e,deco:eo.widget(t.state.selection.head,e,{raw:!0,marks:t.markCursor})}}else t.cursorWrapper=null}function Eo(t){return!t.someProp("editable",(e=>!1===e(t.state)))}function $o(t){let e=Object.create(null);function n(t){for(let n in t)Object.prototype.hasOwnProperty.call(e,n)||(e[n]=t[n])}return t.someProp("nodeViews",n),t.someProp("markViews",n),e}function Po(t){if(t.spec.state||t.spec.filterTransaction||t.spec.appendTransaction)throw new RangeError("Plugins passed directly to the view must not have a state component")}for(var Io={8:"Backspace",9:"Tab",10:"Enter",12:"NumLock",13:"Enter",16:"Shift",17:"Control",18:"Alt",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",44:"PrintScreen",45:"Insert",46:"Delete",59:";",61:"=",91:"Meta",92:"Meta",106:"*",107:"+",108:",",109:"-",110:".",111:"/",144:"NumLock",145:"ScrollLock",160:"Shift",161:"Shift",162:"Control",163:"Control",164:"Alt",165:"Alt",173:"-",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},Ro={48:")",49:"!",50:"@",51:"#",52:"$",53:"%",54:"^",55:"&",56:"*",57:"(",59:":",61:"+",173:"_",186:":",187:"+",188:"<",189:"_",190:">",191:"?",192:"~",219:"{",220:"|",221:"}",222:'"'},zo="undefined"!=typeof navigator&&/Chrome\/(\d+)/.exec(navigator.userAgent),_o="undefined"!=typeof navigator&&/Mac/.test(navigator.platform),Bo="undefined"!=typeof navigator&&/MSIE \d|Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent),jo=_o||zo&&+zo[1]<57,Vo=0;Vo<10;Vo++)Io[48+Vo]=Io[96+Vo]=String(Vo);for(Vo=1;Vo<=24;Vo++)Io[Vo+111]="F"+Vo;for(Vo=65;Vo<=90;Vo++)Io[Vo]=String.fromCharCode(Vo+32),Ro[Vo]=String.fromCharCode(Vo);for(var Fo in Io)Ro.hasOwnProperty(Fo)||(Ro[Fo]=Io[Fo]);const Lo="undefined"!=typeof navigator&&/Mac|iP(hone|[oa]d)/.test(navigator.platform);function Wo(t){let e,n,r,i,o=t.split(/-(?!$)/),s=o[o.length-1];"Space"==s&&(s=" ");for(let l=0;l127)&&(r=Io[n.keyCode])&&r!=i){let i=e[qo(r,n)];if(i&&i(t.state,t.dispatch,t))return!0}}return!1}}const Ho=(t,e)=>!t.selection.empty&&(e&&e(t.tr.deleteSelection().scrollIntoView()),!0);function Yo(t,e,n=!1){for(let r=t;r;r="start"==e?r.firstChild:r.lastChild){if(r.isTextblock)return!0;if(n&&1!=r.childCount)return!1}return!1}function Uo(t){if(!t.parent.type.spec.isolating)for(let e=t.depth-1;e>=0;e--){if(t.index(e)>0)return t.doc.resolve(t.before(e+1));if(t.node(e).type.spec.isolating)break}return null}function Go(t){if(!t.parent.type.spec.isolating)for(let e=t.depth-1;e>=0;e--){let n=t.node(e);if(t.index(e)+1{let{$from:n,$to:r}=t.selection,i=n.blockRange(r),o=i&&$e(i);return null!=o&&(e&&e(t.tr.lift(i,o).scrollIntoView()),!0)};function Xo(t){for(let e=0;e{let{$head:n,$anchor:r}=t.selection;if(!n.parent.type.spec.code||!n.sameParent(r))return!1;let i=n.node(-1),o=n.indexAfter(-1),s=Xo(i.contentMatchAt(o));if(!s||!i.canReplaceWith(o,o,s))return!1;if(e){let r=n.after(),i=t.tr.replaceWith(r,r,s.createAndFill());i.setSelection(Xe.near(i.doc.resolve(r),1)),e(i.scrollIntoView())}return!0};const ts=(t,e)=>{let{$from:n,$to:r}=t.selection;if(t.selection instanceof on&&t.selection.node.isBlock)return!(!n.parentOffset||!Re(t.doc,n.pos)||(e&&e(t.tr.split(n.pos).scrollIntoView()),0));if(!n.parent.isBlock)return!1;if(e){let i=r.parentOffset==r.parent.content.size,o=t.tr;(t.selection instanceof nn||t.selection instanceof ln)&&o.deleteSelection();let s=0==n.depth?null:Xo(n.node(-1).contentMatchAt(n.indexAfter(-1))),l=es&&es(r.parent,i),a=l?[l]:i&&s?[{type:s}]:void 0,c=Re(o.doc,o.mapping.map(n.pos),1,a);if(a||c||!Re(o.doc,o.mapping.map(n.pos),1,s?[{type:s}]:void 0)||(s&&(a=[{type:s}]),c=!0),c&&(o.split(o.mapping.map(n.pos),1,a),!i&&!n.parentOffset&&n.parent.type!=s)){let t=o.mapping.map(n.before()),e=o.doc.resolve(t);s&&n.node(-1).canReplaceWith(e.index(),e.index()+1,s)&&o.setNodeMarkup(o.mapping.map(n.before()),s)}e(o.scrollIntoView())}return!0};var es;function ns(t,e,n){let r,i,o=e.nodeBefore,s=e.nodeAfter;if(o.type.spec.isolating||s.type.spec.isolating)return!1;if(function(t,e,n){let r=e.nodeBefore,i=e.nodeAfter,o=e.index();return!(!(r&&i&&r.type.compatibleContent(i.type))||(!r.content.size&&e.parent.canReplace(o-1,o)?(n&&n(t.tr.delete(e.pos-r.nodeSize,e.pos).scrollIntoView()),0):!e.parent.canReplace(o,o+1)||!i.isTextblock&&!ze(t.doc,e.pos)||(n&&n(t.tr.clearIncompatible(e.pos,r.type,r.contentMatchAt(r.childCount)).join(e.pos).scrollIntoView()),0)))}(t,e,n))return!0;let l=e.parent.canReplace(e.index(),e.index()+1);if(l&&(r=(i=o.contentMatchAt(o.childCount)).findWrapping(s.type))&&i.matchType(r[0]||s.type).validEnd){if(n){let i=e.pos+s.nodeSize,l=dt.empty;for(let t=r.length-1;t>=0;t--)l=dt.from(r[t].create(null,l));l=dt.from(o.copy(l));let a=t.tr.step(new Te(e.pos-1,i,e.pos,i,new vt(l,1,0),r.length,!0)),c=i+2*r.length;ze(a.doc,c)&&a.join(c),n(a.scrollIntoView())}return!0}let a=Xe.findFrom(e,1),c=a&&a.$from.blockRange(a.$to),h=c&&$e(c);if(null!=h&&h>=e.depth)return n&&n(t.tr.lift(c,h).scrollIntoView()),!0;if(l&&Yo(s,"start",!0)&&Yo(o,"end")){let r=o,i=[];for(;i.push(r),!r.isTextblock;)r=r.lastChild;let l=s,a=1;for(;!l.isTextblock;l=l.firstChild)a++;if(r.canReplace(r.childCount,r.childCount,l.content)){if(n){let r=dt.empty;for(let t=i.length-1;t>=0;t--)r=dt.from(i[t].copy(r));n(t.tr.step(new Te(e.pos-i.length,e.pos+s.nodeSize,e.pos+a,e.pos+s.nodeSize-a,new vt(r,i.length,0),0,!0)).scrollIntoView())}return!0}}return!1}function rs(t){return function(e,n){let r=e.selection,i=t<0?r.$from:r.$to,o=i.depth;for(;i.node(o).isInline;){if(!o)return!1;o--}return!!i.node(o).isTextblock&&(n&&n(e.tr.setSelection(nn.create(e.doc,t<0?i.start(o):i.end(o)))),!0)}}const is=rs(-1),ss=rs(1);function ls(t,e=null){return function(n,r){let{$from:i,$to:o}=n.selection,s=i.blockRange(o),l=s&&Pe(s,t,e);return!!l&&(r&&r(n.tr.wrap(s,l).scrollIntoView()),!0)}}function as(t,e=null){return function(n,r){let i=!1;for(let o=0;o{if(i)return!1;if(r.isTextblock&&!r.hasMarkup(t,e))if(r.type==t)i=!0;else{let e=n.doc.resolve(o),r=e.index();i=e.parent.canReplaceWith(r,r+1,t)}}))}if(!i)return!1;if(r){let i=n.tr;for(let r=0;r{if(s)return!1;s=t.inlineContent&&t.type.allowsMarkType(n)})),s)return!0}return!1}(n.doc,s,t))return!1;if(r)if(o)t.isInSet(n.storedMarks||o.marks())?r(n.tr.removeStoredMark(t)):r(n.tr.addStoredMark(t.create(e)));else{let i=!1,o=n.tr;for(let e=0;!i&&e{let r=function(t,e){let{$cursor:n}=t.selection;return!n||(e?!e.endOfTextblock("backward",t):n.parentOffset>0)?null:n}(t,n);if(!r)return!1;let i=Uo(r);if(!i){let n=r.blockRange(),i=n&&$e(n);return null!=i&&(e&&e(t.tr.lift(n,i).scrollIntoView()),!0)}let o=i.nodeBefore;if(!o.type.spec.isolating&&ns(t,i,e))return!0;if(0==r.parent.content.size&&(Yo(o,"end")||on.isSelectable(o))){let n=_e(t.doc,r.before(),r.after(),vt.empty);if(n&&n.slice.size{let{$head:r,empty:i}=t.selection,o=r;if(!i)return!1;if(r.parent.isTextblock){if(n?!n.endOfTextblock("backward",t):r.parentOffset>0)return!1;o=Uo(r)}let s=o&&o.nodeBefore;return!(!s||!on.isSelectable(s))&&(e&&e(t.tr.setSelection(on.create(t.doc,o.pos-s.nodeSize)).scrollIntoView()),!0)})),ds=hs(Ho,((t,e,n)=>{let r=function(t,e){let{$cursor:n}=t.selection;return!n||(e?!e.endOfTextblock("forward",t):n.parentOffset{let{$head:r,empty:i}=t.selection,o=r;if(!i)return!1;if(r.parent.isTextblock){if(n?!n.endOfTextblock("forward",t):r.parentOffset{let{$head:n,$anchor:r}=t.selection;return!(!n.parent.type.spec.code||!n.sameParent(r))&&(e&&e(t.tr.insertText("\n").scrollIntoView()),!0)}),((t,e)=>{let n=t.selection,{$from:r,$to:i}=n;if(n instanceof ln||r.parent.inlineContent||i.parent.inlineContent)return!1;let o=Xo(i.parent.contentMatchAt(i.indexAfter()));if(!o||!o.isTextblock)return!1;if(e){let n=(!r.parentOffset&&i.index(){let{$cursor:n}=t.selection;if(!n||n.parent.content.size)return!1;if(n.depth>1&&n.after()!=n.end(-1)){let r=n.before();if(Re(t.doc,r))return e&&e(t.tr.split(r).scrollIntoView()),!0}let r=n.blockRange(),i=r&&$e(r);return null!=i&&(e&&e(t.tr.lift(r,i).scrollIntoView()),!0)}),ts),"Mod-Enter":Qo,Backspace:us,"Mod-Backspace":us,"Shift-Backspace":us,Delete:ds,"Mod-Delete":ds,"Mod-a":(t,e)=>(e&&e(t.tr.setSelection(new ln(t.doc))),!0)},ps={"Ctrl-h":fs.Backspace,"Alt-Backspace":fs["Mod-Backspace"],"Ctrl-d":fs.Delete,"Ctrl-Alt-Backspace":fs["Mod-Delete"],"Alt-Delete":fs["Mod-Delete"],"Alt-d":fs["Mod-Delete"],"Ctrl-a":is,"Ctrl-e":ss};for(let os in fs)ps[os]=fs[os];const ms=("undefined"!=typeof navigator?/Mac|iP(hone|[oa]d)/.test(navigator.platform):!("undefined"==typeof os||!os.platform)&&"darwin"==os.platform())?ps:fs;class gs{constructor(t,e,n={}){var r;this.match=t,this.match=t,this.handler="string"==typeof e?(r=e,function(t,e,n,i){let o=r;if(e[1]){let t=e[0].lastIndexOf(e[1]);o+=e[0].slice(t+e[1].length);let r=(n+=t)-i;r>0&&(o=e[0].slice(t-r,t)+o,n=i)}return t.tr.insertText(o,n,i)}):e,this.undoable=!1!==n.undoable}}const ys=500;function vs({rules:t}){let e=new vn({state:{init:()=>null,apply(t,e){let n=t.getMeta(this);return n||(t.selectionSet||t.docChanged?null:e)}},props:{handleTextInput:(n,r,i,o)=>ws(n,r,i,o,t,e),handleDOMEvents:{compositionend:n=>{setTimeout((()=>{let{$cursor:r}=n.state.selection;r&&ws(n,r.pos,r.pos,"",t,e)}))}}},isInputRules:!0});return e}function ws(t,e,n,r,i,o){if(t.composing)return!1;let s=t.state,l=s.doc.resolve(e);if(l.parent.type.spec.code)return!1;let a=l.parent.textBetween(Math.max(0,l.parentOffset-ys),l.parentOffset,null,"")+r;for(let c=0;c{let n=t.plugins;for(let r=0;r=0;t--)n.step(r.steps[t].invert(r.docs[t]));if(i.text){let e=n.doc.resolve(i.from).marks();n.replaceWith(i.from,i.to,t.schema.text(i.text,e))}else n.delete(i.from,i.to);e(n)}return!0}}return!1};function xs(t,e,n=null,r){return new gs(t,((t,i,o,s)=>{let l=n instanceof Function?n(i):n,a=t.tr.delete(o,s),c=a.doc.resolve(o).blockRange(),h=c&&Pe(c,e,l);if(!h)return null;a.wrap(c,h);let u=a.doc.resolve(o-1).nodeBefore;return u&&u.type==e&&ze(a.doc,o-1)&&(!r||r(i,u))&&a.join(o-1),a}))}function Ss(t,e,n=null){return new gs(t,((t,r,i,o)=>{let s=t.doc.resolve(i),l=n instanceof Function?n(r):n;return s.node(-1).canReplaceWith(s.index(-1),s.indexAfter(-1),e)?t.tr.delete(i,o).setBlockType(i,i,e,l):null}))}new gs(/--$/,"—"),new gs(/\.\.\.$/,"…"),new gs(/(?:^|[\s\{\[\(\<'"\u2018\u201C])(")$/,"“"),new gs(/"$/,"”"),new gs(/(?:^|[\s\{\[\(\<'"\u2018\u201C])(')$/,"‘"),new gs(/'$/,"’");const ks=["ol",0],Ms=["ul",0],Os=["li",0],Cs={attrs:{order:{default:1}},parseDOM:[{tag:"ol",getAttrs:t=>({order:t.hasAttribute("start")?+t.getAttribute("start"):1})}],toDOM:t=>1==t.attrs.order?ks:["ol",{start:t.attrs.order},0]},Ds={parseDOM:[{tag:"ul"}],toDOM:()=>Ms},Ns={parseDOM:[{tag:"li"}],toDOM:()=>Os,defining:!0};function Ts(t,e){let n={};for(let r in t)n[r]=t[r];for(let r in e)n[r]=e[r];return n}function As(t,e,n){return t.append({ordered_list:Ts(Cs,{content:"list_item+",group:n}),bullet_list:Ts(Ds,{content:"list_item+",group:n}),list_item:Ts(Ns,{content:e})})}function Es(t,e=null){return function(n,r){let{$from:i,$to:o}=n.selection,s=i.blockRange(o),l=!1,a=s;if(!s)return!1;if(s.depth>=2&&i.node(s.depth-1).type.compatibleContent(t)&&0==s.startIndex){if(0==i.index(s.depth-1))return!1;let t=n.doc.resolve(s.start-2);a=new It(t,t,s.depth),s.endIndex=0;h--)o=dt.from(n[h].type.create(n[h].attrs,o));t.step(new Te(e.start-(r?2:0),e.end,e.start,e.end,new vt(o,0,0),n.length,!0));let s=0;for(let h=0;h=i.depth-3;t--)e=dt.from(i.node(t).copy(e));let s=i.indexAfter(-1){if(c>-1)return!1;t.isTextblock&&0==t.content.size&&(c=e+1)})),c>-1&&a.setSelection(Xe.near(a.doc.resolve(c))),r(a.scrollIntoView())}return!0}let a=o.pos==i.end()?l.contentMatchAt(0).defaultType:null,c=n.tr.delete(i.pos,o.pos),h=a?[e?{type:t,attrs:e}:null,{type:a}]:void 0;return!!Re(c.doc,i.pos,2,h)&&(r&&r(c.split(i.pos,2,h).scrollIntoView()),!0)}}function Ps(t){return function(e,n){let{$from:r,$to:i}=e.selection,o=r.blockRange(i,(e=>e.childCount>0&&e.firstChild.type==t));return!!o&&(!n||(r.node(o.depth-1).type==t?function(t,e,n,r){let i=t.tr,o=r.end,s=r.$to.end(r.depth);om;p--)f-=i.child(p).nodeSize,r.delete(f-1,f+1);let o=r.doc.resolve(n.start),s=o.nodeAfter;if(r.mapping.map(n.end)!=n.start+o.nodeAfter.nodeSize)return!1;let l=0==n.startIndex,a=n.endIndex==i.childCount,c=o.node(-1),h=o.index(-1);if(!c.canReplace(h+(l?0:1),h+1,s.content.append(a?dt.empty:dt.from(i))))return!1;let u=o.pos,d=u+s.nodeSize;return r.step(new Te(u-(l?1:0),d+(a?1:0),u+1,d-1,new vt((l?dt.empty:dt.from(i.copy(dt.empty))).append(a?dt.empty:dt.from(i.copy(dt.empty))),l?0:1,a?0:1),l?0:1)),e(r.scrollIntoView()),!0}(e,n,o)))}}function Is(t){return function(e,n){let{$from:r,$to:i}=e.selection,o=r.blockRange(i,(e=>e.childCount>0&&e.firstChild.type==t));if(!o)return!1;let s=o.startIndex;if(0==s)return!1;let l=o.parent,a=l.child(s-1);if(a.type!=t)return!1;if(n){let r=a.lastChild&&a.lastChild.type==l.type,i=dt.from(r?t.create():null),s=new vt(dt.from(t.create(null,dt.from(l.type.create(null,i)))),r?3:1,0),c=o.start,h=o.end;n(e.tr.step(new Te(c-(r?3:1),h,c,h,s,1,!0)).scrollIntoView())}return!0}}var Rs=200,zs=function(){};zs.prototype.append=function(t){return t.length?(t=zs.from(t),!this.length&&t||t.length=e?zs.empty:this.sliceInner(Math.max(0,t),Math.min(this.length,e))},zs.prototype.get=function(t){if(!(t<0||t>=this.length))return this.getInner(t)},zs.prototype.forEach=function(t,e,n){void 0===e&&(e=0),void 0===n&&(n=this.length),e<=n?this.forEachInner(t,e,n,0):this.forEachInvertedInner(t,e,n,0)},zs.prototype.map=function(t,e,n){void 0===e&&(e=0),void 0===n&&(n=this.length);var r=[];return this.forEach((function(e,n){return r.push(t(e,n))}),e,n),r},zs.from=function(t){return t instanceof zs?t:t&&t.length?new _s(t):zs.empty};var _s=function(t){function e(e){t.call(this),this.values=e}t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e;var n={length:{configurable:!0},depth:{configurable:!0}};return e.prototype.flatten=function(){return this.values},e.prototype.sliceInner=function(t,n){return 0==t&&n==this.length?this:new e(this.values.slice(t,n))},e.prototype.getInner=function(t){return this.values[t]},e.prototype.forEachInner=function(t,e,n,r){for(var i=e;i=n;i--)if(!1===t(this.values[i],r+i))return!1},e.prototype.leafAppend=function(t){if(this.length+t.length<=Rs)return new e(this.values.concat(t.flatten()))},e.prototype.leafPrepend=function(t){if(this.length+t.length<=Rs)return new e(t.flatten().concat(this.values))},n.length.get=function(){return this.values.length},n.depth.get=function(){return 0},Object.defineProperties(e.prototype,n),e}(zs);zs.empty=new _s([]);var Bs=function(t){function e(e,n){t.call(this),this.left=e,this.right=n,this.length=e.length+n.length,this.depth=Math.max(e.depth,n.depth)+1}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e.prototype.flatten=function(){return this.left.flatten().concat(this.right.flatten())},e.prototype.getInner=function(t){return ti&&!1===this.right.forEachInner(t,Math.max(e-i,0),Math.min(this.length,n)-i,r+i))&&void 0)},e.prototype.forEachInvertedInner=function(t,e,n,r){var i=this.left.length;return!(e>i&&!1===this.right.forEachInvertedInner(t,e-i,Math.max(n,i)-i,r+i))&&(!(n=n?this.right.slice(t-n,e-n):this.left.slice(t,n).append(this.right.slice(0,e-n))},e.prototype.leafAppend=function(t){var n=this.right.leafAppend(t);if(n)return new e(this.left,n)},e.prototype.leafPrepend=function(t){var n=this.left.leafPrepend(t);if(n)return new e(n,this.right)},e.prototype.appendInner=function(t){return this.left.depth>=Math.max(this.right.depth,t.depth)+1?new e(this.left,new e(this.right,t)):new e(this,t)},e}(zs),js=zs;class Vs{constructor(t,e){this.items=t,this.eventCount=e}popEvent(t,e){if(0==this.eventCount)return null;let n,r,i=this.items.length;for(;;i--){if(this.items.get(i-1).selection){--i;break}}e&&(n=this.remapping(i,this.items.length),r=n.maps.length);let o,s,l=t.tr,a=[],c=[];return this.items.forEach(((t,e)=>{if(!t.step)return n||(n=this.remapping(i,e+1),r=n.maps.length),r--,void c.push(t);if(n){c.push(new Fs(t.map));let e,i=t.step.map(n.slice(r));i&&l.maybeStep(i).doc&&(e=l.mapping.maps[l.mapping.maps.length-1],a.push(new Fs(e,void 0,void 0,a.length+c.length))),r--,e&&n.appendMap(e,r)}else l.maybeStep(t.step);return t.selection?(o=n?t.selection.map(n.slice(r)):t.selection,s=new Vs(this.items.slice(0,i).append(c.reverse().concat(a)),this.eventCount-1),!1):void 0}),this.items.length,0),{remaining:s,transform:l,selection:o}}addTransform(t,e,n,r){let i=[],o=this.eventCount,s=this.items,l=!r&&s.length?s.get(s.length-1):null;for(let c=0;cWs&&(s=function(t,e){let n;return t.forEach(((t,r)=>{if(t.selection&&0==e--)return n=r,!1})),t.slice(n)}(s,a),o-=a),new Vs(s.append(i),o)}remapping(t,e){let n=new we;return this.items.forEach(((e,r)=>{let i=null!=e.mirrorOffset&&r-e.mirrorOffset>=t?n.maps.length-e.mirrorOffset:void 0;n.appendMap(e.map,i)}),t,e),n}addMaps(t){return 0==this.eventCount?this:new Vs(this.items.append(t.map((t=>new Fs(t)))),this.eventCount)}rebased(t,e){if(!this.eventCount)return this;let n=[],r=Math.max(0,this.items.length-e),i=t.mapping,o=t.steps.length,s=this.eventCount;this.items.forEach((t=>{t.selection&&s--}),r);let l=e;this.items.forEach((e=>{let r=i.getMirror(--l);if(null==r)return;o=Math.min(o,r);let a=i.maps[r];if(e.step){let o=t.steps[r].invert(t.docs[r]),c=e.selection&&e.selection.map(i.slice(l+1,r));c&&s++,n.push(new Fs(a,o,c))}else n.push(new Fs(a))}),r);let a=[];for(let u=e;u500&&(h=h.compress(this.items.length-n.length)),h}emptyItemCount(){let t=0;return this.items.forEach((e=>{e.step||t++})),t}compress(t=this.items.length){let e=this.remapping(0,t),n=e.maps.length,r=[],i=0;return this.items.forEach(((o,s)=>{if(s>=t)r.push(o),o.selection&&i++;else if(o.step){let t=o.step.map(e.slice(n)),s=t&&t.getMap();if(n--,s&&e.appendMap(s,n),t){let l=o.selection&&o.selection.map(e.slice(n));l&&i++;let a,c=new Fs(s.invert(),t,l),h=r.length-1;(a=r.length&&r[h].merge(c))?r[h]=a:r.push(c)}}else o.map&&n--}),this.items.length,0),new Vs(js.from(r.reverse()),i)}}Vs.empty=new Vs(js.empty,0);class Fs{constructor(t,e,n,r){this.map=t,this.step=e,this.selection=n,this.mirrorOffset=r}merge(t){if(this.step&&t.step&&!t.selection){let e=t.step.merge(this.step);if(e)return new Fs(e.getMap().invert(),e,this.selection)}}}class Ls{constructor(t,e,n,r,i){this.done=t,this.undone=e,this.prevRanges=n,this.prevTime=r,this.prevComposition=i}}const Ws=20;function qs(t){let e=[];return t.forEach(((t,n,r,i)=>e.push(r,i))),e}function Js(t,e){if(!t)return null;let n=[];for(let r=0;rnew Ls(Vs.empty,Vs.empty,null,0,-1),apply:(e,n,r)=>function(t,e,n,r){let i,o=n.getMeta(Gs);if(o)return o.historyState;n.getMeta(Zs)&&(t=new Ls(t.done,t.undone,null,0,-1));let s=n.getMeta("appendedTransaction");if(0==n.steps.length)return t;if(s&&s.getMeta(Gs))return s.getMeta(Gs).redo?new Ls(t.done.addTransform(n,void 0,r,Us(e)),t.undone,qs(n.mapping.maps[n.steps.length-1]),t.prevTime,t.prevComposition):new Ls(t.done,t.undone.addTransform(n,void 0,r,Us(e)),null,t.prevTime,t.prevComposition);if(!1===n.getMeta("addToHistory")||s&&!1===s.getMeta("addToHistory"))return(i=n.getMeta("rebased"))?new Ls(t.done.rebased(n,i),t.undone.rebased(n,i),Js(t.prevRanges,n.mapping),t.prevTime,t.prevComposition):new Ls(t.done.addMaps(n.mapping.maps),t.undone.addMaps(n.mapping.maps),Js(t.prevRanges,n.mapping),t.prevTime,t.prevComposition);{let i=n.getMeta("composition"),o=0==t.prevTime||!s&&t.prevComposition!=i&&(t.prevTime<(n.time||0)-r.newGroupDelay||!function(t,e){if(!e)return!1;if(!t.docChanged)return!0;let n=!1;return t.mapping.maps[0].forEach(((t,r)=>{for(let i=0;i=e[i]&&(n=!0)})),n}(n,t.prevRanges)),l=s?Js(t.prevRanges,n.mapping):qs(n.mapping.maps[n.steps.length-1]);return new Ls(t.done.addTransform(n,o?e.selection.getBookmark():void 0,r,Us(e)),Vs.empty,l,n.time,null==i?t.prevComposition:i)}}(n,r,e,t)},config:t,props:{handleDOMEvents:{beforeinput(t,e){let n=e.inputType,r="historyUndo"==n?Qs:"historyRedo"==n?tl:null;return!!r&&(e.preventDefault(),r(t.state,t.dispatch))}}}})}const Qs=(t,e)=>{let n=Gs.getState(t);return!(!n||0==n.done.eventCount)&&(e&&Ks(n,t,e,!1),!0)},tl=(t,e)=>{let n=Gs.getState(t);return!(!n||0==n.undone.eventCount)&&(e&&Ks(n,t,e,!0),!0)};function el(t){let e=Gs.getState(t);return e?e.done.eventCount:0}function nl(t){let e=Gs.getState(t);return e?e.undone.eventCount:0} +/*! + * portal-vue © Thorsten Lünborg, 2019 + * + * Version: 2.1.7 + * + * LICENCE: MIT + * + * https://github.com/linusborg/portal-vue + * + */function rl(t){return(rl="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 il(t){return function(t){if(Array.isArray(t)){for(var e=0,n=new Array(t.length);e1&&void 0!==arguments[1]&&arguments[1],n=t.to,r=t.from;if(n&&(r||!1!==e)&&this.transports[n])if(e)this.transports[n]=[];else{var i=this.$_getTransportIndex(t);if(i>=0){var o=this.transports[n].slice(0);o.splice(i,1),this.transports[n]=o}}},registerTarget:function(t,e,n){ol&&(this.trackInstances&&!n&&this.targets[t]&&console.warn("[portal-vue]: Target ".concat(t," already exists")),this.$set(this.targets,t,Object.freeze([e])))},unregisterTarget:function(t){this.$delete(this.targets,t)},registerSource:function(t,e,n){ol&&(this.trackInstances&&!n&&this.sources[t]&&console.warn("[portal-vue]: source ".concat(t," already exists")),this.$set(this.sources,t,Object.freeze([e])))},unregisterSource:function(t){this.$delete(this.sources,t)},hasTarget:function(t){return!(!this.targets[t]||!this.targets[t][0])},hasSource:function(t){return!(!this.sources[t]||!this.sources[t][0])},hasContentFor:function(t){return!!this.transports[t]&&!!this.transports[t].length},$_getTransportIndex:function(t){var e=t.to,n=t.from;for(var r in this.transports[e])if(this.transports[e][r].from===n)return+r;return-1}}}),ul=new hl(ll),dl=1,fl=Vue.extend({name:"portal",props:{disabled:{type:Boolean},name:{type:String,default:function(){return String(dl++)}},order:{type:Number,default:0},slim:{type:Boolean},slotProps:{type:Object,default:function(){return{}}},tag:{type:String,default:"DIV"},to:{type:String,default:function(){return String(Math.round(1e7*Math.random()))}}},created:function(){var t=this;this.$nextTick((function(){ul.registerSource(t.name,t)}))},mounted:function(){this.disabled||this.sendUpdate()},updated:function(){this.disabled?this.clear():this.sendUpdate()},beforeDestroy:function(){ul.unregisterSource(this.name),this.clear()},watch:{to:function(t,e){e&&e!==t&&this.clear(e),this.sendUpdate()}},methods:{clear:function(t){var e={from:this.name,to:t||this.to};ul.close(e)},normalizeSlots:function(){return this.$scopedSlots.default?[this.$scopedSlots.default]:this.$slots.default},normalizeOwnChildren:function(t){return"function"==typeof t?t(this.slotProps):t},sendUpdate:function(){var t=this.normalizeSlots();if(t){var e={from:this.name,to:this.to,passengers:il(t),order:this.order};ul.open(e)}else this.clear()}},render:function(t){var e=this.$slots.default||this.$scopedSlots.default||[],n=this.tag;return e&&this.disabled?e.length<=1&&this.slim?this.normalizeOwnChildren(e)[0]:t(n,[this.normalizeOwnChildren(e)]):this.slim?t():t(n,{class:{"v-portal":!0},style:{display:"none"},key:"v-portal-placeholder"})}}),pl=Vue.extend({name:"portalTarget",props:{multiple:{type:Boolean,default:!1},name:{type:String,required:!0},slim:{type:Boolean,default:!1},slotProps:{type:Object,default:function(){return{}}},tag:{type:String,default:"div"},transition:{type:[String,Object,Function]}},data:function(){return{transports:ul.transports,firstRender:!0}},created:function(){var t=this;this.$nextTick((function(){ul.registerTarget(t.name,t)}))},watch:{ownTransports:function(){this.$emit("change",this.children().length>0)},name:function(t,e){ul.unregisterTarget(e),ul.registerTarget(t,this)}},mounted:function(){var t=this;this.transition&&this.$nextTick((function(){t.firstRender=!1}))},beforeDestroy:function(){ul.unregisterTarget(this.name)},computed:{ownTransports:function(){var t=this.transports[this.name]||[];return this.multiple?t:0===t.length?[]:[t[t.length-1]]},passengers:function(){return function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return t.reduce((function(t,n){var r=n.passengers[0],i="function"==typeof r?r(e):n.passengers;return t.concat(i)}),[])}(this.ownTransports,this.slotProps)}},methods:{children:function(){return 0!==this.passengers.length?this.passengers:this.$scopedSlots.default?this.$scopedSlots.default(this.slotProps):this.$slots.default||[]},noWrapper:function(){var t=this.slim&&!this.transition;return t&&this.children().length>1&&console.warn("[portal-vue]: PortalTarget with `slim` option received more than one child element."),t}},render:function(t){var e=this.noWrapper(),n=this.children(),r=this.transition||this.tag;return e?n[0]:this.slim&&!r?t():t(r,{props:{tag:this.transition&&this.tag?this.tag:void 0},class:{"vue-portal-target":!0}},n)}}),ml=0,gl=["disabled","name","order","slim","slotProps","tag","to"],yl=["multiple","transition"],vl=Vue.extend({name:"MountingPortal",inheritAttrs:!1,props:{append:{type:[Boolean,String]},bail:{type:Boolean},mountTo:{type:String,required:!0},disabled:{type:Boolean},name:{type:String,default:function(){return"mounted_"+String(ml++)}},order:{type:Number,default:0},slim:{type:Boolean},slotProps:{type:Object,default:function(){return{}}},tag:{type:String,default:"DIV"},to:{type:String,default:function(){return String(Math.round(1e7*Math.random()))}},multiple:{type:Boolean,default:!1},targetSlim:{type:Boolean},targetSlotProps:{type:Object,default:function(){return{}}},targetTag:{type:String,default:"div"},transition:{type:[String,Object,Function]}},created:function(){if("undefined"!=typeof document){var t=document.querySelector(this.mountTo);if(t){var e=this.$props;if(ul.targets[e.name])e.bail?console.warn("[portal-vue]: Target ".concat(e.name," is already mounted.\n Aborting because 'bail: true' is set")):this.portalTarget=ul.targets[e.name];else{var n=e.append;if(n){var r="string"==typeof n?n:"DIV",i=document.createElement(r);t.appendChild(i),t=i}var o=sl(this.$props,yl);o.slim=this.targetSlim,o.tag=this.targetTag,o.slotProps=this.targetSlotProps,o.name=this.to,this.portalTarget=new pl({el:t,parent:this.$parent||this,propsData:o})}}else console.error("[portal-vue]: Mount Point '".concat(this.mountTo,"' not found in document"))}},beforeDestroy:function(){var t=this.portalTarget;if(this.append){var e=t.$el;e.parentNode.removeChild(e)}t.$destroy()},render:function(t){if(!this.portalTarget)return console.warn("[portal-vue] Target wasn't mounted"),t();if(!this.$scopedSlots.manual){var e=sl(this.$props,gl);return t(fl,{props:e,attrs:this.$attrs,on:this.$listeners,scopedSlots:this.$scopedSlots},this.$slots.default)}var n=this.$scopedSlots.manual({to:this.to});return Array.isArray(n)&&(n=n[0]),n||t()}});var wl={install:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};t.component(e.portalName||"Portal",fl),t.component(e.portalTargetName||"PortalTarget",pl),t.component(e.MountingPortalName||"MountingPortal",vl)}},bl=new Map;function xl(t){var e=bl.get(t);e&&e.destroy()}function Sl(t){var e=bl.get(t);e&&e.update()}var kl=null;"undefined"==typeof window?((kl=function(t){return t}).destroy=function(t){return t},kl.update=function(t){return t}):((kl=function(t,e){return t&&Array.prototype.forEach.call(t.length?t:[t],(function(t){return function(t){if(t&&t.nodeName&&"TEXTAREA"===t.nodeName&&!bl.has(t)){var e,n=null,r=window.getComputedStyle(t),i=(e=t.value,function(){s({testForHeightReduction:""===e||!t.value.startsWith(e),restoreTextAlign:null}),e=t.value}),o=function(e){t.removeEventListener("autosize:destroy",o),t.removeEventListener("autosize:update",l),t.removeEventListener("input",i),window.removeEventListener("resize",l),Object.keys(e).forEach((function(n){return t.style[n]=e[n]})),bl.delete(t)}.bind(t,{height:t.style.height,resize:t.style.resize,textAlign:t.style.textAlign,overflowY:t.style.overflowY,overflowX:t.style.overflowX,wordWrap:t.style.wordWrap});t.addEventListener("autosize:destroy",o),t.addEventListener("autosize:update",l),t.addEventListener("input",i),window.addEventListener("resize",l),t.style.overflowX="hidden",t.style.wordWrap="break-word",bl.set(t,{destroy:o,update:l}),l()}function s(e){var i,o,l=e.restoreTextAlign,a=void 0===l?null:l,c=e.testForHeightReduction,h=void 0===c||c,u=r.overflowY;if(0!==t.scrollHeight&&("vertical"===r.resize?t.style.resize="none":"both"===r.resize&&(t.style.resize="horizontal"),h&&(i=function(t){for(var e=[];t&&t.parentNode&&t.parentNode instanceof Element;)t.parentNode.scrollTop&&e.push([t.parentNode,t.parentNode.scrollTop]),t=t.parentNode;return function(){return e.forEach((function(t){var e=t[0],n=t[1];e.style.scrollBehavior="auto",e.scrollTop=n,e.style.scrollBehavior=null}))}}(t),t.style.height=""),o="content-box"===r.boxSizing?t.scrollHeight-(parseFloat(r.paddingTop)+parseFloat(r.paddingBottom)):t.scrollHeight+parseFloat(r.borderTopWidth)+parseFloat(r.borderBottomWidth),"none"!==r.maxHeight&&o>parseFloat(r.maxHeight)?("hidden"===r.overflowY&&(t.style.overflow="scroll"),o=parseFloat(r.maxHeight)):"hidden"!==r.overflowY&&(t.style.overflow="hidden"),t.style.height=o+"px",a&&(t.style.textAlign=a),i&&i(),n!==o&&(t.dispatchEvent(new Event("autosize:resized",{bubbles:!0})),n=o),u!==r.overflow&&!a)){var d=r.textAlign;"hidden"===r.overflow&&(t.style.textAlign="start"===d?"end":"start"),s({restoreTextAlign:d,testForHeightReduction:!0})}}function l(){s({testForHeightReduction:!0,restoreTextAlign:null})}}(t)})),t}).destroy=function(t){return t&&Array.prototype.forEach.call(t.length?t:[t],xl),t},kl.update=function(t){return t&&Array.prototype.forEach.call(t.length?t:[t],Sl),t});var Ml=kl,Ol={exports:{}};Ol.exports=function(){var t=1e3,e=6e4,n=36e5,r="millisecond",i="second",o="minute",s="hour",l="day",a="week",c="month",h="quarter",u="year",d="date",f="Invalid Date",p=/^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/,m=/\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,g={name:"en",weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),ordinal:function(t){var e=["th","st","nd","rd"],n=t%100;return"["+t+(e[(n-20)%10]||e[n]||e[0])+"]"}},y=function(t,e,n){var r=String(t);return!r||r.length>=e?t:""+Array(e+1-r.length).join(n)+t},v={s:y,z:function(t){var e=-t.utcOffset(),n=Math.abs(e),r=Math.floor(n/60),i=n%60;return(e<=0?"+":"-")+y(r,2,"0")+":"+y(i,2,"0")},m:function t(e,n){if(e.date()1)return t(s[0])}else{var l=e.name;b[l]=e,i=l}return!r&&i&&(w=i),i||!r&&w},M=function(t,e){if(S(t))return t.clone();var n="object"==typeof e?e:{};return n.date=t,n.args=arguments,new C(n)},O=v;O.l=k,O.i=S,O.w=function(t,e){return M(t,{locale:e.$L,utc:e.$u,x:e.$x,$offset:e.$offset})};var C=function(){function g(t){this.$L=k(t.locale,null,!0),this.parse(t),this.$x=this.$x||t.x||{},this[x]=!0}var y=g.prototype;return y.parse=function(t){this.$d=function(t){var e=t.date,n=t.utc;if(null===e)return new Date(NaN);if(O.u(e))return new Date;if(e instanceof Date)return new Date(e);if("string"==typeof e&&!/Z$/i.test(e)){var r=e.match(p);if(r){var i=r[2]-1||0,o=(r[7]||"0").substring(0,3);return n?new Date(Date.UTC(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,o)):new Date(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,o)}}return new Date(e)}(t),this.init()},y.init=function(){var t=this.$d;this.$y=t.getFullYear(),this.$M=t.getMonth(),this.$D=t.getDate(),this.$W=t.getDay(),this.$H=t.getHours(),this.$m=t.getMinutes(),this.$s=t.getSeconds(),this.$ms=t.getMilliseconds()},y.$utils=function(){return O},y.isValid=function(){return!(this.$d.toString()===f)},y.isSame=function(t,e){var n=M(t);return this.startOf(e)<=n&&n<=this.endOf(e)},y.isAfter=function(t,e){return M(t)68?1900:2e3)},l=function(t){return function(e){this[t]=+e}},a=[/[+-]\d\d:?(\d\d)?|Z/,function(t){(this.zone||(this.zone={})).offset=function(t){if(!t)return 0;if("Z"===t)return 0;var e=t.match(/([+-]|\d\d)/g),n=60*e[1]+(+e[2]||0);return 0===n?0:"+"===e[0]?-n:n}(t)}],c=function(t){var e=o[t];return e&&(e.indexOf?e:e.s.concat(e.f))},h=function(t,e){var n,r=o.meridiem;if(r){for(var i=1;i<=24;i+=1)if(t.indexOf(r(i,0,e))>-1){n=i>12;break}}else n=t===(e?"pm":"PM");return n},u={A:[i,function(t){this.afternoon=h(t,!1)}],a:[i,function(t){this.afternoon=h(t,!0)}],S:[/\d/,function(t){this.milliseconds=100*+t}],SS:[n,function(t){this.milliseconds=10*+t}],SSS:[/\d{3}/,function(t){this.milliseconds=+t}],s:[r,l("seconds")],ss:[r,l("seconds")],m:[r,l("minutes")],mm:[r,l("minutes")],H:[r,l("hours")],h:[r,l("hours")],HH:[r,l("hours")],hh:[r,l("hours")],D:[r,l("day")],DD:[n,l("day")],Do:[i,function(t){var e=o.ordinal,n=t.match(/\d+/);if(this.day=n[0],e)for(var r=1;r<=31;r+=1)e(r).replace(/\[|\]/g,"")===t&&(this.day=r)}],M:[r,l("month")],MM:[n,l("month")],MMM:[i,function(t){var e=c("months"),n=(c("monthsShort")||e.map((function(t){return t.slice(0,3)}))).indexOf(t)+1;if(n<1)throw new Error;this.month=n%12||n}],MMMM:[i,function(t){var e=c("months").indexOf(t)+1;if(e<1)throw new Error;this.month=e%12||e}],Y:[/[+-]?\d+/,l("year")],YY:[n,function(t){this.year=s(t)}],YYYY:[/\d{4}/,l("year")],Z:a,ZZ:a};function d(n){var r,i;r=n,i=o&&o.formats;for(var s=(n=r.replace(/(\[[^\]]+])|(LTS?|l{1,4}|L{1,4})/g,(function(e,n,r){var o=r&&r.toUpperCase();return n||i[r]||t[r]||i[o].replace(/(\[[^\]]+])|(MMMM|MM|DD|dddd)/g,(function(t,e,n){return e||n.slice(1)}))}))).match(e),l=s.length,a=0;a-1)return new Date(("X"===e?1e3:1)*t);var r=d(e)(t),i=r.year,o=r.month,s=r.day,l=r.hours,a=r.minutes,c=r.seconds,h=r.milliseconds,u=r.zone,f=new Date,p=s||(i||o?1:f.getDate()),m=i||f.getFullYear(),g=0;i&&!o||(g=o>0?o-1:f.getMonth());var y=l||0,v=a||0,w=c||0,b=h||0;return u?new Date(Date.UTC(m,g,p,y,v,w,b+60*u.offset*1e3)):n?new Date(Date.UTC(m,g,p,y,v,w,b)):new Date(m,g,p,y,v,w,b)}catch(x){return new Date("")}}(e,l,r),this.init(),u&&!0!==u&&(this.$L=this.locale(u).$L),h&&e!=this.format(l)&&(this.$d=new Date("")),o={}}else if(l instanceof Array)for(var f=l.length,p=1;p<=f;p+=1){s[1]=l[p-1];var m=n.apply(this,s);if(m.isValid()){this.$d=m.$d,this.$L=m.$L,this.init();break}p===f&&(this.$d=new Date(""))}else i.call(this,t)}}}();const Nl=e(Dl.exports);function Tl(t){return{all:t=t||new Map,on:function(e,n){var r=t.get(e);r?r.push(n):t.set(e,[n])},off:function(e,n){var r=t.get(e);r&&(n?r.splice(r.indexOf(n)>>>0,1):t.set(e,[]))},emit:function(e,n){var r=t.get(e);r&&r.slice().map((function(t){t(n)})),(r=t.get("*"))&&r.slice().map((function(t){t(e,n)}))}}} +/*! + * vuex v3.6.2 + * (c) 2021 Evan You + * @license MIT + */var Al=("undefined"!=typeof window?window:"undefined"!=typeof global?global:{}).__VUE_DEVTOOLS_GLOBAL_HOOK__;function El(t,e){if(void 0===e&&(e=[]),null===t||"object"!=typeof t)return t;var n,r=(n=function(e){return e.original===t},e.filter(n)[0]);if(r)return r.copy;var i=Array.isArray(t)?[]:{};return e.push({original:t,copy:i}),Object.keys(t).forEach((function(n){i[n]=El(t[n],e)})),i}function $l(t,e){Object.keys(t).forEach((function(n){return e(t[n],n)}))}function Pl(t){return null!==t&&"object"==typeof t}var Il=function(t,e){this.runtime=e,this._children=Object.create(null),this._rawModule=t;var n=t.state;this.state=("function"==typeof n?n():n)||{}},Rl={namespaced:{configurable:!0}};Rl.namespaced.get=function(){return!!this._rawModule.namespaced},Il.prototype.addChild=function(t,e){this._children[t]=e},Il.prototype.removeChild=function(t){delete this._children[t]},Il.prototype.getChild=function(t){return this._children[t]},Il.prototype.hasChild=function(t){return t in this._children},Il.prototype.update=function(t){this._rawModule.namespaced=t.namespaced,t.actions&&(this._rawModule.actions=t.actions),t.mutations&&(this._rawModule.mutations=t.mutations),t.getters&&(this._rawModule.getters=t.getters)},Il.prototype.forEachChild=function(t){$l(this._children,t)},Il.prototype.forEachGetter=function(t){this._rawModule.getters&&$l(this._rawModule.getters,t)},Il.prototype.forEachAction=function(t){this._rawModule.actions&&$l(this._rawModule.actions,t)},Il.prototype.forEachMutation=function(t){this._rawModule.mutations&&$l(this._rawModule.mutations,t)},Object.defineProperties(Il.prototype,Rl);var zl,_l=function(t){this.register([],t,!1)};function Bl(t,e,n){if(e.update(n),n.modules)for(var r in n.modules){if(!e.getChild(r))return;Bl(t.concat(r),e.getChild(r),n.modules[r])}}_l.prototype.get=function(t){return t.reduce((function(t,e){return t.getChild(e)}),this.root)},_l.prototype.getNamespace=function(t){var e=this.root;return t.reduce((function(t,n){return t+((e=e.getChild(n)).namespaced?n+"/":"")}),"")},_l.prototype.update=function(t){Bl([],this.root,t)},_l.prototype.register=function(t,e,n){var r=this;void 0===n&&(n=!0);var i=new Il(e,n);0===t.length?this.root=i:this.get(t.slice(0,-1)).addChild(t[t.length-1],i);e.modules&&$l(e.modules,(function(e,i){r.register(t.concat(i),e,n)}))},_l.prototype.unregister=function(t){var e=this.get(t.slice(0,-1)),n=t[t.length-1],r=e.getChild(n);r&&r.runtime&&e.removeChild(n)},_l.prototype.isRegistered=function(t){var e=this.get(t.slice(0,-1)),n=t[t.length-1];return!!e&&e.hasChild(n)};var jl=function(t){var e=this;void 0===t&&(t={}),!zl&&"undefined"!=typeof window&&window.Vue&&Hl(window.Vue);var n=t.plugins;void 0===n&&(n=[]);var r=t.strict;void 0===r&&(r=!1),this._committing=!1,this._actions=Object.create(null),this._actionSubscribers=[],this._mutations=Object.create(null),this._wrappedGetters=Object.create(null),this._modules=new _l(t),this._modulesNamespaceMap=Object.create(null),this._subscribers=[],this._watcherVM=new zl,this._makeLocalGettersCache=Object.create(null);var i=this,o=this.dispatch,s=this.commit;this.dispatch=function(t,e){return o.call(i,t,e)},this.commit=function(t,e,n){return s.call(i,t,e,n)},this.strict=r;var l=this._modules.root.state;ql(this,l,[],this._modules.root),Wl(this,l),n.forEach((function(t){return t(e)})),(void 0!==t.devtools?t.devtools:zl.config.devtools)&&function(t){Al&&(t._devtoolHook=Al,Al.emit("vuex:init",t),Al.on("vuex:travel-to-state",(function(e){t.replaceState(e)})),t.subscribe((function(t,e){Al.emit("vuex:mutation",t,e)}),{prepend:!0}),t.subscribeAction((function(t,e){Al.emit("vuex:action",t,e)}),{prepend:!0}))}(this)},Vl={state:{configurable:!0}};function Fl(t,e,n){return e.indexOf(t)<0&&(n&&n.prepend?e.unshift(t):e.push(t)),function(){var n=e.indexOf(t);n>-1&&e.splice(n,1)}}function Ll(t,e){t._actions=Object.create(null),t._mutations=Object.create(null),t._wrappedGetters=Object.create(null),t._modulesNamespaceMap=Object.create(null);var n=t.state;ql(t,n,[],t._modules.root,!0),Wl(t,n,e)}function Wl(t,e,n){var r=t._vm;t.getters={},t._makeLocalGettersCache=Object.create(null);var i=t._wrappedGetters,o={};$l(i,(function(e,n){o[n]=function(t,e){return function(){return t(e)}}(e,t),Object.defineProperty(t.getters,n,{get:function(){return t._vm[n]},enumerable:!0})}));var s=zl.config.silent;zl.config.silent=!0,t._vm=new zl({data:{$$state:e},computed:o}),zl.config.silent=s,t.strict&&function(t){t._vm.$watch((function(){return this._data.$$state}),(function(){}),{deep:!0,sync:!0})}(t),r&&(n&&t._withCommit((function(){r._data.$$state=null})),zl.nextTick((function(){return r.$destroy()})))}function ql(t,e,n,r,i){var o=!n.length,s=t._modules.getNamespace(n);if(r.namespaced&&(t._modulesNamespaceMap[s],t._modulesNamespaceMap[s]=r),!o&&!i){var l=Jl(e,n.slice(0,-1)),a=n[n.length-1];t._withCommit((function(){zl.set(l,a,r.state)}))}var c=r.context=function(t,e,n){var r=""===e,i={dispatch:r?t.dispatch:function(n,r,i){var o=Kl(n,r,i),s=o.payload,l=o.options,a=o.type;return l&&l.root||(a=e+a),t.dispatch(a,s)},commit:r?t.commit:function(n,r,i){var o=Kl(n,r,i),s=o.payload,l=o.options,a=o.type;l&&l.root||(a=e+a),t.commit(a,s,l)}};return Object.defineProperties(i,{getters:{get:r?function(){return t.getters}:function(){return function(t,e){if(!t._makeLocalGettersCache[e]){var n={},r=e.length;Object.keys(t.getters).forEach((function(i){if(i.slice(0,r)===e){var o=i.slice(r);Object.defineProperty(n,o,{get:function(){return t.getters[i]},enumerable:!0})}})),t._makeLocalGettersCache[e]=n}return t._makeLocalGettersCache[e]}(t,e)}},state:{get:function(){return Jl(t.state,n)}}}),i}(t,s,n);r.forEachMutation((function(e,n){!function(t,e,n,r){var i=t._mutations[e]||(t._mutations[e]=[]);i.push((function(e){n.call(t,r.state,e)}))}(t,s+n,e,c)})),r.forEachAction((function(e,n){var r=e.root?n:s+n,i=e.handler||e;!function(t,e,n,r){var i=t._actions[e]||(t._actions[e]=[]);i.push((function(e){var i,o=n.call(t,{dispatch:r.dispatch,commit:r.commit,getters:r.getters,state:r.state,rootGetters:t.getters,rootState:t.state},e);return(i=o)&&"function"==typeof i.then||(o=Promise.resolve(o)),t._devtoolHook?o.catch((function(e){throw t._devtoolHook.emit("vuex:error",e),e})):o}))}(t,r,i,c)})),r.forEachGetter((function(e,n){!function(t,e,n,r){if(t._wrappedGetters[e])return;t._wrappedGetters[e]=function(t){return n(r.state,r.getters,t.state,t.getters)}}(t,s+n,e,c)})),r.forEachChild((function(r,o){ql(t,e,n.concat(o),r,i)}))}function Jl(t,e){return e.reduce((function(t,e){return t[e]}),t)}function Kl(t,e,n){return Pl(t)&&t.type&&(n=e,e=t,t=t.type),{type:t,payload:e,options:n}}function Hl(t){zl&&t===zl||function(t){if(Number(t.version.split(".")[0])>=2)t.mixin({beforeCreate:n});else{var e=t.prototype._init;t.prototype._init=function(t){void 0===t&&(t={}),t.init=t.init?[n].concat(t.init):n,e.call(this,t)}}function n(){var t=this.$options;t.store?this.$store="function"==typeof t.store?t.store():t.store:t.parent&&t.parent.$store&&(this.$store=t.parent.$store)}}(zl=t)}Vl.state.get=function(){return this._vm._data.$$state},Vl.state.set=function(t){},jl.prototype.commit=function(t,e,n){var r=this,i=Kl(t,e,n),o=i.type,s=i.payload,l={type:o,payload:s},a=this._mutations[o];a&&(this._withCommit((function(){a.forEach((function(t){t(s)}))})),this._subscribers.slice().forEach((function(t){return t(l,r.state)})))},jl.prototype.dispatch=function(t,e){var n=this,r=Kl(t,e),i=r.type,o=r.payload,s={type:i,payload:o},l=this._actions[i];if(l){try{this._actionSubscribers.slice().filter((function(t){return t.before})).forEach((function(t){return t.before(s,n.state)}))}catch(c){}var a=l.length>1?Promise.all(l.map((function(t){return t(o)}))):l[0](o);return new Promise((function(t,e){a.then((function(e){try{n._actionSubscribers.filter((function(t){return t.after})).forEach((function(t){return t.after(s,n.state)}))}catch(c){}t(e)}),(function(t){try{n._actionSubscribers.filter((function(t){return t.error})).forEach((function(e){return e.error(s,n.state,t)}))}catch(c){}e(t)}))}))}},jl.prototype.subscribe=function(t,e){return Fl(t,this._subscribers,e)},jl.prototype.subscribeAction=function(t,e){return Fl("function"==typeof t?{before:t}:t,this._actionSubscribers,e)},jl.prototype.watch=function(t,e,n){var r=this;return this._watcherVM.$watch((function(){return t(r.state,r.getters)}),e,n)},jl.prototype.replaceState=function(t){var e=this;this._withCommit((function(){e._vm._data.$$state=t}))},jl.prototype.registerModule=function(t,e,n){void 0===n&&(n={}),"string"==typeof t&&(t=[t]),this._modules.register(t,e),ql(this,this.state,t,this._modules.get(t),n.preserveState),Wl(this,this.state)},jl.prototype.unregisterModule=function(t){var e=this;"string"==typeof t&&(t=[t]),this._modules.unregister(t),this._withCommit((function(){var n=Jl(e.state,t.slice(0,-1));zl.delete(n,t[t.length-1])})),Ll(this)},jl.prototype.hasModule=function(t){return"string"==typeof t&&(t=[t]),this._modules.isRegistered(t)},jl.prototype.hotUpdate=function(t){this._modules.update(t),Ll(this,!0)},jl.prototype._withCommit=function(t){var e=this._committing;this._committing=!0,t(),this._committing=e},Object.defineProperties(jl.prototype,Vl);var Yl=Ql((function(t,e){var n={};return Xl(e).forEach((function(e){var r=e.key,i=e.val;n[r]=function(){var e=this.$store.state,n=this.$store.getters;if(t){var r=ta(this.$store,"mapState",t);if(!r)return;e=r.context.state,n=r.context.getters}return"function"==typeof i?i.call(this,e,n):e[i]},n[r].vuex=!0})),n})),Ul=Ql((function(t,e){var n={};return Xl(e).forEach((function(e){var r=e.key,i=e.val;n[r]=function(){for(var e=[],n=arguments.length;n--;)e[n]=arguments[n];var r=this.$store.commit;if(t){var o=ta(this.$store,"mapMutations",t);if(!o)return;r=o.context.commit}return"function"==typeof i?i.apply(this,[r].concat(e)):r.apply(this.$store,[i].concat(e))}})),n})),Gl=Ql((function(t,e){var n={};return Xl(e).forEach((function(e){var r=e.key,i=e.val;i=t+i,n[r]=function(){if(!t||ta(this.$store,"mapGetters",t))return this.$store.getters[i]},n[r].vuex=!0})),n})),Zl=Ql((function(t,e){var n={};return Xl(e).forEach((function(e){var r=e.key,i=e.val;n[r]=function(){for(var e=[],n=arguments.length;n--;)e[n]=arguments[n];var r=this.$store.dispatch;if(t){var o=ta(this.$store,"mapActions",t);if(!o)return;r=o.context.dispatch}return"function"==typeof i?i.apply(this,[r].concat(e)):r.apply(this.$store,[i].concat(e))}})),n}));function Xl(t){return function(t){return Array.isArray(t)||Pl(t)}(t)?Array.isArray(t)?t.map((function(t){return{key:t,val:t}})):Object.keys(t).map((function(e){return{key:e,val:t[e]}})):[]}function Ql(t){return function(e,n){return"string"!=typeof e?(n=e,e=""):"/"!==e.charAt(e.length-1)&&(e+="/"),t(e,n)}}function ta(t,e,n){return t._modulesNamespaceMap[n]}function ea(t,e,n){var r=n?t.groupCollapsed:t.group;try{r.call(t,e)}catch(i){t.log(e)}}function na(t){try{t.groupEnd()}catch(e){t.log("—— log end ——")}}function ra(){var t=new Date;return" @ "+ia(t.getHours(),2)+":"+ia(t.getMinutes(),2)+":"+ia(t.getSeconds(),2)+"."+ia(t.getMilliseconds(),3)}function ia(t,e){return n="0",r=e-t.toString().length,new Array(r+1).join(n)+t;var n,r}const oa={Store:jl,install:Hl,version:"3.6.2",mapState:Yl,mapMutations:Ul,mapGetters:Gl,mapActions:Zl,createNamespacedHelpers:function(t){return{mapState:Yl.bind(null,t),mapGetters:Gl.bind(null,t),mapMutations:Ul.bind(null,t),mapActions:Zl.bind(null,t)}},createLogger:function(t){void 0===t&&(t={});var e=t.collapsed;void 0===e&&(e=!0);var n=t.filter;void 0===n&&(n=function(t,e,n){return!0});var r=t.transformer;void 0===r&&(r=function(t){return t});var i=t.mutationTransformer;void 0===i&&(i=function(t){return t});var o=t.actionFilter;void 0===o&&(o=function(t,e){return!0});var s=t.actionTransformer;void 0===s&&(s=function(t){return t});var l=t.logMutations;void 0===l&&(l=!0);var a=t.logActions;void 0===a&&(a=!0);var c=t.logger;return void 0===c&&(c=console),function(t){var h=El(t.state);void 0!==c&&(l&&t.subscribe((function(t,o){var s=El(o);if(n(t,h,s)){var l=ra(),a=i(t),u="mutation "+t.type+l;ea(c,u,e),c.log("%c prev state","color: #9E9E9E; font-weight: bold",r(h)),c.log("%c mutation","color: #03A9F4; font-weight: bold",a),c.log("%c next state","color: #4CAF50; font-weight: bold",r(s)),na(c)}h=s})),a&&t.subscribeAction((function(t,n){if(o(t,n)){var r=ra(),i=s(t),l="action "+t.type+r;ea(c,l,e),c.log("%c action","color: #03A9F4; font-weight: bold",i),na(c)}})))}}};var sa={},la={};function aa(t){return null==t}function ca(t){return null!=t}function ha(t,e){return e.tag===t.tag&&e.key===t.key}function ua(t){var e=t.tag;t.vm=new e({data:t.args})}function da(t,e,n){var r,i,o={};for(r=e;r<=n;++r)ca(i=t[r].key)&&(o[i]=r);return o}function fa(t,e,n){for(;e<=n;++e)ua(t[e])}function pa(t,e,n){for(;e<=n;++e){var r=t[e];ca(r)&&(r.vm.$destroy(),r.vm=null)}}function ma(t,e){t!==e&&(e.vm=t.vm,function(t){for(var e=Object.keys(t.args),n=0;nl?fa(e,s,h):s>h&&pa(t,o,l)}(t,e):ca(e)?fa(e,0,e.length-1):ca(t)&&pa(t,0,t.length-1)},function(t){Object.defineProperty(t,"__esModule",{value:!0}),t.Vuelidate=C,t.validationMixin=t.default=void 0,Object.defineProperty(t,"withParams",{enumerable:!0,get:function(){return n.withParams}});var e=la,n=s;function r(t){return function(t){if(Array.isArray(t))return i(t)}(t)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(t)||function(t,e){if(!t)return;if("string"==typeof t)return i(t,e);var n=Object.prototype.toString.call(t).slice(8,-1);"Object"===n&&t.constructor&&(n=t.constructor.name);if("Map"===n||"Set"===n)return Array.from(t);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return i(t,e)}(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function i(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,r=new Array(e);n1?l:l.$sub[0]:null}}},computed:{run:function(){var t=this,e=this.lazyParentModel();if(Array.isArray(e)&&e.__ob__){var n=e.__ob__.dep;n.depend();var r=n.constructor.target;if(!this._indirectWatcher){var i=r.constructor;this._indirectWatcher=new i(this,(function(){return t.runRule(e)}),null,{lazy:!0})}var o=this.getModel();if(!this._indirectWatcher.dirty&&this._lastModel===o)return this._indirectWatcher.depend(),r.value;this._lastModel=o,this._indirectWatcher.evaluate(),this._indirectWatcher.depend()}else this._indirectWatcher&&(this._indirectWatcher.teardown(),this._indirectWatcher=null);return this._indirectWatcher?this._indirectWatcher.value:this.runRule(e)},$params:function(){return this.run.params},proxy:function(){var t=this.run.output;return t[m]?!!t.v:!!t},$pending:function(){var t=this.run.output;return!!t[m]&&t.p}},destroyed:function(){this._indirectWatcher&&(this._indirectWatcher.teardown(),this._indirectWatcher=null)}}),s=i.extend({data:function(){return{dirty:!1,validations:null,lazyModel:null,model:null,prop:null,lazyParentModel:null,rootModel:null}},methods:l(l({},v),{},{refProxy:function(t){return this.getRef(t).proxy},getRef:function(t){return this.refs[t]},isNested:function(t){return"function"!=typeof this.validations[t]}}),computed:l(l({},g),{},{nestedKeys:function(){return this.keys.filter(this.isNested)},ruleKeys:function(){var t=this;return this.keys.filter((function(e){return!t.isNested(e)}))},keys:function(){return Object.keys(this.validations).filter((function(t){return"$params"!==t}))},proxy:function(){var t=this,e=u(this.keys,(function(e){return{enumerable:!0,configurable:!0,get:function(){return t.refProxy(e)}}})),n=u(w,(function(e){return{enumerable:!0,configurable:!0,get:function(){return t[e]}}})),r=u(b,(function(e){return{enumerable:!1,configurable:!0,get:function(){return t[e]}}})),i=this.hasIter()?{$iter:{enumerable:!0,value:Object.defineProperties({},l({},e))}}:{};return Object.defineProperties({},l(l(l(l({},e),i),{},{$model:{enumerable:!0,get:function(){var e=t.lazyParentModel();return null!=e?e[t.prop]:null},set:function(e){var n=t.lazyParentModel();null!=n&&(n[t.prop]=e,t.$touch())}}},n),r))},children:function(){var t=this;return[].concat(r(this.nestedKeys.map((function(e){return y(t,e)}))),r(this.ruleKeys.map((function(e){return S(t,e)})))).filter(Boolean)}})}),a=s.extend({methods:{isNested:function(t){return void 0!==this.validations[t]()},getRef:function(t){var e=this;return{get proxy(){return e.validations[t]()||!1}}}}}),c=s.extend({computed:{keys:function(){var t=this.getModel();return f(t)?Object.keys(t):[]},tracker:function(){var t=this,e=this.validations.$trackBy;return e?function(n){return"".concat(p(t.rootModel,t.getModelKey(n),e))}:function(t){return"".concat(t)}},getModelLazy:function(){var t=this;return function(){return t.getModel()}},children:function(){var t=this,n=this.validations,r=this.getModel(),i=l({},n);delete i.$trackBy;var o={};return this.keys.map((function(n){var l=t.tracker(n);return o.hasOwnProperty(l)?null:(o[l]=!0,(0,e.h)(s,l,{validations:i,prop:n,lazyParentModel:t.getModelLazy,model:r[n],rootModel:t.rootModel}))})).filter(Boolean)}},methods:{isNested:function(){return!0},getRef:function(t){return this.refs[this.tracker(t)]},hasIter:function(){return!0}}}),y=function(t,n){if("$each"===n)return(0,e.h)(c,n,{validations:t.validations[n],lazyParentModel:t.lazyParentModel,prop:n,lazyModel:t.getModel,rootModel:t.rootModel});var r=t.validations[n];if(Array.isArray(r)){var i=t.rootModel,o=u(r,(function(t){return function(){return p(i,i.$v,t)}}),(function(t){return Array.isArray(t)?t.join("."):t}));return(0,e.h)(a,n,{validations:o,lazyParentModel:h,prop:n,lazyModel:h,rootModel:i})}return(0,e.h)(s,n,{validations:r,lazyParentModel:t.getModel,prop:n,lazyModel:t.getModelKey,rootModel:t.rootModel})},S=function(t,n){return(0,e.h)(o,n,{rule:t.validations[n],lazyParentModel:t.lazyParentModel,lazyModel:t.getModel,rootModel:t.rootModel})};return x={VBase:i,Validation:s}},k=null;var M=function(t,n){var r=function(t){if(k)return k;for(var e=t.constructor;e.super;)e=e.super;return k=e,e}(t),i=S(r),o=i.Validation;return new(0,i.VBase)({computed:{children:function(){var r="function"==typeof n?n.call(t):n;return[(0,e.h)(o,"$v",{validations:r,lazyParentModel:h,prop:"$v",model:t,rootModel:t})]}}})},O={data:function(){var t=this.$options.validations;return t&&(this._vuelidate=M(this,t)),{}},beforeCreate:function(){var t=this.$options;t.validations&&(t.computed||(t.computed={}),t.computed.$v||(t.computed.$v=function(){return this._vuelidate?this._vuelidate.refs.$v.proxy:null}))},beforeDestroy:function(){this._vuelidate&&(this._vuelidate.$destroy(),this._vuelidate=null)}};function C(t){t.mixin(O)}t.validationMixin=O;var D=C;t.default=D}(sa);const ga=e(sa);export{wl as A,Cl as B,Nl as C,ne as D,gn as E,dt as F,Ml as G,Tl as H,gs as I,ga as J,t as K,e as L,on as N,vn as P,vt as S,nn as T,oa as V,Zo as a,ls as b,hs as c,xs as d,Qo as e,Ss as f,As as g,$s as h,Is as i,te as j,Jo as k,Ps as l,vs as m,No as n,de as o,ms as p,Qs as q,tl as r,as as s,cs as t,bs as u,n as v,Es as w,el as x,nl as y,Xs as z}; diff --git a/kirby/panel/dist/js/vue.js b/kirby/panel/dist/js/vue.js deleted file mode 100644 index efebcc6..0000000 --- a/kirby/panel/dist/js/vue.js +++ /dev/null @@ -1,11 +0,0 @@ -/*! - * Vue.js v2.7.14 - * (c) 2014-2022 Evan You - * Released under the MIT License. - */ -/*! - * Vue.js v2.7.14 - * (c) 2014-2022 Evan You - * Released under the MIT License. - */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).Vue=e()}(this,(function(){"use strict";var t=Object.freeze({}),e=Array.isArray;function n(t){return null==t}function r(t){return null!=t}function o(t){return!0===t}function i(t){return"string"==typeof t||"number"==typeof t||"symbol"==typeof t||"boolean"==typeof t}function a(t){return"function"==typeof t}function s(t){return null!==t&&"object"==typeof t}var c=Object.prototype.toString;function u(t){return"[object Object]"===c.call(t)}function l(t){var e=parseFloat(String(t));return e>=0&&Math.floor(e)===e&&isFinite(t)}function f(t){return r(t)&&"function"==typeof t.then&&"function"==typeof t.catch}function d(t){return null==t?"":Array.isArray(t)||u(t)&&t.toString===c?JSON.stringify(t,null,2):String(t)}function p(t){var e=parseFloat(t);return isNaN(e)?t:e}function v(t,e){for(var n=Object.create(null),r=t.split(","),o=0;o-1)return t.splice(r,1)}}var y=Object.prototype.hasOwnProperty;function _(t,e){return y.call(t,e)}function b(t){var e=Object.create(null);return function(n){return e[n]||(e[n]=t(n))}}var $=/-(\w)/g,w=b((function(t){return t.replace($,(function(t,e){return e?e.toUpperCase():""}))})),x=b((function(t){return t.charAt(0).toUpperCase()+t.slice(1)})),C=/\B([A-Z])/g,k=b((function(t){return t.replace(C,"-$1").toLowerCase()}));var S=Function.prototype.bind?function(t,e){return t.bind(e)}:function(t,e){function n(n){var r=arguments.length;return r?r>1?t.apply(e,arguments):t.call(e,n):t.call(e)}return n._length=t.length,n};function O(t,e){e=e||0;for(var n=t.length-e,r=new Array(n);n--;)r[n]=t[n+e];return r}function T(t,e){for(var n in e)t[n]=e[n];return t}function A(t){for(var e={},n=0;n0,G=q&&q.indexOf("edge/")>0;q&&q.indexOf("android");var X=q&&/iphone|ipad|ipod|ios/.test(q);q&&/chrome\/\d+/.test(q),q&&/phantomjs/.test(q);var Y,Q=q&&q.match(/firefox\/(\d+)/),tt={}.watch,et=!1;if(J)try{var nt={};Object.defineProperty(nt,"passive",{get:function(){et=!0}}),window.addEventListener("test-passive",null,nt)}catch(t){}var rt=function(){return void 0===Y&&(Y=!J&&"undefined"!=typeof global&&(global.process&&"server"===global.process.env.VUE_ENV)),Y},ot=J&&window.__VUE_DEVTOOLS_GLOBAL_HOOK__;function it(t){return"function"==typeof t&&/native code/.test(t.toString())}var at,st="undefined"!=typeof Symbol&&it(Symbol)&&"undefined"!=typeof Reflect&&it(Reflect.ownKeys);at="undefined"!=typeof Set&&it(Set)?Set:function(){function t(){this.set=Object.create(null)}return t.prototype.has=function(t){return!0===this.set[t]},t.prototype.add=function(t){this.set[t]=!0},t.prototype.clear=function(){this.set=Object.create(null)},t}();var ct=null;function ut(t){void 0===t&&(t=null),t||ct&&ct._scope.off(),ct=t,t&&t._scope.on()}var lt=function(){function t(t,e,n,r,o,i,a,s){this.tag=t,this.data=e,this.children=n,this.text=r,this.elm=o,this.ns=void 0,this.context=i,this.fnContext=void 0,this.fnOptions=void 0,this.fnScopeId=void 0,this.key=e&&e.key,this.componentOptions=a,this.componentInstance=void 0,this.parent=void 0,this.raw=!1,this.isStatic=!1,this.isRootInsert=!0,this.isComment=!1,this.isCloned=!1,this.isOnce=!1,this.asyncFactory=s,this.asyncMeta=void 0,this.isAsyncPlaceholder=!1}return Object.defineProperty(t.prototype,"child",{get:function(){return this.componentInstance},enumerable:!1,configurable:!0}),t}(),ft=function(t){void 0===t&&(t="");var e=new lt;return e.text=t,e.isComment=!0,e};function dt(t){return new lt(void 0,void 0,void 0,String(t))}function pt(t){var e=new lt(t.tag,t.data,t.children&&t.children.slice(),t.text,t.elm,t.context,t.componentOptions,t.asyncFactory);return e.ns=t.ns,e.isStatic=t.isStatic,e.key=t.key,e.isComment=t.isComment,e.fnContext=t.fnContext,e.fnOptions=t.fnOptions,e.fnScopeId=t.fnScopeId,e.asyncMeta=t.asyncMeta,e.isCloned=!0,e}var vt=0,ht=[],mt=function(){function t(){this._pending=!1,this.id=vt++,this.subs=[]}return t.prototype.addSub=function(t){this.subs.push(t)},t.prototype.removeSub=function(t){this.subs[this.subs.indexOf(t)]=null,this._pending||(this._pending=!0,ht.push(this))},t.prototype.depend=function(e){t.target&&t.target.addDep(this)},t.prototype.notify=function(t){for(var e=this.subs.filter((function(t){return t})),n=0,r=e.length;n0&&(Yt((c=Qt(c,"".concat(a||"","_").concat(s)))[0])&&Yt(l)&&(f[u]=dt(l.text+c[0].text),c.shift()),f.push.apply(f,c)):i(c)?Yt(l)?f[u]=dt(l.text+c):""!==c&&f.push(dt(c)):Yt(c)&&Yt(l)?f[u]=dt(l.text+c.text):(o(t._isVList)&&r(c.tag)&&n(c.key)&&r(a)&&(c.key="__vlist".concat(a,"_").concat(s,"__")),f.push(c)));return f}function te(t,n,c,u,l,f){return(e(c)||i(c))&&(l=u,u=c,c=void 0),o(f)&&(l=2),function(t,n,o,i,c){if(r(o)&&r(o.__ob__))return ft();r(o)&&r(o.is)&&(n=o.is);if(!n)return ft();e(i)&&a(i[0])&&((o=o||{}).scopedSlots={default:i[0]},i.length=0);2===c?i=Xt(i):1===c&&(i=function(t){for(var n=0;n0,s=n?!!n.$stable:!a,c=n&&n.$key;if(n){if(n._normalized)return n._normalized;if(s&&o&&o!==t&&c===o.$key&&!a&&!o.$hasNormal)return o;for(var u in i={},n)n[u]&&"$"!==u[0]&&(i[u]=$e(e,r,u,n[u]))}else i={};for(var l in r)l in i||(i[l]=we(r,l));return n&&Object.isExtensible(n)&&(n._normalized=i),z(i,"$stable",s),z(i,"$key",c),z(i,"$hasNormal",a),i}function $e(t,n,r,o){var i=function(){var n=ct;ut(t);var r=arguments.length?o.apply(null,arguments):o({}),i=(r=r&&"object"==typeof r&&!e(r)?[r]:Xt(r))&&r[0];return ut(n),r&&(!i||1===r.length&&i.isComment&&!_e(i))?void 0:r};return o.proxy&&Object.defineProperty(n,r,{get:i,enumerable:!0,configurable:!0}),i}function we(t,e){return function(){return t[e]}}function xe(e){return{get attrs(){if(!e._attrsProxy){var n=e._attrsProxy={};z(n,"_v_attr_proxy",!0),Ce(n,e.$attrs,t,e,"$attrs")}return e._attrsProxy},get listeners(){e._listenersProxy||Ce(e._listenersProxy={},e.$listeners,t,e,"$listeners");return e._listenersProxy},get slots(){return function(t){t._slotsProxy||Se(t._slotsProxy={},t.$scopedSlots);return t._slotsProxy}(e)},emit:S(e.$emit,e),expose:function(t){t&&Object.keys(t).forEach((function(n){return Bt(e,t,n)}))}}}function Ce(t,e,n,r,o){var i=!1;for(var a in e)a in t?e[a]!==n[a]&&(i=!0):(i=!0,ke(t,a,r,o));for(var a in t)a in e||(i=!0,delete t[a]);return i}function ke(t,e,n,r){Object.defineProperty(t,e,{enumerable:!0,configurable:!0,get:function(){return n[r][e]}})}function Se(t,e){for(var n in e)t[n]=e[n];for(var n in t)n in e||delete t[n]}function Oe(){var t=ct;return t._setupContext||(t._setupContext=xe(t))}var Te,Ae=null;function je(t,e){return(t.__esModule||st&&"Module"===t[Symbol.toStringTag])&&(t=t.default),s(t)?e.extend(t):t}function Ee(t){if(e(t))for(var n=0;ndocument.createEvent("Event").timeStamp&&(Ze=function(){return Ge.now()})}var Xe=function(t,e){if(t.post){if(!e.post)return 1}else if(e.post)return-1;return t.id-e.id};function Ye(){var t,e;for(We=Ze(),Je=!0,Ue.sort(Xe),qe=0;qeqe&&Ue[n].id>t.id;)n--;Ue.splice(n+1,0,t)}else Ue.push(t);Ke||(Ke=!0,Cn(Ye))}}var tn="watcher",en="".concat(tn," callback"),nn="".concat(tn," getter"),rn="".concat(tn," cleanup");function on(t,e){return cn(t,null,{flush:"post"})}var an,sn={};function cn(n,r,o){var i=void 0===o?t:o,s=i.immediate,c=i.deep,u=i.flush,l=void 0===u?"pre":u;i.onTrack,i.onTrigger;var f,d,p=ct,v=function(t,e,n){return void 0===n&&(n=null),dn(t,null,n,p,e)},h=!1,m=!1;if(Ft(n)?(f=function(){return n.value},h=It(n)):Mt(n)?(f=function(){return n.__ob__.dep.depend(),n},c=!0):e(n)?(m=!0,h=n.some((function(t){return Mt(t)||It(t)})),f=function(){return n.map((function(t){return Ft(t)?t.value:Mt(t)?Bn(t):a(t)?v(t,nn):void 0}))}):f=a(n)?r?function(){return v(n,nn)}:function(){if(!p||!p._isDestroyed)return d&&d(),v(n,tn,[y])}:j,r&&c){var g=f;f=function(){return Bn(g())}}var y=function(t){d=_.onStop=function(){v(t,rn)}};if(rt())return y=j,r?s&&v(r,en,[f(),m?[]:void 0,y]):f(),j;var _=new Vn(ct,f,j,{lazy:!0});_.noRecurse=!r;var b=m?[]:sn;return _.run=function(){if(_.active)if(r){var t=_.get();(c||h||(m?t.some((function(t,e){return I(t,b[e])})):I(t,b)))&&(d&&d(),v(r,en,[t,b===sn?void 0:b,y]),b=t)}else _.get()},"sync"===l?_.update=_.run:"post"===l?(_.post=!0,_.update=function(){return Qe(_)}):_.update=function(){if(p&&p===ct&&!p._isMounted){var t=p._preWatchers||(p._preWatchers=[]);t.indexOf(_)<0&&t.push(_)}else Qe(_)},r?s?_.run():b=_.get():"post"===l&&p?p.$once("hook:mounted",(function(){return _.get()})):_.get(),function(){_.teardown()}}var un=function(){function t(t){void 0===t&&(t=!1),this.detached=t,this.active=!0,this.effects=[],this.cleanups=[],this.parent=an,!t&&an&&(this.index=(an.scopes||(an.scopes=[])).push(this)-1)}return t.prototype.run=function(t){if(this.active){var e=an;try{return an=this,t()}finally{an=e}}},t.prototype.on=function(){an=this},t.prototype.off=function(){an=this.parent},t.prototype.stop=function(t){if(this.active){var e=void 0,n=void 0;for(e=0,n=this.effects.length;e1)return n&&a(e)?e.call(r):e}},h:function(t,e,n){return te(ct,t,e,n,2,!0)},getCurrentInstance:function(){return ct&&{proxy:ct}},useSlots:function(){return Oe().slots},useAttrs:function(){return Oe().attrs},useListeners:function(){return Oe().listeners},mergeDefaults:function(t,n){var r=e(t)?t.reduce((function(t,e){return t[e]={},t}),{}):t;for(var o in n){var i=r[o];i?e(i)||a(i)?r[o]={type:i,default:n[o]}:i.default=n[o]:null===i&&(r[o]={default:n[o]})}return r},nextTick:Cn,set:jt,del:Et,useCssModule:function(e){return t},useCssVars:function(t){if(J){var e=ct;e&&on((function(){var n=e.$el,r=t(e,e._setupProxy);if(n&&1===n.nodeType){var o=n.style;for(var i in r)o.setProperty("--".concat(i),r[i])}}))}},defineAsyncComponent:function(t){a(t)&&(t={loader:t});var e=t.loader,n=t.loadingComponent,r=t.errorComponent,o=t.delay,i=void 0===o?200:o,s=t.timeout;t.suspensible;var c=t.onError,u=null,l=0,f=function(){var t;return u||(t=u=e().catch((function(t){if(t=t instanceof Error?t:new Error(String(t)),c)return new Promise((function(e,n){c(t,(function(){return e((l++,u=null,f()))}),(function(){return n(t)}),l+1)}));throw t})).then((function(e){return t!==u&&u?u:(e&&(e.__esModule||"Module"===e[Symbol.toStringTag])&&(e=e.default),e)})))};return function(){return{component:f(),delay:i,timeout:s,error:r,loading:n}}},onBeforeMount:Sn,onMounted:On,onBeforeUpdate:Tn,onUpdated:An,onBeforeUnmount:jn,onUnmounted:En,onActivated:Nn,onDeactivated:Pn,onServerPrefetch:Dn,onRenderTracked:Mn,onRenderTriggered:In,onErrorCaptured:function(t,e){void 0===e&&(e=ct),Ln(t,e)}}),Hn=new at;function Bn(t){return Un(t,Hn),Hn.clear(),t}function Un(t,n){var r,o,i=e(t);if(!(!i&&!s(t)||t.__v_skip||Object.isFrozen(t)||t instanceof lt)){if(t.__ob__){var a=t.__ob__.dep.id;if(n.has(a))return;n.add(a)}if(i)for(r=t.length;r--;)Un(t[r],n);else if(Ft(t))Un(t.value,n);else for(r=(o=Object.keys(t)).length;r--;)Un(t[o[r]],n)}}var zn=0,Vn=function(){function t(t,e,n,r,o){!function(t,e){void 0===e&&(e=an),e&&e.active&&e.effects.push(t)}(this,an&&!an._vm?an:t?t._scope:void 0),(this.vm=t)&&o&&(t._watcher=this),r?(this.deep=!!r.deep,this.user=!!r.user,this.lazy=!!r.lazy,this.sync=!!r.sync,this.before=r.before):this.deep=this.user=this.lazy=this.sync=!1,this.cb=n,this.id=++zn,this.active=!0,this.post=!1,this.dirty=this.lazy,this.deps=[],this.newDeps=[],this.depIds=new at,this.newDepIds=new at,this.expression="",a(e)?this.getter=e:(this.getter=function(t){if(!V.test(t)){var e=t.split(".");return function(t){for(var n=0;n-1)if(i&&!_(o,"default"))s=!1;else if(""===s||s===k(t)){var u=xr(String,o.type);(u<0||c-1:"string"==typeof t?t.split(",").indexOf(n)>-1:(r=t,"[object RegExp]"===c.call(r)&&t.test(n));var r}function Tr(t,e){var n=t.cache,r=t.keys,o=t._vnode;for(var i in n){var a=n[i];if(a){var s=a.name;s&&!e(s)&&Ar(n,i,r,o)}}}function Ar(t,e,n,r){var o=t[e];!o||r&&o.tag===r.tag||o.componentInstance.$destroy(),t[e]=null,g(n,e)}!function(e){e.prototype._init=function(e){var n=this;n._uid=tr++,n._isVue=!0,n.__v_skip=!0,n._scope=new un(!0),n._scope._vm=!0,e&&e._isComponent?function(t,e){var n=t.$options=Object.create(t.constructor.options),r=e._parentVnode;n.parent=e.parent,n._parentVnode=r;var o=r.componentOptions;n.propsData=o.propsData,n._parentListeners=o.listeners,n._renderChildren=o.children,n._componentTag=o.tag,e.render&&(n.render=e.render,n.staticRenderFns=e.staticRenderFns)}(n,e):n.$options=gr(er(n.constructor),e||{},n),n._renderProxy=n,n._self=n,function(t){var e=t.$options,n=e.parent;if(n&&!e.abstract){for(;n.$options.abstract&&n.$parent;)n=n.$parent;n.$children.push(t)}t.$parent=n,t.$root=n?n.$root:t,t.$children=[],t.$refs={},t._provided=n?n._provided:Object.create(null),t._watcher=null,t._inactive=null,t._directInactive=!1,t._isMounted=!1,t._isDestroyed=!1,t._isBeingDestroyed=!1}(n),function(t){t._events=Object.create(null),t._hasHookEvent=!1;var e=t.$options._parentListeners;e&&Me(t,e)}(n),function(e){e._vnode=null,e._staticTrees=null;var n=e.$options,r=e.$vnode=n._parentVnode,o=r&&r.context;e.$slots=ge(n._renderChildren,o),e.$scopedSlots=r?be(e.$parent,r.data.scopedSlots,e.$slots):t,e._c=function(t,n,r,o){return te(e,t,n,r,o,!1)},e.$createElement=function(t,n,r,o){return te(e,t,n,r,o,!0)};var i=r&&r.data;At(e,"$attrs",i&&i.attrs||t,null,!0),At(e,"$listeners",n._parentListeners||t,null,!0)}(n),Be(n,"beforeCreate",void 0,!1),function(t){var e=Qn(t.$options.inject,t);e&&(kt(!1),Object.keys(e).forEach((function(n){At(t,n,e[n])})),kt(!0))}(n),qn(n),function(t){var e=t.$options.provide;if(e){var n=a(e)?e.call(t):e;if(!s(n))return;for(var r=ln(t),o=st?Reflect.ownKeys(n):Object.keys(n),i=0;i1?O(n):n;for(var r=O(arguments,1),o='event handler for "'.concat(t,'"'),i=0,a=n.length;iparseInt(this.max)&&Ar(e,n[0],n,this._vnode),this.vnodeToCache=null}}},created:function(){this.cache=Object.create(null),this.keys=[]},destroyed:function(){for(var t in this.cache)Ar(this.cache,t,this.keys)},mounted:function(){var t=this;this.cacheVNode(),this.$watch("include",(function(e){Tr(t,(function(t){return Or(e,t)}))})),this.$watch("exclude",(function(e){Tr(t,(function(t){return!Or(e,t)}))}))},updated:function(){this.cacheVNode()},render:function(){var t=this.$slots.default,e=Ee(t),n=e&&e.componentOptions;if(n){var r=Sr(n),o=this.include,i=this.exclude;if(o&&(!r||!Or(o,r))||i&&r&&Or(i,r))return e;var a=this.cache,s=this.keys,c=null==e.key?n.Ctor.cid+(n.tag?"::".concat(n.tag):""):e.key;a[c]?(e.componentInstance=a[c].componentInstance,g(s,c),s.push(c)):(this.vnodeToCache=e,this.keyToCache=c),e.data.keepAlive=!0}return e||t&&t[0]}},Nr={KeepAlive:Er};!function(t){var e={get:function(){return H}};Object.defineProperty(t,"config",e),t.util={warn:lr,extend:T,mergeOptions:gr,defineReactive:At},t.set=jt,t.delete=Et,t.nextTick=Cn,t.observable=function(t){return Tt(t),t},t.options=Object.create(null),R.forEach((function(e){t.options[e+"s"]=Object.create(null)})),t.options._base=t,T(t.options.components,Nr),function(t){t.use=function(t){var e=this._installedPlugins||(this._installedPlugins=[]);if(e.indexOf(t)>-1)return this;var n=O(arguments,1);return n.unshift(this),a(t.install)?t.install.apply(t,n):a(t)&&t.apply(null,n),e.push(t),this}}(t),function(t){t.mixin=function(t){return this.options=gr(this.options,t),this}}(t),kr(t),function(t){R.forEach((function(e){t[e]=function(t,n){return n?("component"===e&&u(n)&&(n.name=n.name||t,n=this.options._base.extend(n)),"directive"===e&&a(n)&&(n={bind:n,update:n}),this.options[e+"s"][t]=n,n):this.options[e+"s"][t]}}))}(t)}(Cr),Object.defineProperty(Cr.prototype,"$isServer",{get:rt}),Object.defineProperty(Cr.prototype,"$ssrContext",{get:function(){return this.$vnode&&this.$vnode.ssrContext}}),Object.defineProperty(Cr,"FunctionalRenderContext",{value:nr}),Cr.version=Rn;var Pr=v("style,class"),Dr=v("input,textarea,option,select,progress"),Mr=function(t,e,n){return"value"===n&&Dr(t)&&"button"!==e||"selected"===n&&"option"===t||"checked"===n&&"input"===t||"muted"===n&&"video"===t},Ir=v("contenteditable,draggable,spellcheck"),Lr=v("events,caret,typing,plaintext-only"),Rr=v("allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,defaultchecked,defaultmuted,defaultselected,defer,disabled,enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,truespeed,typemustmatch,visible"),Fr="http://www.w3.org/1999/xlink",Hr=function(t){return":"===t.charAt(5)&&"xlink"===t.slice(0,5)},Br=function(t){return Hr(t)?t.slice(6,t.length):""},Ur=function(t){return null==t||!1===t};function zr(t){for(var e=t.data,n=t,o=t;r(o.componentInstance);)(o=o.componentInstance._vnode)&&o.data&&(e=Vr(o.data,e));for(;r(n=n.parent);)n&&n.data&&(e=Vr(e,n.data));return function(t,e){if(r(t)||r(e))return Kr(t,Jr(e));return""}(e.staticClass,e.class)}function Vr(t,e){return{staticClass:Kr(t.staticClass,e.staticClass),class:r(t.class)?[t.class,e.class]:e.class}}function Kr(t,e){return t?e?t+" "+e:t:e||""}function Jr(t){return Array.isArray(t)?function(t){for(var e,n="",o=0,i=t.length;o-1?_o(t,e,n):Rr(e)?Ur(n)?t.removeAttribute(e):(n="allowfullscreen"===e&&"EMBED"===t.tagName?"true":e,t.setAttribute(e,n)):Ir(e)?t.setAttribute(e,function(t,e){return Ur(e)||"false"===e?"false":"contenteditable"===t&&Lr(e)?e:"true"}(e,n)):Hr(e)?Ur(n)?t.removeAttributeNS(Fr,Br(e)):t.setAttributeNS(Fr,e,n):_o(t,e,n)}function _o(t,e,n){if(Ur(n))t.removeAttribute(e);else{if(W&&!Z&&"TEXTAREA"===t.tagName&&"placeholder"===e&&""!==n&&!t.__ieph){var r=function(e){e.stopImmediatePropagation(),t.removeEventListener("input",r)};t.addEventListener("input",r),t.__ieph=!0}t.setAttribute(e,n)}}var bo={create:go,update:go};function $o(t,e){var o=e.elm,i=e.data,a=t.data;if(!(n(i.staticClass)&&n(i.class)&&(n(a)||n(a.staticClass)&&n(a.class)))){var s=zr(e),c=o._transitionClasses;r(c)&&(s=Kr(s,Jr(c))),s!==o._prevClass&&(o.setAttribute("class",s),o._prevClass=s)}}var wo,xo,Co,ko,So,Oo,To={create:$o,update:$o},Ao=/[\w).+\-_$\]]/;function jo(t){var e,n,r,o,i,a=!1,s=!1,c=!1,u=!1,l=0,f=0,d=0,p=0;for(r=0;r=0&&" "===(h=t.charAt(v));v--);h&&Ao.test(h)||(u=!0)}}else void 0===o?(p=r+1,o=t.slice(0,r).trim()):m();function m(){(i||(i=[])).push(t.slice(p,r).trim()),p=r+1}if(void 0===o?o=t.slice(0,r).trim():0!==p&&m(),i)for(r=0;r-1?{exp:t.slice(0,ko),key:'"'+t.slice(ko+1)+'"'}:{exp:t,key:null};xo=t,ko=So=Oo=0;for(;!qo();)Wo(Co=Jo())?Go(Co):91===Co&&Zo(Co);return{exp:t.slice(0,So),key:t.slice(So+1,Oo)}}(t);return null===n.key?"".concat(t,"=").concat(e):"$set(".concat(n.exp,", ").concat(n.key,", ").concat(e,")")}function Jo(){return xo.charCodeAt(++ko)}function qo(){return ko>=wo}function Wo(t){return 34===t||39===t}function Zo(t){var e=1;for(So=ko;!qo();)if(Wo(t=Jo()))Go(t);else if(91===t&&e++,93===t&&e--,0===e){Oo=ko;break}}function Go(t){for(var e=t;!qo()&&(t=Jo())!==e;);}var Xo,Yo="__r";function Qo(t,e,n){var r=Xo;return function o(){var i=e.apply(null,arguments);null!==i&&ni(t,o,n,r)}}var ti=mn&&!(Q&&Number(Q[1])<=53);function ei(t,e,n,r){if(ti){var o=We,i=e;e=i._wrapper=function(t){if(t.target===t.currentTarget||t.timeStamp>=o||t.timeStamp<=0||t.target.ownerDocument!==document)return i.apply(this,arguments)}}Xo.addEventListener(t,e,et?{capture:n,passive:r}:n)}function ni(t,e,n,r){(r||Xo).removeEventListener(t,e._wrapper||e,n)}function ri(t,e){if(!n(t.data.on)||!n(e.data.on)){var o=e.data.on||{},i=t.data.on||{};Xo=e.elm||t.elm,function(t){if(r(t.__r)){var e=W?"change":"input";t[e]=[].concat(t.__r,t[e]||[]),delete t.__r}r(t.__c)&&(t.change=[].concat(t.__c,t.change||[]),delete t.__c)}(o),Wt(o,i,ei,ni,Qo,e.context),Xo=void 0}}var oi,ii={create:ri,update:ri,destroy:function(t){return ri(t,io)}};function ai(t,e){if(!n(t.data.domProps)||!n(e.data.domProps)){var i,a,s=e.elm,c=t.data.domProps||{},u=e.data.domProps||{};for(i in(r(u.__ob__)||o(u._v_attr_proxy))&&(u=e.data.domProps=T({},u)),c)i in u||(s[i]="");for(i in u){if(a=u[i],"textContent"===i||"innerHTML"===i){if(e.children&&(e.children.length=0),a===c[i])continue;1===s.childNodes.length&&s.removeChild(s.childNodes[0])}if("value"===i&&"PROGRESS"!==s.tagName){s._value=a;var l=n(a)?"":String(a);si(s,l)&&(s.value=l)}else if("innerHTML"===i&&Zr(s.tagName)&&n(s.innerHTML)){(oi=oi||document.createElement("div")).innerHTML="".concat(a,"");for(var f=oi.firstChild;s.firstChild;)s.removeChild(s.firstChild);for(;f.firstChild;)s.appendChild(f.firstChild)}else if(a!==c[i])try{s[i]=a}catch(t){}}}}function si(t,e){return!t.composing&&("OPTION"===t.tagName||function(t,e){var n=!0;try{n=document.activeElement!==t}catch(t){}return n&&t.value!==e}(t,e)||function(t,e){var n=t.value,o=t._vModifiers;if(r(o)){if(o.number)return p(n)!==p(e);if(o.trim)return n.trim()!==e.trim()}return n!==e}(t,e))}var ci={create:ai,update:ai},ui=b((function(t){var e={},n=/:(.+)/;return t.split(/;(?![^(]*\))/g).forEach((function(t){if(t){var r=t.split(n);r.length>1&&(e[r[0].trim()]=r[1].trim())}})),e}));function li(t){var e=fi(t.style);return t.staticStyle?T(t.staticStyle,e):e}function fi(t){return Array.isArray(t)?A(t):"string"==typeof t?ui(t):t}var di,pi=/^--/,vi=/\s*!important$/,hi=function(t,e,n){if(pi.test(e))t.style.setProperty(e,n);else if(vi.test(n))t.style.setProperty(k(e),n.replace(vi,""),"important");else{var r=gi(e);if(Array.isArray(n))for(var o=0,i=n.length;o-1?e.split(bi).forEach((function(e){return t.classList.add(e)})):t.classList.add(e);else{var n=" ".concat(t.getAttribute("class")||""," ");n.indexOf(" "+e+" ")<0&&t.setAttribute("class",(n+e).trim())}}function wi(t,e){if(e&&(e=e.trim()))if(t.classList)e.indexOf(" ")>-1?e.split(bi).forEach((function(e){return t.classList.remove(e)})):t.classList.remove(e),t.classList.length||t.removeAttribute("class");else{for(var n=" ".concat(t.getAttribute("class")||""," "),r=" "+e+" ";n.indexOf(r)>=0;)n=n.replace(r," ");(n=n.trim())?t.setAttribute("class",n):t.removeAttribute("class")}}function xi(t){if(t){if("object"==typeof t){var e={};return!1!==t.css&&T(e,Ci(t.name||"v")),T(e,t),e}return"string"==typeof t?Ci(t):void 0}}var Ci=b((function(t){return{enterClass:"".concat(t,"-enter"),enterToClass:"".concat(t,"-enter-to"),enterActiveClass:"".concat(t,"-enter-active"),leaveClass:"".concat(t,"-leave"),leaveToClass:"".concat(t,"-leave-to"),leaveActiveClass:"".concat(t,"-leave-active")}})),ki=J&&!Z,Si="transition",Oi="animation",Ti="transition",Ai="transitionend",ji="animation",Ei="animationend";ki&&(void 0===window.ontransitionend&&void 0!==window.onwebkittransitionend&&(Ti="WebkitTransition",Ai="webkitTransitionEnd"),void 0===window.onanimationend&&void 0!==window.onwebkitanimationend&&(ji="WebkitAnimation",Ei="webkitAnimationEnd"));var Ni=J?window.requestAnimationFrame?window.requestAnimationFrame.bind(window):setTimeout:function(t){return t()};function Pi(t){Ni((function(){Ni(t)}))}function Di(t,e){var n=t._transitionClasses||(t._transitionClasses=[]);n.indexOf(e)<0&&(n.push(e),$i(t,e))}function Mi(t,e){t._transitionClasses&&g(t._transitionClasses,e),wi(t,e)}function Ii(t,e,n){var r=Ri(t,e),o=r.type,i=r.timeout,a=r.propCount;if(!o)return n();var s=o===Si?Ai:Ei,c=0,u=function(){t.removeEventListener(s,l),n()},l=function(e){e.target===t&&++c>=a&&u()};setTimeout((function(){c0&&(n=Si,l=a,f=i.length):e===Oi?u>0&&(n=Oi,l=u,f=c.length):f=(n=(l=Math.max(a,u))>0?a>u?Si:Oi:null)?n===Si?i.length:c.length:0,{type:n,timeout:l,propCount:f,hasTransform:n===Si&&Li.test(r[Ti+"Property"])}}function Fi(t,e){for(;t.length1}function Ki(t,e){!0!==e.data.show&&Bi(e)}var Ji=function(t){var a,s,c={},u=t.modules,l=t.nodeOps;for(a=0;av?b(t,n(o[g+1])?null:o[g+1].elm,o,p,g,i):p>g&&w(e,f,v)}(f,h,m,i,u):r(m)?(r(t.text)&&l.setTextContent(f,""),b(f,null,m,0,m.length-1,i)):r(h)?w(h,0,h.length-1):r(t.text)&&l.setTextContent(f,""):t.text!==e.text&&l.setTextContent(f,e.text),r(v)&&r(p=v.hook)&&r(p=p.postpatch)&&p(t,e)}}}function S(t,e,n){if(o(n)&&r(t.parent))t.parent.data.pendingInsert=e;else for(var i=0;i-1,a.selected!==i&&(a.selected=i);else if(P(Xi(a),r))return void(t.selectedIndex!==s&&(t.selectedIndex=s));o||(t.selectedIndex=-1)}}function Gi(t,e){return e.every((function(e){return!P(e,t)}))}function Xi(t){return"_value"in t?t._value:t.value}function Yi(t){t.target.composing=!0}function Qi(t){t.target.composing&&(t.target.composing=!1,ta(t.target,"input"))}function ta(t,e){var n=document.createEvent("HTMLEvents");n.initEvent(e,!0,!0),t.dispatchEvent(n)}function ea(t){return!t.componentInstance||t.data&&t.data.transition?t:ea(t.componentInstance._vnode)}var na={bind:function(t,e,n){var r=e.value,o=(n=ea(n)).data&&n.data.transition,i=t.__vOriginalDisplay="none"===t.style.display?"":t.style.display;r&&o?(n.data.show=!0,Bi(n,(function(){t.style.display=i}))):t.style.display=r?i:"none"},update:function(t,e,n){var r=e.value;!r!=!e.oldValue&&((n=ea(n)).data&&n.data.transition?(n.data.show=!0,r?Bi(n,(function(){t.style.display=t.__vOriginalDisplay})):Ui(n,(function(){t.style.display="none"}))):t.style.display=r?t.__vOriginalDisplay:"none")},unbind:function(t,e,n,r,o){o||(t.style.display=t.__vOriginalDisplay)}},ra={model:qi,show:na},oa={name:String,appear:Boolean,css:Boolean,mode:String,type:String,enterClass:String,leaveClass:String,enterToClass:String,leaveToClass:String,enterActiveClass:String,leaveActiveClass:String,appearClass:String,appearActiveClass:String,appearToClass:String,duration:[Number,String,Object]};function ia(t){var e=t&&t.componentOptions;return e&&e.Ctor.options.abstract?ia(Ee(e.children)):t}function aa(t){var e={},n=t.$options;for(var r in n.propsData)e[r]=t[r];var o=n._parentListeners;for(var r in o)e[w(r)]=o[r];return e}function sa(t,e){if(/\d-keep-alive$/.test(e.tag))return t("keep-alive",{props:e.componentOptions.propsData})}var ca=function(t){return t.tag||_e(t)},ua=function(t){return"show"===t.name},la={name:"transition",props:oa,abstract:!0,render:function(t){var e=this,n=this.$slots.default;if(n&&(n=n.filter(ca)).length){var r=this.mode,o=n[0];if(function(t){for(;t=t.parent;)if(t.data.transition)return!0}(this.$vnode))return o;var a=ia(o);if(!a)return o;if(this._leaving)return sa(t,o);var s="__transition-".concat(this._uid,"-");a.key=null==a.key?a.isComment?s+"comment":s+a.tag:i(a.key)?0===String(a.key).indexOf(s)?a.key:s+a.key:a.key;var c=(a.data||(a.data={})).transition=aa(this),u=this._vnode,l=ia(u);if(a.data.directives&&a.data.directives.some(ua)&&(a.data.show=!0),l&&l.data&&!function(t,e){return e.key===t.key&&e.tag===t.tag}(a,l)&&!_e(l)&&(!l.componentInstance||!l.componentInstance._vnode.isComment)){var f=l.data.transition=T({},c);if("out-in"===r)return this._leaving=!0,Zt(f,"afterLeave",(function(){e._leaving=!1,e.$forceUpdate()})),sa(t,o);if("in-out"===r){if(_e(a))return u;var d,p=function(){d()};Zt(c,"afterEnter",p),Zt(c,"enterCancelled",p),Zt(f,"delayLeave",(function(t){d=t}))}}return o}}},fa=T({tag:String,moveClass:String},oa);delete fa.mode;var da={props:fa,beforeMount:function(){var t=this,e=this._update;this._update=function(n,r){var o=Le(t);t.__patch__(t._vnode,t.kept,!1,!0),t._vnode=t.kept,o(),e.call(t,n,r)}},render:function(t){for(var e=this.tag||this.$vnode.data.tag||"span",n=Object.create(null),r=this.prevChildren=this.children,o=this.$slots.default||[],i=this.children=[],a=aa(this),s=0;s-1?Yr[t]=e.constructor===window.HTMLUnknownElement||e.constructor===window.HTMLElement:Yr[t]=/HTMLUnknownElement/.test(e.toString())},T(Cr.options.directives,ra),T(Cr.options.components,ma),Cr.prototype.__patch__=J?Ji:j,Cr.prototype.$mount=function(t,e){return function(t,e,n){var r;t.$el=e,t.$options.render||(t.$options.render=ft),Be(t,"beforeMount"),r=function(){t._update(t._render(),n)},new Vn(t,r,j,{before:function(){t._isMounted&&!t._isDestroyed&&Be(t,"beforeUpdate")}},!0),n=!1;var o=t._preWatchers;if(o)for(var i=0;i\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/,Ta=/^\s*((?:v-[\w-]+:|@|:|#)\[[^=]+?\][^\s"'<>\/=]*)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/,Aa="[a-zA-Z_][\\-\\.0-9_a-zA-Z".concat(B.source,"]*"),ja="((?:".concat(Aa,"\\:)?").concat(Aa,")"),Ea=new RegExp("^<".concat(ja)),Na=/^\s*(\/?)>/,Pa=new RegExp("^<\\/".concat(ja,"[^>]*>")),Da=/^]+>/i,Ma=/^",""":'"',"&":"&"," ":"\n"," ":"\t","'":"'"},Ha=/&(?:lt|gt|quot|amp|#39);/g,Ba=/&(?:lt|gt|quot|amp|#39|#10|#9);/g,Ua=v("pre,textarea",!0),za=function(t,e){return t&&Ua(t)&&"\n"===e[0]};function Va(t,e){var n=e?Ba:Ha;return t.replace(n,(function(t){return Fa[t]}))}function Ka(t,e){for(var n,r,o=[],i=e.expectHTML,a=e.isUnaryTag||E,s=e.canBeLeftOpenTag||E,c=0,u=function(){if(n=t,r&&La(r)){var u=0,d=r.toLowerCase(),p=Ra[d]||(Ra[d]=new RegExp("([\\s\\S]*?)(]*>)","i"));w=t.replace(p,(function(t,n,r){return u=r.length,La(d)||"noscript"===d||(n=n.replace(//g,"$1").replace(//g,"$1")),za(d,n)&&(n=n.slice(1)),e.chars&&e.chars(n),""}));c+=t.length-w.length,t=w,f(d,c-u,c)}else{var v=t.indexOf("<");if(0===v){if(Ma.test(t)){var h=t.indexOf("--\x3e");if(h>=0)return e.shouldKeepComment&&e.comment&&e.comment(t.substring(4,h),c,c+h+3),l(h+3),"continue"}if(Ia.test(t)){var m=t.indexOf("]>");if(m>=0)return l(m+2),"continue"}var g=t.match(Da);if(g)return l(g[0].length),"continue";var y=t.match(Pa);if(y){var _=c;return l(y[0].length),f(y[1],_,c),"continue"}var b=function(){var e=t.match(Ea);if(e){var n={tagName:e[1],attrs:[],start:c};l(e[0].length);for(var r=void 0,o=void 0;!(r=t.match(Na))&&(o=t.match(Ta)||t.match(Oa));)o.start=c,l(o[0].length),o.end=c,n.attrs.push(o);if(r)return n.unarySlash=r[1],l(r[0].length),n.end=c,n}}();if(b)return function(t){var n=t.tagName,c=t.unarySlash;i&&("p"===r&&Sa(n)&&f(r),s(n)&&r===n&&f(n));for(var u=a(n)||!!c,l=t.attrs.length,d=new Array(l),p=0;p=0){for(w=t.slice(v);!(Pa.test(w)||Ea.test(w)||Ma.test(w)||Ia.test(w)||(x=w.indexOf("<",1))<0);)v+=x,w=t.slice(v);$=t.substring(0,v)}v<0&&($=t),$&&l($.length),e.chars&&$&&e.chars($,c-$.length,c)}if(t===n)return e.chars&&e.chars(t),"break"};t;){if("break"===u())break}function l(e){c+=e,t=t.substring(e)}function f(t,n,i){var a,s;if(null==n&&(n=c),null==i&&(i=c),t)for(s=t.toLowerCase(),a=o.length-1;a>=0&&o[a].lowerCasedTag!==s;a--);else a=0;if(a>=0){for(var u=o.length-1;u>=a;u--)e.end&&e.end(o[u].tag,n,i);o.length=a,r=a&&o[a-1].tag}else"br"===s?e.start&&e.start(t,[],!0,n,i):"p"===s&&(e.start&&e.start(t,[],!1,n,i),e.end&&e.end(t,n,i))}f()}var Ja,qa,Wa,Za,Ga,Xa,Ya,Qa,ts=/^@|^v-on:/,es=/^v-|^@|^:|^#/,ns=/([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/,rs=/,([^,\}\]]*)(?:,([^,\}\]]*))?$/,os=/^\(|\)$/g,is=/^\[.*\]$/,as=/:(.*)$/,ss=/^:|^\.|^v-bind:/,cs=/\.[^.\]]+(?=[^\]]*$)/g,us=/^v-slot(:|$)|^#/,ls=/[\r\n]/,fs=/[ \f\t\r\n]+/g,ds=b(xa),ps="_empty_";function vs(t,e,n){return{type:1,tag:t,attrsList:e,attrsMap:$s(e),rawAttrsMap:{},parent:n,children:[]}}function hs(t,e){Ja=e.warn||No,Xa=e.isPreTag||E,Ya=e.mustUseProp||E,Qa=e.getTagNamespace||E,e.isReservedTag,Wa=Po(e.modules,"transformNode"),Za=Po(e.modules,"preTransformNode"),Ga=Po(e.modules,"postTransformNode"),qa=e.delimiters;var n,r,o=[],i=!1!==e.preserveWhitespace,a=e.whitespace,s=!1,c=!1;function u(t){if(l(t),s||t.processed||(t=ms(t,e)),o.length||t===n||n.if&&(t.elseif||t.else)&&ys(n,{exp:t.elseif,block:t}),r&&!t.forbidden)if(t.elseif||t.else)a=t,u=function(t){for(var e=t.length;e--;){if(1===t[e].type)return t[e];t.pop()}}(r.children),u&&u.if&&ys(u,{exp:a.elseif,block:a});else{if(t.slotScope){var i=t.slotTarget||'"default"';(r.scopedSlots||(r.scopedSlots={}))[i]=t}r.children.push(t),t.parent=r}var a,u;t.children=t.children.filter((function(t){return!t.slotScope})),l(t),t.pre&&(s=!1),Xa(t.tag)&&(c=!1);for(var f=0;fc&&(s.push(i=t.slice(c,o)),a.push(JSON.stringify(i)));var u=jo(r[1].trim());a.push("_s(".concat(u,")")),s.push({"@binding":u}),c=o+r[0].length}return c-1")+("true"===i?":(".concat(e,")"):":_q(".concat(e,",").concat(i,")"))),Fo(t,"change","var $$a=".concat(e,",")+"$$el=$event.target,"+"$$c=$$el.checked?(".concat(i,"):(").concat(a,");")+"if(Array.isArray($$a)){"+"var $$v=".concat(r?"_n("+o+")":o,",")+"$$i=_i($$a,$$v);"+"if($$el.checked){$$i<0&&(".concat(Ko(e,"$$a.concat([$$v])"),")}")+"else{$$i>-1&&(".concat(Ko(e,"$$a.slice(0,$$i).concat($$a.slice($$i+1))"),")}")+"}else{".concat(Ko(e,"$$c"),"}"),null,!0)}(t,r,o);else if("input"===i&&"radio"===a)!function(t,e,n){var r=n&&n.number,o=Ho(t,"value")||"null";o=r?"_n(".concat(o,")"):o,Do(t,"checked","_q(".concat(e,",").concat(o,")")),Fo(t,"change",Ko(e,o),null,!0)}(t,r,o);else if("input"===i||"textarea"===i)!function(t,e,n){var r=t.attrsMap.type,o=n||{},i=o.lazy,a=o.number,s=o.trim,c=!i&&"range"!==r,u=i?"change":"range"===r?Yo:"input",l="$event.target.value";s&&(l="$event.target.value.trim()");a&&(l="_n(".concat(l,")"));var f=Ko(e,l);c&&(f="if($event.target.composing)return;".concat(f));Do(t,"value","(".concat(e,")")),Fo(t,u,f,null,!0),(s||a)&&Fo(t,"blur","$forceUpdate()")}(t,r,o);else if(!H.isReservedTag(i))return Vo(t,r,o),!1;return!0},text:function(t,e){e.value&&Do(t,"textContent","_s(".concat(e.value,")"),e)},html:function(t,e){e.value&&Do(t,"innerHTML","_s(".concat(e.value,")"),e)}},As={expectHTML:!0,modules:ks,directives:Ts,isPreTag:function(t){return"pre"===t},isUnaryTag:Ca,mustUseProp:Mr,canBeLeftOpenTag:ka,isReservedTag:Gr,getTagNamespace:Xr,staticKeys:function(t){return t.reduce((function(t,e){return t.concat(e.staticKeys||[])}),[]).join(",")}(ks)},js=b((function(t){return v("type,tag,attrsList,attrsMap,plain,parent,children,attrs,start,end,rawAttrsMap"+(t?","+t:""))}));function Es(t,e){t&&(Ss=js(e.staticKeys||""),Os=e.isReservedTag||E,Ns(t),Ps(t,!1))}function Ns(t){if(t.static=function(t){if(2===t.type)return!1;if(3===t.type)return!0;return!(!t.pre&&(t.hasBindings||t.if||t.for||h(t.tag)||!Os(t.tag)||function(t){for(;t.parent;){if("template"!==(t=t.parent).tag)return!1;if(t.for)return!0}return!1}(t)||!Object.keys(t).every(Ss)))}(t),1===t.type){if(!Os(t.tag)&&"slot"!==t.tag&&null==t.attrsMap["inline-template"])return;for(var e=0,n=t.children.length;e|^function(?:\s+[\w$]+)?\s*\(/,Ms=/\([^)]*?\);*$/,Is=/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/,Ls={esc:27,tab:9,enter:13,space:32,up:38,left:37,right:39,down:40,delete:[8,46]},Rs={esc:["Esc","Escape"],tab:"Tab",enter:"Enter",space:[" ","Spacebar"],up:["Up","ArrowUp"],left:["Left","ArrowLeft"],right:["Right","ArrowRight"],down:["Down","ArrowDown"],delete:["Backspace","Delete","Del"]},Fs=function(t){return"if(".concat(t,")return null;")},Hs={stop:"$event.stopPropagation();",prevent:"$event.preventDefault();",self:Fs("$event.target !== $event.currentTarget"),ctrl:Fs("!$event.ctrlKey"),shift:Fs("!$event.shiftKey"),alt:Fs("!$event.altKey"),meta:Fs("!$event.metaKey"),left:Fs("'button' in $event && $event.button !== 0"),middle:Fs("'button' in $event && $event.button !== 1"),right:Fs("'button' in $event && $event.button !== 2")};function Bs(t,e){var n=e?"nativeOn:":"on:",r="",o="";for(var i in t){var a=Us(t[i]);t[i]&&t[i].dynamic?o+="".concat(i,",").concat(a,","):r+='"'.concat(i,'":').concat(a,",")}return r="{".concat(r.slice(0,-1),"}"),o?n+"_d(".concat(r,",[").concat(o.slice(0,-1),"])"):n+r}function Us(t){if(!t)return"function(){}";if(Array.isArray(t))return"[".concat(t.map((function(t){return Us(t)})).join(","),"]");var e=Is.test(t.value),n=Ds.test(t.value),r=Is.test(t.value.replace(Ms,""));if(t.modifiers){var o="",i="",a=[],s=function(e){if(Hs[e])i+=Hs[e],Ls[e]&&a.push(e);else if("exact"===e){var n=t.modifiers;i+=Fs(["ctrl","shift","alt","meta"].filter((function(t){return!n[t]})).map((function(t){return"$event.".concat(t,"Key")})).join("||"))}else a.push(e)};for(var c in t.modifiers)s(c);a.length&&(o+=function(t){return"if(!$event.type.indexOf('key')&&"+"".concat(t.map(zs).join("&&"),")return null;")}(a)),i&&(o+=i);var u=e?"return ".concat(t.value,".apply(null, arguments)"):n?"return (".concat(t.value,").apply(null, arguments)"):r?"return ".concat(t.value):t.value;return"function($event){".concat(o).concat(u,"}")}return e||n?t.value:"function($event){".concat(r?"return ".concat(t.value):t.value,"}")}function zs(t){var e=parseInt(t,10);if(e)return"$event.keyCode!==".concat(e);var n=Ls[t],r=Rs[t];return"_k($event.keyCode,"+"".concat(JSON.stringify(t),",")+"".concat(JSON.stringify(n),",")+"$event.key,"+"".concat(JSON.stringify(r))+")"}var Vs={on:function(t,e){t.wrapListeners=function(t){return"_g(".concat(t,",").concat(e.value,")")}},bind:function(t,e){t.wrapData=function(n){return"_b(".concat(n,",'").concat(t.tag,"',").concat(e.value,",").concat(e.modifiers&&e.modifiers.prop?"true":"false").concat(e.modifiers&&e.modifiers.sync?",true":"",")")}},cloak:j},Ks=function(t){this.options=t,this.warn=t.warn||No,this.transforms=Po(t.modules,"transformCode"),this.dataGenFns=Po(t.modules,"genData"),this.directives=T(T({},Vs),t.directives);var e=t.isReservedTag||E;this.maybeComponent=function(t){return!!t.component||!e(t.tag)},this.onceId=0,this.staticRenderFns=[],this.pre=!1};function Js(t,e){var n=new Ks(e),r=t?"script"===t.tag?"null":qs(t,n):'_c("div")';return{render:"with(this){return ".concat(r,"}"),staticRenderFns:n.staticRenderFns}}function qs(t,e){if(t.parent&&(t.pre=t.pre||t.parent.pre),t.staticRoot&&!t.staticProcessed)return Ws(t,e);if(t.once&&!t.onceProcessed)return Zs(t,e);if(t.for&&!t.forProcessed)return Ys(t,e);if(t.if&&!t.ifProcessed)return Gs(t,e);if("template"!==t.tag||t.slotTarget||e.pre){if("slot"===t.tag)return function(t,e){var n=t.slotName||'"default"',r=nc(t,e),o="_t(".concat(n).concat(r?",function(){return ".concat(r,"}"):""),i=t.attrs||t.dynamicAttrs?ic((t.attrs||[]).concat(t.dynamicAttrs||[]).map((function(t){return{name:w(t.name),value:t.value,dynamic:t.dynamic}}))):null,a=t.attrsMap["v-bind"];!i&&!a||r||(o+=",null");i&&(o+=",".concat(i));a&&(o+="".concat(i?"":",null",",").concat(a));return o+")"}(t,e);var n=void 0;if(t.component)n=function(t,e,n){var r=e.inlineTemplate?null:nc(e,n,!0);return"_c(".concat(t,",").concat(Qs(e,n)).concat(r?",".concat(r):"",")")}(t.component,t,e);else{var r=void 0,o=e.maybeComponent(t);(!t.plain||t.pre&&o)&&(r=Qs(t,e));var i=void 0,a=e.options.bindings;o&&a&&!1!==a.__isScriptSetup&&(i=function(t,e){var n=w(e),r=x(n),o=function(o){return t[e]===o?e:t[n]===o?n:t[r]===o?r:void 0},i=o("setup-const")||o("setup-reactive-const");if(i)return i;var a=o("setup-let")||o("setup-ref")||o("setup-maybe-ref");if(a)return a}(a,t.tag)),i||(i="'".concat(t.tag,"'"));var s=t.inlineTemplate?null:nc(t,e,!0);n="_c(".concat(i).concat(r?",".concat(r):"").concat(s?",".concat(s):"",")")}for(var c=0;c>>0}(a)):"",")")}(t,t.scopedSlots,e),",")),t.model&&(n+="model:{value:".concat(t.model.value,",callback:").concat(t.model.callback,",expression:").concat(t.model.expression,"},")),t.inlineTemplate){var i=function(t,e){var n=t.children[0];if(n&&1===n.type){var r=Js(n,e.options);return"inlineTemplate:{render:function(){".concat(r.render,"},staticRenderFns:[").concat(r.staticRenderFns.map((function(t){return"function(){".concat(t,"}")})).join(","),"]}")}}(t,e);i&&(n+="".concat(i,","))}return n=n.replace(/,$/,"")+"}",t.dynamicAttrs&&(n="_b(".concat(n,',"').concat(t.tag,'",').concat(ic(t.dynamicAttrs),")")),t.wrapData&&(n=t.wrapData(n)),t.wrapListeners&&(n=t.wrapListeners(n)),n}function tc(t){return 1===t.type&&("slot"===t.tag||t.children.some(tc))}function ec(t,e){var n=t.attrsMap["slot-scope"];if(t.if&&!t.ifProcessed&&!n)return Gs(t,e,ec,"null");if(t.for&&!t.forProcessed)return Ys(t,e,ec);var r=t.slotScope===ps?"":String(t.slotScope),o="function(".concat(r,"){")+"return ".concat("template"===t.tag?t.if&&n?"(".concat(t.if,")?").concat(nc(t,e)||"undefined",":undefined"):nc(t,e)||"undefined":qs(t,e),"}"),i=r?"":",proxy:true";return"{key:".concat(t.slotTarget||'"default"',",fn:").concat(o).concat(i,"}")}function nc(t,e,n,r,o){var i=t.children;if(i.length){var a=i[0];if(1===i.length&&a.for&&"template"!==a.tag&&"slot"!==a.tag){var s=n?e.maybeComponent(a)?",1":",0":"";return"".concat((r||qs)(a,e)).concat(s)}var c=n?function(t,e){for(var n=0,r=0;r':'

      ',lc.innerHTML.indexOf(" ")>0}var vc=!!J&&pc(!1),hc=!!J&&pc(!0),mc=b((function(t){var e=to(t);return e&&e.innerHTML})),gc=Cr.prototype.$mount;return Cr.prototype.$mount=function(t,e){if((t=t&&to(t))===document.body||t===document.documentElement)return this;var n=this.$options;if(!n.render){var r=n.template;if(r)if("string"==typeof r)"#"===r.charAt(0)&&(r=mc(r));else{if(!r.nodeType)return this;r=r.innerHTML}else t&&(r=function(t){if(t.outerHTML)return t.outerHTML;var e=document.createElement("div");return e.appendChild(t.cloneNode(!0)),e.innerHTML}(t));if(r){var o=dc(r,{outputSourceRange:!1,shouldDecodeNewlines:vc,shouldDecodeNewlinesForHref:hc,delimiters:n.delimiters,comments:n.comments},this),i=o.render,a=o.staticRenderFns;n.render=i,n.staticRenderFns=a}}return gc.call(this,t,e)},Cr.compile=dc,T(Cr,Fn),Cr.effect=function(t,e){var n=new Vn(ct,t,j,{sync:!0});e&&(n.update=function(){e((function(){return n.run()}))})},Cr})); \ No newline at end of file diff --git a/kirby/panel/dist/js/vue.min.js b/kirby/panel/dist/js/vue.min.js new file mode 100644 index 0000000..5c1c9bf --- /dev/null +++ b/kirby/panel/dist/js/vue.min.js @@ -0,0 +1,11 @@ +/*! + * Vue.js v2.7.15 + * (c) 2014-2023 Evan You + * Released under the MIT License. + */ +/*! + * Vue.js v2.7.15 + * (c) 2014-2023 Evan You + * Released under the MIT License. + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).Vue=e()}(this,(function(){"use strict";var t=Object.freeze({}),e=Array.isArray;function n(t){return null==t}function r(t){return null!=t}function o(t){return!0===t}function i(t){return"string"==typeof t||"number"==typeof t||"symbol"==typeof t||"boolean"==typeof t}function a(t){return"function"==typeof t}function s(t){return null!==t&&"object"==typeof t}var c=Object.prototype.toString;function u(t){return"[object Object]"===c.call(t)}function l(t){var e=parseFloat(String(t));return e>=0&&Math.floor(e)===e&&isFinite(t)}function f(t){return r(t)&&"function"==typeof t.then&&"function"==typeof t.catch}function d(t){return null==t?"":Array.isArray(t)||u(t)&&t.toString===c?JSON.stringify(t,null,2):String(t)}function p(t){var e=parseFloat(t);return isNaN(e)?t:e}function v(t,e){for(var n=Object.create(null),r=t.split(","),o=0;o-1)return t.splice(r,1)}}var y=Object.prototype.hasOwnProperty;function _(t,e){return y.call(t,e)}function b(t){var e=Object.create(null);return function(n){return e[n]||(e[n]=t(n))}}var $=/-(\w)/g,w=b((function(t){return t.replace($,(function(t,e){return e?e.toUpperCase():""}))})),x=b((function(t){return t.charAt(0).toUpperCase()+t.slice(1)})),C=/\B([A-Z])/g,k=b((function(t){return t.replace(C,"-$1").toLowerCase()}));var S=Function.prototype.bind?function(t,e){return t.bind(e)}:function(t,e){function n(n){var r=arguments.length;return r?r>1?t.apply(e,arguments):t.call(e,n):t.call(e)}return n._length=t.length,n};function O(t,e){e=e||0;for(var n=t.length-e,r=new Array(n);n--;)r[n]=t[n+e];return r}function T(t,e){for(var n in e)t[n]=e[n];return t}function A(t){for(var e={},n=0;n0,G=q&&q.indexOf("edge/")>0;q&&q.indexOf("android");var X=q&&/iphone|ipad|ipod|ios/.test(q);q&&/chrome\/\d+/.test(q),q&&/phantomjs/.test(q);var Y,Q=q&&q.match(/firefox\/(\d+)/),tt={}.watch,et=!1;if(J)try{var nt={};Object.defineProperty(nt,"passive",{get:function(){et=!0}}),window.addEventListener("test-passive",null,nt)}catch(t){}var rt=function(){return void 0===Y&&(Y=!J&&"undefined"!=typeof global&&(global.process&&"server"===global.process.env.VUE_ENV)),Y},ot=J&&window.__VUE_DEVTOOLS_GLOBAL_HOOK__;function it(t){return"function"==typeof t&&/native code/.test(t.toString())}var at,st="undefined"!=typeof Symbol&&it(Symbol)&&"undefined"!=typeof Reflect&&it(Reflect.ownKeys);at="undefined"!=typeof Set&&it(Set)?Set:function(){function t(){this.set=Object.create(null)}return t.prototype.has=function(t){return!0===this.set[t]},t.prototype.add=function(t){this.set[t]=!0},t.prototype.clear=function(){this.set=Object.create(null)},t}();var ct=null;function ut(t){void 0===t&&(t=null),t||ct&&ct._scope.off(),ct=t,t&&t._scope.on()}var lt=function(){function t(t,e,n,r,o,i,a,s){this.tag=t,this.data=e,this.children=n,this.text=r,this.elm=o,this.ns=void 0,this.context=i,this.fnContext=void 0,this.fnOptions=void 0,this.fnScopeId=void 0,this.key=e&&e.key,this.componentOptions=a,this.componentInstance=void 0,this.parent=void 0,this.raw=!1,this.isStatic=!1,this.isRootInsert=!0,this.isComment=!1,this.isCloned=!1,this.isOnce=!1,this.asyncFactory=s,this.asyncMeta=void 0,this.isAsyncPlaceholder=!1}return Object.defineProperty(t.prototype,"child",{get:function(){return this.componentInstance},enumerable:!1,configurable:!0}),t}(),ft=function(t){void 0===t&&(t="");var e=new lt;return e.text=t,e.isComment=!0,e};function dt(t){return new lt(void 0,void 0,void 0,String(t))}function pt(t){var e=new lt(t.tag,t.data,t.children&&t.children.slice(),t.text,t.elm,t.context,t.componentOptions,t.asyncFactory);return e.ns=t.ns,e.isStatic=t.isStatic,e.key=t.key,e.isComment=t.isComment,e.fnContext=t.fnContext,e.fnOptions=t.fnOptions,e.fnScopeId=t.fnScopeId,e.asyncMeta=t.asyncMeta,e.isCloned=!0,e}var vt=0,ht=[],mt=function(){function t(){this._pending=!1,this.id=vt++,this.subs=[]}return t.prototype.addSub=function(t){this.subs.push(t)},t.prototype.removeSub=function(t){this.subs[this.subs.indexOf(t)]=null,this._pending||(this._pending=!0,ht.push(this))},t.prototype.depend=function(e){t.target&&t.target.addDep(this)},t.prototype.notify=function(t){for(var e=this.subs.filter((function(t){return t})),n=0,r=e.length;n0&&(Yt((c=Qt(c,"".concat(a||"","_").concat(s)))[0])&&Yt(l)&&(f[u]=dt(l.text+c[0].text),c.shift()),f.push.apply(f,c)):i(c)?Yt(l)?f[u]=dt(l.text+c):""!==c&&f.push(dt(c)):Yt(c)&&Yt(l)?f[u]=dt(l.text+c.text):(o(t._isVList)&&r(c.tag)&&n(c.key)&&r(a)&&(c.key="__vlist".concat(a,"_").concat(s,"__")),f.push(c)));return f}function te(t,n,c,u,l,f){return(e(c)||i(c))&&(l=u,u=c,c=void 0),o(f)&&(l=2),function(t,n,o,i,c){if(r(o)&&r(o.__ob__))return ft();r(o)&&r(o.is)&&(n=o.is);if(!n)return ft();e(i)&&a(i[0])&&((o=o||{}).scopedSlots={default:i[0]},i.length=0);2===c?i=Xt(i):1===c&&(i=function(t){for(var n=0;n0,s=n?!!n.$stable:!a,c=n&&n.$key;if(n){if(n._normalized)return n._normalized;if(s&&o&&o!==t&&c===o.$key&&!a&&!o.$hasNormal)return o;for(var u in i={},n)n[u]&&"$"!==u[0]&&(i[u]=$e(e,r,u,n[u]))}else i={};for(var l in r)l in i||(i[l]=we(r,l));return n&&Object.isExtensible(n)&&(n._normalized=i),z(i,"$stable",s),z(i,"$key",c),z(i,"$hasNormal",a),i}function $e(t,n,r,o){var i=function(){var n=ct;ut(t);var r=arguments.length?o.apply(null,arguments):o({}),i=(r=r&&"object"==typeof r&&!e(r)?[r]:Xt(r))&&r[0];return ut(n),r&&(!i||1===r.length&&i.isComment&&!_e(i))?void 0:r};return o.proxy&&Object.defineProperty(n,r,{get:i,enumerable:!0,configurable:!0}),i}function we(t,e){return function(){return t[e]}}function xe(e){return{get attrs(){if(!e._attrsProxy){var n=e._attrsProxy={};z(n,"_v_attr_proxy",!0),Ce(n,e.$attrs,t,e,"$attrs")}return e._attrsProxy},get listeners(){e._listenersProxy||Ce(e._listenersProxy={},e.$listeners,t,e,"$listeners");return e._listenersProxy},get slots(){return function(t){t._slotsProxy||Se(t._slotsProxy={},t.$scopedSlots);return t._slotsProxy}(e)},emit:S(e.$emit,e),expose:function(t){t&&Object.keys(t).forEach((function(n){return Bt(e,t,n)}))}}}function Ce(t,e,n,r,o){var i=!1;for(var a in e)a in t?e[a]!==n[a]&&(i=!0):(i=!0,ke(t,a,r,o));for(var a in t)a in e||(i=!0,delete t[a]);return i}function ke(t,e,n,r){Object.defineProperty(t,e,{enumerable:!0,configurable:!0,get:function(){return n[r][e]}})}function Se(t,e){for(var n in e)t[n]=e[n];for(var n in t)n in e||delete t[n]}function Oe(){var t=ct;return t._setupContext||(t._setupContext=xe(t))}var Te,Ae,je=null;function Ee(t,e){return(t.__esModule||st&&"Module"===t[Symbol.toStringTag])&&(t=t.default),s(t)?e.extend(t):t}function Ne(t){if(e(t))for(var n=0;ndocument.createEvent("Event").timeStamp&&(Ye=function(){return Qe.now()})}var tn=function(t,e){if(t.post){if(!e.post)return 1}else if(e.post)return-1;return t.id-e.id};function en(){var t,e;for(Xe=Ye(),Ze=!0,Ke.sort(tn),Ge=0;GeGe&&Ke[n].id>t.id;)n--;Ke.splice(n+1,0,t)}else Ke.push(t);We||(We=!0,kn(en))}}var rn="watcher",on="".concat(rn," callback"),an="".concat(rn," getter"),sn="".concat(rn," cleanup");function cn(t,e){return ln(t,null,{flush:"post"})}var un={};function ln(n,r,o){var i=void 0===o?t:o,s=i.immediate,c=i.deep,u=i.flush,l=void 0===u?"pre":u;i.onTrack,i.onTrigger;var f,d,p=ct,v=function(t,e,n){return void 0===n&&(n=null),pn(t,null,n,p,e)},h=!1,m=!1;if(Ft(n)?(f=function(){return n.value},h=It(n)):Mt(n)?(f=function(){return n.__ob__.dep.depend(),n},c=!0):e(n)?(m=!0,h=n.some((function(t){return Mt(t)||It(t)})),f=function(){return n.map((function(t){return Ft(t)?t.value:Mt(t)?Un(t):a(t)?v(t,an):void 0}))}):f=a(n)?r?function(){return v(n,an)}:function(){if(!p||!p._isDestroyed)return d&&d(),v(n,rn,[y])}:j,r&&c){var g=f;f=function(){return Un(g())}}var y=function(t){d=_.onStop=function(){v(t,sn)}};if(rt())return y=j,r?s&&v(r,on,[f(),m?[]:void 0,y]):f(),j;var _=new Kn(ct,f,j,{lazy:!0});_.noRecurse=!r;var b=m?[]:un;return _.run=function(){if(_.active)if(r){var t=_.get();(c||h||(m?t.some((function(t,e){return I(t,b[e])})):I(t,b)))&&(d&&d(),v(r,on,[t,b===un?void 0:b,y]),b=t)}else _.get()},"sync"===l?_.update=_.run:"post"===l?(_.post=!0,_.update=function(){return nn(_)}):_.update=function(){if(p&&p===ct&&!p._isMounted){var t=p._preWatchers||(p._preWatchers=[]);t.indexOf(_)<0&&t.push(_)}else nn(_)},r?s?_.run():b=_.get():"post"===l&&p?p.$once("hook:mounted",(function(){return _.get()})):_.get(),function(){_.teardown()}}function fn(t){var e=t._provided,n=t.$parent&&t.$parent._provided;return n===e?t._provided=Object.create(n):e}function dn(t,e,n){yt();try{if(e)for(var r=e;r=r.$parent;){var o=r.$options.errorCaptured;if(o)for(var i=0;i1)return n&&a(e)?e.call(r):e}},h:function(t,e,n){return te(ct,t,e,n,2,!0)},getCurrentInstance:function(){return ct&&{proxy:ct}},useSlots:function(){return Oe().slots},useAttrs:function(){return Oe().attrs},useListeners:function(){return Oe().listeners},mergeDefaults:function(t,n){var r=e(t)?t.reduce((function(t,e){return t[e]={},t}),{}):t;for(var o in n){var i=r[o];i?e(i)||a(i)?r[o]={type:i,default:n[o]}:i.default=n[o]:null===i&&(r[o]={default:n[o]})}return r},nextTick:kn,set:jt,del:Et,useCssModule:function(e){return t},useCssVars:function(t){if(J){var e=ct;e&&cn((function(){var n=e.$el,r=t(e,e._setupProxy);if(n&&1===n.nodeType){var o=n.style;for(var i in r)o.setProperty("--".concat(i),r[i])}}))}},defineAsyncComponent:function(t){a(t)&&(t={loader:t});var e=t.loader,n=t.loadingComponent,r=t.errorComponent,o=t.delay,i=void 0===o?200:o,s=t.timeout;t.suspensible;var c=t.onError,u=null,l=0,f=function(){var t;return u||(t=u=e().catch((function(t){if(t=t instanceof Error?t:new Error(String(t)),c)return new Promise((function(e,n){c(t,(function(){return e((l++,u=null,f()))}),(function(){return n(t)}),l+1)}));throw t})).then((function(e){return t!==u&&u?u:(e&&(e.__esModule||"Module"===e[Symbol.toStringTag])&&(e=e.default),e)})))};return function(){return{component:f(),delay:i,timeout:s,error:r,loading:n}}},onBeforeMount:On,onMounted:Tn,onBeforeUpdate:An,onUpdated:jn,onBeforeUnmount:En,onUnmounted:Nn,onActivated:Pn,onDeactivated:Dn,onServerPrefetch:Mn,onRenderTracked:In,onRenderTriggered:Ln,onErrorCaptured:function(t,e){void 0===e&&(e=ct),Rn(t,e)}}),Bn=new at;function Un(t){return zn(t,Bn),Bn.clear(),t}function zn(t,n){var r,o,i=e(t);if(!(!i&&!s(t)||t.__v_skip||Object.isFrozen(t)||t instanceof lt)){if(t.__ob__){var a=t.__ob__.dep.id;if(n.has(a))return;n.add(a)}if(i)for(r=t.length;r--;)zn(t[r],n);else if(Ft(t))zn(t.value,n);else for(r=(o=Object.keys(t)).length;r--;)zn(t[o[r]],n)}}var Vn=0,Kn=function(){function t(t,e,n,r,o){!function(t,e){void 0===e&&(e=Ae),e&&e.active&&e.effects.push(t)}(this,Ae&&!Ae._vm?Ae:t?t._scope:void 0),(this.vm=t)&&o&&(t._watcher=this),r?(this.deep=!!r.deep,this.user=!!r.user,this.lazy=!!r.lazy,this.sync=!!r.sync,this.before=r.before):this.deep=this.user=this.lazy=this.sync=!1,this.cb=n,this.id=++Vn,this.active=!0,this.post=!1,this.dirty=this.lazy,this.deps=[],this.newDeps=[],this.depIds=new at,this.newDepIds=new at,this.expression="",a(e)?this.getter=e:(this.getter=function(t){if(!V.test(t)){var e=t.split(".");return function(t){for(var n=0;n-1)if(i&&!_(o,"default"))s=!1;else if(""===s||s===k(t)){var u=Cr(String,o.type);(u<0||c-1:"string"==typeof t?t.split(",").indexOf(n)>-1:(r=t,"[object RegExp]"===c.call(r)&&t.test(n));var r}function Ar(t,e){var n=t.cache,r=t.keys,o=t._vnode;for(var i in n){var a=n[i];if(a){var s=a.name;s&&!e(s)&&jr(n,i,r,o)}}}function jr(t,e,n,r){var o=t[e];!o||r&&o.tag===r.tag||o.componentInstance.$destroy(),t[e]=null,g(n,e)}!function(e){e.prototype._init=function(e){var n=this;n._uid=er++,n._isVue=!0,n.__v_skip=!0,n._scope=new Le(!0),n._scope._vm=!0,e&&e._isComponent?function(t,e){var n=t.$options=Object.create(t.constructor.options),r=e._parentVnode;n.parent=e.parent,n._parentVnode=r;var o=r.componentOptions;n.propsData=o.propsData,n._parentListeners=o.listeners,n._renderChildren=o.children,n._componentTag=o.tag,e.render&&(n.render=e.render,n.staticRenderFns=e.staticRenderFns)}(n,e):n.$options=yr(nr(n.constructor),e||{},n),n._renderProxy=n,n._self=n,function(t){var e=t.$options,n=e.parent;if(n&&!e.abstract){for(;n.$options.abstract&&n.$parent;)n=n.$parent;n.$children.push(t)}t.$parent=n,t.$root=n?n.$root:t,t.$children=[],t.$refs={},t._provided=n?n._provided:Object.create(null),t._watcher=null,t._inactive=null,t._directInactive=!1,t._isMounted=!1,t._isDestroyed=!1,t._isBeingDestroyed=!1}(n),function(t){t._events=Object.create(null),t._hasHookEvent=!1;var e=t.$options._parentListeners;e&&Ie(t,e)}(n),function(e){e._vnode=null,e._staticTrees=null;var n=e.$options,r=e.$vnode=n._parentVnode,o=r&&r.context;e.$slots=ge(n._renderChildren,o),e.$scopedSlots=r?be(e.$parent,r.data.scopedSlots,e.$slots):t,e._c=function(t,n,r,o){return te(e,t,n,r,o,!1)},e.$createElement=function(t,n,r,o){return te(e,t,n,r,o,!0)};var i=r&&r.data;At(e,"$attrs",i&&i.attrs||t,null,!0),At(e,"$listeners",n._parentListeners||t,null,!0)}(n),Ve(n,"beforeCreate",void 0,!1),function(t){var e=tr(t.$options.inject,t);e&&(kt(!1),Object.keys(e).forEach((function(n){At(t,n,e[n])})),kt(!0))}(n),Wn(n),function(t){var e=t.$options.provide;if(e){var n=a(e)?e.call(t):e;if(!s(n))return;for(var r=fn(t),o=st?Reflect.ownKeys(n):Object.keys(n),i=0;i1?O(n):n;for(var r=O(arguments,1),o='event handler for "'.concat(t,'"'),i=0,a=n.length;iparseInt(this.max)&&jr(e,n[0],n,this._vnode),this.vnodeToCache=null}}},created:function(){this.cache=Object.create(null),this.keys=[]},destroyed:function(){for(var t in this.cache)jr(this.cache,t,this.keys)},mounted:function(){var t=this;this.cacheVNode(),this.$watch("include",(function(e){Ar(t,(function(t){return Tr(e,t)}))})),this.$watch("exclude",(function(e){Ar(t,(function(t){return!Tr(e,t)}))}))},updated:function(){this.cacheVNode()},render:function(){var t=this.$slots.default,e=Ne(t),n=e&&e.componentOptions;if(n){var r=Or(n),o=this.include,i=this.exclude;if(o&&(!r||!Tr(o,r))||i&&r&&Tr(i,r))return e;var a=this.cache,s=this.keys,c=null==e.key?n.Ctor.cid+(n.tag?"::".concat(n.tag):""):e.key;a[c]?(e.componentInstance=a[c].componentInstance,g(s,c),s.push(c)):(this.vnodeToCache=e,this.keyToCache=c),e.data.keepAlive=!0}return e||t&&t[0]}},Pr={KeepAlive:Nr};!function(t){var e={get:function(){return H}};Object.defineProperty(t,"config",e),t.util={warn:fr,extend:T,mergeOptions:yr,defineReactive:At},t.set=jt,t.delete=Et,t.nextTick=kn,t.observable=function(t){return Tt(t),t},t.options=Object.create(null),R.forEach((function(e){t.options[e+"s"]=Object.create(null)})),t.options._base=t,T(t.options.components,Pr),function(t){t.use=function(t){var e=this._installedPlugins||(this._installedPlugins=[]);if(e.indexOf(t)>-1)return this;var n=O(arguments,1);return n.unshift(this),a(t.install)?t.install.apply(t,n):a(t)&&t.apply(null,n),e.push(t),this}}(t),function(t){t.mixin=function(t){return this.options=yr(this.options,t),this}}(t),Sr(t),function(t){R.forEach((function(e){t[e]=function(t,n){return n?("component"===e&&u(n)&&(n.name=n.name||t,n=this.options._base.extend(n)),"directive"===e&&a(n)&&(n={bind:n,update:n}),this.options[e+"s"][t]=n,n):this.options[e+"s"][t]}}))}(t)}(kr),Object.defineProperty(kr.prototype,"$isServer",{get:rt}),Object.defineProperty(kr.prototype,"$ssrContext",{get:function(){return this.$vnode&&this.$vnode.ssrContext}}),Object.defineProperty(kr,"FunctionalRenderContext",{value:rr}),kr.version=Fn;var Dr=v("style,class"),Mr=v("input,textarea,option,select,progress"),Ir=function(t,e,n){return"value"===n&&Mr(t)&&"button"!==e||"selected"===n&&"option"===t||"checked"===n&&"input"===t||"muted"===n&&"video"===t},Lr=v("contenteditable,draggable,spellcheck"),Rr=v("events,caret,typing,plaintext-only"),Fr=v("allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,defaultchecked,defaultmuted,defaultselected,defer,disabled,enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,truespeed,typemustmatch,visible"),Hr="http://www.w3.org/1999/xlink",Br=function(t){return":"===t.charAt(5)&&"xlink"===t.slice(0,5)},Ur=function(t){return Br(t)?t.slice(6,t.length):""},zr=function(t){return null==t||!1===t};function Vr(t){for(var e=t.data,n=t,o=t;r(o.componentInstance);)(o=o.componentInstance._vnode)&&o.data&&(e=Kr(o.data,e));for(;r(n=n.parent);)n&&n.data&&(e=Kr(e,n.data));return function(t,e){if(r(t)||r(e))return Jr(t,qr(e));return""}(e.staticClass,e.class)}function Kr(t,e){return{staticClass:Jr(t.staticClass,e.staticClass),class:r(t.class)?[t.class,e.class]:e.class}}function Jr(t,e){return t?e?t+" "+e:t:e||""}function qr(t){return Array.isArray(t)?function(t){for(var e,n="",o=0,i=t.length;o-1?bo(t,e,n):Fr(e)?zr(n)?t.removeAttribute(e):(n="allowfullscreen"===e&&"EMBED"===t.tagName?"true":e,t.setAttribute(e,n)):Lr(e)?t.setAttribute(e,function(t,e){return zr(e)||"false"===e?"false":"contenteditable"===t&&Rr(e)?e:"true"}(e,n)):Br(e)?zr(n)?t.removeAttributeNS(Hr,Ur(e)):t.setAttributeNS(Hr,e,n):bo(t,e,n)}function bo(t,e,n){if(zr(n))t.removeAttribute(e);else{if(W&&!Z&&"TEXTAREA"===t.tagName&&"placeholder"===e&&""!==n&&!t.__ieph){var r=function(e){e.stopImmediatePropagation(),t.removeEventListener("input",r)};t.addEventListener("input",r),t.__ieph=!0}t.setAttribute(e,n)}}var $o={create:yo,update:yo};function wo(t,e){var o=e.elm,i=e.data,a=t.data;if(!(n(i.staticClass)&&n(i.class)&&(n(a)||n(a.staticClass)&&n(a.class)))){var s=Vr(e),c=o._transitionClasses;r(c)&&(s=Jr(s,qr(c))),s!==o._prevClass&&(o.setAttribute("class",s),o._prevClass=s)}}var xo,Co,ko,So,Oo,To,Ao={create:wo,update:wo},jo=/[\w).+\-_$\]]/;function Eo(t){var e,n,r,o,i,a=!1,s=!1,c=!1,u=!1,l=0,f=0,d=0,p=0;for(r=0;r=0&&" "===(h=t.charAt(v));v--);h&&jo.test(h)||(u=!0)}}else void 0===o?(p=r+1,o=t.slice(0,r).trim()):m();function m(){(i||(i=[])).push(t.slice(p,r).trim()),p=r+1}if(void 0===o?o=t.slice(0,r).trim():0!==p&&m(),i)for(r=0;r-1?{exp:t.slice(0,So),key:'"'+t.slice(So+1)+'"'}:{exp:t,key:null};Co=t,So=Oo=To=0;for(;!Wo();)Zo(ko=qo())?Xo(ko):91===ko&&Go(ko);return{exp:t.slice(0,Oo),key:t.slice(Oo+1,To)}}(t);return null===n.key?"".concat(t,"=").concat(e):"$set(".concat(n.exp,", ").concat(n.key,", ").concat(e,")")}function qo(){return Co.charCodeAt(++So)}function Wo(){return So>=xo}function Zo(t){return 34===t||39===t}function Go(t){var e=1;for(Oo=So;!Wo();)if(Zo(t=qo()))Xo(t);else if(91===t&&e++,93===t&&e--,0===e){To=So;break}}function Xo(t){for(var e=t;!Wo()&&(t=qo())!==e;);}var Yo,Qo="__r";function ti(t,e,n){var r=Yo;return function o(){var i=e.apply(null,arguments);null!==i&&ri(t,o,n,r)}}var ei=gn&&!(Q&&Number(Q[1])<=53);function ni(t,e,n,r){if(ei){var o=Xe,i=e;e=i._wrapper=function(t){if(t.target===t.currentTarget||t.timeStamp>=o||t.timeStamp<=0||t.target.ownerDocument!==document)return i.apply(this,arguments)}}Yo.addEventListener(t,e,et?{capture:n,passive:r}:n)}function ri(t,e,n,r){(r||Yo).removeEventListener(t,e._wrapper||e,n)}function oi(t,e){if(!n(t.data.on)||!n(e.data.on)){var o=e.data.on||{},i=t.data.on||{};Yo=e.elm||t.elm,function(t){if(r(t.__r)){var e=W?"change":"input";t[e]=[].concat(t.__r,t[e]||[]),delete t.__r}r(t.__c)&&(t.change=[].concat(t.__c,t.change||[]),delete t.__c)}(o),Wt(o,i,ni,ri,ti,e.context),Yo=void 0}}var ii,ai={create:oi,update:oi,destroy:function(t){return oi(t,ao)}};function si(t,e){if(!n(t.data.domProps)||!n(e.data.domProps)){var i,a,s=e.elm,c=t.data.domProps||{},u=e.data.domProps||{};for(i in(r(u.__ob__)||o(u._v_attr_proxy))&&(u=e.data.domProps=T({},u)),c)i in u||(s[i]="");for(i in u){if(a=u[i],"textContent"===i||"innerHTML"===i){if(e.children&&(e.children.length=0),a===c[i])continue;1===s.childNodes.length&&s.removeChild(s.childNodes[0])}if("value"===i&&"PROGRESS"!==s.tagName){s._value=a;var l=n(a)?"":String(a);ci(s,l)&&(s.value=l)}else if("innerHTML"===i&&Gr(s.tagName)&&n(s.innerHTML)){(ii=ii||document.createElement("div")).innerHTML="".concat(a,"");for(var f=ii.firstChild;s.firstChild;)s.removeChild(s.firstChild);for(;f.firstChild;)s.appendChild(f.firstChild)}else if(a!==c[i])try{s[i]=a}catch(t){}}}}function ci(t,e){return!t.composing&&("OPTION"===t.tagName||function(t,e){var n=!0;try{n=document.activeElement!==t}catch(t){}return n&&t.value!==e}(t,e)||function(t,e){var n=t.value,o=t._vModifiers;if(r(o)){if(o.number)return p(n)!==p(e);if(o.trim)return n.trim()!==e.trim()}return n!==e}(t,e))}var ui={create:si,update:si},li=b((function(t){var e={},n=/:(.+)/;return t.split(/;(?![^(]*\))/g).forEach((function(t){if(t){var r=t.split(n);r.length>1&&(e[r[0].trim()]=r[1].trim())}})),e}));function fi(t){var e=di(t.style);return t.staticStyle?T(t.staticStyle,e):e}function di(t){return Array.isArray(t)?A(t):"string"==typeof t?li(t):t}var pi,vi=/^--/,hi=/\s*!important$/,mi=function(t,e,n){if(vi.test(e))t.style.setProperty(e,n);else if(hi.test(n))t.style.setProperty(k(e),n.replace(hi,""),"important");else{var r=yi(e);if(Array.isArray(n))for(var o=0,i=n.length;o-1?e.split($i).forEach((function(e){return t.classList.add(e)})):t.classList.add(e);else{var n=" ".concat(t.getAttribute("class")||""," ");n.indexOf(" "+e+" ")<0&&t.setAttribute("class",(n+e).trim())}}function xi(t,e){if(e&&(e=e.trim()))if(t.classList)e.indexOf(" ")>-1?e.split($i).forEach((function(e){return t.classList.remove(e)})):t.classList.remove(e),t.classList.length||t.removeAttribute("class");else{for(var n=" ".concat(t.getAttribute("class")||""," "),r=" "+e+" ";n.indexOf(r)>=0;)n=n.replace(r," ");(n=n.trim())?t.setAttribute("class",n):t.removeAttribute("class")}}function Ci(t){if(t){if("object"==typeof t){var e={};return!1!==t.css&&T(e,ki(t.name||"v")),T(e,t),e}return"string"==typeof t?ki(t):void 0}}var ki=b((function(t){return{enterClass:"".concat(t,"-enter"),enterToClass:"".concat(t,"-enter-to"),enterActiveClass:"".concat(t,"-enter-active"),leaveClass:"".concat(t,"-leave"),leaveToClass:"".concat(t,"-leave-to"),leaveActiveClass:"".concat(t,"-leave-active")}})),Si=J&&!Z,Oi="transition",Ti="animation",Ai="transition",ji="transitionend",Ei="animation",Ni="animationend";Si&&(void 0===window.ontransitionend&&void 0!==window.onwebkittransitionend&&(Ai="WebkitTransition",ji="webkitTransitionEnd"),void 0===window.onanimationend&&void 0!==window.onwebkitanimationend&&(Ei="WebkitAnimation",Ni="webkitAnimationEnd"));var Pi=J?window.requestAnimationFrame?window.requestAnimationFrame.bind(window):setTimeout:function(t){return t()};function Di(t){Pi((function(){Pi(t)}))}function Mi(t,e){var n=t._transitionClasses||(t._transitionClasses=[]);n.indexOf(e)<0&&(n.push(e),wi(t,e))}function Ii(t,e){t._transitionClasses&&g(t._transitionClasses,e),xi(t,e)}function Li(t,e,n){var r=Fi(t,e),o=r.type,i=r.timeout,a=r.propCount;if(!o)return n();var s=o===Oi?ji:Ni,c=0,u=function(){t.removeEventListener(s,l),n()},l=function(e){e.target===t&&++c>=a&&u()};setTimeout((function(){c0&&(n=Oi,l=a,f=i.length):e===Ti?u>0&&(n=Ti,l=u,f=c.length):f=(n=(l=Math.max(a,u))>0?a>u?Oi:Ti:null)?n===Oi?i.length:c.length:0,{type:n,timeout:l,propCount:f,hasTransform:n===Oi&&Ri.test(r[Ai+"Property"])}}function Hi(t,e){for(;t.length1}function Ji(t,e){!0!==e.data.show&&Ui(e)}var qi=function(t){var a,s,c={},u=t.modules,l=t.nodeOps;for(a=0;av?b(t,n(o[g+1])?null:o[g+1].elm,o,p,g,i):p>g&&w(e,f,v)}(f,h,m,i,u):r(m)?(r(t.text)&&l.setTextContent(f,""),b(f,null,m,0,m.length-1,i)):r(h)?w(h,0,h.length-1):r(t.text)&&l.setTextContent(f,""):t.text!==e.text&&l.setTextContent(f,e.text),r(v)&&r(p=v.hook)&&r(p=p.postpatch)&&p(t,e)}}}function S(t,e,n){if(o(n)&&r(t.parent))t.parent.data.pendingInsert=e;else for(var i=0;i-1,a.selected!==i&&(a.selected=i);else if(P(Yi(a),r))return void(t.selectedIndex!==s&&(t.selectedIndex=s));o||(t.selectedIndex=-1)}}function Xi(t,e){return e.every((function(e){return!P(e,t)}))}function Yi(t){return"_value"in t?t._value:t.value}function Qi(t){t.target.composing=!0}function ta(t){t.target.composing&&(t.target.composing=!1,ea(t.target,"input"))}function ea(t,e){var n=document.createEvent("HTMLEvents");n.initEvent(e,!0,!0),t.dispatchEvent(n)}function na(t){return!t.componentInstance||t.data&&t.data.transition?t:na(t.componentInstance._vnode)}var ra={bind:function(t,e,n){var r=e.value,o=(n=na(n)).data&&n.data.transition,i=t.__vOriginalDisplay="none"===t.style.display?"":t.style.display;r&&o?(n.data.show=!0,Ui(n,(function(){t.style.display=i}))):t.style.display=r?i:"none"},update:function(t,e,n){var r=e.value;!r!=!e.oldValue&&((n=na(n)).data&&n.data.transition?(n.data.show=!0,r?Ui(n,(function(){t.style.display=t.__vOriginalDisplay})):zi(n,(function(){t.style.display="none"}))):t.style.display=r?t.__vOriginalDisplay:"none")},unbind:function(t,e,n,r,o){o||(t.style.display=t.__vOriginalDisplay)}},oa={model:Wi,show:ra},ia={name:String,appear:Boolean,css:Boolean,mode:String,type:String,enterClass:String,leaveClass:String,enterToClass:String,leaveToClass:String,enterActiveClass:String,leaveActiveClass:String,appearClass:String,appearActiveClass:String,appearToClass:String,duration:[Number,String,Object]};function aa(t){var e=t&&t.componentOptions;return e&&e.Ctor.options.abstract?aa(Ne(e.children)):t}function sa(t){var e={},n=t.$options;for(var r in n.propsData)e[r]=t[r];var o=n._parentListeners;for(var r in o)e[w(r)]=o[r];return e}function ca(t,e){if(/\d-keep-alive$/.test(e.tag))return t("keep-alive",{props:e.componentOptions.propsData})}var ua=function(t){return t.tag||_e(t)},la=function(t){return"show"===t.name},fa={name:"transition",props:ia,abstract:!0,render:function(t){var e=this,n=this.$slots.default;if(n&&(n=n.filter(ua)).length){var r=this.mode,o=n[0];if(function(t){for(;t=t.parent;)if(t.data.transition)return!0}(this.$vnode))return o;var a=aa(o);if(!a)return o;if(this._leaving)return ca(t,o);var s="__transition-".concat(this._uid,"-");a.key=null==a.key?a.isComment?s+"comment":s+a.tag:i(a.key)?0===String(a.key).indexOf(s)?a.key:s+a.key:a.key;var c=(a.data||(a.data={})).transition=sa(this),u=this._vnode,l=aa(u);if(a.data.directives&&a.data.directives.some(la)&&(a.data.show=!0),l&&l.data&&!function(t,e){return e.key===t.key&&e.tag===t.tag}(a,l)&&!_e(l)&&(!l.componentInstance||!l.componentInstance._vnode.isComment)){var f=l.data.transition=T({},c);if("out-in"===r)return this._leaving=!0,Zt(f,"afterLeave",(function(){e._leaving=!1,e.$forceUpdate()})),ca(t,o);if("in-out"===r){if(_e(a))return u;var d,p=function(){d()};Zt(c,"afterEnter",p),Zt(c,"enterCancelled",p),Zt(f,"delayLeave",(function(t){d=t}))}}return o}}},da=T({tag:String,moveClass:String},ia);delete da.mode;var pa={props:da,beforeMount:function(){var t=this,e=this._update;this._update=function(n,r){var o=He(t);t.__patch__(t._vnode,t.kept,!1,!0),t._vnode=t.kept,o(),e.call(t,n,r)}},render:function(t){for(var e=this.tag||this.$vnode.data.tag||"span",n=Object.create(null),r=this.prevChildren=this.children,o=this.$slots.default||[],i=this.children=[],a=sa(this),s=0;s-1?Qr[t]=e.constructor===window.HTMLUnknownElement||e.constructor===window.HTMLElement:Qr[t]=/HTMLUnknownElement/.test(e.toString())},T(kr.options.directives,oa),T(kr.options.components,ga),kr.prototype.__patch__=J?qi:j,kr.prototype.$mount=function(t,e){return function(t,e,n){var r;t.$el=e,t.$options.render||(t.$options.render=ft),Ve(t,"beforeMount"),r=function(){t._update(t._render(),n)},new Kn(t,r,j,{before:function(){t._isMounted&&!t._isDestroyed&&Ve(t,"beforeUpdate")}},!0),n=!1;var o=t._preWatchers;if(o)for(var i=0;i\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/,Aa=/^\s*((?:v-[\w-]+:|@|:|#)\[[^=]+?\][^\s"'<>\/=]*)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/,ja="[a-zA-Z_][\\-\\.0-9_a-zA-Z".concat(B.source,"]*"),Ea="((?:".concat(ja,"\\:)?").concat(ja,")"),Na=new RegExp("^<".concat(Ea)),Pa=/^\s*(\/?)>/,Da=new RegExp("^<\\/".concat(Ea,"[^>]*>")),Ma=/^]+>/i,Ia=/^",""":'"',"&":"&"," ":"\n"," ":"\t","'":"'"},Ba=/&(?:lt|gt|quot|amp|#39);/g,Ua=/&(?:lt|gt|quot|amp|#39|#10|#9);/g,za=v("pre,textarea",!0),Va=function(t,e){return t&&za(t)&&"\n"===e[0]};function Ka(t,e){var n=e?Ua:Ba;return t.replace(n,(function(t){return Ha[t]}))}function Ja(t,e){for(var n,r,o=[],i=e.expectHTML,a=e.isUnaryTag||E,s=e.canBeLeftOpenTag||E,c=0,u=function(){if(n=t,r&&Ra(r)){var u=0,d=r.toLowerCase(),p=Fa[d]||(Fa[d]=new RegExp("([\\s\\S]*?)(]*>)","i"));w=t.replace(p,(function(t,n,r){return u=r.length,Ra(d)||"noscript"===d||(n=n.replace(//g,"$1").replace(//g,"$1")),Va(d,n)&&(n=n.slice(1)),e.chars&&e.chars(n),""}));c+=t.length-w.length,t=w,f(d,c-u,c)}else{var v=t.indexOf("<");if(0===v){if(Ia.test(t)){var h=t.indexOf("--\x3e");if(h>=0)return e.shouldKeepComment&&e.comment&&e.comment(t.substring(4,h),c,c+h+3),l(h+3),"continue"}if(La.test(t)){var m=t.indexOf("]>");if(m>=0)return l(m+2),"continue"}var g=t.match(Ma);if(g)return l(g[0].length),"continue";var y=t.match(Da);if(y){var _=c;return l(y[0].length),f(y[1],_,c),"continue"}var b=function(){var e=t.match(Na);if(e){var n={tagName:e[1],attrs:[],start:c};l(e[0].length);for(var r=void 0,o=void 0;!(r=t.match(Pa))&&(o=t.match(Aa)||t.match(Ta));)o.start=c,l(o[0].length),o.end=c,n.attrs.push(o);if(r)return n.unarySlash=r[1],l(r[0].length),n.end=c,n}}();if(b)return function(t){var n=t.tagName,c=t.unarySlash;i&&("p"===r&&Oa(n)&&f(r),s(n)&&r===n&&f(n));for(var u=a(n)||!!c,l=t.attrs.length,d=new Array(l),p=0;p=0){for(w=t.slice(v);!(Da.test(w)||Na.test(w)||Ia.test(w)||La.test(w)||(x=w.indexOf("<",1))<0);)v+=x,w=t.slice(v);$=t.substring(0,v)}v<0&&($=t),$&&l($.length),e.chars&&$&&e.chars($,c-$.length,c)}if(t===n)return e.chars&&e.chars(t),"break"};t;){if("break"===u())break}function l(e){c+=e,t=t.substring(e)}function f(t,n,i){var a,s;if(null==n&&(n=c),null==i&&(i=c),t)for(s=t.toLowerCase(),a=o.length-1;a>=0&&o[a].lowerCasedTag!==s;a--);else a=0;if(a>=0){for(var u=o.length-1;u>=a;u--)e.end&&e.end(o[u].tag,n,i);o.length=a,r=a&&o[a-1].tag}else"br"===s?e.start&&e.start(t,[],!0,n,i):"p"===s&&(e.start&&e.start(t,[],!1,n,i),e.end&&e.end(t,n,i))}f()}var qa,Wa,Za,Ga,Xa,Ya,Qa,ts,es=/^@|^v-on:/,ns=/^v-|^@|^:|^#/,rs=/([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/,os=/,([^,\}\]]*)(?:,([^,\}\]]*))?$/,is=/^\(|\)$/g,as=/^\[.*\]$/,ss=/:(.*)$/,cs=/^:|^\.|^v-bind:/,us=/\.[^.\]]+(?=[^\]]*$)/g,ls=/^v-slot(:|$)|^#/,fs=/[\r\n]/,ds=/[ \f\t\r\n]+/g,ps=b(Ca),vs="_empty_";function hs(t,e,n){return{type:1,tag:t,attrsList:e,attrsMap:ws(e),rawAttrsMap:{},parent:n,children:[]}}function ms(t,e){qa=e.warn||Po,Ya=e.isPreTag||E,Qa=e.mustUseProp||E,ts=e.getTagNamespace||E,e.isReservedTag,Za=Do(e.modules,"transformNode"),Ga=Do(e.modules,"preTransformNode"),Xa=Do(e.modules,"postTransformNode"),Wa=e.delimiters;var n,r,o=[],i=!1!==e.preserveWhitespace,a=e.whitespace,s=!1,c=!1;function u(t){if(l(t),s||t.processed||(t=gs(t,e)),o.length||t===n||n.if&&(t.elseif||t.else)&&_s(n,{exp:t.elseif,block:t}),r&&!t.forbidden)if(t.elseif||t.else)a=t,u=function(t){for(var e=t.length;e--;){if(1===t[e].type)return t[e];t.pop()}}(r.children),u&&u.if&&_s(u,{exp:a.elseif,block:a});else{if(t.slotScope){var i=t.slotTarget||'"default"';(r.scopedSlots||(r.scopedSlots={}))[i]=t}r.children.push(t),t.parent=r}var a,u;t.children=t.children.filter((function(t){return!t.slotScope})),l(t),t.pre&&(s=!1),Ya(t.tag)&&(c=!1);for(var f=0;fc&&(s.push(i=t.slice(c,o)),a.push(JSON.stringify(i)));var u=Eo(r[1].trim());a.push("_s(".concat(u,")")),s.push({"@binding":u}),c=o+r[0].length}return c-1")+("true"===i?":(".concat(e,")"):":_q(".concat(e,",").concat(i,")"))),Ho(t,"change","var $$a=".concat(e,",")+"$$el=$event.target,"+"$$c=$$el.checked?(".concat(i,"):(").concat(a,");")+"if(Array.isArray($$a)){"+"var $$v=".concat(r?"_n("+o+")":o,",")+"$$i=_i($$a,$$v);"+"if($$el.checked){$$i<0&&(".concat(Jo(e,"$$a.concat([$$v])"),")}")+"else{$$i>-1&&(".concat(Jo(e,"$$a.slice(0,$$i).concat($$a.slice($$i+1))"),")}")+"}else{".concat(Jo(e,"$$c"),"}"),null,!0)}(t,r,o);else if("input"===i&&"radio"===a)!function(t,e,n){var r=n&&n.number,o=Bo(t,"value")||"null";o=r?"_n(".concat(o,")"):o,Mo(t,"checked","_q(".concat(e,",").concat(o,")")),Ho(t,"change",Jo(e,o),null,!0)}(t,r,o);else if("input"===i||"textarea"===i)!function(t,e,n){var r=t.attrsMap.type,o=n||{},i=o.lazy,a=o.number,s=o.trim,c=!i&&"range"!==r,u=i?"change":"range"===r?Qo:"input",l="$event.target.value";s&&(l="$event.target.value.trim()");a&&(l="_n(".concat(l,")"));var f=Jo(e,l);c&&(f="if($event.target.composing)return;".concat(f));Mo(t,"value","(".concat(e,")")),Ho(t,u,f,null,!0),(s||a)&&Ho(t,"blur","$forceUpdate()")}(t,r,o);else if(!H.isReservedTag(i))return Ko(t,r,o),!1;return!0},text:function(t,e){e.value&&Mo(t,"textContent","_s(".concat(e.value,")"),e)},html:function(t,e){e.value&&Mo(t,"innerHTML","_s(".concat(e.value,")"),e)}},js={expectHTML:!0,modules:Ss,directives:As,isPreTag:function(t){return"pre"===t},isUnaryTag:ka,mustUseProp:Ir,canBeLeftOpenTag:Sa,isReservedTag:Xr,getTagNamespace:Yr,staticKeys:function(t){return t.reduce((function(t,e){return t.concat(e.staticKeys||[])}),[]).join(",")}(Ss)},Es=b((function(t){return v("type,tag,attrsList,attrsMap,plain,parent,children,attrs,start,end,rawAttrsMap"+(t?","+t:""))}));function Ns(t,e){t&&(Os=Es(e.staticKeys||""),Ts=e.isReservedTag||E,Ps(t),Ds(t,!1))}function Ps(t){if(t.static=function(t){if(2===t.type)return!1;if(3===t.type)return!0;return!(!t.pre&&(t.hasBindings||t.if||t.for||h(t.tag)||!Ts(t.tag)||function(t){for(;t.parent;){if("template"!==(t=t.parent).tag)return!1;if(t.for)return!0}return!1}(t)||!Object.keys(t).every(Os)))}(t),1===t.type){if(!Ts(t.tag)&&"slot"!==t.tag&&null==t.attrsMap["inline-template"])return;for(var e=0,n=t.children.length;e|^function(?:\s+[\w$]+)?\s*\(/,Is=/\([^)]*?\);*$/,Ls=/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/,Rs={esc:27,tab:9,enter:13,space:32,up:38,left:37,right:39,down:40,delete:[8,46]},Fs={esc:["Esc","Escape"],tab:"Tab",enter:"Enter",space:[" ","Spacebar"],up:["Up","ArrowUp"],left:["Left","ArrowLeft"],right:["Right","ArrowRight"],down:["Down","ArrowDown"],delete:["Backspace","Delete","Del"]},Hs=function(t){return"if(".concat(t,")return null;")},Bs={stop:"$event.stopPropagation();",prevent:"$event.preventDefault();",self:Hs("$event.target !== $event.currentTarget"),ctrl:Hs("!$event.ctrlKey"),shift:Hs("!$event.shiftKey"),alt:Hs("!$event.altKey"),meta:Hs("!$event.metaKey"),left:Hs("'button' in $event && $event.button !== 0"),middle:Hs("'button' in $event && $event.button !== 1"),right:Hs("'button' in $event && $event.button !== 2")};function Us(t,e){var n=e?"nativeOn:":"on:",r="",o="";for(var i in t){var a=zs(t[i]);t[i]&&t[i].dynamic?o+="".concat(i,",").concat(a,","):r+='"'.concat(i,'":').concat(a,",")}return r="{".concat(r.slice(0,-1),"}"),o?n+"_d(".concat(r,",[").concat(o.slice(0,-1),"])"):n+r}function zs(t){if(!t)return"function(){}";if(Array.isArray(t))return"[".concat(t.map((function(t){return zs(t)})).join(","),"]");var e=Ls.test(t.value),n=Ms.test(t.value),r=Ls.test(t.value.replace(Is,""));if(t.modifiers){var o="",i="",a=[],s=function(e){if(Bs[e])i+=Bs[e],Rs[e]&&a.push(e);else if("exact"===e){var n=t.modifiers;i+=Hs(["ctrl","shift","alt","meta"].filter((function(t){return!n[t]})).map((function(t){return"$event.".concat(t,"Key")})).join("||"))}else a.push(e)};for(var c in t.modifiers)s(c);a.length&&(o+=function(t){return"if(!$event.type.indexOf('key')&&"+"".concat(t.map(Vs).join("&&"),")return null;")}(a)),i&&(o+=i);var u=e?"return ".concat(t.value,".apply(null, arguments)"):n?"return (".concat(t.value,").apply(null, arguments)"):r?"return ".concat(t.value):t.value;return"function($event){".concat(o).concat(u,"}")}return e||n?t.value:"function($event){".concat(r?"return ".concat(t.value):t.value,"}")}function Vs(t){var e=parseInt(t,10);if(e)return"$event.keyCode!==".concat(e);var n=Rs[t],r=Fs[t];return"_k($event.keyCode,"+"".concat(JSON.stringify(t),",")+"".concat(JSON.stringify(n),",")+"$event.key,"+"".concat(JSON.stringify(r))+")"}var Ks={on:function(t,e){t.wrapListeners=function(t){return"_g(".concat(t,",").concat(e.value,")")}},bind:function(t,e){t.wrapData=function(n){return"_b(".concat(n,",'").concat(t.tag,"',").concat(e.value,",").concat(e.modifiers&&e.modifiers.prop?"true":"false").concat(e.modifiers&&e.modifiers.sync?",true":"",")")}},cloak:j},Js=function(t){this.options=t,this.warn=t.warn||Po,this.transforms=Do(t.modules,"transformCode"),this.dataGenFns=Do(t.modules,"genData"),this.directives=T(T({},Ks),t.directives);var e=t.isReservedTag||E;this.maybeComponent=function(t){return!!t.component||!e(t.tag)},this.onceId=0,this.staticRenderFns=[],this.pre=!1};function qs(t,e){var n=new Js(e),r=t?"script"===t.tag?"null":Ws(t,n):'_c("div")';return{render:"with(this){return ".concat(r,"}"),staticRenderFns:n.staticRenderFns}}function Ws(t,e){if(t.parent&&(t.pre=t.pre||t.parent.pre),t.staticRoot&&!t.staticProcessed)return Zs(t,e);if(t.once&&!t.onceProcessed)return Gs(t,e);if(t.for&&!t.forProcessed)return Qs(t,e);if(t.if&&!t.ifProcessed)return Xs(t,e);if("template"!==t.tag||t.slotTarget||e.pre){if("slot"===t.tag)return function(t,e){var n=t.slotName||'"default"',r=rc(t,e),o="_t(".concat(n).concat(r?",function(){return ".concat(r,"}"):""),i=t.attrs||t.dynamicAttrs?ac((t.attrs||[]).concat(t.dynamicAttrs||[]).map((function(t){return{name:w(t.name),value:t.value,dynamic:t.dynamic}}))):null,a=t.attrsMap["v-bind"];!i&&!a||r||(o+=",null");i&&(o+=",".concat(i));a&&(o+="".concat(i?"":",null",",").concat(a));return o+")"}(t,e);var n=void 0;if(t.component)n=function(t,e,n){var r=e.inlineTemplate?null:rc(e,n,!0);return"_c(".concat(t,",").concat(tc(e,n)).concat(r?",".concat(r):"",")")}(t.component,t,e);else{var r=void 0,o=e.maybeComponent(t);(!t.plain||t.pre&&o)&&(r=tc(t,e));var i=void 0,a=e.options.bindings;o&&a&&!1!==a.__isScriptSetup&&(i=function(t,e){var n=w(e),r=x(n),o=function(o){return t[e]===o?e:t[n]===o?n:t[r]===o?r:void 0},i=o("setup-const")||o("setup-reactive-const");if(i)return i;var a=o("setup-let")||o("setup-ref")||o("setup-maybe-ref");if(a)return a}(a,t.tag)),i||(i="'".concat(t.tag,"'"));var s=t.inlineTemplate?null:rc(t,e,!0);n="_c(".concat(i).concat(r?",".concat(r):"").concat(s?",".concat(s):"",")")}for(var c=0;c>>0}(a)):"",")")}(t,t.scopedSlots,e),",")),t.model&&(n+="model:{value:".concat(t.model.value,",callback:").concat(t.model.callback,",expression:").concat(t.model.expression,"},")),t.inlineTemplate){var i=function(t,e){var n=t.children[0];if(n&&1===n.type){var r=qs(n,e.options);return"inlineTemplate:{render:function(){".concat(r.render,"},staticRenderFns:[").concat(r.staticRenderFns.map((function(t){return"function(){".concat(t,"}")})).join(","),"]}")}}(t,e);i&&(n+="".concat(i,","))}return n=n.replace(/,$/,"")+"}",t.dynamicAttrs&&(n="_b(".concat(n,',"').concat(t.tag,'",').concat(ac(t.dynamicAttrs),")")),t.wrapData&&(n=t.wrapData(n)),t.wrapListeners&&(n=t.wrapListeners(n)),n}function ec(t){return 1===t.type&&("slot"===t.tag||t.children.some(ec))}function nc(t,e){var n=t.attrsMap["slot-scope"];if(t.if&&!t.ifProcessed&&!n)return Xs(t,e,nc,"null");if(t.for&&!t.forProcessed)return Qs(t,e,nc);var r=t.slotScope===vs?"":String(t.slotScope),o="function(".concat(r,"){")+"return ".concat("template"===t.tag?t.if&&n?"(".concat(t.if,")?").concat(rc(t,e)||"undefined",":undefined"):rc(t,e)||"undefined":Ws(t,e),"}"),i=r?"":",proxy:true";return"{key:".concat(t.slotTarget||'"default"',",fn:").concat(o).concat(i,"}")}function rc(t,e,n,r,o){var i=t.children;if(i.length){var a=i[0];if(1===i.length&&a.for&&"template"!==a.tag&&"slot"!==a.tag){var s=n?e.maybeComponent(a)?",1":",0":"";return"".concat((r||Ws)(a,e)).concat(s)}var c=n?function(t,e){for(var n=0,r=0;r':'
      ',fc.innerHTML.indexOf(" ")>0}var hc=!!J&&vc(!1),mc=!!J&&vc(!0),gc=b((function(t){var e=eo(t);return e&&e.innerHTML})),yc=kr.prototype.$mount;return kr.prototype.$mount=function(t,e){if((t=t&&eo(t))===document.body||t===document.documentElement)return this;var n=this.$options;if(!n.render){var r=n.template;if(r)if("string"==typeof r)"#"===r.charAt(0)&&(r=gc(r));else{if(!r.nodeType)return this;r=r.innerHTML}else t&&(r=function(t){if(t.outerHTML)return t.outerHTML;var e=document.createElement("div");return e.appendChild(t.cloneNode(!0)),e.innerHTML}(t));if(r){var o=pc(r,{outputSourceRange:!1,shouldDecodeNewlines:hc,shouldDecodeNewlinesForHref:mc,delimiters:n.delimiters,comments:n.comments},this),i=o.render,a=o.staticRenderFns;n.render=i,n.staticRenderFns=a}}return yc.call(this,t,e)},kr.compile=pc,T(kr,Hn),kr.effect=function(t,e){var n=new Kn(ct,t,j,{sync:!0});e&&(n.update=function(){e((function(){return n.run()}))})},kr})); \ No newline at end of file diff --git a/kirby/panel/dist/js/vuedraggable.js b/kirby/panel/dist/js/vuedraggable.min.js similarity index 100% rename from kirby/panel/dist/js/vuedraggable.js rename to kirby/panel/dist/js/vuedraggable.min.js diff --git a/kirby/src/Api/Api.php b/kirby/src/Api/Api.php index 91b5788..76306e4 100644 --- a/kirby/src/Api/Api.php +++ b/kirby/src/Api/Api.php @@ -14,7 +14,6 @@ use Kirby\Http\Router; use Kirby\Toolkit\Collection as BaseCollection; use Kirby\Toolkit\I18n; use Kirby\Toolkit\Pagination; -use Kirby\Toolkit\Properties; use Kirby\Toolkit\Str; use Throwable; @@ -32,8 +31,6 @@ use Throwable; */ class Api { - use Properties; - /** * Authentication callback */ @@ -86,6 +83,28 @@ class Api */ protected string|null $requestMethod = null; + /** + * Creates a new API instance + */ + public function __construct(array $props) + { + $this->authentication = $props['authentication'] ?? null; + $this->data = $props['data'] ?? []; + $this->routes = $props['routes'] ?? []; + $this->debug = $props['debug'] ?? false; + + if ($collections = $props['collections'] ?? null) { + $this->collections = array_change_key_case($collections); + } + + if ($models = $props['models'] ?? null) { + $this->models = array_change_key_case($models); + } + + $this->setRequestData($props['requestData'] ?? null); + $this->setRequestMethod($props['requestMethod'] ?? null); + } + /** * Magic accessor for any given data * @@ -96,14 +115,6 @@ class Api return $this->data($method, ...$args); } - /** - * Creates a new API instance - */ - public function __construct(array $props) - { - $this->setProperties($props); - } - /** * Runs the authentication method * if set @@ -115,8 +126,6 @@ class Api /** * Returns the authentication callback - * - * @return \Closure|null */ public function authentication(): Closure|null { @@ -130,8 +139,11 @@ class Api * @throws \Kirby\Exception\NotFoundException * @throws \Exception */ - public function call(string|null $path = null, string $method = 'GET', array $requestData = []) - { + public function call( + string|null $path = null, + string $method = 'GET', + array $requestData = [] + ): mixed { $path = rtrim($path ?? '', '/'); $this->setRequestMethod($method); @@ -194,14 +206,34 @@ class Api return $output; } + /** + * Creates a new instance while + * merging initial and new properties + */ + public function clone(array $props = []): static + { + return new static(array_merge([ + 'autentication' => $this->authentication, + 'data' => $this->data, + 'routes' => $this->routes, + 'debug' => $this->debug, + 'collections' => $this->collections, + 'models' => $this->models, + 'requestData' => $this->requestData, + 'requestMethod' => $this->requestMethod + ], $props)); + } + /** * Setter and getter for an API collection * * @throws \Kirby\Exception\NotFoundException If no collection for `$name` exists * @throws \Exception */ - public function collection(string $name, array|BaseCollection|null $collection = null): Collection - { + public function collection( + string $name, + array|BaseCollection|null $collection = null + ): Collection { if (isset($this->collections[$name]) === false) { throw new NotFoundException(sprintf('The collection "%s" does not exist', $name)); } @@ -223,7 +255,7 @@ class Api * * @throws \Kirby\Exception\NotFoundException If no data for `$key` exists */ - public function data(string|null $key = null, ...$args) + public function data(string|null $key = null, ...$args): mixed { if ($key === null) { return $this->data; @@ -264,8 +296,10 @@ class Api * @param array models or collections * @return string|null key of match */ - protected function match(array $array, $object = null): string|null - { + protected function match( + array $array, + $object = null + ): string|null { foreach ($array as $definition => $model) { if ($object instanceof $model['type']) { return $definition; @@ -280,8 +314,10 @@ class Api * * @throws \Kirby\Exception\NotFoundException If no model for `$name` exists */ - public function model(string|null $name = null, $object = null): Model - { + public function model( + string|null $name = null, + $object = null + ): Model { // Try to auto-match object with API models $name ??= $this->match($this->models, $object); @@ -304,17 +340,12 @@ class Api * Getter for request data * Can either get all the data * or certain parts of it. - * - * @param string|null $type - * @param string|null $key - * @param mixed $default - * @return mixed */ public function requestData( string|null $type = null, string|null $key = null, - $default = null - ) { + mixed $default = null + ): mixed { if ($type === null) { return $this->requestData; } @@ -332,24 +363,30 @@ class Api /** * Returns the request body if available */ - public function requestBody(string|null $key = null, $default = null) - { + public function requestBody( + string|null $key = null, + mixed $default = null + ): mixed { return $this->requestData('body', $key, $default); } /** * Returns the files from the request if available */ - public function requestFiles(string|null $key = null, $default = null) - { + public function requestFiles( + string|null $key = null, + mixed $default = null + ): mixed { return $this->requestData('files', $key, $default); } /** * Returns all headers from the request if available */ - public function requestHeaders(string|null $key = null, $default = null) - { + public function requestHeaders( + string|null $key = null, + mixed $default = null + ): mixed { return $this->requestData('headers', $key, $default); } @@ -364,8 +401,10 @@ class Api /** * Returns the request query if available */ - public function requestQuery(string|null $key = null, $default = null) - { + public function requestQuery( + string|null $key = null, + mixed $default = null + ): mixed { return $this->requestData('query', $key, $default); } @@ -403,102 +442,14 @@ class Api return $this->routes; } - /** - * Setter for the authentication callback - * @return $this - */ - protected function setAuthentication(Closure|null $authentication = null): static - { - $this->authentication = $authentication; - return $this; - } - - /** - * Setter for the collections definition - * @return $this - */ - protected function setCollections(array|null $collections = null): static - { - if ($collections !== null) { - $this->collections = array_change_key_case($collections); - } - return $this; - } - - /** - * Setter for the injected data - * @return $this - */ - protected function setData(array|null $data = null): static - { - $this->data = $data ?? []; - return $this; - } - - /** - * Setter for the debug flag - * @return $this - */ - protected function setDebug(bool $debug = false): static - { - $this->debug = $debug; - return $this; - } - - /** - * Setter for the model definitions - * @return $this - */ - protected function setModels(array|null $models = null): static - { - if ($models !== null) { - $this->models = array_change_key_case($models); - } - - return $this; - } - - /** - * Setter for the request data - * @return $this - */ - protected function setRequestData(array|null $requestData = null): static - { - $defaults = [ - 'query' => [], - 'body' => [], - 'files' => [] - ]; - - $this->requestData = array_merge($defaults, (array)$requestData); - return $this; - } - - /** - * Setter for the request method - * @return $this - */ - protected function setRequestMethod(string|null $requestMethod = null): static - { - $this->requestMethod = $requestMethod ?? 'GET'; - return $this; - } - - /** - * Setter for the route definitions - * @return $this - */ - protected function setRoutes(array|null $routes = null): static - { - $this->routes = $routes ?? []; - return $this; - } - /** * Renders the API call */ - public function render(string $path, string $method = 'GET', array $requestData = []) - { + public function render( + string $path, + string $method = 'GET', + array $requestData = [] + ): mixed { try { $result = $this->call($path, $method, $requestData); } catch (Throwable $e) { @@ -619,6 +570,34 @@ class Api return $result; } + /** + * Setter for the request data + * @return $this + */ + protected function setRequestData( + array|null $requestData = [] + ): static { + $defaults = [ + 'query' => [], + 'body' => [], + 'files' => [] + ]; + + $this->requestData = array_merge($defaults, (array)$requestData); + return $this; + } + + /** + * Setter for the request method + * @return $this + */ + protected function setRequestMethod( + string $requestMethod = null + ): static { + $this->requestMethod = $requestMethod ?? 'GET'; + return $this; + } + /** * Upload helper method * @@ -627,8 +606,11 @@ class Api * * @throws \Exception If request has no files or there was an error with the upload */ - public function upload(Closure $callback, bool $single = false, bool $debug = false): array - { + public function upload( + Closure $callback, + bool $single = false, + bool $debug = false + ): array { $trials = 0; $uploads = []; $errors = []; @@ -668,8 +650,9 @@ class Api try { if ($upload['error'] !== 0) { - $errorMessage = $errorMessages[$upload['error']] ?? I18n::translate('upload.error.default'); - throw new Exception($errorMessage); + throw new Exception( + $errorMessages[$upload['error']] ?? I18n::translate('upload.error.default') + ); } // get the extension of the uploaded file @@ -696,7 +679,9 @@ class Api $debug === false && move_uploaded_file($upload['tmp_name'], $source) === false ) { - throw new Exception(I18n::translate('upload.error.cantMove')); + throw new Exception( + I18n::translate('upload.error.cantMove') + ); } $data = $callback($source, $filename); diff --git a/kirby/src/Api/Collection.php b/kirby/src/Api/Collection.php index cc71b24..af66235 100644 --- a/kirby/src/Api/Collection.php +++ b/kirby/src/Api/Collection.php @@ -4,6 +4,7 @@ namespace Kirby\Api; use Closure; use Exception; +use Kirby\Toolkit\Collection as BaseCollection; use Kirby\Toolkit\Str; /** @@ -20,23 +21,22 @@ use Kirby\Toolkit\Str; */ class Collection { - protected Api $api; - protected $data; - protected $model; - protected $select = null; - protected $view; + protected string|null $model; + protected array|null $select = null; + protected string|null $view; /** * Collection constructor * * @throws \Exception */ - public function __construct(Api $api, $data, array $schema) - { - $this->api = $api; - $this->data = $data; - $this->model = $schema['model'] ?? null; - $this->view = $schema['view'] ?? null; + public function __construct( + protected Api $api, + protected BaseCollection|array|null $data, + array $schema + ) { + $this->model = $schema['model'] ?? null; + $this->view = $schema['view'] ?? null; if ($data === null) { if (($schema['default'] ?? null) instanceof Closure === false) { diff --git a/kirby/src/Api/Model.php b/kirby/src/Api/Model.php index ba14cb2..0c78f93 100644 --- a/kirby/src/Api/Model.php +++ b/kirby/src/Api/Model.php @@ -22,21 +22,20 @@ use Kirby\Toolkit\Str; */ class Model { - protected Api $api; - protected $data; - protected $fields; - protected $select; - protected $views; + protected array $fields; + protected array|null $select; + protected array $views; /** * Model constructor * * @throws \Exception */ - public function __construct(Api $api, $data, array $schema) - { - $this->api = $api; - $this->data = $data; + public function __construct( + protected Api $api, + protected object|array|string|null $data, + array $schema + ) { $this->fields = $schema['fields'] ?? []; $this->select = $schema['select'] ?? null; $this->views = $schema['views'] ?? []; diff --git a/kirby/src/Blueprint/Collection.php b/kirby/src/Blueprint/Collection.php index a943b74..a8dc604 100644 --- a/kirby/src/Blueprint/Collection.php +++ b/kirby/src/Blueprint/Collection.php @@ -16,7 +16,7 @@ use TypeError; * @copyright Bastian Allgeier * @license https://opensource.org/licenses/MIT * - * // TODO: include in test coverage 3.10 + * // TODO: include in test coverage once blueprint refactoring is done * @codeCoverageIgnore */ class Collection extends BaseCollection @@ -37,6 +37,8 @@ class Collection extends BaseCollection * The Kirby Collection class only shows the key to * avoid huge tress with dump, but for the blueprint * collections this is really not useful + * + * @codeCoverageIgnore */ public function __debugInfo(): array { diff --git a/kirby/src/Blueprint/Config.php b/kirby/src/Blueprint/Config.php index 1cd9143..c7edb23 100644 --- a/kirby/src/Blueprint/Config.php +++ b/kirby/src/Blueprint/Config.php @@ -17,7 +17,7 @@ use Kirby\Filesystem\F; * @copyright Bastian Allgeier * @license https://opensource.org/licenses/MIT * - * // TODO: include in test coverage in 3.10 + * // TODO: include in test coverage once blueprint refactoring is done * @codeCoverageIgnore */ class Config diff --git a/kirby/src/Blueprint/Extension.php b/kirby/src/Blueprint/Extension.php index f422b1d..9d3d25f 100644 --- a/kirby/src/Blueprint/Extension.php +++ b/kirby/src/Blueprint/Extension.php @@ -11,7 +11,7 @@ namespace Kirby\Blueprint; * @copyright Bastian Allgeier * @license https://opensource.org/licenses/MIT * - * // TODO: include in test coverage in 3.10 + * // TODO: include in test coverage once blueprint refactoring is done * @codeCoverageIgnore */ class Extension diff --git a/kirby/src/Blueprint/Factory.php b/kirby/src/Blueprint/Factory.php index 148f1eb..756611c 100644 --- a/kirby/src/Blueprint/Factory.php +++ b/kirby/src/Blueprint/Factory.php @@ -16,7 +16,7 @@ use ReflectionUnionType; * @copyright Bastian Allgeier * @license https://opensource.org/licenses/MIT * - * // TODO: include in test coverage in 3.10 + * // TODO: include in test coverage once blueprint refactoring is done * @codeCoverageIgnore */ class Factory diff --git a/kirby/src/Blueprint/Node.php b/kirby/src/Blueprint/Node.php index 80f9ae6..96d4dca 100644 --- a/kirby/src/Blueprint/Node.php +++ b/kirby/src/Blueprint/Node.php @@ -13,7 +13,7 @@ use Kirby\Cms\ModelWithContent; * @copyright Bastian Allgeier * @license https://opensource.org/licenses/MIT * - * // TODO: include in test coverage in 3.10 + * // TODO: include in test coverage once blueprint refactoring is done * @codeCoverageIgnore */ class Node diff --git a/kirby/src/Blueprint/NodeI18n.php b/kirby/src/Blueprint/NodeI18n.php index 5278774..6dc06aa 100644 --- a/kirby/src/Blueprint/NodeI18n.php +++ b/kirby/src/Blueprint/NodeI18n.php @@ -14,7 +14,7 @@ use Kirby\Toolkit\I18n; * @copyright Bastian Allgeier * @license https://opensource.org/licenses/MIT * - * // TODO: include in test coverage in 3.10 + * // TODO: include in test coverage once blueprint refactoring is done * @codeCoverageIgnore */ class NodeI18n extends NodeProperty diff --git a/kirby/src/Blueprint/NodeIcon.php b/kirby/src/Blueprint/NodeIcon.php index c23d589..3fa672d 100644 --- a/kirby/src/Blueprint/NodeIcon.php +++ b/kirby/src/Blueprint/NodeIcon.php @@ -11,7 +11,7 @@ namespace Kirby\Blueprint; * @copyright Bastian Allgeier * @license https://opensource.org/licenses/MIT * - * // TODO: include in test coverage in 3.10 + * // TODO: include in test coverage once blueprint refactoring is done * @codeCoverageIgnore */ class NodeIcon extends NodeString diff --git a/kirby/src/Blueprint/NodeProperty.php b/kirby/src/Blueprint/NodeProperty.php index 9490435..6aef2db 100644 --- a/kirby/src/Blueprint/NodeProperty.php +++ b/kirby/src/Blueprint/NodeProperty.php @@ -13,7 +13,7 @@ use Kirby\Cms\ModelWithContent; * @copyright Bastian Allgeier * @license https://opensource.org/licenses/MIT * - * // TODO: include in test coverage in 3.10 + * // TODO: include in test coverage once blueprint refactoring is done * @codeCoverageIgnore */ abstract class NodeProperty diff --git a/kirby/src/Blueprint/NodeString.php b/kirby/src/Blueprint/NodeString.php index 7769c3a..1093e1f 100644 --- a/kirby/src/Blueprint/NodeString.php +++ b/kirby/src/Blueprint/NodeString.php @@ -13,7 +13,7 @@ use Kirby\Cms\ModelWithContent; * @copyright Bastian Allgeier * @license https://opensource.org/licenses/MIT * - * // TODO: include in test coverage in 3.10 + * // TODO: include in test coverage once blueprint refactoring is done * @codeCoverageIgnore */ class NodeString extends NodeProperty diff --git a/kirby/src/Blueprint/NodeText.php b/kirby/src/Blueprint/NodeText.php index feeccf6..bbc4cd7 100644 --- a/kirby/src/Blueprint/NodeText.php +++ b/kirby/src/Blueprint/NodeText.php @@ -14,12 +14,12 @@ use Kirby\Cms\ModelWithContent; * @copyright Bastian Allgeier * @license https://opensource.org/licenses/MIT * - * // TODO: include in test coverage in 3.10 + * // TODO: include in test coverage once blueprint refactoring is done * @codeCoverageIgnore */ class NodeText extends NodeI18n { - public function render(ModelWithContent $model): ?string + public function render(ModelWithContent $model): string|null { if ($text = parent::render($model)) { return $model->toSafeString($text); diff --git a/kirby/src/Cms/Api.php b/kirby/src/Cms/Api.php index c914e3b..04e4d75 100644 --- a/kirby/src/Cms/Api.php +++ b/kirby/src/Cms/Api.php @@ -18,10 +18,13 @@ use Kirby\Session\Session; */ class Api extends BaseApi { - /** - * @var App - */ - protected $kirby; + protected App $kirby; + + public function __construct(array $props) + { + $this->kirby = $props['kirby']; + parent::__construct($props); + } /** * Execute an API call for the given path, @@ -31,7 +34,7 @@ class Api extends BaseApi string|null $path = null, string $method = 'GET', array $requestData = [] - ) { + ): mixed { $this->setRequestMethod($method); $this->setRequestData($requestData); @@ -46,21 +49,37 @@ class Api extends BaseApi return parent::call($path, $method, $requestData); } + /** + * Creates a new instance while + * merging initial and new properties + */ + public function clone(array $props = []): static + { + return parent::clone(array_merge([ + 'kirby' => $this->kirby + ], $props)); + } + /** * @throws \Kirby\Exception\NotFoundException if the field type cannot be found or the field cannot be loaded */ - public function fieldApi($model, string $name, string|null $path = null) - { + public function fieldApi( + ModelWithContent $model, + string $name, + string|null $path = null + ): mixed { $field = Form::for($model)->field($name); - $fieldApi = new static( - array_merge($this->propertyData, [ - 'data' => array_merge($this->data(), ['field' => $field]), - 'routes' => $field->api(), - ]), - ); + $fieldApi = $this->clone([ + 'data' => array_merge($this->data(), ['field' => $field]), + 'routes' => $field->api(), + ]); - return $fieldApi->call($path, $this->requestMethod(), $this->requestData()); + return $fieldApi->call( + $path, + $this->requestMethod(), + $this->requestData() + ); } /** @@ -70,11 +89,24 @@ class Api extends BaseApi * @param string $path Path to file's parent model * @throws \Kirby\Exception\NotFoundException if the file cannot be found */ - public function file(string $path, string $filename): File|null - { + public function file( + string $path, + string $filename + ): File|null { return Find::file($path, $filename); } + /** + * Returns the all readable files for the parent + * + * @param string $path Path to file's parent model + * @throws \Kirby\Exception\NotFoundException if the file cannot be found + */ + public function files(string $path): Files + { + return $this->parent($path)->files()->filter('isAccessible', true); + } + /** * Returns the model's object for the given path * @@ -82,7 +114,7 @@ class Api extends BaseApi * @throws \Kirby\Exception\InvalidArgumentException if the model type is invalid * @throws \Kirby\Exception\NotFoundException if the model cannot be found */ - public function parent(string $path): Model|null + public function parent(string $path): ModelWithContent|null { return Find::parent($path); } @@ -100,7 +132,9 @@ class Api extends BaseApi */ public function language(): string|null { - return $this->requestQuery('language') ?? $this->requestHeaders('x-language'); + return + $this->requestQuery('language') ?? + $this->requestHeaders('x-language'); } /** @@ -119,11 +153,12 @@ class Api extends BaseApi * parent. The subpages can be filtered * by status (draft, listed, unlisted, published, all) */ - public function pages(string|null $parentId = null, string|null $status = null): Pages - { + public function pages( + string|null $parentId = null, + string|null $status = null + ): Pages { $parent = $parentId === null ? $this->site() : $this->page($parentId); - - return match ($status) { + $pages = match ($status) { 'all' => $parent->childrenAndDrafts(), 'draft', 'drafts' => $parent->drafts(), 'listed' => $parent->children()->listed(), @@ -131,6 +166,8 @@ class Api extends BaseApi 'published' => $parent->children(), default => $parent->children() }; + + return $pages->filter('isAccessible', true); } /** @@ -160,17 +197,6 @@ class Api extends BaseApi ], $options)); } - /** - * Setter for the parent Kirby instance - * - * @return $this - */ - protected function setKirby(App $kirby): static - { - $this->kirby = $kirby; - return $this; - } - /** * Returns the site object */ diff --git a/kirby/src/Cms/App.php b/kirby/src/Cms/App.php index 44b8fba..e394af4 100644 --- a/kirby/src/Cms/App.php +++ b/kirby/src/Cms/App.php @@ -3,7 +3,9 @@ namespace Kirby\Cms; use Closure; +use Generator; use Kirby\Data\Data; +use Kirby\Email\Email as BaseEmail; use Kirby\Exception\ErrorPageException; use Kirby\Exception\Exception; use Kirby\Exception\InvalidArgumentException; @@ -14,17 +16,20 @@ use Kirby\Filesystem\F; use Kirby\Http\Environment; use Kirby\Http\Request; use Kirby\Http\Response; +use Kirby\Http\Route; use Kirby\Http\Router; use Kirby\Http\Uri; use Kirby\Http\Visitor; use Kirby\Session\AutoSession; +use Kirby\Session\Session; use Kirby\Template\Snippet; +use Kirby\Template\Template; use Kirby\Text\KirbyTag; use Kirby\Text\KirbyTags; use Kirby\Toolkit\A; use Kirby\Toolkit\Config; use Kirby\Toolkit\Controller; -use Kirby\Toolkit\Properties; +use Kirby\Toolkit\LazyValue; use Kirby\Toolkit\Str; use Kirby\Uuid\Uuid; use Throwable; @@ -49,45 +54,43 @@ class App use AppPlugins; use AppTranslations; use AppUsers; - use Properties; public const CLASS_ALIAS = 'kirby'; - protected static $instance; - protected static $version; + protected static App|null $instance = null; + protected static string|null $version = null; - public $data = []; + public array $data = []; - protected $api; - protected $collections; - protected $core; - protected $defaultLanguage; - protected $environment; - protected $language; - protected $languages; - protected $locks; - protected $multilang; - protected $nonce; - protected $options; - protected $path; - protected $request; - protected $response; - protected $roles; - protected $roots; - protected $routes; - protected $router; - protected $sessionHandler; - protected $site; - protected $system; - protected $urls; - protected $user; - protected $users; - protected $visitor; + protected Api|null $api = null; + protected Collections|null $collections = null; + protected Core $core; + protected Language|null $defaultLanguage = null; + protected Environment|null $environment = null; + protected Language|null $language = null; + protected Languages|null $languages = null; + protected ContentLocks|null $locks = null; + protected bool|null $multilang = null; + protected string|null $nonce = null; + protected array $options; + protected string|null $path = null; + protected Request|null $request = null; + protected Responder|null $response = null; + protected Roles|null $roles = null; + protected Ingredients $roots; + protected array|null $routes = null; + protected Router|null $router = null; + protected AutoSession|null $sessionHandler = null; + protected Site|null $site = null; + protected System|null $system = null; + protected Ingredients $urls; + protected Visitor|null $visitor = null; + + protected array $propertyData; /** * Creates a new App instance * - * @param array $props * @param bool $setInstance If false, the instance won't be set globally */ public function __construct(array $props = [], bool $setInstance = true) @@ -109,6 +112,8 @@ class App $this->handleErrors(); } + $this->propertyData = $props; + // a custom request setup must come before defining the path $this->setRequest($props['request'] ?? null); @@ -120,17 +125,15 @@ class App $this->bakeUrls($props['urls'] ?? []); // configurable properties - $this->setOptionalProperties($props, [ - 'languages', - 'roles', - 'site', - 'user', - 'users' - ]); + $this->setLanguages($props['languages'] ?? null); + $this->setRoles($props['roles'] ?? null); + $this->setSite($props['site'] ?? null); + $this->setUser($props['user'] ?? null); + $this->setUsers($props['users'] ?? null); // set the singleton if (static::$instance === null || $setInstance === true) { - Model::$kirby = static::$instance = $this; + static::$instance = ModelWithContent::$kirby = Model::$kirby = $this; } // setup the I18n class with the translation loader @@ -156,7 +159,7 @@ class App /** * Improved `var_dump` output * - * @return array + * @codeCoverageIgnore */ public function __debugInfo(): array { @@ -175,9 +178,8 @@ class App * Returns the Api instance * * @internal - * @return \Kirby\Cms\Api */ - public function api() + public function api(): Api { if ($this->api !== null) { return $this->api; @@ -209,8 +211,12 @@ class App * @param \Kirby\Cms\Event|null $originalEvent Event object (internal use) * @return mixed Resulting value as modified by the hooks */ - public function apply(string $name, array $args, string $modify, ?Event $originalEvent = null) - { + public function apply( + string $name, + array $args, + string $modify, + Event|null $originalEvent = null + ): mixed { $event = $originalEvent ?? new Event($name, $args); if ($functions = $this->extension('hooks', $name)) { @@ -243,7 +249,7 @@ class App * * @return $this */ - protected function bakeOptions() + protected function bakeOptions(): static { // convert the old plugin option syntax to the new one foreach ($this->options as $key => $value) { @@ -276,10 +282,9 @@ class App /** * Sets the directory structure * - * @param array|null $roots * @return $this */ - protected function bakeRoots(array $roots = null) + protected function bakeRoots(array $roots = null): static { $roots = array_merge($this->core->roots(), (array)$roots); $this->roots = Ingredients::bake($roots); @@ -289,10 +294,9 @@ class App /** * Sets the Url structure * - * @param array|null $urls * @return $this */ - protected function bakeUrls(array $urls = null) + protected function bakeUrls(array $urls = null): static { $urls = array_merge($this->core->urls(), (array)$urls); $this->urls = Ingredients::bake($urls); @@ -301,9 +305,6 @@ class App /** * Returns all available blueprints for this installation - * - * @param string $type - * @return array */ public function blueprints(string $type = 'pages'): array { @@ -328,12 +329,8 @@ class App /** * Calls any Kirby route - * - * @param string|null $path - * @param string|null $method - * @return mixed */ - public function call(string $path = null, string $method = null) + public function call(string $path = null, string $method = null): mixed { $path ??= $this->path(); $method ??= $this->request()->method(); @@ -344,11 +341,9 @@ class App * Creates an instance with the same * initial properties * - * @param array $props * @param bool $setInstance If false, the instance won't be set globally - * @return static */ - public function clone(array $props = [], bool $setInstance = true) + public function clone(array $props = [], bool $setInstance = true): static { $props = array_replace_recursive($this->propertyData, $props); @@ -371,8 +366,9 @@ class App return $this->collections()->get($name, array_merge($options, [ 'kirby' => $this, 'site' => $site = $this->site(), - 'pages' => $site->children(), - 'users' => $this->users() + 'pages' => new LazyValue(fn () => $site->children()), + 'users' => new LazyValue(fn () => $this->users()) + ])); } @@ -388,10 +384,8 @@ class App * Returns a core component * * @internal - * @param string $name - * @return mixed */ - public function component($name) + public function component(string $name): mixed { return $this->extensions['components'][$name] ?? null; } @@ -400,7 +394,6 @@ class App * Returns the content extension * * @internal - * @return string */ public function contentExtension(): string { @@ -411,7 +404,6 @@ class App * Returns files that should be ignored when scanning folders * * @internal - * @return array */ public function contentIgnore(): array { @@ -424,9 +416,8 @@ class App * * @param mixed $model Object to pass to the salt callback if configured * @param string $value Model data to include in the generated token - * @return string */ - public function contentToken($model, string $value): string + public function contentToken(mixed $model, string $value): string { if (method_exists($model, 'root') === true) { $default = $model->root(); @@ -448,13 +439,12 @@ class App * and with the given arguments * * @internal - * @param string $name - * @param array $arguments - * @param string $contentType - * @return array */ - public function controller(string $name, array $arguments = [], string $contentType = 'html'): array - { + public function controller( + string $name, + array $arguments = [], + string $contentType = 'html' + ): array { $name = basename(strtolower($name)); if ($controller = $this->controllerLookup($name, $contentType)) { @@ -480,8 +470,10 @@ class App /** * Try to find a controller by name */ - protected function controllerLookup(string $name, string $contentType = 'html'): Controller|null - { + protected function controllerLookup( + string $name, + string $contentType = 'html' + ): Controller|null { if ($contentType !== null && $contentType !== 'html') { $name .= '.' . $contentType; } @@ -505,10 +497,8 @@ class App /** * Get access to object that lists * all parts of Kirby core - * - * @return \Kirby\Cms\Core */ - public function core() + public function core(): Core { return $this->core; } @@ -520,7 +510,7 @@ class App * @param string|null $check Pass a token here to compare it to the one in the session * @return string|bool Either the token or a boolean check result */ - public function csrf(string|null $check = null) + public function csrf(string|null $check = null): string|bool { $session = $this->session(); @@ -603,12 +593,8 @@ class App /** * Returns the Email singleton - * - * @param mixed $preset - * @param array $props - * @return \Kirby\Email\Email */ - public function email($preset = [], array $props = []) + public function email(mixed $preset = [], array $props = []): BaseEmail { $debug = $props['debug'] ?? false; $props = (new Email($preset, $props))->toArray(); @@ -619,24 +605,20 @@ class App /** * Returns the environment object with access * to the detected host, base url and dedicated options - * - * @return \Kirby\Http\Environment */ - public function environment() + public function environment(): Environment { - return $this->environment ?? new Environment(); + return $this->environment ??= new Environment(); } /** * Finds any file in the content directory - * - * @param string $path - * @param mixed $parent - * @param bool $drafts - * @return \Kirby\Cms\File|null */ - public function file(string $path, $parent = null, bool $drafts = true) - { + public function file( + string $path, + mixed $parent = null, + bool $drafts = true + ): File|null { // find by global UUID if (Uuid::is($path, 'file') === true) { // prefer files of parent, when parent given @@ -677,12 +659,9 @@ class App * Example: * * - * @param string|null $path - * @return \Kirby\Cms\File|null - * * @todo merge with App::file() */ - public function image(string|null $path = null) + public function image(string|null $path = null): File|null { if ($path === null) { return $this->site()->page()->image(); @@ -707,13 +686,13 @@ class App /** * Returns the current App instance * - * @param \Kirby\Cms\App|null $instance * @param bool $lazy If `true`, the instance is only returned if already existing - * @return static|null * @psalm-return ($lazy is false ? static : static|null) */ - public static function instance(self $instance = null, bool $lazy = false) - { + public static function instance( + self $instance = null, + bool $lazy = false + ): static|null { if ($instance !== null) { return static::$instance = $instance; } @@ -730,10 +709,8 @@ class App * tries to convert it into a valid response * * @internal - * @param mixed $input - * @return \Kirby\Http\Response */ - public function io($input) + public function io(mixed $input): Response { // use the current response configuration $response = $this->response(); @@ -836,13 +813,13 @@ class App * @internal * @param string|array $type Tag type or array with all tag arguments * (the key of the first element becomes the type) - * @param string|null $value - * @param array $attr - * @param array $data - * @return string */ - public function kirbytag($type, string|null $value = null, array $attr = [], array $data = []): string - { + public function kirbytag( + string|array $type, + string|null $value = null, + array $attr = [], + array $data = [] + ): string { if (is_array($type) === true) { $kirbytag = $type; $type = key($kirbytag); @@ -867,9 +844,6 @@ class App * KirbyTags Parser * * @internal - * @param string|null $text - * @param array $data - * @return string */ public function kirbytags(string $text = null, array $data = []): string { @@ -890,9 +864,6 @@ class App * Parses KirbyTags first and Markdown afterwards * * @internal - * @param string|null $text - * @param array $options - * @return string */ public function kirbytext(string $text = null, array $options = []): string { @@ -911,11 +882,8 @@ class App /** * Returns the current language - * - * @param string|null $code - * @return \Kirby\Cms\Language|null */ - public function language(string $code = null) + public function language(string $code = null): Language|null { if ($this->multilang() === false) { return null; @@ -940,8 +908,6 @@ class App * Returns the current language code * * @internal - * @param string|null $languageCode - * @return string|null */ public function languageCode(string $languageCode = null): string|null { @@ -953,6 +919,11 @@ class App */ public function languages(bool $clone = true): Languages { + if ($clone === false) { + $this->multilang = null; + $this->defaultLanguage = null; + } + if ($this->languages !== null) { return $clone === true ? clone $this->languages : $this->languages; } @@ -962,35 +933,24 @@ class App /** * Access Kirby's part loader - * - * @return \Kirby\Cms\Loader */ - public function load() + public function load(): Loader { return new Loader($this); } /** * Returns the app's locks object - * - * @return \Kirby\Cms\ContentLocks */ public function locks(): ContentLocks { - if ($this->locks !== null) { - return $this->locks; - } - - return $this->locks = new ContentLocks(); + return $this->locks ??= new ContentLocks(); } /** * Parses Markdown * * @internal - * @param string|null $text - * @param array $options - * @return string */ public function markdown(string $text = null, array $options = null): string { @@ -1004,25 +964,41 @@ class App } /** - * Check for a multilang setup + * Yields all models (site, pages, files and users) of this site + * @since 4.0.0 * - * @return bool + * @return \Generator|\Kirby\Cms\ModelWithContent[] + */ + public function models(): Generator + { + $site = $this->site(); + + yield from $site->files(); + yield $site; + + foreach ($site->index(true) as $page) { + yield from $page->files(); + yield $page; + } + + foreach ($this->users() as $user) { + yield from $user->files(); + yield $user; + } + } + + /** + * Check for a multilang setup */ public function multilang(): bool { - if ($this->multilang !== null) { - return $this->multilang; - } - - return $this->multilang = $this->languages()->count() !== 0; + return $this->multilang ??= $this->languages()->count() !== 0; } /** * Returns the nonce, which is used * in the panel for inline scripts * @since 3.3.0 - * - * @return string */ public function nonce(): string { @@ -1031,20 +1007,14 @@ class App /** * Load a specific configuration option - * - * @param string $key - * @param mixed $default - * @return mixed */ - public function option(string $key, $default = null) + public function option(string $key, mixed $default = null): mixed { return A::get($this->options, $key, $default); } /** * Returns all configuration options - * - * @return array */ public function options(): array { @@ -1053,8 +1023,6 @@ class App /** * Load all options from files in site/config - * - * @return array */ protected function optionsFromConfig(): array { @@ -1072,9 +1040,6 @@ class App /** * Load all options for the current * server environment - * - * @param array $props - * @return array */ protected function optionsFromEnvironment(array $props = []): array { @@ -1113,9 +1078,6 @@ class App /** * Inject options from Kirby instance props - * - * @param array $options - * @return array */ protected function optionsFromProps(array $options = []): array { @@ -1127,12 +1089,13 @@ class App /** * Merge last-minute options from ready callback - * - * @return array */ protected function optionsFromReadyCallback(): array { - if (isset($this->options['ready']) === true && is_callable($this->options['ready']) === true) { + if ( + isset($this->options['ready']) === true && + is_callable($this->options['ready']) === true + ) { // fetch last-minute options from the callback $options = (array)$this->options['ready']($this); @@ -1170,23 +1133,16 @@ class App /** * Returns any page from the content folder - * - * @param string|null $id - * @param \Kirby\Cms\Page|\Kirby\Cms\Site|null $parent - * @param bool $drafts - * @return \Kirby\Cms\Page|null */ - public function page(string|null $id = null, $parent = null, bool $drafts = true) - { + public function page( + string|null $id = null, + Page|Site|null $parent = null, + bool $drafts = true + ): Page|null { if ($id === null) { return null; } - // find by global UUID - if (Uuid::is($id, 'page') === true) { - return Uuid::for($id, $parent?->childrenAndDrafts())->model(); - } - $parent = $parent ?? $this->site(); if ($page = $parent->find($id)) { @@ -1206,8 +1162,6 @@ class App /** * Returns the request path - * - * @return string */ public function path(): string { @@ -1225,13 +1179,11 @@ class App /** * Returns the Response object for the * current request - * - * @param string|null $path - * @param string|null $method - * @return \Kirby\Http\Response */ - public function render(string $path = null, string $method = null) - { + public function render( + string $path = null, + string $method = null + ): Response|null { if (($_ENV['KIRBY_RENDER'] ?? true) === false) { return null; } @@ -1241,10 +1193,8 @@ class App /** * Returns the Request singleton - * - * @return \Kirby\Http\Request */ - public function request() + public function request(): Request { if ($this->request !== null) { return $this->request; @@ -1262,12 +1212,9 @@ class App * Path resolver for the router * * @internal - * @param string|null $path - * @param string|null $language - * @return mixed * @throws \Kirby\Exception\NotFoundException if the home page cannot be found */ - public function resolve(string $path = null, string $language = null) + public function resolve(string $path = null, string $language = null): mixed { // set the current translation $this->setCurrentTranslation($language); @@ -1346,29 +1293,22 @@ class App /** * Response configuration - * - * @return \Kirby\Cms\Responder */ - public function response() + public function response(): Responder { return $this->response ??= new Responder(); } /** * Returns all user roles - * - * @return \Kirby\Cms\Roles */ - public function roles() + public function roles(): Roles { return $this->roles ??= Roles::load($this->root('roles')); } /** * Returns a system root - * - * @param string $type - * @return string|null */ public function root(string $type = 'index'): string|null { @@ -1377,20 +1317,16 @@ class App /** * Returns the directory structure - * - * @return \Kirby\Cms\Ingredients */ - public function roots() + public function roots(): Ingredients { return $this->roots; } /** * Returns the currently active route - * - * @return \Kirby\Http\Route|null */ - public function route() + public function route(): Route|null { return $this->router()->route(); } @@ -1399,10 +1335,13 @@ class App * Returns the Router singleton * * @internal - * @return \Kirby\Http\Router */ - public function router() + public function router(): Router { + if ($this->router !== null) { + return $this->router; + } + $routes = $this->routes(); if ($this->multilang() === true) { @@ -1422,14 +1361,13 @@ class App } ]; - return $this->router ??= new Router($routes, $hooks); + return $this->router = new Router($routes, $hooks); } /** * Returns all defined routes * * @internal - * @return array */ public function routes(): array { @@ -1448,9 +1386,8 @@ class App * Returns the current session object * * @param array $options Additional options, see the session component - * @return \Kirby\Session\Session */ - public function session(array $options = []) + public function session(array $options = []): Session { $session = $this->sessionHandler()->get($options); @@ -1466,22 +1403,21 @@ class App /** * Returns the session handler - * - * @return \Kirby\Session\AutoSession */ - public function sessionHandler() + public function sessionHandler(): AutoSession { - $this->sessionHandler = $this->sessionHandler ?? new AutoSession($this->root('sessions'), $this->option('session', [])); - return $this->sessionHandler; + return $this->sessionHandler ??= new AutoSession( + $this->root('sessions'), + $this->option('session', []) + ); } /** * Create your own set of languages * - * @param array|null $languages * @return $this */ - protected function setLanguages(array $languages = null) + protected function setLanguages(array $languages = null): static { if ($languages !== null) { $objects = []; @@ -1500,10 +1436,9 @@ class App * Sets the request path that is * used for the router * - * @param string|null $path * @return $this */ - protected function setPath(string $path = null) + protected function setPath(string $path = null): static { $this->path = $path !== null ? trim($path, '/') : null; return $this; @@ -1512,10 +1447,9 @@ class App /** * Sets the request * - * @param array|null $request * @return $this */ - protected function setRequest(array $request = null) + protected function setRequest(array $request = null): static { if ($request !== null) { $this->request = new Request($request); @@ -1527,10 +1461,9 @@ class App /** * Create your own set of roles * - * @param array|null $roles * @return $this */ - protected function setRoles(array $roles = null) + protected function setRoles(array $roles = null): static { if ($roles !== null) { $this->roles = Roles::factory($roles, [ @@ -1544,10 +1477,9 @@ class App /** * Sets a custom Site object * - * @param \Kirby\Cms\Site|array|null $site * @return $this */ - protected function setSite($site = null) + protected function setSite(Site|array $site = null): static { if (is_array($site) === true) { $site = new Site($site + [ @@ -1561,10 +1493,8 @@ class App /** * Initializes and returns the Site object - * - * @return \Kirby\Cms\Site */ - public function site() + public function site(): Site { return $this->site ??= new Site([ 'errorPageId' => $this->options['error'] ?? 'error', @@ -1578,8 +1508,6 @@ class App * Applies the smartypants rule on the text * * @internal - * @param string|null $text - * @return string */ public function smartypants(string $text = null): string { @@ -1612,8 +1540,12 @@ class App * @param bool $return On `false`, directly echo the snippet * @psalm-return ($return is true ? string : null) */ - public function snippet(string|array|null $name, $data = [], bool $return = true, bool $slots = false): Snippet|string|null - { + public function snippet( + string|array|null $name, + array|object $data = [], + bool $return = true, + bool $slots = false + ): Snippet|string|null { if (is_object($data) === true) { $data = ['item' => $data]; } @@ -1635,10 +1567,8 @@ class App /** * System check class - * - * @return \Kirby\Cms\System */ - public function system() + public function system(): System { return $this->system ??= new System($this); } @@ -1648,23 +1578,17 @@ class App * and return the Template object * * @internal - * @return \Kirby\Template\Template - * @param string $name - * @param string $type - * @param string $defaultType */ - public function template(string $name, string $type = 'html', string $defaultType = 'html') - { + public function template( + string $name, + string $type = 'html', + string $defaultType = 'html' + ): Template { return ($this->component('template'))($this, $name, $type, $defaultType); } /** * Thumbnail creator - * - * @param string $src - * @param string $dst - * @param array $options - * @return string */ public function thumb(string $src, string $dst, array $options = []): string { @@ -1677,10 +1601,12 @@ class App * @param string $name Full event name * @param array $args Associative array of named event arguments * @param \Kirby\Cms\Event|null $originalEvent Event object (internal use) - * @return void */ - public function trigger(string $name, array $args = [], ?Event $originalEvent = null) - { + public function trigger( + string $name, + array $args = [], + Event|null $originalEvent = null + ): void { $event = $originalEvent ?? new Event($name, $args); if ($functions = $this->extension('hooks', $name)) { @@ -1719,13 +1645,13 @@ class App /** * Returns a system url * - * @param string $type * @param bool $object If set to `true`, the URL is converted to an object - * @return string|\Kirby\Http\Uri|null * @psalm-return ($object is false ? string|null : \Kirby\Http\Uri) */ - public function url(string $type = 'index', bool $object = false) - { + public function url( + string $type = 'index', + bool $object = false + ): string|Uri|null { $url = $this->urls->__get($type); if ($object === true) { @@ -1745,10 +1671,8 @@ class App /** * Returns the url structure - * - * @return \Kirby\Cms\Ingredients */ - public function urls() + public function urls(): Ingredients { return $this->urls; } @@ -1757,7 +1681,6 @@ class App * Returns the current version number from * the composer.json (Keep that up to date! :)) * - * @return string|null * @throws \Kirby\Exception\LogicException if the Kirby version cannot be detected */ public static function version(): string|null @@ -1771,8 +1694,6 @@ class App /** * Creates a hash of the version number - * - * @return string */ public static function versionHash(): string { @@ -1781,10 +1702,8 @@ class App /** * Returns the visitor object - * - * @return \Kirby\Http\Visitor */ - public function visitor() + public function visitor(): Visitor { return $this->visitor ??= new Visitor(); } diff --git a/kirby/src/Cms/AppCaches.php b/kirby/src/Cms/AppCaches.php index 0ae164f..6c1595c 100644 --- a/kirby/src/Cms/AppCaches.php +++ b/kirby/src/Cms/AppCaches.php @@ -17,15 +17,12 @@ use Kirby\Exception\InvalidArgumentException; */ trait AppCaches { - protected $caches = []; + protected array $caches = []; /** * Returns a cache instance by key - * - * @param string $key - * @return \Kirby\Cache\Cache */ - public function cache(string $key) + public function cache(string $key): Cache { if (isset($this->caches[$key]) === true) { return $this->caches[$key]; @@ -67,9 +64,6 @@ trait AppCaches /** * Returns the cache options by key - * - * @param string $key - * @return array */ protected function cacheOptions(string $key): array { @@ -106,9 +100,6 @@ trait AppCaches * Takes care of converting prefixed plugin cache setups * to the right cache key, while leaving regular cache * setups untouched. - * - * @param string $key - * @return string */ protected function cacheOptionsKey(string $key): string { diff --git a/kirby/src/Cms/AppErrors.php b/kirby/src/Cms/AppErrors.php index f61d02b..43dba9c 100644 --- a/kirby/src/Cms/AppErrors.php +++ b/kirby/src/Cms/AppErrors.php @@ -10,6 +10,7 @@ use Kirby\Toolkit\I18n; use Throwable; use Whoops\Handler\CallbackHandler; use Whoops\Handler\Handler; +use Whoops\Handler\HandlerInterface; use Whoops\Handler\PlainTextHandler; use Whoops\Handler\PrettyPageHandler; use Whoops\Run as Whoops; @@ -26,16 +27,21 @@ use Whoops\Run as Whoops; trait AppErrors { /** - * Whoops instance cache + * Allows to disable Whoops globally in CI; + * can be overridden by explicitly setting + * the `whoops` option to `true` or `false` * - * @var \Whoops\Run + * @internal */ - protected $whoops; + public static bool $enableWhoops = true; + + /** + * Whoops instance cache + */ + protected Whoops $whoops; /** * Registers the PHP error handler for CLI usage - * - * @return void */ protected function handleCliErrors(): void { @@ -45,11 +51,20 @@ trait AppErrors /** * Registers the PHP error handler * based on the environment - * - * @return void */ protected function handleErrors(): void { + // no matter the environment, exit early if + // Whoops was disabled globally + // (but continue if the option was explicitly + // set to `true` in the config) + if ( + static::$enableWhoops === false && + $this->option('whoops') !== true + ) { + return; + } + if ($this->environment()->cli() === true) { $this->handleCliErrors(); return; @@ -65,8 +80,6 @@ trait AppErrors /** * Registers the PHP error handler for HTML output - * - * @return void */ protected function handleHtmlErrors(): void { @@ -76,7 +89,7 @@ trait AppErrors if ($this->option('whoops', true) !== false) { $handler = new PrettyPageHandler(); $handler->setPageTitle('Kirby CMS Debugger'); - $handler->setResourcesPath(dirname(__DIR__, 2) . '/assets'); + $handler->addResourcePath(dirname(__DIR__, 2) . '/assets'); $handler->addCustomCss('whoops.css'); if ($editor = $this->option('editor')) { @@ -114,8 +127,6 @@ trait AppErrors /** * Registers the PHP error handler for JSON output - * - * @return void */ protected function handleJsonErrors(): void { @@ -162,11 +173,8 @@ trait AppErrors /** * Enables Whoops with the specified handler - * - * @param Callable|\Whoops\Handler\HandlerInterface $handler - * @return void */ - protected function setWhoopsHandler($handler): void + protected function setWhoopsHandler(callable|HandlerInterface $handler): void { $whoops = $this->whoops(); $whoops->clearHandlers(); @@ -190,8 +198,6 @@ trait AppErrors /** * Clears the Whoops handlers and disables Whoops - * - * @return void */ protected function unsetWhoopsHandler(): void { @@ -202,15 +208,9 @@ trait AppErrors /** * Returns the Whoops error handler instance - * - * @return \Whoops\Run */ - protected function whoops() + protected function whoops(): Whoops { - if ($this->whoops !== null) { - return $this->whoops; - } - - return $this->whoops = new Whoops(); + return $this->whoops ??= new Whoops(); } } diff --git a/kirby/src/Cms/AppPlugins.php b/kirby/src/Cms/AppPlugins.php index e0210d1..350d93d 100644 --- a/kirby/src/Cms/AppPlugins.php +++ b/kirby/src/Cms/AppPlugins.php @@ -3,6 +3,7 @@ namespace Kirby\Cms; use Closure; +use Kirby\Content\Field; use Kirby\Exception\DuplicateException; use Kirby\Filesystem\Asset; use Kirby\Filesystem\Dir; @@ -10,7 +11,6 @@ use Kirby\Filesystem\F; use Kirby\Filesystem\Mime; use Kirby\Form\Field as FormField; use Kirby\Image\Image; -use Kirby\Panel\Panel; use Kirby\Text\KirbyTag; use Kirby\Toolkit\A; use Kirby\Toolkit\Collection as ToolkitCollection; @@ -29,17 +29,13 @@ trait AppPlugins { /** * A list of all registered plugins - * - * @var array */ - protected static $plugins = []; + protected static array $plugins = []; /** * The extension registry - * - * @var array */ - protected $extensions = [ + protected array $extensions = [ // load options first to make them available for the rest 'options' => [], @@ -77,6 +73,8 @@ trait AppPlugins 'sections' => [], 'siteMethods' => [], 'snippets' => [], + 'structureMethods' => [], + 'structureObjectMethods' => [], 'tags' => [], 'templates' => [], 'thirdParty' => [], @@ -90,21 +88,19 @@ trait AppPlugins /** * Flag when plugins have been loaded * to not load them again - * - * @var bool */ - protected $pluginsAreLoaded = false; + protected bool $pluginsAreLoaded = false; /** * Register all given extensions * * @internal - * @param array $extensions * @param \Kirby\Cms\Plugin $plugin|null The plugin which defined those extensions - * @return array */ - public function extend(array $extensions, Plugin $plugin = null): array - { + public function extend( + array $extensions, + Plugin $plugin = null + ): array { foreach ($this->extensions as $type => $registered) { if (isset($extensions[$type]) === true) { $this->{'extend' . $type}($extensions[$type], $plugin); @@ -116,11 +112,8 @@ trait AppPlugins /** * Registers API extensions - * - * @param array|bool $api - * @return array */ - protected function extendApi($api): array + protected function extendApi(array|bool $api): array { if (is_array($api) === true) { if (($api['routes'] ?? []) instanceof Closure) { @@ -135,9 +128,6 @@ trait AppPlugins /** * Registers additional custom Panel areas - * - * @param array $areas - * @return array */ protected function extendAreas(array $areas): array { @@ -151,9 +141,6 @@ trait AppPlugins /** * Registers additional asset methods - * - * @param array $methods - * @return array */ protected function extendAssetMethods(array $methods): array { @@ -162,9 +149,6 @@ trait AppPlugins /** * Registers additional authentication challenges - * - * @param array $challenges - * @return array */ protected function extendAuthChallenges(array $challenges): array { @@ -173,9 +157,6 @@ trait AppPlugins /** * Registers additional block methods - * - * @param array $methods - * @return array */ protected function extendBlockMethods(array $methods): array { @@ -184,9 +165,6 @@ trait AppPlugins /** * Registers additional block models - * - * @param array $models - * @return array */ protected function extendBlockModels(array $models): array { @@ -195,9 +173,6 @@ trait AppPlugins /** * Registers additional blocks methods - * - * @param array $methods - * @return array */ protected function extendBlocksMethods(array $methods): array { @@ -206,9 +181,6 @@ trait AppPlugins /** * Registers additional blueprints - * - * @param array $blueprints - * @return array */ protected function extendBlueprints(array $blueprints): array { @@ -217,9 +189,6 @@ trait AppPlugins /** * Registers additional cache types - * - * @param array $cacheTypes - * @return array */ protected function extendCacheTypes(array $cacheTypes): array { @@ -228,9 +197,6 @@ trait AppPlugins /** * Registers additional CLI commands - * - * @param array $commands - * @return array */ protected function extendCommands(array $commands): array { @@ -239,9 +205,6 @@ trait AppPlugins /** * Registers additional collection filters - * - * @param array $filters - * @return array */ protected function extendCollectionFilters(array $filters): array { @@ -250,9 +213,6 @@ trait AppPlugins /** * Registers additional collection methods - * - * @param array $methods - * @return array */ protected function extendCollectionMethods(array $methods): array { @@ -261,9 +221,6 @@ trait AppPlugins /** * Registers additional collections - * - * @param array $collections - * @return array */ protected function extendCollections(array $collections): array { @@ -272,9 +229,6 @@ trait AppPlugins /** * Registers core components - * - * @param array $components - * @return array */ protected function extendComponents(array $components): array { @@ -283,9 +237,6 @@ trait AppPlugins /** * Registers additional controllers - * - * @param array $controllers - * @return array */ protected function extendControllers(array $controllers): array { @@ -294,9 +245,6 @@ trait AppPlugins /** * Registers additional file methods - * - * @param array $methods - * @return array */ protected function extendFileMethods(array $methods): array { @@ -305,9 +253,6 @@ trait AppPlugins /** * Registers additional custom file types and mimes - * - * @param array $fileTypes - * @return array */ protected function extendFileTypes(array $fileTypes): array { @@ -358,9 +303,6 @@ trait AppPlugins /** * Registers additional files methods - * - * @param array $methods - * @return array */ protected function extendFilesMethods(array $methods): array { @@ -369,9 +311,6 @@ trait AppPlugins /** * Registers additional field methods - * - * @param array $methods - * @return array */ protected function extendFieldMethods(array $methods): array { @@ -380,9 +319,6 @@ trait AppPlugins /** * Registers Panel fields - * - * @param array $fields - * @return array */ protected function extendFields(array $fields): array { @@ -391,9 +327,6 @@ trait AppPlugins /** * Registers hooks - * - * @param array $hooks - * @return array */ protected function extendHooks(array $hooks): array { @@ -414,20 +347,14 @@ trait AppPlugins /** * Registers markdown component - * - * @param Closure $markdown - * @return Closure */ - protected function extendMarkdown(Closure $markdown) + protected function extendMarkdown(Closure $markdown): Closure { return $this->extensions['markdown'] = $markdown; } /** * Registers additional layout methods - * - * @param array $methods - * @return array */ protected function extendLayoutMethods(array $methods): array { @@ -436,9 +363,6 @@ trait AppPlugins /** * Registers additional layout column methods - * - * @param array $methods - * @return array */ protected function extendLayoutColumnMethods(array $methods): array { @@ -447,9 +371,6 @@ trait AppPlugins /** * Registers additional layouts methods - * - * @param array $methods - * @return array */ protected function extendLayoutsMethods(array $methods): array { @@ -458,13 +379,11 @@ trait AppPlugins /** * Registers additional options - * - * @param array $options - * @param \Kirby\Cms\Plugin|null $plugin - * @return array */ - protected function extendOptions(array $options, Plugin $plugin = null): array - { + protected function extendOptions( + array $options, + Plugin $plugin = null + ): array { if ($plugin !== null) { $options = [$plugin->prefix() => $options]; } @@ -474,9 +393,6 @@ trait AppPlugins /** * Registers additional page methods - * - * @param array $methods - * @return array */ protected function extendPageMethods(array $methods): array { @@ -485,9 +401,6 @@ trait AppPlugins /** * Registers additional pages methods - * - * @param array $methods - * @return array */ protected function extendPagesMethods(array $methods): array { @@ -496,9 +409,6 @@ trait AppPlugins /** * Registers additional page models - * - * @param array $models - * @return array */ protected function extendPageModels(array $models): array { @@ -507,9 +417,6 @@ trait AppPlugins /** * Registers pages - * - * @param array $pages - * @return array */ protected function extendPages(array $pages): array { @@ -518,13 +425,11 @@ trait AppPlugins /** * Registers additional permissions - * - * @param array $permissions - * @param \Kirby\Cms\Plugin|null $plugin - * @return array */ - protected function extendPermissions(array $permissions, Plugin $plugin = null): array - { + protected function extendPermissions( + array $permissions, + Plugin $plugin = null + ): array { if ($plugin !== null) { $permissions = [$plugin->prefix() => $permissions]; } @@ -534,11 +439,8 @@ trait AppPlugins /** * Registers additional routes - * - * @param array|\Closure $routes - * @return array */ - protected function extendRoutes($routes): array + protected function extendRoutes(array|Closure $routes): array { if ($routes instanceof Closure) { $routes = $routes($this); @@ -549,9 +451,6 @@ trait AppPlugins /** * Registers Panel sections - * - * @param array $sections - * @return array */ protected function extendSections(array $sections): array { @@ -560,9 +459,6 @@ trait AppPlugins /** * Registers additional site methods - * - * @param array $methods - * @return array */ protected function extendSiteMethods(array $methods): array { @@ -571,31 +467,38 @@ trait AppPlugins /** * Registers SmartyPants component - * - * @param \Closure $smartypants - * @return \Closure */ - protected function extendSmartypants(Closure $smartypants) + protected function extendSmartypants(Closure $smartypants): Closure { return $this->extensions['smartypants'] = $smartypants; } /** * Registers additional snippets - * - * @param array $snippets - * @return array */ protected function extendSnippets(array $snippets): array { return $this->extensions['snippets'] = array_merge($this->extensions['snippets'], $snippets); } + /** + * Registers additional structure methods + */ + protected function extendStructureMethods(array $methods): array + { + return $this->extensions['structureMethods'] = Structure::$methods = array_merge(Structure::$methods, $methods); + } + + /** + * Registers additional structure object methods + */ + protected function extendStructureObjectMethods(array $methods): array + { + return $this->extensions['structureObjectMethods'] = StructureObject::$methods = array_merge(StructureObject::$methods, $methods); + } + /** * Registers additional KirbyTags - * - * @param array $tags - * @return array */ protected function extendTags(array $tags): array { @@ -604,9 +507,6 @@ trait AppPlugins /** * Registers additional templates - * - * @param array $templates - * @return array */ protected function extendTemplates(array $templates): array { @@ -615,9 +515,6 @@ trait AppPlugins /** * Registers translations - * - * @param array $translations - * @return array */ protected function extendTranslations(array $translations): array { @@ -628,9 +525,6 @@ trait AppPlugins * Add third party extensions to the registry * so they can be used as plugins for plugins * for example. - * - * @param array $extensions - * @return array */ protected function extendThirdParty(array $extensions): array { @@ -639,9 +533,6 @@ trait AppPlugins /** * Registers additional user methods - * - * @param array $methods - * @return array */ protected function extendUserMethods(array $methods): array { @@ -650,9 +541,6 @@ trait AppPlugins /** * Registers additional user models - * - * @param array $models - * @return array */ protected function extendUserModels(array $models): array { @@ -661,9 +549,6 @@ trait AppPlugins /** * Registers additional users methods - * - * @param array $methods - * @return array */ protected function extendUsersMethods(array $methods): array { @@ -672,9 +557,6 @@ trait AppPlugins /** * Registers additional custom validators - * - * @param array $validators - * @return array */ protected function extendValidators(array $validators): array { @@ -687,11 +569,12 @@ trait AppPlugins * @internal * @param string $type i.e. `'hooks'` * @param string $name i.e. `'page.delete:before'` - * @param mixed $fallback - * @return mixed */ - public function extension(string $type, string $name, $fallback = null) - { + public function extension( + string $type, + string $name, + mixed $fallback = null + ): mixed { return $this->extensions($type)[$name] ?? $fallback; } @@ -699,10 +582,8 @@ trait AppPlugins * Returns the extensions registry * * @internal - * @param string|null $type - * @return array */ - public function extensions(string $type = null) + public function extensions(string $type = null): array { if ($type === null) { return $this->extensions; @@ -716,7 +597,7 @@ trait AppPlugins * This is only used for models for now, but * could be extended later */ - protected function extensionsFromFolders() + protected function extensionsFromFolders(): void { $models = []; @@ -739,10 +620,8 @@ trait AppPlugins * Register extensions that could be located in * the options array. I.e. hooks and routes can be * setup from the config. - * - * @return void */ - protected function extensionsFromOptions() + protected function extensionsFromOptions(): void { // register routes and hooks from options $this->extend([ @@ -754,10 +633,8 @@ trait AppPlugins /** * Apply all plugin extensions - * - * @return void */ - protected function extensionsFromPlugins() + protected function extensionsFromPlugins(): void { // register all their extensions foreach ($this->plugins() as $plugin) { @@ -771,21 +648,16 @@ trait AppPlugins /** * Apply all passed extensions - * - * @param array $props - * @return void */ - protected function extensionsFromProps(array $props) + protected function extensionsFromProps(array $props): void { $this->extend($props); } /** * Apply all default extensions - * - * @return void */ - protected function extensionsFromSystem() + protected function extensionsFromSystem(): void { // mixins FormField::$mixins = $this->core->fieldMixins(); @@ -813,9 +685,6 @@ trait AppPlugins /** * Checks if a native component was extended * @since 3.7.0 - * - * @param string $component - * @return bool */ public function isNativeComponent(string $component): bool { @@ -825,11 +694,8 @@ trait AppPlugins /** * Returns the native implementation * of a core component - * - * @param string $component - * @return \Closure|false */ - public function nativeComponent(string $component) + public function nativeComponent(string $component): Closure|false { return $this->core->components()[$component] ?? false; } @@ -837,13 +703,13 @@ trait AppPlugins /** * Kirby plugin factory and getter * - * @param string $name * @param array|null $extends If null is passed it will be used as getter. Otherwise as factory. - * @return \Kirby\Cms\Plugin|null * @throws \Kirby\Exception\DuplicateException */ - public static function plugin(string $name, array $extends = null) - { + public static function plugin( + string $name, + array $extends = null + ): PLugin|null { if ($extends === null) { return static::$plugins[$name] ?? null; } @@ -867,7 +733,6 @@ trait AppPlugins * * @internal * @param array|null $plugins Can be used to overwrite the plugins registry - * @return array */ public function plugins(array $plugins = null): array { diff --git a/kirby/src/Cms/AppTranslations.php b/kirby/src/Cms/AppTranslations.php index 74f332c..5efc39d 100644 --- a/kirby/src/Cms/AppTranslations.php +++ b/kirby/src/Cms/AppTranslations.php @@ -17,12 +17,10 @@ use Kirby\Toolkit\Str; */ trait AppTranslations { - protected $translations; + protected Translations|null $translations = null; /** * Setup internationalization - * - * @return void */ protected function i18n(): void { @@ -88,8 +86,6 @@ trait AppTranslations * Returns the language code that will be used * for the Panel if no user is logged in or if * no language is configured for the user - * - * @return string */ public function panelLanguage(): string { @@ -113,11 +109,10 @@ trait AppTranslations * Otherwise fall back to the default language * * @internal - * @param string|null $languageCode - * @return \Kirby\Cms\Language|null */ - public function setCurrentLanguage(string $languageCode = null) - { + public function setCurrentLanguage( + string $languageCode = null + ): Language|null { if ($this->multilang() === false) { Locale::set($this->option('locale', 'en_US.utf-8')); return $this->language = null; @@ -140,8 +135,6 @@ trait AppTranslations * Set the current translation * * @internal - * @param string|null $translationCode - * @return void */ public function setCurrentTranslation(string $translationCode = null): void { @@ -152,9 +145,8 @@ trait AppTranslations * Load a specific translation by locale * * @param string|null $locale Locale name or `null` for the current locale - * @return \Kirby\Cms\Translation */ - public function translation(string|null $locale = null) + public function translation(string|null $locale = null): Translation { $locale = $locale ?? I18n::locale(); $locale = basename($locale); @@ -180,10 +172,8 @@ trait AppTranslations /** * Returns all available translations - * - * @return \Kirby\Cms\Translations */ - public function translations() + public function translations(): Translations { if ($this->translations instanceof Translations) { return $this->translations; @@ -207,8 +197,6 @@ trait AppTranslations } } - $this->translations = Translations::load($this->root('i18n:translations'), $translations); - - return $this->translations; + return $this->translations = Translations::load($this->root('i18n:translations'), $translations); } } diff --git a/kirby/src/Cms/AppUsers.php b/kirby/src/Cms/AppUsers.php index a370b3a..b479e1e 100644 --- a/kirby/src/Cms/AppUsers.php +++ b/kirby/src/Cms/AppUsers.php @@ -16,20 +16,15 @@ use Throwable; */ trait AppUsers { - /** - * Cache for the auth auth layer - * - * @var Auth - */ - protected $auth; + protected Auth|null $auth = null; + protected User|string|null $user = null; + protected Users|null $users = null; /** * Returns the Authentication layer class - * * @internal - * @return \Kirby\Cms\Auth */ - public function auth() + public function auth(): Auth { return $this->auth ??= new Auth($this); } @@ -48,8 +43,10 @@ trait AppUsers * if called with callback: Return value from the callback * @throws \Throwable */ - public function impersonate(string|null $who = null, Closure|null $callback = null) - { + public function impersonate( + string|null $who = null, + Closure|null $callback = null + ): mixed { $auth = $this->auth(); $userBefore = $auth->currentUserFromImpersonation(); @@ -73,10 +70,9 @@ trait AppUsers /** * Set the currently active user id * - * @param \Kirby\Cms\User|string $user - * @return \Kirby\Cms\App + * @return $this */ - protected function setUser($user = null) + protected function setUser(User|string $user = null): static { $this->user = $user; return $this; @@ -85,10 +81,9 @@ trait AppUsers /** * Create your own set of app users * - * @param array|null $users - * @return \Kirby\Cms\App + * @return $this */ - protected function setUsers(array $users = null) + protected function setUsers(array $users = null): static { if ($users !== null) { $this->users = Users::factory($users, [ @@ -103,14 +98,14 @@ trait AppUsers * Returns a specific user by id * or the current user if no id is given * - * @param string|null $id * @param bool $allowImpersonation If set to false, only the actually * logged in user will be returned * (when `$id` is passed as `null`) - * @return \Kirby\Cms\User|null */ - public function user(string|null $id = null, bool $allowImpersonation = true) - { + public function user( + string|null $id = null, + bool $allowImpersonation = true + ): User|null { if ($id !== null) { return $this->users()->find($id); } @@ -128,15 +123,12 @@ trait AppUsers /** * Returns all users - * - * @return \Kirby\Cms\Users */ - public function users() + public function users(): Users { - if ($this->users instanceof Users) { - return $this->users; - } - - return $this->users = Users::load($this->root('accounts'), ['kirby' => $this]); + return $this->users ??= Users::load( + $this->root('accounts'), + ['kirby' => $this] + ); } } diff --git a/kirby/src/Cms/Auth.php b/kirby/src/Cms/Auth.php index 6f9ed82..91f5d2b 100644 --- a/kirby/src/Cms/Auth.php +++ b/kirby/src/Cms/Auth.php @@ -32,55 +32,37 @@ class Auth /** * Available auth challenge classes * from the core and plugins - * - * @var array */ - public static $challenges = []; + public static array $challenges = []; /** * Currently impersonated user - * - * @var \Kirby\Cms\User|null */ - protected $impersonate; - - /** - * Kirby instance - * - * @var \Kirby\Cms\App - */ - protected $kirby; + protected User|null $impersonate = null; /** * Cache of the auth status object - * - * @var \Kirby\Cms\Auth\Status */ - protected $status; + protected Status|null $status = null; /** * Instance of the currently logged in user or * `false` if the user was not yet determined - * - * @var \Kirby\Cms\User|null|false */ - protected $user = false; + protected User|false|null $user = false; /** * Exception that was thrown while * determining the current user - * - * @var \Throwable */ - protected $userException; + protected Throwable|null $userException = null; /** - * @param \Kirby\Cms\App $kirby * @codeCoverageIgnore */ - public function __construct(App $kirby) - { - $this->kirby = $kirby; + public function __construct( + protected App $kirby + ) { } /** @@ -88,17 +70,18 @@ class Auth * (one-time auth code) * @since 3.5.0 * - * @param string $email * @param bool $long If `true`, a long session will be created - * @param string $mode Either 'login' or 'password-reset' - * @return \Kirby\Cms\Auth\Status + * @param 'login'|'password-reset'|'2fa' $mode Purpose of the code * * @throws \Kirby\Exception\LogicException If there is no suitable authentication challenge (only in debug mode) * @throws \Kirby\Exception\NotFoundException If the user does not exist (only in debug mode) * @throws \Kirby\Exception\PermissionException If the rate limit is exceeded */ - public function createChallenge(string $email, bool $long = false, string $mode = 'login') - { + public function createChallenge( + string $email, + bool $long = false, + string $mode = 'login' + ): Status { $email = Idn::decodeEmail($email); $session = $this->kirby->session([ @@ -145,7 +128,10 @@ class Auth $session->set('kirby.challenge.type', $challenge); if ($code !== null) { - $session->set('kirby.challenge.code', password_hash($code, PASSWORD_DEFAULT)); + $session->set( + 'kirby.challenge.code', + password_hash($code, PASSWORD_DEFAULT) + ); } break; @@ -179,10 +165,8 @@ class Auth /** * Returns the csrf token if it exists and if it is valid - * - * @return string|false */ - public function csrf() + public function csrf(): string|false { // get the csrf from the header $fromHeader = $this->kirby->request()->csrf(); @@ -201,8 +185,6 @@ class Auth /** * Returns either predefined csrf or the one from session * @since 3.6.0 - * - * @return string */ public function csrfFromSession(): string { @@ -217,11 +199,10 @@ class Auth * valid credentials * * @param \Kirby\Http\Request\Auth\BasicAuth|null $auth - * @return \Kirby\Cms\User|null * @throws \Kirby\Exception\InvalidArgumentException if the authorization header is invalid * @throws \Kirby\Exception\PermissionException if basic authentication is not allowed */ - public function currentUserFromBasicAuth(BasicAuth $auth = null) + public function currentUserFromBasicAuth(BasicAuth $auth = null): User|null { if ($this->kirby->option('api.basicAuth', false) !== true) { throw new PermissionException('Basic authentication is not activated'); @@ -240,8 +221,8 @@ class Auth } } - $request = $this->kirby->request(); - $auth = $auth ?? $request->auth(); + $request = $this->kirby->request(); + $auth ??= $request->auth(); if (!$auth || $auth->type() !== 'basic') { throw new InvalidArgumentException('Invalid authorization header'); @@ -257,10 +238,8 @@ class Auth /** * Returns the currently impersonated user - * - * @return \Kirby\Cms\User|null */ - public function currentUserFromImpersonation() + public function currentUserFromImpersonation(): User|null { return $this->impersonate; } @@ -269,12 +248,10 @@ class Auth * Returns the logged in user by checking * the current session and finding a valid * valid user id in there - * - * @param \Kirby\Session\Session|array|null $session - * @return \Kirby\Cms\User|null */ - public function currentUserFromSession($session = null) - { + public function currentUserFromSession( + Session|array $session = null + ): User|null { $session = $this->session($session); $id = $session->data()->get('kirby.userId'); @@ -318,12 +295,12 @@ class Auth * Returns the list of enabled challenges in the * configured order * @since 3.5.1 - * - * @return array */ public function enabledChallenges(): array { - return A::wrap($this->kirby->option('auth.challenges', ['email'])); + return A::wrap( + $this->kirby->option('auth.challenges', ['totp', 'email']) + ); } /** @@ -333,10 +310,9 @@ class Auth * `null` to use the actual user again, * `'kirby'` for a virtual admin user or * `'nobody'` to disable the actual user - * @return \Kirby\Cms\User|null * @throws \Kirby\Exception\NotFoundException if the given user cannot be found */ - public function impersonate(string|null $who = null) + public function impersonate(string|null $who = null): User|null { // clear the status cache $this->status = null; @@ -360,8 +336,6 @@ class Auth /** * Returns the hashed ip of the visitor * which is used to track invalid logins - * - * @return string */ public function ipHash(): string { @@ -373,9 +347,6 @@ class Auth /** * Check if logins are blocked for the current ip or email - * - * @param string $email - * @return bool */ public function isBlocked(string $email): bool { @@ -450,16 +421,12 @@ class Auth /** * Sets a user object as the current user in the cache * @internal - * - * @param \Kirby\Cms\User $user - * @return void */ public function setUser(User $user): void { // stop impersonating $this->impersonate = null; - - $this->user = $user; + $this->user = $user; // clear the status cache $this->status = null; @@ -469,13 +436,13 @@ class Auth * Returns the authentication status object * @since 3.5.1 * - * @param \Kirby\Session\Session|array|null $session * @param bool $allowImpersonation If set to false, only the actually * logged in user will be returned - * @return \Kirby\Cms\Auth\Status */ - public function status($session = null, bool $allowImpersonation = true) - { + public function status( + Session|array $session = null, + bool $allowImpersonation = true + ): Status { // try to return from cache if ($this->status && $session === null && $allowImpersonation === true) { return $this->status; @@ -588,8 +555,6 @@ class Auth /** * Returns the absolute path to the logins log - * - * @return string */ public function logfile(): string { @@ -598,8 +563,6 @@ class Auth /** * Read all tracked logins - * - * @return array */ public function log(): array { @@ -642,8 +605,6 @@ class Auth /** * Logout the current user - * - * @return void */ public function logout(): void { @@ -668,8 +629,6 @@ class Auth /** * Clears the cached user data after logout * @internal - * - * @return void */ public function flush(): void { @@ -681,12 +640,12 @@ class Auth /** * Tracks a login * - * @param string|null $email * @param bool $triggerHook If `false`, no user.login:failed hook is triggered - * @return bool */ - public function track(string|null $email, bool $triggerHook = true): bool - { + public function track( + string|null $email, + bool $triggerHook = true + ): bool { if ($triggerHook === true) { $this->kirby->trigger('user.login:failed', compact('email')); } @@ -730,7 +689,6 @@ class Auth * @param bool $allowImpersonation If set to false, 'impersonate' won't * be returned as authentication type * even if an impersonation is active - * @return string */ public function type(bool $allowImpersonation = true): string { @@ -759,15 +717,15 @@ class Auth /** * Validates the currently logged in user * - * @param \Kirby\Session\Session|array|null $session * @param bool $allowImpersonation If set to false, only the actually * logged in user will be returned - * @return \Kirby\Cms\User|null * * @throws \Throwable If an authentication error occurred */ - public function user($session = null, bool $allowImpersonation = true) - { + public function user( + Session|array $session = null, + bool $allowImpersonation = true + ): User|null { if ($allowImpersonation === true && $this->impersonate !== null) { return $this->impersonate; } @@ -820,7 +778,7 @@ class Auth public function verifyChallenge( #[SensitiveParameter] string $code - ) { + ): User { try { $session = $this->kirby->session(); @@ -887,9 +845,11 @@ class Auth throw new PermissionException(['key' => 'access.code']); } - throw new LogicException('Invalid authentication challenge: ' . $challenge); + throw new LogicException( + 'Invalid authentication challenge: ' . $challenge + ); } catch (Throwable $e) { - $details = $e instanceof \Kirby\Exception\Exception ? $e->getDetails() : []; + $details = $e instanceof Exception ? $e->getDetails() : []; if ( empty($email) === false && @@ -925,8 +885,10 @@ class Auth * @throws \Throwable Either the passed `$exception` or the `$fallback` * (no exception if debugging is disabled and no fallback was passed) */ - protected function fail(Throwable $exception, Throwable $fallback = null): void - { + protected function fail( + Throwable $exception, + Throwable $fallback = null + ): void { $debug = $this->kirby->option('auth.debug', 'log'); // throw the original exception only in debug mode @@ -948,11 +910,8 @@ class Auth /** * Creates a session object from the passed options - * - * @param \Kirby\Session\Session|array|null $session - * @return \Kirby\Session\Session */ - protected function session($session = null) + protected function session(Session|array $session = null): Session { // use passed session options or session object if set if (is_array($session) === true) { diff --git a/kirby/src/Cms/Auth/Challenge.php b/kirby/src/Cms/Auth/Challenge.php index 1897dfa..cee81b9 100644 --- a/kirby/src/Cms/Auth/Challenge.php +++ b/kirby/src/Cms/Auth/Challenge.php @@ -22,8 +22,7 @@ abstract class Challenge * for the passed user and purpose * * @param \Kirby\Cms\User $user User the code will be generated for - * @param string $mode Purpose of the code ('login', 'reset' or '2fa') - * @return bool + * @param 'login'|'password-reset'|'2fa' $mode Purpose of the code */ abstract public static function isAvailable(User $user, string $mode): bool; @@ -33,7 +32,7 @@ abstract class Challenge * * @param \Kirby\Cms\User $user User to generate the code for * @param array $options Details of the challenge request: - * - 'mode': Purpose of the code ('login', 'reset' or '2fa') + * - 'mode': Purpose of the code ('login', 'password-reset' or '2fa') * - 'timeout': Number of seconds the code will be valid for * @return string|null The generated and sent code or `null` in case * there was no code to generate by this algorithm @@ -47,7 +46,6 @@ abstract class Challenge * * @param \Kirby\Cms\User $user User to check the code for * @param string $code Code to verify - * @return bool */ public static function verify( User $user, diff --git a/kirby/src/Cms/Auth/EmailChallenge.php b/kirby/src/Cms/Auth/EmailChallenge.php index e0bcfbe..e2bef6f 100644 --- a/kirby/src/Cms/Auth/EmailChallenge.php +++ b/kirby/src/Cms/Auth/EmailChallenge.php @@ -23,8 +23,7 @@ class EmailChallenge extends Challenge * for the passed user and purpose * * @param \Kirby\Cms\User $user User the code will be generated for - * @param string $mode Purpose of the code ('login', 'reset' or '2fa') - * @return bool + * @param 'login'|'password-reset'|'2fa' $mode Purpose of the code */ public static function isAvailable(User $user, string $mode): bool { @@ -37,7 +36,7 @@ class EmailChallenge extends Challenge * * @param \Kirby\Cms\User $user User to generate the code for * @param array $options Details of the challenge request: - * - 'mode': Purpose of the code ('login', 'reset' or '2fa') + * - 'mode': Purpose of the code ('login', 'password-reset' or '2fa') * - 'timeout': Number of seconds the code will be valid for * @return string The generated and sent code */ diff --git a/kirby/src/Cms/Auth/Status.php b/kirby/src/Cms/Auth/Status.php index adf8bb5..ec43107 100644 --- a/kirby/src/Cms/Auth/Status.php +++ b/kirby/src/Cms/Auth/Status.php @@ -3,6 +3,7 @@ namespace Kirby\Cms\Auth; use Kirby\Cms\App; +use Kirby\Cms\User; use Kirby\Exception\InvalidArgumentException; use Kirby\Toolkit\Properties; @@ -19,44 +20,32 @@ use Kirby\Toolkit\Properties; */ class Status { - use Properties; - /** * Type of the active challenge - * - * @var string|null */ - protected $challenge = null; + protected string|null $challenge = null; /** * Challenge type to use as a fallback * when $challenge is `null` - * - * @var string|null */ - protected $challengeFallback = null; + protected string|null $challengeFallback = null; /** * Email address of the current/pending user - * - * @var string|null */ - protected $email = null; + protected string|null $email; /** * Kirby instance for user lookup - * - * @var \Kirby\Cms\App */ - protected $kirby; + protected App $kirby; /** * Authentication status: * `active|impersonated|pending|inactive` - * - * @var string */ - protected $status; + protected string $status; /** * Class constructor @@ -65,13 +54,24 @@ class Status */ public function __construct(array $props) { - $this->setProperties($props); + if (in_array($props['status'], ['active', 'impersonated', 'pending', 'inactive']) !== true) { + throw new InvalidArgumentException([ + 'data' => [ + 'argument' => '$props[\'status\']', + 'method' => 'Status::__construct' + ] + ]); + } + + $this->kirby = $props['kirby']; + $this->challenge = $props['challenge'] ?? null; + $this->challengeFallback = $props['challengeFallback'] ?? null; + $this->email = $props['email'] ?? null; + $this->status = $props['status']; } /** * Returns the authentication status - * - * @return string */ public function __toString(): string { @@ -84,7 +84,6 @@ class Status * @param bool $automaticFallback If set to `false`, no faked challenge is returned; * WARNING: never send the resulting `null` value to the * user to avoid leaking whether the pending user exists - * @return string|null */ public function challenge(bool $automaticFallback = true): string|null { @@ -100,10 +99,23 @@ class Status return $this->challenge ?? $this->challengeFallback; } + /** + * Creates a new instance while + * merging initial and new properties + */ + public function clone(array $props = []): static + { + return new static(array_replace_recursive([ + 'kirby' => $this->kirby, + 'challenge' => $this->challenge, + 'challengeFallback' => $this->challengeFallback, + 'email' => $this->email, + 'status' => $this->status, + ], $props)); + } + /** * Returns the email address of the current/pending user - * - * @return string|null */ public function email(): string|null { @@ -122,8 +134,6 @@ class Status /** * Returns an array with all public status data - * - * @return array */ public function toArray(): array { @@ -136,10 +146,8 @@ class Status /** * Returns the currently logged in user - * - * @return \Kirby\Cms\User */ - public function user() + public function user(): User|null { // for security, only return the user if they are // already logged in @@ -149,71 +157,4 @@ class Status return $this->kirby->user($this->email()); } - - /** - * Sets the type of the active challenge - * - * @param string|null $challenge - * @return $this - */ - protected function setChallenge(string|null $challenge = null) - { - $this->challenge = $challenge; - return $this; - } - - /** - * Sets the challenge type to use as - * a fallback when $challenge is `null` - * - * @param string|null $challengeFallback - * @return $this - */ - protected function setChallengeFallback(string|null $challengeFallback = null) - { - $this->challengeFallback = $challengeFallback; - return $this; - } - - /** - * Sets the email address of the current/pending user - * - * @param string|null $email - * @return $this - */ - protected function setEmail(string|null $email = null) - { - $this->email = $email; - return $this; - } - - /** - * Sets the Kirby instance for user lookup - * - * @param \Kirby\Cms\App $kirby - * @return $this - */ - protected function setKirby(App $kirby) - { - $this->kirby = $kirby; - return $this; - } - - /** - * Sets the authentication status - * - * @param string $status `active|impersonated|pending|inactive` - * @return $this - */ - protected function setStatus(string $status) - { - if (in_array($status, ['active', 'impersonated', 'pending', 'inactive']) !== true) { - throw new InvalidArgumentException([ - 'data' => ['argument' => '$props[\'status\']', 'method' => 'Status::__construct'] - ]); - } - - $this->status = $status; - return $this; - } } diff --git a/kirby/src/Cms/Auth/TotpChallenge.php b/kirby/src/Cms/Auth/TotpChallenge.php new file mode 100644 index 0000000..67142db --- /dev/null +++ b/kirby/src/Cms/Auth/TotpChallenge.php @@ -0,0 +1,65 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier + * @license https://getkirby.com/license + */ +class TotpChallenge extends Challenge +{ + /** + * Checks whether the challenge is available + * for the passed user and purpose + * + * @param \Kirby\Cms\User $user User the code will be generated for + * @param 'login'|'password-reset'|'2fa' $mode Purpose of the code + */ + public static function isAvailable(User $user, string $mode): bool + { + // user needs to have a TOTP secret set up + return $user->secret('totp') !== null; + } + + /** + * Generates a random one-time auth code and returns that code + * for later verification + * + * @param \Kirby\Cms\User $user User to generate the code for + * @param array $options Details of the challenge request: + * - 'mode': Purpose of the code ('login', 'password-reset' or '2fa') + * - 'timeout': Number of seconds the code will be valid for + * @todo set return type to `null` once support for PHP 8.1 is dropped + */ + public static function create(User $user, array $options): string|null + { + // the user's app will generate the code, we only verify it + return null; + } + + /** + * Verifies the provided code against the created one + * + * @param \Kirby\Cms\User $user User to check the code for + * @param string $code Code to verify + */ + public static function verify(User $user, string $code): bool + { + // verify if code is current, previous or next TOTP code + $secret = $user->secret('totp'); + $totp = new Totp($secret); + return $totp->verify($code); + } +} diff --git a/kirby/src/Cms/Block.php b/kirby/src/Cms/Block.php index 81dbe75..f83c547 100644 --- a/kirby/src/Cms/Block.php +++ b/kirby/src/Cms/Block.php @@ -2,6 +2,8 @@ namespace Kirby\Cms; +use Kirby\Content\Content; +use Kirby\Content\Field; use Kirby\Exception\InvalidArgumentException; use Kirby\Toolkit\Str; use Throwable; @@ -24,36 +26,19 @@ class Block extends Item public const ITEMS_CLASS = Blocks::class; - /** - * @var \Kirby\Cms\Content - */ - protected $content; - - /** - * @var bool - */ - protected $isHidden; - /** * Registry with all block models - * - * @var array */ - public static $models = []; + public static array $models = []; - /** - * @var string - */ - protected $type; + protected Content $content; + protected bool $isHidden; + protected string $type; /** * Proxy for content fields - * - * @param string $method - * @param array $args - * @return \Kirby\Cms\Field */ - public function __call(string $method, array $args = []) + public function __call(string $method, array $args = []): mixed { // block methods if ($this->hasMethod($method)) { @@ -66,7 +51,6 @@ class Block extends Item /** * Creates a new block object * - * @param array $params * @throws \Kirby\Exception\InvalidArgumentException */ public function __construct(array $params) @@ -90,18 +74,15 @@ class Block extends Item $params['content'] = []; } - $this->content = $params['content']; $this->isHidden = $params['isHidden'] ?? false; $this->type = $params['type']; // create the content object - $this->content = new Content($this->content, $this->parent); + $this->content = new Content($params['content'], $this->parent); } /** * Converts the object to a string - * - * @return string */ public function __toString(): string { @@ -110,18 +91,14 @@ class Block extends Item /** * Returns the content object - * - * @return \Kirby\Cms\Content */ - public function content() + public function content(): Content { return $this->content; } /** * Controller for the block snippet - * - * @return array */ public function controller(): array { @@ -140,28 +117,26 @@ class Block extends Item * Converts the block to HTML and then * uses the Str::excerpt method to create * a non-formatted, shortened excerpt from it - * - * @param mixed ...$args - * @return string */ - public function excerpt(...$args) + public function excerpt(mixed ...$args): string { return Str::excerpt($this->toHtml(), ...$args); } /** * Constructs a block object with registering blocks models - * - * @param array $params - * @return static - * @throws \Kirby\Exception\InvalidArgumentException * @internal + * + * @throws \Kirby\Exception\InvalidArgumentException */ - public static function factory(array $params) + public static function factory(array $params): static { $type = $params['type'] ?? null; - if (empty($type) === false && $class = (static::$models[$type] ?? null)) { + if ( + empty($type) === false && + $class = (static::$models[$type] ?? null) + ) { $object = new $class($params); if ($object instanceof self) { @@ -170,7 +145,7 @@ class Block extends Item } // default model for blocks - if ($class = (static::$models['Kirby\Cms\Block'] ?? null)) { + if ($class = (static::$models['default'] ?? null)) { $object = new $class($params); if ($object instanceof self) { @@ -183,8 +158,6 @@ class Block extends Item /** * Checks if the block is empty - * - * @return bool */ public function isEmpty(): bool { @@ -194,8 +167,6 @@ class Block extends Item /** * Checks if the block is hidden * from being rendered in the frontend - * - * @return bool */ public function isHidden(): bool { @@ -204,8 +175,6 @@ class Block extends Item /** * Checks if the block is not empty - * - * @return bool */ public function isNotEmpty(): bool { @@ -214,18 +183,14 @@ class Block extends Item /** * Returns the sibling collection that filtered by block status - * - * @return \Kirby\Cms\Collection */ - protected function siblingsCollection() + protected function siblingsCollection(): Blocks { return $this->siblings->filter('isHidden', $this->isHidden()); } /** * Returns the block type - * - * @return string */ public function type(): string { @@ -235,8 +200,6 @@ class Block extends Item /** * The result is being sent to the editor * via the API in the panel - * - * @return array */ public function toArray(): array { @@ -253,24 +216,24 @@ class Block extends Item * and then places that inside a field * object. This can be used further * with all available field methods - * - * @return \Kirby\Cms\Field */ - public function toField() + public function toField(): Field { return new Field($this->parent(), $this->id(), $this->toHtml()); } /** * Converts the block to HTML - * - * @return string */ public function toHtml(): string { try { $kirby = $this->parent()->kirby(); - return (string)$kirby->snippet('blocks/' . $this->type(), $this->controller(), true); + return (string)$kirby->snippet( + 'blocks/' . $this->type(), + $this->controller(), + true + ); } catch (Throwable $e) { if ($kirby->option('debug') === true) { return '

      Block error: "' . $e->getMessage() . '" in block type: "' . $this->type() . '"

      '; diff --git a/kirby/src/Cms/BlockConverter.php b/kirby/src/Cms/BlockConverter.php index a71f69e..6065fe2 100644 --- a/kirby/src/Cms/BlockConverter.php +++ b/kirby/src/Cms/BlockConverter.php @@ -64,8 +64,8 @@ class BlockConverter foreach ($blocks as $index => $block) { if (in_array($block['type'], ['ul', 'ol']) === true) { - $prev = $blocks[$index-1] ?? null; - $next = $blocks[$index+1] ?? null; + $prev = $blocks[$index - 1] ?? null; + $next = $blocks[$index + 1] ?? null; // new list starts here if (!$prev || $prev['type'] !== $block['type']) { diff --git a/kirby/src/Cms/Blocks.php b/kirby/src/Cms/Blocks.php index af17d99..6ea7a6c 100644 --- a/kirby/src/Cms/Blocks.php +++ b/kirby/src/Cms/Blocks.php @@ -27,16 +27,12 @@ class Blocks extends Items /** * All registered blocks methods - * - * @var array */ - public static $methods = []; + public static array $methods = []; /** * Return HTML when the collection is * converted to a string - * - * @return string */ public function __toString(): string { @@ -47,11 +43,8 @@ class Blocks extends Items * Converts the blocks to HTML and then * uses the Str::excerpt method to create * a non-formatted, shortened excerpt from it - * - * @param mixed ...$args - * @return string */ - public function excerpt(...$args) + public function excerpt(mixed ...$args): string { return Str::excerpt($this->toHtml(), ...$args); } @@ -59,13 +52,11 @@ class Blocks extends Items /** * Wrapper around the factory to * catch blocks from layouts - * - * @param array $items - * @param array $params - * @return \Kirby\Cms\Blocks */ - public static function factory(array $items = null, array $params = []) - { + public static function factory( + array $items = null, + array $params = [] + ): static { $items = static::extractFromLayouts($items); // @deprecated old editor format @@ -79,9 +70,6 @@ class Blocks extends Items /** * Pull out blocks from layouts - * - * @param array $input - * @return array */ protected static function extractFromLayouts(array $input): array { @@ -115,9 +103,6 @@ class Blocks extends Items /** * Checks if a given block type exists in the collection * @since 3.6.0 - * - * @param string $type - * @return bool */ public function hasType(string $type): bool { @@ -126,11 +111,8 @@ class Blocks extends Items /** * Parse and sanitize various block formats - * - * @param array|string $input - * @return array */ - public static function parse($input): array + public static function parse(array|string|null $input): array { if (empty($input) === false && is_array($input) === false) { try { @@ -175,16 +157,10 @@ class Blocks extends Items /** * Convert all blocks to HTML - * - * @return string */ public function toHtml(): string { - $html = []; - - foreach ($this->data as $block) { - $html[] = $block->toHtml(); - } + $html = A::map($this->data, fn ($block) => $block->toHtml()); return implode($html); } diff --git a/kirby/src/Cms/Blueprint.php b/kirby/src/Cms/Blueprint.php index 41a3f58..2121e9d 100644 --- a/kirby/src/Cms/Blueprint.php +++ b/kirby/src/Cms/Blueprint.php @@ -36,12 +36,8 @@ class Blueprint /** * Magic getter/caller for any blueprint prop - * - * @param string $key - * @param array|null $arguments - * @return mixed */ - public function __call(string $key, array $arguments = null) + public function __call(string $key, array $arguments = null): mixed { return $this->props[$key] ?? null; } @@ -49,7 +45,6 @@ class Blueprint /** * Creates a new blueprint object with the given props * - * @param array $props * @throws \Kirby\Exception\InvalidArgumentException If the blueprint model is missing */ public function __construct(array $props) @@ -77,7 +72,8 @@ class Blueprint $props['name'] ??= 'default'; // normalize and translate the title - $props['title'] = $this->i18n($props['title'] ?? ucfirst($props['name'])); + $props['title'] ??= ucfirst($props['name']); + $props['title'] = $this->i18n($props['title']); // convert all shortcuts $props = $this->convertFieldsToSections('main', $props); @@ -93,7 +89,7 @@ class Blueprint /** * Improved `var_dump` output * - * @return array + * @codeCoverageIgnore */ public function __debugInfo(): array { @@ -103,13 +99,11 @@ class Blueprint /** * Converts all column definitions, that * are not wrapped in a tab, into a generic tab - * - * @param string $tabName - * @param array $props - * @return array */ - protected function convertColumnsToTabs(string $tabName, array $props): array - { + protected function convertColumnsToTabs( + string $tabName, + array $props + ): array { if (isset($props['columns']) === false) { return $props; } @@ -130,13 +124,11 @@ class Blueprint * Converts all field definitions, that are not * wrapped in a fields section into a generic * fields section. - * - * @param string $tabName - * @param array $props - * @return array */ - protected function convertFieldsToSections(string $tabName, array $props): array - { + protected function convertFieldsToSections( + string $tabName, + array $props + ): array { if (isset($props['fields']) === false) { return $props; } @@ -157,13 +149,11 @@ class Blueprint /** * Converts all sections that are not wrapped in * columns, into a single generic column. - * - * @param string $tabName - * @param array $props - * @return array */ - protected function convertSectionsToColumns(string $tabName, array $props): array - { + protected function convertSectionsToColumns( + string $tabName, + array $props + ): array { if (isset($props['sections']) === false) { return $props; } @@ -187,7 +177,6 @@ class Blueprint * props is just a string * * @param array|string $props - * @return array */ public static function extend($props): array { @@ -221,14 +210,12 @@ class Blueprint /** * Create a new blueprint for a model - * - * @param string $name - * @param string|null $fallback - * @param \Kirby\Cms\Model $model - * @return static|null */ - public static function factory(string $name, string $fallback = null, Model $model) - { + public static function factory( + string $name, + string $fallback = null, + ModelWithContent $model + ): static|null { try { $props = static::load($name); } catch (Exception) { @@ -247,9 +234,6 @@ class Blueprint /** * Returns a single field definition by name - * - * @param string $name - * @return array|null */ public function field(string $name): array|null { @@ -258,8 +242,6 @@ class Blueprint /** * Returns all field definitions - * - * @return array */ public function fields(): array { @@ -269,8 +251,6 @@ class Blueprint /** * Find a blueprint by name * - * @param string $name - * @return array * @throws \Kirby\Exception\NotFoundException If the blueprint cannot be found */ public static function find(string $name): array @@ -283,8 +263,10 @@ class Blueprint $root = $kirby->root('blueprints'); $file = $root . '/' . $name . '.yml'; - // first try to find a site blueprint, - // then check in the plugin extensions + // first try to find the blueprint in the `site/blueprints` root, + // then check in the plugin extensions which includes some default + // core blueprints (e.g. page, file, site and block defaults) + // as well as blueprints provided by plugins if (F::exists($file, $root) !== true) { $file = $kirby->extension('blueprints', $name); } @@ -311,20 +293,14 @@ class Blueprint /** * Used to translate any label, heading, etc. - * - * @param mixed $value - * @param mixed $fallback - * @return mixed */ - protected function i18n($value, $fallback = null) + protected function i18n(mixed $value, mixed $fallback = null): mixed { - return I18n::translate($value, $fallback ?? $value); + return I18n::translate($value, $fallback) ?? $value; } /** * Checks if this is the default blueprint - * - * @return bool */ public function isDefault(): bool { @@ -333,44 +309,33 @@ class Blueprint /** * Loads a blueprint from file or array - * - * @param string $name - * @return array */ public static function load(string $name): array { $props = static::find($name); - $normalize = function ($props) use ($name) { - // inject the filename as name if no name is set - $props['name'] ??= $name; + // inject the filename as name if no name is set + $props['name'] ??= $name; - // normalize the title - $title = $props['title'] ?? ucfirst($props['name']); + // normalize the title + $title = $props['title'] ?? ucfirst($props['name']); - // translate the title - $props['title'] = I18n::translate($title, $title); + // translate the title + $props['title'] = I18n::translate($title) ?? $title; - return $props; - }; - - return $normalize($props); + return $props; } /** * Returns the parent model - * - * @return \Kirby\Cms\Model */ - public function model() + public function model(): ModelWithContent { return $this->model; } /** * Returns the blueprint name - * - * @return string */ public function name(): string { @@ -379,10 +344,6 @@ class Blueprint /** * Normalizes all required props in a column setup - * - * @param string $tabName - * @param array $columns - * @return array */ protected function normalizeColumns(string $tabName, array $columns): array { @@ -415,10 +376,6 @@ class Blueprint return $columns; } - /** - * @param array $items - * @return string - */ public static function helpList(array $items): string { $md = []; @@ -434,7 +391,6 @@ class Blueprint * Normalize field props for a single field * * @param array|string $props - * @return array * @throws \Kirby\Exception\InvalidArgumentException If the filed name is missing or the field type is invalid */ public static function fieldProps($props): array @@ -486,10 +442,6 @@ class Blueprint /** * Creates an error field with the given error message - * - * @param string $name - * @param string $message - * @return array */ public static function fieldError(string $name, string $message): array { @@ -505,9 +457,6 @@ class Blueprint /** * Normalizes all fields and adds automatic labels, * types and widths. - * - * @param array $fields - * @return array */ public static function fieldsProps($fields): array { @@ -568,12 +517,12 @@ class Blueprint * constructor of an extended class, if you want to make use of it. * * @param array|true|false|null|string $options - * @param array $defaults - * @param array $aliases - * @return array */ - protected function normalizeOptions($options, array $defaults, array $aliases = []): array - { + protected function normalizeOptions( + $options, + array $defaults, + array $aliases = [] + ): array { // return defaults when options are not defined or set to true if ($options === true) { return $defaults; @@ -601,13 +550,11 @@ class Blueprint /** * Normalizes all required keys in sections - * - * @param string $tabName - * @param array $sections - * @return array */ - protected function normalizeSections(string $tabName, array $sections): array - { + protected function normalizeSections( + string $tabName, + array $sections + ): array { foreach ($sections as $sectionName => $sectionProps) { // unset / remove section if its property is false if ($sectionProps === false) { @@ -683,9 +630,6 @@ class Blueprint /** * Normalizes all required keys in tabs - * - * @param array $tabs - * @return array */ protected function normalizeTabs($tabs): array { @@ -723,9 +667,6 @@ class Blueprint /** * Injects a blueprint preset - * - * @param array $props - * @return array */ protected function preset(array $props): array { @@ -748,11 +689,8 @@ class Blueprint /** * Returns a single section by name - * - * @param string $name - * @return \Kirby\Cms\Section|null */ - public function section(string $name) + public function section(string $name): Section|null { if (empty($this->sections[$name]) === true) { return null; @@ -770,8 +708,6 @@ class Blueprint /** * Returns all sections - * - * @return array */ public function sections(): array { @@ -783,9 +719,6 @@ class Blueprint /** * Returns a single tab by name - * - * @param string|null $name - * @return array|null */ public function tab(string|null $name = null): array|null { @@ -798,8 +731,6 @@ class Blueprint /** * Returns all tabs - * - * @return array */ public function tabs(): array { @@ -808,8 +739,6 @@ class Blueprint /** * Returns the blueprint title - * - * @return string */ public function title(): string { @@ -818,8 +747,6 @@ class Blueprint /** * Converts the blueprint object to a plain array - * - * @return array */ public function toArray(): array { diff --git a/kirby/src/Cms/Collection.php b/kirby/src/Cms/Collection.php index 7b525de..a4a5cb6 100644 --- a/kirby/src/Cms/Collection.php +++ b/kirby/src/Cms/Collection.php @@ -72,9 +72,7 @@ class Collection extends BaseCollection * child classes can override it again to add validation * and custom behavior depending on the object type * - * @param string $id * @param object $object - * @return void */ public function __set(string $id, $object): void { @@ -142,7 +140,6 @@ class Collection extends BaseCollection /** * Find a single element by an attribute and its value * - * @param string $attribute * @param mixed $value * @return mixed|null */ @@ -162,11 +159,11 @@ class Collection extends BaseCollection * with an item for each group and a collection for each group. * * @param string|Closure $field - * @param bool $i Ignore upper/lowercase for group names + * @param bool $caseInsensitive Ignore upper/lowercase for group names * @return \Kirby\Cms\Collection * @throws \Kirby\Exception\Exception */ - public function group($field, bool $i = true) + public function group($field, bool $caseInsensitive = true) { if (is_string($field) === true) { $groups = new Collection([], $this->parent()); @@ -179,8 +176,12 @@ class Collection extends BaseCollection throw new InvalidArgumentException('Invalid grouping value for key: ' . $key); } + $value = (string)$value; + // ignore upper/lowercase for group names - $value = $i === true ? Str::lower($value) : (string)$value; + if ($caseInsensitive === true) { + $value = Str::lower($value); + } if (isset($groups->data[$value]) === false) { // create a new entry for the group if it does not exist yet @@ -194,7 +195,7 @@ class Collection extends BaseCollection return $groups; } - return parent::group($field, $i); + return parent::group($field, $caseInsensitive); } /** @@ -202,7 +203,6 @@ class Collection extends BaseCollection * is in the collection * * @param string|object $key - * @return bool */ public function has($key): bool { @@ -219,7 +219,6 @@ class Collection extends BaseCollection * or ids and then search accordingly. * * @param string|object $needle - * @return int|false */ public function indexOf($needle): int|false { @@ -261,20 +260,31 @@ class Collection extends BaseCollection * Add pagination and return a sliced set of data. * * @param mixed ...$arguments - * @return \Kirby\Cms\Collection + * @return $this|static */ public function paginate(...$arguments) { $this->pagination = Pagination::for($this, ...$arguments); // slice and clone the collection according to the pagination - return $this->slice($this->pagination->offset(), $this->pagination->limit()); + return $this->slice( + $this->pagination->offset(), + $this->pagination->limit() + ); + } + + /** + * Get the pagination object + * + * @return \Kirby\Cms\Pagination|null + */ + public function pagination() + { + return $this->pagination; } /** * Returns the parent model - * - * @return \Kirby\Cms\Model */ public function parent() { @@ -311,7 +321,6 @@ class Collection extends BaseCollection * offset, limit, search and paginate on the collection. * Any part of the query is optional. * - * @param array $arguments * @return static */ public function query(array $arguments = []) @@ -354,13 +363,11 @@ class Collection extends BaseCollection /** * Searches the collection - * - * @param string|null $query - * @param array $params - * @return self */ - public function search(string $query = null, $params = []) - { + public function search( + string $query = null, + string|array $params = [] + ): static { return Search::collection($this, $query, $params); } @@ -368,9 +375,6 @@ class Collection extends BaseCollection * Converts all objects in the collection * to an array. This can also take a callback * function to further modify the array result. - * - * @param \Closure|null $map - * @return array */ public function toArray(Closure $map = null): array { diff --git a/kirby/src/Cms/Collections.php b/kirby/src/Cms/Collections.php index 7e0b80c..beafe8d 100644 --- a/kirby/src/Cms/Collections.php +++ b/kirby/src/Cms/Collections.php @@ -52,8 +52,8 @@ class Collections * Loads a collection by name if registered * * @return \Kirby\Toolkit\Collection|null - * @todo 4.0 Add deprecation warning when anything else than a Collection is returned - * @todo 5.0 Add return type declaration + * @todo 5.0 Add deprecation warning when anything else than a Collection is returned + * @todo 6.0 Add PHP return type declaration for `Toolkit\Collection` */ public function get(string $name, array $data = []) { @@ -61,11 +61,9 @@ class Collections $this->collections[$name] ??= $this->load($name); // if not yet cached - if ( - isset($this->cache[$name]) === false || - $this->cache[$name]['data'] !== $data - ) { + if (($this->cache[$name]['data'] ?? null) !== $data) { $controller = new Controller($this->collections[$name]); + $this->cache[$name] = [ 'result' => $controller->call(null, $data), 'data' => $data @@ -82,9 +80,6 @@ class Collections /** * Checks if a collection exists - * - * @param string $name - * @return bool */ public function has(string $name): bool { @@ -104,11 +99,9 @@ class Collections * Loads collection from php file in a * given directory or from plugin extension. * - * @param string $name - * @return mixed * @throws \Kirby\Exception\NotFoundException */ - public function load(string $name) + public function load(string $name): mixed { $kirby = App::instance(); @@ -126,10 +119,7 @@ class Collections // fallback to collections from plugins $collections = $kirby->extensions('collections'); - if (isset($collections[$name]) === true) { - return $collections[$name]; - } - - throw new NotFoundException('The collection cannot be found'); + return $collections[$name] ?? + throw new NotFoundException('The collection cannot be found'); } } diff --git a/kirby/src/Cms/ContentLock.php b/kirby/src/Cms/ContentLock.php index 35cfa0c..1186a3f 100644 --- a/kirby/src/Cms/ContentLock.php +++ b/kirby/src/Cms/ContentLock.php @@ -2,9 +2,9 @@ namespace Kirby\Cms; +use Kirby\Exception\AuthException; use Kirby\Exception\DuplicateException; use Kirby\Exception\LogicException; -use Kirby\Exception\PermissionException; /** * Takes care of content lock and unlock information @@ -17,33 +17,16 @@ use Kirby\Exception\PermissionException; */ class ContentLock { - /** - * Lock data - * - * @var array - */ - protected $data; + protected array $data; - /** - * The model to manage locking/unlocking for - * - * @var ModelWithContent - */ - protected $model; - - /** - * @param \Kirby\Cms\ModelWithContent $model - */ - public function __construct(ModelWithContent $model) - { - $this->model = $model; - $this->data = $this->kirby()->locks()->get($model); + public function __construct( + protected ModelWithContent $model + ) { + $this->data = $this->kirby()->locks()->get($model); } /** * Clears the lock unconditionally - * - * @return bool */ protected function clearLock(): bool { @@ -61,7 +44,6 @@ class ContentLock /** * Sets lock with the current user * - * @return bool * @throws \Kirby\Exception\DuplicateException */ public function create(): bool @@ -86,10 +68,8 @@ class ContentLock /** * Returns either `false` or array with `user`, `email`, * `time` and `unlockable` keys - * - * @return array|bool */ - public function get() + public function get(): array|bool { $data = $this->data['lock'] ?? []; @@ -114,8 +94,6 @@ class ContentLock /** * Returns if the model is locked by another user - * - * @return bool */ public function isLocked(): bool { @@ -130,8 +108,6 @@ class ContentLock /** * Returns if the current user's lock has been removed by another user - * - * @return bool */ public function isUnlocked(): bool { @@ -142,8 +118,6 @@ class ContentLock /** * Returns the app instance - * - * @return \Kirby\Cms\App */ protected function kirby(): App { @@ -153,7 +127,6 @@ class ContentLock /** * Removes lock of current user * - * @return bool * @throws \Kirby\Exception\LogicException */ public function remove(): bool @@ -176,8 +149,6 @@ class ContentLock /** * Removes unlock information for current user - * - * @return bool */ public function resolve(): bool { @@ -199,7 +170,7 @@ class ContentLock * Returns the state for the * form buttons in the frontend */ - public function state(): ?string + public function state(): string|null { return match (true) { $this->isUnlocked() => 'unlock', @@ -211,8 +182,6 @@ class ContentLock /** * Returns a usable lock array * for the frontend - * - * @return array */ public function toArray(): array { @@ -224,8 +193,6 @@ class ContentLock /** * Removes current lock and adds lock user to unlock data - * - * @return bool */ public function unlock(): bool { @@ -245,12 +212,11 @@ class ContentLock * Returns currently authenticated user; * throws exception if none is authenticated * - * @return \Kirby\Cms\User * @throws \Kirby\Exception\PermissionException */ protected function user(): User { return $this->kirby()->user() ?? - throw new PermissionException('No user authenticated.'); + throw new AuthException('No user authenticated.'); } } diff --git a/kirby/src/Cms/ContentLocks.php b/kirby/src/Cms/ContentLocks.php index 2d08d9f..2d7c400 100644 --- a/kirby/src/Cms/ContentLocks.php +++ b/kirby/src/Cms/ContentLocks.php @@ -22,18 +22,14 @@ class ContentLocks * Data from the `.lock` files * that have been read so far * cached by `.lock` file path - * - * @var array */ - protected $data = []; + protected array $data = []; /** * PHP file handles for all currently * open `.lock` files - * - * @var array */ - protected $handles = []; + protected array $handles = []; /** * Closes the open file handles @@ -50,11 +46,9 @@ class ContentLocks /** * Removes the file lock and closes the file handle * - * @param string $file - * @return void * @throws \Kirby\Exception\Exception */ - protected function closeHandle(string $file) + protected function closeHandle(string $file): void { if (isset($this->handles[$file]) === false) { return; @@ -72,20 +66,15 @@ class ContentLocks /** * Returns the path to a model's lock file - * - * @param \Kirby\Cms\ModelWithContent $model - * @return string */ public static function file(ModelWithContent $model): string { - return $model->contentFileDirectory() . '/.lock'; + $root = $model::CLASS_ALIAS === 'file' ? dirname($model->root()) : $model->root(); + return $root . '/.lock'; } /** * Returns the lock/unlock data for the specified model - * - * @param \Kirby\Cms\ModelWithContent $model - * @return array */ public function get(ModelWithContent $model): array { @@ -121,7 +110,6 @@ class ContentLocks /** * Returns the file handle to a `.lock` file * - * @param string $file * @param bool $create Whether to create the file if it does not exist * @return resource|null File handle * @throws \Kirby\Exception\Exception @@ -155,9 +143,6 @@ class ContentLocks /** * Returns model ID used as the key for the data array; * prepended with a slash because the $site otherwise won't have an ID - * - * @param \Kirby\Cms\ModelWithContent $model - * @return string */ public static function id(ModelWithContent $model): string { @@ -167,9 +152,6 @@ class ContentLocks /** * Sets and writes the lock/unlock data for the specified model * - * @param \Kirby\Cms\ModelWithContent $model - * @param array $data - * @return bool * @throws \Kirby\Exception\Exception */ public function set(ModelWithContent $model, array $data): bool diff --git a/kirby/src/Cms/Core.php b/kirby/src/Cms/Core.php index 231b422..d968099 100644 --- a/kirby/src/Cms/Core.php +++ b/kirby/src/Cms/Core.php @@ -2,6 +2,15 @@ namespace Kirby\Cms; +use Kirby\Cache\ApcuCache; +use Kirby\Cache\FileCache; +use Kirby\Cache\MemCached; +use Kirby\Cache\MemoryCache; +use Kirby\Cms\Auth\EmailChallenge; +use Kirby\Cms\Auth\TotpChallenge; +use Kirby\Form\Field\BlocksField; +use Kirby\Form\Field\LayoutField; + /** * The Core class lists all parts of Kirby * that need to be loaded or initalized in order @@ -22,13 +31,11 @@ namespace Kirby\Cms; class Core { protected array $cache = []; - protected App $kirby; protected string $root; - public function __construct(App $kirby) + public function __construct(protected App $kirby) { - $this->kirby = $kirby; - $this->root = dirname(__DIR__, 2) . '/config'; + $this->root = dirname(__DIR__, 2) . '/config'; } /** @@ -52,9 +59,11 @@ class Core return [ 'account' => $this->root . '/areas/account.php', 'installation' => $this->root . '/areas/installation.php', + 'lab' => $this->root . '/areas/lab.php', 'languages' => $this->root . '/areas/languages.php', 'login' => $this->root . '/areas/login.php', 'logout' => $this->root . '/areas/logout.php', + 'search' => $this->root . '/areas/search.php', 'site' => $this->root . '/areas/site.php', 'system' => $this->root . '/areas/system.php', 'users' => $this->root . '/areas/users.php', @@ -67,7 +76,8 @@ class Core public function authChallenges(): array { return [ - 'email' => 'Kirby\Cms\Auth\EmailChallenge' + 'email' => EmailChallenge::class, + 'totp' => TotpChallenge::class, ]; } @@ -86,9 +96,9 @@ class Core } /** - * Returns a list of all paths to core blueprints + * Returns a list of paths to core blueprints or + * the blueprint in array form * - * They are located in `/kirby/config/blueprints`. * Block blueprints are located in `/kirby/config/blocks` */ public function blueprints(): array @@ -108,13 +118,21 @@ class Core 'blocks/video' => $this->root . '/blocks/video/video.yml', // file blueprints - 'files/default' => $this->root . '/blueprints/files/default.yml', + 'files/default' => ['title' => 'File'], // page blueprints - 'pages/default' => $this->root . '/blueprints/pages/default.yml', + 'pages/default' => ['title' => 'Page'], // site blueprints - 'site' => $this->root . '/blueprints/site.yml' + 'site' => [ + 'title' => 'Site', + 'sections' => [ + 'pages' => [ + 'headline' => ['*' => 'pages'], + 'type' => 'pages' + ] + ] + ] ]; } @@ -135,10 +153,10 @@ class Core public function cacheTypes(): array { return [ - 'apcu' => 'Kirby\Cache\ApcuCache', - 'file' => 'Kirby\Cache\FileCache', - 'memcached' => 'Kirby\Cache\MemCached', - 'memory' => 'Kirby\Cache\MemoryCache', + 'apcu' => ApcuCache::class, + 'file' => FileCache::class, + 'memcached' => MemCached::class, + 'memory' => MemoryCache::class, ]; } @@ -216,8 +234,9 @@ class Core public function fields(): array { return [ - 'blocks' => 'Kirby\Form\Field\BlocksField', + 'blocks' => BlocksField::class, 'checkboxes' => $this->root . '/fields/checkboxes.php', + 'color' => $this->root . '/fields/color.php', 'date' => $this->root . '/fields/date.php', 'email' => $this->root . '/fields/email.php', 'files' => $this->root . '/fields/files.php', @@ -225,8 +244,9 @@ class Core 'headline' => $this->root . '/fields/headline.php', 'hidden' => $this->root . '/fields/hidden.php', 'info' => $this->root . '/fields/info.php', - 'layout' => 'Kirby\Form\Field\LayoutField', + 'layout' => LayoutField::class, 'line' => $this->root . '/fields/line.php', + 'link' => $this->root . '/fields/link.php', 'list' => $this->root . '/fields/list.php', 'multiselect' => $this->root . '/fields/multiselect.php', 'number' => $this->root . '/fields/number.php', diff --git a/kirby/src/Cms/Email.php b/kirby/src/Cms/Email.php index 6362584..80900dc 100644 --- a/kirby/src/Cms/Email.php +++ b/kirby/src/Cms/Email.php @@ -4,6 +4,7 @@ namespace Kirby\Cms; use Kirby\Exception\InvalidArgumentException; use Kirby\Exception\NotFoundException; +use Kirby\Template\Template; /** * Wrapper around our Email package, which @@ -21,18 +22,14 @@ class Email { /** * Options configured through the `email` CMS option - * - * @var array */ - protected $options; + protected array $options; /** * Props for the email object; will be passed to the * Kirby\Email\Email class - * - * @var array */ - protected $props; + protected array $props; /** * Class constructor @@ -40,9 +37,9 @@ class Email * @param string|array $preset Preset name from the config or a simple props array * @param array $props Props array to override the $preset */ - public function __construct($preset = [], array $props = []) + public function __construct(string|array $preset = [], array $props = []) { - $this->options = App::instance()->option('email'); + $this->options = App::instance()->option('email', []); // build a prop array based on preset and props $preset = $this->preset($preset); @@ -71,10 +68,9 @@ class Email * prop arrays in case a preset is not needed * * @param string|array $preset Preset name or simple prop array - * @return array * @throws \Kirby\Exception\NotFoundException */ - protected function preset($preset): array + protected function preset(string|array $preset): array { // only passed props, not preset name if (is_array($preset) === true) { @@ -96,7 +92,6 @@ class Email * Renders the email template(s) and sets the body props * to the result * - * @return void * @throws \Kirby\Exception\NotFoundException */ protected function template(): void @@ -129,20 +124,14 @@ class Email /** * Returns an email template by name and type - * - * @param string $name Template name - * @param string|null $type `html` or `text` - * @return \Kirby\Template\Template */ - protected function getTemplate(string $name, string $type = null) + protected function getTemplate(string $name, string $type = null): Template { return App::instance()->template('emails/' . $name, $type, 'text'); } /** * Returns the prop array - * - * @return array */ public function toArray(): array { @@ -154,11 +143,10 @@ class Email * supports simple strings, file objects or collections/arrays of either * * @param string $prop Prop to transform - * @return void */ protected function transformFile(string $prop): void { - $this->props[$prop] = $this->transformModel($prop, 'Kirby\Cms\File', 'root'); + $this->props[$prop] = $this->transformModel($prop, File::class, 'root'); } /** @@ -171,8 +159,12 @@ class Email * returns a simple value-only array if not given * @return array Simple key-value or just value array with the transformed prop data */ - protected function transformModel(string $prop, string $class, string $contentValue, string $contentKey = null): array - { + protected function transformModel( + string $prop, + string $class, + string $contentValue, + string $contentKey = null + ): array { $value = $this->props[$prop] ?? []; // ensure consistent input by making everything an iterable value @@ -212,11 +204,12 @@ class Email * * @param string $addressProp Prop with the email address * @param string $nameProp Prop with the name corresponding to the $addressProp - * @return void */ - protected function transformUserSingle(string $addressProp, string $nameProp): void - { - $result = $this->transformModel($addressProp, 'Kirby\Cms\User', 'name', 'email'); + protected function transformUserSingle( + string $addressProp, + string $nameProp + ): void { + $result = $this->transformModel($addressProp, User::class, 'name', 'email'); $address = array_keys($result)[0] ?? null; $name = $result[$address] ?? null; @@ -239,10 +232,9 @@ class Email * supports simple strings, user objects or collections/arrays of either * * @param string $prop Prop to transform - * @return void */ protected function transformUserMultiple(string $prop): void { - $this->props[$prop] = $this->transformModel($prop, 'Kirby\Cms\User', 'name', 'email'); + $this->props[$prop] = $this->transformModel($prop, User::class, 'name', 'email'); } } diff --git a/kirby/src/Cms/Event.php b/kirby/src/Cms/Event.php index 9e57854..f4dd6f1 100644 --- a/kirby/src/Cms/Event.php +++ b/kirby/src/Cms/Event.php @@ -24,41 +24,31 @@ class Event /** * The full event name * (e.g. `page.create:after`) - * - * @var string */ - protected $name; + protected string $name; /** * The event type * (e.g. `page` in `page.create:after`) - * - * @var string */ - protected $type; + protected string $type; /** * The event action * (e.g. `create` in `page.create:after`) - * - * @var string|null */ - protected $action; + protected string|null $action; /** * The event state * (e.g. `after` in `page.create:after`) - * - * @var string|null */ - protected $state; + protected string|null $state; /** * The event arguments - * - * @var array */ - protected $arguments = []; + protected array $arguments = []; /** * Class constructor @@ -83,20 +73,15 @@ class Event /** * Magic caller for event arguments - * - * @param string $method - * @param array $arguments - * @return mixed */ - public function __call(string $method, array $arguments = []) + public function __call(string $method, array $arguments = []): mixed { return $this->argument($method); } /** * Improved `var_dump` output - * - * @return array + * @codeCoverageIgnore */ public function __debugInfo(): array { @@ -106,8 +91,6 @@ class Event /** * Makes it possible to simply echo * or stringify the entire object - * - * @return string */ public function __toString(): string { @@ -117,8 +100,6 @@ class Event /** * Returns the action of the event (e.g. `create`) * or `null` if the event name does not include an action - * - * @return string|null */ public function action(): string|null { @@ -127,19 +108,14 @@ class Event /** * Returns a specific event argument - * - * @param string $name - * @return mixed */ - public function argument(string $name) + public function argument(string $name): mixed { return $this->arguments[$name] ?? null; } /** * Returns the arguments of the event - * - * @return array */ public function arguments(): array { @@ -151,10 +127,8 @@ class Event * the hook's return value * * @param object|null $bind Optional object to bind to the hook function - * @param \Closure $hook - * @return mixed */ - public function call(object|null $bind, Closure $hook) + public function call(object|null $bind, Closure $hook): mixed { // collect the list of possible hook arguments $data = $this->arguments(); @@ -167,8 +141,6 @@ class Event /** * Returns the full name of the event - * - * @return string */ public function name(): string { @@ -178,13 +150,16 @@ class Event /** * Returns the full list of possible wildcard * event names based on the current event name - * - * @return array */ public function nameWildcards(): array { - // if the event is already a wildcard event, no further variation is possible - if ($this->type === '*' || $this->action === '*' || $this->state === '*') { + // if the event is already a wildcard event, + // no further variation is possible + if ( + $this->type === '*' || + $this->action === '*' || + $this->state === '*' + ) { return []; } @@ -228,8 +203,6 @@ class Event /** * Returns the state of the event (e.g. `after`) - * - * @return string|null */ public function state(): string|null { @@ -238,8 +211,6 @@ class Event /** * Returns the event data as array - * - * @return array */ public function toArray(): array { @@ -251,8 +222,6 @@ class Event /** * Returns the event name as string - * - * @return string */ public function toString(): string { @@ -261,8 +230,6 @@ class Event /** * Returns the type of the event (e.g. `page`) - * - * @return string */ public function type(): string { @@ -273,9 +240,6 @@ class Event * Updates a given argument with a new value * * @internal - * @param string $name - * @param mixed $value - * @return void * @throws \Kirby\Exception\InvalidArgumentException */ public function updateArgument(string $name, $value): void diff --git a/kirby/src/Cms/Fieldset.php b/kirby/src/Cms/Fieldset.php index ff577f5..20338f7 100644 --- a/kirby/src/Cms/Fieldset.php +++ b/kirby/src/Cms/Fieldset.php @@ -21,24 +21,21 @@ class Fieldset extends Item { public const ITEMS_CLASS = Fieldsets::class; - protected $disabled; - protected $editable; - protected $fields = []; - protected $icon; - protected $label; - protected $model; - protected $name; - protected $preview; - protected $tabs; - protected $translate; - protected $type; - protected $unset; - protected $wysiwyg; + protected bool $disabled; + protected bool $editable; + protected array $fields = []; + protected string|null $icon; + protected string|null $label; + protected string|null $name; + protected string|bool|null $preview; + protected array $tabs; + protected bool $translate; + protected string $type; + protected bool $unset; + protected bool $wysiwyg; /** * Creates a new Fieldset object - * - * @param array $params */ public function __construct(array $params = []) { @@ -50,17 +47,17 @@ class Fieldset extends Item parent::__construct($params); - $this->disabled = $params['disabled'] ?? false; - $this->editable = $params['editable'] ?? true; - $this->icon = $params['icon'] ?? null; - $this->model = $this->parent; - $this->name = $this->createName($params['title'] ?? $params['name'] ?? Str::ucfirst($this->type)); - $this->label = $this->createLabel($params['label'] ?? null); - $this->preview = $params['preview'] ?? null; - $this->tabs = $this->createTabs($params); - $this->translate = $params['translate'] ?? true; - $this->unset = $params['unset'] ?? false; - $this->wysiwyg = $params['wysiwyg'] ?? false; + $this->disabled = $params['disabled'] ?? false; + $this->editable = $params['editable'] ?? true; + $this->icon = $params['icon'] ?? null; + $params['title'] ??= $params['name'] ?? Str::ucfirst($this->type); + $this->name = $this->createName($params['title']); + $this->label = $this->createLabel($params['label'] ?? null); + $this->preview = $params['preview'] ?? null; + $this->tabs = $this->createTabs($params); + $this->translate = $params['translate'] ?? true; + $this->unset = $params['unset'] ?? false; + $this->wysiwyg = $params['wysiwyg'] ?? false; if ( $this->translate === false && @@ -73,10 +70,6 @@ class Fieldset extends Item } } - /** - * @param array $fields - * @return array - */ protected function createFields(array $fields = []): array { $fields = Blueprint::fieldsProps($fields); @@ -88,28 +81,16 @@ class Fieldset extends Item return $fields; } - /** - * @param array|string $name - * @return string|null - */ - protected function createName($name): string|null + protected function createName(array|string $name): string|null { return I18n::translate($name, $name); } - /** - * @param array|string $label - * @return string|null - */ - protected function createLabel($label = null): string|null + protected function createLabel(array|string|null $label = null): string|null { return I18n::translate($label, $label); } - /** - * @param array $params - * @return array - */ protected function createTabs(array $params = []): array { $tabs = $params['tabs'] ?? []; @@ -133,9 +114,10 @@ class Fieldset extends Item $tab = Blueprint::extend($tab); - $tab['fields'] = $this->createFields($tab['fields'] ?? []); - $tab['label'] = $this->createLabel($tab['label'] ?? Str::ucfirst($name)); - $tab['name'] = $name; + $tab['fields'] = $this->createFields($tab['fields'] ?? []); + $tab['label'] ??= Str::ucfirst($name); + $tab['label'] = $this->createLabel($tab['label']); + $tab['name'] = $name; $tabs[$name] = $tab; } @@ -143,17 +125,11 @@ class Fieldset extends Item return $tabs; } - /** - * @return bool - */ public function disabled(): bool { return $this->disabled; } - /** - * @return bool - */ public function editable(): bool { if ($this->editable === false) { @@ -167,9 +143,6 @@ class Fieldset extends Item return true; } - /** - * @return array - */ public function fields(): array { return $this->fields; @@ -177,88 +150,57 @@ class Fieldset extends Item /** * Creates a form for the given fields - * - * @param array $fields - * @param array $input - * @return \Kirby\Form\Form */ - public function form(array $fields, array $input = []) + public function form(array $fields, array $input = []): Form { return new Form([ 'fields' => $fields, - 'model' => $this->model, + 'model' => $this->parent, 'strict' => true, 'values' => $input, ]); } - /** - * @return string|null - */ public function icon(): string|null { return $this->icon; } - /** - * @return string|null - */ public function label(): string|null { return $this->label; } - /** - * @return \Kirby\Cms\ModelWithContent - */ - public function model() + public function model(): ModelWithContent { - return $this->model; + return $this->parent; } - /** - * @return string - */ public function name(): string { return $this->name; } - /** - * @return string|bool - */ - public function preview() + public function preview(): string|bool|null { return $this->preview; } - /** - * @return array - */ public function tabs(): array { return $this->tabs; } - /** - * @return bool - */ public function translate(): bool { return $this->translate; } - /** - * @return string - */ public function type(): string { return $this->type; } - /** - * @return array - */ public function toArray(): array { return [ @@ -276,17 +218,11 @@ class Fieldset extends Item ]; } - /** - * @return bool - */ public function unset(): bool { return $this->unset; } - /** - * @return bool - */ public function wysiwyg(): bool { return $this->wysiwyg; diff --git a/kirby/src/Cms/Fieldsets.php b/kirby/src/Cms/Fieldsets.php index 4326c29..181f1c4 100644 --- a/kirby/src/Cms/Fieldsets.php +++ b/kirby/src/Cms/Fieldsets.php @@ -23,15 +23,13 @@ class Fieldsets extends Items /** * All registered fieldsets methods - * - * @var array */ - public static $methods = []; + public static array $methods = []; - protected static function createFieldsets($params) + protected static function createFieldsets(array $params): array { $fieldsets = []; - $groups = []; + $groups = []; foreach ($params as $type => $fieldset) { if (is_int($type) === true && is_string($fieldset)) { @@ -75,8 +73,10 @@ class Fieldsets extends Items ]; } - public static function factory(array $items = null, array $params = []) - { + public static function factory( + array $items = null, + array $params = [] + ): static { $items ??= App::instance()->option('blocks.fieldsets', [ 'code' => 'blocks/code', 'gallery' => 'blocks/gallery', @@ -92,7 +92,10 @@ class Fieldsets extends Items $result = static::createFieldsets($items); - return parent::factory($result['fieldsets'], ['groups' => $result['groups']] + $params); + return parent::factory( + $result['fieldsets'], + ['groups' => $result['groups']] + $params + ); } public function groups(): array diff --git a/kirby/src/Cms/File.php b/kirby/src/Cms/File.php index 9bc1ae0..6e60ab0 100644 --- a/kirby/src/Cms/File.php +++ b/kirby/src/Cms/File.php @@ -2,10 +2,12 @@ namespace Kirby\Cms; +use Exception; +use IntlDateFormatter; +use Kirby\Exception\InvalidArgumentException; use Kirby\Filesystem\F; use Kirby\Filesystem\IsFile; use Kirby\Panel\File as Panel; -use Kirby\Toolkit\A; use Kirby\Toolkit\Str; /** @@ -38,61 +40,65 @@ class File extends ModelWithContent public const CLASS_ALIAS = 'file'; - /** - * Cache for the initialized blueprint object - * - * @var \Kirby\Cms\FileBlueprint - */ - protected $blueprint; - - /** - * @var string - */ - protected $filename; - - /** - * @var string - */ - protected $id; - /** * All registered file methods - * - * @var array + * @todo Remove when support for PHP 8.2 is dropped */ - public static $methods = []; + public static array $methods = []; + + /** + * Cache for the initialized blueprint object + */ + protected FileBlueprint|null $blueprint = null; + + protected string $filename; + + protected string $id; /** * The parent object - * - * @var \Kirby\Cms\Model */ - protected $parent; + protected Page|Site|User|null $parent = null; /** * The absolute path to the file */ - protected string|null $root = null; + protected string|null $root; - /** - * @var string - */ - protected $template; + protected string|null $template; /** * The public file Url */ - protected string|null $url = null; + protected string|null $url; + + /** + * Creates a new File object + */ + public function __construct(array $props) + { + parent::__construct($props); + + if (isset($props['filename'], $props['parent']) === false) { + throw new InvalidArgumentException('The filename and parent are required'); + } + + $this->filename = $props['filename']; + $this->parent = $props['parent']; + $this->template = $props['template'] ?? null; + // Always set the root to null, to invoke + // auto root detection + $this->root = null; + $this->url = $props['url'] ?? null; + + $this->setBlueprint($props['blueprint'] ?? null); + } /** * Magic caller for file methods * and content fields. (in this order) - * - * @param string $method - * @param array $arguments - * @return mixed */ - public function __call(string $method, array $arguments = []) + public function __call(string $method, array $arguments = []): mixed { // public property access if (isset($this->$method) === true) { @@ -113,25 +119,8 @@ class File extends ModelWithContent return $this->content()->get($method); } - /** - * Creates a new File object - * - * @param array $props - */ - public function __construct(array $props) - { - // set filename as the most important prop first - // TODO: refactor later to avoid redundant prop setting - $this->setProperty('filename', $props['filename'] ?? null, true); - - // set other properties - $this->setProperties($props); - } - /** * Improved `var_dump` output - * - * @return array */ public function __debugInfo(): array { @@ -143,10 +132,7 @@ class File extends ModelWithContent /** * Returns the url to api endpoint - * * @internal - * @param bool $relative - * @return string */ public function apiUrl(bool $relative = false): string { @@ -155,22 +141,143 @@ class File extends ModelWithContent /** * Returns the FileBlueprint object for the file - * - * @return \Kirby\Cms\FileBlueprint */ - public function blueprint() + public function blueprint(): FileBlueprint { - if ($this->blueprint instanceof FileBlueprint) { - return $this->blueprint; + return $this->blueprint ??= FileBlueprint::factory( + 'files/' . $this->template(), + 'files/default', + $this + ); + } + + /** + * Returns an array with all blueprints that are available for the file + * by comparing files sections and files fields of the parent model + */ + public function blueprints(string $inSection = null): array + { + if ($inSection === null && $this->blueprints !== null) { + return $this->blueprints; // @codeCoverageIgnore } - return $this->blueprint = FileBlueprint::factory('files/' . $this->template(), 'files/default', $this); + // always include the current template as option + $template = $this->template() ?? 'default'; + $templates = [$template]; + $parent = $this->parent(); + + // what file templates/blueprints should be considered is + // defined bythe parent's blueprint: which templates it allows + // in files sections as well as files fields + $blueprint = $parent->blueprint(); + + $fromFields = function ($fields) use (&$fromFields, $parent) { + $templates = []; + + foreach ($fields as $field) { + // files or textare field + if ( + $field['type'] === 'files' || + $field['type'] === 'textarea' + ) { + $uploads = $field['uploads'] ?? null; + + // only if the `uploads` parent is the actual parent + if ($target = $uploads['parent'] ?? null) { + if ($parent->id() !== $target) { + continue; + } + } + + $templates[] = $uploads['template'] ?? 'default'; + continue; + } + + // structure field + if ($field['type'] === 'structure') { + $fields = $fromFields($field['fields']); + $templates = array_merge($templates, $fields); + continue; + } + } + + return $templates; + }; + + // collect all allowed templates… + foreach ($blueprint->sections() as $section) { + // if collecting for a specific section, skip all others + if ($inSection !== null && $section->name() !== $inSection) { + continue; + } + + // …from files sections + if ($section->type() === 'files') { + $templates[] = $section->template() ?? 'default'; + continue; + } + + // …from fields + if ($section->type() === 'fields') { + $fields = $fromFields($section->fields()); + $templates = array_merge($templates, $fields); + } + } + + // make sure every template is only included once + $templates = array_unique(array_filter($templates)); + + // load the blueprint details for each collected template name + $blueprints = []; + + foreach ($templates as $template) { + // default template doesn't need to exist as file + // to be included in the list + if ($template === 'default') { + $blueprints[$template] = [ + 'name' => 'default', + 'title' => '– (default)', + ]; + continue; + } + + if ($blueprint = FileBlueprint::factory('files/' . $template, null, $this)) { + try { + // ensure that file matches `accept` option, + // if not remove template from available list + $this->match($blueprint->accept()); + + $blueprints[$template] = [ + 'name' => $name = Str::after($blueprint->name(), '/'), + 'title' => $blueprint->title() . ' (' . $name . ')', + ]; + } catch (Exception) { + // skip when `accept` doesn't match + } + } + } + + $blueprints = array_values($blueprints); + + // sort blueprints alphabetically while + // making sure the default blueprint is on top of list + usort($blueprints, fn ($a, $b) => match (true) { + $a['name'] === 'default' => -1, + $b['name'] === 'default' => 1, + default => strnatcmp($a['title'], $b['title']) + }); + + // no caching for when collecting for specific section + if ($inSection !== null) { + return $blueprints; // @codeCoverageIgnore + } + + return $this->blueprints = $blueprints; } /** * Store the template in addition to the * other content. - * * @internal */ public function contentFileData( @@ -192,42 +299,41 @@ class File extends ModelWithContent /** * Returns the directory in which * the content file is located - * * @internal - * @return string + * @deprecated 4.0.0 + * @todo Remove in v5 + * @codeCoverageIgnore */ public function contentFileDirectory(): string { + Helpers::deprecated('The internal $model->contentFileDirectory() method has been deprecated. Please let us know via a GitHub issue if you need this method and tell us your use case.', 'model-content-file'); return dirname($this->root()); } /** * Filename for the content file - * * @internal - * @return string + * @deprecated 4.0.0 + * @todo Remove in v5 + * @codeCoverageIgnore */ public function contentFileName(): string { + Helpers::deprecated('The internal $model->contentFileName() method has been deprecated. Please let us know via a GitHub issue if you need this method and tell us your use case.', 'model-content-file'); return $this->filename(); } /** * Constructs a File object - * * @internal - * @param mixed $props - * @return static */ - public static function factory($props) + public static function factory(array $props): static { return new static($props); } /** * Returns the filename with extension - * - * @return string */ public function filename(): string { @@ -236,19 +342,14 @@ class File extends ModelWithContent /** * Returns the parent Files collection - * - * @return \Kirby\Cms\Files */ - public function files() + public function files(): Files { return $this->siblingsCollection(); } /** * Converts the file to html - * - * @param array $attr - * @return string */ public function html(array $attr = []): string { @@ -260,55 +361,91 @@ class File extends ModelWithContent /** * Returns the id - * - * @return string */ public function id(): string { - if ($this->id !== null) { - return $this->id; - } - if ( $this->parent() instanceof Page || $this->parent() instanceof User ) { - return $this->id = $this->parent()->id() . '/' . $this->filename(); + return $this->id ??= $this->parent()->id() . '/' . $this->filename(); } - return $this->id = $this->filename(); + return $this->id ??= $this->filename(); } /** * Compares the current object with the given file object - * - * @param \Kirby\Cms\File $file - * @return bool */ public function is(File $file): bool { return $this->id() === $file->id(); } + /** + * Checks if the files is accessible. + * This permission depends on the `read` option until v5 + */ + public function isAccessible(): bool + { + // TODO: remove this check when `read` option deprecated in v5 + if ($this->isReadable() === false) { + return false; + } + + static $accessible = []; + + if ($template = $this->template()) { + return $accessible[$template] ??= $this->permissions()->can('access'); + } + + return $accessible['__none__'] ??= $this->permissions()->can('access'); + } + + /** + * Check if the file can be listable by the current user + * This permission depends on the `read` option until v5 + */ + public function isListable(): bool + { + // TODO: remove this check when `read` option deprecated in v5 + if ($this->isReadable() === false) { + return false; + } + + // not accessible also means not listable + if ($this->isAccessible() === false) { + return false; + } + + static $listable = []; + + if ($template = $this->template()) { + return $listable[$template] ??= $this->permissions()->can('list'); + } + + return $listable['__none__'] ??= $this->permissions()->can('list'); + } + /** * Check if the file can be read by the current user * - * @return bool + * @todo Deprecate `read` option in v5 and make the necessary changes for `access` and `list` options. */ public function isReadable(): bool { static $readable = []; - $template = $this->template(); + if ($template = $this->template()) { + return $readable[$template] ??= $this->permissions()->can('read'); + } - return $readable[$template] ??= $this->permissions()->can('read'); + return $readable['__none__'] ??= $this->permissions()->can('read'); } /** * Creates a unique media hash - * * @internal - * @return string */ public function mediaHash(): string { @@ -317,9 +454,7 @@ class File extends ModelWithContent /** * Returns the absolute path to the file in the public media folder - * * @internal - * @return string */ public function mediaRoot(): string { @@ -328,9 +463,7 @@ class File extends ModelWithContent /** * Creates a non-guessable token string for this file - * * @internal - * @return string */ public function mediaToken(): string { @@ -340,9 +473,7 @@ class File extends ModelWithContent /** * Returns the absolute Url to the file in the public media folder - * * @internal - * @return string */ public function mediaUrl(): string { @@ -352,17 +483,16 @@ class File extends ModelWithContent /** * Get the file's last modification time. * - * @param string|\IntlDateFormatter|null $format * @param string|null $handler date, intl or strftime - * @param string|null $languageCode - * @return mixed */ - public function modified($format = null, string $handler = null, string $languageCode = null) - { + public function modified( + string|IntlDateFormatter|null $format = null, + string|null $handler = null, + string|null $languageCode = null + ): string|int|false { $file = $this->modifiedFile(); $content = $this->modifiedContent($languageCode); $modified = max($file, $content); - $handler ??= $this->kirby()->option('date.handler', 'date'); return Str::date($modified, $format, $handler); } @@ -370,20 +500,15 @@ class File extends ModelWithContent /** * Timestamp of the last modification * of the content file - * - * @param string|null $languageCode - * @return int */ protected function modifiedContent(string $languageCode = null): int { - return F::modified($this->contentFile($languageCode)); + return $this->storage()->modified('published', $languageCode) ?? 0; } /** * Timestamp of the last modification * of the source file - * - * @return int */ protected function modifiedFile(): int { @@ -392,10 +517,8 @@ class File extends ModelWithContent /** * Returns the parent Page object - * - * @return \Kirby\Cms\Page|null */ - public function page() + public function page(): Page|null { if ($this->parent() instanceof Page) { return $this->parent(); @@ -406,29 +529,23 @@ class File extends ModelWithContent /** * Returns the panel info object - * - * @return \Kirby\Panel\File */ - public function panel() + public function panel(): Panel { return new Panel($this); } /** - * Returns the parent Model object - * - * @return \Kirby\Cms\Model + * Returns the parent object */ - public function parent() + public function parent(): Page|Site|User { return $this->parent ??= $this->kirby()->site(); } /** * Returns the parent id if a parent exists - * * @internal - * @return string */ public function parentId(): string { @@ -437,13 +554,14 @@ class File extends ModelWithContent /** * Returns a collection of all parent pages - * - * @return \Kirby\Cms\Pages */ - public function parents() + public function parents(): Pages { if ($this->parent() instanceof Page) { - return $this->parent()->parents()->prepend($this->parent()->id(), $this->parent()); + return $this->parent()->parents()->prepend( + $this->parent()->id(), + $this->parent() + ); } return new Pages(); @@ -460,18 +578,14 @@ class File extends ModelWithContent /** * Returns the permissions object for this file - * - * @return \Kirby\Cms\FilePermissions */ - public function permissions() + public function permissions(): FilePermissions { return new FilePermissions($this); } /** * Returns the absolute root to the file - * - * @return string|null */ public function root(): string|null { @@ -481,10 +595,8 @@ class File extends ModelWithContent /** * Returns the FileRules class to * validate any important action. - * - * @return \Kirby\Cms\FileRules */ - protected function rules() + protected function rules(): FileRules { return new FileRules(); } @@ -492,10 +604,9 @@ class File extends ModelWithContent /** * Sets the Blueprint object * - * @param array|null $blueprint * @return $this */ - protected function setBlueprint(array $blueprint = null) + protected function setBlueprint(array $blueprint = null): static { if ($blueprint !== null) { $blueprint['model'] = $this; @@ -505,82 +616,19 @@ class File extends ModelWithContent return $this; } - /** - * Sets the filename - * - * @param string $filename - * @return $this - */ - protected function setFilename(string $filename) - { - $this->filename = $filename; - return $this; - } - - /** - * Sets the parent model object - * - * @param \Kirby\Cms\Model $parent - * @return $this - */ - protected function setParent(Model $parent) - { - $this->parent = $parent; - return $this; - } - - /** - * Always set the root to null, to invoke - * auto root detection - * - * @param string|null $root - * @return $this - */ - protected function setRoot(string $root = null) - { - $this->root = null; - return $this; - } - - /** - * @param string|null $template - * @return $this - */ - protected function setTemplate(string $template = null) - { - $this->template = $template; - return $this; - } - - /** - * Sets the url - * - * @param string|null $url - * @return $this - */ - protected function setUrl(string $url = null) - { - $this->url = $url; - return $this; - } - /** * Returns the parent Files collection * @internal - * - * @return \Kirby\Cms\Files */ - protected function siblingsCollection() + protected function siblingsCollection(): Files { return $this->parent()->files(); } /** * Returns the parent Site object - * - * @return \Kirby\Cms\Site */ - public function site() + public function site(): Site { if ($this->parent() instanceof Site) { return $this->parent(); @@ -591,8 +639,6 @@ class File extends ModelWithContent /** * Returns the final template - * - * @return string|null */ public function template(): string|null { @@ -601,11 +647,8 @@ class File extends ModelWithContent /** * Returns siblings with the same template - * - * @param bool $self - * @return \Kirby\Cms\Files */ - public function templateSiblings(bool $self = true) + public function templateSiblings(bool $self = true): Files { return $this->siblings($self)->filter('template', $this->template()); } @@ -614,18 +657,17 @@ class File extends ModelWithContent * Extended info for the array export * by injecting the information from * the asset. - * - * @return array */ public function toArray(): array { - return array_merge($this->asset()->toArray(), parent::toArray()); + return array_merge(parent::toArray(), $this->asset()->toArray(), [ + 'id' => $this->id(), + 'template' => $this->template(), + ]); } /** * Returns the Url - * - * @return string */ public function url(): string { @@ -636,8 +678,6 @@ class File extends ModelWithContent * Simplified File URL that uses the parent * Page URL and the filename as a more stable * alternative for the media URLs. - * - * @return string */ public function previewUrl(): string { diff --git a/kirby/src/Cms/FileActions.php b/kirby/src/Cms/FileActions.php index 5d2ce6f..3068a11 100644 --- a/kirby/src/Cms/FileActions.php +++ b/kirby/src/Cms/FileActions.php @@ -21,29 +21,49 @@ use Kirby\Uuid\Uuids; */ trait FileActions { + protected function changeExtension( + File $file, + string|null $extension = null + ): File { + if ( + $extension === null || + $extension === $file->extension() + ) { + return $file; + } + + return $file->changeName($file->name(), false, $extension); + } + /** - * Renames the file without touching the extension + * Renames the file (optionally also the extension). * The store is used to actually execute this. * - * @param string $name - * @param bool $sanitize - * @return $this|static * @throws \Kirby\Exception\LogicException */ - public function changeName(string $name, bool $sanitize = true) - { + public function changeName( + string $name, + bool $sanitize = true, + string|null $extension = null + ): static { if ($sanitize === true) { $name = F::safeName($name); } + // if no extension is passed, make sure to maintain current one + $extension ??= $this->extension(); + // don't rename if not necessary - if ($name === $this->name()) { + if ( + $name === $this->name() && + $extension === $this->extension() + ) { return $this; } - return $this->commit('changeName', ['file' => $this, 'name' => $name], function ($oldFile, $name) { + return $this->commit('changeName', ['file' => $this, 'name' => $name, 'extension' => $extension], function ($oldFile, $name, $extension) { $newFile = $oldFile->clone([ - 'filename' => $name . '.' . $oldFile->extension(), + 'filename' => $name . '.' . $extension, ]); // remove all public versions, lock and clear UUID cache @@ -60,16 +80,11 @@ trait FileActions // rename the main file F::move($oldFile->root(), $newFile->root()); - if ($newFile->kirby()->multilang() === true) { - foreach ($newFile->translations() as $translation) { - $translationCode = $translation->code(); - - // rename the content file - F::move($oldFile->contentFile($translationCode), $newFile->contentFile($translationCode)); - } - } else { - // rename the content file - F::move($oldFile->contentFile(), $newFile->contentFile()); + // move the content storage versions + foreach ($oldFile->storage()->all() as $version => $lang) { + $content = $oldFile->storage()->read($version, $lang); + $oldFile->storage()->delete($version, $lang); + $newFile->storage()->create($version, $lang, $content); } // update collections @@ -82,11 +97,8 @@ trait FileActions /** * Changes the file's sorting number in the meta file - * - * @param int $sort - * @return static */ - public function changeSort(int $sort) + public function changeSort(int $sort): static { return $this->commit( 'changeSort', @@ -95,6 +107,40 @@ trait FileActions ); } + /** + * @return $this|static + */ + public function changeTemplate(string|null $template): static + { + if ($template === $this->template()) { + return $this; + } + + $arguments = [ + 'file' => $this, + 'template' => $template ?? 'default' + ]; + + return $this->commit('changeTemplate', $arguments, function ($oldFile, $template) { + // convert to new template/blueprint incl. content + $file = $oldFile->convertTo($template); + + // update template, prefer unset over writing `default` + if ($template === 'default') { + $template = null; + } + + $file = $file->update(['template' => $template]); + + // rename and/or resize the file if configured by new blueprint + $create = $file->blueprint()->create(); + $file = $file->changeExtension($file, $create['format'] ?? null); + $file->manipulate($create); + + return $file; + }); + } + /** * Commits a file action, by following these steps * @@ -103,14 +149,12 @@ trait FileActions * 3. commits the store action * 4. sends the after hook * 5. returns the result - * - * @param string $action - * @param array $arguments - * @param Closure $callback - * @return mixed */ - protected function commit(string $action, array $arguments, Closure $callback) - { + protected function commit( + string $action, + array $arguments, + Closure $callback + ): mixed { $old = $this->hardcopy(); $kirby = $this->kirby(); $argumentValues = array_values($arguments); @@ -134,24 +178,19 @@ trait FileActions /** * Copy the file to the given page - * - * @param \Kirby\Cms\Page $page - * @return \Kirby\Cms\File */ - public function copy(Page $page) + public function copy(Page $page): static { F::copy($this->root(), $page->root() . '/' . $this->filename()); + $copy = $page->clone()->file($this->filename()); - if ($this->kirby()->multilang() === true) { - foreach ($this->kirby()->languages() as $language) { - $contentFile = $this->contentFile($language->code()); - F::copy($contentFile, $page->root() . '/' . basename($contentFile)); - } - } else { - $contentFile = $this->contentFile(); - F::copy($contentFile, $page->root() . '/' . basename($contentFile)); + foreach ($this->storage()->all() as $version => $lang) { + $content = $this->storage()->read($version, $lang); + $copy->storage()->create($version, $lang, $content); } + // ensure the content is re-read after copying it + // @todo find a more elegant way $copy = $page->clone()->file($this->filename()); // overwrite with new UUID (remove old, add new) @@ -168,13 +207,11 @@ trait FileActions * writing, so it can be replaced by any other * way of generating files. * - * @param array $props * @param bool $move If set to `true`, the source will be deleted - * @return static * @throws \Kirby\Exception\InvalidArgumentException * @throws \Kirby\Exception\LogicException */ - public static function create(array $props, bool $move = false) + public static function create(array $props, bool $move = false): File { if (isset($props['source'], $props['parent']) === false) { throw new InvalidArgumentException('Please provide the "source" and "parent" props for the File'); @@ -204,9 +241,15 @@ trait FileActions // inject the content $file = $file->clone(['content' => $form->strings(true)]); + // if the format is different from the original, + // we need to already rename it so that the correct file rules + // are applied + $create = $file->blueprint()->create(); + $file = $file->changeExtension($file, $create['format'] ?? null); + // run the hook $arguments = compact('file', 'upload'); - return $file->commit('create', $arguments, function ($file, $upload) use ($move) { + return $file->commit('create', $arguments, function ($file, $upload) use ($create, $move) { // remove all public versions, lock and clear UUID cache $file->unpublish(); @@ -218,15 +261,15 @@ trait FileActions throw new LogicException('The file could not be created'); } - // always create pages in the default language - if ($file->kirby()->multilang() === true) { - $languageCode = $file->kirby()->defaultLanguage()->code(); - } else { - $languageCode = null; - } + // resize the file on upload if configured + $file = $file->manipulate($create); // store the content if necessary - $file->save($file->content()->toArray(), $languageCode); + // (always create files in the default language) + $file->save( + $file->content()->toArray(), + $file->kirby()->defaultLanguage()?->code() + ); // add the file to the list of siblings $file->siblings()->append($file->id(), $file); @@ -239,8 +282,6 @@ trait FileActions /** * Deletes the file. The store is used to * manipulate the filesystem or whatever you prefer. - * - * @return bool */ public function delete(): bool { @@ -248,12 +289,8 @@ trait FileActions // remove all public versions, lock and clear UUID cache $file->unpublish(); - if ($file->kirby()->multilang() === true) { - foreach ($file->translations() as $translation) { - F::remove($file->contentFile($translation->code())); - } - } else { - F::remove($file->contentFile()); + foreach ($file->storage()->all() as $version => $lang) { + $file->storage()->delete($version, $lang); } F::remove($file->root()); @@ -265,13 +302,29 @@ trait FileActions }); } + /** + * Resizes/crops the original file with Kirby's thumb handler + */ + public function manipulate(array|null $options = []): static + { + // nothing to process + if (empty($options) === true || $this->isResizable() === false) { + return $this; + } + + // generate image file and overwrite it in place + $this->kirby()->thumb($this->root(), $this->root(), $options); + + return $this->clone([]); + } + /** * Move the file to the public media folder * if it's not already there. * * @return $this */ - public function publish() + public function publish(): static { Media::publish($this, $this->mediaRoot()); return $this; @@ -284,12 +337,10 @@ trait FileActions * finally decides what it will support as * source. * - * @param string $source * @param bool $move If set to `true`, the source will be deleted - * @return static * @throws \Kirby\Exception\LogicException */ - public function replace(string $source, bool $move = false) + public function replace(string $source, bool $move = false): static { $file = $this->clone(); @@ -310,6 +361,11 @@ trait FileActions throw new LogicException('The file could not be created'); } + // apply the resizing/crop options from the blueprint + $create = $file->blueprint()->create(); + $file = $file->changeExtension($file, $create['format'] ?? null); + $file = $file->manipulate($create); + // return a fresh clone return $file->clone(); }); @@ -317,15 +373,13 @@ trait FileActions /** * Stores the content on disk - * * @internal - * @param array|null $data - * @param string|null $languageCode - * @param bool $overwrite - * @return static */ - public function save(array $data = null, string $languageCode = null, bool $overwrite = false) - { + public function save( + array $data = null, + string $languageCode = null, + bool $overwrite = false + ): static { $file = parent::save($data, $languageCode, $overwrite); // update model in siblings collection @@ -339,7 +393,7 @@ trait FileActions * * @return $this */ - public function unpublish(bool $onlyMedia = false) + public function unpublish(bool $onlyMedia = false): static { // unpublish media files Media::unpublish($this->parent()->mediaRoot(), $this); @@ -354,4 +408,23 @@ trait FileActions return $this; } + + /** + * Updates the file's data and ensures that + * media files get wiped if `focus` changed + * + * @throws \Kirby\Exception\InvalidArgumentException If the input array contains invalid values + */ + public function update( + array $input = null, + string $languageCode = null, + bool $validate = false + ): static { + // delete all public media versions when focus field gets changed + if (($input['focus'] ?? null) !== $this->focus()->value()) { + $this->unpublish(true); + } + + return parent::update($input, $languageCode, $validate); + } } diff --git a/kirby/src/Cms/FileBlueprint.php b/kirby/src/Cms/FileBlueprint.php index 9d17e5f..6d6bfed 100644 --- a/kirby/src/Cms/FileBlueprint.php +++ b/kirby/src/Cms/FileBlueprint.php @@ -21,10 +21,8 @@ class FileBlueprint extends Blueprint /** * `true` if the default accepted * types are being used - * - * @var bool */ - protected $defaultTypes = false; + protected bool $defaultTypes = false; public function __construct(array $props) { @@ -35,12 +33,15 @@ class FileBlueprint extends Blueprint $this->props['options'] ?? true, // defaults [ - 'changeName' => null, - 'create' => null, - 'delete' => null, - 'read' => null, - 'replace' => null, - 'update' => null, + 'access' => null, + 'changeName' => null, + 'changeTemplate' => null, + 'create' => null, + 'delete' => null, + 'list' => null, + 'read' => null, + 'replace' => null, + 'update' => null, ] ); @@ -48,9 +49,6 @@ class FileBlueprint extends Blueprint $this->props['accept'] = $this->normalizeAccept($this->props['accept'] ?? []); } - /** - * @return array - */ public function accept(): array { return $this->props['accept']; @@ -59,8 +57,6 @@ class FileBlueprint extends Blueprint /** * Returns the list of all accepted MIME types for * file upload or `*` if all MIME types are allowed - * - * @return string */ public function acceptMime(): string { @@ -120,11 +116,7 @@ class FileBlueprint extends Blueprint return '*'; } - /** - * @param mixed $accept - * @return array - */ - protected function normalizeAccept($accept = null): array + protected function normalizeAccept(mixed $accept = null): array { $accept = match (true) { is_string($accept) => ['mime' => $accept], diff --git a/kirby/src/Cms/FileModifications.php b/kirby/src/Cms/FileModifications.php index 11556ba..f5d0bc0 100644 --- a/kirby/src/Cms/FileModifications.php +++ b/kirby/src/Cms/FileModifications.php @@ -2,6 +2,7 @@ namespace Kirby\Cms; +use Kirby\Content\Field; use Kirby\Exception\InvalidArgumentException; use Kirby\Filesystem\Asset; @@ -18,37 +19,30 @@ trait FileModifications { /** * Blurs the image by the given amount of pixels - * - * @param bool $pixels - * @return \Kirby\Cms\FileVersion|\Kirby\Cms\File */ - public function blur($pixels = true) + public function blur(int|bool $pixels = true): FileVersion|File|Asset { return $this->thumb(['blur' => $pixels]); } /** * Converts the image to black and white - * - * @return \Kirby\Cms\FileVersion|\Kirby\Cms\File */ - public function bw() + public function bw(): FileVersion|File|Asset { return $this->thumb(['grayscale' => true]); } /** * Crops the image by the given width and height - * - * @param int $width - * @param int|null $height - * @param string|array $options - * @return \Kirby\Cms\FileVersion|\Kirby\Cms\File */ - public function crop(int $width, int $height = null, $options = null) - { + public function crop( + int $width, + int $height = null, + $options = null + ): FileVersion|File|Asset { $quality = null; - $crop = 'center'; + $crop = true; if (is_int($options) === true) { $quality = $options; @@ -71,31 +65,24 @@ trait FileModifications /** * Alias for File::bw() - * - * @return \Kirby\Cms\FileVersion|\Kirby\Cms\File */ - public function grayscale() + public function grayscale(): FileVersion|File|Asset { return $this->thumb(['grayscale' => true]); } /** * Alias for File::bw() - * - * @return \Kirby\Cms\FileVersion|\Kirby\Cms\File */ - public function greyscale() + public function greyscale(): FileVersion|File|Asset { return $this->thumb(['grayscale' => true]); } /** * Sets the JPEG compression quality - * - * @param int $quality - * @return \Kirby\Cms\FileVersion|\Kirby\Cms\File */ - public function quality(int $quality) + public function quality(int $quality): FileVersion|File|Asset { return $this->thumb(['quality' => $quality]); } @@ -104,14 +91,13 @@ trait FileModifications * Resizes the file with the given width and height * while keeping the aspect ratio. * - * @param int|null $width - * @param int|null $height - * @param int|null $quality - * @return \Kirby\Cms\FileVersion|\Kirby\Cms\File * @throws \Kirby\Exception\InvalidArgumentException */ - public function resize(int $width = null, int $height = null, int $quality = null) - { + public function resize( + int $width = null, + int $height = null, + int $quality = null + ): FileVersion|File|Asset { return $this->thumb([ 'width' => $width, 'height' => $height, @@ -124,11 +110,8 @@ trait FileModifications * Sizes can be defined as a simple array. They can * also be set up in the config with the thumbs.srcsets option. * @since 3.1.0 - * - * @param array|string|null $sizes - * @return string|null */ - public function srcset($sizes = null): string|null + public function srcset(array|string|null $sizes = null): string|null { if (empty($sizes) === true) { $sizes = $this->kirby()->option('thumbs.srcsets.default', []); @@ -175,12 +158,11 @@ trait FileModifications * could potentially also be a CDN or any other * place. * - * @param array|null|string $options - * @return \Kirby\Cms\FileVersion|\Kirby\Cms\File * @throws \Kirby\Exception\InvalidArgumentException */ - public function thumb($options = null) - { + public function thumb( + array|string|null $options = null + ): FileVersion|File|Asset { // thumb presets if (empty($options) === true) { $options = $this->kirby()->option('thumbs.presets.default'); @@ -192,6 +174,11 @@ trait FileModifications return $this; } + // fallback to content file options + if (($options['crop'] ?? false) === true) { + $options['crop'] = $this->focus()->value() ?? 'center'; + } + // fallback to global config options if (isset($options['format']) === false) { if ($format = $this->kirby()->option('thumbs.format')) { diff --git a/kirby/src/Cms/FilePermissions.php b/kirby/src/Cms/FilePermissions.php index e296de5..2f8b777 100644 --- a/kirby/src/Cms/FilePermissions.php +++ b/kirby/src/Cms/FilePermissions.php @@ -13,5 +13,14 @@ namespace Kirby\Cms; */ class FilePermissions extends ModelPermissions { - protected $category = 'files'; + protected string $category = 'files'; + + protected function canChangeTemplate(): bool + { + if (count($this->model->blueprints()) <= 1) { + return false; + } + + return true; + } } diff --git a/kirby/src/Cms/FilePicker.php b/kirby/src/Cms/FilePicker.php index 81899ba..1391110 100644 --- a/kirby/src/Cms/FilePicker.php +++ b/kirby/src/Cms/FilePicker.php @@ -19,8 +19,6 @@ class FilePicker extends Picker { /** * Extends the basic defaults - * - * @return array */ public function defaults(): array { @@ -33,10 +31,9 @@ class FilePicker extends Picker /** * Search all files for the picker * - * @return \Kirby\Cms\Files|null * @throws \Kirby\Exception\InvalidArgumentException */ - public function items() + public function items(): Files|null { $model = $this->options['model']; @@ -65,6 +62,9 @@ class FilePicker extends Picker default => throw new InvalidArgumentException('Your query must return a set of files') }; + // filter protected and hidden pages + $files = $files->filter('isListable', true); + // search $files = $this->search($files); diff --git a/kirby/src/Cms/FileRules.php b/kirby/src/Cms/FileRules.php index 64c961a..33f25e3 100644 --- a/kirby/src/Cms/FileRules.php +++ b/kirby/src/Cms/FileRules.php @@ -4,6 +4,7 @@ namespace Kirby\Cms; use Kirby\Exception\DuplicateException; use Kirby\Exception\InvalidArgumentException; +use Kirby\Exception\LogicException; use Kirby\Exception\PermissionException; use Kirby\Filesystem\File as BaseFile; use Kirby\Toolkit\Str; @@ -23,9 +24,6 @@ class FileRules /** * Validates if the filename can be changed * - * @param \Kirby\Cms\File $file - * @param string $name - * @return bool * @throws \Kirby\Exception\DuplicateException If a file with this name exists * @throws \Kirby\Exception\PermissionException If the user is not allowed to rename the file */ @@ -59,22 +57,51 @@ class FileRules /** * Validates if the file can be sorted - * - * @param \Kirby\Cms\File $file - * @param int $sort - * @return bool */ public static function changeSort(File $file, int $sort): bool { return true; } + /** + * Validates if the template of the file can be changed + * + * @throws \Kirby\Exception\LogicException If the template of the page cannot be changed at all + * @throws \Kirby\Exception\PermissionException If the user is not allowed to change the template + */ + public static function changeTemplate(File $file, string $template): bool + { + if ($file->permissions()->changeTemplate() !== true) { + throw new PermissionException([ + 'key' => 'file.changeTemplate.permission', + 'data' => ['id' => $file->id()] + ]); + } + + $blueprints = $file->blueprints(); + + // ensure that the $template is a valid blueprint + // option for this file + if ( + count($blueprints) <= 1 || + in_array($template, array_column($blueprints, 'name')) === false + ) { + throw new LogicException([ + 'key' => 'file.changeTemplate.invalid', + 'data' => [ + 'id' => $file->id(), + 'template' => $template, + 'blueprints' => implode(', ', array_column($blueprints, 'name')) + ] + ]); + } + + return true; + } + /** * Validates if the file can be created * - * @param \Kirby\Cms\File $file - * @param \Kirby\Filesystem\File $upload - * @return bool * @throws \Kirby\Exception\DuplicateException If a file with the same name exists * @throws \Kirby\Exception\PermissionException If the user is not allowed to create the file */ @@ -121,8 +148,6 @@ class FileRules /** * Validates if the file can be deleted * - * @param \Kirby\Cms\File $file - * @return bool * @throws \Kirby\Exception\PermissionException If the user is not allowed to delete the file */ public static function delete(File $file): bool @@ -137,9 +162,6 @@ class FileRules /** * Validates if the file can be replaced * - * @param \Kirby\Cms\File $file - * @param \Kirby\Filesystem\File $upload - * @return bool * @throws \Kirby\Exception\PermissionException If the user is not allowed to replace the file * @throws \Kirby\Exception\InvalidArgumentException If the file type of the new file is different */ @@ -170,9 +192,6 @@ class FileRules /** * Validates if the file can be updated * - * @param \Kirby\Cms\File $file - * @param array $content - * @return bool * @throws \Kirby\Exception\PermissionException If the user is not allowed to update the file */ public static function update(File $file, array $content = []): bool @@ -187,9 +206,6 @@ class FileRules /** * Validates the file extension * - * @param \Kirby\Cms\File $file - * @param string $extension - * @return bool * @throws \Kirby\Exception\InvalidArgumentException If the extension is missing or forbidden */ public static function validExtension(File $file, string $extension): bool @@ -235,20 +251,19 @@ class FileRules /** * Validates the extension, MIME type and filename * - * @param \Kirby\Cms\File $file - * @param string|null|false $mime If not passed, the MIME type is detected from the file, - * if `false`, the MIME type is not validated for performance reasons - * @return bool + * @param $mime If not passed, the MIME type is detected from the file, + * if `false`, the MIME type is not validated for performance reasons * @throws \Kirby\Exception\InvalidArgumentException If the extension, MIME type or filename is missing or forbidden */ - public static function validFile(File $file, $mime = null): bool - { - if ($mime === false) { + public static function validFile( + File $file, + string|false|null $mime = null + ): bool { + $validMime = match ($mime) { // request to skip the MIME check for performance reasons - $validMime = true; - } else { - $validMime = static::validMime($file, $mime ?? $file->mime()); - } + false => true, + default => static::validMime($file, $mime ?? $file->mime()) + }; return $validMime && @@ -259,9 +274,6 @@ class FileRules /** * Validates the filename * - * @param \Kirby\Cms\File $file - * @param string $filename - * @return bool * @throws \Kirby\Exception\InvalidArgumentException If the filename is missing or forbidden */ public static function validFilename(File $file, string $filename): bool @@ -298,9 +310,6 @@ class FileRules /** * Validates the MIME type * - * @param \Kirby\Cms\File $file - * @param string|null $mime - * @return bool * @throws \Kirby\Exception\InvalidArgumentException If the MIME type is missing or forbidden */ public static function validMime(File $file, string $mime = null): bool diff --git a/kirby/src/Cms/FileVersion.php b/kirby/src/Cms/FileVersion.php index 96a497f..669fdc9 100644 --- a/kirby/src/Cms/FileVersion.php +++ b/kirby/src/Cms/FileVersion.php @@ -17,18 +17,22 @@ class FileVersion { use IsFile; - protected $modifications; + protected array $modifications; protected $original; + public function __construct(array $props) + { + $this->root = $props['root'] ?? null; + $this->url = $props['url'] ?? null; + $this->original = $props['original']; + $this->modifications = $props['modifications'] ?? []; + } + /** * Proxy for public properties, asset methods * and content field getters - * - * @param string $method - * @param array $arguments - * @return mixed */ - public function __call(string $method, array $arguments = []) + public function __call(string $method, array $arguments = []): mixed { // public property access if (isset($this->$method) === true) { @@ -52,8 +56,6 @@ class FileVersion /** * Returns the unique ID - * - * @return string */ public function id(): string { @@ -62,30 +64,24 @@ class FileVersion /** * Returns the parent Kirby App instance - * - * @return \Kirby\Cms\App */ - public function kirby() + public function kirby(): App { return $this->original()->kirby(); } /** * Returns an array with all applied modifications - * - * @return array */ public function modifications(): array { - return $this->modifications ?? []; + return $this->modifications; } /** * Returns the instance of the original File object - * - * @return mixed */ - public function original() + public function original(): mixed { return $this->original; } @@ -96,7 +92,7 @@ class FileVersion * * @return $this */ - public function save() + public function save(): static { $this->kirby()->thumb( $this->original()->root(), @@ -106,36 +102,16 @@ class FileVersion return $this; } - /** - * Setter for modifications - * - * @param array|null $modifications - */ - protected function setModifications(array $modifications = null) - { - $this->modifications = $modifications; - } - - /** - * Setter for the original File object - * - * @param $original - */ - protected function setOriginal($original) - { - $this->original = $original; - } /** * Converts the object to an array - * - * @return array */ public function toArray(): array { - $array = array_merge($this->asset()->toArray(), [ - 'modifications' => $this->modifications(), - ]); + $array = array_merge( + $this->asset()->toArray(), + ['modifications' => $this->modifications()] + ); ksort($array); diff --git a/kirby/src/Cms/Files.php b/kirby/src/Cms/Files.php index 5e30528..6290700 100644 --- a/kirby/src/Cms/Files.php +++ b/kirby/src/Cms/Files.php @@ -26,10 +26,8 @@ class Files extends Collection /** * All registered files methods - * - * @var array */ - public static $methods = []; + public static array $methods = []; /** * Adds a single file or @@ -40,7 +38,7 @@ class Files extends Collection * @return $this * @throws \Kirby\Exception\InvalidArgumentException When no `File` or `Files` object or an ID of an existing file is passed */ - public function add($object) + public function add($object): static { // add a files collection if ($object instanceof self) { @@ -74,7 +72,7 @@ class Files extends Collection * @param int $offset Sorting offset * @return $this */ - public function changeSort(array $files, int $offset = 0) + public function changeSort(array $files, int $offset = 0): static { foreach ($files as $filename) { if ($file = $this->get($filename)) { @@ -88,12 +86,8 @@ class Files extends Collection /** * Creates a files collection from an array of props - * - * @param array $files - * @param \Kirby\Cms\Model $parent - * @return static */ - public static function factory(array $files, Model $parent) + public static function factory(array $files, Page|Site|User $parent): static { $collection = new static([], $parent); $kirby = $parent->kirby(); @@ -114,11 +108,8 @@ class Files extends Collection /** * Finds a file by its filename * @internal Use `$files->find()` instead - * - * @param string $key - * @return \Kirby\Cms\File|null */ - public function findByKey(string $key) + public function findByKey(string $key): File|null { if ($file = $this->findByUuid($key, 'file')) { return $file; @@ -136,7 +127,6 @@ class Files extends Collection * @param string|null|false $locale Locale for number formatting, * `null` for the current locale, * `false` to disable number formatting - * @return string */ public function niceSize($locale = null): string { @@ -147,8 +137,6 @@ class Files extends Collection * Returns the raw size for all * files in the collection * @since 3.6.0 - * - * @return int */ public function size(): int { @@ -158,10 +146,8 @@ class Files extends Collection /** * Returns the collection sorted by * the sort number and the filename - * - * @return static */ - public function sorted() + public function sorted(): static { return $this->sort('sort', 'asc', 'filename', 'asc'); } @@ -169,10 +155,9 @@ class Files extends Collection /** * Filter all files by the given template * - * @param null|string|array $template * @return $this|static */ - public function template($template) + public function template(string|array|null $template): static { if (empty($template) === true) { return $this; diff --git a/kirby/src/Cms/Find.php b/kirby/src/Cms/Find.php index cb2332c..aa2c363 100644 --- a/kirby/src/Cms/Find.php +++ b/kirby/src/Cms/Find.php @@ -24,16 +24,17 @@ class Find * parent path and filename * * @param string $path Path to file's parent model - * @param string $filename Filename - * @return \Kirby\Cms\File|null * @throws \Kirby\Exception\NotFoundException if the file cannot be found */ - public static function file(string $path, string $filename) - { + public static function file( + string $path, + string $filename + ): File|null { $filename = urldecode($filename); - $file = static::parent($path)->file($filename); + $parent = empty($path) ? null : static::parent($path); + $file = App::instance()->file($filename, $parent); - if ($file?->isReadable() === true) { + if ($file?->isAccessible() === true) { return $file; } @@ -49,10 +50,9 @@ class Find * Returns the language object for the given code * * @param string $code Language code - * @return \Kirby\Cms\Language|null * @throws \Kirby\Exception\NotFoundException if the language cannot be found */ - public static function language(string $code) + public static function language(string $code): Language|null { if ($language = App::instance()->language($code)) { return $language; @@ -70,15 +70,16 @@ class Find * Returns the page object for the given id * * @param string $id Page's id - * @return \Kirby\Cms\Page|null * @throws \Kirby\Exception\NotFoundException if the page cannot be found */ - public static function page(string $id) + public static function page(string $id): Page|null { - $id = str_replace(['+', ' '], '/', $id); - $page = App::instance()->page($id); + // decode API ID encoding + $id = str_replace(['+', ' '], '/', $id); + $kirby = App::instance(); + $page = $kirby->page($id, null, true); - if ($page?->isReadable() === true) { + if ($page?->isAccessible() === true) { return $page; } @@ -94,11 +95,10 @@ class Find * Returns the model's object for the given path * * @param string $path Path to parent model - * @return \Kirby\Cms\Model|null * @throws \Kirby\Exception\InvalidArgumentException if the model type is invalid * @throws \Kirby\Exception\NotFoundException if the model cannot be found */ - public static function parent(string $path) + public static function parent(string $path): ModelWithContent { $path = trim($path, '/'); $modelType = in_array($path, ['site', 'account']) ? $path : trim(dirname($path), '/'); @@ -140,10 +140,9 @@ class Find * id is passed * * @param string|null $id User's id - * @return \Kirby\Cms\User|null * @throws \Kirby\Exception\NotFoundException if the user for the given id cannot be found */ - public static function user(string $id = null) + public static function user(string $id = null): User|null { // account is a reserved word to find the current // user. It's used in various API and area routes. diff --git a/kirby/src/Cms/HasChildren.php b/kirby/src/Cms/HasChildren.php index a798272..4bc88da 100644 --- a/kirby/src/Cms/HasChildren.php +++ b/kirby/src/Cms/HasChildren.php @@ -18,72 +18,40 @@ trait HasChildren { /** * The list of available published children - * - * @var \Kirby\Cms\Pages|null */ - public $children; + public Pages|null $children = null; /** * The list of available draft children - * - * @var \Kirby\Cms\Pages|null */ - public $drafts; + public Pages|null $drafts = null; /** * The combined list of available published * and draft children - * - * @var \Kirby\Cms\Pages|null */ - public $childrenAndDrafts; + public Pages|null $childrenAndDrafts = null; /** * Returns all published children - * - * @return \Kirby\Cms\Pages */ - public function children() + public function children(): Pages { - if ($this->children instanceof Pages) { - return $this->children; - } - - return $this->children = Pages::factory($this->inventory()['children'], $this); + return $this->children ??= Pages::factory($this->inventory()['children'], $this); } /** * Returns all published and draft children at the same time - * - * @return \Kirby\Cms\Pages */ - public function childrenAndDrafts() + public function childrenAndDrafts(): Pages { - if ($this->childrenAndDrafts instanceof Pages) { - return $this->childrenAndDrafts; - } - - return $this->childrenAndDrafts = $this->children()->merge($this->drafts()); - } - - /** - * Returns a list of IDs for the model's - * `toArray` method - * - * @return array - */ - protected function convertChildrenToArray(): array - { - return $this->children()->keys(); + return $this->childrenAndDrafts ??= $this->children()->merge($this->drafts()); } /** * Searches for a draft child by ID - * - * @param string $path - * @return \Kirby\Cms\Page|null */ - public function draft(string $path) + public function draft(string $path): Page|null { $path = str_replace('_drafts/', '', $path); @@ -113,10 +81,8 @@ trait HasChildren /** * Returns all draft children - * - * @return \Kirby\Cms\Pages */ - public function drafts() + public function drafts(): Pages { if ($this->drafts instanceof Pages) { return $this->drafts; @@ -137,40 +103,30 @@ trait HasChildren /** * Finds one or multiple published children by ID - * - * @param string ...$arguments - * @return \Kirby\Cms\Page|\Kirby\Cms\Pages|null */ - public function find(...$arguments) + public function find(string|array ...$arguments): Page|Pages|null { return $this->children()->find(...$arguments); } /** * Finds a single published or draft child - * - * @param string $path - * @return \Kirby\Cms\Page|null */ - public function findPageOrDraft(string $path) + public function findPageOrDraft(string $path): Page|null { return $this->children()->find($path) ?? $this->drafts()->find($path); } /** * Returns a collection of all published children of published children - * - * @return \Kirby\Cms\Pages */ - public function grandChildren() + public function grandChildren(): Pages { return $this->children()->children(); } /** * Checks if the model has any published children - * - * @return bool */ public function hasChildren(): bool { @@ -179,8 +135,6 @@ trait HasChildren /** * Checks if the model has any draft children - * - * @return bool */ public function hasDrafts(): bool { @@ -189,8 +143,6 @@ trait HasChildren /** * Checks if the page has any listed children - * - * @return bool */ public function hasListedChildren(): bool { @@ -199,8 +151,6 @@ trait HasChildren /** * Checks if the page has any unlisted children - * - * @return bool */ public function hasUnlistedChildren(): bool { @@ -211,9 +161,8 @@ trait HasChildren * Creates a flat child index * * @param bool $drafts If set to `true`, draft children are included - * @return \Kirby\Cms\Pages */ - public function index(bool $drafts = false) + public function index(bool $drafts = false): Pages { if ($drafts === true) { return $this->childrenAndDrafts()->index($drafts); @@ -225,10 +174,9 @@ trait HasChildren /** * Sets the published children collection * - * @param array|null $children * @return $this */ - protected function setChildren(array $children = null) + protected function setChildren(array $children = null): static { if ($children !== null) { $this->children = Pages::factory($children, $this); @@ -240,10 +188,9 @@ trait HasChildren /** * Sets the draft children collection * - * @param array|null $drafts * @return $this */ - protected function setDrafts(array $drafts = null) + protected function setDrafts(array $drafts = null): static { if ($drafts !== null) { $this->drafts = Pages::factory($drafts, $this, true); diff --git a/kirby/src/Cms/HasFiles.php b/kirby/src/Cms/HasFiles.php index 7d87d83..86788d2 100644 --- a/kirby/src/Cms/HasFiles.php +++ b/kirby/src/Cms/HasFiles.php @@ -17,50 +17,31 @@ trait HasFiles { /** * The Files collection - * - * @var \Kirby\Cms\Files */ - protected $files; + protected Files|array|null $files = null; /** * Filters the Files collection by type audio - * - * @return \Kirby\Cms\Files */ - public function audio() + public function audio(): Files { return $this->files()->filter('type', '==', 'audio'); } /** * Filters the Files collection by type code - * - * @return \Kirby\Cms\Files */ - public function code() + public function code(): Files { return $this->files()->filter('type', '==', 'code'); } - /** - * Returns a list of file ids - * for the toArray method of the model - * - * @return array - */ - protected function convertFilesToArray(): array - { - return $this->files()->keys(); - } - /** * Creates a new file * - * @param array $props * @param bool $move If set to `true`, the source will be deleted - * @return \Kirby\Cms\File */ - public function createFile(array $props, bool $move = false) + public function createFile(array $props, bool $move = false): File { $props = array_merge($props, [ 'parent' => $this, @@ -72,23 +53,19 @@ trait HasFiles /** * Filters the Files collection by type documents - * - * @return \Kirby\Cms\Files */ - public function documents() + public function documents(): Files { return $this->files()->filter('type', '==', 'document'); } /** * Returns a specific file by filename or the first one - * - * @param string|null $filename - * @param string $in - * @return \Kirby\Cms\File|null */ - public function file(string $filename = null, string $in = 'files') - { + public function file( + string $filename = null, + string $in = 'files' + ): File|null { if ($filename === null) { return $this->$in()->first(); } @@ -114,10 +91,8 @@ trait HasFiles /** * Returns the Files collection - * - * @return \Kirby\Cms\Files */ - public function files() + public function files(): Files { if ($this->files instanceof Files) { return $this->files; @@ -128,8 +103,6 @@ trait HasFiles /** * Checks if the Files collection has any audio files - * - * @return bool */ public function hasAudio(): bool { @@ -138,8 +111,6 @@ trait HasFiles /** * Checks if the Files collection has any code files - * - * @return bool */ public function hasCode(): bool { @@ -148,8 +119,6 @@ trait HasFiles /** * Checks if the Files collection has any document files - * - * @return bool */ public function hasDocuments(): bool { @@ -158,8 +127,6 @@ trait HasFiles /** * Checks if the Files collection has any files - * - * @return bool */ public function hasFiles(): bool { @@ -168,8 +135,6 @@ trait HasFiles /** * Checks if the Files collection has any images - * - * @return bool */ public function hasImages(): bool { @@ -178,8 +143,6 @@ trait HasFiles /** * Checks if the Files collection has any videos - * - * @return bool */ public function hasVideos(): bool { @@ -188,21 +151,16 @@ trait HasFiles /** * Returns a specific image by filename or the first one - * - * @param string|null $filename - * @return \Kirby\Cms\File|null */ - public function image(string $filename = null) + public function image(string $filename = null): File|null { return $this->file($filename, 'images'); } /** * Filters the Files collection by type image - * - * @return \Kirby\Cms\Files */ - public function images() + public function images(): Files { return $this->files()->filter('type', '==', 'image'); } @@ -210,10 +168,9 @@ trait HasFiles /** * Sets the Files collection * - * @param \Kirby\Cms\Files|null $files * @return $this */ - protected function setFiles(array $files = null) + protected function setFiles(array $files = null): static { if ($files !== null) { $this->files = Files::factory($files, $this); @@ -224,10 +181,8 @@ trait HasFiles /** * Filters the Files collection by type videos - * - * @return \Kirby\Cms\Files */ - public function videos() + public function videos(): Files { return $this->files()->filter('type', '==', 'video'); } diff --git a/kirby/src/Cms/HasMethods.php b/kirby/src/Cms/HasMethods.php index 053eb8b..9e08d5b 100644 --- a/kirby/src/Cms/HasMethods.php +++ b/kirby/src/Cms/HasMethods.php @@ -2,6 +2,7 @@ namespace Kirby\Cms; +use Closure; use Kirby\Exception\BadMethodCallException; /** @@ -17,22 +18,17 @@ trait HasMethods { /** * All registered methods - * - * @var array */ - public static $methods = []; + public static array $methods = []; /** * Calls a registered method class with the * passed arguments - * * @internal - * @param string $method - * @param array $args - * @return mixed + * * @throws \Kirby\Exception\BadMethodCallException */ - public function callMethod(string $method, array $args = []) + public function callMethod(string $method, array $args = []): mixed { $closure = $this->getMethod($method); @@ -45,10 +41,7 @@ trait HasMethods /** * Checks if the object has a registered method - * * @internal - * @param string $method - * @return bool */ public function hasMethod(string $method): bool { @@ -59,11 +52,8 @@ trait HasMethods * Returns a registered method by name, either from * the current class or from a parent class ordered by * inheritance order (top to bottom) - * - * @param string $method - * @return \Closure|null */ - protected function getMethod(string $method) + protected function getMethod(string $method): Closure|null { if (isset(static::$methods[$method]) === true) { return static::$methods[$method]; diff --git a/kirby/src/Cms/HasSiblings.php b/kirby/src/Cms/HasSiblings.php index fbe7bad..86e63e9 100644 --- a/kirby/src/Cms/HasSiblings.php +++ b/kirby/src/Cms/HasSiblings.php @@ -18,8 +18,6 @@ trait HasSiblings * Returns the position / index in the collection * * @param \Kirby\Cms\Collection|null $collection - * - * @return int|false */ public function indexOf($collection = null): int|false { @@ -88,7 +86,6 @@ trait HasSiblings /** * Returns all sibling elements * - * @param bool $self * @return \Kirby\Cms\Collection */ public function siblings(bool $self = true) @@ -106,8 +103,6 @@ trait HasSiblings * Checks if there's a next item in the collection * * @param \Kirby\Cms\Collection|null $collection - * - * @return bool */ public function hasNext($collection = null): bool { @@ -118,8 +113,6 @@ trait HasSiblings * Checks if there's a previous item in the collection * * @param \Kirby\Cms\Collection|null $collection - * - * @return bool */ public function hasPrev($collection = null): bool { @@ -130,8 +123,6 @@ trait HasSiblings * Checks if the item is the first in the collection * * @param \Kirby\Cms\Collection|null $collection - * - * @return bool */ public function isFirst($collection = null): bool { @@ -143,8 +134,6 @@ trait HasSiblings * Checks if the item is the last in the collection * * @param \Kirby\Cms\Collection|null $collection - * - * @return bool */ public function isLast($collection = null): bool { @@ -156,9 +145,6 @@ trait HasSiblings * Checks if the item is at a certain position * * @param \Kirby\Cms\Collection|null $collection - * @param int $n - * - * @return bool */ public function isNth(int $n, $collection = null): bool { diff --git a/kirby/src/Cms/Helpers.php b/kirby/src/Cms/Helpers.php index ae6c51f..ea5d99a 100644 --- a/kirby/src/Cms/Helpers.php +++ b/kirby/src/Cms/Helpers.php @@ -29,20 +29,14 @@ class Helpers * ``` */ public static $deprecations = [ - // Passing the $slot or $slots variables to snippets is - // deprecated and will break in a future version. - 'snippet-pass-slots' => true, + // Passing a single space as value to `Xml::attr()` has been + // deprecated. In a future version, passing a single space won't + // render an empty value anymore but a single space. + // To render an empty value, please pass an empty string. + 'xml-attr-single-space' => true, - // The `Toolkit\Query` class has been deprecated and will - // be removed in a future version. Use `Query\Query` instead: - // Kirby\Query\Query::factory($query)->resolve($data). - 'toolkit-query-class' => true, - - // Passing an empty string as value to `Xml::attr()` has been - // deprecated. In a future version, passing an empty string won't - // omit the attribute anymore but render it with an empty value. - // To omit the attribute, please pass `null`. - 'xml-attr-empty-string' => false, + // The internal `$model->contentFile*()` methods have been deprecated + 'model-content-file' => true, ]; /** @@ -52,8 +46,10 @@ class Helpers * @param string|null $key If given, the key will be checked against the static array * @return bool Whether the warning was triggered */ - public static function deprecated(string $message, string|null $key = null): bool - { + public static function deprecated( + string $message, + string|null $key = null + ): bool { // only trigger warning in debug mode or when running PHPUnit tests // @codeCoverageIgnoreStart if ( @@ -75,19 +71,16 @@ class Helpers /** * Simple object and variable dumper * to help with debugging. - * - * @param mixed $variable - * @param bool $echo - * @return string */ - public static function dump($variable, bool $echo = true): string + public static function dump(mixed $variable, bool $echo = true): string { - $kirby = App::instance(); + $kirby = App::instance(); + $output = print_r($variable, true); if ($kirby->environment()->cli() === true) { - $output = print_r($variable, true) . PHP_EOL; + $output .= PHP_EOL; } else { - $output = '
      ' . print_r($variable, true) . '
      '; + $output = Str::wrap($output, '
      ', '
      '); } if ($echo === true) { @@ -110,8 +103,11 @@ class Helpers * @return mixed Return value of the `$action` closure, * possibly overridden by `$fallback` */ - public static function handleErrors(Closure $action, Closure $condition, $fallback = null) - { + public static function handleErrors( + Closure $action, + Closure $condition, + $fallback = null + ) { $override = null; $handler = set_error_handler(function () use (&$override, &$handler, $condition, $fallback) { @@ -152,7 +148,6 @@ class Helpers * @internal * * @param string $name Name of the helper - * @return bool */ public static function hasOverride(string $name): bool { @@ -164,11 +159,9 @@ class Helpers * Determines the size/length of numbers, * strings, arrays and countable objects * - * @param mixed $value - * @return int * @throws \Kirby\Exception\InvalidArgumentException */ - public static function size($value): int + public static function size(mixed $value): int { if (is_numeric($value)) { return (int)$value; diff --git a/kirby/src/Cms/Html.php b/kirby/src/Cms/Html.php index f8f3388..9ab61c4 100644 --- a/kirby/src/Cms/Html.php +++ b/kirby/src/Cms/Html.php @@ -23,11 +23,20 @@ class Html extends \Kirby\Toolkit\Html * @since 3.7.0 * * @param string|array $url Relative or absolute URLs, an array of URLs or `@auto` for automatic template css loading - * @param string|array $options Pass an array of attributes for the link tag or a media attribute string - * @return string|null + * @param string|array|null $options Pass an array of attributes for the link tag or a media attribute string */ - public static function css($url, $options = null): string|null - { + public static function css( + string|array|Plugin|PluginAssets $url, + string|array|null $options = null + ): string|null { + if ($url instanceof Plugin) { + $url = $url->assets(); + } + + if ($url instanceof PluginAssets) { + $url = $url->css()->values(fn ($asset) => $asset->url()); + } + if (is_array($url) === true) { $links = A::map($url, fn ($url) => static::css($url, $options)); return implode(PHP_EOL, $links); @@ -69,23 +78,31 @@ class Html extends \Kirby\Toolkit\Html * @param string|null $href Relative or absolute Url * @param string|array|null $text If `null`, the link will be used as link text. If an array is passed, each element will be added unencoded * @param array $attr Additional attributes for the a tag. - * @return string */ - public static function link(string $href = null, $text = null, array $attr = []): string - { + public static function link( + string|null $href = null, + string|array $text = null, + array $attr = [] + ): string { return parent::link(Url::to($href), $text, $attr); } /** * Creates a script tag to load a javascript file * @since 3.7.0 - * - * @param string|array $url - * @param string|array $options - * @return string|null */ - public static function js($url, $options = null): string|null - { + public static function js( + string|array|Plugin|PluginAssets $url, + string|array|bool|null $options = null + ): string|null { + if ($url instanceof Plugin) { + $url = $url->assets(); + } + + if ($url instanceof PluginAssets) { + $url = $url->js()->values(fn ($asset) => $asset->url()); + } + if (is_array($url) === true) { $scripts = A::map($url, fn ($url) => static::js($url, $options)); return implode(PHP_EOL, $scripts); @@ -114,11 +131,8 @@ class Html extends \Kirby\Toolkit\Html * Includes an SVG file by absolute or * relative file path. * @since 3.7.0 - * - * @param string|\Kirby\Cms\File $file - * @return string|false */ - public static function svg($file) + public static function svg(string|File $file): string|false { // support for Kirby's file objects if ( diff --git a/kirby/src/Cms/Ingredients.php b/kirby/src/Cms/Ingredients.php index ab84861..f9c1c59 100644 --- a/kirby/src/Cms/Ingredients.php +++ b/kirby/src/Cms/Ingredients.php @@ -25,8 +25,6 @@ class Ingredients /** * Creates a new ingredient collection - * - * @param array $ingredients */ public function __construct(array $ingredients) { @@ -35,20 +33,15 @@ class Ingredients /** * Magic getter for single ingredients - * - * @param string $method - * @param array|null $args - * @return mixed */ - public function __call(string $method, array $args = null) + public function __call(string $method, array $args = null): mixed { return $this->ingredients[$method] ?? null; } /** * Improved `var_dump` output - * - * @return array + * @codeCoverageIgnore */ public function __debugInfo(): array { @@ -57,9 +50,6 @@ class Ingredients /** * Get a single ingredient by key - * - * @param string $key - * @return mixed */ public function __get(string $key) { @@ -69,12 +59,9 @@ class Ingredients /** * Resolves all ingredient callbacks * and creates a plain array - * * @internal - * @param array $ingredients - * @return static */ - public static function bake(array $ingredients) + public static function bake(array $ingredients): static { foreach ($ingredients as $name => $ingredient) { if ($ingredient instanceof Closure) { @@ -87,8 +74,6 @@ class Ingredients /** * Returns all ingredients as plain array - * - * @return array */ public function toArray(): array { diff --git a/kirby/src/Cms/Item.php b/kirby/src/Cms/Item.php index b7e3d3f..86dd85a 100644 --- a/kirby/src/Cms/Item.php +++ b/kirby/src/Cms/Item.php @@ -2,6 +2,7 @@ namespace Kirby\Cms; +use Kirby\Content\Field; use Kirby\Toolkit\Str; /** @@ -28,49 +29,28 @@ class Item protected Field|null $field; - /** - * @var string - */ - protected $id; - - /** - * @var array - */ - protected $params; - - /** - * @var \Kirby\Cms\Page|\Kirby\Cms\Site|\Kirby\Cms\File|\Kirby\Cms\User - */ - protected $parent; - - /** - * @var \Kirby\Cms\Items - */ - protected $siblings; + protected string $id; + protected array $params; + protected ModelWithContent $parent; + protected Items $siblings; /** * Creates a new item - * - * @param array $params */ public function __construct(array $params = []) { - $siblingsClass = static::ITEMS_CLASS; - + $class = static::ITEMS_CLASS; $this->id = $params['id'] ?? Str::uuid(); $this->params = $params; $this->field = $params['field'] ?? null; $this->parent = $params['parent'] ?? App::instance()->site(); - $this->siblings = $params['siblings'] ?? new $siblingsClass(); + $this->siblings = $params['siblings'] ?? new $class(); } /** * Static Item factory - * - * @param array $params - * @return \Kirby\Cms\Item */ - public static function factory(array $params) + public static function factory(array $params): static { return new static($params); } @@ -85,8 +65,6 @@ class Item /** * Returns the unique item id (UUID v4) - * - * @return string */ public function id(): string { @@ -95,9 +73,6 @@ class Item /** * Compares the item to another one - * - * @param \Kirby\Cms\Item $item - * @return bool */ public function is(Item $item): bool { @@ -106,20 +81,16 @@ class Item /** * Returns the Kirby instance - * - * @return \Kirby\Cms\App */ - public function kirby() + public function kirby(): App { return $this->parent()->kirby(); } /** * Returns the parent model - * - * @return \Kirby\Cms\Page|\Kirby\Cms\Site|\Kirby\Cms\File|\Kirby\Cms\User */ - public function parent() + public function parent(): ModelWithContent { return $this->parent; } @@ -128,18 +99,15 @@ class Item * Returns the sibling collection * This is required by the HasSiblings trait * - * @return \Kirby\Cms\Items * @psalm-return self::ITEMS_CLASS */ - protected function siblingsCollection() + protected function siblingsCollection(): Items { return $this->siblings; } /** * Converts the item to an array - * - * @return array */ public function toArray(): array { diff --git a/kirby/src/Cms/Items.php b/kirby/src/Cms/Items.php index 7910e2f..97f8104 100644 --- a/kirby/src/Cms/Items.php +++ b/kirby/src/Cms/Items.php @@ -3,7 +3,8 @@ namespace Kirby\Cms; use Closure; -use Exception; +use Kirby\Content\Field; +use Kirby\Exception\InvalidArgumentException; /** * A collection of items @@ -23,27 +24,16 @@ class Items extends Collection /** * All registered items methods - * - * @var array */ - public static $methods = []; + public static array $methods = []; - /** - * @var array - */ - protected $options; + protected array $options; /** * @var \Kirby\Cms\ModelWithContent */ protected $parent; - /** - * Constructor - * - * @param array $objects - * @param array $options - */ public function __construct($objects = [], array $options = []) { $this->options = $options; @@ -56,41 +46,36 @@ class Items extends Collection /** * Creates a new item collection from a * an array of item props - * - * @param array $items - * @param array $params - * @return \Kirby\Cms\Items */ - public static function factory(array $items = null, array $params = []) - { - $options = array_merge([ - 'field' => null, - 'options' => [], - 'parent' => App::instance()->site(), - ], $params); - + public static function factory( + array $items = null, + array $params = [] + ): static { if (empty($items) === true || is_array($items) === false) { return new static(); } - if (is_array($options) === false) { - throw new Exception('Invalid item options'); + if (is_array($params) === false) { + throw new InvalidArgumentException('Invalid item options'); } // create a new collection of blocks - $collection = new static([], $options); + $collection = new static([], $params); - foreach ($items as $params) { - if (is_array($params) === false) { - continue; + foreach ($items as $item) { + if (is_array($item) === false) { + throw new InvalidArgumentException('Invalid data for ' . static::ITEM_CLASS); } - $params['field'] = $options['field']; - $params['options'] = $options['options']; - $params['parent'] = $options['parent']; - $params['siblings'] = $collection; + // inject properties from the parent + $item['field'] = $collection->field(); + $item['options'] = $params['options'] ?? []; + $item['parent'] = $collection->parent(); + $item['siblings'] = $collection; + $item['params'] = $item; + $class = static::ITEM_CLASS; - $item = $class::factory($params); + $item = $class::factory($item); $collection->append($item->id(), $item); } @@ -107,8 +92,6 @@ class Items extends Collection /** * Convert the items to an array - * - * @return array */ public function toArray(Closure $map = null): array { diff --git a/kirby/src/Cms/Language.php b/kirby/src/Cms/Language.php index df1733e..5921867 100644 --- a/kirby/src/Cms/Language.php +++ b/kirby/src/Cms/Language.php @@ -4,7 +4,8 @@ namespace Kirby\Cms; use Kirby\Data\Data; use Kirby\Exception\Exception; -use Kirby\Exception\PermissionException; +use Kirby\Exception\InvalidArgumentException; +use Kirby\Exception\LogicException; use Kirby\Filesystem\F; use Kirby\Toolkit\Locale; use Kirby\Toolkit\Str; @@ -26,80 +27,54 @@ use Throwable; * @copyright Bastian Allgeier * @license https://getkirby.com/license */ -class Language extends Model +class Language { - /** - * @var string - */ - protected $code; + use HasSiblings; /** - * @var bool + * The parent Kirby instance */ - protected $default; + public static App|null $kirby; - /** - * @var string - */ - protected $direction; - - /** - * @var array - */ - protected $locale; - - /** - * @var string - */ - protected $name; - - /** - * @var array|null - */ - protected $slugs; - - /** - * @var array|null - */ - protected $smartypants; - - /** - * @var array|null - */ - protected $translations; - - /** - * @var string - */ - protected $url; + protected string $code; + protected bool $default; + protected string $direction; + protected array $locale; + protected string $name; + protected array $slugs; + protected array $smartypants; + protected array $translations; + protected string|null $url; /** * Creates a new language object - * - * @param array $props */ public function __construct(array $props) { - $this->setRequiredProperties($props, [ - 'code' - ]); + if (isset($props['code']) === false) { + throw new InvalidArgumentException('The property "code" is required'); + } - $this->setOptionalProperties($props, [ - 'default', - 'direction', - 'locale', - 'name', - 'slugs', - 'smartypants', - 'translations', - 'url', - ]); + static::$kirby = $props['kirby'] ?? null; + $this->code = trim($props['code']); + $this->default = ($props['default'] ?? false) === true; + $this->direction = ($props['direction'] ?? null) === 'rtl' ? 'rtl' : 'ltr'; + $this->name = trim($props['name'] ?? $this->code); + $this->slugs = $props['slugs'] ?? []; + $this->smartypants = $props['smartypants'] ?? []; + $this->translations = $props['translations'] ?? []; + $this->url = $props['url'] ?? null; + + if ($locale = $props['locale'] ?? null) { + $this->locale = Locale::normalize($locale); + } else { + $this->locale = [LC_ALL => $this->code]; + } } /** * Improved `var_dump` output - * - * @return array + * @codeCoverageIgnore */ public function __debugInfo(): array { @@ -109,8 +84,6 @@ class Language extends Model /** * Returns the language code * when the language is converted to a string - * - * @return string */ public function __toString(): string { @@ -120,8 +93,6 @@ class Language extends Model /** * Returns the base Url for the language * without the path or other cruft - * - * @return string */ public function baseUrl(): string { @@ -139,67 +110,40 @@ class Language extends Model return Url::base($languageUrl) ?? $kirbyUrl; } + /** + * Creates an instance with the same + * initial properties. + */ + public function clone(array $props = []): static + { + return new static(array_replace_recursive([ + 'code' => $this->code, + 'default' => $this->default, + 'direction' => $this->direction, + 'locale' => $this->locale, + 'name' => $this->name, + 'slugs' => $this->slugs, + 'smartypants' => $this->smartypants, + 'translations' => $this->translations, + 'url' => $this->url, + ], $props)); + } + /** * Returns the language code/id. * The language code is used in * text file names as appendix. - * - * @return string */ public function code(): string { return $this->code; } - /** - * Internal converter to create or remove - * translation files. - * - * @param string $from - * @param string $to - * @return bool - */ - protected static function converter(string $from, string $to): bool - { - $kirby = App::instance(); - $site = $kirby->site(); - - // convert site - foreach ($site->files() as $file) { - F::move($file->contentFile($from, true), $file->contentFile($to, true)); - } - - F::move($site->contentFile($from, true), $site->contentFile($to, true)); - - // convert all pages - foreach ($kirby->site()->index(true) as $page) { - foreach ($page->files() as $file) { - F::move($file->contentFile($from, true), $file->contentFile($to, true)); - } - - F::move($page->contentFile($from, true), $page->contentFile($to, true)); - } - - // convert all users - foreach ($kirby->users() as $user) { - foreach ($user->files() as $file) { - F::move($file->contentFile($from, true), $file->contentFile($to, true)); - } - - F::move($user->contentFile($from, true), $user->contentFile($to, true)); - } - - return true; - } - /** * Creates a new language object - * * @internal - * @param array $props - * @return static */ - public static function create(array $props) + public static function create(array $props): static { $props['code'] = Str::slug($props['code'] ?? null); $kirby = App::instance(); @@ -227,7 +171,12 @@ class Language extends Model $language->save(); if ($languages->count() === 0) { - static::converter('', $language->code()); + foreach ($kirby->models() as $model) { + $model->storage()->convertLanguage( + 'default', + $language->code() + ); + } } // update the main languages collection in the app instance @@ -248,17 +197,18 @@ class Language extends Model /** * Delete the current language and * all its translation files - * * @internal - * @return bool + * * @throws \Kirby\Exception\Exception */ public function delete(): bool { - $kirby = App::instance(); - $languages = $kirby->languages(); - $code = $this->code(); - $isLast = $languages->count() === 1; + $kirby = App::instance(); + $code = $this->code(); + + if ($this->isDeletable() === false) { + throw new Exception('The language cannot be deleted'); + } // trigger before hook $kirby->trigger('language.delete:before', [ @@ -269,10 +219,12 @@ class Language extends Model throw new Exception('The language could not be deleted'); } - if ($isLast === true) { - $this->converter($code, ''); - } else { - $this->deleteContentFiles($code); + foreach ($kirby->models() as $model) { + if ($this->isLast() === true) { + $model->storage()->convertLanguage($code, 'default'); + } else { + $model->storage()->deleteLanguage($code); + } } // get the original language collection and remove the current language @@ -286,43 +238,8 @@ class Language extends Model return true; } - /** - * When the language is deleted, all content files with - * the language code must be removed as well. - * - * @param mixed $code - * @return bool - */ - protected function deleteContentFiles($code): bool - { - $kirby = App::instance(); - $site = $kirby->site(); - - F::remove($site->contentFile($code, true)); - - foreach ($kirby->site()->index(true) as $page) { - foreach ($page->files() as $file) { - F::remove($file->contentFile($code, true)); - } - - F::remove($page->contentFile($code, true)); - } - - foreach ($kirby->users() as $user) { - foreach ($user->files() as $file) { - F::remove($file->contentFile($code, true)); - } - - F::remove($user->contentFile($code, true)); - } - - return true; - } - /** * Reading direction of this language - * - * @return string */ public function direction(): string { @@ -331,8 +248,6 @@ class Language extends Model /** * Check if the language file exists - * - * @return bool */ public function exists(): bool { @@ -342,19 +257,36 @@ class Language extends Model /** * Checks if this is the default language * for the site. - * - * @return bool */ public function isDefault(): bool { return $this->default; } + /** + * Checks if the language can be deleted + */ + public function isDeletable(): bool + { + // the default language can only be deleted if it's the last + if ($this->isDefault() === true && $this->isLast() === false) { + return false; + } + + return true; + } + + /** + * Checks if this is the last language + */ + public function isLast(): bool + { + return App::instance()->languages()->count() === 1; + } + /** * The id is required for collections * to work properly. The code is used as id - * - * @return string */ public function id(): string { @@ -362,11 +294,17 @@ class Language extends Model } /** - * Loads the language rules for provided locale code - * - * @param string $code + * Returns the parent Kirby instance */ - public static function loadRules(string $code) + public function kirby(): App + { + return static::$kirby ??= App::instance(); + } + + /** + * Loads the language rules for provided locale code + */ + public static function loadRules(string $code): array { $kirby = App::instance(); $code = Str::contains($code, '.') ? Str::before($code, '.') : $code; @@ -387,9 +325,8 @@ class Language extends Model * Returns the PHP locale setting array * * @param int $category If passed, returns the locale for the specified category (e.g. LC_ALL) as string - * @return array|string */ - public function locale(int $category = null) + public function locale(int $category = null): array|string|null { if ($category !== null) { return $this->locale[$category] ?? $this->locale[LC_ALL] ?? null; @@ -401,8 +338,6 @@ class Language extends Model /** * Returns the human-readable name * of the language - * - * @return string */ public function name(): string { @@ -411,8 +346,6 @@ class Language extends Model /** * Returns the URL path for the language - * - * @return string */ public function path(): string { @@ -425,8 +358,6 @@ class Language extends Model /** * Returns the routing pattern for the language - * - * @return string */ public function pattern(): string { @@ -441,8 +372,6 @@ class Language extends Model /** * Returns the absolute path to the language file - * - * @return string */ public function root(): string { @@ -453,19 +382,15 @@ class Language extends Model * Returns the LanguageRouter instance * which is used to handle language specific * routes. - * - * @return \Kirby\Cms\LanguageRouter */ - public function router() + public function router(): LanguageRouter { return new LanguageRouter($this); } /** * Get slug rules for language - * * @internal - * @return array */ public function rules(): array { @@ -476,11 +401,11 @@ class Language extends Model /** * Saves the language settings in the languages folder - * * @internal + * * @return $this */ - public function save() + public function save(): static { try { $existingData = Data::read($this->root()); @@ -508,104 +433,15 @@ class Language extends Model } /** - * @param string $code - * @return $this + * Private siblings collector */ - protected function setCode(string $code) + protected function siblingsCollection(): Collection { - $this->code = trim($code); - return $this; - } - - /** - * @param bool $default - * @return $this - */ - protected function setDefault(bool $default = false) - { - $this->default = $default; - return $this; - } - - /** - * @param string $direction - * @return $this - */ - protected function setDirection(string $direction = 'ltr') - { - $this->direction = $direction === 'rtl' ? 'rtl' : 'ltr'; - return $this; - } - - /** - * @param string|array $locale - * @return $this - */ - protected function setLocale($locale = null) - { - if ($locale === null) { - $this->locale = [LC_ALL => $this->code]; - } else { - $this->locale = Locale::normalize($locale); - } - - return $this; - } - - /** - * @param string $name - * @return $this - */ - protected function setName(string $name = null) - { - $this->name = trim($name ?? $this->code); - return $this; - } - - /** - * @param array $slugs - * @return $this - */ - protected function setSlugs(array $slugs = null) - { - $this->slugs = $slugs ?? []; - return $this; - } - - /** - * @param array $smartypants - * @return $this - */ - protected function setSmartypants(array $smartypants = null) - { - $this->smartypants = $smartypants ?? []; - return $this; - } - - /** - * @param array $translations - * @return $this - */ - protected function setTranslations(array $translations = null) - { - $this->translations = $translations ?? []; - return $this; - } - - /** - * @param string $url - * @return $this - */ - protected function setUrl(string $url = null) - { - $this->url = $url; - return $this; + return App::instance()->languages(); } /** * Returns the custom slug rules for this language - * - * @return array */ public function slugs(): array { @@ -614,8 +450,6 @@ class Language extends Model /** * Returns the custom SmartyPants options for this language - * - * @return array */ public function smartypants(): array { @@ -625,8 +459,6 @@ class Language extends Model /** * Returns the most important * properties as array - * - * @return array */ public function toArray(): array { @@ -643,8 +475,6 @@ class Language extends Model /** * Returns the translation strings for this language - * - * @return array */ public function translations(): array { @@ -653,8 +483,6 @@ class Language extends Model /** * Returns the absolute Url for the language - * - * @return string */ public function url(): string { @@ -665,12 +493,9 @@ class Language extends Model /** * Update language properties and save them - * * @internal - * @param array $props - * @return static */ - public function update(array $props = null) + public function update(array $props = null): static { // don't change the language code unset($props['code']); @@ -681,6 +506,10 @@ class Language extends Model $kirby = App::instance(); $updated = $this->clone($props); + if (isset($props['translations']) === true) { + $updated->translations = $props['translations']; + } + // validate the updated language LanguageRules::update($updated); @@ -690,32 +519,31 @@ class Language extends Model 'input' => $props ]); - // convert the current default to a non-default language - if ($updated->isDefault() === true) { - $kirby->defaultLanguage()?->clone(['default' => false])->save(); + // if language just got promoted to be the new default language… + if ($this->isDefault() === false && $updated->isDefault() === true) { + // convert the current default to a non-default language + $previous = $kirby->defaultLanguage()?->clone(['default' => false])->save(); + $kirby->languages(false)->set($previous->code(), $previous); - $code = $this->code(); - $site = $kirby->site(); - - touch($site->contentFile($code)); - - foreach ($kirby->site()->index(true) as $page) { - $files = $page->files(); - - foreach ($files as $file) { - touch($file->contentFile($code)); - } - - touch($page->contentFile($code)); + foreach ($kirby->models() as $model) { + $model->storage()->touchLanguage($this); } - } elseif ($this->isDefault() === true) { - throw new PermissionException('Please select another language to be the primary language'); + } + + // if language was the default language and got demoted… + if ( + $this->isDefault() === true && + $updated->isDefault() === false && + $kirby->defaultLanguage()->code() === $this->code() + ) { + // ensure another language has already been set as default + throw new LogicException('Please select another language to be the primary language'); } $language = $updated->save(); - // make sure the language is also updated in the Kirby language collection - App::instance()->languages(false)->set($language->code(), $language); + // make sure the language is also updated in the languages collection + $kirby->languages(false)->set($language->code(), $language); // trigger after hook $kirby->trigger('language.update:after', [ @@ -726,4 +554,19 @@ class Language extends Model return $language; } + + /** + * Returns a language variable object + * for the key in the translations array + */ + public function variable(string $key, bool $decode = false): LanguageVariable + { + // allows decoding if base64-url encoded url is sent + // for compatibility of different environments + if ($decode === true) { + $key = rawurldecode(base64_decode($key)); + } + + return new LanguageVariable($this, $key); + } } diff --git a/kirby/src/Cms/LanguageRouter.php b/kirby/src/Cms/LanguageRouter.php index 60d3623..bca5daf 100644 --- a/kirby/src/Cms/LanguageRouter.php +++ b/kirby/src/Cms/LanguageRouter.php @@ -20,36 +20,21 @@ use Kirby\Toolkit\Str; */ class LanguageRouter { - /** - * The parent language - * - * @var Language - */ - protected $language; - - /** - * The router instance - * - * @var Router - */ - protected $router; + protected Router $router; /** * Creates a new language router instance * for the given language - * - * @param \Kirby\Cms\Language $language */ - public function __construct(Language $language) - { - $this->language = $language; + public function __construct( + protected Language $language + ) { } /** * Fetches all scoped routes for the * current language from the Kirby instance * - * @return array * @throws \Kirby\Exception\NotFoundException */ public function routes(): array @@ -106,26 +91,32 @@ class LanguageRouter * Wrapper around the Router::call method * that injects the Language instance and * if needed also the Page as arguments. - * - * @param string|null $path - * @return mixed */ - public function call(string $path = null) + public function call(string|null $path = null): mixed { - $language = $this->language; - $kirby = $language->kirby(); - $router = new Router($this->routes()); + $language = $this->language; + $kirby = $language->kirby(); + $this->router ??= new Router($this->routes()); try { - return $router->call($path, $kirby->request()->method(), function ($route) use ($kirby, $language) { + return $this->router->call($path, $kirby->request()->method(), function ($route) use ($kirby, $language) { $kirby->setCurrentTranslation($language); $kirby->setCurrentLanguage($language); if ($page = $route->page()) { - return $route->action()->call($route, $language, $page, ...$route->arguments()); + return $route->action()->call( + $route, + $language, + $page, + ...$route->arguments() + ); } - return $route->action()->call($route, $language, ...$route->arguments()); + return $route->action()->call( + $route, + $language, + ...$route->arguments() + ); }); } catch (Exception) { return $kirby->resolve($path, $language->code()); diff --git a/kirby/src/Cms/LanguageRoutes.php b/kirby/src/Cms/LanguageRoutes.php index cfce0ca..68bdf42 100644 --- a/kirby/src/Cms/LanguageRoutes.php +++ b/kirby/src/Cms/LanguageRoutes.php @@ -8,9 +8,6 @@ class LanguageRoutes { /** * Creates all multi-language routes - * - * @param \Kirby\Cms\App $kirby - * @return array */ public static function create(App $kirby): array { @@ -58,9 +55,6 @@ class LanguageRoutes /** * Create the fallback route * for unprefixed default language URLs. - * - * @param \Kirby\Cms\App $kirby - * @return array */ public static function fallback(App $kirby): array { @@ -73,7 +67,10 @@ class LanguageRoutes $extension = F::extension($path); // try to redirect prefixed pages - if (empty($extension) === true && $page = $kirby->page($path)) { + if ( + empty($extension) === true && + $page = $kirby->page($path) + ) { $url = $kirby->request()->url([ 'query' => null, 'params' => null, @@ -81,15 +78,17 @@ class LanguageRoutes ]); if ($url->toString() !== $page->url()) { - // redirect to translated page directly - // if translation is exists and languages detect is enabled + // redirect to translated page directly if translation + // is exists and languages detect is enabled + $lang = $kirby->detectedLanguage()->code(); + if ( $kirby->option('languages.detect') === true && - $page->translation($kirby->detectedLanguage()->code())->exists() === true + $page->translation($lang)->exists() === true ) { return $kirby ->response() - ->redirect($page->url($kirby->detectedLanguage()->code())); + ->redirect($page->url($lang)); } return $kirby @@ -105,9 +104,6 @@ class LanguageRoutes /** * Create the multi-language home page route - * - * @param \Kirby\Cms\App $kirby - * @return array */ public static function home(App $kirby): array { @@ -118,7 +114,10 @@ class LanguageRoutes 'env' => 'site', 'action' => function () use ($kirby) { // find all languages with the same base url as the current installation - $languages = $kirby->languages()->filter('baseurl', $kirby->url()); + $languages = $kirby->languages()->filter( + 'baseurl', + $kirby->url() + ); // if there's no language with a matching base url, // redirect to the default language @@ -128,7 +127,8 @@ class LanguageRoutes ->redirect($kirby->defaultLanguage()->url()); } - // if there's just one language, we take that to render the home page + // if there's just one language, + // we take that to render the home page if ($languages->count() === 1) { $currentLanguage = $languages->first(); } else { diff --git a/kirby/src/Cms/LanguageRules.php b/kirby/src/Cms/LanguageRules.php index 31c751e..6263b90 100644 --- a/kirby/src/Cms/LanguageRules.php +++ b/kirby/src/Cms/LanguageRules.php @@ -20,8 +20,6 @@ class LanguageRules /** * Validates if the language can be created * - * @param \Kirby\Cms\Language $language - * @return bool * @throws \Kirby\Exception\DuplicateException If the language already exists */ public static function create(Language $language): bool @@ -43,8 +41,6 @@ class LanguageRules /** * Validates if the language can be updated - * - * @param \Kirby\Cms\Language $language */ public static function update(Language $language) { @@ -55,8 +51,6 @@ class LanguageRules /** * Validates if the language code is formatted correctly * - * @param \Kirby\Cms\Language $language - * @return bool * @throws \Kirby\Exception\InvalidArgumentException If the language code is not valid */ public static function validLanguageCode(Language $language): bool @@ -77,8 +71,6 @@ class LanguageRules /** * Validates if the language name is formatted correctly * - * @param \Kirby\Cms\Language $language - * @return bool * @throws \Kirby\Exception\InvalidArgumentException If the language name is invalid */ public static function validLanguageName(Language $language): bool diff --git a/kirby/src/Cms/LanguageVariable.php b/kirby/src/Cms/LanguageVariable.php new file mode 100644 index 0000000..b26336e --- /dev/null +++ b/kirby/src/Cms/LanguageVariable.php @@ -0,0 +1,122 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier + * @license https://getkirby.com/license + */ +class LanguageVariable +{ + protected App $kirby; + + public function __construct( + protected Language $language, + protected string $key + ) { + $this->kirby = App::instance(); + } + + /** + * Creates a new language variable. This will + * be added to the default language first and + * can then be translated in other languages. + */ + public static function create( + string $key, + string|null $value = null + ): static { + if (is_numeric($key) === true) { + throw new InvalidArgumentException('The variable key must not be numeric'); + } + + if (empty($key) === true) { + throw new InvalidArgumentException('The variable needs a valid key'); + } + + $kirby = App::instance(); + $language = $kirby->defaultLanguage(); + $translations = $language->translations(); + + if ($kirby->translation()->get($key) !== null) { + if (isset($translations[$key]) === true) { + throw new DuplicateException('The variable already exists'); + } + + throw new DuplicateException('The variable is part of the core translation and cannot be overwritten'); + } + + $translations[$key] = trim($value ?? ''); + + $language->update(['translations' => $translations]); + + return $language->variable($key); + } + + /** + * Deletes a language variable from the translations array. + * This will go through all language files and delete the + * key from all translation arrays to keep them clean. + */ + public function delete(): bool + { + // go through all languages and remove the variable + foreach ($this->kirby->languages() as $language) { + $variables = $language->translations(); + + unset($variables[$this->key]); + + $language->update(['translations' => $variables]); + } + + return true; + } + + /** + * Checks if a language variable exists in the default language + */ + public function exists(): bool + { + $language = $this->kirby->defaultLanguage(); + return isset($language->translations()[$this->key]) === true; + } + + /** + * Returns the unique key for the variable + */ + public function key(): string + { + return $this->key; + } + + /** + * Sets a new value for the language variable + */ + public function update(string $value): static + { + $translations = $this->language->translations(); + $translations[$this->key] = $value; + + $language = $this->language->update(['translations' => $translations]); + + return $language->variable($this->key); + } + + /** + * Returns the value if the variable has been translated. + */ + public function value(): string|null + { + return $this->language->translations()[$this->key] ?? null; + } +} diff --git a/kirby/src/Cms/Languages.php b/kirby/src/Cms/Languages.php index 7143340..ba631b9 100644 --- a/kirby/src/Cms/Languages.php +++ b/kirby/src/Cms/Languages.php @@ -18,20 +18,19 @@ class Languages extends Collection { /** * All registered languages methods - * - * @var array */ - public static $methods = []; + public static array $methods = []; /** * Creates a new collection with the given language objects * - * @param array $objects `Kirby\Cms\Language` objects * @param null $parent * @throws \Kirby\Exception\DuplicateException */ - public function __construct($objects = [], $parent = null) - { + public function __construct( + array $objects = [], + $parent = null + ) { $defaults = array_filter( $objects, fn ($language) => $language->isDefault() === true @@ -41,48 +40,39 @@ class Languages extends Collection throw new DuplicateException('You cannot have multiple default languages. Please check your language config files.'); } - parent::__construct($objects, $parent); + parent::__construct($objects, null); } /** * Returns all language codes as array - * - * @return array */ public function codes(): array { - return $this->keys(); + return App::instance()->multilang() ? $this->keys() : ['default']; } /** * Creates a new language with the given props - * * @internal - * @param array $props - * @return \Kirby\Cms\Language */ - public function create(array $props) + public function create(array $props): Language { return Language::create($props); } /** * Returns the default language - * - * @return \Kirby\Cms\Language|null */ - public function default() + public function default(): Language|null { return $this->findBy('isDefault', true) ?? $this->first(); } /** * Convert all defined languages to a collection - * * @internal - * @return static */ - public static function load() + public static function load(): static { $languages = []; $files = glob(App::instance()->root('languages') . '/*.php'); diff --git a/kirby/src/Cms/Layout.php b/kirby/src/Cms/Layout.php index 2aaa960..c6920fd 100644 --- a/kirby/src/Cms/Layout.php +++ b/kirby/src/Cms/Layout.php @@ -2,6 +2,8 @@ namespace Kirby\Cms; +use Kirby\Content\Content; + /** * Represents a single Layout with * multiple columns @@ -19,24 +21,13 @@ class Layout extends Item public const ITEMS_CLASS = Layouts::class; - /** - * @var \Kirby\Cms\Content - */ - protected $attrs; - - /** - * @var \Kirby\Cms\LayoutColumns - */ - protected $columns; + protected Content $attrs; + protected LayoutColumns $columns; /** * Proxy for attrs - * - * @param string $method - * @param array $args - * @return \Kirby\Cms\Field */ - public function __call(string $method, array $args = []) + public function __call(string $method, array $args = []): mixed { // layout methods if ($this->hasMethod($method) === true) { @@ -48,8 +39,6 @@ class Layout extends Item /** * Creates a new Layout object - * - * @param array $params */ public function __construct(array $params = []) { @@ -66,20 +55,16 @@ class Layout extends Item /** * Returns the attrs object - * - * @return \Kirby\Cms\Content */ - public function attrs() + public function attrs(): Content { return $this->attrs; } /** * Returns the columns in this layout - * - * @return \Kirby\Cms\LayoutColumns */ - public function columns() + public function columns(): LayoutColumns { return $this->columns; } @@ -87,24 +72,18 @@ class Layout extends Item /** * Checks if the layout is empty * @since 3.5.2 - * - * @return bool */ public function isEmpty(): bool { return $this ->columns() - ->filter(function ($column) { - return $column->isNotEmpty(); - }) + ->filter('isEmpty', false) ->count() === 0; } /** * Checks if the layout is not empty * @since 3.5.2 - * - * @return bool */ public function isNotEmpty(): bool { @@ -114,8 +93,6 @@ class Layout extends Item /** * The result is being sent to the editor * via the API in the panel - * - * @return array */ public function toArray(): array { diff --git a/kirby/src/Cms/LayoutColumn.php b/kirby/src/Cms/LayoutColumn.php index b4706cc..11d61d7 100644 --- a/kirby/src/Cms/LayoutColumn.php +++ b/kirby/src/Cms/LayoutColumn.php @@ -21,20 +21,11 @@ class LayoutColumn extends Item public const ITEMS_CLASS = LayoutColumns::class; - /** - * @var \Kirby\Cms\Blocks - */ - protected $blocks; - - /** - * @var string - */ - protected $width; + protected Blocks $blocks; + protected string $width; /** * Creates a new LayoutColumn object - * - * @param array $params */ public function __construct(array $params = []) { @@ -50,12 +41,8 @@ class LayoutColumn extends Item /** * Magic getter function - * - * @param string $method - * @param mixed $args - * @return mixed */ - public function __call(string $method, $args) + public function __call(string $method, mixed $args): mixed { // layout column methods if ($this->hasMethod($method) === true) { @@ -67,9 +54,8 @@ class LayoutColumn extends Item * Returns the blocks collection * * @param bool $includeHidden Sets whether to include hidden blocks - * @return \Kirby\Cms\Blocks */ - public function blocks(bool $includeHidden = false) + public function blocks(bool $includeHidden = false): Blocks { if ($includeHidden === false) { return $this->blocks->filter('isHidden', false); @@ -81,8 +67,6 @@ class LayoutColumn extends Item /** * Checks if the column is empty * @since 3.5.2 - * - * @return bool */ public function isEmpty(): bool { @@ -95,8 +79,6 @@ class LayoutColumn extends Item /** * Checks if the column is not empty * @since 3.5.2 - * - * @return bool */ public function isNotEmpty(): bool { @@ -105,9 +87,6 @@ class LayoutColumn extends Item /** * Returns the number of columns this column spans - * - * @param int $columns - * @return int */ public function span(int $columns = 12): int { @@ -121,8 +100,6 @@ class LayoutColumn extends Item /** * The result is being sent to the editor * via the API in the panel - * - * @return array */ public function toArray(): array { @@ -135,8 +112,6 @@ class LayoutColumn extends Item /** * Returns the width of the column - * - * @return string */ public function width(): string { diff --git a/kirby/src/Cms/LayoutColumns.php b/kirby/src/Cms/LayoutColumns.php index bed504f..195e39e 100644 --- a/kirby/src/Cms/LayoutColumns.php +++ b/kirby/src/Cms/LayoutColumns.php @@ -18,8 +18,6 @@ class LayoutColumns extends Items /** * All registered layout columns methods - * - * @var array */ - public static $methods = []; + public static array $methods = []; } diff --git a/kirby/src/Cms/Layouts.php b/kirby/src/Cms/Layouts.php index 4ddd4c2..1888680 100644 --- a/kirby/src/Cms/Layouts.php +++ b/kirby/src/Cms/Layouts.php @@ -2,7 +2,7 @@ namespace Kirby\Cms; -use Kirby\Data\Data; +use Kirby\Data\Json; use Kirby\Toolkit\Str; use Throwable; @@ -22,17 +22,28 @@ class Layouts extends Items /** * All registered layouts methods - * - * @var array */ - public static $methods = []; + public static array $methods = []; + + public static function factory( + array $items = null, + array $params = [] + ): static { + // convert single layout to layouts array + if ( + isset($items['columns']) === true || + isset($items['id']) === true + ) { + $items = [$items]; + } - public static function factory(array $items = null, array $params = []) - { $first = $items[0] ?? []; // if there are no wrapping layouts for blocks yet … - if (array_key_exists('content', $first) === true || array_key_exists('type', $first) === true) { + if ( + isset($first['content']) === true || + isset($first['type']) === true + ) { $items = [ [ 'id' => Str::uuid(), @@ -52,9 +63,6 @@ class Layouts extends Items /** * Checks if a given block type exists in the layouts collection * @since 3.6.0 - * - * @param string $type - * @return bool */ public function hasBlockType(string $type): bool { @@ -63,15 +71,15 @@ class Layouts extends Items /** * Parse layouts data - * - * @param array|string $input - * @return array */ - public static function parse($input): array + public static function parse(array|string|null $input): array { - if (empty($input) === false && is_array($input) === false) { + if ( + empty($input) === false && + is_array($input) === false + ) { try { - $input = Data::decode($input, 'json'); + $input = Json::decode((string)$input); } catch (Throwable) { return []; } @@ -89,9 +97,8 @@ class Layouts extends Items * @since 3.6.0 * * @param bool $includeHidden Sets whether to include hidden blocks - * @return \Kirby\Cms\Blocks */ - public function toBlocks(bool $includeHidden = false) + public function toBlocks(bool $includeHidden = false): Blocks { $blocks = []; diff --git a/kirby/src/Cms/License.php b/kirby/src/Cms/License.php new file mode 100644 index 0000000..efd2e3d --- /dev/null +++ b/kirby/src/Cms/License.php @@ -0,0 +1,523 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier + * @license https://getkirby.com/license + */ +class License +{ + protected const HISTORY = [ + '3' => '2019-02-05', + '4' => '2023-11-28' + ]; + + protected const SALT = 'kwAHMLyLPBnHEskzH9pPbJsBxQhKXZnX'; + + // cache + protected LicenseStatus $status; + protected LicenseType $type; + + public function __construct( + protected string|null $activation = null, + protected string|null $code = null, + protected string|null $domain = null, + protected string|null $email = null, + protected string|null $order = null, + protected string|null $date = null, + protected string|null $signature = null, + ) { + // normalize the email address + $this->email = $this->email === null ? null : $this->normalizeEmail($this->email); + } + + /** + * Returns the activation date if available + */ + public function activation(string|IntlDateFormatter|null $format = null): int|string|null + { + return $this->activation !== null ? Str::date(strtotime($this->activation), $format) : null; + } + + /** + * Returns the license code if available + */ + public function code(bool $obfuscated = false): string|null + { + if ($this->code !== null && $obfuscated === true) { + return Str::substr($this->code, 0, 10) . str_repeat('X', 22); + } + + return $this->code; + } + + /** + * Content for the license file + */ + public function content(): array + { + return [ + 'activation' => $this->activation, + 'code' => $this->code, + 'date' => $this->date, + 'domain' => $this->domain, + 'email' => $this->email, + 'order' => $this->order, + 'signature' => $this->signature, + ]; + } + + /** + * Returns the purchase date if available + */ + public function date(string|IntlDateFormatter|null $format = null): int|string|null + { + return $this->date !== null ? Str::date(strtotime($this->date), $format) : null; + } + + /** + * Returns the activation domain if available + */ + public function domain(): string|null + { + return $this->domain; + } + + /** + * Returns the activation email if available + */ + public function email(): string|null + { + return $this->email; + } + + /** + * Validates the email address of the license + */ + public function hasValidEmailAddress(): bool + { + return V::email($this->email) === true; + } + + /** + * Hub address + */ + public static function hub(): string + { + return App::instance()->option('hub', 'https://hub.getkirby.com'); + } + + /** + * Checks for all required components of a valid license + */ + public function isComplete(): bool + { + if ( + $this->code !== null && + $this->date !== null && + $this->domain !== null && + $this->email !== null && + $this->order !== null && + $this->signature !== null && + $this->hasValidEmailAddress() === true && + $this->type() !== LicenseType::Invalid + ) { + return true; + } + + return false; + } + + /** + * The license is still valid for the currently + * installed version, but it passed the 3 year period. + */ + public function isInactive(): bool + { + return $this->renewal() < time(); + } + + /** + * Checks for licenses beyond their 3 year period + */ + public function isLegacy(): bool + { + if ($this->type() === LicenseType::Legacy) { + return true; + } + + // without an activation date, the license + // renewal cannot be evaluated and the license + // has to be marked as expired + if ($this->activation === null) { + return true; + } + + // get release date of current major version + $major = Str::before(App::instance()->version(), '.'); + $release = strtotime(static::HISTORY[$major] ?? ''); + + // if there's no matching version in the history + // rather throw an exception to avoid further issues + // @codeCoverageIgnoreStart + if ($release === false) { + throw new InvalidArgumentException('The version for your license could not be found'); + } + // @codeCoverageIgnoreEnd + + // If the renewal date is older than the version launch + // date, the license is expired + return $this->renewal() < $release; + } + + /** + * Runs multiple checks to find out if the license is + * installed and verifiable + */ + public function isMissing(): bool + { + return + $this->isComplete() === false || + $this->isOnCorrectDomain() === false || + $this->isSigned() === false; + } + + /** + * Checks if the license is on the correct domain + */ + public function isOnCorrectDomain(): bool + { + if ($this->domain === null) { + return false; + } + + // compare domains + if ($this->normalizeDomain(App::instance()->system()->indexUrl()) !== $this->normalizeDomain($this->domain)) { + return false; + } + + return true; + } + + /** + * Compares the signature with all ingredients + */ + public function isSigned(): bool + { + if ($this->signature === null) { + return false; + } + + // get the public key + $pubKey = F::read(App::instance()->root('kirby') . '/kirby.pub'); + + // verify the license signature + $data = json_encode($this->signatureData()); + $signature = hex2bin($this->signature); + + return openssl_verify($data, $signature, $pubKey, 'RSA-SHA256') === 1; + } + + /** + * Returns a reliable label for the license type + */ + public function label(): string + { + if ($this->status() === LicenseStatus::Missing) { + return LicenseType::Invalid->label(); + } + + return $this->type()->label(); + } + + /** + * Prepares the email address to be make sure it + * does not have trailing spaces and is lowercase. + */ + protected function normalizeEmail(string $email): string + { + return Str::lower(trim($email)); + } + + /** + * Prepares the domain to be comparable + */ + protected function normalizeDomain(string $domain): string + { + // remove common "testing" subdomains as well as www. + // to ensure that installations of the same site have + // the same license URL; only for installations at /, + // subdirectory installations are difficult to normalize + if (Str::contains($domain, '/') === false) { + if (Str::startsWith($domain, 'www.')) { + return substr($domain, 4); + } + + if (Str::startsWith($domain, 'dev.')) { + return substr($domain, 4); + } + + if (Str::startsWith($domain, 'test.')) { + return substr($domain, 5); + } + + if (Str::startsWith($domain, 'staging.')) { + return substr($domain, 8); + } + } + + return $domain; + } + + /** + * Returns the order id if available + */ + public function order(): string|null + { + return $this->order; + } + + /** + * Support the old license file dataset + * from older licenses + */ + public static function polyfill(array $license): array + { + return [ + 'activation' => $license['activation'] ?? null, + 'code' => $license['code'] ?? $license['license'] ?? null, + 'date' => $license['date'] ?? null, + 'domain' => $license['domain'] ?? null, + 'email' => $license['email'] ?? null, + 'order' => $license['order'] ?? null, + 'signature' => $license['signature'] ?? null, + ]; + } + + /** + * Reads the license file in the config folder + * and creates a new license instance for it. + */ + public static function read(): static + { + try { + $license = Json::read(App::instance()->root('license')); + } catch (Throwable) { + return new static(); + } + + return new static(...static::polyfill($license)); + } + + /** + * Sends a request to the hub to register the license + */ + public function register(): static + { + if ($this->type() === LicenseType::Invalid) { + throw new InvalidArgumentException(['key' => 'license.format']); + } + + if ($this->hasValidEmailAddress() === false) { + throw new InvalidArgumentException(['key' => 'license.email']); + } + + if ($this->domain === null) { + throw new InvalidArgumentException(['key' => 'license.domain']); + } + + // @codeCoverageIgnoreStart + $response = $this->request('register', [ + 'license' => $this->code, + 'email' => $this->email, + 'domain' => $this->domain + ]); + + return $this->update($response); + // @codeCoverageIgnoreEnd + } + + /** + * Returns the renewal date + */ + public function renewal(string|IntlDateFormatter|null $format = null): int|string|null + { + if ($this->activation === null) { + return null; + } + + $time = strtotime('+3 years', $this->activation()); + return Str::date($time, $format); + } + + /** + * Sends a hub request + */ + public function request(string $path, array $data): array + { + // @codeCoverageIgnoreStart + $response = Remote::get(static::hub() . '/' . $path, [ + 'data' => $data + ]); + + // handle request errors + if ($response->code() !== 200) { + $message = $response->json()['message'] ?? 'The request failed'; + + throw new LogicException($message, $response->code()); + } + + return $response->json(); + // @codeCoverageIgnoreEnd + } + + /** + * Saves the license in the config folder + */ + public function save(): bool + { + if ($this->status() !== LicenseStatus::Active) { + throw new InvalidArgumentException([ + 'key' => 'license.verification' + ]); + } + + // where to store the license file + $file = App::instance()->root('license'); + + // save the license information + return Json::write($file, $this->content()); + } + + /** + * Returns the signature if available + */ + public function signature(): string|null + { + return $this->signature; + } + + /** + * Creates the signature data array to compare + * with the signature in ::isSigned + */ + public function signatureData(): array + { + if ($this->type() === LicenseType::Legacy) { + return [ + 'license' => $this->code, + 'order' => $this->order, + 'email' => hash('sha256', $this->email . static::SALT), + 'domain' => $this->domain, + 'date' => $this->date, + ]; + } + + return [ + 'activation' => $this->activation, + 'code' => $this->code, + 'date' => $this->date, + 'domain' => $this->domain, + 'email' => hash('sha256', $this->email . static::SALT), + 'order' => $this->order, + ]; + } + + /** + * Returns the license status as string + * This is used to build the proper UI elements + * for the license activation + */ + public function status(): LicenseStatus + { + return $this->status ??= match (true) { + $this->isMissing() === true => LicenseStatus::Missing, + $this->isLegacy() === true => LicenseStatus::Legacy, + $this->isInactive() === true => LicenseStatus::Inactive, + default => LicenseStatus::Active + }; + } + + /** + * Detects the license type if the license key is available + */ + public function type(): LicenseType + { + return $this->type ??= LicenseType::detect($this->code); + } + + /** + * Updates the license file + */ + public function update(array $data): static + { + // decode the response + $data = static::polyfill($data); + + $this->activation = $data['activation']; + $this->code = $data['code']; + $this->date = $data['date']; + $this->order = $data['order']; + $this->signature = $data['signature']; + + // clear the caches + unset($this->status, $this->type); + + // save the new state of the license + $this->save(); + + return $this; + } + + /** + * Sends an upgrade request to the hub in order + * to either redirect to the upgrade form or + * sync the new license state + * + * @codeCoverageIgnore + */ + public function upgrade(): array + { + $response = $this->request('upgrade', [ + 'domain' => $this->domain, + 'email' => $this->email, + 'license' => $this->code, + ]); + + // the license still needs an upgrade + if (empty($response['url']) === false) { + // validate the redirect URL + if (Str::startsWith($response['url'], static::hub()) === false) { + throw new Exception('We couldn’t redirect you to the Hub'); + } + + return [ + 'status' => 'upgrade', + 'url' => $response['url'] + ]; + } + + // the license has already been upgraded + // and can now be replaced + $this->update($response); + + return [ + 'status' => 'complete', + ]; + } +} diff --git a/kirby/src/Cms/LicenseStatus.php b/kirby/src/Cms/LicenseStatus.php new file mode 100644 index 0000000..51ae5da --- /dev/null +++ b/kirby/src/Cms/LicenseStatus.php @@ -0,0 +1,127 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier + * @license https://getkirby.com/license + * + * @codeCoverageIgnore + */ +enum LicenseStatus: string +{ + /** + * The license is valid and active + */ + case Active = 'active'; + + /** + * Only used for the demo instance + */ + case Demo = 'demo'; + + /** + * The included updates period of + * the license is over. + */ + case Inactive = 'inactive'; + + /** + * The installation has an old + * license (v1, v2, v3) + */ + case Legacy = 'legacy'; + + /** + * The installation has no license or + * the license cannot be validated + */ + case Missing = 'missing'; + + /** + * Returns the dialog according to the status + */ + public function dialog(): string + { + return match ($this) { + static::Missing => 'registration', + default => 'license' + }; + } + + /** + * Returns the icon according to the status. + * The icon is used for the system view and + * in the license dialog. + */ + public function icon(): string + { + return match ($this) { + static::Missing => 'key', + static::Legacy => 'alert', + static::Inactive => 'clock', + static::Active => 'check', + static::Demo => 'preview', + }; + } + + /** + * The info text is shown in the license dialog + * in the status row. + */ + public function info(string|null $end = null): string + { + return I18n::template('license.status.' . $this->value . '.info', ['date' => $end]); + } + + /** + * Label for the system view + */ + public function label(): string + { + return I18n::translate('license.status.' . $this->value . '.label'); + } + + /** + * Checks if the license can be renewed + * The license dialog will show the renew + * button in this case and redirect to the hub + */ + public function renewable(): bool + { + return match ($this) { + static::Demo => false, + static::Active => false, + default => true + }; + } + + /** + * Returns the theme according to the status + * The theme is used for the label in the system + * view and the status icon in the license dialog. + */ + public function theme(): string + { + return match ($this) { + static::Missing => 'love', + static::Legacy => 'negative', + static::Inactive => 'notice', + static::Active => 'positive', + static::Demo => 'notice', + }; + } + + /** + * Returns the status as string value + */ + public function value(): string + { + return $this->value; + } +} diff --git a/kirby/src/Cms/LicenseType.php b/kirby/src/Cms/LicenseType.php new file mode 100644 index 0000000..6a9fea7 --- /dev/null +++ b/kirby/src/Cms/LicenseType.php @@ -0,0 +1,111 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier + * @license https://getkirby.com/license + * + * @codeCoverageIgnore + */ +enum LicenseType: string +{ + /** + * New basic licenses + */ + case Basic = 'basic'; + + /** + * New enterprise licenses + */ + case Enterprise = 'enterprise'; + + /** + * Invalid license codes + */ + case Invalid = 'invalid'; + + /** + * Old Kirby 3 licenses + */ + case Legacy = 'legacy'; + + /** + * Detects the correct LicenseType based on the code + */ + public static function detect(string|null $code): static + { + return match (true) { + static::Basic->isValidCode($code) => static::Basic, + static::Enterprise->isValidCode($code) => static::Enterprise, + static::Legacy->isValidCode($code) => static::Legacy, + default => static::Invalid + }; + } + + /** + * Checks for a valid license code + * by prefix and length. This is just a + * rough validation. + */ + public function isValidCode(string|null $code): bool + { + return + $code !== null && + Str::length($code) === $this->length() && + Str::startsWith($code, $this->prefix()) === true; + } + + /** + * The expected lengths of the license code + */ + public function length(): int + { + return match ($this) { + static::Basic => 38, + static::Enterprise => 38, + static::Legacy => 39, + static::Invalid => 0, + }; + } + + /** + * A human-readable license type label + */ + public function label(): string + { + return match ($this) { + static::Basic => 'Kirby Basic', + static::Enterprise => 'Kirby Enterprise', + static::Legacy => 'Kirby 3', + static::Invalid => I18n::translate('license.unregistered.label'), + }; + } + + /** + * The expected prefix for the license code + */ + public function prefix(): string|null + { + return match ($this) { + static::Basic => 'K-BAS-', + static::Enterprise => 'K-ENT-', + static::Legacy => 'K3-PRO-', + static::Invalid => null, + }; + } + + /** + * Returns the enum value + */ + public function value(): string + { + return $this->value; + } +} diff --git a/kirby/src/Cms/Loader.php b/kirby/src/Cms/Loader.php index d0a5488..6e71878 100644 --- a/kirby/src/Cms/Loader.php +++ b/kirby/src/Cms/Loader.php @@ -50,9 +50,6 @@ class Loader /** * Loads the area definition - * - * @param string $name - * @return array|null */ public function area(string $name): array|null { @@ -62,15 +59,14 @@ class Loader /** * Loads all areas and makes sure that plugins * are injected properly - * - * @return array */ public function areas(): array { $areas = []; $extensions = $this->withPlugins === true ? $this->kirby->extensions('areas') : []; - // load core areas and extend them with elements from plugins if they exist + // load core areas and extend them with elements + // from plugins if they exist foreach ($this->kirby->core()->areas() as $id => $area) { $area = $this->resolveArea($area); @@ -98,9 +94,6 @@ class Loader /** * Loads a core component closure - * - * @param string $name - * @return \Closure|null */ public function component(string $name): Closure|null { @@ -109,8 +102,6 @@ class Loader /** * Loads all core component closures - * - * @return array */ public function components(): array { @@ -119,21 +110,14 @@ class Loader /** * Loads a particular extension - * - * @param string $type - * @param string $name - * @return mixed */ - public function extension(string $type, string $name) + public function extension(string $type, string $name): mixed { return $this->extensions($type)[$name] ?? null; } /** * Loads all defined extensions - * - * @param string $type - * @return array */ public function extensions(string $type): array { @@ -152,11 +136,8 @@ class Loader * * 3.) closures will be called and the Kirby instance will be * passed as first argument - * - * @param mixed $item - * @return mixed */ - public function resolve($item) + public function resolve(mixed $item): mixed { if (is_string($item) === true) { $item = match (F::extension($item)) { @@ -175,9 +156,6 @@ class Loader /** * Calls `static::resolve()` on all items * in the given array - * - * @param array $items - * @return array */ public function resolveAll(array $items): array { @@ -193,11 +171,8 @@ class Loader /** * Areas need a bit of special treatment * when they are being loaded - * - * @param string|array|Closure $area - * @return array */ - public function resolveArea($area): array + public function resolveArea(string|array|Closure $area): array { $area = $this->resolve($area); $dropdowns = $area['dropdowns'] ?? []; @@ -217,9 +192,6 @@ class Loader /** * Loads a particular section definition - * - * @param string $name - * @return array|null */ public function section(string $name): array|null { @@ -228,8 +200,6 @@ class Loader /** * Loads all section defintions - * - * @return array */ public function sections(): array { @@ -239,8 +209,6 @@ class Loader /** * Returns the status flag, which shows * if plugins are loaded as well. - * - * @return bool */ public function withPlugins(): bool { diff --git a/kirby/src/Cms/Media.php b/kirby/src/Cms/Media.php index 777ab9a..ec44de9 100644 --- a/kirby/src/Cms/Media.php +++ b/kirby/src/Cms/Media.php @@ -3,6 +3,8 @@ namespace Kirby\Cms; use Kirby\Data\Data; +use Kirby\Exception\InvalidArgumentException; +use Kirby\Exception\NotFoundException; use Kirby\Filesystem\Dir; use Kirby\Filesystem\F; use Kirby\Toolkit\Str; @@ -23,14 +25,12 @@ class Media /** * Tries to find a file by model and filename * and to copy it to the media folder. - * - * @param \Kirby\Cms\Model|null $model - * @param string $hash - * @param string $filename - * @return \Kirby\Cms\Response|false */ - public static function link(Model $model = null, string $hash, string $filename) - { + public static function link( + Page|Site|User $model = null, + string $hash, + string $filename + ): Response|false { if ($model === null) { return false; } @@ -62,10 +62,6 @@ class Media /** * Copy the file to the final media folder location - * - * @param \Kirby\Cms\File $file - * @param string $dest - * @return bool */ public static function publish(File $file, string $dest): bool { @@ -87,14 +83,12 @@ class Media * Tries to find a job file for the * given filename and then calls the thumb * component to create a thumbnail accordingly - * - * @param \Kirby\Cms\Model|string $model - * @param string $hash - * @param string $filename - * @return \Kirby\Cms\Response|false */ - public static function thumb($model, string $hash, string $filename) - { + public static function thumb( + File|Page|Site|User|string $model, + string $hash, + string $filename + ): Response|false { $kirby = App::instance(); $root = match (true) { @@ -109,15 +103,23 @@ class Media => $model->mediaRoot() . '/' . $hash }; + $thumb = $root . '/' . $filename; + $job = $root . '/.jobs/' . $filename . '.json'; + try { - $thumb = $root . '/' . $filename; - $job = $root . '/.jobs/' . $filename . '.json'; $options = Data::read($job); + } catch (Throwable) { + // send a customized error message to make clearer what happened here + throw new NotFoundException('The thumbnail configuration could not be found'); + } - if (empty($options) === true) { - return false; - } + if (empty($options['filename']) === true) { + throw new InvalidArgumentException('Incomplete thumbnail configuration'); + } + try { + // find the correct source file depending on the model + // this adds support for custom assets $source = match (true) { is_string($model) === true => $kirby->root('index') . '/' . $model . '/' . $options['filename'], @@ -125,30 +127,30 @@ class Media => $model->file($options['filename'])->root() }; - try { - $kirby->thumb($source, $thumb, $options); - F::remove($job); - return Response::file($thumb); - } catch (Throwable) { - F::remove($thumb); - return Response::file($source); - } - } catch (Throwable) { - return false; + // generate the thumbnail and save it in the media folder + $kirby->thumb($source, $thumb, $options); + + // remove the job file once the thumbnail has been created + F::remove($job); + + // read the file and send it to the browser + return Response::file($thumb); + } catch (Throwable $e) { + // remove potentially broken thumbnails + F::remove($thumb); + throw $e; } } /** * Deletes all versions of the given file * within the parent directory - * - * @param string $directory - * @param \Kirby\Cms\File $file - * @param string|null $ignore - * @return bool */ - public static function unpublish(string $directory, File $file, string $ignore = null): bool - { + public static function unpublish( + string $directory, + File $file, + string|null $ignore = null + ): bool { if (is_dir($directory) === false) { return true; } diff --git a/kirby/src/Cms/Model.php b/kirby/src/Cms/Model.php index 7acf7c6..b7010d3 100644 --- a/kirby/src/Cms/Model.php +++ b/kirby/src/Cms/Model.php @@ -5,7 +5,7 @@ namespace Kirby\Cms; use Kirby\Toolkit\Properties; /** - * Foundation for Page, Site, File and User models. + * @deprecated 4.0.0 will be removed in Kirby 5.0 * * @package Kirby Cms * @author Bastian Allgeier diff --git a/kirby/src/Cms/ModelPermissions.php b/kirby/src/Cms/ModelPermissions.php index 916c0d8..0b8cb56 100644 --- a/kirby/src/Cms/ModelPermissions.php +++ b/kirby/src/Cms/ModelPermissions.php @@ -15,28 +15,13 @@ use Kirby\Toolkit\A; */ abstract class ModelPermissions { - protected $category; - protected $model; - protected $options; - protected $permissions; - protected $user; + protected string $category; + protected ModelWithContent $model; + protected array $options; + protected Permissions $permissions; + protected User $user; - /** - * @param string $method - * @param array $arguments - * @return bool - */ - public function __call(string $method, array $arguments = []): bool - { - return $this->can($method); - } - - /** - * ModelPermissions constructor - * - * @param \Kirby\Cms\Model $model - */ - public function __construct(Model $model) + public function __construct(ModelWithContent $model) { $this->model = $model; $this->options = $model->blueprint()->options(); @@ -44,29 +29,34 @@ abstract class ModelPermissions $this->permissions = $this->user->role()->permissions(); } + public function __call(string $method, array $arguments = []): bool + { + return $this->can($method); + } + /** * Improved `var_dump` output - * - * @return array + * @codeCoverageIgnore */ public function __debugInfo(): array { return $this->toArray(); } - /** - * @param string $action - * @return bool - */ public function can(string $action): bool { + $user = $this->user->id(); $role = $this->user->role()->id(); + // users with the `nobody` role can do nothing + // that needs a permission check if ($role === 'nobody') { return false; } - // check for a custom overall can method + // check for a custom `can` method + // which would take priority over any other + // role-based permission rules if ( method_exists($this, 'can' . $action) === true && $this->{'can' . $action}() === false @@ -74,6 +64,11 @@ abstract class ModelPermissions return false; } + // the almighty `kirby` user can do anything + if ($user === 'kirby' && $role === 'admin') { + return true; + } + // evaluate the blueprint options block if (isset($this->options[$action]) === true) { $options = $this->options[$action]; @@ -90,25 +85,24 @@ abstract class ModelPermissions is_array($options) === true && A::isAssociative($options) === true ) { - return $options[$role] ?? $options['*'] ?? false; + if (isset($options[$role]) === true) { + return $options[$role]; + } + + if (isset($options['*']) === true) { + return $options['*']; + } } } return $this->permissions->for($this->category, $action); } - /** - * @param string $action - * @return bool - */ public function cannot(string $action): bool { return $this->can($action) === false; } - /** - * @return array - */ public function toArray(): array { $array = []; diff --git a/kirby/src/Cms/ModelWithContent.php b/kirby/src/Cms/ModelWithContent.php index 42ad403..6737393 100644 --- a/kirby/src/Cms/ModelWithContent.php +++ b/kirby/src/Cms/ModelWithContent.php @@ -3,9 +3,14 @@ namespace Kirby\Cms; use Closure; -use Kirby\Data\Data; +use Kirby\Content\Content; +use Kirby\Content\ContentStorage; +use Kirby\Content\ContentTranslation; +use Kirby\Content\PlainTextContentStorageHandler; use Kirby\Exception\InvalidArgumentException; +use Kirby\Exception\NotFoundException; use Kirby\Form\Form; +use Kirby\Panel\Model; use Kirby\Toolkit\Str; use Kirby\Uuid\Identifiable; use Kirby\Uuid\Uuid; @@ -21,70 +26,106 @@ use Throwable; * @copyright Bastian Allgeier * @license https://getkirby.com/license */ -abstract class ModelWithContent extends Model implements Identifiable +abstract class ModelWithContent implements Identifiable { /** - * The content - * - * @var \Kirby\Cms\Content + * Each model must define a CLASS_ALIAS + * which will be used in template queries. + * The CLASS_ALIAS is a short human-readable + * version of the class name, i.e. page. */ - public $content; + public const CLASS_ALIAS = null; /** - * @var \Kirby\Cms\Translations + * Cached array of valid blueprints + * that could be used for the model */ - public $translations; + public array|null $blueprints = null; + + public Content|null $content; + public static App $kirby; + protected Site|null $site; + protected ContentStorage $storage; + public Collection|null $translations; + + /** + * Store values used to initilaize object + */ + protected array $propertyData = []; + + public function __construct(array $props = []) + { + $this->site = $props['site'] ?? null; + + $this->setContent($props['content'] ?? null); + $this->setTranslations($props['translations'] ?? null); + + $this->propertyData = $props; + } /** * Returns the blueprint of the model - * - * @return \Kirby\Cms\Blueprint */ - abstract public function blueprint(); + abstract public function blueprint(): Blueprint; /** * Returns an array with all blueprints that are available - * - * @param string|null $inSection - * @return array */ public function blueprints(string $inSection = null): array { - $blueprints = []; - $blueprint = $this->blueprint(); - $sections = $inSection !== null ? [$blueprint->section($inSection)] : $blueprint->sections(); + // helper function + $toBlueprints = function (array $sections): array { + $blueprints = []; - foreach ($sections as $section) { - if ($section === null) { - continue; + foreach ($sections as $section) { + if ($section === null) { + continue; + } + + foreach ((array)$section->blueprints() as $blueprint) { + $blueprints[$blueprint['name']] = $blueprint; + } } - foreach ((array)$section->blueprints() as $blueprint) { - $blueprints[$blueprint['name']] = $blueprint; - } + return array_values($blueprints); + }; + + $blueprint = $this->blueprint(); + + // no caching for when collecting for specific section + if ($inSection !== null) { + return $toBlueprints([$blueprint->section($inSection)]); } - return array_values($blueprints); + return $this->blueprints ??= $toBlueprints($blueprint->sections()); + } + + /** + * Creates a new instance with the same + * initial properties + * + * @todo eventually refactor without need of propertyData + */ + public function clone(array $props = []): static + { + return new static(array_replace_recursive($this->propertyData, $props)); } /** * Executes any given model action - * - * @param string $action - * @param array $arguments - * @param \Closure $callback - * @return mixed */ - abstract protected function commit(string $action, array $arguments, Closure $callback); + abstract protected function commit( + string $action, + array $arguments, + Closure $callback + ): mixed; /** * Returns the content * - * @param string|null $languageCode - * @return \Kirby\Cms\Content * @throws \Kirby\Exception\InvalidArgumentException If the language for the given code does not exist */ - public function content(string $languageCode = null) + public function content(string|null $languageCode = null): Content { // single language support if ($this->kirby()->multilang() === false) { @@ -124,72 +165,55 @@ abstract class ModelWithContent extends Model implements Identifiable } /** - * Returns the absolute path to the content file - * + * Returns the absolute path to the content file; + * NOTE: only supports the published content file + * (use `$model->storage()->contentFile()` for other versions) * @internal - * @param string|null $languageCode - * @param bool $force - * @return string + * @deprecated 4.0.0 + * @todo Remove in v5 + * @codeCoverageIgnore + * * @throws \Kirby\Exception\InvalidArgumentException If the language for the given code does not exist */ - public function contentFile(string $languageCode = null, bool $force = false): string - { - $extension = $this->contentFileExtension(); - $directory = $this->contentFileDirectory(); - $filename = $this->contentFileName(); + public function contentFile( + string $languageCode = null, + bool $force = false + ): string { + Helpers::deprecated('The internal $model->contentFile() method has been deprecated. You can use $model->storage()->contentFile() instead, however please note that this method is also internal and may be removed in the future.', 'model-content-file'); - // overwrite the language code - if ($force === true) { - if (empty($languageCode) === false) { - return $directory . '/' . $filename . '.' . $languageCode . '.' . $extension; - } - - return $directory . '/' . $filename . '.' . $extension; - } - - // add and validate the language code in multi language mode - if ($this->kirby()->multilang() === true) { - if ($language = $this->kirby()->languageCode($languageCode)) { - return $directory . '/' . $filename . '.' . $language . '.' . $extension; - } - - throw new InvalidArgumentException('Invalid language: ' . $languageCode); - } - - return $directory . '/' . $filename . '.' . $extension; + return $this->storage()->contentFile( + $this->storage()->defaultVersion(), + $languageCode, + $force + ); } /** - * Returns an array with all content files - * - * @return array + * Returns an array with all content files; + * NOTE: only supports the published content file + * (use `$model->storage()->contentFiles()` for other versions) + * @deprecated 4.0.0 + * @todo Remove in v5 + * @codeCoverageIgnore */ public function contentFiles(): array { - if ($this->kirby()->multilang() === true) { - $files = []; - foreach ($this->kirby()->languages()->codes() as $code) { - $files[] = $this->contentFile($code); - } - return $files; - } + Helpers::deprecated('The internal $model->contentFiles() method has been deprecated. You can use $model->storage()->contentFiles() instead, however please note that this method is also internal and may be removed in the future.', 'model-content-file'); - return [ - $this->contentFile() - ]; + return $this->storage()->contentFiles( + $this->storage()->defaultVersion() + ); } /** * Prepares the content that should be written * to the text file - * * @internal - * @param array $data - * @param string|null $languageCode - * @return array */ - public function contentFileData(array $data, string $languageCode = null): array - { + public function contentFileData( + array $data, + string $languageCode = null + ): array { return $data; } @@ -197,44 +221,102 @@ abstract class ModelWithContent extends Model implements Identifiable * Returns the absolute path to the * folder in which the content file is * located - * * @internal - * @return string|null + * @deprecated 4.0.0 + * @todo Remove in v5 + * @codeCoverageIgnore */ public function contentFileDirectory(): string|null { + Helpers::deprecated('The internal $model->contentFileDirectory() method has been deprecated. Please let us know via a GitHub issue if you need this method and tell us your use case.', 'model-content-file'); return $this->root(); } /** * Returns the extension of the content file - * * @internal - * @return string + * @deprecated 4.0.0 + * @todo Remove in v5 + * @codeCoverageIgnore */ public function contentFileExtension(): string { + Helpers::deprecated('The internal $model->contentFileName() method has been deprecated. Please let us know via a GitHub issue if you need this method and tell us your use case.', 'model-content-file'); return $this->kirby()->contentExtension(); } /** * Needs to be declared by the final model - * * @internal - * @return string + * @deprecated 4.0.0 + * @todo Remove in v5 + * @codeCoverageIgnore */ abstract public function contentFileName(): string; /** - * Decrement a given field value - * - * @param string $field - * @param int $by - * @param int $min - * @return static + * Converts model to new blueprint + * incl. its content for all translations */ - public function decrement(string $field, int $by = 1, int $min = 0) + protected function convertTo(string $blueprint): static { + // first close object with new blueprint as template + $new = $this->clone(['template' => $blueprint]); + + // temporary compatibility change (TODO: also convert changes) + $identifier = $this->storage()->defaultVersion(); + + // for multilang, we go through all translations and + // covnert the content for each of them, remove the content file + // to rewrite it with converted content afterwards + if ($this->kirby()->multilang() === true) { + $translations = []; + + foreach ($this->kirby()->languages()->codes() as $code) { + if ($this->translation($code)?->exists() === true) { + $content = $this->content($code)->convertTo($blueprint); + + // delete the old text file + $this->storage()->delete( + $identifier, + $code + ); + + // save to re-create the translation content file + // with the converted/updated content + $new->save($content, $code); + } + + $translations[] = [ + 'code' => $code, + 'content' => $content ?? null + ]; + } + + // cloning the object with the new translations content ensures + // that `propertyData` prop does not hold any old translations + // content that could surface on subsequent cloning + return $new->clone(['translations' => $translations]); + } + + // for single language setups, we do the same, + // just once for the main content + $content = $this->content()->convertTo($blueprint); + + // delete the old text file + $this->storage()->delete($identifier, 'default'); + + return $new->save($content); + } + + /** + * Decrement a given field value + */ + public function decrement( + string $field, + int $by = 1, + int $min = 0 + ): static { $value = (int)$this->content()->get($field)->value() - $by; if ($value < $min) { @@ -246,8 +328,6 @@ abstract class ModelWithContent extends Model implements Identifiable /** * Returns all content validation errors - * - * @return array */ public function errors(): array { @@ -261,15 +341,38 @@ abstract class ModelWithContent extends Model implements Identifiable } /** - * Increment a given field value - * - * @param string $field - * @param int $by - * @param int|null $max - * @return static + * Creates a clone and fetches all + * lazy-loaded getters to get a full copy */ - public function increment(string $field, int $by = 1, int $max = null) + public function hardcopy(): static { + $clone = $this->clone(); + + foreach (get_object_vars($clone) as $name => $default) { + if (method_exists($clone, $name) === true) { + $clone->$name(); + } + } + + return $clone; + } + + /** + * Each model must return a unique id + */ + public function id(): string|null + { + return null; + } + + /** + * Increment a given field value + */ + public function increment( + string $field, + int $by = 1, + int $max = null + ): static { $value = (int)$this->content()->get($field)->value() + $by; if ($max && $value > $max) { @@ -281,8 +384,6 @@ abstract class ModelWithContent extends Model implements Identifiable /** * Checks if the model is locked for the current user - * - * @return bool */ public function isLocked(): bool { @@ -292,12 +393,18 @@ abstract class ModelWithContent extends Model implements Identifiable /** * Checks if the data has any errors - * - * @return bool */ public function isValid(): bool { - return Form::for($this)->hasErrors() === false; + return Form::for($this)->isValid() === true; + } + + /** + * Returns the parent Kirby instance + */ + public function kirby(): App + { + return static::$kirby ??= App::instance(); } /** @@ -305,12 +412,14 @@ abstract class ModelWithContent extends Model implements Identifiable * * Only if a content directory exists, * virtual pages will need to overwrite this method - * - * @return \Kirby\Cms\ContentLock|null */ - public function lock() + public function lock(): ContentLock|null { - $dir = $this->contentFileDirectory(); + $dir = $this->root(); + + if ($this::CLASS_ALIAS === 'file') { + $dir = dirname($dir); + } if ( $this->kirby()->option('content.locking', true) && @@ -319,33 +428,43 @@ abstract class ModelWithContent extends Model implements Identifiable ) { return new ContentLock($this); } + + return null; } /** * Returns the panel info of the model * @since 3.6.0 - * - * @return \Kirby\Panel\Model */ - abstract public function panel(); + abstract public function panel(): Model; /** * Must return the permissions object for the model - * - * @return \Kirby\Cms\ModelPermissions */ - abstract public function permissions(); + abstract public function permissions(): ModelPermissions; + + /** + * Clean internal caches + * + * @return $this + */ + public function purge(): static + { + $this->blueprints = null; + $this->content = null; + $this->translations = null; + + return $this; + } /** * Creates a string query, starting from the model - * * @internal - * @param string|null $query - * @param string|null $expect - * @return mixed */ - public function query(string $query = null, string $expect = null) - { + public function query( + string $query = null, + string $expect = null + ): mixed { if ($query === null) { return null; } @@ -370,43 +489,37 @@ abstract class ModelWithContent extends Model implements Identifiable /** * Read the content from the content file - * * @internal - * @param string|null $languageCode - * @return array */ public function readContent(string $languageCode = null): array { - $file = $this->contentFile($languageCode); - - // only if the content file really does not exist, it's ok - // to return empty content. Otherwise this could lead to - // content loss in case of file reading issues - if (file_exists($file) === false) { + try { + return $this->storage()->read( + $this->storage()->defaultVersion(), + $languageCode + ); + } catch (NotFoundException) { + // only if the content file really does not exist, it's ok + // to return empty content. Otherwise this could lead to + // content loss in case of file reading issues return []; } - - return Data::read($file); } /** * Returns the absolute path to the model - * - * @return string|null */ abstract public function root(): string|null; /** * Stores the content on disk - * * @internal - * @param array|null $data - * @param string|null $languageCode - * @param bool $overwrite - * @return static */ - public function save(array $data = null, string $languageCode = null, bool $overwrite = false) - { + public function save( + array|null $data = null, + string|null $languageCode = null, + bool $overwrite = false + ): static { if ($this->kirby()->multilang() === true) { return $this->saveTranslation($data, $languageCode, $overwrite); } @@ -416,13 +529,11 @@ abstract class ModelWithContent extends Model implements Identifiable /** * Save the single language content - * - * @param array|null $data - * @param bool $overwrite - * @return static */ - protected function saveContent(array $data = null, bool $overwrite = false) - { + protected function saveContent( + array $data = null, + bool $overwrite = false + ): static { // create a clone to avoid modifying the original $clone = $this->clone(); @@ -438,14 +549,13 @@ abstract class ModelWithContent extends Model implements Identifiable /** * Save a translation * - * @param array|null $data - * @param string|null $languageCode - * @param bool $overwrite - * @return static * @throws \Kirby\Exception\InvalidArgumentException If the language for the given code does not exist */ - protected function saveTranslation(array $data = null, string $languageCode = null, bool $overwrite = false) - { + protected function saveTranslation( + array $data = null, + string $languageCode = null, + bool $overwrite = false + ): static { // create a clone to not touch the original $clone = $this->clone(); @@ -491,10 +601,9 @@ abstract class ModelWithContent extends Model implements Identifiable /** * Sets the Content object * - * @param array|null $content * @return $this */ - protected function setContent(array $content = null) + protected function setContent(array $content = null): static { if ($content !== null) { $content = new Content($content, $this); @@ -507,10 +616,9 @@ abstract class ModelWithContent extends Model implements Identifiable /** * Create the translations collection from an array * - * @param array|null $translations * @return $this */ - protected function setTranslations(array $translations = null) + protected function setTranslations(array $translations = null): static { if ($translations !== null) { $this->translations = new Collection(); @@ -520,23 +628,57 @@ abstract class ModelWithContent extends Model implements Identifiable $translation = new ContentTranslation($props); $this->translations->data[$translation->code()] = $translation; } + } else { + $this->translations = null; } return $this; } + /** + * Returns the parent Site instance + */ + public function site(): Site + { + return $this->site ??= $this->kirby()->site(); + } + + /** + * Returns the content storage handler + * @internal + */ + public function storage(): ContentStorage + { + return $this->storage ??= new ContentStorage( + model: $this, + handler: PlainTextContentStorageHandler::class + ); + } + + /** + * Convert the model to a simple array + */ + public function toArray(): array + { + return [ + 'content' => $this->content()->toArray(), + 'translations' => $this->translations()->toArray() + ]; + } + /** * String template builder with automatic HTML escaping * @since 3.6.0 * * @param string|null $template Template string or `null` to use the model ID - * @param array $data * @param string|null $fallback Fallback for tokens in the template that cannot be replaced * (`null` to keep the original token) - * @return string */ - public function toSafeString(string $template = null, array $data = [], string|null $fallback = ''): string - { + public function toSafeString( + string $template = null, + array $data = [], + string|null $fallback = '' + ): string { return $this->toString($template, $data, $fallback, 'safeTemplate'); } @@ -544,14 +686,16 @@ abstract class ModelWithContent extends Model implements Identifiable * String template builder * * @param string|null $template Template string or `null` to use the model ID - * @param array $data * @param string|null $fallback Fallback for tokens in the template that cannot be replaced * (`null` to keep the original token) * @param string $handler For internal use - * @return string */ - public function toString(string $template = null, array $data = [], string|null $fallback = '', string $handler = 'template'): string - { + public function toString( + string $template = null, + array $data = [], + string|null $fallback = '', + string $handler = 'template' + ): string { if ($template === null) { return $this->id() ?? ''; } @@ -570,15 +714,22 @@ abstract class ModelWithContent extends Model implements Identifiable return $result; } + /** + * Makes it possible to convert the entire model + * to a string. Mostly useful for debugging + */ + public function __toString(): string + { + return $this->id(); + } + /** * Returns a single translation by language code * If no code is specified the current translation is returned - * - * @param string|null $languageCode - * @return \Kirby\Cms\ContentTranslation|null */ - public function translation(string $languageCode = null) - { + public function translation( + string $languageCode = null + ): ContentTranslation|null { if ($language = $this->kirby()->language($languageCode)) { return $this->translations()->find($language->code()); } @@ -588,10 +739,8 @@ abstract class ModelWithContent extends Model implements Identifiable /** * Returns the translations collection - * - * @return \Kirby\Cms\Collection */ - public function translations() + public function translations(): Collection { if ($this->translations !== null) { return $this->translations; @@ -614,14 +763,13 @@ abstract class ModelWithContent extends Model implements Identifiable /** * Updates the model data * - * @param array|null $input - * @param string|null $languageCode - * @param bool $validate - * @return static * @throws \Kirby\Exception\InvalidArgumentException If the input array contains invalid values */ - public function update(array $input = null, string $languageCode = null, bool $validate = false) - { + public function update( + array $input = null, + string $languageCode = null, + bool $validate = false + ): static { $form = Form::for($this, [ 'ignoreDisabled' => $validate === false, 'input' => $input, @@ -654,17 +802,21 @@ abstract class ModelWithContent extends Model implements Identifiable /** * Low level data writer method * to store the given data on disk or anywhere else - * * @internal - * @param array $data - * @param string|null $languageCode - * @return bool */ public function writeContent(array $data, string $languageCode = null): bool { - return Data::write( - $this->contentFile($languageCode), - $this->contentFileData($data, $languageCode) - ); + $data = $this->contentFileData($data, $languageCode); + $id = $this->storage()->defaultVersion(); + + try { + // we can only update if the version already exists + $this->storage()->update($id, $languageCode, $data); + } catch (NotFoundException) { + // otherwise create a new version + $this->storage()->create($id, $languageCode, $data); + } + + return true; } } diff --git a/kirby/src/Cms/Nest.php b/kirby/src/Cms/Nest.php index cde1b29..0f8521d 100644 --- a/kirby/src/Cms/Nest.php +++ b/kirby/src/Cms/Nest.php @@ -2,6 +2,8 @@ namespace Kirby\Cms; +use Kirby\Content\Field; + /** * The Nest class converts any array type * into a Kirby style collection/object. This diff --git a/kirby/src/Cms/NestObject.php b/kirby/src/Cms/NestObject.php index 2dc9176..2466023 100644 --- a/kirby/src/Cms/NestObject.php +++ b/kirby/src/Cms/NestObject.php @@ -2,6 +2,7 @@ namespace Kirby\Cms; +use Kirby\Content\Field; use Kirby\Toolkit\Obj; /** diff --git a/kirby/src/Cms/Page.php b/kirby/src/Cms/Page.php index 49fa97f..3fab7b3 100644 --- a/kirby/src/Cms/Page.php +++ b/kirby/src/Cms/Page.php @@ -3,15 +3,19 @@ namespace Kirby\Cms; use Closure; +use Kirby\Content\Field; use Kirby\Exception\Exception; use Kirby\Exception\InvalidArgumentException; use Kirby\Exception\NotFoundException; use Kirby\Filesystem\Dir; -use Kirby\Filesystem\F; use Kirby\Http\Response; use Kirby\Http\Uri; use Kirby\Panel\Page as Panel; +use Kirby\Template\Template; use Kirby\Toolkit\A; +use Kirby\Toolkit\LazyValue; +use Kirby\Toolkit\Str; +use Throwable; /** * The `$page` object is the heart and @@ -27,141 +31,127 @@ use Kirby\Toolkit\A; */ class Page extends ModelWithContent { - use PageActions; - use PageSiblings; use HasChildren; use HasFiles; use HasMethods; use HasSiblings; + use PageActions; + use PageSiblings; public const CLASS_ALIAS = 'page'; /** * All registered page methods - * - * @var array + * @todo Remove when support for PHP 8.2 is dropped */ - public static $methods = []; + public static array $methods = []; /** * Registry with all Page models - * - * @var array */ - public static $models = []; + public static array $models = []; /** * The PageBlueprint object - * - * @var \Kirby\Cms\PageBlueprint */ - protected $blueprint; + protected PageBlueprint|null $blueprint = null; /** * Nesting level - * - * @var int */ - protected $depth; + protected int $depth; /** * Sorting number + slug - * - * @var string */ - protected $dirname; + protected string|null $dirname; /** * Path of dirnames - * - * @var string */ - protected $diruri; + protected string|null $diruri = null; /** * Draft status flag - * - * @var bool */ - protected $isDraft; + protected bool $isDraft; /** * The Page id - * - * @var string */ - protected $id; + protected string|null $id = null; /** * The template, that should be loaded * if it exists - * - * @var \Kirby\Template\Template */ - protected $intendedTemplate; + protected Template|null $intendedTemplate = null; - /** - * @var array - */ - protected $inventory; + protected array|null $inventory = null; /** * The sorting number - * - * @var int|null */ - protected $num; + protected int|null $num; /** * The parent page - * - * @var \Kirby\Cms\Page|null */ - protected $parent; + protected Page|null $parent; /** * Absolute path to the page directory - * - * @var string */ - protected $root; - - /** - * The parent Site object - * - * @var \Kirby\Cms\Site|null - */ - protected $site; + protected string|null $root; /** * The URL-appendix aka slug - * - * @var string */ - protected $slug; + protected string $slug; /** * The intended page template - * - * @var \Kirby\Template\Template */ - protected $template; + protected Template|null $template = null; /** * The page url - * - * @var string|null */ - protected $url; + protected string|null $url; + + /** + * Creates a new page object + */ + public function __construct(array $props) + { + if (isset($props['slug']) === false) { + throw new InvalidArgumentException('The page slug is required'); + } + + parent::__construct($props); + + $this->slug = $props['slug']; + // Sets the dirname manually, which works + // more reliable in connection with the inventory + // than computing the dirname afterwards + $this->dirname = $props['dirname'] ?? null; + $this->isDraft = $props['isDraft'] ?? false; + $this->num = $props['num'] ?? null; + $this->parent = $props['parent'] ?? null; + $this->root = $props['root'] ?? null; + + $this->setBlueprint($props['blueprint'] ?? null); + $this->setChildren($props['children'] ?? null); + $this->setDrafts($props['drafts'] ?? null); + $this->setFiles($props['files'] ?? null); + $this->setTemplate($props['template'] ?? null); + $this->setUrl($props['url'] ?? null); + } /** * Magic caller - * - * @param string $method - * @param array $arguments - * @return mixed */ - public function __call(string $method, array $arguments = []) + public function __call(string $method, array $arguments = []): mixed { // public property access if (isset($this->$method) === true) { @@ -177,24 +167,9 @@ class Page extends ModelWithContent return $this->content()->get($method); } - /** - * Creates a new page object - * - * @param array $props - */ - public function __construct(array $props) - { - // set the slug as the first property - $this->slug = $props['slug'] ?? null; - - // add all other properties - $this->setProperties($props); - } - /** * Improved `var_dump` output - * - * @return array + * @codeCoverageIgnore */ public function __debugInfo(): array { @@ -209,10 +184,7 @@ class Page extends ModelWithContent /** * Returns the url to the api endpoint - * * @internal - * @param bool $relative - * @return string */ public function apiUrl(bool $relative = false): string { @@ -225,23 +197,18 @@ class Page extends ModelWithContent /** * Returns the blueprint object - * - * @return \Kirby\Cms\PageBlueprint */ - public function blueprint() + public function blueprint(): PageBlueprint { - if ($this->blueprint instanceof PageBlueprint) { - return $this->blueprint; - } - - return $this->blueprint = PageBlueprint::factory('pages/' . $this->intendedTemplate(), 'pages/default', $this); + return $this->blueprint ??= PageBlueprint::factory( + 'pages/' . $this->intendedTemplate(), + 'pages/default', + $this + ); } /** * Returns an array with all blueprints that are available for the page - * - * @param string|null $inSection - * @return array */ public function blueprints(string|null $inSection = null): array { @@ -249,6 +216,10 @@ class Page extends ModelWithContent return $this->blueprint()->section($inSection)->blueprints(); } + if ($this->blueprints !== null) { + return $this->blueprints; + } + $blueprints = []; $templates = $this->blueprint()->changeTemplate() ?? $this->blueprint()->options()['changeTemplate'] ?? []; $currentTemplate = $this->intendedTemplate()->name(); @@ -278,14 +249,11 @@ class Page extends ModelWithContent } } - return array_values($blueprints); + return $this->blueprints = array_values($blueprints); } /** * Builds the cache id for the page - * - * @param string $contentType - * @return string */ protected function cacheId(string $contentType): string { @@ -302,14 +270,12 @@ class Page extends ModelWithContent /** * Prepares the content for the write method - * * @internal - * @param array $data - * @param string|null $languageCode - * @return array */ - public function contentFileData(array $data, string|null $languageCode = null): array - { + public function contentFileData( + array $data, + string|null $languageCode = null + ): array { return A::prepend($data, [ 'title' => $data['title'] ?? null, 'slug' => $data['slug'] ?? null @@ -319,68 +285,75 @@ class Page extends ModelWithContent /** * Returns the content text file * which is found by the inventory method - * * @internal - * @param string|null $languageCode - * @return string + * @deprecated 4.0.0 + * @todo Remove in v5 + * @codeCoverageIgnore */ public function contentFileName(string|null $languageCode = null): string { + Helpers::deprecated('The internal $model->contentFileName() method has been deprecated. Please let us know via a GitHub issue if you need this method and tell us your use case.', 'model-content-file'); return $this->intendedTemplate()->name(); } /** * Call the page controller - * * @internal - * @param array $data - * @param string $contentType - * @return array + * * @throws \Kirby\Exception\InvalidArgumentException If the controller returns invalid objects for `kirby`, `site`, `pages` or `page` */ - public function controller(array $data = [], string $contentType = 'html'): array - { + public function controller( + array $data = [], + string $contentType = 'html' + ): array { // create the template data $data = array_merge($data, [ 'kirby' => $kirby = $this->kirby(), 'site' => $site = $this->site(), - 'pages' => $site->children(), - 'page' => $site->visit($this) + 'pages' => new LazyValue(fn () => $site->children()), + 'page' => new LazyValue(fn () => $site->visit($this)) ]); // call the template controller if there's one. - $controllerData = $kirby->controller($this->template()->name(), $data, $contentType); + $controllerData = $kirby->controller( + $this->template()->name(), + $data, + $contentType + ); // merge controller data with original data safely + // to provide original data to template even if + // it wasn't returned by the controller explicitly if (empty($controllerData) === false) { $classes = [ - 'kirby' => 'Kirby\Cms\App', - 'site' => 'Kirby\Cms\Site', - 'pages' => 'Kirby\Cms\Pages', - 'page' => 'Kirby\Cms\Page' + 'kirby' => App::class, + 'site' => Site::class, + 'pages' => Pages::class, + 'page' => Page::class ]; foreach ($controllerData as $key => $value) { - if (array_key_exists($key, $classes) === true) { - if ($value instanceof $classes[$key]) { - $data[$key] = $value; - } else { - throw new InvalidArgumentException('The returned variable "' . $key . '" from the controller "' . $this->template()->name() . '" is not of the required type "' . $classes[$key] . '"'); - } - } else { - $data[$key] = $value; - } + $data[$key] = match (true) { + // original data wasn't overwritten + array_key_exists($key, $classes) === false => $value, + // original data was overwritten, but matches expected type + $value instanceof $classes[$key] => $value, + // throw error if data was overwritten with wrong type + default => throw new InvalidArgumentException('The returned variable "' . $key . '" from the controller "' . $this->template()->name() . '" is not of the required type "' . $classes[$key] . '"') + }; } } + // unwrap remaining lazy values in data + // (happens if the controller didn't override an original lazy Kirby object) + $data = LazyValue::unwrap($data); + return $data; } /** * Returns a number indicating how deep the page * is nested within the content folder - * - * @return int */ public function depth(): int { @@ -389,8 +362,6 @@ class Page extends ModelWithContent /** * Sorting number + Slug - * - * @return string */ public function dirname(): string { @@ -407,8 +378,6 @@ class Page extends ModelWithContent /** * Sorting number + Slug - * - * @return string */ public function diruri(): string { @@ -431,8 +400,6 @@ class Page extends ModelWithContent /** * Checks if the page exists on disk - * - * @return bool */ public function exists(): bool { @@ -442,18 +409,11 @@ class Page extends ModelWithContent /** * Constructs a Page object and also * takes page models into account. - * * @internal - * @param mixed $props - * @return static */ - public static function factory($props) + public static function factory($props): static { - if (empty($props['model']) === false) { - return static::model($props['model'], $props); - } - - return new static($props); + return static::model($props['model'] ?? 'default', $props); } /** @@ -465,7 +425,7 @@ class Page extends ModelWithContent * @param array $options Options for `Kirby\Http\Uri` to create URL parts * @param int $code HTTP status code */ - public function go(array $options = [], int $code = 302) + public function go(array $options = [], int $code = 302): void { Response::go($this->url($options), $code); } @@ -473,8 +433,6 @@ class Page extends ModelWithContent /** * Checks if the intended template * for the page exists. - * - * @return bool */ public function hasTemplate(): bool { @@ -483,8 +441,6 @@ class Page extends ModelWithContent /** * Returns the Page Id - * - * @return string */ public function id(): string { @@ -503,10 +459,8 @@ class Page extends ModelWithContent /** * Returns the template that should be * loaded if it exists. - * - * @return \Kirby\Template\Template */ - public function intendedTemplate() + public function intendedTemplate(): Template { if ($this->intendedTemplate !== null) { return $this->intendedTemplate; @@ -518,9 +472,7 @@ class Page extends ModelWithContent /** * Returns the inventory of files * children and content files - * * @internal - * @return array */ public function inventory(): array { @@ -542,7 +494,6 @@ class Page extends ModelWithContent * Compares the current object with the given page object * * @param \Kirby\Cms\Page|string $page - * @return bool */ public function is($page): bool { @@ -562,24 +513,34 @@ class Page extends ModelWithContent } /** - * Checks if the page is the current page - * - * @return bool + * Checks if the page is accessible that accessible and listable. + * This permission depends on the `read` option until v5 */ - public function isActive(): bool + public function isAccessible(): bool { - if ($this->site()->page()?->is($this) === true) { - return true; + // TODO: remove this check when `read` option deprecated in v5 + if ($this->isReadable() === false) { + return false; } - return false; + static $accessible = []; + + $template = $this->intendedTemplate()->name(); + + return $accessible[$template] ??= $this->permissions()->can('access'); } /** - * Checks if the page is a direct or indirect ancestor of the given $page object - * - * @param Page $child - * @return bool + * Checks if the page is the current page + */ + public function isActive(): bool + { + return $this->site()->page()?->is($this) === true; + } + + /** + * Checks if the page is a direct or indirect ancestor + * of the given $page object */ public function isAncestorOf(Page $child): bool { @@ -590,8 +551,6 @@ class Page extends ModelWithContent * Checks if the page can be cached in the * pages cache. This will also check if one * of the ignore rules from the config kick in. - * - * @return bool */ public function isCacheable(): bool { @@ -644,7 +603,6 @@ class Page extends ModelWithContent * Checks if the page is a child of the given page * * @param \Kirby\Cms\Page|string $parent - * @return bool */ public function isChildOf($parent): bool { @@ -655,7 +613,6 @@ class Page extends ModelWithContent * Checks if the page is a descendant of the given page * * @param \Kirby\Cms\Page|string $parent - * @return bool */ public function isDescendantOf($parent): bool { @@ -672,8 +629,6 @@ class Page extends ModelWithContent /** * Checks if the page is a descendant of the currently active page - * - * @return bool */ public function isDescendantOfActive(): bool { @@ -686,8 +641,6 @@ class Page extends ModelWithContent /** * Checks if the current page is a draft - * - * @return bool */ public function isDraft(): bool { @@ -696,8 +649,6 @@ class Page extends ModelWithContent /** * Checks if the page is the error page - * - * @return bool */ public function isErrorPage(): bool { @@ -706,8 +657,6 @@ class Page extends ModelWithContent /** * Checks if the page is the home page - * - * @return bool */ public function isHomePage(): bool { @@ -718,30 +667,56 @@ class Page extends ModelWithContent * It's often required to check for the * home and error page to stop certain * actions. That's why there's a shortcut. - * - * @return bool */ public function isHomeOrErrorPage(): bool { return $this->isHomePage() === true || $this->isErrorPage() === true; } + /** + * Check if the page can be listable by the current user + * This permission depends on the `read` option until v5 + */ + public function isListable(): bool + { + // TODO: remove this check when `read` option deprecated in v5 + if ($this->isReadable() === false) { + return false; + } + + // not accessible also means not listable + if ($this->isAccessible() === false) { + return false; + } + + static $listable = []; + + $template = $this->intendedTemplate()->name(); + + return $listable[$template] ??= $this->permissions()->can('list'); + } + /** * Checks if the page has a sorting number - * - * @return bool */ public function isListed(): bool { return $this->isPublished() && $this->num() !== null; } + public function isMovableTo(Page|Site $parent): bool + { + try { + return PageRules::move($this, $parent); + } catch (Throwable) { + return false; + } + } + /** * Checks if the page is open. * Open pages are either the current one * or descendants of the current one. - * - * @return bool */ public function isOpen(): bool { @@ -758,8 +733,6 @@ class Page extends ModelWithContent /** * Checks if the page is not a draft. - * - * @return bool */ public function isPublished(): bool { @@ -768,8 +741,7 @@ class Page extends ModelWithContent /** * Check if the page can be read by the current user - * - * @return bool + * @todo Deprecate `read` option in v5 and make the necessary changes for `access` and `list` options. */ public function isReadable(): bool { @@ -782,8 +754,6 @@ class Page extends ModelWithContent /** * Checks if the page is sortable - * - * @return bool */ public function isSortable(): bool { @@ -792,8 +762,6 @@ class Page extends ModelWithContent /** * Checks if the page has no sorting number - * - * @return bool */ public function isUnlisted(): bool { @@ -803,12 +771,9 @@ class Page extends ModelWithContent /** * Checks if the page access is verified. * This is only used for drafts so far. - * * @internal - * @param string|null $token - * @return bool */ - public function isVerified(string $token = null) + public function isVerified(string $token = null): bool { if ( $this->isPublished() === true && @@ -826,9 +791,7 @@ class Page extends ModelWithContent /** * Returns the root to the media folder for the page - * * @internal - * @return string */ public function mediaRoot(): string { @@ -837,9 +800,7 @@ class Page extends ModelWithContent /** * The page's base URL for any files - * * @internal - * @return string */ public function mediaUrl(): string { @@ -848,15 +809,14 @@ class Page extends ModelWithContent /** * Creates a page model if it has been registered - * * @internal - * @param string $name - * @param array $props - * @return static */ - public static function model(string $name, array $props = []) + public static function model(string $name, array $props = []): static { - if ($class = (static::$models[$name] ?? null)) { + $class = static::$models[$name] ?? null; + $class ??= static::$models['default'] ?? null; + + if ($class !== null) { $object = new $class($props); if ($object instanceof self) { @@ -869,25 +829,28 @@ class Page extends ModelWithContent /** * Returns the last modification date of the page - * - * @param string|null $format - * @param string|null $handler - * @param string|null $languageCode - * @return int|string */ - public function modified(string $format = null, string $handler = null, string $languageCode = null) - { - return F::modified( - $this->contentFile($languageCode), - $format, - $handler ?? $this->kirby()->option('date.handler', 'date') + public function modified( + string|null $format = null, + string|null $handler = null, + string|null $languageCode = null + ): int|string|false|null { + $identifier = $this->isDraft() === true ? 'changes' : 'published'; + + $modified = $this->storage()->modified( + $identifier, + $languageCode ); + + if ($modified === null) { + return null; + } + + return Str::date($modified, $format, $handler); } /** * Returns the sorting number - * - * @return int|null */ public function num(): int|null { @@ -896,29 +859,23 @@ class Page extends ModelWithContent /** * Returns the panel info object - * - * @return \Kirby\Panel\Page */ - public function panel() + public function panel(): Panel { return new Panel($this); } /** * Returns the parent Page object - * - * @return \Kirby\Cms\Page|null */ - public function parent() + public function parent(): Page|null { return $this->parent; } /** * Returns the parent id, if a parent exists - * * @internal - * @return string|null */ public function parentId(): string|null { @@ -929,21 +886,17 @@ class Page extends ModelWithContent * Returns the parent model, * which can either be another Page * or the Site - * * @internal - * @return \Kirby\Cms\Page|\Kirby\Cms\Site */ - public function parentModel() + public function parentModel(): Page|Site { return $this->parent() ?? $this->site(); } /** * Returns a list of all parents and their parents recursively - * - * @return \Kirby\Cms\Pages */ - public function parents() + public function parents(): Pages { $parents = new Pages(); $page = $this->parent(); @@ -967,19 +920,15 @@ class Page extends ModelWithContent /** * Returns the permissions object for this page - * - * @return \Kirby\Cms\PagePermissions */ - public function permissions() + public function permissions(): PagePermissions { return new PagePermissions($this); } /** * Draft preview Url - * * @internal - * @return string|null */ public function previewUrl(): string|null { @@ -989,11 +938,10 @@ class Page extends ModelWithContent return null; } - if ($preview === true) { - $url = $this->url(); - } else { - $url = $preview; - } + $url = match ($preview) { + true => $this->url(), + default => $preview + }; if ($this->isDraft() === true) { $uri = new Uri($url); @@ -1012,9 +960,7 @@ class Page extends ModelWithContent * render a content representation instead of * the default template. * - * @param array $data * @param string $contentType - * @return string * @throws \Kirby\Exception\NotFoundException If the default template cannot be found */ public function render(array $data = [], $contentType = 'html'): string @@ -1095,11 +1041,9 @@ class Page extends ModelWithContent /** * @internal - * @param mixed $type - * @return \Kirby\Template\Template * @throws \Kirby\Exception\NotFoundException If the content representation cannot be found */ - public function representation($type) + public function representation(mixed $type): Template { $kirby = $this->kirby(); $template = $this->template(); @@ -1115,8 +1059,6 @@ class Page extends ModelWithContent /** * Returns the absolute root to the page directory * No matter if it exists or not. - * - * @return string */ public function root(): string { @@ -1127,22 +1069,16 @@ class Page extends ModelWithContent * Returns the PageRules class instance * which is being used in various methods * to check for valid actions and input. - * - * @return \Kirby\Cms\PageRules */ - protected function rules() + protected function rules(): PageRules { return new PageRules(); } /** * Search all pages within the current page - * - * @param string|null $query - * @param array $params - * @return \Kirby\Cms\Pages */ - public function search(string $query = null, $params = []) + public function search(string|null $query = null, string|array $params = []): Pages { return $this->index()->search($query, $params); } @@ -1150,10 +1086,9 @@ class Page extends ModelWithContent /** * Sets the Blueprint object * - * @param array|null $blueprint * @return $this */ - protected function setBlueprint(array $blueprint = null) + protected function setBlueprint(array $blueprint = null): static { if ($blueprint !== null) { $blueprint['model'] = $this; @@ -1163,87 +1098,12 @@ class Page extends ModelWithContent return $this; } - /** - * Sets the dirname manually, which works - * more reliable in connection with the inventory - * than computing the dirname afterwards - * - * @param string|null $dirname - * @return $this - */ - protected function setDirname(string $dirname = null) - { - $this->dirname = $dirname; - return $this; - } - - /** - * Sets the draft flag - * - * @param bool $isDraft - * @return $this - */ - protected function setIsDraft(bool $isDraft = null) - { - $this->isDraft = $isDraft ?? false; - return $this; - } - - /** - * Sets the sorting number - * - * @param int|null $num - * @return $this - */ - protected function setNum(int $num = null) - { - $this->num = $num === null ? $num : (int)$num; - return $this; - } - - /** - * Sets the parent page object - * - * @param \Kirby\Cms\Page|null $parent - * @return $this - */ - protected function setParent(Page $parent = null) - { - $this->parent = $parent; - return $this; - } - - /** - * Sets the absolute path to the page - * - * @param string|null $root - * @return $this - */ - protected function setRoot(string $root = null) - { - $this->root = $root; - return $this; - } - - /** - * Sets the required Page slug - * - * @param string $slug - * @return $this - */ - protected function setSlug(string $slug) - { - $this->slug = $slug; - return $this; - } - /** * Sets the intended template * - * @param string|null $template * @return $this */ - protected function setTemplate(string $template = null) + protected function setTemplate(string $template = null): static { if ($template !== null) { $this->intendedTemplate = $this->kirby()->template($template); @@ -1255,10 +1115,9 @@ class Page extends ModelWithContent /** * Sets the Url * - * @param string|null $url * @return $this */ - protected function setUrl(string $url = null) + protected function setUrl(string $url = null): static { if (is_string($url) === true) { $url = rtrim($url, '/'); @@ -1270,9 +1129,6 @@ class Page extends ModelWithContent /** * Returns the slug of the page - * - * @param string|null $languageCode - * @return string */ public function slug(string $languageCode = null): string { @@ -1294,8 +1150,6 @@ class Page extends ModelWithContent /** * Returns the page status, which * can be `draft`, `listed` or `unlisted` - * - * @return string */ public function status(): string { @@ -1312,10 +1166,8 @@ class Page extends ModelWithContent /** * Returns the final template - * - * @return \Kirby\Template\Template */ - public function template() + public function template(): Template { if ($this->template !== null) { return $this->template; @@ -1332,10 +1184,8 @@ class Page extends ModelWithContent /** * Returns the title field or the slug as fallback - * - * @return \Kirby\Cms\Field */ - public function title() + public function title(): Field { return $this->content()->get('title')->or($this->slug()); } @@ -1343,38 +1193,35 @@ class Page extends ModelWithContent /** * Converts the most important * properties to array - * - * @return array */ public function toArray(): array { - return [ - 'children' => $this->children()->keys(), - 'content' => $this->content()->toArray(), - 'files' => $this->files()->keys(), - 'id' => $this->id(), - 'mediaUrl' => $this->mediaUrl(), - 'mediaRoot' => $this->mediaRoot(), - 'num' => $this->num(), - 'parent' => $this->parent() ? $this->parent()->id() : null, - 'slug' => $this->slug(), - 'template' => $this->template(), - 'translations' => $this->translations()->toArray(), - 'uid' => $this->uid(), - 'uri' => $this->uri(), - 'url' => $this->url() - ]; + return array_merge(parent::toArray(), [ + 'children' => $this->children()->keys(), + 'files' => $this->files()->keys(), + 'id' => $this->id(), + 'mediaUrl' => $this->mediaUrl(), + 'mediaRoot' => $this->mediaRoot(), + 'num' => $this->num(), + 'parent' => $this->parent()?->id(), + 'slug' => $this->slug(), + 'template' => $this->template(), + 'uid' => $this->uid(), + 'uri' => $this->uri(), + 'url' => $this->url() + ]); } /** * Returns a verification token, which * is used for the draft authentication - * - * @return string */ protected function token(): string { - return $this->kirby()->contentToken($this, $this->id() . $this->template()); + return $this->kirby()->contentToken( + $this, + $this->id() . $this->template() + ); } /** @@ -1385,7 +1232,6 @@ class Page extends ModelWithContent * can be translated. * * @see self::slug() - * @return string */ public function uid(): string { @@ -1395,9 +1241,6 @@ class Page extends ModelWithContent /** * The uri is the same as the id, except * that it will be translated in multi-language setups - * - * @param string|null $languageCode - * @return string */ public function uri(string $languageCode = null): string { @@ -1413,7 +1256,6 @@ class Page extends ModelWithContent * Returns the Url * * @param array|string|null $options - * @return string */ public function url($options = null): string { @@ -1453,11 +1295,11 @@ class Page extends ModelWithContent * * @internal * @param string|null $language - * @param array|null $options - * @return string */ - public function urlForLanguage($language = null, array $options = null): string - { + public function urlForLanguage( + $language = null, + array $options = null + ): string { if ($options !== null) { return Url::to($this->urlForLanguage($language), $options); } diff --git a/kirby/src/Cms/PageActions.php b/kirby/src/Cms/PageActions.php index ccea50e..e1a71c0 100644 --- a/kirby/src/Cms/PageActions.php +++ b/kirby/src/Cms/PageActions.php @@ -8,7 +8,6 @@ use Kirby\Exception\InvalidArgumentException; use Kirby\Exception\LogicException; use Kirby\Exception\NotFoundException; use Kirby\Filesystem\Dir; -use Kirby\Filesystem\F; use Kirby\Form\Form; use Kirby\Toolkit\A; use Kirby\Toolkit\I18n; @@ -30,6 +29,7 @@ trait PageActions /** * Adapts necessary modifications which page uuid, page slug and files uuid * of copy objects for single or multilang environments + * @internal */ protected function adaptCopy(Page $copy, bool $files = false, bool $children = false): Page { @@ -103,11 +103,10 @@ trait PageActions * This only affects this page, * siblings will not be resorted. * - * @param int|null $num * @return $this|static * @throws \Kirby\Exception\LogicException If a draft is being sorted or the directory cannot be moved */ - public function changeNum(int $num = null) + public function changeNum(int|null $num = null): static { if ($this->isDraft() === true) { throw new LogicException('Drafts cannot change their sorting number'); @@ -130,7 +129,7 @@ trait PageActions if (Dir::move($oldPage->root(), $newPage->root()) === true) { // Updates the root path of the old page with the root path // of the moved new page to use fly actions on old page in loop - $oldPage->setRoot($newPage->root()); + $oldPage->root = $newPage->root(); } else { throw new LogicException('The page directory cannot be moved'); } @@ -146,13 +145,13 @@ trait PageActions /** * Changes the slug/uid of the page * - * @param string $slug - * @param string|null $languageCode * @return $this|static * @throws \Kirby\Exception\LogicException If the directory cannot be moved */ - public function changeSlug(string $slug, string $languageCode = null) - { + public function changeSlug( + string $slug, + string|null $languageCode = null + ): static { // always sanitize the slug $slug = Str::slug($slug); @@ -205,14 +204,13 @@ trait PageActions /** * Change the slug for a specific language * - * @param string $slug - * @param string|null $languageCode - * @return static * @throws \Kirby\Exception\NotFoundException If the language for the given language code cannot be found * @throws \Kirby\Exception\InvalidArgumentException If the slug for the default language is being changed */ - protected function changeSlugForLanguage(string $slug, string $languageCode = null) - { + protected function changeSlugForLanguage( + string $slug, + string|null $languageCode = null + ): static { $language = $this->kirby()->language($languageCode); if (!$language) { @@ -247,10 +245,9 @@ trait PageActions * * @param string $status "draft", "listed" or "unlisted" * @param int|null $position Optional sorting number - * @return static * @throws \Kirby\Exception\InvalidArgumentException If an invalid status is being passed */ - public function changeStatus(string $status, int $position = null) + public function changeStatus(string $status, int|null $position = null): static { return match ($status) { 'draft' => $this->changeStatusToDraft(), @@ -260,10 +257,7 @@ trait PageActions }; } - /** - * @return static - */ - protected function changeStatusToDraft() + protected function changeStatusToDraft(): static { $arguments = ['page' => $this, 'status' => 'draft', 'position' => null]; $page = $this->commit( @@ -276,10 +270,9 @@ trait PageActions } /** - * @param int|null $position * @return $this|static */ - protected function changeStatusToListed(int $position = null) + protected function changeStatusToListed(int|null $position = null): static { // create a sorting number for the page $num = $this->createNum($position); @@ -304,7 +297,7 @@ trait PageActions /** * @return $this|static */ - protected function changeStatusToUnlisted() + protected function changeStatusToUnlisted(): static { if ($this->status() === 'unlisted') { return $this; @@ -325,10 +318,9 @@ trait PageActions * collection. Siblings will be resorted. If the page * status isn't yet `listed`, it will be changed to it. * - * @param int|null $position * @return $this|static */ - public function changeSort(int $position = null) + public function changeSort(int|null $position = null): static { return $this->changeStatus('listed', $position); } @@ -336,51 +328,18 @@ trait PageActions /** * Changes the page template * - * @param string $template * @return $this|static * @throws \Kirby\Exception\LogicException If the textfile cannot be renamed/moved */ - public function changeTemplate(string $template) + public function changeTemplate(string $template): static { if ($template === $this->intendedTemplate()->name()) { return $this; } return $this->commit('changeTemplate', ['page' => $this, 'template' => $template], function ($oldPage, $template) { - if ($this->kirby()->multilang() === true) { - $newPage = $this->clone([ - 'template' => $template - ]); - - foreach ($this->kirby()->languages()->codes() as $code) { - if ($oldPage->translation($code)->exists() !== true) { - continue; - } - - $content = $oldPage->content($code)->convertTo($template); - - if (F::remove($oldPage->contentFile($code)) !== true) { - throw new LogicException('The old text file could not be removed'); - } - - // save the language file - $newPage->save($content, $code); - } - - // return a fresh copy of the object - $page = $newPage->clone(); - } else { - $newPage = $this->clone([ - 'content' => $this->content()->convertTo($template), - 'template' => $template - ]); - - if (F::remove($oldPage->contentFile()) !== true) { - throw new LogicException('The old text file could not be removed'); - } - - $page = $newPage->save(); - } + // convert for new template/blueprint + $page = $oldPage->convertTo($template); // update the parent collection static::updateParentCollections($page, 'set'); @@ -391,13 +350,11 @@ trait PageActions /** * Change the page title - * - * @param string $title - * @param string|null $languageCode - * @return static */ - public function changeTitle(string $title, string $languageCode = null) - { + public function changeTitle( + string $title, + string|null $languageCode = null + ): static { $arguments = ['page' => $this, 'title' => $title, 'languageCode' => $languageCode]; return $this->commit('changeTitle', $arguments, function ($page, $title, $languageCode) { $page = $page->save(['title' => $title], $languageCode); @@ -417,14 +374,12 @@ trait PageActions * 3. commits the store action * 4. sends the after hook * 5. returns the result - * - * @param string $action - * @param array $arguments - * @param \Closure $callback - * @return mixed */ - protected function commit(string $action, array $arguments, Closure $callback) - { + protected function commit( + string $action, + array $arguments, + Closure $callback + ): mixed { $old = $this->hardcopy(); $kirby = $this->kirby(); $argumentValues = array_values($arguments); @@ -452,11 +407,9 @@ trait PageActions /** * Copies the page to a new parent * - * @param array $options - * @return \Kirby\Cms\Page * @throws \Kirby\Exception\DuplicateException If the page already exists */ - public function copy(array $options = []) + public function copy(array $options = []): static { $slug = $options['slug'] ?? $this->slug(); $isDraft = $options['isDraft'] ?? $this->isDraft(); @@ -495,7 +448,8 @@ trait PageActions $ignore[] = $file->root(); // append all content files - array_push($ignore, ...$file->contentFiles()); + array_push($ignore, ...$file->storage()->contentFiles('published')); + array_push($ignore, ...$file->storage()->contentFiles('changes')); } } @@ -514,11 +468,8 @@ trait PageActions /** * Creates and stores a new page - * - * @param array $props - * @return static */ - public static function create(array $props) + public static function create(array $props): Page { // clean up the slug $props['slug'] = Str::slug($props['slug'] ?? $props['content']['title'] ?? null); @@ -584,11 +535,8 @@ trait PageActions /** * Creates a child of the current page - * - * @param array $props - * @return static */ - public function createChild(array $props) + public function createChild(array $props): Page { $props = array_merge($props, [ 'url' => null, @@ -597,16 +545,13 @@ trait PageActions 'site' => $this->site(), ]); - $modelClass = Page::$models[$props['template']] ?? Page::class; + $modelClass = Page::$models[$props['template'] ?? null] ?? Page::class; return $modelClass::create($props); } /** * Create the sorting number for the page * depending on the blueprint settings - * - * @param int|null $num - * @return int */ public function createNum(int $num = null): int { @@ -664,9 +609,6 @@ trait PageActions /** * Deletes the page - * - * @param bool $force - * @return bool */ public function delete(bool $force = false): bool { @@ -716,12 +658,8 @@ trait PageActions /** * Duplicates the page with the given * slug and optionally copies all files - * - * @param string|null $slug - * @param array $options - * @return \Kirby\Cms\Page */ - public function duplicate(string $slug = null, array $options = []) + public function duplicate(string|null $slug = null, array $options = []): static { // create the slug for the duplicate $slug = Str::slug($slug ?? $this->slug() . '-' . Str::slug(I18n::translate('page.duplicate.appendix'))); @@ -749,11 +687,60 @@ trait PageActions }); } + /** + * Moves the page to a new parent if the + * new parent accepts the page type + */ + public function move(Site|Page $parent): Page + { + // nothing to move + if ($this->parentModel()->is($parent) === true) { + return $this; + } + + $arguments = [ + 'page' => $this, + 'parent' => $parent + ]; + + return $this->commit('move', $arguments, function ($page, $parent) { + // remove the uuid cache for this page + $page->uuid()?->clear(true); + + // move drafts into the drafts folder of the parent + if ($page->isDraft() === true) { + $newRoot = $parent->root() . '/_drafts/' . $page->dirname(); + } else { + $newRoot = $parent->root() . '/' . $page->dirname(); + } + + // try to move the page directory on disk + if (Dir::move($page->root(), $newRoot) !== true) { + throw new LogicException([ + 'key' => 'page.move.directory' + ]); + } + + // flush all collection caches to be sure that + // the new child is included afterwards + $parent->purge(); + + // double-check if the new child can actually be found + if (!$newPage = $parent->childrenAndDrafts()->find($page->slug())) { + throw new LogicException([ + 'key' => 'page.move.notFound' + ]); + } + + return $newPage; + }); + } + /** * @return $this|static * @throws \Kirby\Exception\LogicException If the folder cannot be moved */ - public function publish() + public function publish(): static { if ($this->isDraft() === false) { return $this; @@ -794,25 +781,24 @@ trait PageActions /** * Clean internal caches + * * @return $this */ - public function purge() + public function purge(): static { + parent::purge(); + $this->blueprint = null; $this->children = null; $this->childrenAndDrafts = null; - $this->content = null; $this->drafts = null; $this->files = null; $this->inventory = null; - $this->translations = null; return $this; } /** - * @param int|null $position - * @return bool * @throws \Kirby\Exception\LogicException If the page is not included in the siblings collection */ protected function resortSiblingsAfterListing(int $position = null): bool @@ -859,7 +845,7 @@ trait PageActions } /** - * @return bool + * @internal */ public function resortSiblingsAfterUnlisting(): bool { @@ -886,15 +872,13 @@ trait PageActions /** * Stores the content on disk - * * @internal - * @param array|null $data - * @param string|null $languageCode - * @param bool $overwrite - * @return static */ - public function save(array $data = null, string $languageCode = null, bool $overwrite = false) - { + public function save( + array|null $data = null, + string|null $languageCode = null, + bool $overwrite = false + ): static { $page = parent::save($data, $languageCode, $overwrite); // overwrite the updated page in the parent collection @@ -910,7 +894,7 @@ trait PageActions * @return $this|static * @throws \Kirby\Exception\LogicException If the folder cannot be moved */ - public function unpublish() + public function unpublish(): static { if ($this->isDraft() === true) { return $this; @@ -947,14 +931,12 @@ trait PageActions /** * Updates the page data - * - * @param array|null $input - * @param string|null $languageCode - * @param bool $validate - * @return static */ - public function update(array $input = null, string $languageCode = null, bool $validate = false) - { + public function update( + array|null $input = null, + string|null $languageCode = null, + bool $validate = false + ): static { if ($this->isDraft() === true) { $validate = false; } @@ -982,10 +964,12 @@ trait PageActions * @param \Kirby\Cms\Page $page * @param string $method Method to call on the parent collections * @param \Kirby\Cms\Page|null $parentMdel - * @return void */ - protected static function updateParentCollections($page, string $method, $parentModel = null): void - { + protected static function updateParentCollections( + $page, + string $method, + $parentModel = null + ): void { $parentModel ??= $page->parentModel(); // method arguments depending on the called method diff --git a/kirby/src/Cms/PageBlueprint.php b/kirby/src/Cms/PageBlueprint.php index 12843d3..79be695 100644 --- a/kirby/src/Cms/PageBlueprint.php +++ b/kirby/src/Cms/PageBlueprint.php @@ -16,8 +16,6 @@ class PageBlueprint extends Blueprint /** * Creates a new page blueprint object * with the given props - * - * @param array $props */ public function __construct(array $props) { @@ -28,6 +26,7 @@ class PageBlueprint extends Blueprint $this->props['options'] ?? true, // defaults [ + 'access' => null, 'changeSlug' => null, 'changeStatus' => null, 'changeTemplate' => null, @@ -35,8 +34,10 @@ class PageBlueprint extends Blueprint 'create' => null, 'delete' => null, 'duplicate' => null, - 'read' => null, + 'list' => null, + 'move' => null, 'preview' => null, + 'read' => null, 'sort' => null, 'update' => null, ], @@ -58,8 +59,6 @@ class PageBlueprint extends Blueprint /** * Returns the page numbering mode - * - * @return string */ public function num(): string { @@ -70,7 +69,6 @@ class PageBlueprint extends Blueprint * Normalizes the ordering number * * @param mixed $num - * @return string */ protected function normalizeNum($num): string { @@ -86,7 +84,6 @@ class PageBlueprint extends Blueprint * Normalizes the available status options for the page * * @param mixed $status - * @return array */ protected function normalizeStatus($status): array { @@ -163,8 +160,6 @@ class PageBlueprint extends Blueprint /** * Returns the options object * that handles page options and permissions - * - * @return array */ public function options(): array { @@ -176,10 +171,8 @@ class PageBlueprint extends Blueprint * The preview setting controls the "Open" * button in the panel and redirects it to a * different URL if necessary. - * - * @return string|bool */ - public function preview() + public function preview(): string|bool { $preview = $this->props['options']['preview'] ?? true; @@ -192,8 +185,6 @@ class PageBlueprint extends Blueprint /** * Returns the status array - * - * @return array */ public function status(): array { diff --git a/kirby/src/Cms/PagePermissions.php b/kirby/src/Cms/PagePermissions.php index fbc69a9..b4ca118 100644 --- a/kirby/src/Cms/PagePermissions.php +++ b/kirby/src/Cms/PagePermissions.php @@ -13,30 +13,18 @@ namespace Kirby\Cms; */ class PagePermissions extends ModelPermissions { - /** - * @var string - */ - protected $category = 'pages'; + protected string $category = 'pages'; - /** - * @return bool - */ protected function canChangeSlug(): bool { return $this->model->isHomeOrErrorPage() !== true; } - /** - * @return bool - */ protected function canChangeStatus(): bool { return $this->model->isErrorPage() !== true; } - /** - * @return bool - */ protected function canChangeTemplate(): bool { if ($this->model->isErrorPage() === true) { @@ -50,17 +38,16 @@ class PagePermissions extends ModelPermissions return true; } - /** - * @return bool - */ protected function canDelete(): bool { return $this->model->isHomeOrErrorPage() !== true; } - /** - * @return bool - */ + protected function canMove(): bool + { + return $this->model->isHomeOrErrorPage() !== true; + } + protected function canSort(): bool { if ($this->model->isErrorPage() === true) { diff --git a/kirby/src/Cms/PagePicker.php b/kirby/src/Cms/PagePicker.php index a8e38de..f84cc55 100644 --- a/kirby/src/Cms/PagePicker.php +++ b/kirby/src/Cms/PagePicker.php @@ -18,25 +18,14 @@ use Kirby\Exception\InvalidArgumentException; */ class PagePicker extends Picker { - /** - * @var \Kirby\Cms\Pages - */ - protected $items; - - /** - * @var \Kirby\Cms\Pages - */ - protected $itemsForQuery; - - /** - * @var \Kirby\Cms\Page|\Kirby\Cms\Site|null - */ - protected $parent; + // TODO: null only due to our Properties setters, + // remove once our implementation is better + protected Pages|null $items = null; + protected Pages|null $itemsForQuery = null; + protected Page|Site|null $parent; /** * Extends the basic defaults - * - * @return array */ public function defaults(): array { @@ -55,10 +44,8 @@ class PagePicker extends Picker * also be any subpage. When a query is given * and subpage navigation is deactivated, * there will be no model available at all. - * - * @return \Kirby\Cms\Page|\Kirby\Cms\Site|null */ - public function model() + public function model(): Page|Site|null { // no subpages navigation = no model if ($this->options['subpages'] === false) { @@ -77,10 +64,8 @@ class PagePicker extends Picker * Returns a model object for the given * query, depending on the parent and subpages * options. - * - * @return \Kirby\Cms\Page|\Kirby\Cms\Site|null */ - public function modelForQuery() + public function modelForQuery(): Page|Site|null { if ($this->options['subpages'] === true && empty($this->options['parent']) === false) { return $this->parent(); @@ -93,11 +78,8 @@ class PagePicker extends Picker * Returns basic information about the * parent model that is currently selected * in the page picker. - * - * @param \Kirby\Cms\Site|\Kirby\Cms\Page|null - * @return array|null */ - public function modelToArray($model = null): array|null + public function modelToArray(Page|Site $model = null): array|null { if ($model === null) { return null; @@ -132,10 +114,8 @@ class PagePicker extends Picker /** * Search all pages for the picker - * - * @return \Kirby\Cms\Pages|null */ - public function items() + public function items(): Pages|null { // cache if ($this->items !== null) { @@ -157,8 +137,8 @@ class PagePicker extends Picker $items = $this->itemsForQuery(); } - // filter protected pages - $items = $items->filter('isReadable', true); + // filter protected and hidden pages + $items = $items->filter('isListable', true); // search $items = $this->search($items); @@ -169,10 +149,8 @@ class PagePicker extends Picker /** * Search for pages by parent - * - * @return \Kirby\Cms\Pages */ - public function itemsForParent() + public function itemsForParent(): Pages { return $this->parent()->children(); } @@ -180,10 +158,9 @@ class PagePicker extends Picker /** * Search for pages by query string * - * @return \Kirby\Cms\Pages * @throws \Kirby\Exception\InvalidArgumentException */ - public function itemsForQuery() + public function itemsForQuery(): Pages { // cache if ($this->itemsForQuery !== null) { @@ -212,26 +189,18 @@ class PagePicker extends Picker * The model will be used to fetch * subpages unless there's a specific * query to find pages instead. - * - * @return \Kirby\Cms\Page|\Kirby\Cms\Site */ - public function parent() + public function parent(): Page|Site { - if ($this->parent !== null) { - return $this->parent; - } - - return $this->parent = $this->kirby->page($this->options['parent']) ?? $this->site; + return $this->parent ??= $this->kirby->page($this->options['parent']) ?? $this->site; } /** * Calculates the top-most model (page or site) * that can be accessed when navigating * through pages. - * - * @return \Kirby\Cms\Page|\Kirby\Cms\Site */ - public function start() + public function start(): Page|Site { if (empty($this->options['query']) === false) { return $this->itemsForQuery()?->parent() ?? $this->site; @@ -244,8 +213,6 @@ class PagePicker extends Picker * Returns an associative array * with all information for the picker. * This will be passed directly to the API. - * - * @return array */ public function toArray(): array { diff --git a/kirby/src/Cms/PageRules.php b/kirby/src/Cms/PageRules.php index 30ef6e8..b981058 100644 --- a/kirby/src/Cms/PageRules.php +++ b/kirby/src/Cms/PageRules.php @@ -6,6 +6,7 @@ use Kirby\Exception\DuplicateException; use Kirby\Exception\InvalidArgumentException; use Kirby\Exception\LogicException; use Kirby\Exception\PermissionException; +use Kirby\Toolkit\A; use Kirby\Toolkit\Str; /** @@ -22,9 +23,6 @@ class PageRules /** * Validates if the sorting number of the page can be changed * - * @param \Kirby\Cms\Page $page - * @param int|null $num - * @return bool * @throws \Kirby\Exception\InvalidArgumentException If the given number is invalid */ public static function changeNum(Page $page, int $num = null): bool @@ -39,9 +37,6 @@ class PageRules /** * Validates if the slug for the page can be changed * - * @param \Kirby\Cms\Page $page - * @param string $slug - * @return bool * @throws \Kirby\Exception\DuplicateException If a page with this slug already exists * @throws \Kirby\Exception\PermissionException If the user is not allowed to change the slug */ @@ -57,6 +52,7 @@ class PageRules } self::validateSlugLength($slug); + self::validateSlugProtectedPaths($page, $slug); $siblings = $page->parentModel()->children(); $drafts = $page->parentModel()->drafts(); @@ -85,14 +81,13 @@ class PageRules /** * Validates if the status for the page can be changed * - * @param \Kirby\Cms\Page $page - * @param string $status - * @param int|null $position - * @return bool * @throws \Kirby\Exception\InvalidArgumentException If the given status is invalid */ - public static function changeStatus(Page $page, string $status, int $position = null): bool - { + public static function changeStatus( + Page $page, + string $status, + int $position = null + ): bool { if (isset($page->blueprint()->status()[$status]) === false) { throw new InvalidArgumentException(['key' => 'page.status.invalid']); } @@ -108,11 +103,9 @@ class PageRules /** * Validates if a page can be converted to a draft * - * @param \Kirby\Cms\Page $page - * @return bool * @throws \Kirby\Exception\PermissionException If the user is not allowed to change the status or the page cannot be converted to a draft */ - public static function changeStatusToDraft(Page $page) + public static function changeStatusToDraft(Page $page): bool { if ($page->permissions()->changeStatus() !== true) { throw new PermissionException([ @@ -138,13 +131,10 @@ class PageRules /** * Validates if the status of a page can be changed to listed * - * @param \Kirby\Cms\Page $page - * @param int $position - * @return bool * @throws \Kirby\Exception\InvalidArgumentException If the given position is invalid * @throws \Kirby\Exception\PermissionException If the user is not allowed to change the status or the status for the page cannot be changed by any user */ - public static function changeStatusToListed(Page $page, int $position) + public static function changeStatusToListed(Page $page, int $position): bool { // no need to check for status changing permissions, // instead we need to check for sorting permissions @@ -173,8 +163,6 @@ class PageRules /** * Validates if the status of a page can be changed to unlisted * - * @param \Kirby\Cms\Page $page - * @return bool * @throws \Kirby\Exception\PermissionException If the user is not allowed to change the status */ public static function changeStatusToUnlisted(Page $page) @@ -187,9 +175,6 @@ class PageRules /** * Validates if the template of the page can be changed * - * @param \Kirby\Cms\Page $page - * @param string $template - * @return bool * @throws \Kirby\Exception\LogicException If the template of the page cannot be changed at all * @throws \Kirby\Exception\PermissionException If the user is not allowed to change the template */ @@ -222,9 +207,6 @@ class PageRules /** * Validates if the title of the page can be changed * - * @param \Kirby\Cms\Page $page - * @param string $title - * @return bool * @throws \Kirby\Exception\InvalidArgumentException If the new title is empty * @throws \Kirby\Exception\PermissionException If the user is not allowed to change the title */ @@ -239,11 +221,7 @@ class PageRules ]); } - if (Str::length($title) === 0) { - throw new InvalidArgumentException([ - 'key' => 'page.changeTitle.empty', - ]); - } + static::validateTitleLength($title); return true; } @@ -251,8 +229,6 @@ class PageRules /** * Validates if the page can be created * - * @param \Kirby\Cms\Page $page - * @return bool * @throws \Kirby\Exception\DuplicateException If the same page or a draft already exists * @throws \Kirby\Exception\InvalidArgumentException If the slug is invalid * @throws \Kirby\Exception\PermissionException If the user is not allowed to create this page @@ -269,6 +245,7 @@ class PageRules } self::validateSlugLength($page->slug()); + self::validateSlugProtectedPaths($page, $page->slug()); if ($page->exists() === true) { throw new DuplicateException([ @@ -303,9 +280,6 @@ class PageRules /** * Validates if the page can be deleted * - * @param \Kirby\Cms\Page $page - * @param bool $force - * @return bool * @throws \Kirby\Exception\LogicException If the page has children and should not be force-deleted * @throws \Kirby\Exception\PermissionException If the user is not allowed to delete the page */ @@ -330,14 +304,13 @@ class PageRules /** * Validates if the page can be duplicated * - * @param \Kirby\Cms\Page $page - * @param string $slug - * @param array $options - * @return bool * @throws \Kirby\Exception\PermissionException If the user is not allowed to duplicate the page */ - public static function duplicate(Page $page, string $slug, array $options = []): bool - { + public static function duplicate( + Page $page, + string $slug, + array $options = [] + ): bool { if ($page->permissions()->duplicate() !== true) { throw new PermissionException([ 'key' => 'page.duplicate.permission', @@ -352,12 +325,82 @@ class PageRules return true; } + /** + * Check if the page can be moved + * to the given parent + */ + public static function move(Page $page, Site|Page $parent): bool + { + // if nothing changes, there's no need for checks + if ($parent->is($page->parent()) === true) { + return true; + } + + if ($page->permissions()->move() !== true) { + throw new PermissionException([ + 'key' => 'page.move.permission', + 'data' => [ + 'slug' => $page->slug() + ] + ]); + } + + // the page cannot be moved into itself + if ($parent instanceof Page && ($page->is($parent) === true || $page->isAncestorOf($parent) === true)) { + throw new LogicException([ + 'key' => 'page.move.ancestor', + ]); + } + + // check for duplicates + if ($parent->childrenAndDrafts()->find($page->slug())) { + throw new DuplicateException([ + 'key' => 'page.move.duplicate', + 'data' => [ + 'slug' => $page->slug(), + ] + ]); + } + + $allowed = []; + + // collect all allowed subpage templates + foreach ($parent->blueprint()->sections() as $section) { + // only take pages sections into consideration + if ($section->type() !== 'pages') { + continue; + } + + // only consider page sections that list pages + // of the targeted new parent page + if ($section->parent() !== $parent) { + continue; + } + + // go through all allowed blueprints and + // add the name to the allow list + foreach ($section->blueprints() as $blueprint) { + $allowed[] = $blueprint['name']; + } + } + + // check if the template of this page is allowed as subpage type + if (in_array($page->intendedTemplate()->name(), $allowed) === false) { + throw new PermissionException([ + 'key' => 'page.move.template', + 'data' => [ + 'template' => $page->intendedTemplate()->name(), + 'parent' => $parent->id() ?? '/', + ] + ]); + } + + return true; + } + /** * Check if the page can be published * (status change from draft to listed or unlisted) - * - * @param Page $page - * @return bool */ public static function publish(Page $page): bool { @@ -383,9 +426,6 @@ class PageRules /** * Validates if the page can be updated * - * @param \Kirby\Cms\Page $page - * @param array $content - * @return bool * @throws \Kirby\Exception\PermissionException If the user is not allowed to update the page */ public static function update(Page $page, array $content = []): bool @@ -406,11 +446,9 @@ class PageRules * Ensures that the slug is not empty and doesn't exceed the maximum length * to make sure that the directory name will be accepted by the filesystem * - * @param string $slug New slug to check - * @return void * @throws \Kirby\Exception\InvalidArgumentException If the slug is empty or too long */ - protected static function validateSlugLength(string $slug): void + public static function validateSlugLength(string $slug): void { $slugLength = Str::length($slug); @@ -433,4 +471,48 @@ class PageRules } } } + + + /** + * Ensure that a top-level page path does not start with one of + * the reserved URL paths, e.g. for API or the Panel + * + * @throws \Kirby\Exception\InvalidArgumentException If the page ID starts as one of the disallowed paths + */ + protected static function validateSlugProtectedPaths( + Page $page, + string $slug + ): void { + if ($page->parent() === null) { + $paths = A::map( + ['api', 'assets', 'media', 'panel'], + fn ($url) => $page->kirby()->url($url, true)->path()->toString() + ); + + $index = array_search($slug, $paths); + + if ($index !== false) { + throw new InvalidArgumentException([ + 'key' => 'page.changeSlug.reserved', + 'data' => [ + 'path' => $paths[$index] + ] + ]); + } + } + } + + /** + * Ensures that the page title is not empty + * + * @throws \Kirby\Exception\InvalidArgumentException If the title is empty + */ + public static function validateTitleLength(string $title): void + { + if (Str::length($title) === 0) { + throw new InvalidArgumentException([ + 'key' => 'page.changeTitle.empty', + ]); + } + } } diff --git a/kirby/src/Cms/PageSiblings.php b/kirby/src/Cms/PageSiblings.php index 22014d2..04f9f63 100644 --- a/kirby/src/Cms/PageSiblings.php +++ b/kirby/src/Cms/PageSiblings.php @@ -18,8 +18,6 @@ trait PageSiblings * page in the siblings collection * * @param \Kirby\Cms\Collection|null $collection - * - * @return bool */ public function hasNextListed($collection = null): bool { @@ -31,8 +29,6 @@ trait PageSiblings * page in the siblings collection * * @param \Kirby\Cms\Collection|null $collection - * - * @return bool */ public function hasNextUnlisted($collection = null): bool { @@ -44,8 +40,6 @@ trait PageSiblings * page in the siblings collection * * @param \Kirby\Cms\Collection|null $collection - * - * @return bool */ public function hasPrevListed($collection = null): bool { @@ -57,8 +51,6 @@ trait PageSiblings * page in the siblings collection * * @param \Kirby\Cms\Collection|null $collection - * - * @return bool */ public function hasPrevUnlisted($collection = null): bool { @@ -130,7 +122,6 @@ trait PageSiblings /** * Returns siblings with the same template * - * @param bool $self * @return \Kirby\Cms\Pages */ public function templateSiblings(bool $self = true) diff --git a/kirby/src/Cms/Pages.php b/kirby/src/Cms/Pages.php index e5773dd..95ccfc5 100644 --- a/kirby/src/Cms/Pages.php +++ b/kirby/src/Cms/Pages.php @@ -41,10 +41,8 @@ class Pages extends Collection /** * All registered pages methods - * - * @var array */ - public static $methods = []; + public static array $methods = []; /** * Adds a single page or @@ -55,7 +53,7 @@ class Pages extends Collection * @return $this * @throws \Kirby\Exception\InvalidArgumentException When no `Page` or `Pages` object or an ID of an existing page is passed */ - public function add($object) + public function add($object): static { $site = App::instance()->site(); @@ -85,20 +83,16 @@ class Pages extends Collection /** * Returns all audio files of all children - * - * @return \Kirby\Cms\Files */ - public function audio() + public function audio(): Files { return $this->files()->filter('type', 'audio'); } /** * Returns all children for each page in the array - * - * @return \Kirby\Cms\Pages */ - public function children() + public function children(): Pages { $children = new Pages([]); @@ -113,30 +107,24 @@ class Pages extends Collection /** * Returns all code files of all children - * - * @return \Kirby\Cms\Files */ - public function code() + public function code(): Files { return $this->files()->filter('type', 'code'); } /** * Returns all documents of all children - * - * @return \Kirby\Cms\Files */ - public function documents() + public function documents(): Files { return $this->files()->filter('type', 'document'); } /** * Fetch all drafts for all pages in the collection - * - * @return \Kirby\Cms\Pages */ - public function drafts() + public function drafts(): Pages { $drafts = new Pages([]); @@ -151,14 +139,12 @@ class Pages extends Collection /** * Creates a pages collection from an array of props - * - * @param array $pages - * @param \Kirby\Cms\Model|null $model - * @param bool|null $draft - * @return static */ - public static function factory(array $pages, Model $model = null, bool $draft = null) - { + public static function factory( + array $pages, + Page|Site $model = null, + bool $draft = null + ): static { $model ??= App::instance()->site(); $children = new static([], $model); $kirby = $model->kirby(); @@ -187,10 +173,8 @@ class Pages extends Collection /** * Returns all files of all children - * - * @return \Kirby\Cms\Files */ - public function files() + public function files(): Files { $files = new Files([], $this->parent); @@ -206,11 +190,8 @@ class Pages extends Collection /** * Finds a page by its ID or URI * @internal Use `$pages->find()` instead - * - * @param string|null $key - * @return \Kirby\Cms\Page|null */ - public function findByKey(string|null $key = null) + public function findByKey(string|null $key = null): Page|null { if ($key === null) { return null; @@ -239,17 +220,21 @@ class Pages extends Collection return $page; } - // try to find the page by its (translated) URI by stepping through the page tree + $kirby = App::instance(); + $multiLang = $kirby->multilang(); + + // try to find the page by its (translated) URI + // by stepping through the page tree $start = $this->parent instanceof Page ? $this->parent->id() : ''; - if ($page = $this->findByKeyRecursive($key, $start, App::instance()->multilang())) { + if ($page = $this->findByKeyRecursive($key, $start, $multiLang)) { return $page; } // for secondary languages, try the full translated URI // (for collections without parent that won't have a result above) if ( - App::instance()->multilang() === true && - App::instance()->language()->isDefault() === false && + $multiLang === true && + $kirby->language()->isDefault() === false && $page = $this->findBy('uri', $key) ) { return $page; @@ -263,8 +248,11 @@ class Pages extends Collection * * @return mixed */ - protected function findByKeyRecursive(string $id, string $startAt = null, bool $multiLang = false) - { + protected function findByKeyRecursive( + string $id, + string $startAt = null, + bool $multiLang = false + ) { $path = explode('/', $id); $item = null; $query = $startAt; @@ -274,9 +262,14 @@ class Pages extends Collection $query = ltrim($query . '/' . $key, '/'); $item = $collection->get($query) ?? null; - if ($item === null && $multiLang === true && !App::instance()->language()->isDefault()) { + if ( + $item === null && + $multiLang === true && + App::instance()->language()->isDefault() === false + ) { if (count($path) > 1 || $collection->parent()) { - // either the desired path is definitely not a slug, or collection is the children of another collection + // either the desired path is definitely not a slug, + // or collection is the children of another collection $item = $collection->findBy('slug', $key); } else { // desired path _could_ be a slug or a "top level" uri @@ -294,10 +287,8 @@ class Pages extends Collection /** * Finds the currently open page - * - * @return \Kirby\Cms\Page|null */ - public function findOpen() + public function findOpen(): Page|null { return $this->findBy('isOpen', true); } @@ -325,10 +316,8 @@ class Pages extends Collection /** * Returns all images of all children - * - * @return \Kirby\Cms\Files */ - public function images() + public function images(): Files { return $this->files()->filter('type', 'image'); } @@ -337,7 +326,6 @@ class Pages extends Collection * Create a recursive flat index of all * pages and subpages, etc. * - * @param bool $drafts * @return \Kirby\Cms\Pages */ public function index(bool $drafts = false) @@ -371,20 +359,16 @@ class Pages extends Collection /** * Returns all listed pages in the collection - * - * @return \Kirby\Cms\Pages */ - public function listed() + public function listed(): static { return $this->filter('isListed', '==', true); } /** * Returns all unlisted pages in the collection - * - * @return \Kirby\Cms\Pages */ - public function unlisted() + public function unlisted(): static { return $this->filter('isUnlisted', '==', true); } @@ -468,20 +452,14 @@ class Pages extends Collection /** * Returns an array with all page numbers - * - * @return array */ public function nums(): array { return $this->pluck('num'); } - /* - * Returns all listed and unlisted pages in the collection - * - * @return \Kirby\Cms\Pages - */ - public function published() + // Returns all listed and unlisted pages in the collection + public function published(): static { return $this->filter('isDraft', '==', false); } @@ -509,10 +487,8 @@ class Pages extends Collection /** * Returns all video files of all children - * - * @return \Kirby\Cms\Files */ - public function videos() + public function videos(): Files { return $this->files()->filter('type', 'video'); } diff --git a/kirby/src/Cms/Pagination.php b/kirby/src/Cms/Pagination.php index c88f749..fdefddd 100644 --- a/kirby/src/Cms/Pagination.php +++ b/kirby/src/Cms/Pagination.php @@ -60,8 +60,6 @@ class Pagination extends BasePagination * 'url' => new Uri('https://getkirby.com/blog') * ]); * ``` - * - * @param array $params */ public function __construct(array $params = []) { @@ -95,8 +93,6 @@ class Pagination extends BasePagination /** * Returns the Url for the first page - * - * @return string|null */ public function firstPageUrl(): string|null { @@ -105,8 +101,6 @@ class Pagination extends BasePagination /** * Returns the Url for the last page - * - * @return string|null */ public function lastPageUrl(): string|null { @@ -116,8 +110,6 @@ class Pagination extends BasePagination /** * Returns the Url for the next page. * Returns null if there's no next page. - * - * @return string|null */ public function nextPageUrl(): string|null { @@ -132,9 +124,6 @@ class Pagination extends BasePagination * Returns the URL of the current page. * If the `$page` variable is set, the URL * for that page will be returned. - * - * @param int|null $page - * @return string|null */ public function pageUrl(int $page = null): string|null { @@ -165,8 +154,6 @@ class Pagination extends BasePagination /** * Returns the Url for the previous page. * Returns null if there's no previous page. - * - * @return string|null */ public function prevPageUrl(): string|null { diff --git a/kirby/src/Cms/Permissions.php b/kirby/src/Cms/Permissions.php index 2780a84..20ba4ea 100644 --- a/kirby/src/Cms/Permissions.php +++ b/kirby/src/Cms/Permissions.php @@ -17,15 +17,9 @@ use Kirby\Exception\InvalidArgumentException; */ class Permissions { - /** - * @var array - */ - public static $extendedActions = []; + public static array $extendedActions = []; - /** - * @var array - */ - protected $actions = [ + protected array $actions = [ 'access' => [ 'account' => true, 'languages' => true, @@ -35,18 +29,22 @@ class Permissions 'users' => true, ], 'files' => [ - 'changeName' => true, - 'create' => true, - 'delete' => true, - 'read' => true, - 'replace' => true, - 'update' => true + 'access' => true, + 'changeName' => true, + 'changeTemplate' => true, + 'create' => true, + 'delete' => true, + 'list' => true, + 'read' => true, + 'replace' => true, + 'update' => true ], 'languages' => [ 'create' => true, 'delete' => true ], 'pages' => [ + 'access' => true, 'changeSlug' => true, 'changeStatus' => true, 'changeTemplate' => true, @@ -54,6 +52,8 @@ class Permissions 'create' => true, 'delete' => true, 'duplicate' => true, + 'list' => true, + 'move' => true, 'preview' => true, 'read' => true, 'sort' => true, @@ -87,10 +87,9 @@ class Permissions /** * Permissions constructor * - * @param array $settings * @throws \Kirby\Exception\InvalidArgumentException */ - public function __construct($settings = []) + public function __construct(array|bool|null $settings = []) { // dynamically register the extended actions foreach (static::$extendedActions as $key => $actions) { @@ -110,11 +109,6 @@ class Permissions } } - /** - * @param string|null $category - * @param string|null $action - * @return bool - */ public function for(string $category = null, string $action = null): bool { if ($action === null) { @@ -132,33 +126,26 @@ class Permissions return $this->actions[$category][$action]; } - /** - * @param string $category - * @param string $action - * @return bool - */ protected function hasAction(string $category, string $action): bool { - return $this->hasCategory($category) === true && array_key_exists($action, $this->actions[$category]) === true; + return + $this->hasCategory($category) === true && + array_key_exists($action, $this->actions[$category]) === true; } - /** - * @param string $category - * @return bool - */ protected function hasCategory(string $category): bool { return array_key_exists($category, $this->actions) === true; } /** - * @param string $category - * @param string $action - * @param $setting * @return $this */ - protected function setAction(string $category, string $action, $setting) - { + protected function setAction( + string $category, + string $action, + $setting + ): static { // wildcard to overwrite the entire category if ($action === '*') { return $this->setCategory($category, $setting); @@ -170,10 +157,9 @@ class Permissions } /** - * @param bool $setting * @return $this */ - protected function setAll(bool $setting) + protected function setAll(bool $setting): static { foreach ($this->actions as $categoryName => $actions) { $this->setCategory($categoryName, $setting); @@ -183,10 +169,9 @@ class Permissions } /** - * @param array $settings * @return $this */ - protected function setCategories(array $settings) + protected function setCategories(array $settings): static { foreach ($settings as $categoryName => $categoryActions) { if (is_bool($categoryActions) === true) { @@ -204,12 +189,10 @@ class Permissions } /** - * @param string $category - * @param bool $setting * @return $this * @throws \Kirby\Exception\InvalidArgumentException */ - protected function setCategory(string $category, bool $setting) + protected function setCategory(string $category, bool $setting): static { if ($this->hasCategory($category) === false) { throw new InvalidArgumentException('Invalid permissions category'); @@ -222,9 +205,6 @@ class Permissions return $this; } - /** - * @return array - */ public function toArray(): array { return $this->actions; diff --git a/kirby/src/Cms/Picker.php b/kirby/src/Cms/Picker.php index 7c26ad6..da4b7a5 100644 --- a/kirby/src/Cms/Picker.php +++ b/kirby/src/Cms/Picker.php @@ -14,25 +14,12 @@ namespace Kirby\Cms; */ abstract class Picker { - /** - * @var \Kirby\Cms\App - */ - protected $kirby; - - /** - * @var array - */ - protected $options; - - /** - * @var \Kirby\Cms\Site - */ - protected $site; + protected App $kirby; + protected array $options; + protected Site $site; /** * Creates a new Picker instance - * - * @param array $params */ public function __construct(array $params = []) { @@ -43,8 +30,6 @@ abstract class Picker /** * Return the array of default values - * - * @return array */ protected function defaults(): array { @@ -55,7 +40,7 @@ abstract class Picker // query template for the info field 'info' => false, // listing style: list, cards, cardlets - 'layout' =>'list', + 'layout' => 'list', // number of users displayed per pagination page 'limit' => 20, // optional mapping function for the result array @@ -75,20 +60,15 @@ abstract class Picker /** * Fetches all items for the picker - * - * @return \Kirby\Cms\Collection|null */ - abstract public function items(); + abstract public function items(): Collection|null; /** * Converts all given items to an associative * array that is already optimized for the * panel picker component. - * - * @param \Kirby\Cms\Collection|null $items - * @return array */ - public function itemsToArray($items = null): array + public function itemsToArray(Collection $items = null): array { if ($items === null) { return []; @@ -116,11 +96,8 @@ abstract class Picker /** * Apply pagination to the collection * of items according to the options. - * - * @param \Kirby\Cms\Collection $items - * @return \Kirby\Cms\Collection */ - public function paginate(Collection $items) + public function paginate(Collection $items): Collection { return $items->paginate([ 'limit' => $this->options['limit'], @@ -131,9 +108,6 @@ abstract class Picker /** * Return the most relevant pagination * info as array - * - * @param \Kirby\Cms\Pagination $pagination - * @return array */ public function paginationToArray(Pagination $pagination): array { @@ -147,11 +121,8 @@ abstract class Picker /** * Search through the collection of items * if not deactivate in the options - * - * @param \Kirby\Cms\Collection $items - * @return \Kirby\Cms\Collection */ - public function search(Collection $items) + public function search(Collection $items): Collection { if (empty($this->options['search']) === false) { return $items->search($this->options['search']); @@ -164,8 +135,6 @@ abstract class Picker * Returns an associative array * with all information for the picker. * This will be passed directly to the API. - * - * @return array */ public function toArray(): array { diff --git a/kirby/src/Cms/Plugin.php b/kirby/src/Cms/Plugin.php index 84d1f1d..5a69ccf 100644 --- a/kirby/src/Cms/Plugin.php +++ b/kirby/src/Cms/Plugin.php @@ -23,8 +23,9 @@ use Throwable; * @copyright Bastian Allgeier * @license https://getkirby.com/license */ -class Plugin extends Model +class Plugin { + protected PluginAssets $assets; protected array $extends; protected string $name; protected string $root; @@ -33,21 +34,17 @@ class Plugin extends Model protected array|null $info = null; protected UpdateStatus|null $updateStatus = null; - /** - * Allows access to any composer.json field by method call - */ - public function __call(string $key, array $arguments = null) - { - return $this->info()[$key] ?? null; - } - /** * @param string $name Plugin name within Kirby (`vendor/plugin`) * @param array $extends Associative array of plugin extensions + * + * @throws \Kirby\Exception\InvalidArgumentException If the plugin name has an invalid format */ public function __construct(string $name, array $extends = []) { - $this->setName($name); + static::validateName($name); + + $this->name = $name; $this->extends = $extends; $this->root = $extends['root'] ?? dirname(debug_backtrace()[0]['file']); $this->info = empty($extends['info']) === false && is_array($extends['info']) ? $extends['info'] : null; @@ -55,6 +52,30 @@ class Plugin extends Model unset($this->extends['root'], $this->extends['info']); } + /** + * Allows access to any composer.json field by method call + */ + public function __call(string $key, array $arguments = null): mixed + { + return $this->info()[$key] ?? null; + } + + /** + * Returns the plugin asset object for a specific asset + */ + public function asset(string $path): PluginAsset|null + { + return $this->assets()->get($path); + } + + /** + * Returns the plugin assets collection + */ + public function assets(): PluginAssets + { + return $this->assets ??= PluginAssets::factory($this); + } + /** * Returns the array with author information * from the composer.json file @@ -114,6 +135,14 @@ class Plugin extends Model return $this->info = $info; } + /** + * Current $kirby instance + */ + public function kirby(): App + { + return App::instance(); + } + /** * Returns the link to the plugin homepage */ @@ -185,23 +214,6 @@ class Plugin extends Model return $this->root; } - /** - * Validates and sets the plugin name - * - * @return $this - * - * @throws \Kirby\Exception\InvalidArgumentException If the plugin name has an invalid format - */ - protected function setName(string $name): static - { - if (preg_match('!^[a-z0-9-]+\/[a-z0-9-]+$!i', $name) !== 1) { - throw new InvalidArgumentException('The plugin name must follow the format "a-z0-9-/a-z0-9-"'); - } - - $this->name = $name; - return $this; - } - /** * Returns all available plugin metadata */ @@ -264,6 +276,19 @@ class Plugin extends Model return $this->updateStatus = new UpdateStatus($this, false, $data); } + /** + * Checks if the name follows the required pattern + * and throws an exception if not + * + * @throws \Kirby\Exception\InvalidArgumentException + */ + public static function validateName(string $name): void + { + if (preg_match('!^[a-z0-9-]+\/[a-z0-9-]+$!i', $name) !== 1) { + throw new InvalidArgumentException('The plugin name must follow the format "a-z0-9-/a-z0-9-"'); + } + } + /** * Returns the normalized version number * from the composer.json file diff --git a/kirby/src/Cms/PluginAsset.php b/kirby/src/Cms/PluginAsset.php new file mode 100644 index 0000000..f5f7f69 --- /dev/null +++ b/kirby/src/Cms/PluginAsset.php @@ -0,0 +1,120 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier + * @license https://getkirby.com/license + */ +class PluginAsset +{ + public function __construct( + protected string $path, + protected string $root, + protected Plugin $plugin + ) { + } + + public function extension(): string + { + return F::extension($this->path()); + } + + public function filename(): string + { + return F::filename($this->path()); + } + + /** + * Create a unique media hash + */ + public function mediaHash(): string + { + return crc32($this->filename()) . '-' . $this->modified(); + } + + /** + * Absolute path to the asset file in the media folder + */ + public function mediaRoot(): string + { + return $this->plugin()->mediaRoot() . '/' . $this->mediaHash() . '/' . $this->path(); + } + + /** + * Public accessible url path for the asset + */ + public function mediaUrl(): string + { + return $this->plugin()->mediaUrl() . '/' . $this->mediaHash() . '/' . $this->path(); + } + + /** + * Timestamp when asset file was last modified + */ + public function modified(): int|false + { + return F::modified($this->root()); + } + + public function path(): string + { + return $this->path; + } + + public function plugin(): Plugin + { + return $this->plugin; + } + + /** + * Publishes the asset file to the plugin's media folder + * by creating a symlink + */ + public function publish(): void + { + F::link($this->root(), $this->mediaRoot(), 'symlink'); + } + + /** + * @internal + * @since 4.0.0 + * @deprecated 4.0.0 + * @codeCoverageIgnore + */ + public function publishAt(string $path): void + { + $media = $this->plugin()->mediaRoot() . '/' . $path; + F::link($this->root(), $media, 'symlink'); + } + + public function root(): string + { + return $this->root; + } + + /** + * @see ::mediaUrl + */ + public function url(): string + { + return $this->mediaUrl(); + } + + /** + * @see ::url + */ + public function __toString(): string + { + return $this->url(); + } +} diff --git a/kirby/src/Cms/PluginAssets.php b/kirby/src/Cms/PluginAssets.php index a05ffea..45c415f 100644 --- a/kirby/src/Cms/PluginAssets.php +++ b/kirby/src/Cms/PluginAssets.php @@ -2,9 +2,11 @@ namespace Kirby\Cms; +use Closure; use Kirby\Filesystem\Dir; use Kirby\Filesystem\F; use Kirby\Http\Response; +use Kirby\Toolkit\Str; /** * Plugin assets are automatically copied/linked @@ -13,65 +15,167 @@ use Kirby\Http\Response; * * @package Kirby Cms * @author Bastian Allgeier + * @author Nico Hoffmann * @link https://getkirby.com * @copyright Bastian Allgeier * @license https://getkirby.com/license */ -class PluginAssets +class PluginAssets extends Collection { /** * Clean old/deprecated assets on every resolve - * - * @param string $pluginName - * @return void */ public static function clean(string $pluginName): void { if ($plugin = App::instance()->plugin($pluginName)) { - $root = $plugin->root() . '/assets'; $media = $plugin->mediaRoot(); - $assets = Dir::index($media, true); + $assets = $plugin->assets(); - foreach ($assets as $asset) { - $original = $root . '/' . $asset; + // get all media files + $files = Dir::index($media, true); - if (file_exists($original) === false) { - $assetRoot = $media . '/' . $asset; + // get all active assets' paths from the plugin + $active = $assets->values( + function ($asset) { + $path = $asset->mediaHash() . '/' . $asset->path(); + $paths = []; + $parts = explode('/', $path); - if (is_file($assetRoot) === true) { - F::remove($assetRoot); - } else { - Dir::remove($assetRoot); + // collect all path segments + // (e.g. foo/, foo/bar/, foo/bar/baz.css) for the asset + for ($i = 1, $max = count($parts); $i <= $max; $i++) { + $paths[] = implode('/', array_slice($parts, 0, $i)); + + // TODO: remove when media hash is enforced as mandatory + $paths[] = implode('/', array_slice($parts, 1, $i)); } + + return $paths; + } + ); + + // flatten the array and remove duplicates + $active = array_unique(array_merge(...array_values($active))); + + // get outdated media files by comparing all + // files in the media folder against the set of asset paths + $stale = array_diff($files, $active); + + foreach ($stale as $file) { + $root = $media . '/' . $file; + + if (is_file($root) === true) { + F::remove($root); + } else { + Dir::remove($root); } } } } + /** + * Filters assets collection by CSS files + */ + public function css(): static + { + return $this->filter(fn ($asset) => $asset->extension() === 'css'); + } + + /** + * Creates a new collection for the plugin's assets + * by considering the plugin's `asset` extension + * (and `assets` directory as fallback) + */ + public static function factory(Plugin $plugin): static + { + // get assets defined in the plugin extension + if ($assets = $plugin->extends()['assets'] ?? null) { + if ($assets instanceof Closure) { + $assets = $assets(); + } + + // normalize array: use relative path as + // key when no key is defined + foreach ($assets as $key => $root) { + if (is_int($key) === true) { + unset($assets[$key]); + $path = Str::after($root, $plugin->root() . '/'); + $assets[$path] = $root; + } + } + } + + // fallback: if no assets are defined in the plugin extension, + // use all files in the plugin's `assets` directory + if ($assets === null) { + $assets = []; + $root = $plugin->root() . '/assets'; + + foreach (Dir::index($root, true) as $path) { + if (is_file($root . '/' . $path) === true) { + $assets[$path] = $root . '/' . $path; + } + } + } + + $collection = new static([], $plugin); + + foreach ($assets as $path => $root) { + $collection->data[$path] = new PluginAsset($path, $root, $plugin); + } + + return $collection; + } + + /** + * Filters assets collection by JavaScript files + */ + public function js(): static + { + return $this->filter(fn ($asset) => $asset->extension() === 'js'); + } + + public function plugin(): Plugin + { + return $this->parent; + } + /** * Create a symlink for a plugin asset and * return the public URL - * - * @param string $pluginName - * @param string $filename - * @return \Kirby\Cms\Response|null */ - public static function resolve(string $pluginName, string $filename) - { + public static function resolve( + string $pluginName, + string $hash, + string $path + ): Response|null { if ($plugin = App::instance()->plugin($pluginName)) { - $source = $plugin->root() . '/assets/' . $filename; + // do some spring cleaning for older files + static::clean($pluginName); - if (F::exists($source, $plugin->root()) === true) { - // do some spring cleaning for older files - static::clean($pluginName); + // @codeCoverageIgnoreStart + // TODO: deprecated media URL without hash + if (empty($hash) === true) { + $asset = $plugin->asset($path); + $asset->publishAt($path); + return Response::file($asset->root()); + } - $target = $plugin->mediaRoot() . '/' . $filename; + // TODO: deprecated media URL with hash (but path) + if ($asset = $plugin->asset($hash . '/' . $path)) { + $asset->publishAt($hash . '/' . $path); + return Response::file($asset->root()); + } + // @codeCoverageIgnoreEnd - // create a symlink if possible - F::link($source, $target, 'symlink'); + if ($asset = $plugin->asset($path)) { + if ($asset->mediaHash() === $hash) { + // create a symlink if possible + $asset->publish(); - // return the file response - return Response::file($source); + // return the file response + return Response::file($asset->root()); + } } } diff --git a/kirby/src/Cms/R.php b/kirby/src/Cms/R.php index 5ef5df9..312fc2b 100644 --- a/kirby/src/Cms/R.php +++ b/kirby/src/Cms/R.php @@ -2,6 +2,7 @@ namespace Kirby\Cms; +use Kirby\Http\Request; use Kirby\Toolkit\Facade; /** @@ -15,10 +16,7 @@ use Kirby\Toolkit\Facade; */ class R extends Facade { - /** - * @return \Kirby\Http\Request - */ - public static function instance() + public static function instance(): Request { return App::instance()->request(); } diff --git a/kirby/src/Cms/Responder.php b/kirby/src/Cms/Responder.php index 49640fd..e99ce56 100644 --- a/kirby/src/Cms/Responder.php +++ b/kirby/src/Cms/Responder.php @@ -20,68 +20,50 @@ class Responder /** * Timestamp when the response expires * in Kirby's cache - * - * @var int|null */ - protected $expires = null; + protected int|null $expires = null; /** * HTTP status code - * - * @var int */ - protected $code = null; + protected int|null $code = null; /** * Response body - * - * @var string */ - protected $body = null; + protected string|null $body = null; /** * Flag that defines whether the current * response can be cached by Kirby's cache - * - * @var bool */ - protected $cache = true; + protected bool $cache = true; /** * HTTP headers - * - * @var array */ - protected $headers = []; + protected array $headers = []; /** * Content type - * - * @var string */ - protected $type = null; + protected string|null $type = null; /** * Flag that defines whether the current * response uses the HTTP `Authorization` * request header - * - * @var bool */ - protected $usesAuth = false; + protected bool $usesAuth = false; /** * List of cookie names the response * relies on - * - * @var array */ - protected $usesCookies = []; + protected array $usesCookies = []; /** * Creates and sends the response - * - * @return string */ public function __toString(): string { @@ -91,10 +73,9 @@ class Responder /** * Setter and getter for the response body * - * @param string|null $body - * @return string|$this + * @return $this|string|null */ - public function body(string $body = null) + public function body(string $body = null): static|string|null { if ($body === null) { return $this->body; @@ -110,10 +91,9 @@ class Responder * by Kirby's cache * @since 3.5.5 * - * @param bool|null $cache * @return bool|$this */ - public function cache(bool|null $cache = null) + public function cache(bool|null $cache = null): bool|static { if ($cache === null) { // never ever cache private responses @@ -134,10 +114,9 @@ class Responder * `Authorization` request header * @since 3.7.0 * - * @param bool|null $usesAuth * @return bool|$this */ - public function usesAuth(bool|null $usesAuth = null) + public function usesAuth(bool|null $usesAuth = null): bool|static { if ($usesAuth === null) { return $this->usesAuth; @@ -151,9 +130,6 @@ class Responder * Setter for a cookie name that is * used by the response * @since 3.7.0 - * - * @param string $name - * @return void */ public function usesCookie(string $name): void { @@ -168,7 +144,6 @@ class Responder * names the response relies on * @since 3.7.0 * - * @param array|null $usesCookies * @return array|$this */ public function usesCookies(array|null $usesCookies = null) @@ -233,7 +208,6 @@ class Responder /** * Setter and getter for the status code * - * @param int|null $code * @return int|$this */ public function code(int $code = null) @@ -248,8 +222,6 @@ class Responder /** * Construct response from an array - * - * @param array $response */ public function fromArray(array $response): void { @@ -266,7 +238,6 @@ class Responder /** * Setter and getter for a single header * - * @param string $key * @param string|false|null $value * @param bool $lazy If `true`, an existing header value is not overridden * @return string|$this @@ -293,7 +264,6 @@ class Responder /** * Setter and getter for all headers * - * @param array|null $headers * @return array|$this */ public function headers(array $headers = null) @@ -333,7 +303,6 @@ class Responder /** * Shortcut to configure a json response * - * @param array|null $json * @return string|$this */ public function json(array $json = null) @@ -348,12 +317,12 @@ class Responder /** * Shortcut to create a redirect response * - * @param string|null $location - * @param int|null $code * @return $this */ - public function redirect(string|null $location = null, int|null $code = null) - { + public function redirect( + string|null $location = null, + int|null $code = null + ) { $location = Url::to($location ?? '/'); $location = Url::unIdn($location); @@ -364,11 +333,8 @@ class Responder /** * Creates and returns the response object from the config - * - * @param string|null $body - * @return \Kirby\Cms\Response */ - public function send(string $body = null) + public function send(string $body = null): Response { if ($body !== null) { $this->body($body); @@ -380,8 +346,6 @@ class Responder /** * Converts the response configuration * to an array - * - * @return array */ public function toArray(): array { @@ -399,7 +363,6 @@ class Responder /** * Setter and getter for the content type * - * @param string|null $type * @return string|$this */ public function type(string $type = null) @@ -423,10 +386,6 @@ class Responder * is actually used/relied on by the response * @since 3.7.0 * @internal - * - * @param bool $usesAuth - * @param array $usesCookies - * @return bool */ public static function isPrivate(bool $usesAuth, array $usesCookies): bool { diff --git a/kirby/src/Cms/Response.php b/kirby/src/Cms/Response.php index 804ec43..5a21bc6 100644 --- a/kirby/src/Cms/Response.php +++ b/kirby/src/Cms/Response.php @@ -19,8 +19,10 @@ class Response extends \Kirby\Http\Response * parses locations with the Url::to method * first. */ - public static function redirect(string $location = '/', int $code = 302): static - { + public static function redirect( + string $location = '/', + int $code = 302 + ): static { return parent::redirect(Url::to($location), $code); } } diff --git a/kirby/src/Cms/Role.php b/kirby/src/Cms/Role.php index 1860690..3579b62 100644 --- a/kirby/src/Cms/Role.php +++ b/kirby/src/Cms/Role.php @@ -17,41 +17,38 @@ use Kirby\Toolkit\I18n; * @copyright Bastian Allgeier * @license https://getkirby.com/license */ -class Role extends Model +class Role { - protected $description; - protected $name; - protected $permissions; - protected $title; + protected string|null $description; + protected string $name; + protected Permissions $permissions; + protected string|null $title; public function __construct(array $props) { - $this->setProperties($props); + $this->name = $props['name']; + $this->permissions = new Permissions($props['permissions'] ?? null); + $title = $props['title'] ?? null; + $this->title = I18n::translate($title) ?? $title; + $description = $props['description'] ?? null; + $this->description = I18n::translate($description) ?? $description; } /** * Improved `var_dump` output - * - * @return array + * @codeCoverageIgnore */ public function __debugInfo(): array { return $this->toArray(); } - /** - * @return string - */ public function __toString(): string { return $this->name(); } - /** - * @param array $inject - * @return static - */ - public static function admin(array $inject = []) + public static function admin(array $inject = []): static { try { return static::load('admin'); @@ -60,9 +57,6 @@ class Role extends Model } } - /** - * @return array - */ protected static function defaults(): array { return [ @@ -81,54 +75,32 @@ class Role extends Model ]; } - /** - * @return mixed - */ - public function description() + public function description(): string|null { return $this->description; } - /** - * @param array $props - * @param array $inject - * @return static - */ - public static function factory(array $props, array $inject = []) + public static function factory(array $props, array $inject = []): static { return new static($props + $inject); } - /** - * @return string - */ public function id(): string { return $this->name(); } - /** - * @return bool - */ public function isAdmin(): bool { return $this->name() === 'admin'; } - /** - * @return bool - */ public function isNobody(): bool { return $this->name() === 'nobody'; } - /** - * @param string $file - * @param array $inject - * @return static - */ - public static function load(string $file, array $inject = []) + public static function load(string $file, array $inject = []): static { $data = Data::read($file); $data['name'] = F::name($file); @@ -136,19 +108,12 @@ class Role extends Model return static::factory($data, $inject); } - /** - * @return string - */ public function name(): string { return $this->name; } - /** - * @param array $inject - * @return static - */ - public static function nobody(array $inject = []) + public static function nobody(array $inject = []): static { try { return static::load('nobody'); @@ -157,57 +122,11 @@ class Role extends Model } } - /** - * @return \Kirby\Cms\Permissions - */ - public function permissions() + public function permissions(): Permissions { return $this->permissions; } - /** - * @param mixed $description - * @return $this - */ - protected function setDescription($description = null) - { - $this->description = I18n::translate($description, $description); - return $this; - } - - /** - * @param string $name - * @return $this - */ - protected function setName(string $name) - { - $this->name = $name; - return $this; - } - - /** - * @param mixed $permissions - * @return $this - */ - protected function setPermissions($permissions = null) - { - $this->permissions = new Permissions($permissions); - return $this; - } - - /** - * @param mixed $title - * @return $this - */ - protected function setTitle($title = null) - { - $this->title = I18n::translate($title, $title); - return $this; - } - - /** - * @return string - */ public function title(): string { return $this->title ??= ucfirst($this->name()); @@ -216,8 +135,6 @@ class Role extends Model /** * Converts the most important role * properties to an array - * - * @return array */ public function toArray(): array { diff --git a/kirby/src/Cms/Roles.php b/kirby/src/Cms/Roles.php index f4d37cd..11be6c9 100644 --- a/kirby/src/Cms/Roles.php +++ b/kirby/src/Cms/Roles.php @@ -20,10 +20,8 @@ class Roles extends Collection { /** * All registered roles methods - * - * @var array */ - public static $methods = []; + public static array $methods = []; /** * Returns a filtered list of all @@ -33,7 +31,7 @@ class Roles extends Collection * @return $this|static * @throws \Exception */ - public function canBeChanged() + public function canBeChanged(): static { if (App::instance()->user()) { return $this->filter(function ($role) { @@ -57,7 +55,7 @@ class Roles extends Collection * @return $this|static * @throws \Exception */ - public function canBeCreated() + public function canBeCreated(): static { if (App::instance()->user()) { return $this->filter(function ($role) { @@ -73,12 +71,7 @@ class Roles extends Collection return $this; } - /** - * @param array $roles - * @param array $inject - * @return static - */ - public static function factory(array $roles, array $inject = []) + public static function factory(array $roles, array $inject = []): static { $collection = new static(); @@ -97,12 +90,7 @@ class Roles extends Collection return $collection->sort('name', 'asc'); } - /** - * @param string|null $root - * @param array $inject - * @return static - */ - public static function load(string $root = null, array $inject = []) + public static function load(string $root = null, array $inject = []): static { $kirby = App::instance(); $roles = new static(); diff --git a/kirby/src/Cms/S.php b/kirby/src/Cms/S.php index cced071..260ee30 100644 --- a/kirby/src/Cms/S.php +++ b/kirby/src/Cms/S.php @@ -2,6 +2,7 @@ namespace Kirby\Cms; +use Kirby\Session\Session; use Kirby\Toolkit\Facade; /** @@ -15,10 +16,7 @@ use Kirby\Toolkit\Facade; */ class S extends Facade { - /** - * @return \Kirby\Session\Session - */ - public static function instance() + public static function instance(): Session { return App::instance()->session(); } diff --git a/kirby/src/Cms/Search.php b/kirby/src/Cms/Search.php index 0169b40..14add5c 100644 --- a/kirby/src/Cms/Search.php +++ b/kirby/src/Cms/Search.php @@ -16,47 +16,36 @@ namespace Kirby\Cms; */ class Search { - /** - * @param string|null $query - * @param array $params - * @return \Kirby\Cms\Files - */ - public static function files(string $query = null, $params = []) - { + public static function files( + string $query = null, + array $params = [] + ): Files { return App::instance()->site()->index()->files()->search($query, $params); } /** * Native search method to search for anything within the collection - * - * @param \Kirby\Cms\Collection $collection - * @param string|null $query - * @param mixed $params - * @return \Kirby\Cms\Collection|bool */ - public static function collection(Collection $collection, string $query = null, $params = []) - { + public static function collection( + Collection $collection, + string|null $query = null, + string|array $params = [] + ): Collection { $kirby = App::instance(); return ($kirby->component('search'))($kirby, $collection, $query, $params); } - /** - * @param string|null $query - * @param array $params - * @return \Kirby\Cms\Pages - */ - public static function pages(string $query = null, $params = []) - { + public static function pages( + string $query = null, + array $params = [] + ): Pages { return App::instance()->site()->index()->search($query, $params); } - /** - * @param string|null $query - * @param array $params - * @return \Kirby\Cms\Users - */ - public static function users(string $query = null, $params = []) - { + public static function users( + string $query = null, + array $params = [] + ): Users { return App::instance()->users()->search($query, $params); } } diff --git a/kirby/src/Cms/Section.php b/kirby/src/Cms/Section.php index 51ea070..56402be 100644 --- a/kirby/src/Cms/Section.php +++ b/kirby/src/Cms/Section.php @@ -18,24 +18,15 @@ class Section extends Component { /** * Registry for all component mixins - * - * @var array */ - public static $mixins = []; + public static array $mixins = []; /** * Registry for all component types - * - * @var array */ - public static $types = []; - + public static array $types = []; /** - * Section constructor. - * - * @param string $type - * @param array $attrs * @throws \Kirby\Exception\InvalidArgumentException */ public function __construct(string $type, array $attrs = []) @@ -44,7 +35,7 @@ class Section extends Component throw new InvalidArgumentException('Undefined section model'); } - if ($attrs['model'] instanceof Model === false) { + if ($attrs['model'] instanceof ModelWithContent === false) { throw new InvalidArgumentException('Invalid section model'); } @@ -64,25 +55,16 @@ class Section extends Component return $this->errors ?? []; } - /** - * @return \Kirby\Cms\App - */ - public function kirby() + public function kirby(): App { return $this->model()->kirby(); } - /** - * @return \Kirby\Cms\Model - */ - public function model() + public function model(): ModelWithContent { return $this->model; } - /** - * @return array - */ public function toArray(): array { $array = parent::toArray(); @@ -92,9 +74,6 @@ class Section extends Component return $array; } - /** - * @return array - */ public function toResponse(): array { return array_merge([ diff --git a/kirby/src/Cms/Site.php b/kirby/src/Cms/Site.php index b27bdc3..cab166d 100644 --- a/kirby/src/Cms/Site.php +++ b/kirby/src/Cms/Site.php @@ -22,87 +22,83 @@ use Kirby\Toolkit\A; */ class Site extends ModelWithContent { - use SiteActions; use HasChildren; use HasFiles; use HasMethods; + use SiteActions; public const CLASS_ALIAS = 'site'; /** * The SiteBlueprint object - * - * @var \Kirby\Cms\SiteBlueprint */ - protected $blueprint; + protected SiteBlueprint|null $blueprint = null; /** * The error page object - * - * @var \Kirby\Cms\Page */ - protected $errorPage; + protected Page|null $errorPage = null; /** * The id of the error page, which is * fetched in the errorPage method - * - * @var string */ - protected $errorPageId = 'error'; + protected string $errorPageId; /** * The home page object - * - * @var \Kirby\Cms\Page */ - protected $homePage; + protected Page|null $homePage = null; /** * The id of the home page, which is * fetched in the errorPage method - * - * @var string */ - protected $homePageId = 'home'; + protected string $homePageId; /** * Cache for the inventory array - * - * @var array */ - protected $inventory; + protected array|null $inventory = null; /** * The current page object - * - * @var \Kirby\Cms\Page */ - protected $page; + protected Page|null $page; /** * The absolute path to the site directory - * - * @var string */ - protected $root; + protected string $root; /** * The page url - * - * @var string */ - protected $url; + protected string|null $url; + + /** + * Creates a new Site object + */ + public function __construct(array $props = []) + { + parent::__construct($props); + + $this->errorPageId = $props['errorPageId'] ?? 'error'; + $this->homePageId = $props['homePageId'] ?? 'home'; + $this->page = $props['page'] ?? null; + $this->url = $props['url'] ?? null; + + $this->setBlueprint($props['blueprint'] ?? null); + $this->setChildren($props['children'] ?? null); + $this->setDrafts($props['drafts'] ?? null); + $this->setFiles($props['files'] ?? null); + } /** * Modified getter to also return fields * from the content - * - * @param string $method - * @param array $arguments - * @return mixed */ - public function __call(string $method, array $arguments = []) + public function __call(string $method, array $arguments = []): mixed { // public property access if (isset($this->$method) === true) { @@ -118,20 +114,9 @@ class Site extends ModelWithContent return $this->content()->get($method); } - /** - * Creates a new Site object - * - * @param array $props - */ - public function __construct(array $props = []) - { - $this->setProperties($props); - } - /** * Improved `var_dump` output - * - * @return array + * @codeCoverageIgnore */ public function __debugInfo(): array { @@ -145,8 +130,6 @@ class Site extends ModelWithContent /** * Makes it possible to convert the site model * to a string. Mostly useful for debugging. - * - * @return string */ public function __toString(): string { @@ -155,10 +138,7 @@ class Site extends ModelWithContent /** * Returns the url to the api endpoint - * * @internal - * @param bool $relative - * @return string */ public function apiUrl(bool $relative = false): string { @@ -171,10 +151,8 @@ class Site extends ModelWithContent /** * Returns the blueprint object - * - * @return \Kirby\Cms\SiteBlueprint */ - public function blueprint() + public function blueprint(): SiteBlueprint { if ($this->blueprint instanceof SiteBlueprint) { return $this->blueprint; @@ -185,10 +163,8 @@ class Site extends ModelWithContent /** * Builds a breadcrumb collection - * - * @return \Kirby\Cms\Pages */ - public function breadcrumb() + public function breadcrumb(): Pages { // get all parents and flip the order $crumb = $this->page()->parents()->flip(); @@ -204,53 +180,39 @@ class Site extends ModelWithContent /** * Prepares the content for the write method - * * @internal - * @param array $data - * @param string|null $languageCode - * @return array */ - public function contentFileData(array $data, string|null $languageCode = null): array - { - return A::prepend($data, [ - 'title' => $data['title'] ?? null, - ]); + public function contentFileData( + array $data, + string|null $languageCode = null + ): array { + return A::prepend($data, ['title' => $data['title'] ?? null]); } /** * Filename for the content file - * * @internal - * @return string + * @deprecated 4.0.0 + * @todo Remove in v5 + * @codeCoverageIgnore */ public function contentFileName(): string { + Helpers::deprecated('The internal $model->contentFileName() method has been deprecated. Please let us know via a GitHub issue if you need this method and tell us your use case.', 'model-content-file'); return 'site'; } /** * Returns the error page object - * - * @return \Kirby\Cms\Page|null */ - public function errorPage() + public function errorPage(): Page|null { - if ($this->errorPage instanceof Page) { - return $this->errorPage; - } - - if ($error = $this->find($this->errorPageId())) { - return $this->errorPage = $error; - } - - return null; + return $this->errorPage ??= $this->find($this->errorPageId()); } /** * Returns the global error page id - * * @internal - * @return string */ public function errorPageId(): string { @@ -259,8 +221,6 @@ class Site extends ModelWithContent /** * Checks if the site exists on disk - * - * @return bool */ public function exists(): bool { @@ -269,27 +229,15 @@ class Site extends ModelWithContent /** * Returns the home page object - * - * @return \Kirby\Cms\Page|null */ - public function homePage() + public function homePage(): Page|null { - if ($this->homePage instanceof Page) { - return $this->homePage; - } - - if ($home = $this->find($this->homePageId())) { - return $this->homePage = $home; - } - - return null; + return $this->homePage ??= $this->find($this->homePageId()); } /** * Returns the global home page id - * * @internal - * @return string */ public function homePageId(): string { @@ -299,9 +247,7 @@ class Site extends ModelWithContent /** * Creates an inventory of all files * and children in the site directory - * * @internal - * @return array */ public function inventory(): array { @@ -323,7 +269,6 @@ class Site extends ModelWithContent * Compares the current object with the given site object * * @param mixed $site - * @return bool */ public function is($site): bool { @@ -336,9 +281,7 @@ class Site extends ModelWithContent /** * Returns the root to the media folder for the site - * * @internal - * @return string */ public function mediaRoot(): string { @@ -347,9 +290,7 @@ class Site extends ModelWithContent /** * The site's base url for any files - * * @internal - * @return string */ public function mediaUrl(): string { @@ -359,18 +300,12 @@ class Site extends ModelWithContent /** * Gets the last modification date of all pages * in the content folder. - * - * @param string|null $format - * @param string|null $handler - * @return int|string */ - public function modified(string|null $format = null, string|null $handler = null) - { - return Dir::modified( - $this->root(), - $format, - $handler ?? $this->kirby()->option('date.handler', 'date') - ); + public function modified( + string|null $format = null, + string|null $handler = null + ): int|string { + return Dir::modified($this->root(), $format, $handler); } /** @@ -384,9 +319,8 @@ class Site extends ModelWithContent * * @param string|null $path omit for current page, * otherwise e.g. `notes/across-the-ocean` - * @return \Kirby\Cms\Page|null */ - public function page(string|null $path = null) + public function page(string|null $path = null): Page|null { if ($path !== null) { return $this->find($path); @@ -405,39 +339,31 @@ class Site extends ModelWithContent /** * Alias for `Site::children()` - * - * @return \Kirby\Cms\Pages */ - public function pages() + public function pages(): Pages { return $this->children(); } /** * Returns the panel info object - * - * @return \Kirby\Panel\Site */ - public function panel() + public function panel(): Panel { return new Panel($this); } /** * Returns the permissions object for this site - * - * @return \Kirby\Cms\SitePermissions */ - public function permissions() + public function permissions(): SitePermissions { return new SitePermissions($this); } /** * Preview Url - * * @internal - * @return string|null */ public function previewUrl(): string|null { @@ -458,8 +384,6 @@ class Site extends ModelWithContent /** * Returns the absolute path to the content directory - * - * @return string */ public function root(): string { @@ -470,22 +394,16 @@ class Site extends ModelWithContent * Returns the SiteRules class instance * which is being used in various methods * to check for valid actions and input. - * - * @return \Kirby\Cms\SiteRules */ - protected function rules() + protected function rules(): SiteRules { return new SiteRules(); } /** * Search all pages in the site - * - * @param string|null $query - * @param array $params - * @return \Kirby\Cms\Pages */ - public function search(string|null $query = null, $params = []) + public function search(string|null $query = null, string|array $params = []): Pages { return $this->index()->search($query, $params); } @@ -493,10 +411,9 @@ class Site extends ModelWithContent /** * Sets the Blueprint object * - * @param array|null $blueprint * @return $this */ - protected function setBlueprint(array|null $blueprint = null) + protected function setBlueprint(array|null $blueprint = null): static { if ($blueprint !== null) { $blueprint['model'] = $this; @@ -506,86 +423,25 @@ class Site extends ModelWithContent return $this; } - /** - * Sets the id of the error page, which - * is used in the errorPage method - * to get the default error page if nothing - * else is set. - * - * @param string $id - * @return $this - */ - protected function setErrorPageId(string $id = 'error') - { - $this->errorPageId = $id; - return $this; - } - - /** - * Sets the id of the home page, which - * is used in the homePage method - * to get the default home page if nothing - * else is set. - * - * @param string $id - * @return $this - */ - protected function setHomePageId(string $id = 'home') - { - $this->homePageId = $id; - return $this; - } - - /** - * Sets the current page object - * - * @internal - * @param \Kirby\Cms\Page|null $page - * @return $this - */ - public function setPage(?Page $page = null) - { - $this->page = $page; - return $this; - } - - /** - * Sets the Url - * - * @param string|null $url - * @return $this - */ - protected function setUrl(string|null $url = null) - { - $this->url = $url; - return $this; - } - /** * Converts the most important site * properties to an array - * - * @return array */ public function toArray(): array { - return [ - 'children' => $this->children()->keys(), - 'content' => $this->content()->toArray(), - 'errorPage' => $this->errorPage() ? $this->errorPage()->id() : false, - 'files' => $this->files()->keys(), - 'homePage' => $this->homePage() ? $this->homePage()->id() : false, - 'page' => $this->page() ? $this->page()->id() : false, - 'title' => $this->title()->value(), - 'url' => $this->url(), - ]; + return array_merge(parent::toArray(), [ + 'children' => $this->children()->keys(), + 'errorPage' => $this->errorPage()?->id() ?? false, + 'files' => $this->files()->keys(), + 'homePage' => $this->homePage()?->id() ?? false, + 'page' => $this->page()?->id() ?? false, + 'title' => $this->title()->value(), + 'url' => $this->url(), + ]); } /** * Returns the Url - * - * @param string|null $language - * @return string */ public function url(string|null $language = null): string { @@ -598,14 +454,12 @@ class Site extends ModelWithContent /** * Returns the translated url - * * @internal - * @param string|null $languageCode - * @param array|null $options - * @return string */ - public function urlForLanguage(string|null $languageCode = null, array|null $options = null): string - { + public function urlForLanguage( + string|null $languageCode = null, + array|null $options = null + ): string { if ($language = $this->kirby()->language($languageCode)) { return $language->url(); } @@ -617,21 +471,19 @@ class Site extends ModelWithContent * Sets the current page by * id or page object and * returns the current page - * * @internal - * @param string|\Kirby\Cms\Page $page - * @param string|null $languageCode - * @return \Kirby\Cms\Page */ - public function visit($page, string|null $languageCode = null) - { + public function visit( + string|Page $page, + string|null $languageCode = null + ): Page { if ($languageCode !== null) { $this->kirby()->setCurrentTranslation($languageCode); $this->kirby()->setCurrentLanguage($languageCode); } // convert ids to a Page object - if (is_string($page)) { + if (is_string($page) === true) { $page = $this->find($page); } @@ -640,22 +492,16 @@ class Site extends ModelWithContent throw new InvalidArgumentException('Invalid page object'); } - // set the current active page - $this->setPage($page); - - // return the page - return $page; + // set and return the current active page + return $this->page = $page; } /** * Checks if any content of the site has been * modified after the given unix timestamp * This is mainly used to auto-update the cache - * - * @param mixed $time - * @return bool */ - public function wasModifiedAfter($time): bool + public function wasModifiedAfter(int $time): bool { return Dir::wasModifiedAfter($this->root(), $time); } diff --git a/kirby/src/Cms/SiteActions.php b/kirby/src/Cms/SiteActions.php index dd1ceb9..8f86eb4 100644 --- a/kirby/src/Cms/SiteActions.php +++ b/kirby/src/Cms/SiteActions.php @@ -23,14 +23,12 @@ trait SiteActions * 3. commits the store action * 4. sends the after hook * 5. returns the result - * - * @param string $action - * @param mixed ...$arguments - * @param Closure $callback - * @return mixed */ - protected function commit(string $action, array $arguments, Closure $callback) - { + protected function commit( + string $action, + array $arguments, + Closure $callback + ): mixed { $old = $this->hardcopy(); $kirby = $this->kirby(); $argumentValues = array_values($arguments); @@ -48,14 +46,12 @@ trait SiteActions /** * Change the site title - * - * @param string $title - * @param string|null $languageCode - * @return static */ - public function changeTitle(string $title, string $languageCode = null) - { - $site = $this; + public function changeTitle( + string $title, + string $languageCode = null + ): static { + $site = $this; $title = trim($title); $arguments = compact('site', 'title', 'languageCode'); @@ -66,11 +62,8 @@ trait SiteActions /** * Creates a main page - * - * @param array $props - * @return \Kirby\Cms\Page */ - public function createChild(array $props) + public function createChild(array $props): Page { $props = array_merge($props, [ 'url' => null, @@ -87,14 +80,16 @@ trait SiteActions * * @return $this */ - public function purge() + public function purge(): static { - $this->blueprint = null; - $this->children = null; - $this->content = null; - $this->files = null; - $this->inventory = null; - $this->translations = null; + parent::purge(); + + $this->blueprint = null; + $this->children = null; + $this->childrenAndDrafts = null; + $this->drafts = null; + $this->files = null; + $this->inventory = null; return $this; } diff --git a/kirby/src/Cms/SiteBlueprint.php b/kirby/src/Cms/SiteBlueprint.php index ce38ab9..5f8b751 100644 --- a/kirby/src/Cms/SiteBlueprint.php +++ b/kirby/src/Cms/SiteBlueprint.php @@ -17,8 +17,6 @@ class SiteBlueprint extends Blueprint /** * Creates a new page blueprint object * with the given props - * - * @param array $props */ public function __construct(array $props) { @@ -44,10 +42,8 @@ class SiteBlueprint extends Blueprint * The preview setting controls the "Open" * button in the panel and redirects it to a * different URL if necessary. - * - * @return string|bool */ - public function preview() + public function preview(): string|bool { $preview = $this->props['options']['preview'] ?? true; diff --git a/kirby/src/Cms/SitePermissions.php b/kirby/src/Cms/SitePermissions.php index b6ce350..8e58415 100644 --- a/kirby/src/Cms/SitePermissions.php +++ b/kirby/src/Cms/SitePermissions.php @@ -13,5 +13,5 @@ namespace Kirby\Cms; */ class SitePermissions extends ModelPermissions { - protected $category = 'site'; + protected string $category = 'site'; } diff --git a/kirby/src/Cms/SiteRules.php b/kirby/src/Cms/SiteRules.php index 07db1b9..08da997 100644 --- a/kirby/src/Cms/SiteRules.php +++ b/kirby/src/Cms/SiteRules.php @@ -20,9 +20,6 @@ class SiteRules /** * Validates if the site title can be changed * - * @param \Kirby\Cms\Site $site - * @param string $title - * @return bool * @throws \Kirby\Exception\InvalidArgumentException If the title is empty * @throws \Kirby\Exception\PermissionException If the user is not allowed to change the title */ @@ -42,9 +39,6 @@ class SiteRules /** * Validates if the site can be updated * - * @param \Kirby\Cms\Site $site - * @param array $content - * @return bool * @throws \Kirby\Exception\PermissionException If the user is not allowed to update the site */ public static function update(Site $site, array $content = []): bool diff --git a/kirby/src/Cms/Structure.php b/kirby/src/Cms/Structure.php index 791f4dd..9721869 100644 --- a/kirby/src/Cms/Structure.php +++ b/kirby/src/Cms/Structure.php @@ -2,8 +2,6 @@ namespace Kirby\Cms; -use Kirby\Exception\InvalidArgumentException; - /** * The Structure class wraps * array data into a nicely chainable @@ -18,56 +16,34 @@ use Kirby\Exception\InvalidArgumentException; * @copyright Bastian Allgeier * @license https://getkirby.com/license */ -class Structure extends Collection +class Structure extends Items { + public const ITEM_CLASS = StructureObject::class; + /** * All registered structure methods - * - * @var array */ - public static $methods = []; + public static array $methods = []; /** - * Creates a new Collection with the given objects - * - * @param array $objects Kirby\Cms\StructureObject` objects or props arrays - * @param object|null $parent + * Creates a new structure collection from a + * an array of item props */ - public function __construct($objects = [], $parent = null) - { - $this->parent = $parent; - $this->set($objects); - } - - /** - * The internal setter for collection items. - * This makes sure that nothing unexpected ends - * up in the collection. You can pass arrays or - * StructureObjects - * - * @param string $id - * @param array|StructureObject $props - * @return void - * - * @throws \Kirby\Exception\InvalidArgumentException - */ - public function __set(string $id, $props): void - { - if ($props instanceof StructureObject) { - $object = $props; - } else { - if (is_array($props) === false) { - throw new InvalidArgumentException('Invalid structure data'); - } - - $object = new StructureObject([ - 'content' => $props, - 'id' => $props['id'] ?? $id, - 'parent' => $this->parent, - 'structure' => $this - ]); + public static function factory( + array $items = null, + array $params = [] + ): static { + // Bake-in index as ID for all items + // TODO: remove when adding UUID supports to Structures + if (is_array($items) === true) { + $items = array_map(function ($item, $index) { + if (is_array($item) === true) { + $item['id'] ??= $index; + } + return $item; + }, $items, array_keys($items)); } - parent::__set($object->id(), $object); + return parent::factory($items, $params); } } diff --git a/kirby/src/Cms/StructureObject.php b/kirby/src/Cms/StructureObject.php index 0805707..9806b7d 100644 --- a/kirby/src/Cms/StructureObject.php +++ b/kirby/src/Cms/StructureObject.php @@ -2,6 +2,8 @@ namespace Kirby\Cms; +use Kirby\Content\Content; + /** * The StructureObject represents each item * in a Structure collection. StructureObjects @@ -18,44 +20,38 @@ namespace Kirby\Cms; * @copyright Bastian Allgeier * @license https://getkirby.com/license */ -class StructureObject extends Model +class StructureObject extends Item { - use HasSiblings; + use HasMethods; + + public const ITEMS_CLASS = Structure::class; + + protected Content $content; /** - * The content - * - * @var Content + * Creates a new StructureObject with the given props */ - protected $content; + public function __construct(array $params = []) + { + parent::__construct($params); - /** - * @var string - */ - protected $id; - - /** - * @var \Kirby\Cms\Site|\Kirby\Cms\Page|\Kirby\Cms\File|\Kirby\Cms\User|null - */ - protected $parent; - - /** - * The parent Structure collection - * - * @var Structure - */ - protected $structure; + $this->content = new Content( + $params['content'] ?? $params['params'] ?? [], + $this->parent + ); + } /** * Modified getter to also return fields * from the object's content - * - * @param string $method - * @param array $arguments - * @return mixed */ - public function __call(string $method, array $arguments = []) + public function __call(string $method, array $args = []): mixed { + // structure object methods + if ($this->hasMethod($method) === true) { + return $this->callMethod($method, $args); + } + // public property access if (isset($this->$method) === true) { return $this->$method; @@ -64,146 +60,26 @@ class StructureObject extends Model return $this->content()->get($method); } - /** - * Creates a new StructureObject with the given props - * - * @param array $props - */ - public function __construct(array $props) - { - $this->setProperties($props); - } - /** * Returns the content - * - * @return \Kirby\Cms\Content */ - public function content() + public function content(): Content { - if ($this->content instanceof Content) { - return $this->content; - } - - if (is_array($this->content) !== true) { - $this->content = []; - } - - return $this->content = new Content($this->content, $this->parent()); - } - - /** - * Returns the required id - * - * @return string - */ - public function id(): string - { - return $this->id; - } - - /** - * Compares the current object with the given structure object - * - * @param mixed $structure - * @return bool - */ - public function is($structure): bool - { - if ($structure instanceof self === false) { - return false; - } - - return $this === $structure; - } - - /** - * Returns the parent Model object - * - * @return \Kirby\Cms\Model - */ - public function parent() - { - return $this->parent; - } - - /** - * Sets the Content object with the given parent - * - * @param array|null $content - * @return $this - */ - protected function setContent(array $content = null) - { - $this->content = $content; - return $this; - } - - /** - * Sets the id of the object. - * The id is required. The structure - * class will use the index, if no id is - * specified. - * - * @param string $id - * @return $this - */ - protected function setId(string $id) - { - $this->id = $id; - return $this; - } - - /** - * Sets the parent Model - * - * @return $this - * @param \Kirby\Cms\Site|\Kirby\Cms\Page|\Kirby\Cms\File|\Kirby\Cms\User|null $parent - */ - protected function setParent(Model $parent = null) - { - $this->parent = $parent; - return $this; - } - - /** - * Sets the parent Structure collection - * - * @param \Kirby\Cms\Structure|null $structure - * @return $this - */ - protected function setStructure(Structure $structure = null) - { - $this->structure = $structure; - return $this; - } - - /** - * Returns the parent Structure collection as siblings - * - * @return \Kirby\Cms\Structure - */ - protected function siblingsCollection() - { - return $this->structure; + return $this->content; } /** * Converts all fields in the object to a * plain associative array. The id is - * injected into the array afterwards + * injected from the parent into the array * to make sure it's always present and - * not overloaded in the content. - * - * @return array + * not overloaded by the content. */ public function toArray(): array { - $array = $this->content()->toArray(); - $array['id'] = $this->id(); - - ksort($array); - - return $array; + return array_merge( + $this->content()->toArray(), + parent::toArray() + ); } } diff --git a/kirby/src/Cms/System.php b/kirby/src/Cms/System.php index 481efa1..472c756 100644 --- a/kirby/src/Cms/System.php +++ b/kirby/src/Cms/System.php @@ -3,16 +3,11 @@ namespace Kirby\Cms; use Kirby\Cms\System\UpdateStatus; -use Kirby\Data\Json; -use Kirby\Exception\Exception; use Kirby\Exception\InvalidArgumentException; use Kirby\Exception\PermissionException; use Kirby\Filesystem\Dir; -use Kirby\Filesystem\F; -use Kirby\Http\Remote; use Kirby\Toolkit\A; use Kirby\Toolkit\Str; -use Kirby\Toolkit\V; use Throwable; /** @@ -32,6 +27,7 @@ use Throwable; class System { // cache + protected License|null $license = null; protected UpdateStatus|null $updateStatus = null; public function __construct(protected App $app) @@ -79,7 +75,10 @@ class System switch ($folder) { case 'content': - return $url . '/' . basename($this->app->site()->contentFile()); + return $url . '/' . basename($this->app->site()->storage()->contentFile( + 'published', + 'default' + )); case 'git': return $url . '/config'; case 'kirby': @@ -201,7 +200,25 @@ class System } /** - * Check if the panel is installable. + * Check if the Panel has 2FA activated + */ + public function is2FA(): bool + { + return ($this->loginMethods()['password']['2fa'] ?? null) === true; + } + + /** + * Check if the Panel has 2FA with TOTP activated + */ + public function is2FAWithTOTP(): bool + { + return + $this->is2FA() === true && + in_array('totp', $this->app->auth()->enabledChallenges()) === true; + } + + /** + * Check if the Panel is installable. * On a public server the panel.install * option must be explicitly set to true * to get the installer up and running. @@ -240,99 +257,10 @@ class System /** * Loads the license file and returns * the license information if available - * - * @return string|bool License key or `false` if the current user has - * permissions for access.settings, otherwise just a - * boolean that tells whether a valid license is active */ - public function license() + public function license(): License { - try { - $license = Json::read($this->app->root('license')); - } catch (Throwable) { - return false; - } - - // check for all required fields for the validation - if (isset( - $license['license'], - $license['order'], - $license['date'], - $license['email'], - $license['domain'], - $license['signature'] - ) !== true) { - return false; - } - - // build the license verification data - $data = [ - 'license' => $license['license'], - 'order' => $license['order'], - 'email' => hash('sha256', $license['email'] . 'kwAHMLyLPBnHEskzH9pPbJsBxQhKXZnX'), - 'domain' => $license['domain'], - 'date' => $license['date'] - ]; - - - // get the public key - $pubKey = F::read($this->app->root('kirby') . '/kirby.pub'); - - // verify the license signature - $data = json_encode($data); - $signature = hex2bin($license['signature']); - if (openssl_verify($data, $signature, $pubKey, 'RSA-SHA256') !== 1) { - return false; - } - - // verify the URL - if ($this->licenseUrl() !== $this->licenseUrl($license['domain'])) { - return false; - } - - // only return the actual license key if the - // current user has appropriate permissions - if ($this->app->user()?->isAdmin() === true) { - return $license['license']; - } - - return true; - } - - /** - * Normalizes the app's index URL for - * licensing purposes - * - * @param string|null $url Input URL, by default the app's index URL - * @return string Normalized URL - */ - protected function licenseUrl(string $url = null): string - { - $url ??= $this->indexUrl(); - - // remove common "testing" subdomains as well as www. - // to ensure that installations of the same site have - // the same license URL; only for installations at /, - // subdirectory installations are difficult to normalize - if (Str::contains($url, '/') === false) { - if (Str::startsWith($url, 'www.')) { - return substr($url, 4); - } - - if (Str::startsWith($url, 'dev.')) { - return substr($url, 4); - } - - if (Str::startsWith($url, 'test.')) { - return substr($url, 5); - } - - if (Str::startsWith($url, 'staging.')) { - return substr($url, 8); - } - } - - return $url; + return $this->license ??= License::read(); } /** @@ -417,8 +345,8 @@ class System public function php(): bool { return - version_compare(PHP_VERSION, '8.0.0', '>=') === true && - version_compare(PHP_VERSION, '8.3.0', '<') === true; + version_compare(PHP_VERSION, '8.1.0', '>=') === true && + version_compare(PHP_VERSION, '8.4.0', '<') === true; } /** @@ -441,46 +369,13 @@ class System */ public function register(string $license = null, string $email = null): bool { - if (Str::startsWith($license, 'K3-PRO-') === false) { - throw new InvalidArgumentException(['key' => 'license.format']); - } - - if (V::email($email) === false) { - throw new InvalidArgumentException(['key' => 'license.email']); - } - - // @codeCoverageIgnoreStart - $response = Remote::get('https://hub.getkirby.com/register', [ - 'data' => [ - 'license' => $license, - 'email' => Str::lower(trim($email)), - 'domain' => $this->indexUrl() - ] - ]); - - if ($response->code() !== 200) { - throw new Exception($response->content()); - } - - // decode the response - $json = Json::decode($response->content()); - - // replace the email with the plaintext version - $json['email'] = $email; - - // where to store the license file - $file = $this->app->root('license'); - - // save the license information - Json::write($file, $json); - - if ($this->license() === false) { - throw new InvalidArgumentException([ - 'key' => 'license.verification' - ]); - } - // @codeCoverageIgnoreEnd + $license = new License( + code: $license, + domain: $this->indexUrl(), + email: $email, + ); + $this->license = $license->register(); return true; } @@ -607,6 +502,7 @@ class System /** * Improved `var_dump` output + * @codeCoverageIgnore */ public function __debugInfo(): array { diff --git a/kirby/src/Cms/Translation.php b/kirby/src/Cms/Translation.php index 783bcfb..680f212 100644 --- a/kirby/src/Cms/Translation.php +++ b/kirby/src/Cms/Translation.php @@ -18,30 +18,15 @@ use Kirby\Toolkit\Str; */ class Translation { - /** - * @var string - */ - protected $code; - - /** - * @var array - */ - protected $data = []; - - /** - * @param string $code - * @param array $data - */ - public function __construct(string $code, array $data) - { - $this->code = $code; - $this->data = $data; + public function __construct( + protected string $code, + protected array $data + ) { } /** * Improved `var_dump` output - * - * @return array + * @codeCoverageIgnore */ public function __debugInfo(): array { @@ -50,8 +35,6 @@ class Translation /** * Returns the translation author - * - * @return string */ public function author(): string { @@ -60,8 +43,6 @@ class Translation /** * Returns the official translation code - * - * @return string */ public function code(): string { @@ -71,8 +52,6 @@ class Translation /** * Returns an array with all * translation strings - * - * @return array */ public function data(): array { @@ -82,8 +61,6 @@ class Translation /** * Returns the translation data and merges * it with the data from the default translation - * - * @return array */ public function dataWithFallback(): array { @@ -100,8 +77,6 @@ class Translation /** * Returns the writing direction * (ltr or rtl) - * - * @return string */ public function direction(): string { @@ -111,10 +86,6 @@ class Translation /** * Returns a single translation * string by key - * - * @param string $key - * @param string|null $default - * @return string|null */ public function get(string $key, string $default = null): string|null { @@ -124,8 +95,6 @@ class Translation /** * Returns the translation id, * which is also the code - * - * @return string */ public function id(): string { @@ -135,14 +104,12 @@ class Translation /** * Loads the translation from the * json file in Kirby's translations folder - * - * @param string $code - * @param string $root - * @param array $inject - * @return static */ - public static function load(string $code, string $root, array $inject = []) - { + public static function load( + string $code, + string $root, + array $inject = [] + ): static { try { $data = array_merge(Data::read($root), $inject); } catch (Exception) { @@ -154,8 +121,6 @@ class Translation /** * Returns the PHP locale of the translation - * - * @return string */ public function locale(): string { @@ -169,8 +134,6 @@ class Translation /** * Returns the human-readable translation name. - * - * @return string */ public function name(): string { @@ -180,8 +143,6 @@ class Translation /** * Converts the most important * properties to an array - * - * @return array */ public function toArray(): array { diff --git a/kirby/src/Cms/Translations.php b/kirby/src/Cms/Translations.php index b31f204..40c0b55 100644 --- a/kirby/src/Cms/Translations.php +++ b/kirby/src/Cms/Translations.php @@ -21,34 +21,10 @@ class Translations extends Collection { /** * All registered translations methods - * - * @var array */ - public static $methods = []; + public static array $methods = []; - /** - * @param string $code - * @return void - */ - public function start(string $code): void - { - F::move($this->parent->contentFile('', true), $this->parent->contentFile($code, true)); - } - - /** - * @param string $code - * @return void - */ - public function stop(string $code): void - { - F::move($this->parent->contentFile($code, true), $this->parent->contentFile('', true)); - } - - /** - * @param array $translations - * @return static - */ - public static function factory(array $translations) + public static function factory(array $translations): static { $collection = new static(); @@ -60,12 +36,7 @@ class Translations extends Collection return $collection; } - /** - * @param string $root - * @param array $inject - * @return static - */ - public static function load(string $root, array $inject = []) + public static function load(string $root, array $inject = []): static { $collection = new static(); diff --git a/kirby/src/Cms/Url.php b/kirby/src/Cms/Url.php index f10604f..bb44139 100644 --- a/kirby/src/Cms/Url.php +++ b/kirby/src/Cms/Url.php @@ -35,8 +35,10 @@ class Url extends BaseUrl * Creates an absolute Url to a template asset if it exists. * This is used in the `css()` and `js()` helpers */ - public static function toTemplateAsset(string $assetPath, string $extension): string|null - { + public static function toTemplateAsset( + string $assetPath, + string $extension + ): string|null { $kirby = App::instance(); $page = $kirby->site()->page(); $path = $assetPath . '/' . $page->template() . '.' . $extension; @@ -51,8 +53,10 @@ class Url extends BaseUrl * * @param array|string|null $options Either an array of options for the Uri class or a language string */ - public static function to(string|null $path = null, array|string|null $options = null): string - { + public static function to( + string|null $path = null, + array|string|null $options = null + ): string { $kirby = App::instance(); return ($kirby->component('url'))($kirby, $path, $options); } diff --git a/kirby/src/Cms/User.php b/kirby/src/Cms/User.php index 47fdb88..ce5fbb0 100644 --- a/kirby/src/Cms/User.php +++ b/kirby/src/Cms/User.php @@ -2,9 +2,12 @@ namespace Kirby\Cms; +use Closure; use Exception; +use Kirby\Content\Field; use Kirby\Exception\InvalidArgumentException; use Kirby\Exception\NotFoundException; +use Kirby\Exception\PermissionException; use Kirby\Filesystem\Dir; use Kirby\Filesystem\F; use Kirby\Panel\User as Panel; @@ -31,81 +34,66 @@ class User extends ModelWithContent public const CLASS_ALIAS = 'user'; - /** - * @var UserBlueprint - */ - protected $blueprint; - - /** - * @var array - */ - protected $credentials; - - /** - * @var string - */ - protected $email; - - /** - * @var string - */ - protected $hash; - - /** - * @var string - */ - protected $id; - - /** - * @var array|null - */ - protected $inventory; - - /** - * @var string - */ - protected $language; - /** * All registered user methods - * - * @var array + * @todo Remove when support for PHP 8.2 is dropped */ - public static $methods = []; + public static array $methods = []; /** * Registry with all User models - * - * @var array */ - public static $models = []; + public static array $models = []; + + protected UserBlueprint|null $blueprint = null; + protected array $credentials; + protected string|null $email; + protected string $hash; + protected string $id; + protected array|null $inventory = null; + protected string|null $language; + protected Field|string|null $name; + protected string|null $password; + protected Role|string|null $role; /** - * @var \Kirby\Cms\Field + * Creates a new User object */ - protected $name; + public function __construct(array $props) + { + // helper function to easily edit values (if not null) + // before assigning them to their properties + $set = function (string $key, Closure $callback) use ($props) { + if ($value = $props[$key] ?? null) { + $value = $callback($value); + } - /** - * @var string - */ - protected $password; + return $value; + }; - /** - * The user role - * - * @var string - */ - protected $role; + // if no ID passed, generate one; + // do so before calling parent constructor + // so it also gets stored in propertyData prop + $props['id'] ??= $this->createId(); + + parent::__construct($props); + + $this->id = $props['id']; + $this->email = $set('email', fn ($email) => Str::lower(trim($email))); + $this->language = $set('language', fn ($language) => trim($language)); + $this->name = $set('name', fn ($name) => trim(strip_tags($name))); + $this->password = $props['password'] ?? null; + $this->role = $set('role', fn ($role) => Str::lower(trim($role))); + + $this->setBlueprint($props['blueprint'] ?? null); + $this->setFiles($props['files'] ?? null); + } /** * Modified getter to also return fields * from the content - * - * @param string $method - * @param array $arguments - * @return mixed */ - public function __call(string $method, array $arguments = []) + public function __call(string $method, array $arguments = []): mixed { // public property access if (isset($this->$method) === true) { @@ -121,22 +109,9 @@ class User extends ModelWithContent return $this->content()->get($method); } - /** - * Creates a new User object - * - * @param array $props - */ - public function __construct(array $props) - { - // TODO: refactor later to avoid redundant prop setting - $this->setProperty('id', $props['id'] ?? $this->createId(), true); - $this->setProperties($props); - } - /** * Improved `var_dump` output - * - * @return array + * @codeCoverageIgnore */ public function __debugInfo(): array { @@ -149,10 +124,7 @@ class User extends ModelWithContent /** * Returns the url to the api endpoint - * * @internal - * @param bool $relative - * @return string */ public function apiUrl(bool $relative = false): string { @@ -165,29 +137,21 @@ class User extends ModelWithContent /** * Returns the File object for the avatar or null - * - * @return \Kirby\Cms\File|null */ - public function avatar() + public function avatar(): File|null { return $this->files()->template('avatar')->first(); } /** * Returns the UserBlueprint object - * - * @return \Kirby\Cms\Blueprint */ - public function blueprint() + public function blueprint(): UserBlueprint { - if ($this->blueprint instanceof Blueprint) { - return $this->blueprint; - } - try { - return $this->blueprint = UserBlueprint::factory('users/' . $this->role(), 'users/default', $this); + return $this->blueprint ??= UserBlueprint::factory('users/' . $this->role(), 'users/default', $this); } catch (Exception) { - return $this->blueprint = new UserBlueprint([ + return $this->blueprint ??= new UserBlueprint([ 'model' => $this, 'name' => 'default', 'title' => 'Default', @@ -197,14 +161,13 @@ class User extends ModelWithContent /** * Prepares the content for the write method - * * @internal - * @param array $data * @param string $languageCode|null Not used so far - * @return array */ - public function contentFileData(array $data, string $languageCode = null): array - { + public function contentFileData( + array $data, + string|null $languageCode = null + ): array { // remove stuff that has nothing to do in the text files unset( $data['email'], @@ -221,10 +184,13 @@ class User extends ModelWithContent * Filename for the content file * * @internal - * @return string + * @deprecated 4.0.0 + * @todo Remove in v5 + * @codeCoverageIgnore */ public function contentFileName(): string { + Helpers::deprecated('The internal $model->contentFileName() method has been deprecated. Please let us know via a GitHub issue if you need this method and tell us your use case.', 'model-content-file'); return 'user'; } @@ -235,8 +201,6 @@ class User extends ModelWithContent /** * Returns the user email address - * - * @return string */ public function email(): string|null { @@ -245,23 +209,21 @@ class User extends ModelWithContent /** * Checks if the user exists - * - * @return bool */ public function exists(): bool { - return is_file($this->contentFile('default')) === true; + return $this->storage()->exists( + 'published', + 'default' + ); } /** * Constructs a User object and also * takes User models into account. - * * @internal - * @param mixed $props - * @return static */ - public static function factory($props) + public static function factory(mixed $props): static { if (empty($props['model']) === false) { return static::model($props['model'], $props); @@ -273,7 +235,6 @@ class User extends ModelWithContent /** * Hashes the user's password unless it is `null`, * which will leave it as `null` - * * @internal */ public static function hashPassword( @@ -289,8 +250,6 @@ class User extends ModelWithContent /** * Returns the user id - * - * @return string */ public function id(): string { @@ -300,8 +259,6 @@ class User extends ModelWithContent /** * Returns the inventory of files * children and content files - * - * @return array */ public function inventory(): array { @@ -321,9 +278,6 @@ class User extends ModelWithContent /** * Compares the current object with the given user object - * - * @param \Kirby\Cms\User|null $user - * @return bool */ public function is(User $user = null): bool { @@ -336,8 +290,6 @@ class User extends ModelWithContent /** * Checks if this user has the admin role - * - * @return bool */ public function isAdmin(): bool { @@ -347,8 +299,6 @@ class User extends ModelWithContent /** * Checks if the current user is the virtual * Kirby user - * - * @return bool */ public function isKirby(): bool { @@ -357,8 +307,6 @@ class User extends ModelWithContent /** * Checks if the current user is this user - * - * @return bool */ public function isLoggedIn(): bool { @@ -368,8 +316,6 @@ class User extends ModelWithContent /** * Checks if the user is the last one * with the admin role - * - * @return bool */ public function isLastAdmin(): bool { @@ -380,8 +326,6 @@ class User extends ModelWithContent /** * Checks if the user is the last user - * - * @return bool */ public function isLastUser(): bool { @@ -391,8 +335,6 @@ class User extends ModelWithContent /** * Checks if the current user is the virtual * Nobody user - * - * @return bool */ public function isNobody(): bool { @@ -401,8 +343,6 @@ class User extends ModelWithContent /** * Returns the user language - * - * @return string */ public function language(): string { @@ -431,33 +371,43 @@ class User extends ModelWithContent * Logs the user in without checking the password * * @param \Kirby\Session\Session|array|null $session Session options or session object to set the user in - * @return void */ - public function loginPasswordless($session = null): void - { - $kirby = $this->kirby(); + public function loginPasswordless( + Session|array|null $session = null + ): void { + if ($this->id() === 'kirby') { + throw new PermissionException('The almighty user "kirby" cannot be used for login, only for raising permissions in code via `$kirby->impersonate()`'); + } + $kirby = $this->kirby(); $session = $this->sessionFromOptions($session); - $kirby->trigger('user.login:before', ['user' => $this, 'session' => $session]); + $kirby->trigger( + 'user.login:before', + ['user' => $this, 'session' => $session] + ); $session->regenerateToken(); // privilege change $session->data()->set('kirby.userId', $this->id()); + if ($this->passwordTimestamp() !== null) { $session->data()->set('kirby.loginTimestamp', time()); } - $this->kirby()->auth()->setUser($this); - $kirby->trigger('user.login:after', ['user' => $this, 'session' => $session]); + $kirby->auth()->setUser($this); + + $kirby->trigger( + 'user.login:after', + ['user' => $this, 'session' => $session] + ); } /** * Logs the user out * * @param \Kirby\Session\Session|array|null $session Session options or session object to unset the user in - * @return void */ - public function logout($session = null): void + public function logout(Session|array|null $session = null): void { $kirby = $this->kirby(); $session = $this->sessionFromOptions($session); @@ -486,9 +436,7 @@ class User extends ModelWithContent /** * Returns the root to the media folder for the user - * * @internal - * @return string */ public function mediaRoot(): string { @@ -497,9 +445,7 @@ class User extends ModelWithContent /** * Returns the media url for the user object - * * @internal - * @return string */ public function mediaUrl(): string { @@ -508,13 +454,9 @@ class User extends ModelWithContent /** * Creates a user model if it has been registered - * * @internal - * @param string $name - * @param array $props - * @return \Kirby\Cms\User */ - public static function model(string $name, array $props = []) + public static function model(string $name, array $props = []): static { if ($class = (static::$models[$name] ?? null)) { $object = new $class($props); @@ -529,47 +471,36 @@ class User extends ModelWithContent /** * Returns the last modification date of the user - * - * @param string $format - * @param string|null $handler - * @param string|null $languageCode - * @return int|string */ - public function modified(string $format = 'U', string $handler = null, string $languageCode = null) - { - $modifiedContent = F::modified($this->contentFile($languageCode)); + public function modified( + string $format = 'U', + string|null $handler = null, + string|null $languageCode = null + ): int|string|false { + $modifiedContent = $this->storage()->modified('published', $languageCode); $modifiedIndex = F::modified($this->root() . '/index.php'); $modifiedTotal = max([$modifiedContent, $modifiedIndex]); - $handler ??= $this->kirby()->option('date.handler', 'date'); return Str::date($modifiedTotal, $format, $handler); } /** * Returns the user's name - * - * @return \Kirby\Cms\Field */ - public function name() + public function name(): Field { if (is_string($this->name) === true) { return new Field($this, 'name', $this->name); } - if ($this->name !== null) { - return $this->name; - } - - return $this->name = new Field($this, 'name', $this->credentials()['name'] ?? null); + return $this->name ??= new Field($this, 'name', $this->credentials()['name'] ?? null); } /** * Returns the user's name or, * if empty, the email address - * - * @return \Kirby\Cms\Field */ - public function nameOrEmail() + public function nameOrEmail(): Field { $name = $this->name(); return $name->isNotEmpty() ? $name : new Field($this, 'email', $this->email()); @@ -577,11 +508,9 @@ class User extends ModelWithContent /** * Create a dummy nobody - * * @internal - * @return static */ - public static function nobody() + public static function nobody(): static { return new static([ 'email' => 'nobody@getkirby.com', @@ -591,26 +520,18 @@ class User extends ModelWithContent /** * Returns the panel info object - * - * @return \Kirby\Panel\User */ - public function panel() + public function panel(): Panel { return new Panel($this); } /** * Returns the encrypted user password - * - * @return string|null */ public function password(): string|null { - if ($this->password !== null) { - return $this->password; - } - - return $this->password = $this->readPassword(); + return $this->password ??= $this->readPassword(); } /** @@ -619,7 +540,7 @@ class User extends ModelWithContent */ public function passwordTimestamp(): int|null { - $file = $this->passwordFile(); + $file = $this->secretsFile(); // ensure we have the latest information // to prevent cache attacks @@ -633,20 +554,15 @@ class User extends ModelWithContent return filemtime($file); } - /** - * @return \Kirby\Cms\UserPermissions - */ - public function permissions() + public function permissions(): UserPermissions { return new UserPermissions($this); } /** * Returns the user role - * - * @return \Kirby\Cms\Role */ - public function role() + public function role(): Role { if ($this->role instanceof Role) { return $this->role; @@ -661,10 +577,8 @@ class User extends ModelWithContent * Returns all available roles * for this user, that can be selected * by the authenticated user - * - * @return \Kirby\Cms\Roles */ - public function roles() + public function roles(): Roles { $kirby = $this->kirby(); $roles = $kirby->roles(); @@ -691,8 +605,6 @@ class User extends ModelWithContent /** * The absolute path to the user directory - * - * @return string */ public function root(): string { @@ -702,21 +614,27 @@ class User extends ModelWithContent /** * Returns the UserRules class to * validate any important action. - * - * @return \Kirby\Cms\UserRules */ - protected function rules() + protected function rules(): UserRules { return new UserRules(); } + /** + * Reads a specific secret from the user secrets file on disk + * @since 4.0.0 + */ + public function secret(string $key): mixed + { + return $this->readSecrets()[$key] ?? null; + } + /** * Sets the Blueprint object * - * @param array|null $blueprint * @return $this */ - protected function setBlueprint(array $blueprint = null) + protected function setBlueprint(array $blueprint = null): static { if ($blueprint !== null) { $blueprint['model'] = $this; @@ -726,88 +644,12 @@ class User extends ModelWithContent return $this; } - /** - * Sets the user email - * - * @param string $email|null - * @return $this - */ - protected function setEmail(string $email = null) - { - if ($email !== null) { - $this->email = Str::lower(trim($email)); - } - return $this; - } - - /** - * Sets the user id - * - * @param string $id|null - * @return $this - */ - protected function setId(string $id = null) - { - $this->id = $id; - return $this; - } - - /** - * Sets the user language - * - * @param string $language|null - * @return $this - */ - protected function setLanguage(string $language = null) - { - $this->language = $language !== null ? trim($language) : null; - return $this; - } - - /** - * Sets the user name - * - * @param string $name|null - * @return $this - */ - protected function setName(string $name = null) - { - $this->name = $name !== null ? trim(strip_tags($name)) : null; - return $this; - } - - /** - * Sets the user's password hash - * - * @return $this - */ - protected function setPassword( - #[SensitiveParameter] - string $password = null - ): static { - $this->password = $password; - return $this; - } - - /** - * Sets the user role - * - * @param string $role|null - * @return $this - */ - protected function setRole(string $role = null) - { - $this->role = $role !== null ? Str::lower(trim($role)) : null; - return $this; - } - /** * Converts session options into a session object * * @param \Kirby\Session\Session|array $session Session options or session object to unset the user in - * @return \Kirby\Session\Session */ - protected function sessionFromOptions($session) + protected function sessionFromOptions(Session|array|null $session): Session { // use passed session options or session object if set if (is_array($session) === true) { @@ -821,10 +663,8 @@ class User extends ModelWithContent /** * Returns the parent Users collection - * - * @return \Kirby\Cms\Users */ - protected function siblingsCollection() + protected function siblingsCollection(): Users { return $this->kirby()->users(); } @@ -832,33 +672,31 @@ class User extends ModelWithContent /** * Converts the most important user properties * to an array - * - * @return array */ public function toArray(): array { - return [ - 'avatar' => $this->avatar() ? $this->avatar()->toArray() : null, - 'content' => $this->content()->toArray(), + return array_merge(parent::toArray(), [ + 'avatar' => $this->avatar()?->toArray(), 'email' => $this->email(), 'id' => $this->id(), 'language' => $this->language(), 'role' => $this->role()->name(), 'username' => $this->username() - ]; + ]); } /** * String template builder * - * @param string|null $template - * @param array|null $data * @param string|null $fallback Fallback for tokens in the template that cannot be replaced * (`null` to keep the original token) - * @return string */ - public function toString(string $template = null, array $data = [], string|null $fallback = '', string $handler = 'template'): string - { + public function toString( + string $template = null, + array $data = [], + string|null $fallback = '', + string $handler = 'template' + ): string { $template ??= $this->email(); return parent::toString($template, $data, $fallback, $handler); } @@ -867,8 +705,6 @@ class User extends ModelWithContent * Returns the username * which is the given name or the email * as a fallback - * - * @return string|null */ public function username(): string|null { @@ -909,9 +745,20 @@ class User extends ModelWithContent } /** - * Returns the path to the password file + * @deprecated 4.0.0 Use `->secretsFile()` instead + * @codeCoverageIgnore */ protected function passwordFile(): string + { + return $this->secretsFile(); + } + + /** + * Returns the path to the file containing + * all user secrets, including the password + * @since 4.0.0 + */ + protected function secretsFile(): string { return $this->root() . '/.htpasswd'; } diff --git a/kirby/src/Cms/UserActions.php b/kirby/src/Cms/UserActions.php index 2c89043..728acc1 100644 --- a/kirby/src/Cms/UserActions.php +++ b/kirby/src/Cms/UserActions.php @@ -4,12 +4,14 @@ namespace Kirby\Cms; use Closure; use Kirby\Data\Data; +use Kirby\Data\Json; use Kirby\Exception\LogicException; use Kirby\Exception\PermissionException; use Kirby\Filesystem\Dir; use Kirby\Filesystem\F; use Kirby\Form\Form; use Kirby\Http\Idn; +use Kirby\Toolkit\A; use Kirby\Toolkit\Str; use SensitiveParameter; use Throwable; @@ -27,11 +29,8 @@ trait UserActions { /** * Changes the user email address - * - * @param string $email - * @return static */ - public function changeEmail(string $email) + public function changeEmail(string $email): static { $email = trim($email); @@ -53,11 +52,8 @@ trait UserActions /** * Changes the user language - * - * @param string $language - * @return static */ - public function changeLanguage(string $language) + public function changeLanguage(string $language): static { return $this->commit('changeLanguage', ['user' => $this, 'language' => $language], function ($user, $language) { $user = $user->clone([ @@ -77,11 +73,8 @@ trait UserActions /** * Changes the screen name of the user - * - * @param string $name - * @return static */ - public function changeName(string $name) + public function changeName(string $name): static { $name = trim($name); @@ -131,11 +124,8 @@ trait UserActions /** * Changes the user role - * - * @param string $role - * @return static */ - public function changeRole(string $role) + public function changeRole(string $role): static { return $this->commit('changeRole', ['user' => $this, 'role' => $role], function ($user, $role) { $user = $user->clone([ @@ -153,6 +143,28 @@ trait UserActions }); } + /** + * Changes the user's TOTP secret + * @since 4.0.0 + */ + public function changeTotp( + #[SensitiveParameter] + string|null $secret + ): static { + return $this->commit('changeTotp', ['user' => $this, 'secret' => $secret], function ($user, $secret) { + $this->writeSecret('totp', $secret); + + // keep the user logged in to the current browser + // if they changed their own TOTP secret + // (regenerate the session token, update the login timestamp) + if ($user->isLoggedIn() === true) { + $user->loginPasswordless(); + } + + return $user; + }); + } + /** * Commits a user action, by following these steps * @@ -162,14 +174,13 @@ trait UserActions * 4. sends the after hook * 5. returns the result * - * @param string $action - * @param array $arguments - * @param \Closure $callback - * @return mixed * @throws \Kirby\Exception\PermissionException */ - protected function commit(string $action, array $arguments, Closure $callback) - { + protected function commit( + string $action, + array $arguments, + Closure $callback + ): mixed { if ($this->isKirby() === true) { throw new PermissionException('The Kirby user cannot be changed'); } @@ -183,13 +194,12 @@ trait UserActions $result = $callback(...$argumentValues); - if ($action === 'create') { - $argumentsAfter = ['user' => $result]; - } elseif ($action === 'delete') { - $argumentsAfter = ['status' => $result, 'user' => $old]; - } else { - $argumentsAfter = ['newUser' => $result, 'oldUser' => $old]; - } + $argumentsAfter = match ($action) { + 'create' => ['user' => $result], + 'delete' => ['status' => $result, 'user' => $old], + default => ['newUser' => $result, 'oldUser' => $old] + }; + $kirby->trigger('user.' . $action . ':after', $argumentsAfter); $kirby->cache('pages')->flush(); @@ -198,11 +208,8 @@ trait UserActions /** * Creates a new User from the given props and returns a new User object - * - * @param array|null $props - * @return static */ - public static function create(array $props = null) + public static function create(array $props = null): User { $data = $props; @@ -254,8 +261,6 @@ trait UserActions /** * Returns a random user id - * - * @return string */ public function createId(): string { @@ -280,7 +285,6 @@ trait UserActions /** * Deletes the user * - * @return bool * @throws \Kirby\Exception\LogicException */ public function delete(): bool @@ -307,8 +311,6 @@ trait UserActions /** * Read the account information from disk - * - * @return array */ protected function readCredentials(): array { @@ -325,24 +327,47 @@ trait UserActions /** * Reads the user password from disk - * - * @return string|false */ - protected function readPassword() + protected function readPassword(): string|false { - return F::read($this->passwordFile()); + return $this->secret('password') ?? false; + } + + /** + * Reads the secrets from the user secrets file on disk + * @since 4.0.0 + */ + protected function readSecrets(): array + { + $file = $this->secretsFile(); + $secrets = []; + + if (is_file($file) === true) { + $lines = explode("\n", file_get_contents($file)); + + if (isset($lines[1]) === true) { + $secrets = Json::decode($lines[1]); + } + + $secrets['password'] = $lines[0]; + } + + // an empty password hash means that no password was set + if (($secrets['password'] ?? null) === '') { + unset($secrets['password']); + } + + return $secrets; } /** * Updates the user data - * - * @param array|null $input - * @param string|null $languageCode - * @param bool $validate - * @return static */ - public function update(array $input = null, string $languageCode = null, bool $validate = false) - { + public function update( + array $input = null, + string $languageCode = null, + bool $validate = false + ): static { $user = parent::update($input, $languageCode, $validate); // set auth user data only if the current user is this user @@ -359,9 +384,6 @@ trait UserActions /** * This always merges the existing credentials * with the given input. - * - * @param array $credentials - * @return bool */ protected function updateCredentials(array $credentials): bool { @@ -375,9 +397,6 @@ trait UserActions /** * Writes the account information to disk - * - * @param array $credentials - * @return bool */ protected function writeCredentials(array $credentials): bool { @@ -391,6 +410,39 @@ trait UserActions #[SensitiveParameter] string $password = null ): bool { - return F::write($this->passwordFile(), $password); + return $this->writeSecret('password', $password); + } + + /** + * Writes a specific secret to the user secrets file on disk; + * `password` is the first line, the rest is stored as JSON + * @since 4.0.0 + */ + protected function writeSecret( + string $key, + #[SensitiveParameter] + mixed $secret + ): bool { + $secrets = $this->readSecrets(); + + if ($secret === null) { + unset($secrets[$key]); + } else { + $secrets[$key] = $secret; + } + + // first line is always the password + $lines = $secrets['password'] ?? ''; + + // everything else is for the second line + $secondLine = Json::encode( + A::without($secrets, 'password') + ); + + if ($secondLine !== '[]') { + $lines .= "\n" . $secondLine; + } + + return F::write($this->secretsFile(), $lines); } } diff --git a/kirby/src/Cms/UserBlueprint.php b/kirby/src/Cms/UserBlueprint.php index 0f86fe1..d44d852 100644 --- a/kirby/src/Cms/UserBlueprint.php +++ b/kirby/src/Cms/UserBlueprint.php @@ -17,7 +17,6 @@ class UserBlueprint extends Blueprint /** * UserBlueprint constructor. * - * @param array $props * @throws \Kirby\Exception\InvalidArgumentException */ public function __construct(array $props) diff --git a/kirby/src/Cms/UserPermissions.php b/kirby/src/Cms/UserPermissions.php index bc91161..62cd662 100644 --- a/kirby/src/Cms/UserPermissions.php +++ b/kirby/src/Cms/UserPermissions.php @@ -13,35 +13,22 @@ namespace Kirby\Cms; */ class UserPermissions extends ModelPermissions { - /** - * @var string - */ - protected $category = 'users'; + protected string $category = 'users'; - /** - * UserPermissions constructor - * - * @param \Kirby\Cms\Model $model - */ - public function __construct(Model $model) + public function __construct(User $model) { parent::__construct($model); - // change the scope of the permissions, when the current user is this user - $this->category = $this->user && $this->user->is($model) ? 'user' : 'users'; + // change the scope of the permissions, + // when the current user is this user + $this->category = $this->user?->is($model) ? 'user' : 'users'; } - /** - * @return bool - */ protected function canChangeRole(): bool { return $this->model->roles()->count() > 1; } - /** - * @return bool - */ protected function canCreate(): bool { // the admin can always create new users @@ -57,9 +44,6 @@ class UserPermissions extends ModelPermissions return true; } - /** - * @return bool - */ protected function canDelete(): bool { return $this->model->isLastAdmin() !== true; diff --git a/kirby/src/Cms/UserPicker.php b/kirby/src/Cms/UserPicker.php index 0861bc8..0e9ab3e 100644 --- a/kirby/src/Cms/UserPicker.php +++ b/kirby/src/Cms/UserPicker.php @@ -19,8 +19,6 @@ class UserPicker extends Picker { /** * Extends the basic defaults - * - * @return array */ public function defaults(): array { @@ -33,21 +31,21 @@ class UserPicker extends Picker /** * Search all users for the picker * - * @return \Kirby\Cms\Users|null * @throws \Kirby\Exception\InvalidArgumentException */ - public function items() + public function items(): Users|null { $model = $this->options['model']; // find the right default query - if (empty($this->options['query']) === false) { - $query = $this->options['query']; - } elseif ($model instanceof User) { - $query = 'user.siblings'; - } else { - $query = 'kirby.users'; - } + $query = match (true) { + empty($this->options['query']) === false + => $this->options['query'], + $model instanceof User + => 'user.siblings', + default + => 'kirby.users' + }; // fetch all users for the picker $users = $model->query($query); diff --git a/kirby/src/Cms/UserRules.php b/kirby/src/Cms/UserRules.php index 8c6402a..cf32665 100644 --- a/kirby/src/Cms/UserRules.php +++ b/kirby/src/Cms/UserRules.php @@ -7,6 +7,7 @@ use Kirby\Exception\InvalidArgumentException; use Kirby\Exception\LogicException; use Kirby\Exception\PermissionException; use Kirby\Toolkit\Str; +use Kirby\Toolkit\Totp; use Kirby\Toolkit\V; use SensitiveParameter; @@ -24,9 +25,6 @@ class UserRules /** * Validates if the email address can be changed * - * @param \Kirby\Cms\User $user - * @param string $email - * @return bool * @throws \Kirby\Exception\PermissionException If the user is not allowed to change the address */ public static function changeEmail(User $user, string $email): bool @@ -44,9 +42,6 @@ class UserRules /** * Validates if the language can be changed * - * @param \Kirby\Cms\User $user - * @param string $language - * @return bool * @throws \Kirby\Exception\PermissionException If the user is not allowed to change the language */ public static function changeLanguage(User $user, string $language): bool @@ -64,9 +59,6 @@ class UserRules /** * Validates if the name can be changed * - * @param \Kirby\Cms\User $user - * @param string $name - * @return bool * @throws \Kirby\Exception\PermissionException If the user is not allowed to change the name */ public static function changeName(User $user, string $name): bool @@ -104,9 +96,6 @@ class UserRules /** * Validates if the role can be changed * - * @param \Kirby\Cms\User $user - * @param string $role - * @return bool * @throws \Kirby\Exception\LogicException If the user is the last admin * @throws \Kirby\Exception\PermissionException If the user is not allowed to change the role */ @@ -152,12 +141,38 @@ class UserRules return true; } + /** + * Validates if the TOTP can be changed + * @since 4.0.0 + * + * @throws \Kirby\Exception\PermissionException If the user is not allowed to change the password + */ + public static function changeTotp( + User $user, + #[SensitiveParameter] + string|null $secret + ): bool { + $currentUser = $user->kirby()->user(); + + if ( + $currentUser->is($user) === false && + $currentUser->isAdmin() === false + ) { + throw new PermissionException('You cannot change the time-based code for ' . $user->email()); + } + + // safety check to avoid accidental insecure secrets; + // throws an exception for secrets of the wrong length + if ($secret !== null) { + new Totp($secret); + } + + return true; + } + /** * Validates if the user can be created * - * @param \Kirby\Cms\User $user - * @param array $props - * @return bool * @throws \Kirby\Exception\PermissionException If the user is not allowed to create a new user */ public static function create(User $user, array $props = []): bool @@ -209,8 +224,6 @@ class UserRules /** * Validates if the user can be deleted * - * @param \Kirby\Cms\User $user - * @return bool * @throws \Kirby\Exception\LogicException If this is the last user or last admin, which cannot be deleted * @throws \Kirby\Exception\PermissionException If the user is not allowed to delete this user */ @@ -239,14 +252,13 @@ class UserRules /** * Validates if the user can be updated * - * @param \Kirby\Cms\User $user - * @param array $values - * @param array $strings - * @return bool * @throws \Kirby\Exception\PermissionException If the user it not allowed to update this user */ - public static function update(User $user, array $values = [], array $strings = []): bool - { + public static function update( + User $user, + array $values = [], + array $strings = [] + ): bool { if ($user->permissions()->update() !== true) { throw new PermissionException([ 'key' => 'user.update.permission', @@ -260,15 +272,14 @@ class UserRules /** * Validates an email address * - * @param \Kirby\Cms\User $user - * @param string $email - * @param bool $strict - * @return bool * @throws \Kirby\Exception\DuplicateException If the email address already exists * @throws \Kirby\Exception\InvalidArgumentException If the email address is invalid */ - public static function validEmail(User $user, string $email, bool $strict = false): bool - { + public static function validEmail( + User $user, + string $email, + bool $strict = false + ): bool { if (V::email($email ?? null) === false) { throw new InvalidArgumentException([ 'key' => 'user.email.invalid', @@ -294,9 +305,6 @@ class UserRules /** * Validates a user id * - * @param \Kirby\Cms\User $user - * @param string $id - * @return bool * @throws \Kirby\Exception\DuplicateException If the user already exists */ public static function validId(User $user, string $id): bool @@ -315,9 +323,6 @@ class UserRules /** * Validates a user language code * - * @param \Kirby\Cms\User $user - * @param string $language - * @return bool * @throws \Kirby\Exception\InvalidArgumentException If the language does not exist */ public static function validLanguage(User $user, string $language): bool @@ -364,9 +369,6 @@ class UserRules /** * Validates a user role * - * @param \Kirby\Cms\User $user - * @param string $role - * @return bool * @throws \Kirby\Exception\InvalidArgumentException If the user role does not exist */ public static function validRole(User $user, string $role): bool diff --git a/kirby/src/Cms/Users.php b/kirby/src/Cms/Users.php index b9e51fb..df2f6c3 100644 --- a/kirby/src/Cms/Users.php +++ b/kirby/src/Cms/Users.php @@ -26,12 +26,10 @@ class Users extends Collection /** * All registered users methods - * - * @var array */ - public static $methods = []; + public static array $methods = []; - public function create(array $data) + public function create(array $data): User { return User::create($data); } @@ -45,7 +43,7 @@ class Users extends Collection * @return $this * @throws \Kirby\Exception\InvalidArgumentException When no `User` or `Users` object or an ID of an existing user is passed */ - public function add($object) + public function add($object): static { // add a users collection if ($object instanceof self) { @@ -72,13 +70,10 @@ class Users extends Collection } /** - * Takes an array of user props and creates a nice and clean user collection from it - * - * @param array $users - * @param array $inject - * @return static + * Takes an array of user props and creates a nice + * and clean user collection from it */ - public static function factory(array $users, array $inject = []) + public static function factory(array $users, array $inject = []): static { $collection = new static(); @@ -93,10 +88,8 @@ class Users extends Collection /** * Returns all files of all users - * - * @return \Kirby\Cms\Files */ - public function files() + public function files(): Files { $files = new Files([], $this->parent); @@ -112,11 +105,8 @@ class Users extends Collection /** * Finds a user in the collection by ID or email address * @internal Use `$users->find()` instead - * - * @param string $key - * @return \Kirby\Cms\User|null */ - public function findByKey(string $key) + public function findByKey(string $key): User|null { if ($user = $this->findByUuid($key, 'user')) { return $user; @@ -131,12 +121,8 @@ class Users extends Collection /** * Loads a user from disk by passing the absolute path (root) - * - * @param string $root - * @param array $inject - * @return static */ - public static function load(string $root, array $inject = []) + public static function load(string $root, array $inject = []): static { $users = new static(); @@ -165,11 +151,8 @@ class Users extends Collection /** * Shortcut for `$users->filter('role', 'admin')` - * - * @param string $role - * @return static */ - public function role(string $role) + public function role(string $role): static { return $this->filter('role', $role); } diff --git a/kirby/src/Cms/Visitor.php b/kirby/src/Cms/Visitor.php index eae9f3a..511eb8e 100644 --- a/kirby/src/Cms/Visitor.php +++ b/kirby/src/Cms/Visitor.php @@ -2,6 +2,7 @@ namespace Kirby\Cms; +use Kirby\Http\Visitor as BaseVisitor; use Kirby\Toolkit\Facade; /** @@ -15,10 +16,7 @@ use Kirby\Toolkit\Facade; */ class Visitor extends Facade { - /** - * @return \Kirby\Http\Visitor - */ - public static function instance() + public static function instance(): BaseVisitor { return App::instance()->visitor(); } diff --git a/kirby/src/Cms/Content.php b/kirby/src/Content/Content.php similarity index 72% rename from kirby/src/Cms/Content.php rename to kirby/src/Content/Content.php index cfe6c0c..978c8d2 100644 --- a/kirby/src/Cms/Content.php +++ b/kirby/src/Content/Content.php @@ -1,14 +1,16 @@ * @link https://getkirby.com * @copyright Bastian Allgeier @@ -18,39 +20,29 @@ class Content { /** * The raw data array - * - * @var array */ - protected $data = []; + protected array $data = []; /** * Cached field objects * Once a field is being fetched * it is added to this array for * later reuse - * - * @var array */ - protected $fields = []; + protected array $fields = []; /** * A potential parent object. * Not necessarily needed. Especially * for testing, but field methods might * need it. - * - * @var Model */ - protected $parent; + protected ModelWithContent|null $parent; /** * Magic getter for content fields - * - * @param string $name - * @param array $arguments - * @return \Kirby\Cms\Field */ - public function __call(string $name, array $arguments = []) + public function __call(string $name, array $arguments = []): Field { return $this->get($name); } @@ -58,12 +50,13 @@ class Content /** * Creates a new Content object * - * @param array|null $data - * @param object|null $parent * @param bool $normalize Set to `false` if the input field keys are already lowercase */ - public function __construct(array $data = [], $parent = null, bool $normalize = true) - { + public function __construct( + array $data = [], + ModelWithContent $parent = null, + bool $normalize = true + ) { if ($normalize === true) { $data = array_change_key_case($data, CASE_LOWER); } @@ -75,9 +68,9 @@ class Content /** * Same as `self::data()` to improve * `var_dump` output + * @codeCoverageIgnore * * @see self::data() - * @return array */ public function __debugInfo(): array { @@ -86,9 +79,6 @@ class Content /** * Converts the content to a new blueprint - * - * @param string $to - * @return array */ public function convertTo(string $to): array { @@ -99,11 +89,21 @@ class Content // blueprints $old = $this->parent->blueprint(); $subfolder = dirname($old->name()); - $new = Blueprint::factory($subfolder . '/' . $to, $subfolder . '/default', $this->parent); + $new = Blueprint::factory( + $subfolder . '/' . $to, + $subfolder . '/default', + $this->parent + ); // forms - $oldForm = new Form(['fields' => $old->fields(), 'model' => $this->parent]); - $newForm = new Form(['fields' => $new->fields(), 'model' => $this->parent]); + $oldForm = new Form([ + 'fields' => $old->fields(), + 'model' => $this->parent + ]); + $newForm = new Form([ + 'fields' => $new->fields(), + 'model' => $this->parent + ]); // fields $oldFields = $oldForm->fields(); @@ -128,8 +128,6 @@ class Content /** * Returns the raw data array - * - * @return array */ public function data(): array { @@ -138,8 +136,6 @@ class Content /** * Returns all registered field objects - * - * @return array */ public function fields(): array { @@ -152,11 +148,8 @@ class Content /** * Returns either a single field object * or all registered fields - * - * @param string|null $key - * @return \Kirby\Cms\Field|array */ - public function get(string $key = null) + public function get(string $key = null): Field|array { if ($key === null) { return $this->fields(); @@ -173,9 +166,6 @@ class Content /** * Checks if a content field is set - * - * @param string $key - * @return bool */ public function has(string $key): bool { @@ -184,8 +174,6 @@ class Content /** * Returns all field keys - * - * @return array */ public function keys(): array { @@ -196,14 +184,11 @@ class Content * Returns a clone of the content object * without the fields, specified by the * passed key(s) - * - * @param string ...$keys - * @return static */ - public function not(...$keys) + public function not(string ...$keys): static { $copy = clone $this; - $copy->fields = null; + $copy->fields = []; foreach ($keys as $key) { unset($copy->data[strtolower($key)]); @@ -215,10 +200,8 @@ class Content /** * Returns the parent * Site, Page, File or User object - * - * @return \Kirby\Cms\Model */ - public function parent() + public function parent(): ModelWithContent|null { return $this->parent; } @@ -226,10 +209,9 @@ class Content /** * Set the parent model * - * @param \Kirby\Cms\Model $parent * @return $this */ - public function setParent(Model $parent) + public function setParent(ModelWithContent $parent): static { $this->parent = $parent; return $this; @@ -239,7 +221,6 @@ class Content * Returns the raw data array * * @see self::data() - * @return array */ public function toArray(): array { @@ -250,12 +231,12 @@ class Content * Updates the content and returns * a cloned object * - * @param array|null $content - * @param bool $overwrite * @return $this */ - public function update(array $content = null, bool $overwrite = false) - { + public function update( + array $content = null, + bool $overwrite = false + ): static { $content = array_change_key_case((array)$content, CASE_LOWER); $this->data = $overwrite === true ? $content : array_merge($this->data, $content); diff --git a/kirby/src/Content/ContentStorage.php b/kirby/src/Content/ContentStorage.php new file mode 100644 index 0000000..23eab40 --- /dev/null +++ b/kirby/src/Content/ContentStorage.php @@ -0,0 +1,314 @@ + + * @author Nico Hoffmann + * @link https://getkirby.com + * @copyright Bastian Allgeier + * @license https://getkirby.com/license + */ +class ContentStorage +{ + protected ContentStorageHandler $handler; + + public function __construct( + protected ModelWithContent $model, + string $handler = PlainTextContentStorageHandler::class + ) { + $this->handler = new $handler($model); + } + + /** + * Magic caller for handler methods + */ + public function __call(string $name, array $args): mixed + { + return $this->handler->$name(...$args); + } + + /** + * Returns generator for all existing versions-languages combinations + * + * @return Generator + * @todo 4.0.0 consider more descpritive name + */ + public function all(): Generator + { + foreach ($this->model->kirby()->languages()->codes() as $lang) { + foreach ($this->dynamicVersions() as $version) { + if ($this->exists($version, $lang) === true) { + yield $version => $lang; + } + } + } + } + + /** + * Returns the absolute path to the content file + * @internal eventually should only exists in PlainTextContentStorage, + * when not relying anymore on language helper + * + * @param string $lang Code `'default'` in a single-lang installation + * + * @throws \Kirby\Exception\LogicException If the model type doesn't have a known content filename + */ + public function contentFile( + string $version, + string $lang, + bool $force = false + ): string { + $lang = $this->language($lang, $force); + return $this->handler->contentFile($version, $lang); + } + + /** + * Adapts all versions when converting languages + * @internal + */ + public function convertLanguage(string $from, string $to): void + { + $from = $this->language($from, true); + $to = $this->language($to, true); + + foreach ($this->dynamicVersions() as $version) { + $this->handler->move($version, $from, $version, $to); + } + } + + /** + * Creates a new version + * + * @param string|null $lang Code `'default'` in a single-lang installation + * @param array $fields Content fields + */ + public function create( + string $versionType, + string|null $lang, + array $fields + ): void { + $lang = $this->language($lang); + $this->handler->create($versionType, $lang, $fields); + } + + /** + * Returns the default version identifier for the model + * @internal + */ + public function defaultVersion(): string + { + if ( + $this->model instanceof Page === true && + $this->model->isDraft() === true + ) { + return 'changes'; + } + + return 'published'; + } + + /** + * Deletes an existing version in an idempotent way if it was already deleted + * + * @param string $lang Code `'default'` in a single-lang installation + */ + public function delete( + string $version, + string|null $lang = null, + bool $force = false + ): void { + $lang = $this->language($lang, $force); + $this->handler->delete($version, $lang); + } + + /** + * Deletes all versions when deleting a language + * @internal + */ + public function deleteLanguage(string|null $lang): void + { + $lang = $this->language($lang, true); + + foreach ($this->dynamicVersions() as $version) { + $this->handler->delete($version, $lang); + } + } + + /** + * Returns all versions availalbe for the model that can be updated + * @internal + */ + public function dynamicVersions(): array + { + $versions = ['changes']; + + if ( + $this->model instanceof Page === false || + $this->model->isDraft() === false + ) { + $versions[] = 'published'; + } + + return $versions; + } + + /** + * Checks if a version exists + * + * @param string|null $lang Code `'default'` in a single-lang installation; + * checks for "any language" if not provided + */ + public function exists( + string $version, + string|null $lang + ): bool { + if ($lang !== null) { + $lang = $this->language($lang); + } + + return $this->handler->exists($version, $lang); + } + + /** + * Returns the modification timestamp of a version + * if it exists + * + * @param string $lang Code `'default'` in a single-lang installation + */ + public function modified( + string $version, + string|null $lang = null + ): int|null { + $lang = $this->language($lang); + return $this->handler->modified($version, $lang); + } + + /** + * Returns the stored content fields + * + * @param string $lang Code `'default'` in a single-lang installation + * @return array + * + * @throws \Kirby\Exception\NotFoundException If the version does not exist + */ + public function read( + string $version, + string|null $lang = null + ): array { + $lang = $this->language($lang); + $this->ensureExistingVersion($version, $lang); + return $this->handler->read($version, $lang); + } + + /** + * Updates the modification timestamp of an existing version + * + * @param string $lang Code `'default'` in a single-lang installation + * + * @throws \Kirby\Exception\NotFoundException If the version does not exist + */ + public function touch( + string $version, + string|null $lang = null + ): void { + $lang = $this->language($lang); + $this->ensureExistingVersion($version, $lang); + $this->handler->touch($version, $lang); + } + + /** + * Touches all versions of a language + * @internal + */ + public function touchLanguage(string|null $lang): void + { + $lang = $this->language($lang, true); + + foreach ($this->dynamicVersions() as $version) { + if ($this->exists($version, $lang) === true) { + $this->handler->touch($version, $lang); + } + } + } + + /** + * Updates the content fields of an existing version + * + * @param string $lang Code `'default'` in a single-lang installation + * @param array $fields Content fields + * + * @throws \Kirby\Exception\NotFoundException If the version does not exist + */ + public function update( + string $version, + string|null $lang = null, + array $fields = [] + ): void { + $lang = $this->language($lang); + $this->ensureExistingVersion($version, $lang); + $this->handler->update($version, $lang, $fields); + } + + /** + * @throws \Kirby\Exception\NotFoundException If the version does not exist + */ + protected function ensureExistingVersion( + string $version, + string $lang + ): void { + if ($this->exists($version, $lang) !== true) { + throw new NotFoundException('Version "' . $version . ' (' . $lang . ')" does not already exist'); + } + } + + /** + * Converts a "user-facing" language code to a "raw" language code to be + * used for storage + * + * @param bool $force If set to `true`, the language code is not validated + * @return string Language code + */ + protected function language( + string|null $languageCode = null, + bool $force = false + ): string { + // in force mode, use the provided language code even in single-lang for + // compatibility with the previous behavior in `$model->contentFile()` + if ($force === true) { + return $languageCode ?? 'default'; + } + + // in multi-lang, … + if ($this->model->kirby()->multilang() === true) { + // look up the actual language object if possible + $language = $this->model->kirby()->language($languageCode); + + // validate the language code + if ($language === null) { + throw new InvalidArgumentException('Invalid language: ' . $languageCode); + } + + return $language->code(); + } + + // otherwise use hardcoded "default" code for single lang + return 'default'; + } +} diff --git a/kirby/src/Content/ContentStorageHandler.php b/kirby/src/Content/ContentStorageHandler.php new file mode 100644 index 0000000..6f39d11 --- /dev/null +++ b/kirby/src/Content/ContentStorageHandler.php @@ -0,0 +1,96 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier + * @license https://getkirby.com/license + */ +interface ContentStorageHandler +{ + public function __construct(ModelWithContent $model); + + /** + * Creates a new version + * + * @param string $lang Code `'default'` in a single-lang installation + * @param array $fields Content fields + */ + public function create(string $versionType, string $lang, array $fields): void; + + /** + * Deletes an existing version in an idempotent way if it was already deleted + * + * @param string $lang Code `'default'` in a single-lang installation + */ + public function delete(string $version, string $lang): void; + + /** + * Checks if a version exists + * + * @param string|null $lang Code `'default'` in a single-lang installation; + * checks for "any language" if not provided + */ + public function exists(string $version, string|null $lang): bool; + + /** + * Returns the modification timestamp of a version if it exists + * + * @param string $lang Code `'default'` in a single-lang installation + */ + public function modified(string $version, string $lang): int|null; + + /** + * Moves content from one version-language combination to another + * + * @param string $fromLang Code `'default'` in a single-lang installation + * @param string $toLang Code `'default'` in a single-lang installation + */ + public function move( + string $fromVersion, + string $fromLang, + string $toVersion, + string $toLang + ): void; + + /** + * Returns the stored content fields + * + * @param string $lang Code `'default'` in a single-lang installation + * @return array + * + * @throws \Kirby\Exception\NotFoundException If the version does not exist + */ + public function read(string $version, string $lang): array; + + /** + * Updates the modification timestamp of an existing version + * + * @param string $lang Code `'default'` in a single-lang installation + * + * @throws \Kirby\Exception\NotFoundException If the version does not exist + */ + public function touch(string $version, string $lang): void; + + /** + * Updates the content fields of an existing version + * + * @param string $lang Code `'default'` in a single-lang installation + * @param array $fields Content fields + * + * @throws \Kirby\Exception\NotFoundException If the version does not exist + */ + public function update(string $version, string $lang, array $fields): void; +} diff --git a/kirby/src/Cms/ContentTranslation.php b/kirby/src/Content/ContentTranslation.php similarity index 56% rename from kirby/src/Cms/ContentTranslation.php rename to kirby/src/Content/ContentTranslation.php index 8f1e5be..c04ec32 100644 --- a/kirby/src/Cms/ContentTranslation.php +++ b/kirby/src/Content/ContentTranslation.php @@ -1,15 +1,15 @@ * @link https://getkirby.com * @copyright Bastian Allgeier @@ -17,48 +17,31 @@ use Kirby\Toolkit\Properties; */ class ContentTranslation { - use Properties; - - /** - * @var string - */ - protected $code; - - /** - * @var array - */ - protected $content; - - /** - * @var string - */ - protected $contentFile; - - /** - * @var Model - */ - protected $parent; - - /** - * @var string - */ - protected $slug; + protected string $code; + protected array|null $content; + protected string $contentFile; + protected ModelWithContent $parent; + protected string|null $slug; /** * Creates a new translation object - * - * @param array $props */ public function __construct(array $props) { - $this->setRequiredProperties($props, ['parent', 'code']); - $this->setOptionalProperties($props, ['slug', 'content']); + $this->code = $props['code']; + $this->parent = $props['parent']; + $this->slug = $props['slug'] ?? null; + + if ($content = $props['content'] ?? null) { + $this->content = array_change_key_case($content); + } else { + $this->content = null; + } } /** * Improve `var_dump` output - * - * @return array + * @codeCoverageIgnore */ public function __debugInfo(): array { @@ -68,8 +51,6 @@ class ContentTranslation /** * Returns the language code of the * translation - * - * @return string */ public function code(): string { @@ -79,8 +60,6 @@ class ContentTranslation /** * Returns the translation content * as plain array - * - * @return array */ public function content(): array { @@ -92,9 +71,10 @@ class ContentTranslation $this->isDefault() === false && $defaultLanguage = $parent->kirby()->defaultLanguage() ) { - if ($default = $parent->translation($defaultLanguage->code())?->content()) { - $content = array_merge($default, $content); - } + $content = array_merge( + $parent->translation($defaultLanguage->code())?->content() ?? [], + $content + ); } return $content; @@ -102,12 +82,19 @@ class ContentTranslation /** * Absolute path to the translation content file - * - * @return string */ public function contentFile(): string { - return $this->contentFile = $this->parent->contentFile($this->code, true); + // temporary compatibility change (TODO: take this from the parent `ModelVersion` object) + $identifier = $this->parent::CLASS_ALIAS === 'page' && $this->parent->isDraft() === true ? + 'changes' : + 'published'; + + return $this->contentFile = $this->parent->storage()->contentFile( + $identifier, + $this->code, + true + ); } /** @@ -122,8 +109,6 @@ class ContentTranslation /** * Returns the translation code as id - * - * @return string */ public function id(): string { @@ -133,77 +118,22 @@ class ContentTranslation /** * Checks if the this is the default translation * of the model - * - * @return bool */ public function isDefault(): bool { - if ($defaultLanguage = $this->parent->kirby()->defaultLanguage()) { - return $this->code() === $defaultLanguage->code(); - } - - return false; + return $this->code() === $this->parent->kirby()->defaultLanguage()?->code(); } /** * Returns the parent page, file or site object - * - * @return \Kirby\Cms\Model */ - public function parent() + public function parent(): ModelWithContent { return $this->parent; } - /** - * @param string $code - * @return $this - */ - protected function setCode(string $code) - { - $this->code = $code; - return $this; - } - - /** - * @param array|null $content - * @return $this - */ - protected function setContent(array $content = null) - { - if ($content !== null) { - $this->content = array_change_key_case($content); - } else { - $this->content = null; - } - - return $this; - } - - /** - * @param \Kirby\Cms\Model $parent - * @return $this - */ - protected function setParent(Model $parent) - { - $this->parent = $parent; - return $this; - } - - /** - * @param string|null $slug - * @return $this - */ - protected function setSlug(string $slug = null) - { - $this->slug = $slug; - return $this; - } - /** * Returns the custom translation slug - * - * @return string|null */ public function slug(): string|null { @@ -213,22 +143,23 @@ class ContentTranslation /** * Merge the old and new data * - * @param array|null $data - * @param bool $overwrite * @return $this */ - public function update(array $data = null, bool $overwrite = false) + public function update(array $data = null, bool $overwrite = false): static { $data = array_change_key_case((array)$data); - $this->content = $overwrite === true ? $data : array_merge($this->content(), $data); + + $this->content = match ($overwrite) { + true => $data, + default => array_merge($this->content(), $data) + }; + return $this; } /** * Converts the most important translation * props to an array - * - * @return array */ public function toArray(): array { diff --git a/kirby/src/Cms/Field.php b/kirby/src/Content/Field.php similarity index 67% rename from kirby/src/Cms/Field.php rename to kirby/src/Content/Field.php index 710f36c..0082063 100644 --- a/kirby/src/Cms/Field.php +++ b/kirby/src/Content/Field.php @@ -1,8 +1,9 @@ myField()->lower(); * ``` * - * @package Kirby Cms + * @package Kirby Content * @author Bastian Allgeier * @link https://getkirby.com * @copyright Bastian Allgeier @@ -28,49 +29,48 @@ class Field { /** * Field method aliases - * - * @var array */ - public static $aliases = []; + public static array $aliases = []; /** * The field name - * - * @var string */ - protected $key; + protected string $key; /** * Registered field methods - * - * @var array */ - public static $methods = []; + public static array $methods = []; /** * The parent object if available. * This will be the page, site, user or file * to which the content belongs - * - * @var Model */ - protected $parent; + protected ModelWithContent|null $parent; /** * The value of the field - * - * @var mixed */ - public $value; + public mixed $value; + + /** + * Creates a new field object + */ + public function __construct( + ModelWithContent|null $parent, + string $key, + mixed $value + ) { + $this->key = $key; + $this->value = $value; + $this->parent = $parent; + } /** * Magic caller for field methods - * - * @param string $method - * @param array $arguments - * @return mixed */ - public function __call(string $method, array $arguments = []) + public function __call(string $method, array $arguments = []): mixed { $method = strtolower($method); @@ -89,27 +89,13 @@ class Field return $this; } - /** - * Creates a new field object - * - * @param object|null $parent - * @param string $key - * @param mixed $value - */ - public function __construct(object|null $parent, string $key, $value) - { - $this->key = $key; - $this->value = $value; - $this->parent = $parent; - } - /** * Simplifies the var_dump result + * @codeCoverageIgnore * * @see Field::toArray - * @return array */ - public function __debugInfo() + public function __debugInfo(): array { return $this->toArray(); } @@ -119,7 +105,6 @@ class Field * or stringify the entire object * * @see Field::toString - * @return string */ public function __toString(): string { @@ -128,8 +113,6 @@ class Field /** * Checks if the field exists in the content data array - * - * @return bool */ public function exists(): bool { @@ -138,18 +121,16 @@ class Field /** * Checks if the field content is empty - * - * @return bool */ public function isEmpty(): bool { - return empty($this->value) === true && in_array($this->value, [0, '0', false], true) === false; + return + empty($this->value) === true && + in_array($this->value, [0, '0', false], true) === false; } /** * Checks if the field content is not empty - * - * @return bool */ public function isNotEmpty(): bool { @@ -158,8 +139,6 @@ class Field /** * Returns the name of the field - * - * @return string */ public function key(): string { @@ -168,9 +147,8 @@ class Field /** * @see Field::parent() - * @return \Kirby\Cms\Model|null */ - public function model() + public function model(): ModelWithContent|null { return $this->parent; } @@ -178,10 +156,9 @@ class Field /** * Provides a fallback if the field value is empty * - * @param mixed $fallback * @return $this|static */ - public function or($fallback = null) + public function or(mixed $fallback = null): static { if ($this->isNotEmpty()) { return $this; @@ -198,18 +175,14 @@ class Field /** * Returns the parent object of the field - * - * @return \Kirby\Cms\Model|null */ - public function parent() + public function parent(): ModelWithContent|null { return $this->parent; } /** * Converts the Field object to an array - * - * @return array */ public function toArray(): array { @@ -218,8 +191,6 @@ class Field /** * Returns the field value as string - * - * @return string */ public function toString(): string { @@ -230,27 +201,19 @@ class Field * Returns the field content. If a new value is passed, * the modified field will be returned. Otherwise it * will return the field value. - * - * @param string|\Closure $value - * @return mixed - * @throws \Kirby\Exception\InvalidArgumentException */ - public function value($value = null) + public function value(string|Closure $value = null): mixed { if ($value === null) { return $this->value; } - if (is_scalar($value)) { - $value = (string)$value; - } elseif (is_callable($value)) { - $value = (string)$value->call($this, $this->value); - } else { - throw new InvalidArgumentException('Invalid field value type: ' . gettype($value)); + if ($value instanceof Closure) { + $value = $value->call($this, $this->value); } $clone = clone $this; - $clone->value = $value; + $clone->value = (string)$value; return $clone; } diff --git a/kirby/src/Content/PlainTextContentStorageHandler.php b/kirby/src/Content/PlainTextContentStorageHandler.php new file mode 100644 index 0000000..756816a --- /dev/null +++ b/kirby/src/Content/PlainTextContentStorageHandler.php @@ -0,0 +1,253 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier + * @license https://getkirby.com/license + */ +class PlainTextContentStorageHandler implements ContentStorageHandler +{ + public function __construct(protected ModelWithContent $model) + { + } + + /** + * Creates a new version + * + * @param string $lang Code `'default'` in a single-lang installation + * @param array $fields Content fields + */ + public function create(string $versionType, string $lang, array $fields): void + { + $success = Data::write($this->contentFile($versionType, $lang), $fields); + + // @codeCoverageIgnoreStart + if ($success !== true) { + throw new Exception('Could not write new content file'); + } + // @codeCoverageIgnoreEnd + } + + /** + * Deletes an existing version in an idempotent way if it was already deleted + * + * @param string $lang Code `'default'` in a single-lang installation + */ + public function delete(string $version, string $lang): void + { + $contentFile = $this->contentFile($version, $lang); + $success = F::unlink($contentFile); + + // @codeCoverageIgnoreStart + if ($success !== true) { + throw new Exception('Could not delete content file'); + } + // @codeCoverageIgnoreEnd + + // clean up empty directories + $contentDir = dirname($contentFile); + if ( + Dir::exists($contentDir) === true && + Dir::isEmpty($contentDir) === true + ) { + $success = rmdir($contentDir); + + // @codeCoverageIgnoreStart + if ($success !== true) { + throw new Exception('Could not delete empty content directory'); + } + // @codeCoverageIgnoreEnd + } + } + + /** + * Checks if a version exists + * + * @param string|null $lang Code `'default'` in a single-lang installation; + * checks for "any language" if not provided + */ + public function exists(string $version, string|null $lang): bool + { + if ($lang === null) { + foreach ($this->contentFiles($version) as $file) { + if (is_file($file) === true) { + return true; + } + } + + return false; + } + + return is_file($this->contentFile($version, $lang)) === true; + } + + /** + * Returns the modification timestamp of a version + * if it exists + * + * @param string $lang Code `'default'` in a single-lang installation + */ + public function modified(string $version, string $lang): int|null + { + $modified = F::modified($this->contentFile($version, $lang)); + + if (is_int($modified) === true) { + return $modified; + } + + return null; + } + + /** + * Returns the stored content fields + * + * @param string $lang Code `'default'` in a single-lang installation + * @return array + * + * @throws \Kirby\Exception\NotFoundException If the version does not exist + */ + public function read(string $version, string $lang): array + { + return Data::read($this->contentFile($version, $lang)); + } + + /** + * Updates the modification timestamp of an existing version + * + * @param string $lang Code `'default'` in a single-lang installation + * + * @throws \Kirby\Exception\NotFoundException If the version does not exist + */ + public function touch(string $version, string $lang): void + { + $success = touch($this->contentFile($version, $lang)); + + // @codeCoverageIgnoreStart + if ($success !== true) { + throw new Exception('Could not touch existing content file'); + } + // @codeCoverageIgnoreEnd + } + + /** + * Updates the content fields of an existing version + * + * @param string $lang Code `'default'` in a single-lang installation + * @param array $fields Content fields + * + * @throws \Kirby\Exception\NotFoundException If the version does not exist + */ + public function update(string $version, string $lang, array $fields): void + { + $success = Data::write($this->contentFile($version, $lang), $fields); + + // @codeCoverageIgnoreStart + if ($success !== true) { + throw new Exception('Could not write existing content file'); + } + // @codeCoverageIgnoreEnd + } + + /** + * Returns the absolute path to the content file + * @internal To be made `protected` when the CMS core no longer relies on it + * + * @param string $lang Code `'default'` in a single-lang installation + * + * @throws \Kirby\Exception\LogicException If the model type doesn't have a known content filename + */ + public function contentFile(string $version, string $lang): string + { + if (in_array($version, ['published', 'changes']) !== true) { + throw new InvalidArgumentException('Invalid version identifier "' . $version . '"'); + } + + $extension = $this->model->kirby()->contentExtension(); + $directory = $this->model->root(); + + $directory = match ($this->model::CLASS_ALIAS) { + 'file' => dirname($this->model->root()), + default => $this->model->root() + }; + + $filename = match ($this->model::CLASS_ALIAS) { + 'file' => $this->model->filename(), + 'page' => $this->model->intendedTemplate()->name(), + 'site', + 'user' => $this->model::CLASS_ALIAS, + // @codeCoverageIgnoreStart + default => throw new LogicException('Cannot determine content filename for model type "' . $this->model::CLASS_ALIAS . '"') + // @codeCoverageIgnoreEnd + }; + + if ($this->model::CLASS_ALIAS === 'page' && $this->model->isDraft() === true) { + // changes versions don't need anything extra + // (drafts already have the `_drafts` prefix in their root), + // but a published version is not possible + if ($version === 'published') { + throw new LogicException('Drafts cannot have a published content file'); + } + } elseif ($version === 'changes') { + // other model type or published page that has a changes subfolder + $directory .= '/_changes'; + } + + if ($lang !== 'default') { + return $directory . '/' . $filename . '.' . $lang . '.' . $extension; + } + + return $directory . '/' . $filename . '.' . $extension; + } + + /** + * Returns an array with content files of all languages + * @internal To be made `protected` when the CMS core no longer relies on it + */ + public function contentFiles(string $version): array + { + if ($this->model->kirby()->multilang() === true) { + return $this->model->kirby()->languages()->values( + fn ($lang) => $this->contentFile($version, $lang) + ); + } + + return [ + $this->contentFile($version, 'default') + ]; + } + + /** + * Moves content from one version-language combination to another + * + * @param string $fromLang Code `'default'` in a single-lang installation + * @param string $toLang Code `'default'` in a single-lang installation + */ + public function move( + string $fromVersion, + string $fromLang, + string $toVersion, + string $toLang + ): void { + F::move( + $this->contentFile($fromVersion, $fromLang), + $this->contentFile($toVersion, $toLang) + ); + } +} diff --git a/kirby/src/Data/Data.php b/kirby/src/Data/Data.php index c4b89f9..b6a1cb6 100644 --- a/kirby/src/Data/Data.php +++ b/kirby/src/Data/Data.php @@ -38,11 +38,11 @@ class Data * All registered handlers */ public static array $handlers = [ - 'json' => 'Kirby\Data\Json', - 'php' => 'Kirby\Data\PHP', - 'txt' => 'Kirby\Data\Txt', - 'xml' => 'Kirby\Data\Xml', - 'yaml' => 'Kirby\Data\Yaml', + 'json' => Json::class, + 'php' => PHP::class, + 'txt' => Txt::class, + 'xml' => Xml::class, + 'yaml' => Yaml::class ]; /** @@ -54,10 +54,11 @@ class Data $type = strtolower($type); // find a handler or alias - $alias = static::$aliases[$type] ?? null; - $handler = - static::$handlers[$type] ?? - ($alias ? static::$handlers[$alias] ?? null : null); + $handler = static::$handlers[$type] ?? null; + + if ($alias = static::$aliases[$type] ?? null) { + $handler ??= static::$handlers[$alias] ?? null; + } if ($handler === null || class_exists($handler) === false) { throw new Exception('Missing handler for type: "' . $type . '"'); @@ -95,7 +96,9 @@ class Data */ public static function read(string $file, string|null $type = null): array { - return static::handler($type ?? F::extension($file))->read($file); + $type ??= F::extension($file); + $handler = static::handler($type); + return $handler->read($file); } /** @@ -108,6 +111,8 @@ class Data $data = [], string|null $type = null ): bool { - return static::handler($type ?? F::extension($file))->write($file, $data); + $type ??= F::extension($file); + $handler = static::handler($type); + return $handler->write($file, $data); } } diff --git a/kirby/src/Data/Txt.php b/kirby/src/Data/Txt.php index 432d255..7ea7d06 100644 --- a/kirby/src/Data/Txt.php +++ b/kirby/src/Data/Txt.php @@ -65,11 +65,10 @@ class Txt extends Handler $result = $key . ':'; // multi-line content - if (preg_match('!\R!', $value) === 1) { - $result .= "\n\n"; - } else { - $result .= ' '; - } + $result .= match (preg_match('!\R!', $value)) { + 1 => "\n\n", + default => ' ', + }; $result .= $value; diff --git a/kirby/src/Database/Database.php b/kirby/src/Database/Database.php index 9d480e9..1284017 100644 --- a/kirby/src/Database/Database.php +++ b/kirby/src/Database/Database.php @@ -3,6 +3,8 @@ namespace Kirby\Database; use Closure; +use Kirby\Database\Sql\Mysql; +use Kirby\Database\Sql\Sqlite; use Kirby\Exception\InvalidArgumentException; use Kirby\Toolkit\A; use Kirby\Toolkit\Collection; @@ -536,7 +538,7 @@ class Database * MySQL database connector */ Database::$types['mysql'] = [ - 'sql' => 'Kirby\Database\Sql\Mysql', + 'sql' => Mysql::class, 'dsn' => function (array $params): string { if (isset($params['host']) === false && isset($params['socket']) === false) { throw new InvalidArgumentException('The mysql connection requires either a "host" or a "socket" parameter'); @@ -574,7 +576,7 @@ Database::$types['mysql'] = [ * SQLite database connector */ Database::$types['sqlite'] = [ - 'sql' => 'Kirby\Database\Sql\Sqlite', + 'sql' => Sqlite::class, 'dsn' => function (array $params): string { if (isset($params['database']) === false) { throw new InvalidArgumentException('The sqlite connection requires a "database" parameter'); diff --git a/kirby/src/Database/Db.php b/kirby/src/Database/Db.php index 01c3f22..3b4648d 100644 --- a/kirby/src/Database/Db.php +++ b/kirby/src/Database/Db.php @@ -191,7 +191,7 @@ Db::$queries['column'] = function ( * @param array $values An array of values which should be inserted * @return mixed Returns the last inserted id on success or false */ -Db::$queries['insert'] = function (string $table, array $values) { +Db::$queries['insert'] = function (string $table, array $values): mixed { return Db::table($table)->insert($values); }; @@ -225,9 +225,8 @@ Db::$queries['delete'] = function (string $table, $where = null): bool { * * @param string $table The name of the table which should be queried * @param mixed $where An optional WHERE clause - * @return int */ -Db::$queries['count'] = function (string $table, $where = null): int { +Db::$queries['count'] = function (string $table, mixed $where = null): int { return Db::table($table)->where($where)->count(); }; diff --git a/kirby/src/Database/Query.php b/kirby/src/Database/Query.php index 7e82e62..f3acd71 100644 --- a/kirby/src/Database/Query.php +++ b/kirby/src/Database/Query.php @@ -259,8 +259,11 @@ class Query * @param string $type The join type. Uses an inner join by default * @return $this */ - public function join(string $table, string $on, string $type = 'JOIN'): static - { + public function join( + string $table, + string $on, + string $type = 'JOIN' + ): static { $join = [ 'table' => $table, 'on' => $on, @@ -678,7 +681,7 @@ class Query $collection = $this ->offset($pagination->offset()) ->limit($pagination->limit()) - ->iterator('Kirby\Toolkit\Collection') + ->iterator(Collection::class) ->all(); $this->iterator($iterator); diff --git a/kirby/src/Database/Sql.php b/kirby/src/Database/Sql.php index e5ec88e..136b394 100644 --- a/kirby/src/Database/Sql.php +++ b/kirby/src/Database/Sql.php @@ -144,7 +144,7 @@ abstract class Sql * Combines an identifier (table and column) * * @param $values bool Whether the identifier is going to be used for a VALUES clause; - * only relevant for SQLite + * only relevant for SQLite */ public function combineIdentifier(string $table, string $column, bool $values = false): string { diff --git a/kirby/src/Email/Body.php b/kirby/src/Email/Body.php index 8fc0a0b..a25904a 100644 --- a/kirby/src/Email/Body.php +++ b/kirby/src/Email/Body.php @@ -17,17 +17,29 @@ use Kirby\Toolkit\Properties; */ class Body { - use Properties; - - protected string|null $html = null; - protected string|null $text = null; + protected string|null $html; + protected string|null $text; /** * Email body constructor */ public function __construct(array $props = []) { - $this->setProperties($props); + $this->html = $props['html'] ?? null; + $this->text = $props['text'] ?? null; + } + + /** + * Creates a new instance while + * merging initial and new properties + * @deprecated 4.0.0 + */ + public function clone(array $props = []): static + { + return new static(array_merge_recursive([ + 'html' => $this->html, + 'text' => $this->text + ], $props)); } /** @@ -47,24 +59,13 @@ class Body } /** - * Sets the HTML content for the email body - * - * @return $this + * @since 4.0.0 */ - protected function setHtml(string|null $html = null): static + public function toArray(): array { - $this->html = $html; - return $this; - } - - /** - * Sets the plain text content for the email body - * - * @return $this - */ - protected function setText(string|null $text = null): static - { - $this->text = $text; - return $this; + return [ + 'html' => $this->html(), + 'text' => $this->text() + ]; } } diff --git a/kirby/src/Email/Email.php b/kirby/src/Email/Email.php index 73fb883..2f1b15f 100644 --- a/kirby/src/Email/Email.php +++ b/kirby/src/Email/Email.php @@ -4,7 +4,7 @@ namespace Kirby\Email; use Closure; use Exception; -use Kirby\Toolkit\Properties; +use Kirby\Exception\InvalidArgumentException; use Kirby\Toolkit\V; /** @@ -19,8 +19,6 @@ use Kirby\Toolkit\V; */ class Email { - use Properties; - /** * If set to `true`, the debug mode is enabled * for all emails @@ -33,56 +31,48 @@ class Email */ public static array $emails = []; - /** - * @var array - */ - protected array|null $attachments = null; - - protected Body|null $body = null; - - /** - * @var array - */ - protected array|null $bcc = null; - - protected Closure|null $beforeSend = null; - - /** - * @var array - */ - protected array|null $cc = null; - - /** - * @var string - */ - protected string|null $from = null; - protected string|null $fromName = null; - protected bool $isSent = false; - /** - * @var string - */ - protected string|null $replyTo = null; - protected string|null $replyToName = null; - - /** - * @var string - */ - protected string|null $subject = null; - - /** - * @var array - */ - protected array|null $to = null; - protected array|null $transport = null; + protected array $attachments; + protected Body $body; + protected array $bcc; + protected Closure|null $beforeSend; + protected array $cc; + protected string $from; + protected string|null $fromName; + protected string $replyTo; + protected string|null $replyToName; + protected string $subject; + protected array $to; + protected array|null $transport; /** * Email constructor */ public function __construct(array $props = [], bool $debug = false) { - $this->setProperties($props); + foreach (['body', 'from', 'to', 'subject'] as $required) { + if (isset($props[$required]) === false) { + throw new InvalidArgumentException('The property "' . $required . '" is required'); + } + } + + if (is_string($props['body']) === true) { + $props['body'] = ['text' => $props['body']]; + } + + $this->attachments = $props['attachments'] ?? []; + $this->bcc = $this->resolveEmail($props['bcc'] ?? null); + $this->beforeSend = $props['beforeSend'] ?? null; + $this->body = new Body($props['body']); + $this->cc = $this->resolveEmail($props['cc'] ?? null); + $this->from = $this->resolveEmail($props['from'], false); + $this->fromName = $props['fromName'] ?? null; + $this->replyTo = $this->resolveEmail($props['replyTo'] ?? null, false); + $this->replyToName = $props['replyToName'] ?? null; + $this->subject = $props['subject']; + $this->to = $this->resolveEmail($props['to']); + $this->transport = $props['transport'] ?? null; // @codeCoverageIgnoreStart if (static::$debug === false && $debug === false) { @@ -134,6 +124,29 @@ class Email return $this->cc; } + /** + * Creates a new instance while + * merging initial and new properties + * @deprecated 4.0.0 + */ + public function clone(array $props = []): static + { + return new static(array_merge_recursive([ + 'attachments' => $this->attachments, + 'bcc' => $this->bcc, + 'beforeSend' => $this->beforeSend, + 'body' => $this->body->toArray(), + 'cc' => $this->cc, + 'from' => $this->from, + 'fromName' => $this->fromName, + 'replyTo' => $this->replyTo, + 'replyToName' => $this->replyToName, + 'subject' => $this->subject, + 'to' => $this->to, + 'transport' => $this->transport + ], $props)); + } + /** * Returns default transport settings */ @@ -237,142 +250,6 @@ class Email return $this->isSent = true; } - /** - * Sets the email attachments - * - * @return $this - */ - protected function setAttachments(array|null $attachments = null): static - { - $this->attachments = $attachments ?? []; - return $this; - } - - /** - * Sets the email body - * - * @return $this - */ - protected function setBody(string|array $body): static - { - if (is_string($body) === true) { - $body = ['text' => $body]; - } - - $this->body = new Body($body); - return $this; - } - - /** - * Sets "bcc" recipients - * - * @return $this - */ - protected function setBcc(string|array|null $bcc = null): static - { - $this->bcc = $this->resolveEmail($bcc); - return $this; - } - - /** - * Sets the "beforeSend" callback - * - * @return $this - */ - protected function setBeforeSend(Closure|null $beforeSend = null): static - { - $this->beforeSend = $beforeSend; - return $this; - } - - /** - * Sets "cc" recipients - * - * @return $this - */ - protected function setCc(string|array|null $cc = null): static - { - $this->cc = $this->resolveEmail($cc); - return $this; - } - - /** - * Sets the "from" email address - * - * @return $this - */ - protected function setFrom(string $from): static - { - $this->from = $this->resolveEmail($from, false); - return $this; - } - - /** - * Sets the "from" name - * - * @return $this - */ - protected function setFromName(string|null $fromName = null): static - { - $this->fromName = $fromName; - return $this; - } - - /** - * Sets the "reply to" email address - * - * @return $this - */ - protected function setReplyTo(string|null $replyTo = null): static - { - $this->replyTo = $this->resolveEmail($replyTo, false); - return $this; - } - - /** - * Sets the "reply to" name - * - * @return $this - */ - protected function setReplyToName(string|null $replyToName = null): static - { - $this->replyToName = $replyToName; - return $this; - } - - /** - * Sets the email subject - * - * @return $this - */ - protected function setSubject(string $subject): static - { - $this->subject = $subject; - return $this; - } - - /** - * Sets the recipients of the email - * - * @return $this - */ - protected function setTo(string|array $to): static - { - $this->to = $this->resolveEmail($to); - return $this; - } - - /** - * Sets the email transport settings - * - * @return $this - */ - protected function setTransport(array|null $transport = null): static - { - $this->transport = $transport; - return $this; - } - /** * Returns the email subject */ @@ -396,4 +273,24 @@ class Email { return $this->transport ?? $this->defaultTransport(); } + + /** + * @since 4.0.0 + */ + public function toArray(): array + { + return [ + 'attachments' => $this->attachments(), + 'bcc' => $this->bcc(), + 'body' => $this->body()->toArray(), + 'cc' => $this->cc(), + 'from' => $this->from(), + 'fromName' => $this->fromName(), + 'replyTo' => $this->replyTo(), + 'replyToName' => $this->replyToName(), + 'subject' => $this->subject(), + 'to' => $this->to(), + 'transport' => $this->transport() + ]; + } } diff --git a/kirby/src/Exception/AuthException.php b/kirby/src/Exception/AuthException.php new file mode 100644 index 0000000..1ea6203 --- /dev/null +++ b/kirby/src/Exception/AuthException.php @@ -0,0 +1,21 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier + * @license https://opensource.org/licenses/MIT + */ +class AuthException extends Exception +{ + protected static string $defaultKey = 'auth'; + protected static string $defaultFallback = 'Unauthenticated'; + protected static int $defaultHttpCode = 401; +} diff --git a/kirby/src/Exception/BadMethodCallException.php b/kirby/src/Exception/BadMethodCallException.php index 68c1f05..f8a1d1b 100644 --- a/kirby/src/Exception/BadMethodCallException.php +++ b/kirby/src/Exception/BadMethodCallException.php @@ -14,8 +14,8 @@ namespace Kirby\Exception; */ class BadMethodCallException extends Exception { - protected static $defaultKey = 'invalidMethod'; - protected static $defaultFallback = 'The method "{ method }" does not exist'; - protected static $defaultHttpCode = 400; - protected static $defaultData = ['method' => null]; + protected static string $defaultKey = 'invalidMethod'; + protected static string $defaultFallback = 'The method "{ method }" does not exist'; + protected static int $defaultHttpCode = 400; + protected static array $defaultData = ['method' => null]; } diff --git a/kirby/src/Exception/DuplicateException.php b/kirby/src/Exception/DuplicateException.php index 74bc859..1bea2bf 100644 --- a/kirby/src/Exception/DuplicateException.php +++ b/kirby/src/Exception/DuplicateException.php @@ -15,7 +15,7 @@ namespace Kirby\Exception; */ class DuplicateException extends Exception { - protected static $defaultKey = 'duplicate'; - protected static $defaultFallback = 'The entry exists'; - protected static $defaultHttpCode = 400; + protected static string $defaultKey = 'duplicate'; + protected static string $defaultFallback = 'The entry exists'; + protected static int $defaultHttpCode = 400; } diff --git a/kirby/src/Exception/ErrorPageException.php b/kirby/src/Exception/ErrorPageException.php index 7e9ed69..bd26c6f 100644 --- a/kirby/src/Exception/ErrorPageException.php +++ b/kirby/src/Exception/ErrorPageException.php @@ -15,7 +15,7 @@ namespace Kirby\Exception; */ class ErrorPageException extends Exception { - protected static $defaultKey = 'errorPage'; - protected static $defaultFallback = 'Triggered error page'; - protected static $defaultHttpCode = 404; + protected static string $defaultKey = 'errorPage'; + protected static string $defaultFallback = 'Triggered error page'; + protected static int $defaultHttpCode = 404; } diff --git a/kirby/src/Exception/Exception.php b/kirby/src/Exception/Exception.php index d3a0ba9..ae16686 100644 --- a/kirby/src/Exception/Exception.php +++ b/kirby/src/Exception/Exception.php @@ -2,6 +2,7 @@ namespace Kirby\Exception; +use Kirby\Cms\App; use Kirby\Http\Environment; use Kirby\Toolkit\I18n; use Kirby\Toolkit\Str; @@ -21,48 +22,39 @@ class Exception extends \Exception { /** * Data variables that can be used inside the exception message - * - * @var array */ - protected $data; + protected array $data; /** * HTTP code that corresponds with the exception - * - * @var int */ - protected $httpCode; + protected int $httpCode; /** * Additional details that are not included in the exception message - * - * @var array */ - protected $details; + protected array $details; /** - * Whether the exception message could be translated into the user's language - * - * @var bool + * Whether the exception message could be translated + * into the user's language */ - protected $isTranslated = true; + protected bool $isTranslated = true; /** * Defaults that can be overridden by specific * exception classes */ - protected static $defaultKey = 'general'; - protected static $defaultFallback = 'An error occurred'; - protected static $defaultData = []; - protected static $defaultHttpCode = 500; - protected static $defaultDetails = []; + protected static string $defaultKey = 'general'; + protected static string $defaultFallback = 'An error occurred'; + protected static array $defaultData = []; + protected static int $defaultHttpCode = 500; + protected static array $defaultDetails = []; /** * Prefix for the exception key (e.g. 'error.general') - * - * @var string */ - private static $prefix = 'error'; + private static string $prefix = 'error'; /** * Class constructor @@ -71,7 +63,7 @@ class Exception extends \Exception * 'data', 'httpCode', 'details' and 'previous') or * just the message string */ - public function __construct($args = []) + public function __construct(array|string $args = []) { // set data and httpCode from provided arguments or defaults $this->data = $args['data'] ?? static::$defaultData; @@ -79,19 +71,25 @@ class Exception extends \Exception $this->details = $args['details'] ?? static::$defaultDetails; // define the Exception key - $key = self::$prefix . '.' . ($args['key'] ?? static::$defaultKey); + $key = $args['key'] ?? static::$defaultKey; + + if (Str::startsWith($key, self::$prefix . '.') === false) { + $key = self::$prefix . '.' . $key; + } if (is_string($args) === true) { $this->isTranslated = false; parent::__construct($args); } else { // define whether message can/should be translated - $translate = ($args['translate'] ?? true) === true && class_exists('Kirby\Cms\App') === true; + $translate = + ($args['translate'] ?? true) === true && + class_exists(App::class) === true; // fallback waterfall for message string $message = null; - if ($translate) { + if ($translate === true) { // 1. translation for provided key in current language // 2. translation for provided key in default language if (isset($args['key']) === true) { @@ -106,7 +104,7 @@ class Exception extends \Exception $this->isTranslated = false; } - if ($translate) { + if ($translate === true) { // 4. translation for default key in current language // 5. translation for default key in default language if ($message === null) { @@ -122,11 +120,7 @@ class Exception extends \Exception } // format message with passed data - $message = Str::template($message, $this->data, [ - 'fallback' => '-', - 'start' => '{', - 'end' => '}' - ]); + $message = Str::template($message, $this->data, ['fallback' => '-']); // handover to Exception parent class constructor parent::__construct($message, 0, $args['previous'] ?? null); @@ -139,8 +133,6 @@ class Exception extends \Exception /** * Returns the file in which the Exception was created * relative to the document root - * - * @return string */ final public function getFileRelative(): string { @@ -156,8 +148,6 @@ class Exception extends \Exception /** * Returns the data variables from the message - * - * @return array */ final public function getData(): array { @@ -167,8 +157,6 @@ class Exception extends \Exception /** * Returns the additional details that are * not included in the message - * - * @return array */ final public function getDetails(): array { @@ -177,8 +165,6 @@ class Exception extends \Exception /** * Returns the exception key (error type) - * - * @return string */ final public function getKey(): string { @@ -188,8 +174,6 @@ class Exception extends \Exception /** * Returns the HTTP code that corresponds * with the exception - * - * @return array */ final public function getHttpCode(): int { @@ -199,8 +183,6 @@ class Exception extends \Exception /** * Returns whether the exception message could * be translated into the user's language - * - * @return bool */ final public function isTranslated(): bool { @@ -209,8 +191,6 @@ class Exception extends \Exception /** * Converts the object to an array - * - * @return array */ public function toArray(): array { diff --git a/kirby/src/Exception/InvalidArgumentException.php b/kirby/src/Exception/InvalidArgumentException.php index d6cef17..25603b7 100644 --- a/kirby/src/Exception/InvalidArgumentException.php +++ b/kirby/src/Exception/InvalidArgumentException.php @@ -14,8 +14,8 @@ namespace Kirby\Exception; */ class InvalidArgumentException extends Exception { - protected static $defaultKey = 'invalidArgument'; - protected static $defaultFallback = 'Invalid argument "{ argument }" in method "{ method }"'; - protected static $defaultHttpCode = 400; - protected static $defaultData = ['argument' => null, 'method' => null]; + protected static string $defaultKey = 'invalidArgument'; + protected static string $defaultFallback = 'Invalid argument "{ argument }" in method "{ method }"'; + protected static int $defaultHttpCode = 400; + protected static array $defaultData = ['argument' => null, 'method' => null]; } diff --git a/kirby/src/Exception/LogicException.php b/kirby/src/Exception/LogicException.php index d1fbc55..8fac228 100644 --- a/kirby/src/Exception/LogicException.php +++ b/kirby/src/Exception/LogicException.php @@ -14,7 +14,7 @@ namespace Kirby\Exception; */ class LogicException extends Exception { - protected static $defaultKey = 'logic'; - protected static $defaultFallback = 'This task cannot be finished'; - protected static $defaultHttpCode = 400; + protected static string $defaultKey = 'logic'; + protected static string $defaultFallback = 'This task cannot be finished'; + protected static int $defaultHttpCode = 400; } diff --git a/kirby/src/Exception/NotFoundException.php b/kirby/src/Exception/NotFoundException.php index 1f48c4b..5c7e284 100644 --- a/kirby/src/Exception/NotFoundException.php +++ b/kirby/src/Exception/NotFoundException.php @@ -14,7 +14,7 @@ namespace Kirby\Exception; */ class NotFoundException extends Exception { - protected static $defaultKey = 'notFound'; - protected static $defaultFallback = 'Not found'; - protected static $defaultHttpCode = 404; + protected static string $defaultKey = 'notFound'; + protected static string $defaultFallback = 'Not found'; + protected static int $defaultHttpCode = 404; } diff --git a/kirby/src/Exception/PermissionException.php b/kirby/src/Exception/PermissionException.php index 4863f66..ae82a66 100644 --- a/kirby/src/Exception/PermissionException.php +++ b/kirby/src/Exception/PermissionException.php @@ -15,7 +15,7 @@ namespace Kirby\Exception; */ class PermissionException extends Exception { - protected static $defaultKey = 'permission'; - protected static $defaultFallback = 'You are not allowed to do this'; - protected static $defaultHttpCode = 403; + protected static string $defaultKey = 'permission'; + protected static string $defaultFallback = 'You are not allowed to do this'; + protected static int $defaultHttpCode = 403; } diff --git a/kirby/src/Filesystem/Asset.php b/kirby/src/Filesystem/Asset.php index 1ced36b..021d6c8 100644 --- a/kirby/src/Filesystem/Asset.php +++ b/kirby/src/Filesystem/Asset.php @@ -27,18 +27,19 @@ class Asset /** * Relative file path */ - protected string|null $path = null; + protected string|null $path; + /** * Creates a new Asset object for the given path. */ public function __construct(string $path) { - $this->setProperties([ - 'path' => dirname($path), - 'root' => $this->kirby()->root('index') . '/' . $path, - 'url' => $this->kirby()->url('base') . '/' . $path - ]); + $this->root = $this->kirby()->root('index') . '/' . $path; + $this->url = $this->kirby()->url('base') . '/' . $path; + + $path = dirname($path); + $this->path = $path === '.' ? '' : $path; } /** @@ -46,7 +47,7 @@ class Asset * * @throws \Kirby\Exception\BadMethodCallException */ - public function __call(string $method, array $arguments = []) + public function __call(string $method, array $arguments = []): mixed { // public property access if (isset($this->$method) === true) { @@ -114,15 +115,4 @@ class Asset { return $this->path; } - - /** - * Setter for the path - * - * @return $this - */ - protected function setPath(string $path): static - { - $this->path = $path === '.' ? '' : $path; - return $this; - } } diff --git a/kirby/src/Filesystem/Dir.php b/kirby/src/Filesystem/Dir.php index b51b80b..43360f4 100644 --- a/kirby/src/Filesystem/Dir.php +++ b/kirby/src/Filesystem/Dir.php @@ -426,8 +426,10 @@ class Dir * subfolders have been modified for the last time. * * @param string $dir The path of the directory + * @param 'date'|'intl'|'strftime'|null $handler Custom date handler or `null` + * for the globally configured one */ - public static function modified(string $dir, string $format = null, string $handler = 'date'): int|string + public static function modified(string $dir, string $format = null, string|null $handler = null): int|string { $modified = filemtime($dir); $items = static::read($dir); diff --git a/kirby/src/Filesystem/F.php b/kirby/src/Filesystem/F.php index 10938a3..7d3b9bc 100644 --- a/kirby/src/Filesystem/F.php +++ b/kirby/src/Filesystem/F.php @@ -475,12 +475,13 @@ class F /** * Get the file's last modification time. * - * @param string $handler date, intl or strftime + * @param 'date'|'intl'|'strftime'|null $handler Custom date handler or `null` + * for the globally configured one */ public static function modified( string $file, string|IntlDateFormatter|null $format = null, - string $handler = 'date' + string|null $handler = null ): string|int|false { if (file_exists($file) !== true) { return false; @@ -727,7 +728,8 @@ class F } /** - * Sanitize a filename to strip unwanted special characters + * Sanitize a file's full name (filename and extension) + * to strip unwanted special characters * * * @@ -740,12 +742,34 @@ class F */ public static function safeName(string $string): string { - $name = static::name($string); - $extension = static::extension($string); - $safeName = Str::slug($name, '-', 'a-z0-9@._-'); - $safeExtension = empty($extension) === false ? '.' . Str::slug($extension) : ''; + $basename = static::safeBasename($string); + $extension = static::safeExtension($string); - return $safeName . $safeExtension; + if (empty($extension) === false) { + $extension = '.' . $extension; + } + + return $basename . $extension; + } + + /** + * Sanitize a file's name (without extension) + * @since 4.0.0 + */ + public static function safeBasename(string $string): string + { + $name = static::name($string); + return Str::slug($name, '-', 'a-z0-9@._-'); + } + + /** + * Sanitize a file's extension + * @since 4.0.0 + */ + public static function safeExtension(string $string): string + { + $extension = static::extension($string); + return Str::slug($extension); } /** diff --git a/kirby/src/Filesystem/File.php b/kirby/src/Filesystem/File.php index b56ae0d..8fa2191 100644 --- a/kirby/src/Filesystem/File.php +++ b/kirby/src/Filesystem/File.php @@ -10,7 +10,6 @@ use Kirby\Http\Response; use Kirby\Sane\Sane; use Kirby\Toolkit\Escape; use Kirby\Toolkit\Html; -use Kirby\Toolkit\Properties; use Kirby\Toolkit\V; /** @@ -27,23 +26,21 @@ use Kirby\Toolkit\V; */ class File { - use Properties; - /** * Parent file model * The model object must use the `\Kirby\Filesystem\IsFile` trait */ - protected object|null $model = null; + protected object|null $model; /** * Absolute file path */ - protected string|null $root = null; + protected string|null $root; /** * Absolute file URL */ - protected string|null $url = null; + protected string|null $url; /** * Validation rules to be used for `::match()` @@ -58,14 +55,15 @@ class File * * @param array|string|null $props Properties or deprecated `$root` string * @param string|null $url Deprecated argument, use `$props['url']` instead + * + * @throws \Kirby\Exception\InvalidArgumentException When the model does not use the `Kirby\Filesystem\IsFile` trait */ public function __construct( - array|string|null $props = null, - string|null $url = null + array|string $props = null, + string $url = null ) { // Legacy support for old constructor of // the `Kirby\Image\Image` class - // @todo 4.0.0 remove if (is_array($props) === false) { $props = [ 'root' => $props, @@ -73,11 +71,21 @@ class File ]; } - $this->setProperties($props); + $this->root = $props['root'] ?? null; + $this->url = $props['url'] ?? null; + $this->model = $props['model'] ?? null; + + if ( + $this->model !== null && + method_exists($this->model, 'hasIsFileTrait') !== true + ) { + throw new InvalidArgumentException('The model object must use the "Kirby\Filesystem\IsFile" trait'); + } } /** * Improved `var_dump` output + * @codeCoverageIgnore */ public function __debugInfo(): array { @@ -343,19 +351,14 @@ class File /** * Returns the file's last modification time * - * @param string|null $handler date, intl or strftime + * @param 'date'|'intl'|'strftime'|null $handler Custom date handler or `null` + * for the globally configured one */ public function modified( string|IntlDateFormatter|null $format = null, string|null $handler = null ): string|int|false { - $kirby = $this->kirby(); - - return F::modified( - $this->root(), - $format, - $handler ?? $kirby?->option('date.handler', 'date') ?? 'date' - ); + return F::modified($this->root(), $format, $handler); } /** @@ -435,45 +438,6 @@ class File return $this->root ??= $this->model?->root(); } - /** - * Setter for the parent file model, which uses this instance as proxied file asset - * - * @return $this - * - * @throws \Kirby\Exception\InvalidArgumentException When the model does not use the `Kirby\Filesystem\IsFile` trait - */ - protected function setModel(object|null $model = null): static - { - if ($model !== null && method_exists($model, 'hasIsFileTrait') !== true) { - throw new InvalidArgumentException('The model object must use the "Kirby\Filesystem\IsFile" trait'); - } - - $this->model = $model; - return $this; - } - - /** - * Setter for the root - * - * @return $this - */ - protected function setRoot(string|null $root = null): static - { - $this->root = $root; - return $this; - } - - /** - * Setter for the file url - * - * @return $this - */ - protected function setUrl(string|null $url = null): static - { - $this->url = $url; - return $this; - } - /** * Returns the absolute url for the file */ diff --git a/kirby/src/Filesystem/Filename.php b/kirby/src/Filesystem/Filename.php index ba817c1..80f9b8c 100644 --- a/kirby/src/Filesystem/Filename.php +++ b/kirby/src/Filesystem/Filename.php @@ -65,7 +65,7 @@ class Filename $attributes['format'] ?? pathinfo($filename, PATHINFO_EXTENSION) ); - $this->name = $this->sanitizeName(pathinfo($filename, PATHINFO_FILENAME)); + $this->name = $this->sanitizeName($filename); } /** @@ -227,24 +227,21 @@ class Filename /** * Sanitizes the file extension. - * The extension will be converted - * to lowercase and `jpeg` will be - * replaced with `jpg` + * It also replaces `jpeg` with `jpg`. */ protected function sanitizeExtension(string $extension): string { - $extension = strtolower($extension); + $extension = F::safeExtension('test.' . $extension); $extension = str_replace('jpeg', 'jpg', $extension); return $extension; } /** - * Sanitizes the name with Kirby's - * Str::slug function + * Sanitizes the file name */ protected function sanitizeName(string $name): string { - return Str::slug($name); + return F::safeBasename($name); } /** diff --git a/kirby/src/Filesystem/IsFile.php b/kirby/src/Filesystem/IsFile.php index 6093238..e5b1ab1 100644 --- a/kirby/src/Filesystem/IsFile.php +++ b/kirby/src/Filesystem/IsFile.php @@ -5,7 +5,6 @@ namespace Kirby\Filesystem; use Kirby\Cms\App; use Kirby\Exception\BadMethodCallException; use Kirby\Image\Image; -use Kirby\Toolkit\Properties; /** * Trait for all objects that represent an asset file. @@ -22,8 +21,6 @@ use Kirby\Toolkit\Properties; */ trait IsFile { - use Properties; - /** * File asset object */ @@ -32,19 +29,20 @@ trait IsFile /** * Absolute file path */ - protected string|null $root = null; + protected string|null $root; /** * Absolute file URL */ - protected string|null $url = null; + protected string|null $url; /** * Constructor sets all file properties */ public function __construct(array $props) { - $this->setProperties($props); + $this->root = $props['root'] ?? null; + $this->url = $props['url'] ?? null; } /** @@ -52,7 +50,7 @@ trait IsFile * * @throws \Kirby\Exception\BadMethodCallException */ - public function __call(string $method, array $arguments = []) + public function __call(string $method, array $arguments = []): mixed { // public property access if (isset($this->$method) === true) { @@ -136,28 +134,6 @@ trait IsFile return $this->root; } - /** - * Setter for the root - * - * @return $this - */ - protected function setRoot(string|null $root = null): static - { - $this->root = $root; - return $this; - } - - /** - * Setter for the file url - * - * @return $this - */ - protected function setUrl(string|null $url = null): static - { - $this->url = $url; - return $this; - } - /** * Returns the file type */ diff --git a/kirby/src/Filesystem/Mime.php b/kirby/src/Filesystem/Mime.php index c7a218c..61bf3dc 100644 --- a/kirby/src/Filesystem/Mime.php +++ b/kirby/src/Filesystem/Mime.php @@ -30,7 +30,7 @@ class Mime 'aifc' => 'audio/x-aiff', 'aiff' => 'audio/x-aiff', 'avi' => 'video/x-msvideo', - 'avif' => 'image/avif', + 'avif' => 'image/avif', 'bmp' => 'image/bmp', 'css' => 'text/css', 'csv' => ['text/csv', 'text/x-comma-separated-values', 'text/comma-separated-values', 'application/octet-stream'], @@ -119,24 +119,22 @@ class Mime /** * Fixes an invalid MIME type guess for the given file - * - * @param string $file - * @param string $mime - * @param string $extension - * @return string|null */ - public static function fix(string $file, string $mime = null, string $extension = null) - { + public static function fix( + string $file, + string|null $mime = null, + string|null $extension = null + ): string|null { // fixing map $map = [ 'text/html' => [ - 'svg' => ['Kirby\Filesystem\Mime', 'fromSvg'], + 'svg' => [Mime::class, 'fromSvg'], ], 'text/plain' => [ 'css' => 'text/css', 'json' => 'application/json', 'mjs' => 'text/javascript', - 'svg' => ['Kirby\Filesystem\Mime', 'fromSvg'], + 'svg' => [Mime::class, 'fromSvg'], ], 'text/x-asm' => [ 'css' => 'text/css' @@ -167,9 +165,6 @@ class Mime /** * Guesses a MIME type by extension - * - * @param string $extension - * @return string|null */ public static function fromExtension(string $extension): string|null { @@ -179,11 +174,8 @@ class Mime /** * Returns the MIME type of a file - * - * @param string $file - * @return string|false */ - public static function fromFileInfo(string $file) + public static function fromFileInfo(string $file): string|false { if (function_exists('finfo_file') === true && file_exists($file) === true) { $finfo = finfo_open(FILEINFO_MIME_TYPE); @@ -197,13 +189,13 @@ class Mime /** * Returns the MIME type of a file - * - * @param string $file - * @return string|false */ - public static function fromMimeContentType(string $file) + public static function fromMimeContentType(string $file): string|false { - if (function_exists('mime_content_type') === true && file_exists($file) === true) { + if ( + function_exists('mime_content_type') === true && + file_exists($file) === true + ) { return mime_content_type($file); } @@ -212,11 +204,8 @@ class Mime /** * Tries to detect a valid SVG and returns the MIME type accordingly - * - * @param string $file - * @return string|false */ - public static function fromSvg(string $file) + public static function fromSvg(string $file): string|false { if (file_exists($file) === true) { libxml_use_internal_errors(true); @@ -234,10 +223,6 @@ class Mime /** * Tests if a given MIME type is matched by an `Accept` header * pattern; returns true if the MIME type is contained at all - * - * @param string $mime - * @param string $pattern - * @return bool */ public static function isAccepted(string $mime, string $pattern): bool { @@ -256,10 +241,6 @@ class Mime * Tests if a MIME wildcard pattern from an `Accept` header * matches a given type * @since 3.3.0 - * - * @param string $test - * @param string $wildcard - * @return bool */ public static function matches(string $test, string $wildcard): bool { @@ -268,11 +249,8 @@ class Mime /** * Returns the extension for a given MIME type - * - * @param string|null $mime - * @return string|false */ - public static function toExtension(string $mime = null) + public static function toExtension(string $mime = null): string|false { foreach (static::$types as $key => $value) { if (is_array($value) === true && in_array($mime, $value) === true) { @@ -289,9 +267,6 @@ class Mime /** * Returns all available extensions for a given MIME type - * - * @param string|null $mime - * @return array */ public static function toExtensions(string $mime = null): array { @@ -314,8 +289,10 @@ class Mime /** * Returns the MIME type of a file */ - public static function type(string $file, string|null $extension = null): string|null - { + public static function type( + string $file, + string|null $extension = null + ): string|null { // use the standard finfo extension $mime = static::fromFileInfo($file); @@ -338,8 +315,6 @@ class Mime /** * Returns all detectable MIME types - * - * @return array */ public static function types(): array { diff --git a/kirby/src/Form/Field.php b/kirby/src/Form/Field.php index 0e3a02a..cb95fd4 100644 --- a/kirby/src/Form/Field.php +++ b/kirby/src/Form/Field.php @@ -4,6 +4,7 @@ namespace Kirby\Form; use Closure; use Exception; +use Kirby\Cms\App; use Kirby\Exception\InvalidArgumentException; use Kirby\Toolkit\A; use Kirby\Toolkit\Component; @@ -25,42 +26,32 @@ class Field extends Component { /** * An array of all found errors - * - * @var array|null */ - protected $errors; + protected array|null $errors = null; /** * Parent collection with all fields of the current form - * - * @var \Kirby\Form\Fields|null */ - protected $formFields; + protected Fields|null $formFields; /** * Registry for all component mixins - * - * @var array */ - public static $mixins = []; + public static array $mixins = []; /** * Registry for all component types - * - * @var array */ - public static $types = []; + public static array $types = []; /** - * Field constructor - * - * @param string $type - * @param array $attrs - * @param \Kirby\Form\Fields|null $formFields * @throws \Kirby\Exception\InvalidArgumentException */ - public function __construct(string $type, array $attrs = [], ?Fields $formFields = null) - { + public function __construct( + string $type, + array $attrs = [], + Fields|null $formFields = null + ) { if (isset(static::$types[$type]) === false) { throw new InvalidArgumentException([ 'key' => 'field.type.missing', @@ -83,10 +74,8 @@ class Field extends Component /** * Returns field api call - * - * @return mixed */ - public function api() + public function api(): mixed { if ( isset($this->options['api']) === true && @@ -94,15 +83,14 @@ class Field extends Component ) { return $this->options['api']->call($this); } + + return null; } /** * Returns field data - * - * @param bool $default - * @return mixed */ - public function data(bool $default = false) + public function data(bool $default = false): mixed { $save = $this->options['save'] ?? true; @@ -125,8 +113,6 @@ class Field extends Component /** * Default props and computed of the field - * - * @return array */ public static function defaults(): array { @@ -264,15 +250,43 @@ class Field extends Component } /** - * Creates a new field instance - * - * @param string $type - * @param array $attrs - * @param Fields|null $formFields - * @return static + * Returns optional dialog routes for the field */ - public static function factory(string $type, array $attrs = [], ?Fields $formFields = null) + public function dialogs(): array { + if ( + isset($this->options['dialogs']) === true && + $this->options['dialogs'] instanceof Closure + ) { + return $this->options['dialogs']->call($this); + } + + return []; + } + + /** + * Returns optional drawer routes for the field + */ + public function drawers(): array + { + if ( + isset($this->options['drawers']) === true && + $this->options['drawers'] instanceof Closure + ) { + return $this->options['drawers']->call($this); + } + + return []; + } + + /** + * Creates a new field instance + */ + public static function factory( + string $type, + array $attrs = [], + Fields|null $formFields = null + ): static|FieldClass { $field = static::$types[$type] ?? null; if (is_string($field) && class_exists($field) === true) { @@ -285,18 +299,14 @@ class Field extends Component /** * Parent collection with all fields of the current form - * - * @return \Kirby\Form\Fields|null */ - public function formFields(): ?Fields + public function formFields(): Fields|null { return $this->formFields; } /** * Validates when run for the first time and returns any errors - * - * @return array */ public function errors(): array { @@ -309,29 +319,31 @@ class Field extends Component /** * Checks if the field is empty - * - * @param mixed ...$args - * @return bool */ - public function isEmpty(...$args): bool + public function isEmpty(mixed ...$args): bool { - if (count($args) === 0) { - $value = $this->value(); - } else { - $value = $args[0]; - } + $value = match (count($args)) { + 0 => $this->value(), + default => $args[0] + }; - if (isset($this->options['isEmpty']) === true) { - return $this->options['isEmpty']->call($this, $value); + if ($empty = $this->options['isEmpty'] ?? null) { + return $empty->call($this, $value); } return in_array($value, [null, '', []], true); } + /** + * Checks if the field is hidden + */ + public function isHidden(): bool + { + return ($this->options['hidden'] ?? false) === true; + } + /** * Checks if the field is invalid - * - * @return bool */ public function isInvalid(): bool { @@ -340,8 +352,6 @@ class Field extends Component /** * Checks if the field is required - * - * @return bool */ public function isRequired(): bool { @@ -350,8 +360,6 @@ class Field extends Component /** * Checks if the field is valid - * - * @return bool */ public function isValid(): bool { @@ -360,20 +368,16 @@ class Field extends Component /** * Returns the Kirby instance - * - * @return \Kirby\Cms\App */ - public function kirby() + public function kirby(): App { return $this->model()->kirby(); } /** * Returns the parent model - * - * @return mixed */ - public function model() + public function model(): mixed { return $this->model; } @@ -385,31 +389,33 @@ class Field extends Component * - The field is required * - The field is currently empty * - The field is not currently inactive because of a `when` rule - * - * @return bool */ protected function needsValue(): bool { // check simple conditions first - if ($this->save() === false || $this->isRequired() === false || $this->isEmpty() === false) { + if ( + $this->save() === false || + $this->isRequired() === false || + $this->isEmpty() === false + ) { return false; } // check the data of the relevant fields if there is a `when` option - if (empty($this->when) === false && is_array($this->when) === true) { - $formFields = $this->formFields(); + if ( + empty($this->when) === false && + is_array($this->when) === true && + $formFields = $this->formFields() + ) { + foreach ($this->when as $field => $value) { + $field = $formFields->get($field); + $inputValue = $field?->value() ?? ''; - if ($formFields !== null) { - foreach ($this->when as $field => $value) { - $field = $formFields->get($field); - $inputValue = $field?->value() ?? ''; - - // if the input data doesn't match the requested `when` value, - // that means that this field is not required and can be saved - // (*all* `when` conditions must be met for this field to be required) - if ($inputValue !== $value) { - return false; - } + // if the input data doesn't match the requested `when` value, + // that means that this field is not required and can be saved + // (*all* `when` conditions must be met for this field to be required) + if ($inputValue !== $value) { + return false; } } } @@ -420,8 +426,6 @@ class Field extends Component /** * Checks if the field is saveable - * - * @return bool */ public function save(): bool { @@ -430,8 +434,6 @@ class Field extends Component /** * Converts the field to a plain array - * - * @return array */ public function toArray(): array { @@ -439,6 +441,7 @@ class Field extends Component unset($array['model']); + $array['hidden'] = $this->isHidden(); $array['saveable'] = $this->save(); $array['signature'] = md5(json_encode($array)); @@ -452,8 +455,6 @@ class Field extends Component /** * Runs the validations defined for the field - * - * @return void */ protected function validate(): void { @@ -501,10 +502,8 @@ class Field extends Component /** * Returns the value of the field if saveable * otherwise it returns null - * - * @return mixed */ - public function value() + public function value(): mixed { return $this->save() ? $this->value : null; } diff --git a/kirby/src/Form/Field/BlocksField.php b/kirby/src/Form/Field/BlocksField.php index 29b4993..e37d373 100644 --- a/kirby/src/Form/Field/BlocksField.php +++ b/kirby/src/Form/Field/BlocksField.php @@ -5,7 +5,9 @@ namespace Kirby\Form\Field; use Kirby\Cms\App; use Kirby\Cms\Block; use Kirby\Cms\Blocks as BlocksCollection; +use Kirby\Cms\Fieldset; use Kirby\Cms\Fieldsets; +use Kirby\Cms\ModelWithContent; use Kirby\Exception\InvalidArgumentException; use Kirby\Exception\NotFoundException; use Kirby\Form\FieldClass; @@ -22,15 +24,17 @@ class BlocksField extends FieldClass use Max; use Min; - protected $blocks; - protected $fieldsets; - protected $group; - protected $pretty; - protected $value = []; + protected Fieldsets $fieldsets; + protected string|null $group; + protected bool $pretty; + protected mixed $value = []; public function __construct(array $params = []) { - $this->setFieldsets($params['fieldsets'] ?? null, $params['model'] ?? App::instance()->site()); + $this->setFieldsets( + $params['fieldsets'] ?? null, + $params['model'] ?? App::instance()->site() + ); parent::__construct($params); @@ -41,8 +45,10 @@ class BlocksField extends FieldClass $this->setPretty($params['pretty'] ?? false); } - public function blocksToValues($blocks, $to = 'values'): array - { + public function blocksToValues( + array $blocks, + string $to = 'values' + ): array { $result = []; $fields = []; @@ -54,53 +60,58 @@ class BlocksField extends FieldClass $fields[$type] ??= $this->fields($block['type']); // overwrite the block content with form values - $block['content'] = $this->form($fields[$type], $block['content'])->$to(); + $block['content'] = $this->form( + $fields[$type], + $block['content'] + )->$to(); - $result[] = $block; + // create id if not exists + $block['id'] ??= Str::uuid(); } catch (Throwable) { - $result[] = $block; - // skip invalid blocks - continue; + } finally { + $result[] = $block; } } return $result; } - public function fields(string $type) + public function fields(string $type): array { return $this->fieldset($type)->fields(); } - public function fieldset(string $type) + public function fieldset(string $type): Fieldset { if ($fieldset = $this->fieldsets->find($type)) { return $fieldset; } - throw new NotFoundException('The fieldset ' . $type . ' could not be found'); + throw new NotFoundException( + 'The fieldset ' . $type . ' could not be found' + ); } - public function fieldsets() + public function fieldsets(): Fieldsets { return $this->fieldsets; } public function fieldsetGroups(): array|null { - $fieldsetGroups = $this->fieldsets()->groups(); - return empty($fieldsetGroups) === true ? null : $fieldsetGroups; + $groups = $this->fieldsets()->groups(); + return empty($groups) === true ? null : $groups; } - public function fill($value = null) + public function fill(mixed $value = null): void { $value = BlocksCollection::parse($value); - $blocks = BlocksCollection::factory($value); - $this->value = $this->blocksToValues($blocks->toArray()); + $blocks = BlocksCollection::factory($value)->toArray(); + $this->value = $this->blocksToValues($blocks); } - public function form(array $fields, array $input = []) + public function form(array $fields, array $input = []): Form { return new Form([ 'fields' => $fields, @@ -125,6 +136,30 @@ class BlocksField extends FieldClass return $this->pretty; } + /** + * Paste action for blocks: + * - generates new uuids for the blocks + * - filters only supported fieldsets + * - applies max limit if defined + */ + public function pasteBlocks(array $blocks): array + { + $blocks = $this->blocksToValues($blocks); + + foreach ($blocks as $index => &$block) { + $block['id'] = Str::uuid(); + + // remove the block if it's not available + try { + $this->fieldset($block['type']); + } catch (Throwable) { + unset($blocks[$index]); + } + } + + return array_values($blocks); + } + public function props(): array { return [ @@ -144,23 +179,25 @@ class BlocksField extends FieldClass return [ [ 'pattern' => 'uuid', - 'action' => fn () => ['uuid' => Str::uuid()] + 'action' => fn (): array => ['uuid' => Str::uuid()] ], [ 'pattern' => 'paste', 'method' => 'POST', - 'action' => function () use ($field) { + 'action' => function () use ($field): array { $request = App::instance()->request(); + $value = BlocksCollection::parse($request->get('html')); + $blocks = BlocksCollection::factory($value); - $value = BlocksCollection::parse($request->get('html')); - $blocks = BlocksCollection::factory($value); - return $field->blocksToValues($blocks->toArray()); + return $field->pasteBlocks($blocks->toArray()); } ], [ 'pattern' => 'fieldsets/(:any)', 'method' => 'GET', - 'action' => function ($fieldsetType) use ($field) { + 'action' => function ( + string $fieldsetType + ) use ($field): array { $fields = $field->fields($fieldsetType); $defaults = $field->form($fields, [])->data(true); $content = $field->form($fields, $defaults)->values(); @@ -174,22 +211,33 @@ class BlocksField extends FieldClass [ 'pattern' => 'fieldsets/(:any)/fields/(:any)/(:all?)', 'method' => 'ALL', - 'action' => function (string $fieldsetType, string $fieldName, string $path = null) use ($field) { + 'action' => function ( + string $fieldsetType, + string $fieldName, + string $path = null + ) use ($field) { $fields = $field->fields($fieldsetType); $field = $field->form($fields)->field($fieldName); $fieldApi = $this->clone([ 'routes' => $field->api(), - 'data' => array_merge($this->data(), ['field' => $field]) + 'data' => array_merge( + $this->data(), + ['field' => $field] + ) ]); - return $fieldApi->call($path, $this->requestMethod(), $this->requestData()); + return $fieldApi->call( + $path, + $this->requestMethod(), + $this->requestData() + ); } ], ]; } - public function store($value) + public function store(mixed $value): mixed { $blocks = $this->blocksToValues((array)$value, 'content'); @@ -202,7 +250,7 @@ class BlocksField extends FieldClass return $this->valueToJson($blocks, $this->pretty()); } - protected function setDefault($default = null) + protected function setDefault(mixed $default = null): void { // set id for blocks if not exists if (is_array($default) === true) { @@ -214,23 +262,26 @@ class BlocksField extends FieldClass parent::setDefault($default); } - protected function setFieldsets($fieldsets, $model) - { + protected function setFieldsets( + string|array|null $fieldsets, + ModelWithContent $model + ): void { if (is_string($fieldsets) === true) { $fieldsets = []; } - $this->fieldsets = Fieldsets::factory($fieldsets, [ - 'parent' => $model - ]); + $this->fieldsets = Fieldsets::factory( + $fieldsets, + ['parent' => $model] + ); } - protected function setGroup(string $group = null) + protected function setGroup(string $group = null): void { $this->group = $group; } - protected function setPretty(bool $pretty = false) + protected function setPretty(bool $pretty = false): void { $this->pretty = $pretty; } @@ -262,21 +313,22 @@ class BlocksField extends FieldClass foreach ($value as $block) { $index++; - $blockType = $block['type']; + $type = $block['type']; try { - $fieldset = $this->fieldset($blockType); - $blockFields = $fields[$blockType] ?? $fieldset->fields() ?? []; + $fieldset = $this->fieldset($type); + $blockFields = $fields[$type] ?? $fieldset->fields() ?? []; } catch (Throwable) { // skip invalid blocks continue; } // store the fields for the next round - $fields[$blockType] = $blockFields; + $fields[$type] = $blockFields; // overwrite the content with the serialized form - foreach ($this->form($blockFields, $block['content'])->fields() as $field) { + $form = $this->form($blockFields, $block['content']); + foreach ($form->fields() as $field) { $errors = $field->errors(); // rough first validation diff --git a/kirby/src/Form/Field/LayoutField.php b/kirby/src/Form/Field/LayoutField.php index 7147234..2e595c0 100644 --- a/kirby/src/Form/Field/LayoutField.php +++ b/kirby/src/Form/Field/LayoutField.php @@ -14,19 +14,21 @@ use Throwable; class LayoutField extends BlocksField { - protected $layouts; - protected $settings; + protected array|null $layouts; + protected array|null $selector; + protected Fieldset|null $settings; public function __construct(array $params) { $this->setModel($params['model'] ?? App::instance()->site()); $this->setLayouts($params['layouts'] ?? ['1/1']); + $this->setSelector($params['selector'] ?? null); $this->setSettings($params['settings'] ?? null); parent::__construct($params); } - public function fill($value = null) + public function fill(mixed $value = null): void { $value = $this->valueFromJson($value); $layouts = Layouts::factory($value, ['parent' => $this->model])->toArray(); @@ -44,7 +46,7 @@ class LayoutField extends BlocksField $this->value = $layouts; } - public function attrsForm(array $input = []) + public function attrsForm(array $input = []): Form { $settings = $this->settings(); @@ -61,13 +63,71 @@ class LayoutField extends BlocksField return $this->layouts; } + /** + * Creates form values for each layout + */ + public function layoutsToValues(array $layouts): array + { + foreach ($layouts as &$layout) { + $layout['id'] ??= Str::uuid(); + $layout['columns'] ??= []; + + array_walk($layout['columns'], function (&$column) { + $column['id'] ??= Str::uuid(); + $column['blocks'] = $this->blocksToValues($column['blocks'] ?? []); + }); + } + + return $layouts; + } + + /** + * Paste action for layouts: + * - generates new uuids for layout, column and blocks + * - filters only supported layouts + * - filters only supported fieldsets + */ + public function pasteLayouts(array $layouts): array + { + $layouts = $this->layoutsToValues($layouts); + + foreach ($layouts as $layoutIndex => &$layout) { + $layout['id'] = Str::uuid(); + + // remove the row if layout not available for the pasted layout field + $columns = array_column($layout['columns'], 'width'); + if (in_array($columns, $this->layouts()) === false) { + unset($layouts[$layoutIndex]); + continue; + } + + array_walk($layout['columns'], function (&$column) { + $column['id'] = Str::uuid(); + + array_walk($column['blocks'], function (&$block, $index) use ($column) { + $block['id'] = Str::uuid(); + + // remove the block if it's not available + try { + $this->fieldset($block['type']); + } catch (Throwable) { + unset($column['blocks'][$index]); + } + }); + }); + } + + return $layouts; + } + public function props(): array { $settings = $this->settings(); return array_merge(parent::props(), [ - 'settings' => $settings?->toArray(), - 'layouts' => $this->layouts() + 'layouts' => $this->layouts(), + 'selector' => $this->selector(), + 'settings' => $settings?->toArray() ]); } @@ -75,13 +135,15 @@ class LayoutField extends BlocksField { $field = $this; $routes = parent::routes(); + $routes[] = [ 'pattern' => 'layout', 'method' => 'POST', - 'action' => function () use ($field) { + 'action' => function () use ($field): array { $request = App::instance()->request(); - $defaults = $field->attrsForm([])->data(true); + $input = $request->get('attrs') ?? []; + $defaults = $field->attrsForm($input)->data(true); $attrs = $field->attrsForm($defaults)->values(); $columns = $request->get('columns') ?? ['1/1']; @@ -96,26 +158,53 @@ class LayoutField extends BlocksField }, ]; + $routes[] = [ + 'pattern' => 'layout/paste', + 'method' => 'POST', + 'action' => function () use ($field): array { + $request = App::instance()->request(); + $value = Layouts::parse($request->get('json')); + $layouts = Layouts::factory($value); + + return $field->pasteLayouts($layouts->toArray()); + } + ]; + $routes[] = [ 'pattern' => 'fields/(:any)/(:all?)', 'method' => 'ALL', - 'action' => function (string $fieldName, string $path = null) use ($field) { + 'action' => function ( + string $fieldName, + string $path = null + ) use ($field): array { $form = $field->attrsForm(); $field = $form->field($fieldName); $fieldApi = $this->clone([ 'routes' => $field->api(), - 'data' => array_merge($this->data(), ['field' => $field]) + 'data' => array_merge( + $this->data(), + ['field' => $field] + ) ]); - return $fieldApi->call($path, $this->requestMethod(), $this->requestData()); + return $fieldApi->call( + $path, + $this->requestMethod(), + $this->requestData() + ); } ]; return $routes; } - protected function setDefault($default = null) + public function selector(): array|null + { + return $this->selector; + } + + protected function setDefault(mixed $default = null): void { // set id for layouts, columns and blocks within layout if not exists if (is_array($default) === true) { @@ -141,7 +230,7 @@ class LayoutField extends BlocksField parent::setDefault($default); } - protected function setLayouts(array $layouts = []) + protected function setLayouts(array $layouts = []): void { $this->layouts = array_map( fn ($layout) => Str::split($layout), @@ -149,7 +238,15 @@ class LayoutField extends BlocksField ); } - protected function setSettings($settings = null) + /** + * Layout selector's styles such as size (`small`, `medium`, `large` or `huge`) and columns + */ + protected function setSelector(array|null $selector = null): void + { + $this->selector = $selector; + } + + protected function setSettings(array|string|null $settings = null): void { if (empty($settings) === true) { $this->settings = null; @@ -165,12 +262,12 @@ class LayoutField extends BlocksField $this->settings = Fieldset::factory($settings); } - public function settings() + public function settings(): Fieldset|null { return $this->settings; } - public function store($value) + public function store(mixed $value): mixed { $value = Layouts::factory($value, ['parent' => $this->model])->toArray(); @@ -204,7 +301,9 @@ class LayoutField extends BlocksField $layoutIndex++; // validate settings form - foreach ($this->attrsForm($layout['attrs'] ?? [])->fields() as $field) { + $form = $this->attrsForm($layout['attrs'] ?? []); + + foreach ($form->fields() as $field) { $errors = $field->errors(); if (empty($errors) === false) { @@ -237,7 +336,9 @@ class LayoutField extends BlocksField $fields[$blockType] = $blockFields; // overwrite the content with the serialized form - foreach ($this->form($blockFields, $block['content'])->fields() as $field) { + $form = $this->form($blockFields, $block['content']); + + foreach ($form->fields() as $field) { $errors = $field->errors(); // rough first validation diff --git a/kirby/src/Form/FieldClass.php b/kirby/src/Form/FieldClass.php index 6aa173b..cc4115e 100644 --- a/kirby/src/Form/FieldClass.php +++ b/kirby/src/Form/FieldClass.php @@ -27,117 +27,27 @@ abstract class FieldClass { use HasSiblings; - /** - * @var string|null - */ - protected $after; - - /** - * @var bool - */ - protected $autofocus; - - /** - * @var string|null - */ - protected $before; - - /** - * @var mixed - */ - protected $default; - - /** - * @var bool - */ - protected $disabled; - - /** - * @var string|null - */ - protected $help; - - /** - * @var string|null - */ - protected $icon; - - /** - * @var string|null - */ - protected $label; - - /** - * @var \Kirby\Cms\ModelWithContent - */ - protected $model; - - /** - * @var string - */ - protected $name; - - /** - * @var array - */ - protected $params; - - /** - * @var string|null - */ - protected $placeholder; - - /** - * @var bool - */ - protected $required; - - /** - * @var \Kirby\Form\Fields - */ - protected $siblings; - - /** - * @var bool - */ - protected $translate; - - /** - * @var mixed - */ - protected $value; - - /** - * @var array|null - */ - protected $when; - - /** - * @var string|null - */ - protected $width; - - /** - * @param string $param - * @param array $args - * @return mixed - */ - public function __call(string $param, array $args) - { - if (isset($this->$param) === true) { - return $this->$param; - } - - return $this->params[$param] ?? null; - } - - /** - * @param array $params - */ - public function __construct(array $params = []) - { - $this->params = $params; + protected string|null $after; + protected bool $autofocus; + protected string|null $before; + protected mixed $default; + protected bool $disabled; + protected string|null $help; + protected string|null $icon; + protected string|null $label; + protected ModelWithContent $model; + protected string|null $name; + protected string|null $placeholder; + protected bool $required; + protected Fields $siblings; + protected bool $translate; + protected mixed $value = null; + protected array|null $when; + protected string|null $width; + public function __construct( + protected array $params = [] + ) { $this->setAfter($params['after'] ?? null); $this->setAutofocus($params['autofocus'] ?? false); $this->setBefore($params['before'] ?? null); @@ -160,33 +70,30 @@ abstract class FieldClass } } - /** - * @return string|null - */ + public function __call(string $param, array $args): mixed + { + if (isset($this->$param) === true) { + return $this->$param; + } + + return $this->params[$param] ?? null; + } + public function after(): string|null { return $this->stringTemplate($this->after); } - /** - * @return array - */ public function api(): array { return $this->routes(); } - /** - * @return bool - */ public function autofocus(): bool { return $this->autofocus; } - /** - * @return string|null - */ public function before(): string|null { return $this->stringTemplate($this->before); @@ -199,11 +106,8 @@ abstract class FieldClass * Returns the field data * in a format to be stored * in Kirby's content fields - * - * @param bool $default - * @return mixed */ - public function data(bool $default = false) + public function data(bool $default = false): mixed { return $this->store($this->value($default)); } @@ -211,10 +115,8 @@ abstract class FieldClass /** * Returns the default value for the field, * which will be used when a page/file/user is created - * - * @return mixed */ - public function default() + public function default(): mixed { if (is_string($this->default) === false) { return $this->default; @@ -223,21 +125,33 @@ abstract class FieldClass return $this->stringTemplate($this->default); } + /** + * Returns optional dialog routes for the field + */ + public function dialogs(): array + { + return []; + } + /** * If `true`, the field is no longer editable and will not be saved - * - * @return bool */ public function disabled(): bool { return $this->disabled; } + /** + * Returns optional drawer routes for the field + */ + public function drawers(): array + { + return []; + } + /** * Runs all validations and returns an array of * error messages - * - * @return array */ public function errors(): array { @@ -246,19 +160,14 @@ abstract class FieldClass /** * Setter for the value - * - * @param mixed $value - * @return void */ - public function fill($value = null) + public function fill(mixed $value = null): void { $this->value = $value; } /** * Optional help text below the field - * - * @return string|null */ public function help(): string|null { @@ -271,79 +180,57 @@ abstract class FieldClass return null; } - /** - * @param string|array|null $param - * @return string|null - */ - protected function i18n($param = null): string|null + protected function i18n(string|array|null $param = null): string|null { return empty($param) === false ? I18n::translate($param, $param) : null; } /** * Optional icon that will be shown at the end of the field - * - * @return string|null */ public function icon(): string|null { return $this->icon; } - /** - * @return string - */ public function id(): string { return $this->name(); } - /** - * @return bool - */ public function isDisabled(): bool { return $this->disabled; } - /** - * @return bool - */ public function isEmpty(): bool { return $this->isEmptyValue($this->value()); } - /** - * @param mixed $value - * @return bool - */ - public function isEmptyValue($value = null): bool + public function isEmptyValue(mixed $value = null): bool { return in_array($value, [null, '', []], true); } + public function isHidden(): bool + { + return false; + } + /** * Checks if the field is invalid - * - * @return bool */ public function isInvalid(): bool { return $this->isValid() === false; } - /** - * @return bool - */ public function isRequired(): bool { return $this->required; } - /** - * @return bool - */ public function isSaveable(): bool { return true; @@ -351,8 +238,6 @@ abstract class FieldClass /** * Checks if the field is valid - * - * @return bool */ public function isValid(): bool { @@ -361,38 +246,32 @@ abstract class FieldClass /** * Returns the Kirby instance - * - * @return \Kirby\Cms\App */ - public function kirby() + public function kirby(): App { return $this->model->kirby(); } /** * The field label can be set as string or associative array with translations - * - * @return string */ public function label(): string { - return $this->stringTemplate($this->label ?? Str::ucfirst($this->name())); + return $this->stringTemplate( + $this->label ?? Str::ucfirst($this->name()) + ); } /** * Returns the parent model - * - * @return mixed */ - public function model() + public function model(): ModelWithContent { return $this->model; } /** * Returns the field name - * - * @return string */ public function name(): string { @@ -406,8 +285,6 @@ abstract class FieldClass * - The field is required * - The field is currently empty * - The field is not currently inactive because of a `when` rule - * - * @return bool */ protected function needsValue(): bool { @@ -421,20 +298,20 @@ abstract class FieldClass } // check the data of the relevant fields if there is a `when` option - if (empty($this->when) === false && is_array($this->when) === true) { - $formFields = $this->siblings(); + if ( + empty($this->when) === false && + is_array($this->when) === true && + $formFields = $this->siblings() + ) { + foreach ($this->when as $field => $value) { + $field = $formFields->get($field); + $inputValue = $field?->value() ?? ''; - if ($formFields !== null) { - foreach ($this->when as $field => $value) { - $field = $formFields->get($field); - $inputValue = $field?->value() ?? ''; - - // if the input data doesn't match the requested `when` value, - // that means that this field is not required and can be saved - // (*all* `when` conditions must be met for this field to be required) - if ($inputValue !== $value) { - return false; - } + // if the input data doesn't match the requested `when` value, + // that means that this field is not required and can be saved + // (*all* `when` conditions must be met for this field to be required) + if ($inputValue !== $value) { + return false; } } } @@ -445,8 +322,6 @@ abstract class FieldClass /** * Returns all original params for the field - * - * @return array */ public function params(): array { @@ -455,8 +330,6 @@ abstract class FieldClass /** * Optional placeholder value that will be shown when the field is empty - * - * @return string|null */ public function placeholder(): string|null { @@ -466,8 +339,6 @@ abstract class FieldClass /** * Define the props that will be sent to * the Vue component - * - * @return array */ public function props(): array { @@ -478,6 +349,7 @@ abstract class FieldClass 'default' => $this->default(), 'disabled' => $this->isDisabled(), 'help' => $this->help(), + 'hidden' => $this->isHidden(), 'icon' => $this->icon(), 'label' => $this->label(), 'name' => $this->name(), @@ -493,8 +365,6 @@ abstract class FieldClass /** * If `true`, the field has to be filled in correctly to be saved. - * - * @return bool */ public function required(): bool { @@ -503,8 +373,6 @@ abstract class FieldClass /** * Routes for the field API - * - * @return array */ public function routes(): array { @@ -514,176 +382,108 @@ abstract class FieldClass /** * @deprecated 3.5.0 * @todo remove when the general field class setup has been refactored - * @return bool */ - public function save() + public function save(): bool { return $this->isSaveable(); } - /** - * @param array|string|null $after - * @return void - */ - protected function setAfter($after = null) + protected function setAfter(array|string|null $after = null): void { $this->after = $this->i18n($after); } - /** - * @param bool $autofocus - * @return void - */ - protected function setAutofocus(bool $autofocus = false) + protected function setAutofocus(bool $autofocus = false): void { $this->autofocus = $autofocus; } - /** - * @param array|string|null $before - * @return void - */ - protected function setBefore($before = null) + protected function setBefore(array|string|null $before = null): void { $this->before = $this->i18n($before); } - /** - * @param mixed $default - * @return void - */ - protected function setDefault($default = null) + protected function setDefault(mixed $default = null): void { $this->default = $default; } - /** - * @param bool $disabled - * @return void - */ - protected function setDisabled(bool $disabled = false) + protected function setDisabled(bool $disabled = false): void { $this->disabled = $disabled; } - /** - * @param array|string|null $help - * @return void - */ - protected function setHelp($help = null) + protected function setHelp(array|string|null $help = null): void { $this->help = $this->i18n($help); } - /** - * @param string|null $icon - * @return void - */ - protected function setIcon(string|null $icon = null) + protected function setIcon(string|null $icon = null): void { $this->icon = $icon; } - /** - * @param array|string|null $label - * @return void - */ - protected function setLabel($label = null) + protected function setLabel(array|string|null $label = null): void { $this->label = $this->i18n($label); } - /** - * @param \Kirby\Cms\ModelWithContent $model - * @return void - */ - protected function setModel(ModelWithContent $model) + protected function setModel(ModelWithContent $model): void { $this->model = $model; } - /** - * @param string|null $name - * @return void - */ - protected function setName(string $name = null) + protected function setName(string|null $name = null): void { $this->name = $name; } - /** - * @param array|string|null $placeholder - * @return void - */ - protected function setPlaceholder($placeholder = null) + protected function setPlaceholder(array|string|null $placeholder = null): void { $this->placeholder = $this->i18n($placeholder); } - /** - * @param bool $required - * @return void - */ - protected function setRequired(bool $required = false) + protected function setRequired(bool $required = false): void { $this->required = $required; } - /** - * @param \Kirby\Form\Fields|null $siblings - * @return void - */ - protected function setSiblings(?Fields $siblings = null) + protected function setSiblings(Fields|null $siblings = null): void { $this->siblings = $siblings ?? new Fields([$this]); } - /** - * @param bool $translate - * @return void - */ - protected function setTranslate(bool $translate = true) + protected function setTranslate(bool $translate = true): void { $this->translate = $translate; } /** * Setter for the when condition - * - * @param mixed $when - * @return void */ - protected function setWhen($when = null) + protected function setWhen(array|null $when = null): void { $this->when = $when; } /** * Setter for the field width - * - * @param string|null $width - * @return void */ - protected function setWidth(string $width = null) + protected function setWidth(string|null $width = null): void { $this->width = $width; } /** * Returns all sibling fields - * - * @return \Kirby\Form\Fields */ - protected function siblingsCollection() + protected function siblingsCollection(): Fields { return $this->siblings; } /** * Parses a string template in the given value - * - * @param string|null $string - * @return string|null */ protected function stringTemplate(string|null $string = null): string|null { @@ -697,19 +497,14 @@ abstract class FieldClass /** * Converts the given value to a value * that can be stored in the text file - * - * @param mixed $value - * @return mixed */ - public function store($value) + public function store(mixed $value): mixed { return $value; } /** * Should the field be translatable? - * - * @return bool */ public function translate(): bool { @@ -718,8 +513,6 @@ abstract class FieldClass /** * Converts the field to a plain array - * - * @return array */ public function toArray(): array { @@ -733,8 +526,6 @@ abstract class FieldClass /** * Returns the field type - * - * @return string */ public function type(): string { @@ -743,8 +534,6 @@ abstract class FieldClass /** * Runs the validations defined for the field - * - * @return array */ protected function validate(): array { @@ -782,8 +571,7 @@ abstract class FieldClass /** * Defines all validation rules - * - * @return array + * @codeCoverageIgnore */ protected function validations(): array { @@ -793,10 +581,8 @@ abstract class FieldClass /** * Returns the value of the field if saveable * otherwise it returns null - * - * @return mixed */ - public function value(bool $default = false) + public function value(bool $default = false): mixed { if ($this->isSaveable() === false) { return null; @@ -809,11 +595,7 @@ abstract class FieldClass return $this->value; } - /** - * @param mixed $value - * @return array - */ - protected function valueFromJson($value): array + protected function valueFromJson(mixed $value): array { try { return Data::decode($value, 'json'); @@ -822,22 +604,15 @@ abstract class FieldClass } } - /** - * @param mixed $value - * @return array - */ - protected function valueFromYaml($value): array + protected function valueFromYaml(mixed $value): array { return Data::decode($value, 'yaml'); } - /** - * @param array|null $value - * @param bool $pretty - * @return string - */ - protected function valueToJson(array $value = null, bool $pretty = false): string - { + protected function valueToJson( + array $value = null, + bool $pretty = false + ): string { $constants = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; if ($pretty === true) { @@ -847,10 +622,6 @@ abstract class FieldClass return json_encode($value, $constants); } - /** - * @param array|null $value - * @return string - */ protected function valueToYaml(array $value = null): string { return Data::encode($value, 'yaml'); @@ -858,8 +629,6 @@ abstract class FieldClass /** * Conditions when the field will be shown - * - * @return array|null */ public function when(): array|null { @@ -869,8 +638,6 @@ abstract class FieldClass /** * Returns the width of the field in * the Panel grid - * - * @return string */ public function width(): string { diff --git a/kirby/src/Form/Fields.php b/kirby/src/Form/Fields.php index 9abd199..ef46f2e 100644 --- a/kirby/src/Form/Fields.php +++ b/kirby/src/Form/Fields.php @@ -21,9 +21,7 @@ class Fields extends Collection * This takes care of validation and of setting * the collection prop on each object correctly. * - * @param string $name * @param object|array $field - * @return void */ public function __set(string $name, $field): void { @@ -40,9 +38,6 @@ class Fields extends Collection * Converts the fields collection to an * array and also does that for every * included field. - * - * @param \Closure|null $map - * @return array */ public function toArray(Closure $map = null): array { diff --git a/kirby/src/Form/Form.php b/kirby/src/Form/Form.php index 9ee3059..40714e7 100644 --- a/kirby/src/Form/Form.php +++ b/kirby/src/Form/Form.php @@ -4,9 +4,11 @@ namespace Kirby\Form; use Closure; use Kirby\Cms\App; -use Kirby\Cms\Model; +use Kirby\Cms\File; +use Kirby\Cms\ModelWithContent; use Kirby\Data\Data; use Kirby\Exception\NotFoundException; +use Kirby\Toolkit\A; use Kirby\Toolkit\Str; use Throwable; @@ -26,29 +28,21 @@ class Form { /** * An array of all found errors - * - * @var array|null */ - protected $errors; + protected array|null $errors = null; /** * Fields in the form - * - * @var \Kirby\Form\Fields|null */ - protected $fields; + protected Fields|null $fields; /** * All values of form - * - * @var array */ - protected $values = []; + protected array $values = []; /** * Form constructor - * - * @param array $props */ public function __construct(array $props) { @@ -80,15 +74,12 @@ class Form // inject the name $props['name'] = $name = strtolower($name); - // check if the field is disabled - $disabled = $props['disabled'] ?? false; - + // check if the field is disabled and // overwrite the field value if not set - if ($disabled === true) { - $props['value'] = $values[$name] ?? null; - } else { - $props['value'] = $input[$name] ?? $values[$name] ?? null; - } + $props['value'] = match ($props['disabled'] ?? false) { + true => $values[$name] ?? null, + default => $input[$name] ?? $values[$name] ?? null + }; try { $field = Field::factory($props['type'], $props, $this->fields); @@ -117,8 +108,6 @@ class Form /** * Returns the data required to write to the content file * Doesn't include default and null values - * - * @return array */ public function content(): array { @@ -129,8 +118,6 @@ class Form * Returns data for all fields in the form * * @param false $defaults - * @param bool $includeNulls - * @return array */ public function data($defaults = false, bool $includeNulls = true): array { @@ -153,8 +140,6 @@ class Form /** * An array of all found errors - * - * @return array */ public function errors(): array { @@ -178,17 +163,16 @@ class Form /** * Shows the error with the field - * - * @param \Throwable $exception - * @param array $props - * @return \Kirby\Form\Field */ - public static function exceptionField(Throwable $exception, array $props = []) - { + public static function exceptionField( + Throwable $exception, + array $props = [] + ): Field { $message = $exception->getMessage(); if (App::instance()->option('debug') === true) { - $message .= ' in file: ' . $exception->getFile() . ' line: ' . $exception->getLine(); + $message .= ' in file: ' . $exception->getFile(); + $message .= ' line: ' . $exception->getLine(); } $props = array_merge($props, [ @@ -204,11 +188,9 @@ class Form * Get the field object by name * and handle nested fields correctly * - * @param string $name * @throws \Kirby\Exception\NotFoundException - * @return \Kirby\Form\Field */ - public function field(string $name) + public function field(string $name): Field|FieldClass { $form = $this; $fieldNames = Str::split($name, '+'); @@ -223,9 +205,11 @@ class Form if ($count !== $index) { $form = $field->form(); } - } else { - throw new NotFoundException('The field "' . $fieldName . '" could not be found'); + + continue; } + + throw new NotFoundException('The field "' . $fieldName . '" could not be found'); } // it can get this error only if $name is an empty string as $name = '' @@ -238,21 +222,16 @@ class Form /** * Returns form fields - * - * @return \Kirby\Form\Fields|null */ - public function fields() + public function fields(): Fields|null { return $this->fields; } - /** - * @param \Kirby\Cms\Model $model - * @param array $props - * @return static - */ - public static function for(Model $model, array $props = []) - { + public static function for( + ModelWithContent $model, + array $props = [] + ): static { // get the original model data $original = $model->content($props['language'] ?? null)->toArray(); $values = $props['values'] ?? []; @@ -270,7 +249,10 @@ class Form $props['model'] = $model; // search for the blueprint - if (method_exists($model, 'blueprint') === true && $blueprint = $model->blueprint()) { + if ( + method_exists($model, 'blueprint') === true && + $blueprint = $model->blueprint() + ) { $props['fields'] = $blueprint->fields(); } @@ -289,18 +271,14 @@ class Form /** * Checks if the form is invalid - * - * @return bool */ public function isInvalid(): bool { - return empty($this->errors()) === false; + return $this->isValid() === false; } /** * Checks if the form is valid - * - * @return bool */ public function isValid(): bool { @@ -310,17 +288,15 @@ class Form /** * Disables fields in secondary languages when * they are configured to be untranslatable - * - * @param array $fields - * @param string|null $language - * @return array */ - protected static function prepareFieldsForLanguage(array $fields, string|null $language = null): array - { + protected static function prepareFieldsForLanguage( + array $fields, + string|null $language = null + ): array { $kirby = App::instance(null, true); // only modify the fields if we have a valid Kirby multilang instance - if (!$kirby || $kirby->multilang() === false) { + if ($kirby?->multilang() === false) { return $fields; } @@ -343,29 +319,20 @@ class Form * Converts the data of fields to strings * * @param false $defaults - * @return array */ public function strings($defaults = false): array { - $strings = []; - - foreach ($this->data($defaults) as $key => $value) { - if ($value === null) { - $strings[$key] = null; - } elseif (is_array($value) === true) { - $strings[$key] = Data::encode($value, 'yaml'); - } else { - $strings[$key] = $value; + return A::map( + $this->data($defaults), + fn ($value) => match (true) { + is_array($value) => Data::encode($value, 'yaml'), + default => $value } - } - - return $strings; + ); } /** * Converts the form to a plain array - * - * @return array */ public function toArray(): array { @@ -380,8 +347,6 @@ class Form /** * Returns form values - * - * @return array */ public function values(): array { diff --git a/kirby/src/Form/Mixin/EmptyState.php b/kirby/src/Form/Mixin/EmptyState.php index 9ea8b0c..6f7d72a 100644 --- a/kirby/src/Form/Mixin/EmptyState.php +++ b/kirby/src/Form/Mixin/EmptyState.php @@ -4,9 +4,9 @@ namespace Kirby\Form\Mixin; trait EmptyState { - protected $empty; + protected string|null $empty; - protected function setEmpty($empty = null) + protected function setEmpty(string|array|null $empty = null): void { $this->empty = $this->i18n($empty); } diff --git a/kirby/src/Form/Mixin/Max.php b/kirby/src/Form/Mixin/Max.php index 501ba2d..3141bbe 100644 --- a/kirby/src/Form/Mixin/Max.php +++ b/kirby/src/Form/Mixin/Max.php @@ -4,7 +4,7 @@ namespace Kirby\Form\Mixin; trait Max { - protected $max; + protected int|null $max; public function max(): int|null { diff --git a/kirby/src/Form/Mixin/Min.php b/kirby/src/Form/Mixin/Min.php index b177a08..1b585e1 100644 --- a/kirby/src/Form/Mixin/Min.php +++ b/kirby/src/Form/Mixin/Min.php @@ -4,7 +4,7 @@ namespace Kirby\Form\Mixin; trait Min { - protected $min; + protected int|null $min; public function min(): int|null { diff --git a/kirby/src/Form/Options.php b/kirby/src/Form/Options.php deleted file mode 100644 index 3b5da34..0000000 --- a/kirby/src/Form/Options.php +++ /dev/null @@ -1,205 +0,0 @@ - - * @link https://getkirby.com - * @copyright Bastian Allgeier - * @license https://opensource.org/licenses/MIT - * - * @deprecated 3.8.0 Use `Kirby\Option\Options` instead - */ -class Options -{ - /** - * Returns the classes of predefined Kirby objects - * - * @return array - */ - protected static function aliases(): array - { - return [ - 'Kirby\Cms\File' => 'file', - 'Kirby\Toolkit\Obj' => 'arrayItem', - 'Kirby\Cms\Block' => 'block', - 'Kirby\Cms\Page' => 'page', - 'Kirby\Cms\StructureObject' => 'structureItem', - 'Kirby\Cms\User' => 'user', - ]; - } - - /** - * Brings options through api - * - * @param $api - * @param \Kirby\Cms\Model|null $model - * @return array - */ - public static function api($api, $model = null): array - { - $model ??= App::instance()->site(); - $fetch = null; - $text = null; - $value = null; - - if (is_array($api) === true) { - $fetch = $api['fetch'] ?? null; - $text = $api['text'] ?? null; - $value = $api['value'] ?? null; - $url = $api['url'] ?? null; - } else { - $url = $api; - } - - $optionsApi = new OptionsApi([ - 'data' => static::data($model), - 'fetch' => $fetch, - 'url' => $url, - 'text' => $text, - 'value' => $value - ]); - - return $optionsApi->options(); - } - - /** - * @param \Kirby\Cms\Model $model - * @return array - */ - protected static function data($model): array - { - $kirby = $model->kirby(); - - // default data setup - $data = [ - 'kirby' => $kirby, - 'site' => $kirby->site(), - 'users' => $kirby->users(), - ]; - - // add the model by the proper alias - foreach (static::aliases() as $className => $alias) { - if ($model instanceof $className) { - $data[$alias] = $model; - } - } - - return $data; - } - - /** - * Brings options by supporting both api and query - * - * @param $options - * @param array $props - * @param \Kirby\Cms\Model|null $model - * @return array - */ - public static function factory($options, array $props = [], $model = null): array - { - $options = match ($options) { - 'api' => static::api($props['api'], $model), - 'query' => static::query($props['query'], $model), - 'pages' => static::query('site.index', $model), - 'children', - 'grandChildren', - 'siblings', - 'index', - 'files', - 'images', - 'documents', - 'videos', - 'audio', - 'code', - 'archives' => static::query('page.' . $options, $model), - default => $options - }; - - if (is_array($options) === false) { - return []; - } - - $result = []; - - foreach ($options as $key => $option) { - if (is_array($option) === false || isset($option['value']) === false) { - $option = [ - 'value' => is_int($key) ? $option : $key, - 'text' => $option - ]; - } - - // fallback for the text - $option['text'] ??= $option['value']; - - // translate the option text - if (is_array($option['text']) === true) { - $option['text'] = I18n::translate($option['text'], $option['text']); - } - - // add the option to the list - $result[] = $option; - } - - return $result; - } - - /** - * Brings options with query - * - * @param $query - * @param \Kirby\Cms\Model|null $model - * @return array - */ - public static function query($query, $model = null): array - { - $model ??= App::instance()->site(); - - // default text setup - $text = [ - 'arrayItem' => '{{ arrayItem.value }}', - 'block' => '{{ block.type }}: {{ block.id }}', - 'file' => '{{ file.filename }}', - 'page' => '{{ page.title }}', - 'structureItem' => '{{ structureItem.title }}', - 'user' => '{{ user.username }}', - ]; - - // default value setup - $value = [ - 'arrayItem' => '{{ arrayItem.value }}', - 'block' => '{{ block.id }}', - 'file' => '{{ file.id }}', - 'page' => '{{ page.id }}', - 'structureItem' => '{{ structureItem.id }}', - 'user' => '{{ user.email }}', - ]; - - // resolve array query setup - if (is_array($query) === true) { - $text = $query['text'] ?? $text; - $value = $query['value'] ?? $value; - $query = $query['fetch'] ?? null; - } - - $optionsQuery = new OptionsQuery([ - 'aliases' => static::aliases(), - 'data' => static::data($model), - 'query' => $query, - 'text' => $text, - 'value' => $value - ]); - - return $optionsQuery->options(); - } -} diff --git a/kirby/src/Form/OptionsApi.php b/kirby/src/Form/OptionsApi.php deleted file mode 100644 index a568520..0000000 --- a/kirby/src/Form/OptionsApi.php +++ /dev/null @@ -1,244 +0,0 @@ - - * @link https://getkirby.com - * @copyright Bastian Allgeier - * @license https://opensource.org/licenses/MIT - * - * @deprecated 3.8.0 Use `Kirby\Option\OptionsApi` instead - */ -class OptionsApi -{ - use Properties; - - /** - * @var array - */ - protected $data; - - /** - * @var string|null - */ - protected $fetch; - - /** - * @var array|string|null - */ - protected $options; - - /** - * @var string - */ - protected $text = '{{ item.value }}'; - - /** - * @var string - */ - protected $url; - - /** - * @var string - */ - protected $value = '{{ item.key }}'; - - /** - * OptionsApi constructor - * - * @param array $props - */ - public function __construct(array $props) - { - $this->setProperties($props); - } - - /** - * @return array - */ - public function data(): array - { - return $this->data; - } - - /** - * @return mixed - */ - public function fetch() - { - return $this->fetch; - } - - /** - * @param string $field - * @param array $data - * @return string - */ - protected function field(string $field, array $data): string - { - $value = $this->$field(); - return Str::safeTemplate($value, $data); - } - - /** - * @return array - * @throws \Exception - * @throws \Kirby\Exception\InvalidArgumentException - */ - public function options(): array - { - if (is_array($this->options) === true) { - return $this->options; - } - - if (Url::isAbsolute($this->url()) === true) { - // URL, request via cURL - $data = Remote::get($this->url())->json(); - } else { - // local file, get contents locally - - // ensure the file exists before trying to load it as the - // file_get_contents() warnings need to be suppressed - if (is_file($this->url()) !== true) { - throw new Exception('Local file ' . $this->url() . ' was not found'); - } - - $content = @file_get_contents($this->url()); - - if (is_string($content) !== true) { - throw new Exception('Unexpected read error'); // @codeCoverageIgnore - } - - if (empty($content) === true) { - return []; - } - - $data = json_decode($content, true); - } - - if (is_array($data) === false) { - throw new InvalidArgumentException('Invalid options format'); - } - - $result = (new Query($this->fetch()))->resolve(Nest::create($data)); - $options = []; - - foreach ($result as $item) { - $data = array_merge($this->data(), ['item' => $item]); - - $options[] = [ - 'text' => $this->field('text', $data), - 'value' => $this->field('value', $data), - ]; - } - - return $options; - } - - /** - * @param array $data - * @return $this - */ - protected function setData(array $data) - { - $this->data = $data; - return $this; - } - - /** - * @param string|null $fetch - * @return $this - */ - protected function setFetch(string|null $fetch = null) - { - $this->fetch = $fetch; - return $this; - } - - /** - * @param array|string|null $options - * @return $this - */ - protected function setOptions($options = null) - { - $this->options = $options; - return $this; - } - - /** - * @param string $text - * @return $this - */ - protected function setText(string|null $text = null) - { - $this->text = $text; - return $this; - } - - /** - * @param string $url - * @return $this - */ - protected function setUrl(string $url) - { - $this->url = $url; - return $this; - } - - /** - * @param string|null $value - * @return $this - */ - protected function setValue(string|null $value = null) - { - $this->value = $value; - return $this; - } - - /** - * @return string - */ - public function text(): string - { - return $this->text; - } - - /** - * @return array - * @throws \Kirby\Exception\InvalidArgumentException - */ - public function toArray(): array - { - return $this->options(); - } - - /** - * @return string - */ - public function url(): string - { - return Str::template($this->url, $this->data()); - } - - /** - * @return string - */ - public function value(): string - { - return $this->value; - } -} diff --git a/kirby/src/Form/OptionsQuery.php b/kirby/src/Form/OptionsQuery.php deleted file mode 100644 index 36ff287..0000000 --- a/kirby/src/Form/OptionsQuery.php +++ /dev/null @@ -1,273 +0,0 @@ - - * @link https://getkirby.com - * @copyright Bastian Allgeier - * @license https://opensource.org/licenses/MIT - * - * @deprecated 3.8.0 Use `Kirby\Option\OptionsQuery` instead - */ -class OptionsQuery -{ - use Properties; - - /** - * @var array - */ - protected $aliases = []; - - /** - * @var array - */ - protected $data; - - /** - * @var array|string|null - */ - protected $options; - - /** - * @var string - */ - protected $query; - - /** - * @var mixed - */ - protected $text; - - /** - * @var mixed - */ - protected $value; - - /** - * OptionsQuery constructor - * - * @param array $props - */ - public function __construct(array $props) - { - $this->setProperties($props); - } - - /** - * @return array - */ - public function aliases(): array - { - return $this->aliases; - } - - /** - * @return array - */ - public function data(): array - { - return $this->data; - } - - /** - * @param string $object - * @param string $field - * @param array $data - * @return string - * @throws \Kirby\Exception\NotFoundException - */ - protected function template(string $object, string $field, array $data) - { - $value = $this->$field(); - - if (is_array($value) === true) { - if (isset($value[$object]) === false) { - throw new NotFoundException('Missing "' . $field . '" definition'); - } - - $value = $value[$object]; - } - - return Str::safeTemplate($value, $data); - } - - /** - * @return array - */ - public function options(): array - { - if (is_array($this->options) === true) { - return $this->options; - } - - $data = $this->data(); - $query = new Query($this->query()); - $result = $query->resolve($data); - $result = $this->resultToCollection($result); - $options = []; - - foreach ($result as $item) { - $alias = $this->resolve($item); - $data = array_merge($data, [$alias => $item]); - - $options[] = [ - 'text' => $this->template($alias, 'text', $data), - 'value' => $this->template($alias, 'value', $data) - ]; - } - - return $this->options = $options; - } - - /** - * @return string - */ - public function query(): string - { - return $this->query; - } - - /** - * @param $object - * @return mixed|string|null - */ - public function resolve($object) - { - // fast access - if ($alias = ($this->aliases[get_class($object)] ?? null)) { - return $alias; - } - - // slow but precise resolving - foreach ($this->aliases as $className => $alias) { - if ($object instanceof $className) { - return $alias; - } - } - - return 'item'; - } - - /** - * @param $result - * @throws \Kirby\Exception\InvalidArgumentException - */ - protected function resultToCollection($result) - { - if (is_array($result)) { - foreach ($result as $key => $item) { - if (is_scalar($item) === true) { - $result[$key] = new Obj([ - 'key' => new Field(null, 'key', $key), - 'value' => new Field(null, 'value', $item), - ]); - } - } - - $result = new Collection($result); - } - - if ($result instanceof Collection === false) { - throw new InvalidArgumentException('Invalid query result data'); - } - - return $result; - } - - /** - * @param array|null $aliases - * @return $this - */ - protected function setAliases(array|null $aliases = null) - { - $this->aliases = $aliases; - return $this; - } - - /** - * @param array $data - * @return $this - */ - protected function setData(array $data) - { - $this->data = $data; - return $this; - } - - /** - * @param array|string|null $options - * @return $this - */ - protected function setOptions($options = null) - { - $this->options = $options; - return $this; - } - - /** - * @param string $query - * @return $this - */ - protected function setQuery(string $query) - { - $this->query = $query; - return $this; - } - - /** - * @param mixed $text - * @return $this - */ - protected function setText($text) - { - $this->text = $text; - return $this; - } - - /** - * @param mixed $value - * @return $this - */ - protected function setValue($value) - { - $this->value = $value; - return $this; - } - - /** - * @return mixed - */ - public function text() - { - return $this->text; - } - - public function toArray(): array - { - return $this->options(); - } - - /** - * @return mixed - */ - public function value() - { - return $this->value; - } -} diff --git a/kirby/src/Form/Validations.php b/kirby/src/Form/Validations.php index 5834624..7f0a539 100644 --- a/kirby/src/Form/Validations.php +++ b/kirby/src/Form/Validations.php @@ -20,8 +20,6 @@ class Validations * Validates if the field value is boolean * * @param \Kirby\Form\Field|\Kirby\Form\FieldClass $field - * @param $value - * @return bool * @throws \Kirby\Exception\InvalidArgumentException */ public static function boolean($field, $value): bool @@ -40,12 +38,9 @@ class Validations /** * Validates if the field value is valid date * - * @param \Kirby\Form\Field|\Kirby\Form\FieldClass $field - * @param $value - * @return bool * @throws \Kirby\Exception\InvalidArgumentException */ - public static function date($field, $value): bool + public static function date(Field|FieldClass $field, mixed $value): bool { if ($field->isEmpty($value) === false) { if (V::date($value) !== true) { @@ -61,12 +56,9 @@ class Validations /** * Validates if the field value is valid email * - * @param \Kirby\Form\Field|\Kirby\Form\FieldClass $field - * @param $value - * @return bool * @throws \Kirby\Exception\InvalidArgumentException */ - public static function email($field, $value): bool + public static function email(Field|FieldClass $field, mixed $value): bool { if ($field->isEmpty($value) === false) { if (V::email($value) === false) { @@ -82,14 +74,14 @@ class Validations /** * Validates if the field value is maximum * - * @param \Kirby\Form\Field|\Kirby\Form\FieldClass $field - * @param $value - * @return bool * @throws \Kirby\Exception\InvalidArgumentException */ - public static function max($field, $value): bool + public static function max(Field|FieldClass $field, mixed $value): bool { - if ($field->isEmpty($value) === false && $field->max() !== null) { + if ( + $field->isEmpty($value) === false && + $field->max() !== null + ) { if (V::max($value, $field->max()) === false) { throw new InvalidArgumentException( V::message('max', $value, $field->max()) @@ -103,14 +95,14 @@ class Validations /** * Validates if the field value is max length * - * @param \Kirby\Form\Field|\Kirby\Form\FieldClass $field - * @param $value - * @return bool * @throws \Kirby\Exception\InvalidArgumentException */ - public static function maxlength($field, $value): bool + public static function maxlength(Field|FieldClass $field, mixed $value): bool { - if ($field->isEmpty($value) === false && $field->maxlength() !== null) { + if ( + $field->isEmpty($value) === false && + $field->maxlength() !== null + ) { if (V::maxLength($value, $field->maxlength()) === false) { throw new InvalidArgumentException( V::message('maxlength', $value, $field->maxlength()) @@ -124,14 +116,14 @@ class Validations /** * Validates if the field value is minimum * - * @param \Kirby\Form\Field|\Kirby\Form\FieldClass $field - * @param $value - * @return bool * @throws \Kirby\Exception\InvalidArgumentException */ - public static function min($field, $value): bool + public static function min(Field|FieldClass $field, mixed $value): bool { - if ($field->isEmpty($value) === false && $field->min() !== null) { + if ( + $field->isEmpty($value) === false && + $field->min() !== null + ) { if (V::min($value, $field->min()) === false) { throw new InvalidArgumentException( V::message('min', $value, $field->min()) @@ -145,14 +137,14 @@ class Validations /** * Validates if the field value is min length * - * @param \Kirby\Form\Field|\Kirby\Form\FieldClass $field - * @param $value - * @return bool * @throws \Kirby\Exception\InvalidArgumentException */ - public static function minlength($field, $value): bool + public static function minlength(Field|FieldClass $field, mixed $value): bool { - if ($field->isEmpty($value) === false && $field->minlength() !== null) { + if ( + $field->isEmpty($value) === false && + $field->minlength() !== null + ) { if (V::minLength($value, $field->minlength()) === false) { throw new InvalidArgumentException( V::message('minlength', $value, $field->minlength()) @@ -166,12 +158,9 @@ class Validations /** * Validates if the field value matches defined pattern * - * @param \Kirby\Form\Field|\Kirby\Form\FieldClass $field - * @param $value - * @return bool * @throws \Kirby\Exception\InvalidArgumentException */ - public static function pattern($field, $value): bool + public static function pattern(Field|FieldClass $field, mixed $value): bool { if ($field->isEmpty($value) === false && $field->pattern() !== null) { if (V::match($value, '/' . $field->pattern() . '/i') === false) { @@ -187,14 +176,15 @@ class Validations /** * Validates if the field value is required * - * @param \Kirby\Form\Field|\Kirby\Form\FieldClass $field - * @param $value - * @return bool * @throws \Kirby\Exception\InvalidArgumentException */ - public static function required($field, $value): bool + public static function required(Field|FieldClass $field, mixed $value): bool { - if ($field->isRequired() === true && $field->save() === true && $field->isEmpty($value) === true) { + if ( + $field->isRequired() === true && + $field->save() === true && + $field->isEmpty($value) === true + ) { throw new InvalidArgumentException([ 'key' => 'validation.required' ]); @@ -206,12 +196,9 @@ class Validations /** * Validates if the field value is in defined options * - * @param \Kirby\Form\Field|\Kirby\Form\FieldClass $field - * @param $value - * @return bool * @throws \Kirby\Exception\InvalidArgumentException */ - public static function option($field, $value): bool + public static function option(Field|FieldClass $field, mixed $value): bool { if ($field->isEmpty($value) === false) { $values = array_column($field->options(), 'value'); @@ -229,12 +216,9 @@ class Validations /** * Validates if the field values is in defined options * - * @param \Kirby\Form\Field|\Kirby\Form\FieldClass $field - * @param $value - * @return bool * @throws \Kirby\Exception\InvalidArgumentException */ - public static function options($field, $value): bool + public static function options(Field|FieldClass $field, mixed $value): bool { if ($field->isEmpty($value) === false) { $values = array_column($field->options(), 'value'); @@ -253,12 +237,9 @@ class Validations /** * Validates if the field value is valid time * - * @param \Kirby\Form\Field|\Kirby\Form\FieldClass $field - * @param $value - * @return bool * @throws \Kirby\Exception\InvalidArgumentException */ - public static function time($field, $value): bool + public static function time(Field|FieldClass $field, mixed $value): bool { if ($field->isEmpty($value) === false) { if (V::time($value) !== true) { @@ -274,12 +255,9 @@ class Validations /** * Validates if the field value is valid url * - * @param \Kirby\Form\Field|\Kirby\Form\FieldClass $field - * @param $value - * @return bool * @throws \Kirby\Exception\InvalidArgumentException */ - public static function url($field, $value): bool + public static function url(Field|FieldClass $field, mixed $value): bool { if ($field->isEmpty($value) === false) { if (V::url($value) === false) { diff --git a/kirby/src/Http/Cookie.php b/kirby/src/Http/Cookie.php index 1d5c3e6..a44b752 100644 --- a/kirby/src/Http/Cookie.php +++ b/kirby/src/Http/Cookie.php @@ -39,8 +39,11 @@ class Cookie * @return bool true: cookie was created, * false: cookie creation failed */ - public static function set(string $key, string $value, array $options = []): bool - { + public static function set( + string $key, + string $value, + array $options = [] + ): bool { // modify CMS caching behavior static::trackUsage($key); @@ -59,8 +62,11 @@ class Cookie $_COOKIE[$key] = $value; // store the cookie - $options = compact('expires', 'path', 'domain', 'secure', 'httponly', 'samesite'); - return setcookie($key, $value, $options); + return setcookie( + $key, + $value, + compact('expires', 'path', 'domain', 'secure', 'httponly', 'samesite') + ); } /** @@ -70,13 +76,13 @@ class Cookie */ public static function lifetime(int $minutes): int { + // absolute timestamp if ($minutes > 1000000000) { - // absolute timestamp return $minutes; } + // minutes from now if ($minutes > 0) { - // minutes from now return time() + ($minutes * 60); } @@ -100,8 +106,11 @@ class Cookie * @return bool true: cookie was created, * false: cookie creation failed */ - public static function forever(string $key, string $value, array $options = []): bool - { + public static function forever( + string $key, + string $value, + array $options = [] + ): bool { // 9999-12-31 if supported (lower on 32-bit servers) $options['lifetime'] = min(253402214400, PHP_INT_MAX); return static::set($key, $value, $options); @@ -111,10 +120,8 @@ class Cookie * Get a cookie value * * - * * cookie::get('mycookie', 'peter'); * // sample output: 'hello' or if the cookie is not set 'peter' - * * * * @param string|null $key The name of the cookie @@ -122,8 +129,10 @@ class Cookie * if the cookie has not been found * @return string|array|null The found value */ - public static function get(string|null $key = null, string|null $default = null): string|array|null - { + public static function get( + string|null $key = null, + string|null $default = null + ): string|array|null { if ($key === null) { return $_COOKIE; } @@ -131,8 +140,11 @@ class Cookie // modify CMS caching behavior static::trackUsage($key); - $value = $_COOKIE[$key] ?? null; - return empty($value) ? $default : static::parse($value); + if ($value = $_COOKIE[$key] ?? null) { + return static::parse($value); + } + + return $default; } /** diff --git a/kirby/src/Http/Environment.php b/kirby/src/Http/Environment.php index f60b055..8eccc13 100644 --- a/kirby/src/Http/Environment.php +++ b/kirby/src/Http/Environment.php @@ -103,14 +103,16 @@ class Environment * * @param array|null $info Optional override for `$_SERVER` */ - public function __construct(array|null $options = null, array|null $info = null) - { + public function __construct( + array|null $options = null, + array|null $info = null + ) { $this->detect($options, $info); } /** * Returns the server's IP address - * @see static::ip + * @see ::ip */ public function address(): string|null { @@ -150,13 +152,17 @@ class Environment * * @param array|null $info Optional override for `$_SERVER` */ - public function detect(array $options = null, array $info = null): array - { - $info ??= $_SERVER; - $options = array_merge([ + public function detect( + array $options = null, + array $info = null + ): array { + $defaults = [ 'cli' => null, 'allowed' => null - ], $options ?? []); + ]; + + $info ??= $_SERVER; + $options = array_merge($defaults, $options ?? []); $this->info = static::sanitize($info); $this->cli = $this->detectCli($options['cli']); @@ -235,9 +241,9 @@ class Environment $uri = new Uri($url, ['slash' => false]); + // the current environment is allowed, + // stop before the exception below is thrown if ($uri->toString() === $this->baseUrl) { - // the current environment is allowed, - // stop before the exception below is thrown return; } } @@ -318,7 +324,11 @@ class Environment // @codeCoverageIgnoreStart $term = getenv('TERM'); - if (substr(PHP_SAPI, 0, 3) === 'cgi' && $term && $term !== 'unknown') { + if ( + substr(PHP_SAPI, 0, 3) === 'cgi' && + $term && + $term !== 'unknown' + ) { return true; } @@ -340,8 +350,7 @@ class Environment ]; // prefer the standardized `Forwarded` header if defined - $forwarded = $this->get('HTTP_FORWARDED'); - if ($forwarded) { + if ($forwarded = $this->get('HTTP_FORWARDED')) { // only use the first (outermost) proxy by using the first set of values // before the first comma (but only a comma outside of quotes) if (Str::contains($forwarded, ',') === true) { @@ -513,7 +522,9 @@ class Environment return false; } - return in_array(strtolower($protocol), ['https', 'https, http']) === true; + $protocols = ['https', 'https, http']; + + return in_array(strtolower($protocol), $protocols) === true; } /** @@ -660,11 +671,12 @@ class Environment * @param mixed $default Optional default value, which should be * returned if no element has been found */ - public static function getGlobally(string|false|null $key = null, $default = null) - { + public static function getGlobally( + string|false|null $key = null, + $default = null + ) { // first try the global `Environment` object if the CMS is running - $app = App::instance(null, true); - if ($app) { + if ($app = App::instance(null, true)) { return $app->environment()->get($key, $default); } @@ -851,8 +863,10 @@ class Environment /** * Sanitizes some `$_SERVER` keys */ - public static function sanitize(string|array $key, $value = null) - { + public static function sanitize( + string|array $key, + $value = null + ) { if (is_array($key) === true) { foreach ($key as $k => $v) { $key[$k] = static::sanitize($k, $v); @@ -877,8 +891,9 @@ class Environment /** * Sanitizes the given host name */ - protected static function sanitizeHost(string|null $host = null): string|null - { + protected static function sanitizeHost( + string|null $host = null + ): string|null { if (empty($host) === true) { return null; } @@ -902,8 +917,9 @@ class Environment /** * Sanitizes the given port number */ - protected static function sanitizePort(string|int|false|null $port = null): int|null - { + protected static function sanitizePort( + string|int|false|null $port = null + ): int|null { // already fine if (is_int($port) === true) { return $port; diff --git a/kirby/src/Http/Header.php b/kirby/src/Http/Header.php index 0c36b58..857798c 100644 --- a/kirby/src/Http/Header.php +++ b/kirby/src/Http/Header.php @@ -18,7 +18,6 @@ class Header { // configuration public static array $codes = [ - // successful '_200' => 'OK', '_201' => 'Created', @@ -58,8 +57,11 @@ class Header * * @return string|void */ - public static function contentType(string $mime, string $charset = 'UTF-8', bool $send = true) - { + public static function contentType( + string $mime, + string $charset = 'UTF-8', + bool $send = true + ) { if ($found = F::extensionToMime($mime)) { $mime = $found; } @@ -80,8 +82,10 @@ class Header /** * Creates headers by key and value */ - public static function create(string|array $key, string|null $value = null): string - { + public static function create( + string|array $key, + string|null $value = null + ): string { if (is_array($key) === true) { $headers = []; @@ -92,7 +96,8 @@ class Header return implode("\r\n", $headers); } - // prevent header injection by stripping any newline characters from single headers + // prevent header injection by stripping + // any newline characters from single headers return str_replace(["\r", "\n"], '', $key . ': ' . $value); } @@ -258,8 +263,11 @@ class Header * * @return string|void */ - public static function redirect(string $url, int $code = 302, bool $send = true) - { + public static function redirect( + string $url, + int $code = 302, + bool $send = true + ) { $status = static::status($code, false); $location = 'Location:' . Url::unIdn($url); diff --git a/kirby/src/Http/Params.php b/kirby/src/Http/Params.php index 6ad821f..4067a0f 100644 --- a/kirby/src/Http/Params.php +++ b/kirby/src/Http/Params.php @@ -93,7 +93,7 @@ class Params extends Obj public function isNotEmpty(): bool { - return empty((array)$this) === false; + return $this->isEmpty() === false; } /** diff --git a/kirby/src/Http/Query.php b/kirby/src/Http/Query.php index 8cd3de8..410e2f0 100644 --- a/kirby/src/Http/Query.php +++ b/kirby/src/Http/Query.php @@ -33,7 +33,7 @@ class Query extends Obj public function isNotEmpty(): bool { - return empty((array)$this) === false; + return $this->isEmpty() === false; } public function toString(bool $questionMark = false): string diff --git a/kirby/src/Http/Remote.php b/kirby/src/Http/Remote.php index 2348ec4..ce93bb3 100644 --- a/kirby/src/Http/Remote.php +++ b/kirby/src/Http/Remote.php @@ -50,17 +50,6 @@ class Remote public array $options = []; /** - * Magic getter for request info data - */ - public function __call(string $method, array $arguments = []) - { - $method = str_replace('-', '_', Str::kebab($method)); - return $this->info[$method] ?? null; - } - - /** - * Constructor - * * @throws \Exception when the curl request failed */ public function __construct(string $url, array $options = []) @@ -76,8 +65,7 @@ class Remote // update the defaults with App config if set; // request the App instance lazily - $app = App::instance(null, true); - if ($app !== null) { + if ($app = App::instance(null, true)) { $defaults = array_merge($defaults, $app->option('remote', [])); } @@ -91,8 +79,19 @@ class Remote $this->fetch(); } - public static function __callStatic(string $method, array $arguments = []): static + /** + * Magic getter for request info data + */ + public function __call(string $method, array $arguments = []) { + $method = str_replace('-', '_', Str::kebab($method)); + return $this->info[$method] ?? null; + } + + public static function __callStatic( + string $method, + array $arguments = [] + ): static { return new static( url: $arguments[0], options: array_merge( diff --git a/kirby/src/Http/Request.php b/kirby/src/Http/Request.php index 3ad338c..85178a9 100644 --- a/kirby/src/Http/Request.php +++ b/kirby/src/Http/Request.php @@ -4,6 +4,9 @@ namespace Kirby\Http; use Kirby\Cms\App; use Kirby\Http\Request\Auth; +use Kirby\Http\Request\Auth\BasicAuth; +use Kirby\Http\Request\Auth\BearerAuth; +use Kirby\Http\Request\Auth\SessionAuth; use Kirby\Http\Request\Body; use Kirby\Http\Request\Files; use Kirby\Http\Request\Query; @@ -24,9 +27,9 @@ use Kirby\Toolkit\Str; class Request { public static array $authTypes = [ - 'basic' => 'Kirby\Http\Request\Auth\BasicAuth', - 'bearer' => 'Kirby\Http\Request\Auth\BearerAuth', - 'session' => 'Kirby\Http\Request\Auth\SessionAuth', + 'basic' => BasicAuth::class, + 'bearer' => BearerAuth::class, + 'session' => SessionAuth::class, ]; /** @@ -99,24 +102,37 @@ class Request $this->method = $this->detectRequestMethod($options['method'] ?? null); if (isset($options['body']) === true) { - $this->body = $options['body'] instanceof Body ? $options['body'] : new Body($options['body']); + $this->body = + $options['body'] instanceof Body + ? $options['body'] + : new Body($options['body']); } if (isset($options['files']) === true) { - $this->files = $options['files'] instanceof Files ? $options['files'] : new Files($options['files']); + $this->files = + $options['files'] instanceof Files + ? $options['files'] + : new Files($options['files']); } if (isset($options['query']) === true) { - $this->query = $options['query'] instanceof Query ? $options['query'] : new Query($options['query']); + $this->query = + $options['query'] instanceof Query + ? $options['query'] + : new Query($options['query']); } if (isset($options['url']) === true) { - $this->url = $options['url'] instanceof Uri ? $options['url'] : new Uri($options['url']); + $this->url = + $options['url'] instanceof Uri + ? $options['url'] + : new Uri($options['url']); } } /** * Improved `var_dump` output + * @codeCoverageIgnore */ public function __debugInfo(): array { @@ -195,7 +211,10 @@ class Request */ public function data(): array { - return array_replace($this->body()->toArray(), $this->query()->toArray()); + return array_replace( + $this->body()->toArray(), + $this->query()->toArray() + ); } /** @@ -215,7 +234,7 @@ class Request } // final chain of options to detect the method - $method = $method ?? Environment::getGlobally('REQUEST_METHOD', 'GET'); + $method ??= Environment::getGlobally('REQUEST_METHOD', 'GET'); // uppercase the shit out of it $method = strtoupper($method); diff --git a/kirby/src/Http/Request/Auth.php b/kirby/src/Http/Request/Auth.php index f197379..e73f4da 100644 --- a/kirby/src/Http/Request/Auth.php +++ b/kirby/src/Http/Request/Auth.php @@ -16,19 +16,12 @@ use SensitiveParameter; abstract class Auth { /** - * Raw authentication data after the first space - * in the `Authorization` header - */ - protected string $data; - - /** - * Constructor + * @param string $data Raw authentication data after the first space in the `Authorization` header */ public function __construct( #[SensitiveParameter] - string $data + protected string $data ) { - $this->data = $data; } /** diff --git a/kirby/src/Http/Request/Data.php b/kirby/src/Http/Request/Data.php index 58be233..d2f3d95 100644 --- a/kirby/src/Http/Request/Data.php +++ b/kirby/src/Http/Request/Data.php @@ -20,6 +20,7 @@ trait Data { /** * Improved `var_dump` output + * @codeCoverageIgnore */ public function __debugInfo(): array { diff --git a/kirby/src/Http/Response.php b/kirby/src/Http/Response.php index 9146832..53892d6 100644 --- a/kirby/src/Http/Response.php +++ b/kirby/src/Http/Response.php @@ -6,7 +6,6 @@ use Closure; use Exception; use Kirby\Exception\LogicException; use Kirby\Filesystem\F; -use Throwable; /** * Representation of an Http response, @@ -82,6 +81,7 @@ class Response /** * Improved `var_dump` output + * @codeCoverageIgnore */ public function __debugInfo(): array { @@ -95,11 +95,7 @@ class Response */ public function __toString(): string { - try { - return $this->send(); - } catch (Throwable) { - return ''; - } + return $this->send(); } /** @@ -198,9 +194,8 @@ class Response * @since 3.7.0 * * @codeCoverageIgnore - * @todo Change return type to `never` once support for PHP 8.0 is dropped */ - public static function go(string $url = '/', int $code = 302): void + public static function go(string $url = '/', int $code = 302): never { die(static::redirect($url, $code)); } @@ -251,7 +246,7 @@ class Response array $headers = [] ): static { if (is_array($body) === true) { - $body = json_encode($body, $pretty === true ? JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES : 0); + $body = json_encode($body, $pretty === true ? JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES : 0); } return new static([ diff --git a/kirby/src/Http/Route.php b/kirby/src/Http/Route.php index 18796c4..7d6dd7d 100644 --- a/kirby/src/Http/Route.php +++ b/kirby/src/Http/Route.php @@ -63,7 +63,7 @@ class Route /** * Magic getter for route attributes */ - public function __call(string $key, array $arguments = null) + public function __call(string $key, array $args = null): mixed { return $this->attributes[$key] ?? null; } diff --git a/kirby/src/Http/Uri.php b/kirby/src/Http/Uri.php index a44526f..08c4618 100644 --- a/kirby/src/Http/Uri.php +++ b/kirby/src/Http/Uri.php @@ -4,7 +4,6 @@ namespace Kirby\Http; use Kirby\Cms\App; use Kirby\Exception\InvalidArgumentException; -use Kirby\Toolkit\Properties; use SensitiveParameter; use Throwable; @@ -19,8 +18,6 @@ use Throwable; */ class Uri { - use Properties; - /** * Cache for the current Uri object */ @@ -29,32 +26,32 @@ class Uri /** * The fragment after the hash */ - protected string|false|null $fragment = null; + protected string|false|null $fragment; /** * The host address */ - protected string|null $host = null; + protected string|null $host; /** * The optional password for basic authentication */ - protected string|false|null $password = null; + protected string|false|null $password; /** * The optional list of params */ - protected Params|null $params = null; + protected Params $params; /** * The optional path */ - protected Path|null $path = null; + protected Path $path; /** * The optional port number */ - protected int|false|null $port = null; + protected int|false|null $port; /** * All original properties @@ -64,44 +61,25 @@ class Uri /** * The optional query string without leading ? */ - protected Query|null $query = null; + protected Query $query; /** * https or http */ - protected string|null $scheme = 'http'; + protected string|null $scheme; /** * Supported schemes */ protected static array $schemes = ['http', 'https', 'ftp']; - protected bool $slash = false; + protected bool $slash; /** * The optional username for basic authentication */ protected string|false|null $username = null; - /** - * Magic caller to access all properties - */ - public function __call(string $property, array $arguments = []) - { - return $this->$property ?? null; - } - - /** - * Make sure that cloning also clones - * the path and query objects - */ - public function __clone() - { - $this->path = clone $this->path; - $this->query = clone $this->query; - $this->params = clone $this->params; - } - /** * Creates a new URI object * @@ -122,7 +100,36 @@ class Uri $props = static::parsePath($props); } - $this->setProperties($this->props = $props); + $this->props = $props; + $this->setFragment($props['fragment'] ?? null); + $this->setHost($props['host'] ?? null); + $this->setParams($props['params'] ?? null); + $this->setPassword($props['password'] ?? null); + $this->setPath($props['path'] ?? null); + $this->setPort($props['port'] ?? null); + $this->setQuery($props['query'] ?? null); + $this->setScheme($props['scheme'] ?? 'http'); + $this->setSlash($props['slash'] ?? false); + $this->setUsername($props['username'] ?? null); + } + + /** + * Magic caller to access all properties + */ + public function __call(string $property, array $arguments = []) + { + return $this->$property ?? null; + } + + /** + * Make sure that cloning also clones + * the path and query objects + */ + public function __clone() + { + $this->path = clone $this->path; + $this->query = clone $this->query; + $this->params = clone $this->params; } /** @@ -416,7 +423,7 @@ class Uri { $array = []; - foreach ($this->propertyData as $key => $value) { + foreach ($this->props as $key => $value) { $value = $this->$key; if (is_object($value) === true) { diff --git a/kirby/src/Http/Url.php b/kirby/src/Http/Url.php index 7a52907..622f350 100644 --- a/kirby/src/Http/Url.php +++ b/kirby/src/Http/Url.php @@ -38,9 +38,13 @@ class Url * Url Builder * Actually just a factory for `new Uri($parts)` */ - public static function build(array $parts = [], string|null $url = null): string - { - return (string)(new Uri($url ?? static::current()))->clone($parts); + public static function build( + array $parts = [], + string|null $url = null + ): string { + $url ??= static::current(); + $uri = new Uri($url); + return $uri->clone($parts)->toString(); } /** @@ -139,7 +143,9 @@ class Url bool $leadingSlash = false, bool $trailingSlash = false ): string { - return Url::toObject($url)->path()->toString($leadingSlash, $trailingSlash); + return Url::toObject($url) + ->path() + ->toString($leadingSlash, $trailingSlash); } /** @@ -212,8 +218,10 @@ class Url /** * Smart resolver for internal and external urls */ - public static function to(string|null $path = null, array $options = null): string - { + public static function to( + string|null $path = null, + array $options = null + ): string { // make sure $path is string $path ??= ''; diff --git a/kirby/src/Http/Visitor.php b/kirby/src/Http/Visitor.php index bf1d56b..ea2ac2e 100644 --- a/kirby/src/Http/Visitor.php +++ b/kirby/src/Http/Visitor.php @@ -34,10 +34,19 @@ class Visitor */ public function __construct(array $arguments = []) { - $this->ip($arguments['ip'] ?? Environment::getGlobally('REMOTE_ADDR', '')); - $this->userAgent($arguments['userAgent'] ?? Environment::getGlobally('HTTP_USER_AGENT', '')); - $this->acceptedLanguage($arguments['acceptedLanguage'] ?? Environment::getGlobally('HTTP_ACCEPT_LANGUAGE', '')); - $this->acceptedMimeType($arguments['acceptedMimeType'] ?? Environment::getGlobally('HTTP_ACCEPT', '')); + $ip = $arguments['ip'] ?? null; + $ip ??= Environment::getGlobally('REMOTE_ADDR', ''); + $agent = $arguments['userAgent'] ?? null; + $agent ??= Environment::getGlobally('HTTP_USER_AGENT', ''); + $language = $arguments['acceptedLanguage'] ?? null; + $language ??= Environment::getGlobally('HTTP_ACCEPT_LANGUAGE', ''); + $mime = $arguments['acceptedMimeType'] ?? null; + $mime ??= Environment::getGlobally('HTTP_ACCEPT', ''); + + $this->ip($ip); + $this->userAgent($agent); + $this->acceptedLanguage($language); + $this->acceptedMimeType($mime); } /** @@ -47,8 +56,9 @@ class Visitor * * @return $this|\Kirby\Toolkit\Obj|null */ - public function acceptedLanguage(string|null $acceptedLanguage = null): static|Obj|null - { + public function acceptedLanguage( + string|null $acceptedLanguage = null + ): static|Obj|null { if ($acceptedLanguage === null) { return $this->acceptedLanguages()->first(); } @@ -108,8 +118,9 @@ class Visitor * * @return $this|\Kirby\Toolkit\Obj|null */ - public function acceptedMimeType(string|null $acceptedMimeType = null): static|Obj|null - { + public function acceptedMimeType( + string|null $acceptedMimeType = null + ): static|Obj|null { if ($acceptedMimeType === null) { return $this->acceptedMimeTypes()->first(); } @@ -178,7 +189,8 @@ class Visitor */ public function prefersJson(): bool { - return $this->preferredMimeType('application/json', 'text/html') === 'application/json'; + $preferred = $this->preferredMimeType('application/json', 'text/html'); + return $preferred === 'application/json'; } /** @@ -193,6 +205,7 @@ class Visitor if ($ip === null) { return $this->ip; } + $this->ip = $ip; return $this; } @@ -209,6 +222,7 @@ class Visitor if ($userAgent === null) { return $this->userAgent; } + $this->userAgent = $userAgent; return $this; } diff --git a/kirby/src/Image/Camera.php b/kirby/src/Image/Camera.php index 8655330..c149c2e 100644 --- a/kirby/src/Image/Camera.php +++ b/kirby/src/Image/Camera.php @@ -13,16 +13,7 @@ namespace Kirby\Image; */ class Camera { - /** - * Make exif data - */ protected string|null $make; - - /** - * Model exif data - * - * @var - */ protected string|null $model; public function __construct(array $exif) @@ -68,6 +59,7 @@ class Camera /** * Improved `var_dump` output + * @codeCoverageIgnore */ public function __debugInfo(): array { diff --git a/kirby/src/Image/Darkroom.php b/kirby/src/Image/Darkroom.php index e8277ab..a09747c 100644 --- a/kirby/src/Image/Darkroom.php +++ b/kirby/src/Image/Darkroom.php @@ -3,6 +3,8 @@ namespace Kirby\Image; use Exception; +use Kirby\Image\Darkroom\GdLib; +use Kirby\Image\Darkroom\ImageMagick; /** * A wrapper around resizing and cropping @@ -17,17 +19,13 @@ use Exception; class Darkroom { public static array $types = [ - 'gd' => 'Kirby\Image\Darkroom\GdLib', - 'im' => 'Kirby\Image\Darkroom\ImageMagick' + 'gd' => GdLib::class, + 'im' => ImageMagick::class ]; - protected array $settings = []; - - /** - * Darkroom constructor - */ - public function __construct(array $settings = []) - { + public function __construct( + protected array $settings = [] + ) { $this->settings = array_merge($this->defaults(), $settings); } @@ -110,18 +108,24 @@ class Darkroom $options = $this->options($options); $image = new Image($file); - $dimensions = $image->dimensions(); - $thumbDimensions = $dimensions->thumb($options); + $options['sourceWidth'] = $image->width(); + $options['sourceHeight'] = $image->height(); - $sourceWidth = $image->width(); - $sourceHeight = $image->height(); + $dimensions = $image->dimensions(); + $thumbDimensions = $dimensions->thumb($options); $options['width'] = $thumbDimensions->width(); $options['height'] = $thumbDimensions->height(); // scale ratio compared to the source dimensions - $options['scaleWidth'] = $sourceWidth ? $options['width'] / $sourceWidth : null; - $options['scaleHeight'] = $sourceHeight ? $options['height'] / $sourceHeight : null; + $options['scaleWidth'] = Focus::ratio( + $options['width'], + $options['sourceWidth'] + ); + $options['scaleHeight'] = Focus::ratio( + $options['height'], + $options['sourceHeight'] + ); return $options; } diff --git a/kirby/src/Image/Darkroom/GdLib.php b/kirby/src/Image/Darkroom/GdLib.php index 62f31cf..8e05f72 100644 --- a/kirby/src/Image/Darkroom/GdLib.php +++ b/kirby/src/Image/Darkroom/GdLib.php @@ -5,6 +5,7 @@ namespace Kirby\Image\Darkroom; use claviska\SimpleImage; use Kirby\Filesystem\Mime; use Kirby\Image\Darkroom; +use Kirby\Image\Focus; /** * GdLib @@ -56,10 +57,34 @@ class GdLib extends Darkroom */ protected function resize(SimpleImage $image, array $options): SimpleImage { + // just resize, no crop if ($options['crop'] === false) { return $image->resize($options['width'], $options['height']); } + // crop based on focus point + if (Focus::isFocalPoint($options['crop']) === true) { + // get crop coords for focal point: + // if image needs to be cropped, crop before resizing + if ($focus = Focus::coords( + $options['crop'], + $options['sourceWidth'], + $options['sourceHeight'], + $options['width'], + $options['height'] + )) { + $image->crop( + $focus['x1'], + $focus['y1'], + $focus['x2'], + $focus['y2'] + ); + } + + return $image->thumbnail($options['width'], $options['height']); + } + + // normal crop with crop anchor return $image->thumbnail( $options['width'], $options['height'] ?? $options['width'], diff --git a/kirby/src/Image/Darkroom/ImageMagick.php b/kirby/src/Image/Darkroom/ImageMagick.php index cf10cf2..ea7e8a1 100644 --- a/kirby/src/Image/Darkroom/ImageMagick.php +++ b/kirby/src/Image/Darkroom/ImageMagick.php @@ -5,6 +5,7 @@ namespace Kirby\Image\Darkroom; use Exception; use Kirby\Filesystem\F; use Kirby\Image\Darkroom; +use Kirby\Image\Focus; /** * ImageMagick @@ -167,20 +168,39 @@ class ImageMagick extends Darkroom return '-thumbnail ' . escapeshellarg(sprintf('%sx%s!', $options['width'], $options['height'])); } - $gravities = [ + // crop based on focus point + if (Focus::isFocalPoint($options['crop']) === true) { + if ($focus = Focus::coords( + $options['crop'], + $options['sourceWidth'], + $options['sourceHeight'], + $options['width'], + $options['height'] + )) { + return sprintf( + '-crop %sx%s+%s+%s -resize %sx%s^', + $focus['width'], + $focus['height'], + $focus['x1'], + $focus['y1'], + $options['width'], + $options['height'] + ); + } + } + + // translate the gravity option into something imagemagick understands + $gravity = match ($options['crop'] ?? null) { 'top left' => 'NorthWest', 'top' => 'North', 'top right' => 'NorthEast', 'left' => 'West', - 'center' => 'Center', 'right' => 'East', 'bottom left' => 'SouthWest', 'bottom' => 'South', - 'bottom right' => 'SouthEast' - ]; - - // translate the gravity option into something imagemagick understands - $gravity = $gravities[$options['crop']] ?? 'Center'; + 'bottom right' => 'SouthEast', + default => 'Center' + }; $command = '-thumbnail ' . escapeshellarg(sprintf('%sx%s^', $options['width'], $options['height'])); $command .= ' -gravity ' . escapeshellarg($gravity); diff --git a/kirby/src/Image/Dimensions.php b/kirby/src/Image/Dimensions.php index 54bb636..cf6d334 100644 --- a/kirby/src/Image/Dimensions.php +++ b/kirby/src/Image/Dimensions.php @@ -26,6 +26,7 @@ class Dimensions /** * Improved `var_dump` output + * @codeCoverageIgnore */ public function __debugInfo(): array { @@ -138,8 +139,10 @@ class Dimensions * upscaled to fit the box if smaller * @return $this object with recalculated dimensions */ - public function fitHeight(int|null $fit = null, bool $force = false): static - { + public function fitHeight( + int|null $fit = null, + bool $force = false + ): static { return $this->fitSize('height', $fit, $force); } @@ -152,8 +155,11 @@ class Dimensions * upscaled to fit the box if smaller * @return $this object with recalculated dimensions */ - protected function fitSize(string $ref, int|null $fit = null, bool $force = false): static - { + protected function fitSize( + string $ref, + int|null $fit = null, + bool $force = false + ): static { if ($fit === 0 || $fit === null) { return $this; } @@ -191,8 +197,10 @@ class Dimensions * upscaled to fit the box if smaller * @return $this object with recalculated dimensions */ - public function fitWidth(int|null $fit = null, bool $force = false): static - { + public function fitWidth( + int|null $fit = null, + bool $force = false + ): static { return $this->fitSize('width', $fit, $force); } @@ -254,8 +262,7 @@ class Dimensions $xml = simplexml_load_string($content); if ($xml !== false) { - $attr = $xml->attributes(); - + $attr = $xml->attributes(); $rawWidth = $attr->width; $width = (int)$rawWidth; $rawHeight = $attr->height; @@ -300,11 +307,11 @@ class Dimensions return false; } - if ($this->portrait()) { + if ($this->portrait() === true) { return 'portrait'; } - if ($this->landscape()) { + if ($this->landscape() === true) { return 'landscape'; } @@ -336,7 +343,7 @@ class Dimensions return $this->width / $this->height; } - return 0; + return 0.0; } /** diff --git a/kirby/src/Image/Exif.php b/kirby/src/Image/Exif.php index 1bbb8a5..f32afd0 100644 --- a/kirby/src/Image/Exif.php +++ b/kirby/src/Image/Exif.php @@ -15,61 +15,30 @@ use Kirby\Toolkit\V; */ class Exif { - /** - * The parent image object - */ - protected Image $image; - /** * The raw exif array */ protected array $data = []; - /** - * The camera object with model and make - */ protected Camera|null $camera = null; - - /** - * The location object - */ protected Location|null $location = null; - - /** - * The timestamp - */ protected string|null $timestamp = null; - - /** - * The exposure value - */ protected string|null $exposure = null; - - /** - * The aperture value - */ protected string|null $aperture = null; - - /** - * ISO value - */ protected string|null $iso = null; - - /** - * Focal length - */ protected string|null $focalLength = null; - - /** - * Color or black/white - */ protected bool|null $isColor = null; - public function __construct(Image $image) - { - $this->image = $image; - $this->data = $this->read(); - $this->parse(); + public function __construct( + protected Image $image + ) { + $this->data = $this->read(); + $this->timestamp = $this->parseTimestamp(); + $this->exposure = $this->data['ExposureTime'] ?? null; + $this->iso = $this->data['ISOSpeedRatings'] ?? null; + $this->focalLength = $this->parseFocalLength(); + $this->aperture = $this->computed()['ApertureFNumber'] ?? null; + $this->isColor = V::accepted($this->computed()['IsColor'] ?? null); } /** @@ -85,11 +54,7 @@ class Exif */ public function camera(): Camera { - if ($this->camera !== null) { - return $this->camera; - } - - return $this->camera = new Camera($this->data); + return $this->camera ??= new Camera($this->data); } /** @@ -97,11 +62,7 @@ class Exif */ public function location(): Location { - if ($this->location !== null) { - return $this->location; - } - - return $this->location = new Location($this->data); + return $this->location ??= new Location($this->data); } /** @@ -183,19 +144,6 @@ class Exif return $this->data['COMPUTED'] ?? []; } - /** - * Parses and stores all relevant exif data - */ - protected function parse(): void - { - $this->timestamp = $this->parseTimestamp(); - $this->exposure = $this->data['ExposureTime'] ?? null; - $this->iso = $this->data['ISOSpeedRatings'] ?? null; - $this->focalLength = $this->parseFocalLength(); - $this->aperture = $this->computed()['ApertureFNumber'] ?? null; - $this->isColor = V::accepted($this->computed()['IsColor'] ?? null); - } - /** * Return the timestamp when the picture has been taken */ @@ -240,6 +188,7 @@ class Exif /** * Improved `var_dump` output + * @codeCoverageIgnore */ public function __debugInfo(): array { diff --git a/kirby/src/Image/Focus.php b/kirby/src/Image/Focus.php new file mode 100644 index 0000000..da1dc73 --- /dev/null +++ b/kirby/src/Image/Focus.php @@ -0,0 +1,110 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier + * @license https://opensource.org/licenses/MIT + */ +class Focus +{ + /** + * Generates crop coordinates based on focal point + */ + public static function coords( + string $crop, + int $sourceWidth, + int $sourceHeight, + int $width, + int $height + ): array|null { + [$x, $y] = static::parse($crop); + + // determine aspect ratios + $ratioSource = static::ratio($sourceWidth, $sourceHeight); + $ratioThumb = static::ratio($width, $height); + + // no cropping necessary + if ($ratioSource == $ratioThumb) { + return null; + } + + // defaults + $width = $sourceWidth; + $height = $sourceHeight; + + if ($ratioThumb > $ratioSource) { + $height = $sourceWidth / $ratioThumb; + } else { + $width = $sourceHeight * $ratioThumb; + } + + // calculate focus for original image + $x = $sourceWidth * $x; + $y = $sourceHeight * $y; + + $x1 = max(0, $x - $width / 2); + $y1 = max(0, $y - $height / 2); + + // off canvas? + if ($x1 + $width > $sourceWidth) { + $x1 = $sourceWidth - $width; + } + + if ($y1 + $height > $sourceHeight) { + $y1 = $sourceHeight - $height; + } + + return [ + 'x1' => (int)floor($x1), + 'y1' => (int)floor($y1), + 'x2' => (int)floor($x1 + $width), + 'y2' => (int)floor($y1 + $height), + 'width' => (int)floor($width), + 'height' => (int)floor($height), + ]; + } + + public static function isFocalPoint(string $value): bool + { + return Str::contains($value, '%') === true; + } + + /** + * Transforms the focal point's string value (from content field) + * to a [x, y] array (values 0.0-1.0) + */ + public static function parse(string $value): array + { + // support for former Focus plugin + if (Str::startsWith($value, '{') === true) { + $focus = json_decode($value); + return [$focus->x, $focus->y]; + } + + preg_match_all("/(\d{1,3}\.?\d*)[%|,|\s]*/", $value, $points); + + return A::map( + $points[1], + function ($point) { + $point = (float)$point; + $point = $point > 1 ? $point / 100 : $point; + return round($point, 3); + } + ); + } + + /** + * Calculates the image ratio + */ + public static function ratio(int $width, int $height): float + { + return $height !== 0 ? $width / $height : 0; + } +} diff --git a/kirby/src/Image/Image.php b/kirby/src/Image/Image.php index 83c0a7b..5bc1b7f 100644 --- a/kirby/src/Image/Image.php +++ b/kirby/src/Image/Image.php @@ -2,7 +2,7 @@ namespace Kirby\Image; -use Kirby\Cms\Content; +use Kirby\Content\Content; use Kirby\Exception\LogicException; use Kirby\Filesystem\File; use Kirby\Toolkit\Html; diff --git a/kirby/src/Image/Location.php b/kirby/src/Image/Location.php index 4b60c6a..0eddb68 100644 --- a/kirby/src/Image/Location.php +++ b/kirby/src/Image/Location.php @@ -24,13 +24,20 @@ class Location */ public function __construct(array $exif) { - if (isset($exif['GPSLatitude']) === true && + if ( + isset($exif['GPSLatitude']) === true && isset($exif['GPSLatitudeRef']) === true && isset($exif['GPSLongitude']) === true && isset($exif['GPSLongitudeRef']) === true ) { - $this->lat = $this->gps($exif['GPSLatitude'], $exif['GPSLatitudeRef']); - $this->lng = $this->gps($exif['GPSLongitude'], $exif['GPSLongitudeRef']); + $this->lat = $this->gps( + $exif['GPSLatitude'], + $exif['GPSLatitudeRef'] + ); + $this->lng = $this->gps( + $exif['GPSLongitude'], + $exif['GPSLongitudeRef'] + ); } } @@ -95,11 +102,12 @@ class Location */ public function __toString(): string { - return trim(trim($this->lat() . ', ' . $this->lng(), ',')); + return trim($this->lat() . ', ' . $this->lng(), ','); } /** * Improved `var_dump` output + * @codeCoverageIgnore */ public function __debugInfo(): array { diff --git a/kirby/src/Image/QrCode.php b/kirby/src/Image/QrCode.php new file mode 100644 index 0000000..b6599fb --- /dev/null +++ b/kirby/src/Image/QrCode.php @@ -0,0 +1,1603 @@ +, + * Lukas Bestle + * @link https://getkirby.com + * @copyright Bastian Allgeier + * @license https://opensource.org/licenses/MIT + * + * QR Code® is a registered trademark of DENSO WAVE INCORPORATED. + * + * The code of this class is based on: + * https://github.com/psyon/php-qrcode + * + * qrcode.php - Generate QR Codes. MIT license. + * + * Copyright for portions of this project are held by Kreative Software, 2016-2018. + * All other copyright for the project are held by Donald Becker, 2019 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +class QrCode +{ + public function __construct(public string $data) + { + } + + /** + * Returns the QR code as a PNG data URI + * + * @param int|null $size Image width/height in pixels, defaults to a size per module of 4x4 + * @param string $color Foreground color in hex format + * @param string $back Background color in hex format + */ + public function toDataUri( + int|null $size = null, + string $color = '#000000', + string $back = '#ffffff' + ): string { + $image = $this->toImage($size, $color, $back); + + ob_start(); + imagepng($image); + imagedestroy($image); + $data = ob_get_contents(); + ob_end_clean(); + + return 'data:image/png;base64,' . base64_encode($data); + } + + /** + * Returns the QR code as a GdImage object + * + * @param int|null $size Image width/height in pixels, defaults to a size per module of 4x4 + * @param string $color Foreground color in hex format + * @param string $back Background color in hex format + */ + public function toImage( + int|null $size = null, + string $color = '#000000', + string $back = '#ffffff' + ): GdImage { + // get code and size measurements + $code = $this->encode(); + [$width, $height] = $this->measure($code); + $size ??= ceil($width * 4); + $ws = $size / $width; + $hs = $size / $height; + + // create image baseplate + $image = imagecreatetruecolor($size, $size); + + $allocateColor = function (string $hex) use ($image) { + $hex = preg_replace('/[^0-9A-Fa-f]/', '', $hex); + $r = hexdec(substr($hex, 0, 2)); + $g = hexdec(substr($hex, 2, 2)); + $b = hexdec(substr($hex, 4, 2)); + return imagecolorallocate($image, $r, $g, $b); + }; + + $back = $allocateColor($back); + $color = $allocateColor($color); + imagefill($image, 0, 0, $back); + + // paint square for each module + $this->eachModuleGroup( + $code, + fn ($x, $y, $width, $height) => imagefilledrectangle( + $image, + floor($x * $ws), + floor($y * $hs), + floor($x * $ws + $ws * $width) - 1, + floor($y * $hs + $hs * $height) - 1, + $color + ) + ); + + return $image; + } + + /** + * Returns the QR code as `` element + * + * @param int|string|null $size Optional CSS width of the `` element + * @param string $color Foreground color in hex format + * @param string $back Background color in hex format + */ + public function toSvg( + int|string|null $size = null, + string $color = '#000000', + string $back = '#ffffff' + ): string { + $code = $this->encode(); + [$vbw, $vbh] = $this->measure($code); + + $modules = $this->eachModuleGroup( + $code, + fn ($x, $y, $width, $height) => 'M' . $x . ',' . $y . 'h' . $width . 'v' . $height . 'h-' . $width . 'z' + ); + + $size = $size ? ' style="width: ' . $size . '"' : ''; + + return '' . + '' . + '' . + ''; + } + + public function __toString(): string + { + return $this->toSvg(); + } + + /** + * Saves the QR code to a file. + * Supported formats: gif, jpg, jpeg, png, svg, webp + * + * @param string $file Path to the output file with one of the supported file extensions + * @param int|string|null $size Optional image width/height in pixels (defaults to a size per module of 4x4) or CSS width of the `` element + * @param string $color Foreground color in hex format + * @param string $back Background color in hex format + */ + public function write( + string $file, + int|string|null $size = null, + string $color = '#000000', + string $back = '#ffffff' + ): void { + $format = F::extension($file); + $args = [$size, $color, $back]; + + match ($format) { + 'gif' => imagegif($this->toImage(...$args), $file), + 'jpg', + 'jpeg' => imagejpeg($this->toImage(...$args), $file), + 'png' => imagepng($this->toImage(...$args), $file), + 'svg' => F::write($file, $this->toSvg(...$args)), + 'webp' => imagewebp($this->toImage(...$args), $file), + default => throw new InvalidArgumentException('Cannot write QR code as ' . $format) + }; + } + + protected function applyMask(array $matrix, int $size, int $mask): array + { + for ($i = 0; $i < $size; $i++) { + for ($j = 0; $j < $size; $j++) { + if ($matrix[$i][$j] >= 4 && $this->mask($mask, $i, $j)) { + $matrix[$i][$j] ^= 1; + } + } + } + + return $matrix; + } + + protected function applyBestMask(array $matrix, int $size): array + { + $mask = 0; + $mmatrix = $this->applyMask($matrix, $size, $mask); + $penalty = $this->penalty($mmatrix, $size); + + for ($tmask = 1; $tmask < 8; $tmask++) { + $tmatrix = $this->applyMask($matrix, $size, $tmask); + $tpenalty = $this->penalty($tmatrix, $size); + + if ($tpenalty < $penalty) { + $mask = $tmask; + $mmatrix = $tmatrix; + $penalty = $tpenalty; + } + } + + return [$mask, $mmatrix]; + } + + protected function createMatrix(int $version, array $data): array + { + $size = $version * 4 + 17; + $matrix = []; + $row = array_fill(0, $size, 0); + + for ($i = 0; $i < $size; $i++) { + $matrix[] = $row; + } + + // finder patterns + for ($i = 0; $i < 8; $i++) { + for ($j = 0; $j < 8; $j++) { + $m = (($i == 7 || $j == 7) ? 2 : + (($i == 0 || $j == 0 || $i == 6 || $j == 6) ? 3 : + (($i == 1 || $j == 1 || $i == 5 || $j == 5) ? 2 : 3))); + $matrix[$i][$j] = $m; + $matrix[$size - $i - 1][$j] = $m; + $matrix[$i][$size - $j - 1] = $m; + } + } + + // alignment patterns + if ($version >= 2) { + $alignment = static::ALIGNMENT_PATTERNS[$version - 2]; + + foreach ($alignment as $i) { + foreach ($alignment as $j) { + if (!$matrix[$i][$j]) { + for ($ii = -2; $ii <= 2; $ii++) { + for ($jj = -2; $jj <= 2; $jj++) { + $m = (max(abs($ii), abs($jj)) & 1) ^ 3; + $matrix[$i + $ii][$j + $jj] = $m; + } + } + } + } + } + } + + // timing patterns + for ($i = $size - 9; $i >= 8; $i--) { + $matrix[$i][6] = ($i & 1) ^ 3; + $matrix[6][$i] = ($i & 1) ^ 3; + } + + // dark module – such an ominous name for such an innocuous thing + $matrix[$size - 8][8] = 3; + + // format information area + for ($i = 0; $i <= 8; $i++) { + if (!$matrix[$i][8]) { + $matrix[$i][8] = 1; + } + if (!$matrix[8][$i]) { + $matrix[8][$i] = 1; + } + if ($i && !$matrix[$size - $i][8]) { + $matrix[$size - $i][8] = 1; + } + if ($i && !$matrix[8][$size - $i]) { + $matrix[8][$size - $i] = 1; + } + } + + // version information area + if ($version >= 7) { + for ($i = 9; $i < 12; $i++) { + for ($j = 0; $j < 6; $j++) { + $matrix[$size - $i][$j] = 1; + $matrix[$j][$size - $i] = 1; + } + } + } + + // data + $col = $size - 1; + $row = $size - 1; + $dir = -1; + $offset = 0; + $length = count($data); + + while ($col > 0 && $offset < $length) { + if (!$matrix[$row][$col]) { + $matrix[$row][$col] = $data[$offset] ? 5 : 4; + $offset++; + } + if (!$matrix[$row][$col - 1]) { + $matrix[$row][$col - 1] = $data[$offset] ? 5 : 4; + $offset++; + } + $row += $dir; + if ($row < 0 || $row >= $size) { + $dir = -$dir; + $row += $dir; + $col -= 2; + + if ($col == 6) { + $col--; + } + } + } + + return [$size, $matrix]; + } + + /** + * Loops over every row and column, finds all modules that can + * be grouped as rectangle (starting at the top left corner) + * and applies the given action to each active module group + */ + protected function eachModuleGroup(array $code, Closure $action): array + { + $result = []; + $xStart = $code['q'][3]; + $yStart = $code['q'][0]; + + // generate empty matrix to track what modules have been covered + $covered = array_fill(0, count($code['bits']), array_fill(0, count($code['bits'][0]), 0)); + + foreach ($code['bits'] as $by => $row) { + foreach ($row as $bx => $module) { + // skip if module is inactive or already covered + if ($module === 0 || $covered[$by][$bx] === 1) { + continue; + } + + $width = 0; + $height = 0; + + $rowLength = count($row); + $colLength = count($code['bits']); + + // extend to the right as long as the modules are active + // and use this to determine the width of the group + for ($x = $bx; $x < $rowLength; $x++) { + if ($row[$x] === 0) { + break; + } + $width++; + $covered[$by][$x] = 1; + } + + // extend downwards as long as all the modules + // at the same width range are active; + // use this to determine the height of the group + for ($y = $by; $y < $colLength; $y++) { + $below = array_slice($code['bits'][$y], $bx, $width); + + // if the sum is less than the width, + // there is at least one inactive module + if (array_sum($below) < $width) { + break; + } + + $height++; + + for ($x = $bx; $x < $bx + $width; $x++) { + $covered[$y][$x] = 1; + } + } + + $result[] = $action( + $xStart + $bx, + $yStart + $by, + $width, + $height + ); + } + } + + return $result; + } + + protected function encode(): array + { + [$data, $version, $ecl, $ec] = $this->encodeData(); + $data = $this->encodeErrorCorrection($data, $ec, $version); + [$size, $mtx] = $this->createMatrix($version, $data); + [$mask, $mtx] = $this->applyBestMask($mtx, $size); + $mtx = $this->finalizeMatrix($mtx, $size, $ecl, $mask, $version); + + return [ + 'q' => [4, 4, 4, 4], + 'size' => [$size, $size], + 'bits' => $mtx + ]; + } + + protected function encodeData(): array + { + $mode = $this->mode(); + [$version, $ecl] = $this->version($mode); + + $group = match (true) { + $version >= 27 => 2, + $version >= 10 => 1, + default => 0 + }; + + $ec = static::EC_PARAMS[($version - 1) * 4 + $ecl]; + + // don't cut off mid-character if exceeding capacity + $max_chars = static::CAPACITY[$version - 1][$ecl][$mode]; + + if ($mode == 3) { + $max_chars <<= 1; + } + + $data = substr($this->data, 0, $max_chars); + + // convert from character level to bit level + $code = match ($mode) { + 0 => $this->encodeNumeric($data, $group), + 1 => $this->encodeAlphanum($data, $group), + 2 => $this->encodeBinary($data, $group), + default => throw new LogicException('Invalid QR mode') // @codeCoverageIgnore + }; + + $code = array_merge($code, array_fill(0, 4, 0)); + + if ($remainder = count($code) % 8) { + $code = array_merge($code, array_fill(0, 8 - $remainder, 0)); + } + + // convert from bit level to byte level + $data = []; + + for ($i = 0, $n = count($code); $i < $n; $i += 8) { + $byte = 0; + + if ($code[$i + 0]) { + $byte |= 0x80; + } + if ($code[$i + 1]) { + $byte |= 0x40; + } + if ($code[$i + 2]) { + $byte |= 0x20; + } + if ($code[$i + 3]) { + $byte |= 0x10; + } + if ($code[$i + 4]) { + $byte |= 0x08; + } + if ($code[$i + 5]) { + $byte |= 0x04; + } + if ($code[$i + 6]) { + $byte |= 0x02; + } + if ($code[$i + 7]) { + $byte |= 0x01; + } + + $data[] = $byte; + } + + for ( + $i = count($data), + $a = 1, + $n = $ec[0]; + $i < $n; + $i++, + $a ^= 1 + ) { + $data[] = $a ? 236 : 17; + } + + return [ + $data, + $version, + $ecl, + $ec + ]; + } + + protected function encodeNumeric($data, $version_group): array + { + $code = [0, 0, 0, 1]; + $length = strlen($data); + + switch ($version_group) { + case 2: // 27 - 40 + $code[] = $length & 0x2000; + $code[] = $length & 0x1000; + // no break + case 1: // 10 - 26 + $code[] = $length & 0x0800; + $code[] = $length & 0x0400; + // no break + case 0: // 1 - 9 + $code[] = $length & 0x0200; + $code[] = $length & 0x0100; + $code[] = $length & 0x0080; + $code[] = $length & 0x0040; + $code[] = $length & 0x0020; + $code[] = $length & 0x0010; + $code[] = $length & 0x0008; + $code[] = $length & 0x0004; + $code[] = $length & 0x0002; + $code[] = $length & 0x0001; + } + for ($i = 0; $i < $length; $i += 3) { + $group = substr($data, $i, 3); + switch (strlen($group)) { + case 3: + $code[] = $group & 0x200; + $code[] = $group & 0x100; + $code[] = $group & 0x080; + // no break + case 2: + $code[] = $group & 0x040; + $code[] = $group & 0x020; + $code[] = $group & 0x010; + // no break + case 1: + $code[] = $group & 0x008; + $code[] = $group & 0x004; + $code[] = $group & 0x002; + $code[] = $group & 0x001; + } + } + return $code; + } + + protected function encodeAlphanum($data, $version_group): array + { + $alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:'; + $code = [0, 0, 1, 0]; + $length = strlen($data); + switch ($version_group) { + case 2: // 27 - 40 + $code[] = $length & 0x1000; + $code[] = $length & 0x0800; + // no break + case 1: // 10 - 26 + $code[] = $length & 0x0400; + $code[] = $length & 0x0200; + // no break + case 0: // 1 - 9 + $code[] = $length & 0x0100; + $code[] = $length & 0x0080; + $code[] = $length & 0x0040; + $code[] = $length & 0x0020; + $code[] = $length & 0x0010; + $code[] = $length & 0x0008; + $code[] = $length & 0x0004; + $code[] = $length & 0x0002; + $code[] = $length & 0x0001; + } + for ($i = 0; $i < $length; $i += 2) { + $group = substr($data, $i, 2); + if (strlen($group) > 1) { + $c1 = strpos($alphabet, substr($group, 0, 1)); + $c2 = strpos($alphabet, substr($group, 1, 1)); + $ch = $c1 * 45 + $c2; + $code[] = $ch & 0x400; + $code[] = $ch & 0x200; + $code[] = $ch & 0x100; + $code[] = $ch & 0x080; + $code[] = $ch & 0x040; + $code[] = $ch & 0x020; + $code[] = $ch & 0x010; + $code[] = $ch & 0x008; + $code[] = $ch & 0x004; + $code[] = $ch & 0x002; + $code[] = $ch & 0x001; + } else { + $ch = strpos($alphabet, $group); + $code[] = $ch & 0x020; + $code[] = $ch & 0x010; + $code[] = $ch & 0x008; + $code[] = $ch & 0x004; + $code[] = $ch & 0x002; + $code[] = $ch & 0x001; + } + } + return $code; + } + + protected function encodeBinary(string $data, int $version_group): array + { + $code = [0, 1, 0, 0]; + $length = strlen($data); + + switch ($version_group) { + case 2: // 27 - 40 + case 1: // 10 - 26 + $code[] = $length & 0x8000; + $code[] = $length & 0x4000; + $code[] = $length & 0x2000; + $code[] = $length & 0x1000; + $code[] = $length & 0x0800; + $code[] = $length & 0x0400; + $code[] = $length & 0x0200; + $code[] = $length & 0x0100; + // no break + case 0: // 1 - 9 + $code[] = $length & 0x0080; + $code[] = $length & 0x0040; + $code[] = $length & 0x0020; + $code[] = $length & 0x0010; + $code[] = $length & 0x0008; + $code[] = $length & 0x0004; + $code[] = $length & 0x0002; + $code[] = $length & 0x0001; + } + + for ($i = 0; $i < $length; $i++) { + $ch = ord(substr($data, $i, 1)); + $code[] = $ch & 0x80; + $code[] = $ch & 0x40; + $code[] = $ch & 0x20; + $code[] = $ch & 0x10; + $code[] = $ch & 0x08; + $code[] = $ch & 0x04; + $code[] = $ch & 0x02; + $code[] = $ch & 0x01; + } + + return $code; + } + + protected function encodeErrorCorrection( + array $data, + array $ec_params, + int $version + ): array { + $blocks = $this->errorCorrectionSplit($data, $ec_params); + $ec_blocks = []; + + for ($i = 0, $n = count($blocks); $i < $n; $i++) { + $ec_blocks[] = $this->errorCorrectionDivide($blocks[$i], $ec_params); + } + + $data = $this->errorCorrectionInterleave($blocks); + $ec_data = $this->errorCorrectionInterleave($ec_blocks); + $code = []; + + foreach ($data as $ch) { + $code[] = $ch & 0x80; + $code[] = $ch & 0x40; + $code[] = $ch & 0x20; + $code[] = $ch & 0x10; + $code[] = $ch & 0x08; + $code[] = $ch & 0x04; + $code[] = $ch & 0x02; + $code[] = $ch & 0x01; + } + foreach ($ec_data as $ch) { + $code[] = $ch & 0x80; + $code[] = $ch & 0x40; + $code[] = $ch & 0x20; + $code[] = $ch & 0x10; + $code[] = $ch & 0x08; + $code[] = $ch & 0x04; + $code[] = $ch & 0x02; + $code[] = $ch & 0x01; + } + for ($n = static::REMAINER_BITS[$version - 1]; $n > 0; $n--) { + $code[] = 0; + } + + return $code; + } + + protected function errorCorrectionSplit(array $data, array $ec): array + { + $blocks = []; + $offset = 0; + + for ($i = $ec[2], $length = $ec[3]; $i > 0; $i--) { + $blocks[] = array_slice($data, $offset, $length); + $offset += $length; + } + for ($i = $ec[4], $length = $ec[5]; $i > 0; $i--) { + $blocks[] = array_slice($data, $offset, $length); + $offset += $length; + } + + return $blocks; + } + + protected function errorCorrectionDivide(array $data, array $ec): array + { + $num_data = count($data); + $num_error = $ec[1]; + $generator = static::EC_POLYNOMIALS[$num_error]; + $message = $data; + + for ($i = 0; $i < $num_error; $i++) { + $message[] = 0; + } + + for ($i = 0; $i < $num_data; $i++) { + if ($message[$i]) { + $leadterm = static::LOG[$message[$i]]; + + for ($j = 0; $j <= $num_error; $j++) { + $term = ($generator[$j] + $leadterm) % 255; + $message[$i + $j] ^= static::EXP[$term]; + } + } + } + + return array_slice($message, $num_data, $num_error); + } + + protected function errorCorrectionInterleave(array $blocks): array + { + $data = []; + $num_blocks = count($blocks); + + for ($offset = 0; true; $offset++) { + $break = true; + + for ($i = 0; $i < $num_blocks; $i++) { + if (isset($blocks[$i][$offset]) === true) { + $data[] = $blocks[$i][$offset]; + $break = false; + } + } + + if ($break) { + break; + } + } + + return $data; + } + + protected function finalizeMatrix( + array $matrix, + int $size, + int $ecl, + int $mask, + int $version + ): array { + // Format info + $format = static::FORMAT_INFO[$ecl * 8 + $mask]; + $matrix[8][0] = $format[0]; + $matrix[8][1] = $format[1]; + $matrix[8][2] = $format[2]; + $matrix[8][3] = $format[3]; + $matrix[8][4] = $format[4]; + $matrix[8][5] = $format[5]; + $matrix[8][7] = $format[6]; + $matrix[8][8] = $format[7]; + $matrix[7][8] = $format[8]; + $matrix[5][8] = $format[9]; + $matrix[4][8] = $format[10]; + $matrix[3][8] = $format[11]; + $matrix[2][8] = $format[12]; + $matrix[1][8] = $format[13]; + $matrix[0][8] = $format[14]; + $matrix[$size - 1][8] = $format[0]; + $matrix[$size - 2][8] = $format[1]; + $matrix[$size - 3][8] = $format[2]; + $matrix[$size - 4][8] = $format[3]; + $matrix[$size - 5][8] = $format[4]; + $matrix[$size - 6][8] = $format[5]; + $matrix[$size - 7][8] = $format[6]; + $matrix[8][$size - 8] = $format[7]; + $matrix[8][$size - 7] = $format[8]; + $matrix[8][$size - 6] = $format[9]; + $matrix[8][$size - 5] = $format[10]; + $matrix[8][$size - 4] = $format[11]; + $matrix[8][$size - 3] = $format[12]; + $matrix[8][$size - 2] = $format[13]; + $matrix[8][$size - 1] = $format[14]; + + // version info + if ($version >= 7) { + $version = static::VERSION_INFO[$version - 7]; + + for ($i = 0; $i < 18; $i++) { + $r = $size - 9 - ($i % 3); + $c = 5 - floor($i / 3); + $matrix[$r][$c] = $version[$i]; + $matrix[$c][$r] = $version[$i]; + } + } + + // patterns and data + for ($i = 0; $i < $size; $i++) { + for ($j = 0; $j < $size; $j++) { + $matrix[$i][$j] &= 1; + } + } + + return $matrix; + } + + protected function mask(int $mask, int $row, int $column): int + { + return match ($mask) { + 0 => !(($row + $column) % 2), + 1 => !($row % 2), + 2 => !($column % 3), + 3 => !(($row + $column) % 3), + 4 => !((floor($row / 2) + floor($column / 3)) % 2), + 5 => !(((($row * $column) % 2) + (($row * $column) % 3))), + 6 => !(((($row * $column) % 2) + (($row * $column) % 3)) % 2), + 7 => !(((($row + $column) % 2) + (($row * $column) % 3)) % 2), + default => throw new LogicException('Invalid QR mask') // @codeCoverageIgnore + }; + } + + /** + * Returns width and height based on the + * generated modules and quiet zone + */ + protected function measure($code): array + { + return [ + $code['q'][3] + $code['size'][0] + $code['q'][1], + $code['q'][0] + $code['size'][1] + $code['q'][2] + ]; + } + + /** + * Detect what encoding mode (numeric, alphanumeric, binary) + * can be used + */ + protected function mode(): int + { + // numeric + if (preg_match('/^[0-9]*$/', $this->data)) { + return 0; + } + + // alphanumeric + if (preg_match('/^[0-9A-Z .\/:$%*+-]*$/', $this->data)) { + return 1; + } + + return 2; + } + + protected function penalty(array &$matrix, int $size): int + { + $score = $this->penalty1($matrix, $size); + $score += $this->penalty2($matrix, $size); + $score += $this->penalty3($matrix, $size); + $score += $this->penalty4($matrix, $size); + return $score; + } + + protected function penalty1(array &$matrix, int $size): int + { + $score = 0; + + for ($i = 0; $i < $size; $i++) { + $rowvalue = 0; + $rowcount = 0; + $colvalue = 0; + $colcount = 0; + + for ($j = 0; $j < $size; $j++) { + $rv = ($matrix[$i][$j] == 5 || $matrix[$i][$j] == 3) ? 1 : 0; + $cv = ($matrix[$j][$i] == 5 || $matrix[$j][$i] == 3) ? 1 : 0; + + if ($rv == $rowvalue) { + $rowcount++; + } else { + if ($rowcount >= 5) { + $score += $rowcount - 2; + } + $rowvalue = $rv; + $rowcount = 1; + } + + if ($cv == $colvalue) { + $colcount++; + } else { + if ($colcount >= 5) { + $score += $colcount - 2; + } + $colvalue = $cv; + $colcount = 1; + } + } + + if ($rowcount >= 5) { + $score += $rowcount - 2; + } + if ($colcount >= 5) { + $score += $colcount - 2; + } + } + + return $score; + } + + protected function penalty2(array &$matrix, int $size): int + { + $score = 0; + + for ($i = 1; $i < $size; $i++) { + for ($j = 1; $j < $size; $j++) { + $v1 = $matrix[$i - 1][$j - 1]; + $v2 = $matrix[$i - 1][$j ]; + $v3 = $matrix[$i ][$j - 1]; + $v4 = $matrix[$i ][$j ]; + $v1 = ($v1 == 5 || $v1 == 3) ? 1 : 0; + $v2 = ($v2 == 5 || $v2 == 3) ? 1 : 0; + $v3 = ($v3 == 5 || $v3 == 3) ? 1 : 0; + $v4 = ($v4 == 5 || $v4 == 3) ? 1 : 0; + + if ($v1 == $v2 && $v2 == $v3 && $v3 == $v4) { + $score += 3; + } + } + } + + return $score; + } + + protected function penalty3(array &$matrix, int $size): int + { + $score = 0; + + for ($i = 0; $i < $size; $i++) { + $rowvalue = 0; + $colvalue = 0; + + for ($j = 0; $j < 11; $j++) { + $rv = ($matrix[$i][$j] == 5 || $matrix[$i][$j] == 3) ? 1 : 0; + $cv = ($matrix[$j][$i] == 5 || $matrix[$j][$i] == 3) ? 1 : 0; + $rowvalue = (($rowvalue << 1) & 0x7FF) | $rv; + $colvalue = (($colvalue << 1) & 0x7FF) | $cv; + } + + if ($rowvalue == 0x5D0 || $rowvalue == 0x5D) { + $score += 40; + } + if ($colvalue == 0x5D0 || $colvalue == 0x5D) { + $score += 40; + } + + for ($j = 11; $j < $size; $j++) { + $rv = ($matrix[$i][$j] == 5 || $matrix[$i][$j] == 3) ? 1 : 0; + $cv = ($matrix[$j][$i] == 5 || $matrix[$j][$i] == 3) ? 1 : 0; + $rowvalue = (($rowvalue << 1) & 0x7FF) | $rv; + $colvalue = (($colvalue << 1) & 0x7FF) | $cv; + + if ($rowvalue == 0x5D0 || $rowvalue == 0x5D) { + $score += 40; + } + + if ($colvalue == 0x5D0 || $colvalue == 0x5D) { + $score += 40; + } + } + } + + return $score; + } + + protected function penalty4(array &$matrix, int $size): int + { + $dark = 0; + + for ($i = 0; $i < $size; $i++) { + for ($j = 0; $j < $size; $j++) { + if ($matrix[$i][$j] == 5 || $matrix[$i][$j] == 3) { + $dark++; + } + } + } + + $dark *= 20; + $dark /= $size * $size; + $a = abs(floor($dark) - 10); + $b = abs(ceil($dark) - 10); + return min($a, $b) * 10; + } + + /** + * Detect what version needs to be used by + * trying to maximize the error correction level + */ + protected function version(int $mode): array + { + $length = strlen($this->data); + + if ($mode == 3) { + $length >>= 1; + } + + $ecl = 0; + + // first try to find the minimum version + // that can contain the data + for ($version = 1; $version <= 40; $version++) { + if ($length <= static::CAPACITY[$version - 1][$ecl][$mode]) { + break; + } + } + + // with the version in place, try to raise + // the error correction level as long as + // the data still fits + for ($newEcl = 1; $newEcl <= 3; $newEcl++) { + if ($length <= static::CAPACITY[$version - 1][$newEcl][$mode]) { + $ecl = $newEcl; + } + } + + return [$version, $ecl]; + } + + /** + * maximum encodable characters = $qr_capacity [ (version - 1) ] + * [ (0 for L, 1 for M, 2 for Q, 3 for H) ] + * [ (0 for numeric, 1 for alpha, 2 for binary) ] + */ + protected const CAPACITY = [ + [ + [ 41, 25, 17], + [ 34, 20, 14], + [ 27, 16, 11], + [ 17, 10, 7] + ], + [ + [ 77, 47, 32], + [ 63, 38, 26], + [ 48, 29, 20], + [ 34, 20, 14] + ], + [ + [ 127, 77, 53], + [ 101, 61, 42], + [ 77, 47, 32], + [ 58, 35, 24] + ], + [ + [ 187, 114, 78], + [ 149, 90, 62], + [ 111, 67, 46], + [ 82, 50, 34] + ], + [ + [ 255, 154, 106], + [ 202, 122, 84], + [ 144, 87, 60], + [ 106, 64, 44] + ], + [ + [ 322, 195, 134], + [ 255, 154, 106], + [ 178, 108, 74], + [ 139, 84, 58] + ], + [ + [ 370, 224, 154], + [ 293, 178, 122], + [ 207, 125, 86], + [ 154, 93, 64] + ], + [ + [ 461, 279, 192], + [ 365, 221, 152], + [ 259, 157, 108], + [ 202, 122, 84] + ], + [ + [ 552, 335, 230], + [ 432, 262, 180], + [ 312, 189, 130], + [ 235, 143, 98]], + [ + [ 652, 395, 271], + [ 513, 311, 213], + [ 364, 221, 151], + [ 288, 174, 119] + ], + [ + [ 772, 468, 321], + [ 604, 366, 251], + [ 427, 259, 177], + [ 331, 200, 137] + ], + [ + [ 883, 535, 367], + [ 691, 419, 287], + [ 489, 296, 203], + [ 374, 227, 155] + ], + [ + [1022, 619, 425], + [ 796, 483, 331], + [ 580, 352, 241], + [ 427, 259, 177] + ], + [ + [1101, 667, 458], + [ 871, 528, 362], + [ 621, 376, 258], + [ 468, 283, 194] + ], + [ + [1250, 758, 520], + [ 991, 600, 412], + [ 703, 426, 292], + [ 530, 321, 220] + ], + [ + [1408, 854, 586], + [1082, 656, 450], + [ 775, 470, 322], + [ 602, 365, 250] + ], + [ + [1548, 938, 644], + [1212, 734, 504], + [ 876, 531, 364], + [ 674, 408, 280] + ], + [ + [1725, 1046, 718], + [1346, 816, 560], + [ 948, 574, 394], + [ 746, 452, 310] + ], + [ + [1903, 1153, 792], + [1500, 909, 624], + [1063, 644, 442], + [ 813, 493, 338] + ], + [ + [2061, 1249, 858], + [1600, 970, 666], + [1159, 702, 482], + [ 919, 557, 382] + ], + [ + [2232, 1352, 929], + [1708, 1035, 711], + [1224, 742, 509], + [ 969, 587, 403] + ], + [ + [2409, 1460, 1003], + [1872, 1134, 779], + [1358, 823, 565], + [1056, 640, 439] + ], + [ + [2620, 1588, 1091], + [2059, 1248, 857], + [1468, 890, 611], + [1108, 672, 461] + ], + [ + [2812, 1704, 1171], + [2188, 1326, 911], + [1588, 963, 661], + [1228, 744, 511] + ], + [ + [3057, 1853, 1273], + [2395, 1451, 997], + [1718, 1041, 715], + [1286, 779, 535] + ], + [ + [3283, 1990, 1367], + [2544, 1542, 1059], + [1804, 1094, 751], + [1425, 864, 593] + ], + [ + [3517, 2132, 1465], + [2701, 1637, 1125], + [1933, 1172, 805], + [1501, 910, 625] + ], + [ + [3669, 2223, 1528], + [2857, 1732, 1190], + [2085, 1263, 868], + [1581, 958, 658] + ], + [ + [3909, 2369, 1628], + [3035, 1839, 1264], + [2181, 1322, 908], + [1677, 1016, 698] + ], + [ + [4158, 2520, 1732], + [3289, 1994, 1370], + [2358, 1429, 982], + [1782, 1080, 742] + ], + [ + [4417, 2677, 1840], + [3486, 2113, 1452], + [2473, 1499, 1030], + [1897, 1150, 790] + ], + [ + [4686, 2840, 1952], + [3693, 2238, 1538], + [2670, 1618, 1112], + [2022, 1226, 842] + ], + [ + [4965, 3009, 2068], + [3909, 2369, 1628], + [2805, 1700, 1168], + [2157, 1307, 898] + ], + [ + [5253, 3183, 2188], + [4134, 2506, 1722], + [2949, 1787, 1228], + [2301, 1394, 958] + ], + [ + [5529, 3351, 2303], + [4343, 2632, 1809], + [3081, 1867, 1283], + [2361, 1431, 983] + ], + [ + [5836, 3537, 2431], + [4588, 2780, 1911], + [3244, 1966, 1351], + [2524, 1530, 1051] + ], + [ + [6153, 3729, 2563], + [4775, 2894, 1989], + [3417, 2071, 1423], + [2625, 1591, 1093] + ], + [ + [6479, 3927, 2699], + [5039, 3054, 2099], + [3599, 2181, 1499], + [2735, 1658, 1139] + ], + [ + [6743, 4087, 2809], + [5313, 3220, 2213], + [3791, 2298, 1579], + [2927, 1774, 1219] + ], + [ + [7089, 4296, 2953], + [5596, 3391, 2331], + [3993, 2420, 1663], + [3057, 1852, 1273] + ], + ]; + + /** + * $qr_ec_params[ + * 4 * (version - 1) + (0 for L, 1 for M, 2 for Q, 3 for H) + * ] = [ + * total number of data codewords, + * number of error correction codewords per block, + * number of blocks in first group, + * number of data codewords per block in first group, + * number of blocks in second group, + * number of data codewords per block in second group + * ); + */ + protected const EC_PARAMS = [ + [ 19, 7, 1, 19, 0, 0], + [ 16, 10, 1, 16, 0, 0], + [ 13, 13, 1, 13, 0, 0], + [ 9, 17, 1, 9, 0, 0], + [ 34, 10, 1, 34, 0, 0], + [ 28, 16, 1, 28, 0, 0], + [ 22, 22, 1, 22, 0, 0], + [ 16, 28, 1, 16, 0, 0], + [ 55, 15, 1, 55, 0, 0], + [ 44, 26, 1, 44, 0, 0], + [ 34, 18, 2, 17, 0, 0], + [ 26, 22, 2, 13, 0, 0], + [ 80, 20, 1, 80, 0, 0], + [ 64, 18, 2, 32, 0, 0], + [ 48, 26, 2, 24, 0, 0], + [ 36, 16, 4, 9, 0, 0], + [ 108, 26, 1, 108, 0, 0], + [ 86, 24, 2, 43, 0, 0], + [ 62, 18, 2, 15, 2, 16], + [ 46, 22, 2, 11, 2, 12], + [ 136, 18, 2, 68, 0, 0], + [ 108, 16, 4, 27, 0, 0], + [ 76, 24, 4, 19, 0, 0], + [ 60, 28, 4, 15, 0, 0], + [ 156, 20, 2, 78, 0, 0], + [ 124, 18, 4, 31, 0, 0], + [ 88, 18, 2, 14, 4, 15], + [ 66, 26, 4, 13, 1, 14], + [ 194, 24, 2, 97, 0, 0], + [ 154, 22, 2, 38, 2, 39], + [ 110, 22, 4, 18, 2, 19], + [ 86, 26, 4, 14, 2, 15], + [ 232, 30, 2, 116, 0, 0], + [ 182, 22, 3, 36, 2, 37], + [ 132, 20, 4, 16, 4, 17], + [ 100, 24, 4, 12, 4, 13], + [ 274, 18, 2, 68, 2, 69], + [ 216, 26, 4, 43, 1, 44], + [ 154, 24, 6, 19, 2, 20], + [ 122, 28, 6, 15, 2, 16], + [ 324, 20, 4, 81, 0, 0], + [ 254, 30, 1, 50, 4, 51], + [ 180, 28, 4, 22, 4, 23], + [ 140, 24, 3, 12, 8, 13], + [ 370, 24, 2, 92, 2, 93], + [ 290, 22, 6, 36, 2, 37], + [ 206, 26, 4, 20, 6, 21], + [ 158, 28, 7, 14, 4, 15], + [ 428, 26, 4, 107, 0, 0], + [ 334, 22, 8, 37, 1, 38], + [ 244, 24, 8, 20, 4, 21], + [ 180, 22, 12, 11, 4, 12], + [ 461, 30, 3, 115, 1, 116], + [ 365, 24, 4, 40, 5, 41], + [ 261, 20, 11, 16, 5, 17], + [ 197, 24, 11, 12, 5, 13], + [ 523, 22, 5, 87, 1, 88], + [ 415, 24, 5, 41, 5, 42], + [ 295, 30, 5, 24, 7, 25], + [ 223, 24, 11, 12, 7, 13], + [ 589, 24, 5, 98, 1, 99], + [ 453, 28, 7, 45, 3, 46], + [ 325, 24, 15, 19, 2, 20], + [ 253, 30, 3, 15, 13, 16], + [ 647, 28, 1, 107, 5, 108], + [ 507, 28, 10, 46, 1, 47], + [ 367, 28, 1, 22, 15, 23], + [ 283, 28, 2, 14, 17, 15], + [ 721, 30, 5, 120, 1, 121], + [ 563, 26, 9, 43, 4, 44], + [ 397, 28, 17, 22, 1, 23], + [ 313, 28, 2, 14, 19, 15], + [ 795, 28, 3, 113, 4, 114], + [ 627, 26, 3, 44, 11, 45], + [ 445, 26, 17, 21, 4, 22], + [ 341, 26, 9, 13, 16, 14], + [ 861, 28, 3, 107, 5, 108], + [ 669, 26, 3, 41, 13, 42], + [ 485, 30, 15, 24, 5, 25], + [ 385, 28, 15, 15, 10, 16], + [ 932, 28, 4, 116, 4, 117], + [ 714, 26, 17, 42, 0, 0], + [ 512, 28, 17, 22, 6, 23], + [ 406, 30, 19, 16, 6, 17], + [1006, 28, 2, 111, 7, 112], + [ 782, 28, 17, 46, 0, 0], + [ 568, 30, 7, 24, 16, 25], + [ 442, 24, 34, 13, 0, 0], + [1094, 30, 4, 121, 5, 122], + [ 860, 28, 4, 47, 14, 48], + [ 614, 30, 11, 24, 14, 25], + [ 464, 30, 16, 15, 14, 16], + [1174, 30, 6, 117, 4, 118], + [ 914, 28, 6, 45, 14, 46], + [ 664, 30, 11, 24, 16, 25], + [ 514, 30, 30, 16, 2, 17], + [1276, 26, 8, 106, 4, 107], + [1000, 28, 8, 47, 13, 48], + [ 718, 30, 7, 24, 22, 25], + [ 538, 30, 22, 15, 13, 16], + [1370, 28, 10, 114, 2, 115], + [1062, 28, 19, 46, 4, 47], + [ 754, 28, 28, 22, 6, 23], + [ 596, 30, 33, 16, 4, 17], + [1468, 30, 8, 122, 4, 123], + [1128, 28, 22, 45, 3, 46], + [ 808, 30, 8, 23, 26, 24], + [ 628, 30, 12, 15, 28, 16], + [1531, 30, 3, 117, 10, 118], + [1193, 28, 3, 45, 23, 46], + [ 871, 30, 4, 24, 31, 25], + [ 661, 30, 11, 15, 31, 16], + [1631, 30, 7, 116, 7, 117], + [1267, 28, 21, 45, 7, 46], + [ 911, 30, 1, 23, 37, 24], + [ 701, 30, 19, 15, 26, 16], + [1735, 30, 5, 115, 10, 116], + [1373, 28, 19, 47, 10, 48], + [ 985, 30, 15, 24, 25, 25], + [ 745, 30, 23, 15, 25, 16], + [1843, 30, 13, 115, 3, 116], + [1455, 28, 2, 46, 29, 47], + [1033, 30, 42, 24, 1, 25], + [ 793, 30, 23, 15, 28, 16], + [1955, 30, 17, 115, 0, 0], + [1541, 28, 10, 46, 23, 47], + [1115, 30, 10, 24, 35, 25], + [ 845, 30, 19, 15, 35, 16], + [2071, 30, 17, 115, 1, 116], + [1631, 28, 14, 46, 21, 47], + [1171, 30, 29, 24, 19, 25], + [ 901, 30, 11, 15, 46, 16], + [2191, 30, 13, 115, 6, 116], + [1725, 28, 14, 46, 23, 47], + [1231, 30, 44, 24, 7, 25], + [ 961, 30, 59, 16, 1, 17], + [2306, 30, 12, 121, 7, 122], + [1812, 28, 12, 47, 26, 48], + [1286, 30, 39, 24, 14, 25], + [ 986, 30, 22, 15, 41, 16], + [2434, 30, 6, 121, 14, 122], + [1914, 28, 6, 47, 34, 48], + [1354, 30, 46, 24, 10, 25], + [1054, 30, 2, 15, 64, 16], + [2566, 30, 17, 122, 4, 123], + [1992, 28, 29, 46, 14, 47], + [1426, 30, 49, 24, 10, 25], + [1096, 30, 24, 15, 46, 16], + [2702, 30, 4, 122, 18, 123], + [2102, 28, 13, 46, 32, 47], + [1502, 30, 48, 24, 14, 25], + [1142, 30, 42, 15, 32, 16], + [2812, 30, 20, 117, 4, 118], + [2216, 28, 40, 47, 7, 48], + [1582, 30, 43, 24, 22, 25], + [1222, 30, 10, 15, 67, 16], + [2956, 30, 19, 118, 6, 119], + [2334, 28, 18, 47, 31, 48], + [1666, 30, 34, 24, 34, 25], + [1276, 30, 20, 15, 61, 16], + ]; + + protected const EC_POLYNOMIALS = [ + 7 => [0, 87, 229, 146, 149, 238, 102, 21], + 10 => [0, 251, 67, 46, 61, 118, 70, 64, 94, 32, 45], + 13 => [0, 74, 152, 176, 100, 86, 100, 106, 104, 130, 218, 206, 140, 78], + 15 => [0, 8, 183, 61, 91, 202, 37, 51, 58, 58, 237, 140, 124, 5, 99, 105], + 16 => [0, 120, 104, 107, 109, 102, 161, 76, 3, 91, 191, 147, 169, 182, 194, 225, 120], + 17 => [0, 43, 139, 206, 78, 43, 239, 123, 206, 214, 147, 24, 99, 150, 39, 243, 163, 136], + 18 => [0, 215, 234, 158, 94, 184, 97, 118, 170, 79, 187, 152, 148, 252, 179, 5, 98, 96, 153], + 20 => [0, 17, 60, 79, 50, 61, 163, 26, 187, 202, 180, 221, 225, 83, 239, 156, 164, 212, 212, 188, 190], + 22 => [0, 210, 171, 247, 242, 93, 230, 14, 109, 221, 53, 200, 74, 8, 172, 98, 80, 219, 134, 160, 105, 165, 231], + 24 => [0, 229, 121, 135, 48, 211, 117, 251, 126, 159, 180, 169, 152, 192, 226, 228, 218, 111, 0, 117, 232, 87, 96, 227, 21], + 26 => [0, 173, 125, 158, 2, 103, 182, 118, 17, 145, 201, 111, 28, 165, 53, 161, 21, 245, 142, 13, 102, 48, 227, 153, 145, 218, 70], + 28 => [0, 168, 223, 200, 104, 224, 234, 108, 180, 110, 190, 195, 147, 205, 27, 232, 201, 21, 43, 245, 87, 42, 195, 212, 119, 242, 37, 9, 123], + 30 => [0, 41, 173, 145, 152, 216, 31, 179, 182, 50, 48, 110, 86, 239, 96, 222, 125, 42, 173, 226, 193, 224, 130, 156, 37, 251, 216, 238, 40, 192, 180], + ]; + + protected const LOG = [0, 0, 1, 25, 2, 50, 26, 198, 3, 223, 51, 238, 27, 104, 199, 75, 4, 100, 224, 14, 52, 141, 239, 129, 28, 193, 105, 248, 200, 8, 76, 113, 5, 138, 101, 47, 225, 36, 15, 33, 53, 147, 142, 218, 240, 18, 130, 69, 29, 181, 194, 125, 106, 39, 249, 185, 201, 154, 9, 120, 77, 228, 114, 166, 6, 191, 139, 98, 102, 221, 48, 253, 226, 152, 37, 179, 16, 145, 34, 136, 54, 208, 148, 206, 143, 150, 219, 189, 241, 210, 19, 92, 131, 56, 70, 64, 30, 66, 182, 163, 195, 72, 126, 110, 107, 58, 40, 84, 250, 133, 186, 61, 202, 94, 155, 159, 10, 21, 121, 43, 78, 212, 229, 172, 115, 243, 167, 87, 7, 112, 192, 247, 140, 128, 99, 13, 103, 74, 222, 237, 49, 197, 254, 24, 227, 165, 153, 119, 38, 184, 180, 124, 17, 68, 146, 217, 35, 32, 137, 46, 55, 63, 209, 91, 149, 188, 207, 205, 144, 135, 151, 178, 220, 252, 190, 97, 242, 86, 211, 171, 20, 42, 93, 158, 132, 60, 57, 83, 71, 109, 65, 162, 31, 45, 67, 216, 183, 123, 164, 118, 196, 23, 73, 236, 127, 12, 111, 246, 108, 161, 59, 82, 41, 157, 85, 170, 251, 96, 134, 177, 187, 204, 62, 90, 203, 89, 95, 176, 156, 169, 160, 81, 11, 245, 22, 235, 122, 117, 44, 215, 79, 174, 213, 233, 230, 231, 173, 232, 116, 214, 244, 234, 168, 80, 88, 175]; + + protected const EXP = [1, 2, 4, 8, 16, 32, 64, 128, 29, 58, 116, 232, 205, 135, 19, 38, 76, 152, 45, 90, 180, 117, 234, 201, 143, 3, 6, 12, 24, 48, 96, 192, 157, 39, 78, 156, 37, 74, 148, 53, 106, 212, 181, 119, 238, 193, 159, 35, 70, 140, 5, 10, 20, 40, 80, 160, 93, 186, 105, 210, 185, 111, 222, 161, 95, 190, 97, 194, 153, 47, 94, 188, 101, 202, 137, 15, 30, 60, 120, 240, 253, 231, 211, 187, 107, 214, 177, 127, 254, 225, 223, 163, 91, 182, 113, 226, 217, 175, 67, 134, 17, 34, 68, 136, 13, 26, 52, 104, 208, 189, 103, 206, 129, 31, 62, 124, 248, 237, 199, 147, 59, 118, 236, 197, 151, 51, 102, 204, 133, 23, 46, 92, 184, 109, 218, 169, 79, 158, 33, 66, 132, 21, 42, 84, 168, 77, 154, 41, 82, 164, 85, 170, 73, 146, 57, 114, 228, 213, 183, 115, 230, 209, 191, 99, 198, 145, 63, 126, 252, 229, 215, 179, 123, 246, 241, 255, 227, 219, 171, 75, 150, 49, 98, 196, 149, 55, 110, 220, 165, 87, 174, 65, 130, 25, 50, 100, 200, 141, 7, 14, 28, 56, 112, 224, 221, 167, 83, 166, 81, 162, 89, 178, 121, 242, 249, 239, 195, 155, 43, 86, 172, 69, 138, 9, 18, 36, 72, 144, 61, 122, 244, 245, 247, 243, 251, 235, 203, 139, 11, 22, 44, 88, 176, 125, 250, 233, 207, 131, 27, 54, 108, 216, 173, 71, 142, 1]; + + protected const REMAINER_BITS = [0, 7, 7, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0]; + + protected const ALIGNMENT_PATTERNS = [ + [6, 18], + [6, 22], + [6, 26], + [6, 30], + [6, 34], + [6, 22, 38], + [6, 24, 42], + [6, 26, 46], + [6, 28, 50], + [6, 30, 54], + [6, 32, 58], + [6, 34, 62], + [6, 26, 46, 66], + [6, 26, 48, 70], + [6, 26, 50, 74], + [6, 30, 54, 78], + [6, 30, 56, 82], + [6, 30, 58, 86], + [6, 34, 62, 90], + [6, 28, 50, 72, 94], + [6, 26, 50, 74, 98], + [6, 30, 54, 78, 102], + [6, 28, 54, 80, 106], + [6, 32, 58, 84, 110], + [6, 30, 58, 86, 114], + [6, 34, 62, 90, 118], + [6, 26, 50, 74, 98, 122], + [6, 30, 54, 78, 102, 126], + [6, 26, 52, 78, 104, 130], + [6, 30, 56, 82, 108, 134], + [6, 34, 60, 86, 112, 138], + [6, 30, 58, 86, 114, 142], + [6, 34, 62, 90, 118, 146], + [6, 30, 54, 78, 102, 126, 150], + [6, 24, 50, 76, 102, 128, 154], + [6, 28, 54, 80, 106, 132, 158], + [6, 32, 58, 84, 110, 136, 162], + [6, 26, 54, 82, 110, 138, 166], + [6, 30, 58, 86, 114, 142, 170], + ]; + + /** + * format info string = $qr_format_info[ + * (0 for L, 8 for M, 16 for Q, 24 for H) + mask + *]; + */ + protected const FORMAT_INFO = [ + [1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0], + [1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0], + [1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1], + [1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1], + [1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0], + [1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1], + [1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0], + [1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0], + [1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1], + [1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0], + [1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1], + [1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1], + [1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0], + [1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1], + [1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0], + [0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1], + [0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0], + [0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1], + [0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0], + [0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0], + [0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1], + [0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0], + [0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1], + [0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1], + [0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0], + [0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1], + [0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0], + [0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1], + [0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0], + [0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1] + ]; + + /** + * version info string = $qr_version_info[ (version - 7) ] + */ + protected const VERSION_INFO = [ + [0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1], + [0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1], + [0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0], + [0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0], + [0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1], + [0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1], + [0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0], + [0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1], + [0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1], + [0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0], + [0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0], + [0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1], + [0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1], + [0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0], + [0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0], + [0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1], + [0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1], + [0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0], + [0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0], + [0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1], + [0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1], + [0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1], + [1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0], + [1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0], + [1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1], + [1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1], + [1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0], + [1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0], + [1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1], + [1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1] + ]; +} diff --git a/kirby/src/Option/OptionsApi.php b/kirby/src/Option/OptionsApi.php index 011d19e..a3b7ae8 100644 --- a/kirby/src/Option/OptionsApi.php +++ b/kirby/src/Option/OptionsApi.php @@ -2,9 +2,9 @@ namespace Kirby\Option; -use Kirby\Cms\Field; use Kirby\Cms\ModelWithContent; use Kirby\Cms\Nest; +use Kirby\Content\Field; use Kirby\Data\Json; use Kirby\Exception\NotFoundException; use Kirby\Http\Remote; diff --git a/kirby/src/Option/OptionsQuery.php b/kirby/src/Option/OptionsQuery.php index 62800ca..ce4f4e4 100644 --- a/kirby/src/Option/OptionsQuery.php +++ b/kirby/src/Option/OptionsQuery.php @@ -3,12 +3,12 @@ namespace Kirby\Option; use Kirby\Cms\Block; -use Kirby\Cms\Field; use Kirby\Cms\File; use Kirby\Cms\ModelWithContent; use Kirby\Cms\Page; use Kirby\Cms\StructureObject; use Kirby\Cms\User; +use Kirby\Content\Field; use Kirby\Exception\InvalidArgumentException; use Kirby\Toolkit\Collection; use Kirby\Toolkit\Obj; diff --git a/kirby/src/Panel/Assets.php b/kirby/src/Panel/Assets.php new file mode 100644 index 0000000..5e02a59 --- /dev/null +++ b/kirby/src/Panel/Assets.php @@ -0,0 +1,298 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier + * @license https://getkirby.com/license + * @since 4.0.0 + */ +class Assets +{ + protected bool $dev; + protected App $kirby; + protected string $nonce; + protected Plugins $plugins; + protected string $url; + protected bool $vite; + + public function __construct() + { + $this->kirby = App::instance(); + $this->nonce = $this->kirby->nonce(); + $this->plugins = new Plugins(); + + $vite = $this->kirby->roots()->panel() . '/.vite-running'; + $this->vite = is_file($vite) === true; + + // get the assets from the Vite dev server in dev mode; + // dev mode = explicitly enabled in the config AND Vite is running + $dev = $this->kirby->option('panel.dev', false); + $this->dev = $dev !== false && $this->vite === true; + + // get the base URL + $this->url = $this->url(); + } + + /** + * Get all CSS files + */ + public function css(): array + { + $css = A::merge( + [ + 'index' => $this->url . '/css/style.min.css', + 'plugins' => $this->plugins->url('css') + ], + $this->custom('panel.css') + ); + + // during dev mode we do not need to load + // the general stylesheet (as styling will be inlined) + if ($this->dev === true) { + $css['index'] = null; + } + + return array_filter($css); + } + + /** + * Check for a custom asset file from the + * config (e.g. panel.css or panel.js) + */ + public function custom(string $option): array + { + $customs = []; + + if ($assets = $this->kirby->option($option)) { + $assets = A::wrap($assets); + + foreach ($assets as $index => $path) { + if (Url::isAbsolute($path) === true) { + $customs['custom-' . $index] = $path; + continue; + } + + $asset = new Asset($path); + + if ($asset->exists() === true) { + $customs['custom-' . $index] = $asset->url() . '?' . $asset->modified(); + } + } + } + + return $customs; + } + + /** + * Generates an array with all assets + * that need to be loaded for the panel (js, css, icons) + */ + public function external(): array + { + return [ + 'css' => $this->css(), + 'icons' => $this->favicons(), + // loader for plugins' index.dev.mjs files – inlined, + // so we provide the code instead of the asset URL + 'plugin-imports' => $this->plugins->read('mjs'), + 'js' => $this->js() + ]; + } + + /** + * Returns array of favicon icons + * based on config option + * + * @throws \Kirby\Exception\InvalidArgumentException + */ + public function favicons(): array + { + $icons = $this->kirby->option('panel.favicon', [ + 'apple-touch-icon' => [ + 'type' => 'image/png', + 'url' => $this->url . '/apple-touch-icon.png', + ], + 'alternate icon' => [ + 'type' => 'image/png', + 'url' => $this->url . '/favicon.png', + ], + 'shortcut icon' => [ + 'type' => 'image/svg+xml', + 'url' => $this->url . '/favicon.svg', + ] + ]); + + if (is_array($icons) === true) { + return $icons; + } + + // make sure to convert favicon string to array + if (is_string($icons) === true) { + return [ + 'shortcut icon' => [ + 'type' => F::mime($icons), + 'url' => $icons, + ] + ]; + } + + throw new InvalidArgumentException('Invalid panel.favicon option'); + } + + /** + * Load the SVG icon sprite + * This will be injected in the + * initial HTML document for the Panel + */ + public function icons(): string + { + $dir = $this->kirby->root('panel') . '/'; + $dir .= $this->dev ? 'public' : 'dist'; + $icons = F::read($dir . '/img/icons.svg'); + $icons = preg_replace('//', '', $icons); + return $icons; + } + + /** + * Get all js files + */ + public function js(): array + { + $js = A::merge( + [ + 'vue' => [ + 'nonce' => $this->nonce, + 'src' => $this->url . '/js/vue.min.js' + ], + 'vendor' => [ + 'nonce' => $this->nonce, + 'src' => $this->url . '/js/vendor.min.js', + 'type' => 'module' + ], + 'pluginloader' => [ + 'nonce' => $this->nonce, + 'src' => $this->url . '/js/plugins.js', + 'type' => 'module' + ], + 'plugins' => [ + 'nonce' => $this->nonce, + 'src' => $this->plugins->url('js'), + 'defer' => true + ] + ], + A::map($this->custom('panel.js'), fn ($src) => [ + 'nonce' => $this->nonce, + 'src' => $src, + 'type' => 'module' + ]), + [ + 'index' => [ + 'nonce' => $this->nonce, + 'src' => $this->url . '/js/index.min.js', + 'type' => 'module' + ], + ] + ); + + + // during dev mode, add vite client and adapt + // path to `index.js` - vendor does not need + // to be loaded in dev mode + if ($this->dev === true) { + $js['vite'] = [ + 'nonce' => $this->nonce, + 'src' => $this->url . '/@vite/client', + 'type' => 'module' + ]; + + $js['index'] = [ + 'nonce' => $this->nonce, + 'src' => $this->url . '/src/index.js', + 'type' => 'module' + ]; + + // load the development version of Vue + $js['vue']['src'] = $this->url . '/node_modules/vue/dist/vue.js'; + + // remove the vendor script + $js['vendor']['src'] = null; + } + + return array_filter($js, fn ($js) => empty($js['src']) === false); + } + + /** + * Links all dist files in the media folder + * and returns the link to the requested asset + * + * @throws \Kirby\Exception\Exception If Panel assets could not be moved to the public directory + */ + public function link(): bool + { + $mediaRoot = $this->kirby->root('media') . '/panel'; + $panelRoot = $this->kirby->root('panel') . '/dist'; + $versionHash = $this->kirby->versionHash(); + $versionRoot = $mediaRoot . '/' . $versionHash; + + // check if the version already exists + if (is_dir($versionRoot) === true) { + return false; + } + + // delete the panel folder and all previous versions + Dir::remove($mediaRoot); + + // recreate the panel folder + Dir::make($mediaRoot, true); + + // copy assets to the dist folder + if (Dir::copy($panelRoot, $versionRoot) !== true) { + throw new Exception('Panel assets could not be linked'); + } + + return true; + } + + /** + * Get the base URL for all assets depending on dev mode + */ + public function url(): string + { + // vite is not running, use production assets + if ($this->dev === false) { + return $this->kirby->url('media') . '/panel/' . $this->kirby->versionHash(); + } + + // explicitly configured base URL + $dev = $this->kirby->option('panel.dev'); + if (is_string($dev) === true) { + return $dev; + } + + // port 3000 of the current Kirby request + return rtrim($this->kirby->request()->url([ + 'port' => 3000, + 'path' => null, + 'params' => null, + 'query' => null + ])->toString(), '/'); + } +} diff --git a/kirby/src/Panel/ChangesDialog.php b/kirby/src/Panel/ChangesDialog.php new file mode 100644 index 0000000..7053626 --- /dev/null +++ b/kirby/src/Panel/ChangesDialog.php @@ -0,0 +1,71 @@ +multilang(); + $changes = []; + + foreach ($ids as $id) { + try { + // parse the given ID to extract + // the path and an optional query + $uri = new Uri($id); + $path = $uri->path()->toString(); + $query = $uri->query(); + $model = Find::parent($path); + $item = $model->panel()->dropdownOption(); + + // add the language to each option, if it is included in the query + // of the given ID and the language actually exists + if ( + $multilang && + $query->language && + $language = $kirby->language($query->language) + ) { + $item['text'] .= ' (' . $language->code() . ')'; + $item['link'] .= '?language=' . $language->code(); + } + + $item['text'] = Escape::html($item['text']); + + $changes[] = $item; + } catch (Throwable) { + continue; + } + } + + return $changes; + } + + public function load(): array + { + return $this->state(); + } + + public function state(bool $loading = true, array $changes = []) + { + return [ + 'component' => 'k-changes-dialog', + 'props' => [ + 'changes' => $changes, + 'loading' => $loading + ] + ]; + } + + public function submit(array $ids): array + { + return $this->state(false, $this->changes($ids)); + } +} diff --git a/kirby/src/Panel/Dialog.php b/kirby/src/Panel/Dialog.php index 3f0fc1c..df43c4a 100644 --- a/kirby/src/Panel/Dialog.php +++ b/kirby/src/Panel/Dialog.php @@ -7,7 +7,7 @@ use Kirby\Http\Response; /** * The Dialog response class handles Fiber * requests to render the JSON object for - * Panel dialogs + * Panel dialogs and creates the routes * @since 3.6.0 * * @package Kirby Panel @@ -34,4 +34,39 @@ class Dialog extends Json return parent::response($data, $options); } + + /** + * Builds the routes for a dialog + */ + public static function routes( + string $id, + string $areaId, + string $prefix = '', + array $options = [] + ) { + $routes = []; + + // create the full pattern with dialogs prefix + $pattern = trim($prefix . '/' . ($options['pattern'] ?? $id), '/'); + $type = str_replace('$', '', static::$key); + + // load event + $routes[] = [ + 'pattern' => $pattern, + 'type' => $type, + 'area' => $areaId, + 'action' => $options['load'] ?? fn () => 'The load handler is missing' + ]; + + // submit event + $routes[] = [ + 'pattern' => $pattern, + 'type' => $type, + 'area' => $areaId, + 'method' => 'POST', + 'action' => $options['submit'] ?? fn () => 'The submit handler is missing' + ]; + + return $routes; + } } diff --git a/kirby/src/Panel/Document.php b/kirby/src/Panel/Document.php index ee2a62d..1c145f0 100644 --- a/kirby/src/Panel/Document.php +++ b/kirby/src/Panel/Document.php @@ -3,11 +3,6 @@ namespace Kirby\Panel; use Kirby\Cms\App; -use Kirby\Exception\Exception; -use Kirby\Exception\InvalidArgumentException; -use Kirby\Filesystem\Asset; -use Kirby\Filesystem\Dir; -use Kirby\Filesystem\F; use Kirby\Http\Response; use Kirby\Http\Uri; use Kirby\Toolkit\Tpl; @@ -27,234 +22,18 @@ use Throwable; */ class Document { - /** - * Generates an array with all assets - * that need to be loaded for the panel (js, css, icons) - */ - public static function assets(): array - { - $kirby = App::instance(); - $nonce = $kirby->nonce(); - - // get the assets from the Vite dev server in dev mode; - // dev mode = explicitly enabled in the config AND Vite is running - $dev = $kirby->option('panel.dev', false); - $isDev = $dev !== false && is_file($kirby->roots()->panel() . '/.vite-running') === true; - - if ($isDev === true) { - // vite on explicitly configured base URL or port 3000 - // of the current Kirby request - if (is_string($dev) === true) { - $url = $dev; - } else { - $url = rtrim($kirby->request()->url([ - 'port' => 3000, - 'path' => null, - 'params' => null, - 'query' => null - ])->toString(), '/'); - } - } else { - // vite is not running, use production assets - $url = $kirby->url('media') . '/panel/' . $kirby->versionHash(); - } - - // fetch all plugins - $plugins = new Plugins(); - - $assets = [ - 'css' => [ - 'index' => $url . '/css/style.css', - 'plugins' => $plugins->url('css'), - 'custom' => static::customAsset('panel.css'), - ], - 'icons' => static::favicon($url), - // loader for plugins' index.dev.mjs files – - // inlined, so we provide the code instead of the asset URL - 'plugin-imports' => $plugins->read('mjs'), - 'js' => [ - 'vue' => [ - 'nonce' => $nonce, - 'src' => $url . '/js/vue.js' - ], - 'vendor' => [ - 'nonce' => $nonce, - 'src' => $url . '/js/vendor.js', - 'type' => 'module' - ], - 'pluginloader' => [ - 'nonce' => $nonce, - 'src' => $url . '/js/plugins.js', - 'type' => 'module' - ], - 'plugins' => [ - 'nonce' => $nonce, - 'src' => $plugins->url('js'), - 'defer' => true - ], - 'custom' => [ - 'nonce' => $nonce, - 'src' => static::customAsset('panel.js'), - 'type' => 'module' - ], - 'index' => [ - 'nonce' => $nonce, - 'src' => $url . '/js/index.js', - 'type' => 'module' - ], - ] - ]; - - // during dev mode, add vite client and adapt - // path to `index.js` - vendor and stylesheet - // don't need to be loaded in dev mode - if ($isDev === true) { - $assets['js']['vite'] = [ - 'nonce' => $nonce, - 'src' => $url . '/@vite/client', - 'type' => 'module' - ]; - $assets['js']['index'] = [ - 'nonce' => $nonce, - 'src' => $url . '/src/index.js', - 'type' => 'module' - ]; - - // load the development version of Vue - $assets['js']['vue']['src'] = $url . '/node_modules/vue/dist/vue.js'; - - unset($assets['css']['index'], $assets['js']['vendor']); - } - - // remove missing files - $assets['css'] = array_filter($assets['css']); - $assets['js'] = array_filter( - $assets['js'], - fn ($js) => empty($js['src']) === false - ); - - return $assets; - } - - /** - * Check for a custom asset file from the - * config (e.g. panel.css or panel.js) - * @since 3.7.0 - * - * @param string $option asset option name - */ - public static function customAsset(string $option): string|null - { - if ($path = App::instance()->option($option)) { - $asset = new Asset($path); - - if ($asset->exists() === true) { - return $asset->url() . '?' . $asset->modified(); - } - } - - return null; - } - - /** - * Returns array of favicon icons - * based on config option - * @since 3.7.0 - * - * @param string $url URL prefix for default icons - * @throws \Kirby\Exception\InvalidArgumentException - */ - public static function favicon(string $url = ''): array - { - $kirby = App::instance(); - $icons = $kirby->option('panel.favicon', [ - 'apple-touch-icon' => [ - 'type' => 'image/png', - 'url' => $url . '/apple-touch-icon.png', - ], - 'alternate icon' => [ - 'type' => 'image/png', - 'url' => $url . '/favicon.png', - ], - 'shortcut icon' => [ - 'type' => 'image/svg+xml', - 'url' => $url . '/favicon.svg', - ] - ]); - - if (is_array($icons) === true) { - return $icons; - } - - // make sure to convert favicon string to array - if (is_string($icons) === true) { - return [ - 'shortcut icon' => [ - 'type' => F::mime($icons), - 'url' => $icons, - ] - ]; - } - - throw new InvalidArgumentException('Invalid panel.favicon option'); - } - - /** - * Load the SVG icon sprite - * This will be injected in the - * initial HTML document for the Panel - */ - public static function icons(): string - { - $dev = App::instance()->option('panel.dev', false); - $dir = $dev ? 'public' : 'dist'; - return F::read(App::instance()->root('kirby') . '/panel/' . $dir . '/img/icons.svg'); - } - - /** - * Links all dist files in the media folder - * and returns the link to the requested asset - * - * @throws \Kirby\Exception\Exception If Panel assets could not be moved to the public directory - */ - public static function link(): bool - { - $kirby = App::instance(); - $mediaRoot = $kirby->root('media') . '/panel'; - $panelRoot = $kirby->root('panel') . '/dist'; - $versionHash = $kirby->versionHash(); - $versionRoot = $mediaRoot . '/' . $versionHash; - - // check if the version already exists - if (is_dir($versionRoot) === true) { - return false; - } - - // delete the panel folder and all previous versions - Dir::remove($mediaRoot); - - // recreate the panel folder - Dir::make($mediaRoot, true); - - // copy assets to the dist folder - if (Dir::copy($panelRoot, $versionRoot) !== true) { - throw new Exception('Panel assets could not be linked'); - } - - return true; - } - /** * Renders the panel document */ public static function response(array $fiber): Response { - $kirby = App::instance(); + $kirby = App::instance(); + $assets = new Assets(); // Full HTML response // @codeCoverageIgnoreStart try { - if (static::link() === true) { + if ($assets->link() === true) { usleep(1); Response::go($kirby->url('base') . '/' . $kirby->path()); } @@ -264,15 +43,15 @@ class Document // @codeCoverageIgnoreEnd // get the uri object for the panel url - $uri = new Uri($url = $kirby->url('panel')); + $uri = new Uri($kirby->url('panel')); // proper response code $code = $fiber['$view']['code'] ?? 200; // load the main Panel view template $body = Tpl::load($kirby->root('kirby') . '/views/panel.php', [ - 'assets' => static::assets(), - 'icons' => static::icons(), + 'assets' => $assets->external(), + 'icons' => $assets->icons(), 'nonce' => $kirby->nonce(), 'fiber' => $fiber, 'panelUrl' => $uri->path()->toString(true) . '/', diff --git a/kirby/src/Panel/Drawer.php b/kirby/src/Panel/Drawer.php new file mode 100644 index 0000000..0952088 --- /dev/null +++ b/kirby/src/Panel/Drawer.php @@ -0,0 +1,21 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier + * @license https://getkirby.com/license + */ +class Drawer extends Dialog +{ + protected static string $key = '$drawer'; +} diff --git a/kirby/src/Panel/Dropdown.php b/kirby/src/Panel/Dropdown.php index b0e5fda..de01bf6 100644 --- a/kirby/src/Panel/Dropdown.php +++ b/kirby/src/Panel/Dropdown.php @@ -2,13 +2,8 @@ namespace Kirby\Panel; -use Kirby\Cms\App; -use Kirby\Cms\Find; -use Kirby\Exception\LogicException; +use Closure; use Kirby\Http\Response; -use Kirby\Http\Uri; -use Kirby\Toolkit\Str; -use Throwable; /** * The Dropdown response class handles Fiber @@ -26,49 +21,6 @@ class Dropdown extends Json { protected static string $key = '$dropdown'; - /** - * Returns the options for the changes dropdown - */ - public static function changes(): array - { - $kirby = App::instance(); - $multilang = $kirby->multilang(); - $ids = Str::split($kirby->request()->get('ids')); - $options = []; - - foreach ($ids as $id) { - try { - // parse the given ID to extract - // the path and an optional query - $uri = new Uri($id); - $path = $uri->path()->toString(); - $query = $uri->query(); - $option = Find::parent($path)->panel()->dropdownOption(); - - // add the language to each option, if it is included in the query - // of the given ID and the language actually exists - if ($multilang && $query->language && $language = $kirby->language($query->language)) { - $option['text'] .= ' (' . $language->code() . ')'; - $option['link'] .= '?language=' . $language->code(); - } - - $options[] = $option; - } catch (Throwable) { - continue; - } - } - - // the given set of ids does not match any - // real models. This means that the stored ids - // in local storage are not correct and the changes - // store needs to be cleared - if (empty($options) === true) { - throw new LogicException('No changes for given models'); - } - - return $options; - } - /** * Renders dropdowns */ @@ -82,4 +34,38 @@ class Dropdown extends Json return parent::response($data, $options); } + + /** + * Routes for the dropdown + */ + public static function routes( + string $id, + string $areaId, + string $prefix = '', + Closure|array $options = [] + ): array { + // Handle shortcuts for dropdowns. The name is the pattern + // and options are defined in a Closure + if ($options instanceof Closure) { + $options = [ + 'pattern' => $id, + 'action' => $options + ]; + } + + // create the full pattern with dialogs prefix + $pattern = trim($prefix . '/' . ($options['pattern'] ?? $id), '/'); + $type = str_replace('$', '', static::$key); + + return [ + // load event + [ + 'pattern' => $pattern, + 'type' => $type, + 'area' => $areaId, + 'method' => 'GET|POST', + 'action' => $options['options'] ?? $options['action'] + ] + ]; + } } diff --git a/kirby/src/Panel/Field.php b/kirby/src/Panel/Field.php index ab53aa1..8e290c4 100644 --- a/kirby/src/Panel/Field.php +++ b/kirby/src/Panel/Field.php @@ -4,7 +4,10 @@ namespace Kirby\Panel; use Kirby\Cms\App; use Kirby\Cms\File; +use Kirby\Cms\ModelWithContent; use Kirby\Cms\Page; +use Kirby\Form\Form; +use Kirby\Http\Router; use Kirby\Toolkit\I18n; /** @@ -20,6 +23,58 @@ use Kirby\Toolkit\I18n; */ class Field { + /** + * Creates the routes for a field dialog + * This is most definitely not a good place for this + * method, but as long as the other classes are + * not fully refactored, it still feels appropriate + */ + public static function dialog( + ModelWithContent $model, + string $fieldName, + string|null $path = null, + string $method = 'GET', + ) { + $field = Form::for($model)->field($fieldName); + $routes = []; + + foreach ($field->dialogs() as $dialogId => $dialog) { + $routes = array_merge($routes, Dialog::routes( + id: $dialogId, + areaId: 'site', + options: $dialog + )); + } + + return Router::execute($path, $method, $routes); + } + + /** + * Creates the routes for a field drawer + * This is most definitely not a good place for this + * method, but as long as the other classes are + * not fully refactored, it still feels appropriate + */ + public static function drawer( + ModelWithContent $model, + string $fieldName, + string|null $path = null, + string $method = 'GET', + ) { + $field = Form::for($model)->field($fieldName); + $routes = []; + + foreach ($field->drawers() as $drawerId => $drawer) { + $routes = array_merge($routes, Drawer::routes( + id: $drawerId, + areaId: 'site', + options: $drawer + )); + } + + return Router::execute($path, $method, $routes); + } + /** * A standard email field */ @@ -73,7 +128,7 @@ class Field public static function hidden(): array { - return ['type' => 'hidden']; + return ['hidden' => true]; } /** @@ -138,8 +193,7 @@ class Field public static function role(array $props = []): array { $kirby = App::instance(); - $user = $kirby->user(); - $isAdmin = $user && $user->isAdmin(); + $isAdmin = $kirby->user()?->isAdmin() ?? false; $roles = []; foreach ($kirby->roles() as $role) { @@ -171,8 +225,10 @@ class Field ], $props); } - public static function template(array|null $blueprints = [], array|null $props = []): array - { + public static function template( + array|null $blueprints = [], + array|null $props = [] + ): array { $options = []; foreach ($blueprints as $blueprint) { @@ -217,7 +273,7 @@ class Field return array_merge([ 'label' => I18n::translate('language'), 'type' => 'select', - 'icon' => 'globe', + 'icon' => 'translate', 'options' => $translations, 'empty' => false ], $props); diff --git a/kirby/src/Panel/File.php b/kirby/src/Panel/File.php index 531c679..d58e7a1 100644 --- a/kirby/src/Panel/File.php +++ b/kirby/src/Panel/File.php @@ -3,6 +3,7 @@ namespace Kirby\Panel; use Kirby\Cms\File as CmsFile; +use Kirby\Cms\ModelWithContent; use Kirby\Filesystem\Asset; use Kirby\Toolkit\I18n; use Throwable; @@ -19,6 +20,11 @@ use Throwable; */ class File extends Model { + /** + * @var \Kirby\Cms\File + */ + protected ModelWithContent $model; + /** * Breadcrumb array */ @@ -41,10 +47,12 @@ class File extends Model break; case 'page': /** @var \Kirby\Cms\Page $parent */ - $breadcrumb = $this->model->parents()->flip()->values(fn ($parent) => [ - 'label' => $parent->title()->toString(), - 'link' => $parent->panel()->url(true), - ]); + $breadcrumb = $this->model->parents()->flip()->values( + fn ($parent) => [ + 'label' => $parent->title()->toString(), + 'link' => $parent->panel()->url(true), + ] + ); } // add the file @@ -65,8 +73,10 @@ class File extends Model * @internal * @param string|null $type (`auto`|`kirbytext`|`markdown`) */ - public function dragText(string|null $type = null, bool $absolute = false): string - { + public function dragText( + string|null $type = null, + bool $absolute = false + ): string { $type = $this->dragTextType($type); $url = $this->model->filename(); $file = $this->model->type(); @@ -76,14 +86,17 @@ class File extends Model // for markdown notation or the UUID for Kirbytext (since // Kirbytags support can resolve UUIDs directly) if ($absolute === true) { - $url = $type === 'markdown' ? $this->model->permalink() : $this->model->uuid(); + $url = match ($type) { + 'markdown' => $this->model->permalink(), + default => $this->model->uuid() + }; + // if UUIDs are disabled, fall back to URL $url ??= $this->model->url(); } - - if ($dragTextFromCallback = $this->dragTextFromCallback($type, $url)) { - return $dragTextFromCallback; + if ($callback = $this->dragTextFromCallback($type, $url)) { + return $callback; } if ($type === 'markdown') { @@ -104,9 +117,9 @@ class File extends Model */ public function dropdown(array $options = []): array { - $file = $this->model; - - $defaults = $file->kirby()->request()->get(['view', 'update', 'delete']); + $file = $this->model; + $request = $file->kirby()->request(); + $defaults = $request->get(['view', 'update', 'delete']); $options = array_merge($defaults, $options); $permissions = $this->options(['preview']); @@ -131,15 +144,7 @@ class File extends Model 'disabled' => $this->isDisabledDropdownOption('changeName', $options, $permissions) ]; - $result[] = [ - 'click' => 'replace', - 'icon' => 'upload', - 'text' => I18n::translate('replace'), - 'disabled' => $this->isDisabledDropdownOption('replace', $options, $permissions) - ]; - if ($view === 'list') { - $result[] = '-'; $result[] = [ 'dialog' => $url . '/changeSort', 'icon' => 'sort', @@ -148,6 +153,22 @@ class File extends Model ]; } + $result[] = [ + 'dialog' => $url . '/changeTemplate', + 'icon' => 'template', + 'text' => I18n::translate('file.changeTemplate'), + 'disabled' => $this->isDisabledDropdownOption('changeTemplate', $options, $permissions) + ]; + + $result[] = '-'; + + $result[] = [ + 'click' => 'replace', + 'icon' => 'upload', + 'text' => I18n::translate('replace'), + 'disabled' => $this->isDisabledDropdownOption('replace', $options, $permissions) + ]; + $result[] = '-'; $result[] = [ 'dialog' => $url . '/delete', @@ -178,22 +199,22 @@ class File extends Model protected function imageColor(): string { $types = [ - 'image' => 'orange-400', - 'video' => 'yellow-400', - 'document' => 'red-400', - 'audio' => 'aqua-400', - 'code' => 'blue-400', - 'archive' => 'gray-500' + 'archive' => 'gray-500', + 'audio' => 'aqua-500', + 'code' => 'pink-500', + 'document' => 'red-500', + 'image' => 'orange-500', + 'video' => 'yellow-500', ]; $extensions = [ - 'indd' => 'purple-400', - 'xls' => 'green-400', - 'xlsx' => 'green-400', - 'csv' => 'green-400', - 'docx' => 'blue-400', - 'doc' => 'blue-400', - 'rtf' => 'blue-400' + 'csv' => 'green-500', + 'doc' => 'blue-500', + 'docx' => 'blue-500', + 'indd' => 'purple-500', + 'rtf' => 'blue-500', + 'xls' => 'green-500', + 'xlsx' => 'green-500', ]; return @@ -219,23 +240,23 @@ class File extends Model protected function imageIcon(): string { $types = [ - 'image' => 'image', - 'video' => 'video', - 'document' => 'document', + 'archive' => 'archive', 'audio' => 'audio', 'code' => 'code', - 'archive' => 'archive' + 'document' => 'document', + 'image' => 'image', + 'video' => 'video', ]; $extensions = [ + 'csv' => 'table', + 'doc' => 'pen', + 'docx' => 'pen', + 'md' => 'markdown', + 'mdown' => 'markdown', + 'rtf' => 'pen', 'xls' => 'table', 'xlsx' => 'table', - 'csv' => 'table', - 'docx' => 'pen', - 'doc' => 'pen', - 'rtf' => 'pen', - 'mdown' => 'markdown', - 'md' => 'markdown' ]; return @@ -258,6 +279,40 @@ class File extends Model return parent::imageSource($query); } + /** + * Whether focus can be added in Panel view + */ + public function isFocusable(): bool + { + // blueprint option + $option = $this->model->blueprint()->focus(); + // fallback to whether the file is viewable + // (images should be focusable by default, others not) + $option ??= $this->model->isViewable(); + + if ($option === false) { + return false; + } + + // ensure that user can update content file + if ($this->options()['update'] === false) { + return false; + } + + $kirby = $this->model->kirby(); + + // ensure focus is only added when editing primary/only language + if ( + $kirby->multilang() === false || + $kirby->languages()->count() === 0 || + $kirby->language()->isDefault() === true + ) { + return true; + } + + return false; + } + /** * Returns an array of all actions * that can be performed in the Panel @@ -297,7 +352,8 @@ class File extends Model $id = $this->model->id(); if (empty($params['model']) === false) { - $parent = $this->model->parent(); + $parent = $this->model->parent(); + // if the file belongs to the current parent model, // store only name as ID to keep its path relative to the model $id = $parent === $params['model'] ? $name : $id; @@ -324,13 +380,6 @@ class File extends Model { $file = $this->model; $dimensions = $file->dimensions(); - $siblings = $file->templateSiblings()->sortBy( - 'sort', - 'asc', - 'filename', - 'asc' - ); - return array_merge( parent::props(), @@ -352,12 +401,13 @@ class File extends Model 'url' => $file->url(), ], 'preview' => [ - 'image' => $this->image([ + 'focusable' => $this->isFocusable(), + 'image' => $this->image([ 'back' => 'transparent', 'ratio' => '1/1' ], 'cards'), - 'url' => $url = $file->previewUrl(), - 'details' => [ + 'url' => $url = $file->previewUrl(), + 'details' => [ [ 'title' => I18n::translate('template'), 'text' => $file->template() ?? '—' diff --git a/kirby/src/Panel/Home.php b/kirby/src/Panel/Home.php index 95ba708..3abb1c4 100644 --- a/kirby/src/Panel/Home.php +++ b/kirby/src/Panel/Home.php @@ -50,7 +50,8 @@ class Home // needed to create a proper menu $areas = Panel::areas(); - $menu = View::menu($areas, $permissions->toArray()); + $menu = new Menu($areas, $permissions->toArray()); + $menu = $menu->entries(); // go through the menu and search for the first // available view we can go to @@ -65,11 +66,16 @@ class Home continue; } - // skip the logout button - if ($menuItem['id'] === 'logout') { + // skip buttons that don't open a link + // (but e.g. a dialog) + if (isset($menuItem['link']) === false) { continue; } + // skip the logout button + if ($menuItem['link'] === 'logout') { + continue; + } return Panel::url($menuItem['link']); } @@ -100,9 +106,10 @@ class Home // create a dummy router to check if we can access this route at all try { return Router::execute($path, 'GET', $routes, function ($route) use ($user) { - $auth = $route->attributes()['auth'] ?? true; - $areaId = $route->attributes()['area'] ?? null; - $type = $route->attributes()['type'] ?? 'view'; + $attrs = $route->attributes(); + $auth = $attrs['auth'] ?? true; + $areaId = $attrs['area'] ?? null; + $type = $attrs['type'] ?? 'view'; // only allow redirects to views if ($type !== 'view') { @@ -131,7 +138,8 @@ class Home public static function hasValidDomain(Uri $uri): bool { $rootUrl = App::instance()->site()->url(); - return $uri->domain() === (new Uri($rootUrl))->domain(); + $rootUri = new Uri($rootUrl); + return $uri->domain() === $rootUri->domain(); } /** @@ -139,7 +147,8 @@ class Home */ public static function isPanelUrl(string $url): bool { - return Str::startsWith($url, App::instance()->url('panel')); + $panel = App::instance()->url('panel'); + return Str::startsWith($url, $panel); } /** @@ -161,10 +170,12 @@ class Home public static function remembered(): string|null { // check for a stored path after login - $remembered = App::instance()->session()->pull('panel.path'); + if ($remembered = App::instance()->session()->pull('panel.path')) { + // convert the result to an absolute URL if available + return Panel::url($remembered); + } - // convert the result to an absolute URL if available - return $remembered ? Panel::url($remembered) : null; + return null; } /** diff --git a/kirby/src/Panel/Json.php b/kirby/src/Panel/Json.php index 1e67e31..2cfa895 100644 --- a/kirby/src/Panel/Json.php +++ b/kirby/src/Panel/Json.php @@ -2,6 +2,7 @@ namespace Kirby\Panel; +use Kirby\Cms\App; use Kirby\Exception\Exception; use Kirby\Http\Response; use Throwable; @@ -39,35 +40,45 @@ abstract class Json */ public static function response($data, array $options = []): Response { - // handle redirects - if ($data instanceof Redirect) { - $data = [ - 'redirect' => $data->location(), - 'code' => $data->code() - ]; - - // handle Kirby exceptions - } elseif ($data instanceof Exception) { - $data = static::error($data->getMessage(), $data->getHttpCode()); - - // handle exceptions - } elseif ($data instanceof Throwable) { - $data = static::error($data->getMessage(), 500); - - // only expect arrays from here on - } elseif (is_array($data) === false) { - $data = static::error('Invalid response', 500); - } - - if (empty($data) === true) { - $data = static::error('The response is empty', 404); - } + $data = static::responseData($data); // always inject the response code $data['code'] ??= 200; $data['path'] = $options['path'] ?? null; + $data['query'] = App::instance()->request()->query()->toArray(); $data['referrer'] = Panel::referrer(); return Panel::json([static::$key => $data], $data['code']); } + + public static function responseData(mixed $data): array + { + // handle redirects + if ($data instanceof Redirect) { + return [ + 'redirect' => $data->location(), + ]; + } + + // handle Kirby exceptions + if ($data instanceof Exception) { + return static::error($data->getMessage(), $data->getHttpCode()); + } + + // handle exceptions + if ($data instanceof Throwable) { + return static::error($data->getMessage(), 500); + } + + // only expect arrays from here on + if (is_array($data) === false) { + return static::error('Invalid response', 500); + } + + if (empty($data) === true) { + return static::error('The response is empty', 404); + } + + return $data; + } } diff --git a/kirby/src/Panel/Lab/Category.php b/kirby/src/Panel/Lab/Category.php new file mode 100644 index 0000000..4926737 --- /dev/null +++ b/kirby/src/Panel/Lab/Category.php @@ -0,0 +1,134 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier + * @license https://getkirby.com/license + */ +class Category +{ + protected string $root; + + public function __construct( + protected string $id, + string|null $root = null, + protected array $props = [] + ) { + $this->root = $root ?? static::base() . '/' . $this->id; + + if (file_exists($this->root . '/index.php') === true) { + $this->props = array_merge( + require $this->root . '/index.php', + $this->props + ); + } + } + + public static function all(): array + { + // all core lab examples from `kirby/panel/lab` + $examples = A::map( + Dir::inventory(static::base())['children'], + fn ($props) => (new static($props['dirname']))->toArray() + ); + + // all custom lab examples from `site/lab` + $custom = static::factory('site')->toArray(); + + array_push($examples, $custom); + + return $examples; + } + + public static function base(): string + { + return App::instance()->root('panel') . '/lab'; + } + + public function example(string $id, string|null $tab = null): Example + { + return new Example(parent: $this, id: $id, tab: $tab); + } + + public function examples(): array + { + return A::map( + Dir::inventory($this->root)['children'], + fn ($props) => $this->example($props['dirname'])->toArray() + ); + } + + public static function factory(string $id) + { + return match ($id) { + 'site' => static::site(), + default => new static($id) + }; + } + + public function icon(): string + { + return $this->props['icon'] ?? 'palette'; + } + + public function id(): string + { + return $this->id; + } + + public static function installed(): bool + { + return Dir::exists(static::base()) === true; + } + + public function name(): string + { + return $this->props['name'] ?? ucfirst($this->id); + } + + public function root(): string + { + return $this->root; + } + + public static function site(): static + { + return new static( + 'site', + App::instance()->root('site') . '/lab', + [ + 'name' => 'Your examples', + 'icon' => 'live' + ] + ); + } + + public function toArray(): array + { + return [ + 'name' => $this->name(), + 'examples' => $this->examples(), + 'icon' => $this->icon(), + 'path' => Str::after( + $this->root(), + App::instance()->root('index') + ), + ]; + } +} diff --git a/kirby/src/Panel/Lab/Docs.php b/kirby/src/Panel/Lab/Docs.php new file mode 100644 index 0000000..44a6af0 --- /dev/null +++ b/kirby/src/Panel/Lab/Docs.php @@ -0,0 +1,340 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier + * @license https://getkirby.com/license + */ +class Docs +{ + protected array $json; + protected App $kirby; + + public function __construct( + protected string $name + ) { + $this->kirby = App::instance(); + $this->json = $this->read(); + } + + public static function all(): array + { + $dist = static::root(); + $tmp = static::root(true); + $files = Dir::inventory($dist)['files']; + + if (Dir::exists($tmp) === true) { + $files = [...Dir::inventory($tmp)['files'], ...$files]; + } + + $docs = A::map( + $files, + function ($file) { + $component = 'k-' . Str::camelToKebab(F::name($file['filename'])); + + return [ + 'image' => [ + 'icon' => 'book', + 'back' => 'white', + ], + 'text' => $component, + 'link' => '/lab/docs/' . $component, + ]; + } + ); + + usort($docs, fn ($a, $b) => $a['text'] <=> $b['text']); + + return array_values($docs); + } + + public function deprecated(): string|null + { + return $this->kt($this->json['tags']['deprecated'][0]['description'] ?? ''); + } + + public function description(): string + { + return $this->kt($this->json['description'] ?? ''); + } + + public function docBlock(): string + { + return $this->kt($this->json['docsBlocks'][0] ?? ''); + } + + public function events(): array + { + $events = A::map( + $this->json['events'] ?? [], + fn ($event) => [ + 'name' => $event['name'], + 'description' => $this->kt($event['description'] ?? ''), + 'deprecated' => $this->kt($event['tags']['deprecated'][0]['description'] ?? ''), + 'since' => $event['tags']['since'][0]['description'] ?? null, + 'properties' => A::map( + $event['properties'] ?? [], + fn ($property) => [ + 'name' => $property['name'], + 'type' => $property['type']['names'][0] ?? '', + 'description' => $this->kt($property['description'] ?? '', true), + ] + ), + ] + ); + + usort($events, fn ($a, $b) => $a['name'] <=> $b['name']); + + return $events; + } + + public function examples(): array + { + if (empty($this->json['tags']['examples']) === false) { + return $this->json['tags']['examples']; + } + + return []; + } + + public function file(string $context): string + { + $root = match ($context) { + 'dev' => $this->kirby->root('panel') . '/tmp', + 'dist' => $this->kirby->root('panel') . '/dist/ui', + }; + + $name = Str::after($this->name, 'k-'); + $name = Str::kebabToCamel($name); + return $root . '/' . $name . '.json'; + } + + public function github(): string + { + return 'https://github.com/getkirby/kirby/tree/main/panel/' . $this->json['sourceFile']; + } + + public static function installed(): bool + { + return Dir::exists(static::root()) === true; + } + + protected function kt(string $text, bool $inline = false): string + { + return $this->kirby->kirbytext($text, [ + 'markdown' => [ + 'breaks' => false, + 'inline' => $inline, + ] + ]); + } + + public function lab(): string|null + { + $root = $this->kirby->root('panel') . '/lab'; + + foreach (glob($root . '/{,*/,*/*/,*/*/*/}index.php', GLOB_BRACE) as $example) { + $props = require $example; + + if (($props['docs'] ?? null) === $this->name) { + return Str::before(Str::after($example, $root), 'index.php'); + } + } + + return null; + } + + public function methods(): array + { + $methods = A::map( + $this->json['methods'] ?? [], + fn ($method) => [ + 'name' => $method['name'], + 'description' => $this->kt($method['description'] ?? ''), + 'deprecated' => $this->kt($method['tags']['deprecated'][0]['description'] ?? ''), + 'since' => $method['tags']['since'][0]['description'] ?? null, + 'params' => A::map( + $method['params'] ?? [], + fn ($param) => [ + 'name' => $param['name'], + 'type' => $param['type']['name'] ?? '', + 'description' => $this->kt($param['description'] ?? '', true), + ] + ), + 'returns' => $method['returns']['type']['name'] ?? null, + ] + ); + + usort($methods, fn ($a, $b) => $a['name'] <=> $b['name']); + + return $methods; + } + + public function name(): string + { + return $this->name; + } + + public function prop(string|int $key): array|null + { + $prop = $this->json['props'][$key]; + + // filter private props + if (($prop['tags']['access'][0]['description'] ?? null) === 'private') { + return null; + } + + // filter unset props + if (($type = $prop['type']['name'] ?? null) === 'null') { + return null; + } + + $default = $prop['defaultValue']['value'] ?? null; + $deprecated = $this->kt($prop['tags']['deprecated'][0]['description'] ?? ''); + + return [ + 'name' => Str::camelToKebab($prop['name']), + 'type' => $type, + 'description' => $this->kt($prop['description'] ?? ''), + 'default' => $this->propDefault($default, $type), + 'deprecated' => $deprecated, + 'example' => $prop['tags']['example'][0]['description'] ?? null, + 'required' => $prop['required'] ?? false, + 'since' => $prop['tags']['since'][0]['description'] ?? null, + 'value' => $prop['tags']['value'][0]['description'] ?? null, + 'values' => $prop['values'] ?? null, + ]; + } + + protected function propDefault( + string|null $default, + string|null $type + ): string|null { + if ($default !== null) { + // normalize longform function + if (preg_match('/function\(\) {.*return (.*);.*}/si', $default, $matches) === 1) { + return $matches[1]; + } + + // normalize object shorthand function + if (preg_match('/\(\) => \((.*)\)/si', $default, $matches) === 1) { + return $matches[1]; + } + + // normalize all other defaults from shorthand function + if (preg_match('/\(\) => (.*)/si', $default, $matches) === 1) { + return $matches[1]; + } + + return $default; + } + + // if type is boolean primarily and no default + // value has been set, add `false` as default + // for clarity + if (Str::startsWith($type, 'boolean')) { + return 'false'; + } + + return null; + } + + public function props(): array + { + $props = A::map( + array_keys($this->json['props'] ?? []), + fn ($key) => $this->prop($key) + ); + + // remove empty props + $props = array_filter($props); + + usort($props, fn ($a, $b) => $a['name'] <=> $b['name']); + + // always return an array + return array_values($props); + } + + protected function read(): array + { + $file = $this->file('dev'); + + if (file_exists($file) === false) { + $file = $this->file('dist'); + } + + return Data::read($file); + } + + public static function root(bool $tmp = false): string + { + return App::instance()->root('panel') . '/' . match ($tmp) { + true => 'tmp', + default => 'dist/ui', + }; + } + + public function since(): string|null + { + return $this->json['tags']['since'][0]['description'] ?? null; + } + + public function slots(): array + { + $slots = A::map( + $this->json['slots'] ?? [], + fn ($slot) => [ + 'name' => $slot['name'], + 'description' => $this->kt($slot['description'] ?? ''), + 'deprecated' => $this->kt($slot['tags']['deprecated'][0]['description'] ?? ''), + 'since' => $slot['tags']['since'][0]['description'] ?? null, + 'bindings' => A::map( + $slot['bindings'] ?? [], + fn ($binding) => [ + 'name' => $binding['name'], + 'type' => $binding['type']['name'] ?? '', + 'description' => $this->kt($binding['description'] ?? '', true), + ] + ), + ] + ); + + usort($slots, fn ($a, $b) => $a['name'] <=> $b['name']); + + return $slots; + } + + public function toArray(): array + { + return [ + 'component' => $this->name(), + 'deprecated' => $this->deprecated(), + 'description' => $this->description(), + 'docBlock' => $this->docBlock(), + 'events' => $this->events(), + 'examples' => $this->examples(), + 'github' => $this->github(), + 'methods' => $this->methods(), + 'props' => $this->props(), + 'since' => $this->since(), + 'slots' => $this->slots(), + ]; + } +} diff --git a/kirby/src/Panel/Lab/Example.php b/kirby/src/Panel/Lab/Example.php new file mode 100644 index 0000000..6df026a --- /dev/null +++ b/kirby/src/Panel/Lab/Example.php @@ -0,0 +1,271 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier + * @license https://getkirby.com/license + */ +class Example +{ + protected string $root; + protected string|null $tab = null; + protected array $tabs; + + public function __construct( + protected Category $parent, + protected string $id, + string|null $tab = null, + ) { + $this->root = $this->parent->root() . '/' . $this->id; + + if ($this->exists() === false) { + throw new NotFoundException('The example could not be found'); + } + + $this->tabs = $this->collectTabs(); + $this->tab = $this->collectTab($tab); + } + + public function collectTab(string|null $tab): string|null + { + if (empty($this->tabs) === true) { + return null; + } + + if (array_key_exists($tab, $this->tabs) === true) { + return $tab; + } + + return array_key_first($this->tabs); + } + + public function collectTabs(): array + { + $tabs = []; + + foreach (Dir::inventory($this->root)['children'] as $child) { + $tabs[$child['dirname']] = [ + 'name' => $child['dirname'], + 'label' => $child['slug'], + 'link' => '/lab/' . $this->parent->id() . '/' . $this->id . '/' . $child['dirname'] + ]; + } + + return $tabs; + } + + public function exists(): bool + { + return is_dir($this->root) === true; + } + + public function file(string $filename): string + { + return $this->parent->root() . '/' . $this->path() . '/' . $filename; + } + + public function github(): string + { + $path = Str::after($this->root(), App::instance()->root('kirby')); + + if ($tab = $this->tab()) { + $path .= '/' . $tab; + } + + return 'https://github.com/getkirby/kirby/tree/main' . $path; + } + + public function id(): string + { + return $this->id; + } + + public function load(string $filename): array|null + { + if ($file = $this->file($filename)) { + return F::load($file); + } + + return null; + } + + public function module(): string + { + return $this->url() . '/index.vue'; + } + + public function path(): string + { + return match ($this->tab) { + null => $this->id, + default => $this->id . '/' . $this->tab + }; + } + + public function props(): array + { + if ($this->tab !== null) { + $props = $this->load('../index.php'); + } + + return array_replace_recursive( + $props ?? [], + $this->load('index.php') ?? [] + ); + } + + public function read(string $filename): string|null + { + $file = $this->file($filename); + + if (is_file($file) === false) { + return null; + } + + return F::read($file); + } + + public function root(): string + { + return $this->root; + } + + public function serve(): Response + { + return new Response($this->vue()['script'], 'application/javascript'); + } + + public function tab(): string|null + { + return $this->tab; + } + + public function tabs(): array + { + return $this->tabs; + } + + public function template(string $filename): string|null + { + $file = $this->file($filename); + + if (is_file($file) === false) { + return null; + } + + $data = $this->props(); + return (new Template($file))->render($data); + } + + public function title(): string + { + return basename($this->id); + } + + public function toArray(): array + { + return [ + 'image' => [ + 'icon' => $this->parent->icon(), + 'back' => 'white', + ], + 'text' => $this->title(), + 'link' => $this->url() + ]; + } + + public function url(): string + { + return '/lab/' . $this->parent->id() . '/' . $this->path(); + } + + public function vue(): array + { + // read the index.vue file (or programmabel Vue PHP file) + $file = $this->read('index.vue'); + $file ??= $this->template('index.vue.php'); + $file ??= ''; + + // extract parts + $parts['template'] = $this->vueTemplate($file); + $parts['examples'] = $this->vueExamples($parts['template']); + $parts['script'] = $this->vueScript($file); + $parts['style'] = $this->vueStyle($file); + + return $parts; + } + + public function vueExamples(string|null $template): array + { + $template ??= ''; + $examples = []; + + if (preg_match_all('!(.*?)<\/k-lab-example>!s', $template, $matches)) { + foreach ($matches[1] as $key => $name) { + $code = $matches[2][$key]; + + // only use the code between the @code and @code-end comments + if (preg_match('$(.*?)$s', $code, $match)) { + $code = $match[1]; + } + + if (preg_match_all('/^(\t*)\S/m', $code, $indents)) { + // get minimum indent + $indents = array_map(fn ($i) => strlen($i), $indents[1]); + $indents = min($indents); + + // strip minimum indent from each line + $code = preg_replace('/^\t{' . $indents . '}/m', '', $code); + } + + $examples[$name] = trim($code); + } + } + + return $examples; + } + + public function vueScript(string $file): string + { + if (preg_match('!!s', $file, $match)) { + return trim($match[1]); + } + + return 'export default {}'; + } + + public function vueStyle(string $file): string|null + { + if (preg_match('!!s', $file, $match)) { + return trim($match[1]); + } + + return null; + } + + public function vueTemplate(string $file): string|null + { + if (preg_match('!!s', $file, $match)) { + return preg_replace('!^\n!', '', $match[1]); + } + + return null; + } +} diff --git a/kirby/src/Panel/Lab/Snippet.php b/kirby/src/Panel/Lab/Snippet.php new file mode 100644 index 0000000..0739eb7 --- /dev/null +++ b/kirby/src/Panel/Lab/Snippet.php @@ -0,0 +1,26 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier + * @license https://getkirby.com/license + */ +class Snippet extends BaseSnippet +{ + public static function root(): string + { + return __DIR__ . '/snippets'; + } +} diff --git a/kirby/src/Panel/Lab/Template.php b/kirby/src/Panel/Lab/Template.php new file mode 100644 index 0000000..71cf484 --- /dev/null +++ b/kirby/src/Panel/Lab/Template.php @@ -0,0 +1,34 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier + * @license https://getkirby.com/license + */ +class Template extends BaseTemplate +{ + public function __construct( + public string $file + ) { + parent::__construct( + name: basename($this->file) + ); + } + + public function file(): string|null + { + return $this->file; + } +} diff --git a/kirby/src/Panel/Menu.php b/kirby/src/Panel/Menu.php new file mode 100644 index 0000000..c362665 --- /dev/null +++ b/kirby/src/Panel/Menu.php @@ -0,0 +1,221 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier + * @license https://getkirby.com/license + */ +class Menu +{ + public function __construct( + protected array $areas = [], + protected array $permissions = [], + protected string|null $current = null + ) { + } + + /** + * Returns all areas that are configured for the menu + * @internal + */ + public function areas(): array + { + // get from config option which areas should be listed in the menu + $kirby = App::instance(); + $areas = $kirby->option('panel.menu'); + + if ($areas instanceof Closure) { + $areas = $areas($kirby); + } + + // if no config is defined… + if ($areas === null) { + // ensure that some defaults are on top in the right order + $defaults = ['site', 'languages', 'users', 'system']; + // add all other areas after that + $additionals = array_diff(array_keys($this->areas), $defaults); + $areas = array_merge($defaults, $additionals); + } + + $result = []; + + foreach ($areas as $id => $area) { + // separator, keep as is in array + if ($area === '-') { + $result[] = '-'; + continue; + } + + // for a simple id, get global area definition + if (is_numeric($id) === true) { + $id = $area; + $area = $this->areas[$id] ?? null; + } + + // did not receive custom entry definition in config, + // but also is not a global area + if ($area === null) { + continue; + } + + // merge area definition (e.g. from config) + // with global area definition + if (is_array($area) === true) { + $area = array_merge( + $this->areas[$id] ?? [], + ['menu' => true], + $area + ); + $area = Panel::area($id, $area); + } + + $result[] = $area; + } + + return $result; + } + + /** + * Transforms an area definition into a menu entry + * @internal + */ + public function entry(array $area): array|false + { + // areas without access permissions get skipped entirely + if ($this->hasPermission($area['id']) === false) { + return false; + } + + // check menu setting from the area definition + $menu = $area['menu'] ?? false; + + // menu setting can be a callback + // that returns true, false or 'disabled' + if ($menu instanceof Closure) { + $menu = $menu($this->areas, $this->permissions, $this->current); + } + + // false will remove the area/entry entirely + //just like with disabled permissions + if ($menu === false) { + return false; + } + + $menu = match ($menu) { + 'disabled' => ['disabled' => true], + true => [], + default => $menu + }; + + $entry = array_merge([ + 'current' => $this->isCurrent( + $area['id'], + $area['current'] ?? null + ), + 'icon' => $area['icon'] ?? null, + 'link' => $area['link'] ?? null, + 'dialog' => $area['dialog'] ?? null, + 'drawer' => $area['drawer'] ?? null, + 'text' => $area['label'], + ], $menu); + + // unset the link (which is always added by default to an area) + // if a dialog or drawer should be opened instead + if (isset($entry['dialog']) || isset($entry['drawer'])) { + unset($entry['link']); + } + + return array_filter($entry); + } + + /** + * Returns all menu entries + */ + public function entries(): array + { + $entries = []; + $areas = $this->areas(); + + foreach ($areas as $area) { + if ($area === '-') { + $entries[] = '-'; + } elseif ($entry = $this->entry($area)) { + $entries[] = $entry; + } + } + + $entries[] = '-'; + + return array_merge($entries, $this->options()); + } + + /** + * Checks if the access permission to a specific area is granted. + * Defaults to allow access. + * @internal + */ + public function hasPermission(string $id): bool + { + return $this->permissions['access'][$id] ?? true; + } + + /** + * Whether the menu entry should receive aria-current + * @internal + */ + public function isCurrent( + string $id, + bool|Closure|null $callback = null + ): bool { + if ($callback !== null) { + if ($callback instanceof Closure) { + $callback = $callback($this->current); + } + + return $callback; + } + + return $this->current === $id; + } + + /** + * Default options entries for bottom of menu + * @internal + */ + public function options(): array + { + $options = [ + [ + 'icon' => 'edit-line', + 'dialog' => 'changes', + 'text' => I18n::translate('changes'), + ], + [ + 'current' => $this->isCurrent('account'), + 'icon' => 'account', + 'link' => 'account', + 'disabled' => $this->hasPermission('account') === false, + 'text' => I18n::translate('view.account'), + ], + [ + 'icon' => 'logout', + 'link' => 'logout', + 'text' => I18n::translate('logout') + ] + ]; + + return $options; + } +} diff --git a/kirby/src/Panel/Model.php b/kirby/src/Panel/Model.php index 83a1621..7338405 100644 --- a/kirby/src/Panel/Model.php +++ b/kirby/src/Panel/Model.php @@ -22,11 +22,9 @@ use Kirby\Toolkit\A; */ abstract class Model { - protected ModelWithContent $model; - - public function __construct(ModelWithContent $model) - { - $this->model = $model; + public function __construct( + protected ModelWithContent $model + ) { } /** @@ -85,9 +83,10 @@ abstract class Model public function dropdownOption(): array { return [ - 'icon' => 'page', - 'link' => $this->url(), - 'text' => $this->model->id(), + 'icon' => 'page', + 'image' => $this->image(['back' => 'black']), + 'link' => $this->url(true), + 'text' => $this->model->id(), ]; } @@ -107,14 +106,10 @@ abstract class Model // skip image thumbnail if option // is explicitly set to show the icon if ($settings === 'icon') { - $settings = [ - 'query' => false - ]; + $settings = ['query' => false]; } elseif (is_string($settings) === true) { // convert string settings to proper array - $settings = [ - 'query' => $settings - ]; + $settings = ['query' => $settings]; } // merge with defaults and blueprint option @@ -128,35 +123,10 @@ abstract class Model // main url $settings['url'] = $image->url(); - // only create srcsets for resizable files if ($image->isResizable() === true) { - $settings['src'] = static::imagePlaceholder(); - - $sizes = match ($layout) { - 'cards' => [352, 864, 1408], - 'cardlets' => [96, 192], - default => [38, 76] - }; - - if ( - ($settings['cover'] ?? false) === false || - $layout === 'cards' - ) { - $settings['srcset'] = $image->srcset($sizes); - } else { - $settings['srcset'] = $image->srcset([ - '1x' => [ - 'width' => $sizes[0], - 'height' => $sizes[0], - 'crop' => 'center' - ], - '2x' => [ - 'width' => $sizes[1], - 'height' => $sizes[1], - 'crop' => 'center' - ] - ]); - } + // only create srcsets for resizable files + $settings['src'] = static::imagePlaceholder(); + $settings['srcset'] = $this->imageSrcset($image, $layout, $settings); } elseif ($image->isViewable() === true) { $settings['src'] = $image->url(); } @@ -183,8 +153,7 @@ abstract class Model 'back' => 'pattern', 'color' => 'gray-500', 'cover' => false, - 'icon' => 'page', - 'ratio' => '3/2', + 'icon' => 'page' ]; } @@ -217,14 +186,85 @@ abstract class Model return null; } + /** + * Provides the correct srcset string based on + * the layout and settings + * @internal + */ + protected function imageSrcset( + CmsFile|Asset $image, + string $layout, + array $settings + ): string|null { + // depending on layout type, set different sizes + // to have multiple options for the srcset attribute + $sizes = match ($layout) { + 'cards' => [352, 864, 1408], + 'cardlets' => [96, 192], + default => [38, 76] + }; + + // no additional modfications needed if `cover: false` + if (($settings['cover'] ?? false) === false) { + return $image->srcset($sizes); + } + + // for card layouts with `cover: true` provide + // crops based on the card ratio + if ($layout === 'cards') { + $ratio = explode('/', $settings['ratio'] ?? '1/1'); + $ratio = $ratio[0] / $ratio[1]; + + return $image->srcset([ + $sizes[0] . 'w' => [ + 'width' => $sizes[0], + 'height' => round($sizes[0] / $ratio), + 'crop' => true + ], + $sizes[1] . 'w' => [ + 'width' => $sizes[1], + 'height' => round($sizes[1] / $ratio), + 'crop' => true + ], + $sizes[2] . 'w' => [ + 'width' => $sizes[2], + 'height' => round($sizes[2] / $ratio), + 'crop' => true + ] + ]); + } + + // for list and cardlets with `cover: true` + // provide square crops in two resolutions + return $image->srcset([ + '1x' => [ + 'width' => $sizes[0], + 'height' => $sizes[0], + 'crop' => true + ], + '2x' => [ + 'width' => $sizes[1], + 'height' => $sizes[1], + 'crop' => true + ] + ]); + } + /** * Checks for disabled dropdown options according * to the given permissions */ - public function isDisabledDropdownOption(string $action, array $options, array $permissions): bool - { + public function isDisabledDropdownOption( + string $action, + array $options, + array $permissions + ): bool { $option = $options[$action] ?? true; - return $permissions[$action] === false || $option === false || $option === 'false'; + + return + $permissions[$action] === false || + $option === false || + $option === 'false'; } /** @@ -235,11 +275,7 @@ abstract class Model */ public function lock(): array|false { - if ($lock = $this->model->lock()) { - return $lock->toArray(); - } - - return false; + return $this->model->lock()?->toArray() ?? false; } /** @@ -320,33 +356,34 @@ abstract class Model } /** - * Returns link url and tooltip - * for model (e.g. used for prev/next - * navigation) + * Returns link url and title + * for model (e.g. used for prev/next navigation) * @internal */ - public function toLink(string $tooltip = 'title'): array + public function toLink(string $title = 'title'): array { return [ 'link' => $this->url(true), - 'tooltip' => (string)$this->model->{$tooltip}() + 'title' => $title = (string)$this->model->{$title}() ]; } /** - * Returns link url and tooltip + * Returns link url and title * for optional sibling model and * preserves tab selection * * @internal */ - protected function toPrevNextLink(ModelWithContent|null $model = null, string $tooltip = 'title'): array|null - { + protected function toPrevNextLink( + ModelWithContent|null $model = null, + string $title = 'title' + ): array|null { if ($model === null) { return null; } - $data = $model->panel()->toLink($tooltip); + $data = $model->panel()->toLink($title); if ($tab = $model->kirby()->request()->get('tab')) { $uri = new Uri($data['link'], [ diff --git a/kirby/src/Panel/Page.php b/kirby/src/Panel/Page.php index 088822c..72c564c 100644 --- a/kirby/src/Panel/Page.php +++ b/kirby/src/Panel/Page.php @@ -3,6 +3,7 @@ namespace Kirby\Panel; use Kirby\Cms\File as CmsFile; +use Kirby\Cms\ModelWithContent; use Kirby\Filesystem\Asset; use Kirby\Toolkit\I18n; @@ -18,16 +19,24 @@ use Kirby\Toolkit\I18n; */ class Page extends Model { + /** + * @var \Kirby\Cms\Page + */ + protected ModelWithContent $model; + /** * Breadcrumb array */ public function breadcrumb(): array { $parents = $this->model->parents()->flip()->merge($this->model); - return $parents->values(fn ($parent) => [ - 'label' => $parent->title()->toString(), - 'link' => $parent->panel()->url(true), - ]); + + return $parents->values( + fn ($parent) => [ + 'label' => $parent->title()->toString(), + 'link' => $parent->panel()->url(true), + ] + ); } /** @@ -65,9 +74,9 @@ class Page extends Model */ public function dropdown(array $options = []): array { - $page = $this->model; - - $defaults = $page->kirby()->request()->get(['view', 'sort', 'delete']); + $page = $this->model; + $request = $page->kirby()->request(); + $defaults = $request->get(['view', 'sort', 'delete']); $options = array_merge($defaults, $options); $permissions = $this->options(['preview']); @@ -98,15 +107,6 @@ class Page extends Model 'disabled' => $this->isDisabledDropdownOption('changeTitle', $options, $permissions) ]; - $result['duplicate'] = [ - 'dialog' => $url . '/duplicate', - 'icon' => 'copy', - 'text' => I18n::translate('duplicate'), - 'disabled' => $this->isDisabledDropdownOption('duplicate', $options, $permissions) - ]; - - $result[] = '-'; - $result['changeSlug'] = [ 'dialog' => [ 'url' => $url . '/changeTitle', @@ -143,6 +143,23 @@ class Page extends Model ]; $result[] = '-'; + + $result['move'] = [ + 'dialog' => $url . '/move', + 'icon' => 'parent', + 'text' => I18n::translate('page.move'), + 'disabled' => $this->isDisabledDropdownOption('move', $options, $permissions) + ]; + + $result['duplicate'] = [ + 'dialog' => $url . '/duplicate', + 'icon' => 'copy', + 'text' => I18n::translate('duplicate'), + 'disabled' => $this->isDisabledDropdownOption('duplicate', $options, $permissions) + ]; + + $result[] = '-'; + $result['delete'] = [ 'dialog' => $url . '/delete', 'icon' => 'trash', @@ -290,7 +307,7 @@ class Page extends Model ->filter('status', $page->status()); } - return $siblings->filter('isReadable', true); + return $siblings->filter('isListable', true); }; return [ diff --git a/kirby/src/Panel/PageCreateDialog.php b/kirby/src/Panel/PageCreateDialog.php new file mode 100644 index 0000000..68a7bf1 --- /dev/null +++ b/kirby/src/Panel/PageCreateDialog.php @@ -0,0 +1,313 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier + * @license https://getkirby.com/license + */ +class PageCreateDialog +{ + protected PageBlueprint $blueprint; + protected Page $model; + protected Page|Site $parent; + protected string $parentId; + protected string|null $sectionId; + protected string|null $slug; + protected string|null $template; + protected string|null $title; + protected Page|Site $view; + protected string|null $viewId; + + public static array $fieldTypes = [ + 'checkboxes', + 'date', + 'email', + 'info', + 'line', + 'link', + 'list', + 'number', + 'multiselect', + 'radio', + 'range', + 'select', + 'slug', + 'tags', + 'tel', + 'text', + 'toggles', + 'time', + 'url' + ]; + + public function __construct( + string|null $parentId, + string|null $sectionId, + string|null $template, + string|null $viewId, + + // optional + string|null $slug = null, + string|null $title = null, + ) { + $this->parentId = $parentId ?? 'site'; + $this->parent = Find::parent($this->parentId); + $this->sectionId = $sectionId; + $this->slug = $slug; + $this->template = $template; + $this->title = $title; + $this->viewId = $viewId; + $this->view = Find::parent($this->viewId ?? $this->parentId); + } + + /** + * Get the blueprint settings for the new page + */ + public function blueprint(): PageBlueprint + { + // create a temporary page object + return $this->blueprint ??= $this->model()->blueprint(); + } + + /** + * Get an array of all blueprints for the parent view + */ + public function blueprints(): array + { + return A::map( + $this->view->blueprints($this->sectionId), + function ($blueprint) { + $blueprint['name'] ??= $blueprint['value'] ?? null; + return $blueprint; + } + ); + } + + /** + * All the default fields for the dialog + */ + public function coreFields(): array + { + $title = $this->blueprint()->create()['title']['label'] ?? 'title'; + + return [ + 'title' => Field::title([ + 'label' => I18n::translate($title, $title), + 'required' => true, + 'preselect' => true + ]), + 'slug' => Field::slug([ + 'required' => true, + 'sync' => 'title', + 'path' => $this->parent instanceof Page ? '/' . $this->parent->id() . '/' : '/' + ]), + 'parent' => Field::hidden(), + 'section' => Field::hidden(), + 'template' => Field::hidden(), + 'view' => Field::hidden(), + ]; + } + + /** + * Loads custom fields for the page type + */ + public function customFields(): array + { + $custom = []; + $ignore = ['title', 'slug', 'parent', 'template']; + $blueprint = $this->blueprint(); + $fields = $blueprint->fields(); + + foreach ($blueprint->create()['fields'] ?? [] as $name) { + if (!$field = ($fields[$name] ?? null)) { + throw new InvalidArgumentException('Unknown field "' . $name . '" in create dialog'); + } + + if (in_array($field['type'], static::$fieldTypes) === false) { + throw new InvalidArgumentException('Field type "' . $field['type'] . '" not supported in create dialog'); + } + + if (in_array($name, $ignore) === true) { + throw new InvalidArgumentException('Field name "' . $name . '" not allowed as custom field in create dialog'); + } + + // switch all fields to 1/1 + $field['width'] = '1/1'; + + // add the field to the form + $custom[$name] = $field; + } + + // create form so that field props, options etc. + // can be properly resolved + $form = new Form([ + 'fields' => $custom, + 'model' => $this->model(), + 'strict' => true + ]); + + return $form->fields()->toArray(); + } + + /** + * Loads all the fields for the dialog + */ + public function fields(): array + { + return array_merge( + $this->coreFields(), + $this->customFields() + ); + } + + /** + * Provides all the props for the + * dialog, including the fields and + * initial values + */ + public function load(): array + { + $blueprints = $this->blueprints(); + + $this->template ??= $blueprints[0]['name']; + + $status = $this->blueprint()->create()['status'] ?? 'draft'; + $status = $this->blueprint()->status()[$status]['label'] ?? I18n::translate('page.status.' . $status); + + return [ + 'component' => 'k-page-create-dialog', + 'props' => [ + 'blueprints' => $blueprints, + 'fields' => $this->fields(), + 'submitButton' => I18n::template('page.create', [ + 'status' => $status + ]), + 'template' => $this->template, + 'value' => $this->value() + ] + ]; + } + + /** + * Temporary model for the page to + * be created, used to properly render + * the blueprint for fields + */ + public function model(): Page + { + return $this->model ??= Page::factory([ + 'slug' => 'new', + 'template' => $this->template, + 'model' => $this->template, + 'parent' => $this->parent instanceof Page ? $this->parent : null + ]); + } + + /** + * Prepares and cleans up the input data + */ + public function sanitize(array $input): array + { + $input['slug'] ??= $this->slug ?? ''; + $input['title'] ??= $this->title ?? ''; + + $content = [ + 'title' => trim($input['title']), + ]; + + foreach ($this->customFields() as $name => $field) { + $content[$name] = $input[$name] ?? null; + } + + return [ + 'content' => $content, + 'slug' => $input['slug'], + 'template' => $this->template, + ]; + } + + /** + * Submits the dialog form and creates the new page + */ + public function submit(array $input): array + { + $input = $this->sanitize($input); + $status = $this->blueprint()->create()['status'] ?? 'draft'; + + // validate the input before creating the page + $this->validate($input, $status); + + $page = $this->parent->createChild($input); + + if ($status !== 'draft') { + // grant all permissions as the status is set in the blueprint and + // should not be treated as if the user would try to change it + $page->kirby()->impersonate( + 'kirby', + fn () => $page->changeStatus($status) + ); + } + + $payload = [ + 'event' => 'page.create' + ]; + + // add redirect, if not explicitly disabled + if (($this->blueprint()->create()['redirect'] ?? null) !== false) { + $payload['redirect'] = $page->panel()->url(true); + } + + return $payload; + } + + public function validate(array $input, string $status = 'draft'): bool + { + // basic validation + PageRules::validateTitleLength($input['content']['title']); + PageRules::validateSlugLength($input['slug']); + + // if the page is supposed to be published directly, + // ensure that all field validations are met + if ($status !== 'draft') { + // create temporary form to validate the input + $form = Form::for($this->model(), ['values' => $input['content']]); + + if ($form->isInvalid() === true) { + throw new InvalidArgumentException([ + 'key' => 'page.changeStatus.incomplete' + ]); + } + } + + return true; + } + + public function value(): array + { + return [ + 'parent' => $this->parentId, + 'section' => $this->sectionId, + 'slug' => $this->slug ?? '', + 'template' => $this->template, + 'title' => $this->title ?? '', + 'view' => $this->viewId, + ]; + } +} diff --git a/kirby/src/Panel/Panel.php b/kirby/src/Panel/Panel.php index 48f3267..cb3698b 100644 --- a/kirby/src/Panel/Panel.php +++ b/kirby/src/Panel/Panel.php @@ -60,9 +60,15 @@ class Panel $areas = $kirby->load()->areas(); // the system is not ready - if ($system->isOk() === false || $system->isInstalled() === false) { + if ( + $system->isOk() === false || + $system->isInstalled() === false + ) { return [ - 'installation' => static::area('installation', $areas['installation']), + 'installation' => static::area( + 'installation', + $areas['installation'] + ), ]; } @@ -70,7 +76,6 @@ class Panel if (!$user) { return [ 'logout' => static::area('logout', $areas['logout']), - // login area last because it defines a fallback route 'login' => static::area('login', $areas['login']), ]; @@ -85,24 +90,8 @@ class Panel unset($areas['languages']); } - $menu = $kirby->option('panel.menu', [ - 'site', - 'languages', - 'users', - 'system', - ]); - $result = []; - // add the sorted areas - foreach ($menu as $id) { - if ($area = ($areas[$id] ?? null)) { - $result[$id] = static::area($id, $area); - unset($areas[$id]); - } - } - - // add the remaining areas foreach ($areas as $id => $area) { $result[$id] = static::area($id, $area); } @@ -185,7 +174,9 @@ class Panel $request = App::instance()->request(); if ($request->method() === 'GET') { - return (bool)($request->get('_json') ?? $request->header('X-Fiber')); + return + (bool)($request->get('_json') ?? + $request->header('X-Fiber')); } return false; @@ -200,7 +191,7 @@ class Panel $request = App::instance()->request(); return Response::json($data, $code, $request->get('_pretty'), [ - 'X-Fiber' => 'true', + 'X-Fiber' => 'true', 'Cache-Control' => 'no-store, private' ]); } @@ -252,7 +243,9 @@ class Panel // handle different response types (view, dialog, ...) return match ($options['type'] ?? null) { 'dialog' => Dialog::response($result, $options), + 'drawer' => Drawer::response($result, $options), 'dropdown' => Dropdown::response($result, $options), + 'request' => Request::response($result, $options), 'search' => Search::response($result, $options), default => View::response($result, $options) }; @@ -291,7 +284,11 @@ class Panel // call the route action to check the result try { // trigger hook - $route = $kirby->apply('panel.route:before', compact('route', 'path', 'method'), 'route'); + $route = $kirby->apply( + 'panel.route:before', + compact('route', 'path', 'method'), + 'route' + ); // check for access before executing area routes if ($auth !== false) { @@ -310,7 +307,11 @@ class Panel 'type' => $type ]); - return $kirby->apply('panel.route:after', compact('route', 'path', 'method', 'response'), 'response'); + return $kirby->apply( + 'panel.route:after', + compact('route', 'path', 'method', 'response'), + 'response' + ); }); } @@ -341,7 +342,9 @@ class Panel static::routesForViews($areaId, $area), static::routesForSearches($areaId, $area), static::routesForDialogs($areaId, $area), + static::routesForDrawers($areaId, $area), static::routesForDropdowns($areaId, $area), + static::routesForRequests($areaId, $area), ); } @@ -362,7 +365,7 @@ class Panel // catch all route $routes[] = [ 'pattern' => '(:all)', - 'action' => fn () => 'The view could not be found' + 'action' => fn (string $pattern) => 'Could not find Panel view for route: ' . $pattern ]; return $routes; @@ -376,26 +379,33 @@ class Panel $dialogs = $area['dialogs'] ?? []; $routes = []; - foreach ($dialogs as $key => $dialog) { - // create the full pattern with dialogs prefix - $pattern = 'dialogs/' . trim(($dialog['pattern'] ?? $key), '/'); + foreach ($dialogs as $dialogId => $dialog) { + $routes = array_merge($routes, Dialog::routes( + id: $dialogId, + areaId: $areaId, + prefix: 'dialogs', + options: $dialog + )); + } - // load event - $routes[] = [ - 'pattern' => $pattern, - 'type' => 'dialog', - 'area' => $areaId, - 'action' => $dialog['load'] ?? fn () => 'The load handler for your dialog is missing' - ]; + return $routes; + } - // submit event - $routes[] = [ - 'pattern' => $pattern, - 'type' => 'dialog', - 'area' => $areaId, - 'method' => 'POST', - 'action' => $dialog['submit'] ?? fn () => 'Your dialog does not define a submit handler' - ]; + /** + * Extract all routes from an area + */ + public static function routesForDrawers(string $areaId, array $area): array + { + $drawers = $area['drawers'] ?? []; + $routes = []; + + foreach ($drawers as $drawerId => $drawer) { + $routes = array_merge($routes, Drawer::routes( + id: $drawerId, + areaId: $areaId, + prefix: 'drawers', + options: $drawer + )); } return $routes; @@ -409,27 +419,28 @@ class Panel $dropdowns = $area['dropdowns'] ?? []; $routes = []; - foreach ($dropdowns as $name => $dropdown) { - // Handle shortcuts for dropdowns. The name is the pattern - // and options are defined in a Closure - if ($dropdown instanceof Closure) { - $dropdown = [ - 'pattern' => $name, - 'action' => $dropdown - ]; - } + foreach ($dropdowns as $dropdownId => $dropdown) { + $routes = array_merge($routes, Dropdown::routes( + id: $dropdownId, + areaId: $areaId, + prefix: 'dropdowns', + options: $dropdown + )); + } - // create the full pattern with dropdowns prefix - $pattern = 'dropdowns/' . trim(($dropdown['pattern'] ?? $name), '/'); + return $routes; + } - // load event - $routes[] = [ - 'pattern' => $pattern, - 'type' => 'dropdown', - 'area' => $areaId, - 'method' => 'GET|POST', - 'action' => $dropdown['options'] ?? $dropdown['action'] - ]; + /** + * Extract all routes from an area + */ + public static function routesForRequests(string $areaId, array $area): array + { + $routes = $area['requests'] ?? []; + + foreach ($routes as $key => $route) { + $routes[$key]['area'] = $areaId; + $routes[$key]['type'] = 'request'; } return $routes; @@ -453,9 +464,13 @@ class Panel 'type' => 'search', 'area' => $areaId, 'action' => function () use ($params) { - $request = App::instance()->request(); + $kirby = App::instance(); + $request = $kirby->request(); + $query = $request->get('query'); + $limit = (int)$request->get('limit', $kirby->option('panel.search.limit', 10)); + $page = (int)$request->get('page', 1); - return $params['query']($request->get('query')); + return $params['query']($query, $limit, $page); } ]; } @@ -474,7 +489,18 @@ class Panel foreach ($views as $view) { $view['area'] = $areaId; $view['type'] = 'view'; - $routes[] = $view; + + $when = $view['when'] ?? null; + unset($view['when']); + + // enable the route by default, but if there is a + // when condition closure, it must return `true` + if ( + $when instanceof Closure === false || + $when($view, $area) === true + ) { + $routes[] = $view; + } } return $routes; @@ -537,7 +563,7 @@ class Panel * Creates an absolute Panel URL * independent of the Panel slug config */ - public static function url(string|null $url = null): string + public static function url(string|null $url = null, array $options = []): string { // only touch relative paths if (Url::isAbsolute($url) === false) { @@ -559,7 +585,7 @@ class Panel } // create an absolute URL - $url = CmsUrl::to($path); + $url = CmsUrl::to($path, $options); } return $url; diff --git a/kirby/src/Panel/Request.php b/kirby/src/Panel/Request.php new file mode 100644 index 0000000..9656d7d --- /dev/null +++ b/kirby/src/Panel/Request.php @@ -0,0 +1,24 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier + * @license https://getkirby.com/license + */ +class Request +{ + /** + * Renders request responses + */ + public static function response($data, array $options = []): Response + { + $data = Json::responseData($data); + return Panel::json($data, $data['code'] ?? 200); + } +} diff --git a/kirby/src/Panel/Search.php b/kirby/src/Panel/Search.php index 984d38d..f9b4295 100644 --- a/kirby/src/Panel/Search.php +++ b/kirby/src/Panel/Search.php @@ -22,9 +22,17 @@ class Search extends Json public static function response($data, array $options = []): Response { - if (is_array($data) === true) { + if ( + is_array($data) === true && + array_key_exists('results', $data) === false + ) { $data = [ - 'results' => $data + 'results' => $data, + 'pagination' => [ + 'page' => 1, + 'limit' => $total = count($data), + 'total' => $total + ] ]; } diff --git a/kirby/src/Panel/Site.php b/kirby/src/Panel/Site.php index fc32b9d..68f7892 100644 --- a/kirby/src/Panel/Site.php +++ b/kirby/src/Panel/Site.php @@ -3,6 +3,7 @@ namespace Kirby\Panel; use Kirby\Cms\File as CmsFile; +use Kirby\Cms\ModelWithContent; use Kirby\Filesystem\Asset; /** @@ -17,6 +18,11 @@ use Kirby\Filesystem\Asset; */ class Site extends Model { + /** + * @var \Kirby\Cms\Site + */ + protected ModelWithContent $model; + /** * Returns the setup for a dropdown option * which is used in the changes dropdown diff --git a/kirby/src/Panel/User.php b/kirby/src/Panel/User.php index 3b8ae2e..e0a5149 100644 --- a/kirby/src/Panel/User.php +++ b/kirby/src/Panel/User.php @@ -3,6 +3,7 @@ namespace Kirby\Panel; use Kirby\Cms\File as CmsFile; +use Kirby\Cms\ModelWithContent; use Kirby\Cms\Translation; use Kirby\Cms\Url; use Kirby\Filesystem\Asset; @@ -20,6 +21,11 @@ use Kirby\Toolkit\I18n; */ class User extends Model { + /** + * @var \Kirby\Cms\User + */ + protected ModelWithContent $model; + /** * Breadcrumb array */ @@ -67,6 +73,15 @@ class User extends Model 'disabled' => $this->isDisabledDropdownOption('changeRole', $options, $permissions) ]; + $result[] = [ + 'dialog' => $url . '/changeLanguage', + 'icon' => 'translate', + 'text' => I18n::translate('user.changeLanguage'), + 'disabled' => $this->isDisabledDropdownOption('changeLanguage', $options, $permissions) + ]; + + $result[] = '-'; + $result[] = [ 'dialog' => $url . '/changePassword', 'icon' => 'key', @@ -74,12 +89,23 @@ class User extends Model 'disabled' => $this->isDisabledDropdownOption('changePassword', $options, $permissions) ]; - $result[] = [ - 'dialog' => $url . '/changeLanguage', - 'icon' => 'globe', - 'text' => I18n::translate('user.changeLanguage'), - 'disabled' => $this->isDisabledDropdownOption('changeLanguage', $options, $permissions) - ]; + if ($this->model->kirby()->system()->is2FAWithTOTP() === true) { + if ($account || $this->model->kirby()->user()->isAdmin()) { + if ($this->model->secret('totp') !== null) { + $result[] = [ + 'dialog' => $url . '/totp/disable', + 'icon' => 'qr-code', + 'text' => I18n::translate('login.totp.disable.option'), + ]; + } elseif ($account) { + $result[] = [ + 'dialog' => $url . '/totp/enable', + 'icon' => 'qr-code', + 'text' => I18n::translate('login.totp.enable.option') + ]; + } + } + } $result[] = '-'; @@ -194,7 +220,6 @@ class User extends Model { $user = $this->model; $account = $user->isLoggedIn(); - $avatar = $user->avatar(); return array_merge( parent::props(), @@ -203,7 +228,7 @@ class User extends Model 'blueprint' => $this->model->role()->name(), 'model' => [ 'account' => $account, - 'avatar' => $avatar ? $avatar->url() : null, + 'avatar' => $user->avatar()?->url(), 'content' => $this->content(), 'email' => $user->email(), 'id' => $user->id(), diff --git a/kirby/src/Panel/UserTotpDisableDialog.php b/kirby/src/Panel/UserTotpDisableDialog.php new file mode 100644 index 0000000..7050cc7 --- /dev/null +++ b/kirby/src/Panel/UserTotpDisableDialog.php @@ -0,0 +1,114 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier + * @license https://getkirby.com/license + */ +class UserTotpDisableDialog +{ + public App $kirby; + public User $user; + + public function __construct( + string|null $id = null + ) { + $this->kirby = App::instance(); + $this->user = $id ? Find::user($id) : $this->kirby->user(); + } + + /** + * Returns the Panel dialog state when opening the dialog + */ + public function load(): array + { + $currentUser = $this->kirby->user(); + $submitBtn = [ + 'text' => I18n::translate('disable'), + 'icon' => 'protected', + 'theme' => 'negative' + ]; + + // admins can disable TOTP for other users without + // entering their password (but not for themselves) + if ( + $currentUser->isAdmin() === true && + $currentUser->is($this->user) === false + ) { + $name = $this->user->name()->or($this->user->email()); + + return [ + 'component' => 'k-remove-dialog', + 'props' => [ + 'text' => I18n::template('login.totp.disable.admin', ['user' => Escape::html($name)]), + 'submitButton' => $submitBtn, + ] + ]; + } + + // everybody else + return [ + 'component' => 'k-form-dialog', + 'props' => [ + 'fields' => [ + 'password' => [ + 'type' => 'password', + 'required' => true, + 'counter' => false, + 'label' => I18n::translate('login.totp.disable.label'), + 'help' => I18n::translate('login.totp.disable.help'), + ] + ], + 'submitButton' => $submitBtn, + ] + ]; + } + + /** + * Removes the user's TOTP secret when the dialog is submitted + */ + public function submit(): array + { + $password = $this->kirby->request()->get('password'); + + try { + if ($this->kirby->user()->is($this->user) === true) { + $this->user->validatePassword($password); + } elseif ($this->kirby->user()->isAdmin() === false) { + throw new PermissionException('You are not allowed to disable TOTP for other users'); + } + + // Remove the TOTP secret from the account + $this->user->changeTotp(null); + + return [ + 'message' => I18n::translate('login.totp.disable.success') + ]; + } catch (InvalidArgumentException $e) { + // Catch and re-throw exception so that any + // Unauthenticated exception for incorrect passwords + // does not trigger a logout + throw new InvalidArgumentException([ + 'key' => $e->getKey(), + 'data' => $e->getData(), + 'fallback' => $e->getMessage(), + 'previous' => $e + ]); + } + } +} diff --git a/kirby/src/Panel/UserTotpEnableDialog.php b/kirby/src/Panel/UserTotpEnableDialog.php new file mode 100644 index 0000000..e2917db --- /dev/null +++ b/kirby/src/Panel/UserTotpEnableDialog.php @@ -0,0 +1,95 @@ + + * @link https://getkirby.com + * @copyright Bastian Allgeier + * @license https://getkirby.com/license + */ +class UserTotpEnableDialog +{ + public App $kirby; + public Totp $totp; + public User $user; + + public function __construct() + { + $this->kirby = App::instance(); + $this->user = $this->kirby->user(); + } + + /** + * Returns the Panel dialog state when opening the dialog + */ + public function load(): array + { + return [ + 'component' => 'k-totp-dialog', + 'props' => [ + 'qr' => $this->qr()->toSvg(size: '100%'), + 'value' => ['secret' => $this->secret()] + ] + ]; + } + + /** + * Creates a QR code with a new TOTP secret for the user + */ + public function qr(): QrCode + { + $issuer = $this->kirby->site()->title(); + $label = $this->user->email(); + $uri = $this->totp()->uri($issuer, $label); + return new QrCode($uri); + } + + public function secret(): string + { + return $this->totp()->secret(); + } + + /** + * Changes the user's TOTP secret when the dialog is submitted + */ + public function submit(): array + { + $secret = $this->kirby->request()->get('secret'); + $confirm = $this->kirby->request()->get('confirm'); + + if ($confirm === null) { + throw new InvalidArgumentException( + ['key' => 'login.totp.confirm.missing'] + ); + } + + if ($this->totp($secret)->verify($confirm) === false) { + throw new InvalidArgumentException( + ['key' => 'login.totp.confirm.invalid'] + ); + } + + $this->user->changeTotp($secret); + + return [ + 'message' => I18n::translate('login.totp.enable.success') + ]; + } + + public function totp(string|null $secret = null): Totp + { + return $this->totp ??= new Totp($secret); + } +} diff --git a/kirby/src/Panel/View.php b/kirby/src/Panel/View.php index 328fd0a..a2fcebc 100644 --- a/kirby/src/Panel/View.php +++ b/kirby/src/Panel/View.php @@ -2,12 +2,10 @@ namespace Kirby\Panel; -use Closure; use Kirby\Cms\App; use Kirby\Exception\Exception; use Kirby\Http\Response; use Kirby\Toolkit\A; -use Kirby\Toolkit\I18n; use Kirby\Toolkit\Str; use Throwable; @@ -40,7 +38,9 @@ class View return static::applyOnly($data, $only); } - $globals = $request->header('X-Fiber-Globals') ?? $request->get('_globals'); + $globals = + $request->header('X-Fiber-Globals') ?? + $request->get('_globals'); if (empty($globals) === false) { return static::applyGlobals($data, $globals); @@ -56,8 +56,10 @@ class View * A global request can be activated with the `X-Fiber-Globals` header or the * `_globals` query parameter. */ - public static function applyGlobals(array $data, string|null $globals = null): array - { + public static function applyGlobals( + array $data, + string|null $globals = null + ): array { // split globals string into an array of fields $globalKeys = Str::split($globals, ','); @@ -86,8 +88,10 @@ class View * Such requests can fetch shared data or globals. * Globals will be loaded on demand. */ - public static function applyOnly(array $data, string|null $only = null): array - { + public static function applyOnly( + array $data, + string|null $only = null + ): array { // split include string into an array of fields $onlyKeys = Str::split($only, ','); @@ -115,9 +119,7 @@ class View } // Nest dotted keys in array but ignore $translation - return A::nest($result, [ - '$translation' - ]); + return A::nest($result, ['$translation']); } /** @@ -146,14 +148,18 @@ class View return [ '$direction' => function () use ($kirby, $multilang, $language, $user) { if ($multilang === true && $language && $user) { - $isDefault = $language->direction() === $kirby->defaultLanguage()->direction(); - $isFromUser = $language->code() === $user->language(); + $default = $kirby->defaultLanguage(); - if ($isDefault === false && $isFromUser === false) { + if ( + $language->direction() !== $default->direction() && + $language->code() !== $user->language() + ) { return $language->direction(); } } }, + '$dialog' => null, + '$drawer' => null, '$language' => function () use ($kirby, $multilang, $language) { if ($multilang === true && $language) { return [ @@ -178,15 +184,20 @@ class View return []; }, - '$menu' => function () use ($options, $permissions) { - return static::menu($options['areas'] ?? [], $permissions, $options['area']['id'] ?? null); + '$menu' => function () use ($options, $permissions) { + $menu = new Menu( + $options['areas'] ?? [], + $permissions, + $options['area']['id'] ?? null + ); + return $menu->entries(); }, '$permissions' => $permissions, - '$license' => (bool)$kirby->system()->license(), - '$multilang' => $multilang, - '$searches' => static::searches($options['areas'] ?? [], $permissions), - '$url' => $kirby->request()->url()->toString(), - '$user' => function () use ($user) { + '$license' => $kirby->system()->license()->status()->value(), + '$multilang' => $multilang, + '$searches' => static::searches($options['areas'] ?? [], $permissions), + '$url' => $kirby->request()->url()->toString(), + '$user' => function () use ($user) { if ($user) { return [ 'email' => $user->email(), @@ -204,17 +215,25 @@ class View 'breadcrumb' => [], 'code' => 200, 'path' => Str::after($kirby->path(), '/'), - 'timestamp' => (int)(microtime(true) * 1000), 'props' => [], - 'search' => $kirby->option('panel.search.type', 'pages') + 'query' => App::instance()->request()->query()->toArray(), + 'referrer' => Panel::referrer(), + 'search' => $kirby->option('panel.search.type', 'pages'), + 'timestamp' => (int)(microtime(true) * 1000), ]; - $view = array_replace_recursive($defaults, $options['area'] ?? [], $view); + $view = array_replace_recursive( + $defaults, + $options['area'] ?? [], + $view + ); // make sure that views and dialogs are gone unset( $view['dialogs'], + $view['drawers'], $view['dropdowns'], + $view['requests'], $view['searches'], $view['views'] ); @@ -255,17 +274,11 @@ class View $kirby = App::instance(); return [ - '$config' => function () use ($kirby) { - return [ - 'debug' => $kirby->option('debug', false), - 'kirbytext' => $kirby->option('panel.kirbytext', true), - 'search' => [ - 'limit' => $kirby->option('panel.search.limit', 10), - 'type' => $kirby->option('panel.search.type', 'pages') - ], - 'translation' => $kirby->option('panel.language', 'en'), - ]; - }, + '$config' => fn () => [ + 'debug' => $kirby->option('debug', false), + 'kirbytext' => $kirby->option('panel.kirbytext', true), + 'translation' => $kirby->option('panel.language', 'en'), + ], '$system' => function () use ($kirby) { $locales = []; @@ -303,67 +316,6 @@ class View ]; } - /** - * Creates the menu for the topbar - */ - public static function menu(array|null $areas = [], array|null $permissions = [], string|null $current = null): array - { - $menu = []; - - // areas - foreach ($areas as $areaId => $area) { - $access = $permissions['access'][$areaId] ?? true; - - // areas without access permissions get skipped entirely - if ($access === false) { - continue; - } - - // fetch custom menu settings from the area definition - $menuSetting = $area['menu'] ?? false; - - // menu settings can be a callback that can return true, false or disabled - if ($menuSetting instanceof Closure) { - $menuSetting = $menuSetting($areas, $permissions, $current); - } - - // false will remove the area entirely just like with - // disabled permissions - if ($menuSetting === false) { - continue; - } - - $menu[] = [ - 'current' => $areaId === $current, - 'disabled' => $menuSetting === 'disabled', - 'icon' => $area['icon'], - 'id' => $areaId, - 'link' => $area['link'], - 'text' => $area['label'], - ]; - } - - $menu[] = '-'; - $menu[] = [ - 'current' => $current === 'account', - 'icon' => 'account', - 'id' => 'account', - 'link' => 'account', - 'disabled' => ($permissions['access']['account'] ?? false) === false, - 'text' => I18n::translate('view.account'), - ]; - $menu[] = '-'; - - // logout - $menu[] = [ - 'icon' => 'logout', - 'id' => 'logout', - 'link' => 'logout', - 'text' => I18n::translate('logout') - ]; - return $menu; - } - /** * Renders the main panel view either as * JSON response or full HTML document based @@ -414,13 +366,17 @@ class View { $searches = []; - foreach ($areas as $area) { - foreach ($area['searches'] ?? [] as $id => $params) { - $searches[$id] = [ - 'icon' => $params['icon'] ?? 'search', - 'label' => $params['label'] ?? Str::ucfirst($id), - 'id' => $id - ]; + foreach ($areas as $areaId => $area) { + // by default, all areas are accessible unless + // the permissions are explicitly set to false + if (($permissions['access'][$areaId] ?? true) !== false) { + foreach ($area['searches'] ?? [] as $id => $params) { + $searches[$id] = [ + 'icon' => $params['icon'] ?? 'search', + 'label' => $params['label'] ?? Str::ucfirst($id), + 'id' => $id + ]; + } } } return $searches; diff --git a/kirby/src/Parsley/Element.php b/kirby/src/Parsley/Element.php index 2deae25..649c5a3 100644 --- a/kirby/src/Parsley/Element.php +++ b/kirby/src/Parsley/Element.php @@ -21,22 +21,21 @@ use Kirby\Toolkit\Str; */ class Element { - protected array $marks; - protected DOMElement $node; - - public function __construct(DOMElement $node, array $marks = []) - { - $this->marks = $marks; - $this->node = $node; + public function __construct( + protected DOMElement $node, + protected array $marks = [] + ) { } /** * The returns the attribute value or * the given fallback if the attribute does not exist */ - public function attr(string $attr, string|null $fallback = null): string|null - { - if ($this->node->hasAttribute($attr)) { + public function attr( + string $attr, + string|null $fallback = null + ): string|null { + if ($this->node->hasAttribute($attr) === true) { return $this->node->getAttribute($attr) ?? $fallback; } @@ -112,7 +111,9 @@ class Element */ public function innerHtml(array|null $marks = null): string { - return (new Inline($this->node, $marks ?? $this->marks))->innerHtml(); + $marks ??= $this->marks; + $inline = new Inline($this->node, $marks); + return $inline->innerHtml(); } /** diff --git a/kirby/src/Parsley/Inline.php b/kirby/src/Parsley/Inline.php index 8c6a08e..32e6b08 100644 --- a/kirby/src/Parsley/Inline.php +++ b/kirby/src/Parsley/Inline.php @@ -2,7 +2,7 @@ namespace Kirby\Parsley; -use DOMComment; +use DOMElement; use DOMNode; use DOMNodeList; use DOMText; @@ -52,21 +52,22 @@ class Inline } /** - * Get all allowed attributes for a DOMNode + * Get all allowed attributes for a DOMElement * as clean array */ - public static function parseAttrs(DOMNode $node, array $marks = []): array - { + public static function parseAttrs( + DOMElement $node, + array $marks = [] + ): array { $attrs = []; $mark = $marks[$node->tagName]; $defaults = $mark['defaults'] ?? []; foreach ($mark['attrs'] ?? [] as $attr) { - if ($node->hasAttribute($attr)) { - $attrs[$attr] = $node->getAttribute($attr); - } else { - $attrs[$attr] = $defaults[$attr] ?? null; - } + $attrs[$attr] = match ($node->hasAttribute($attr)) { + true => $node->getAttribute($attr), + default => $defaults[$attr] ?? null + }; } return $attrs; @@ -76,8 +77,10 @@ class Inline * Parses all children and creates clean HTML * for each of them. */ - public static function parseChildren(DOMNodeList $children, array $marks): string - { + public static function parseChildren( + DOMNodeList $children, + array $marks + ): string { $html = ''; foreach ($children as $child) { $html .= static::parseNode($child, $marks); @@ -89,8 +92,10 @@ class Inline * Go through all child elements and create * clean inner HTML for them */ - public static function parseInnerHtml(DOMNode $node, array $marks = []): string|null - { + public static function parseInnerHtml( + DOMElement $node, + array $marks = [] + ): string|null { $html = static::parseChildren($node->childNodes, $marks); // trim the inner HTML for paragraphs @@ -115,33 +120,35 @@ class Inline return Html::encode($node->textContent); } - // ignore comments - if ($node instanceof DOMComment) { - return null; + if ($node instanceof DOMElement) { + // unknown marks + if (array_key_exists($node->tagName, $marks) === false) { + return static::parseChildren($node->childNodes, $marks); + } + + // collect all allowed attributes + $attrs = static::parseAttrs($node, $marks); + + // close self-closing elements + if (Html::isVoid($node->tagName) === true) { + return '<' . $node->tagName . Html::attr($attrs, null, ' ') . ' />'; + } + + $innerHtml = static::parseInnerHtml($node, $marks); + + // skip empty paragraphs + if ($innerHtml === null && $node->tagName === 'p') { + return null; + } + + // create the outer html for the element + $html = '<' . $node->tagName . Html::attr($attrs, null, ' ') . '>'; + $html .= $innerHtml; + $html .= 'tagName . '>'; + return $html; } - // unknown marks - if (array_key_exists($node->tagName, $marks) === false) { - return static::parseChildren($node->childNodes, $marks); - } - - // collect all allowed attributes - $attrs = static::parseAttrs($node, $marks); - - // close self-closing elements - if (Html::isVoid($node->tagName) === true) { - return '<' . $node->tagName . Html::attr($attrs, null, ' ') . ' />'; - } - - $innerHtml = static::parseInnerHtml($node, $marks); - - // skip empty paragraphs - if ($innerHtml === null && $node->tagName === 'p') { - return null; - } - - // create the outer html for the element - return '<' . $node->tagName . Html::attr($attrs, null, ' ') . '>' . $innerHtml . 'tagName . '>'; + return null; } /** diff --git a/kirby/src/Parsley/Parsley.php b/kirby/src/Parsley/Parsley.php index 460ecfb..3f2242e 100644 --- a/kirby/src/Parsley/Parsley.php +++ b/kirby/src/Parsley/Parsley.php @@ -40,10 +40,8 @@ class Parsley // or should be skipped if ($this->useXmlExtension() === false) { $this->blocks[] = [ - 'type' => 'markdown', - 'content' => [ - 'text' => $html, - ] + 'type' => 'markdown', + 'content' => ['text' => $html] ]; return; } @@ -104,7 +102,10 @@ class Parsley } foreach ($element->childNodes as $childNode) { - if ($this->isBlock($childNode) === true || $this->containsBlock($childNode)) { + if ( + $this->isBlock($childNode) === true || + $this->containsBlock($childNode) + ) { return true; } } @@ -129,7 +130,7 @@ class Parsley $html = []; foreach ($this->inline as $inline) { - $node = new Inline($inline, $this->marks); + $node = new Inline($inline, $this->marks); $html[] = $node->innerHTML(); } @@ -161,11 +162,11 @@ class Parsley */ public function isBlock(DOMNode $element): bool { - if ($element instanceof DOMElement === false) { - return false; + if ($element instanceof DOMElement) { + return array_key_exists($element->tagName, $this->nodes) === true; } - return array_key_exists($element->tagName, $this->nodes) === true; + return false; } /** @@ -204,7 +205,11 @@ class Parsley $lastItem = $this->blocks[$lastIndex] ?? null; // merge with previous block - if ($block['type'] === 'text' && $lastItem && $lastItem['type'] === 'text') { + if ( + $block['type'] === 'text' && + $lastItem && + $lastItem['type'] === 'text' + ) { $this->blocks[$lastIndex]['content']['text'] .= ' ' . $block['content']['text']; // append @@ -227,15 +232,18 @@ class Parsley } // inline context - if ($this->isInline($element)) { + if ($this->isInline($element) === true) { $this->inline[] = $element; return true; - } else { - $this->endInlineBlock(); } + $this->endInlineBlock(); + // known block nodes if ($this->isBlock($element) === true) { + /** + * @var DOMElement $element + */ if ($parser = ($this->nodes[$element->tagName]['parse'] ?? null)) { if ($result = $parser(new Element($element, $this->marks))) { $this->blocks[] = $result; @@ -246,6 +254,9 @@ class Parsley // has only unknown children (div, etc.) if ($this->containsBlock($element) === false) { + /** + * @var DOMElement $element + */ if (in_array($element->tagName, $this->skip) === true) { return false; } diff --git a/kirby/src/Parsley/Schema/Blocks.php b/kirby/src/Parsley/Schema/Blocks.php index 1f54619..a72106c 100644 --- a/kirby/src/Parsley/Schema/Blocks.php +++ b/kirby/src/Parsley/Schema/Blocks.php @@ -23,8 +23,7 @@ class Blocks extends Plain { public function blockquote(Element $node): array { - $citation = null; - $text = []; + $text = []; // get all the text for the quote foreach ($node->children() as $child) { @@ -36,7 +35,8 @@ class Blocks extends Plain $child instanceof DOMElement && $child->tagName !== 'footer' ) { - $text[] = (new Element($child))->innerHTML($this->marks()); + $element = new Element($child); + $text[] = $element->innerHTML($this->marks()); } } @@ -44,9 +44,7 @@ class Blocks extends Plain $text = implode('', array_filter($text)); // get the citation from the footer - if ($footer = $node->find('footer')) { - $citation = $footer->innerHTML($this->marks()); - } + $citation = $node->find('footer')?->innerHTML($this->marks()); return [ 'content' => [ @@ -115,15 +113,12 @@ class Blocks extends Plain public function iframe(Element $node): array { - $caption = null; - $src = $node->attr('src'); + $src = $node->attr('src'); + $figcaption = $node->find('ancestor::figure[1]//figcaption'); + $caption = $figcaption?->innerHTML($this->marks()); - if ($figcaption = $node->find('ancestor::figure[1]//figcaption')) { - $caption = $figcaption->innerHTML($this->marks()); - - // avoid parsing the caption twice - $figcaption->remove(); - } + // avoid parsing the caption twice + $figcaption?->remove(); // reverse engineer video URLs if (preg_match('!player.vimeo.com\/video\/([0-9]+)!i', $src, $array) === 1) { @@ -157,19 +152,12 @@ class Blocks extends Plain public function img(Element $node): array { - $caption = null; - $link = null; + $link = $node->find('ancestor::a')?->attr('href'); + $figcaption = $node->find('ancestor::figure[1]//figcaption'); + $caption = $figcaption?->innerHTML($this->marks()); - if ($figcaption = $node->find('ancestor::figure[1]//figcaption')) { - $caption = $figcaption->innerHTML($this->marks()); - - // avoid parsing the caption twice - $figcaption->remove(); - } - - if ($a = $node->find('ancestor::a')) { - $link = $a->attr('href'); - } + // avoid parsing the caption twice + $figcaption?->remove(); return [ 'content' => [ @@ -198,19 +186,21 @@ class Blocks extends Plain $innerHtml .= $child->textContent; } elseif ($child instanceof DOMElement) { $child = new Element($child); - - if (in_array($child->tagName(), ['ul', 'ol']) === true) { - $innerHtml .= $this->list($child); - } else { - $innerHtml .= $child->innerHTML($this->marks()); - } + $list = ['ul', 'ol']; + $innerHtml .= match (in_array($child->tagName(), $list)) { + true => $this->list($child), + default => $child->innerHTML($this->marks()) + }; } } $html[] = '
    • ' . trim($innerHtml) . '
    • '; } - return '<' . $node->tagName() . '>' . implode($html) . 'tagName() . '>'; + $outerHtml = '<' . $node->tagName() . '>'; + $outerHtml .= implode($html); + $outerHtml .= 'tagName() . '>'; + return $outerHtml; } /** diff --git a/kirby/src/Query/Expression.php b/kirby/src/Query/Expression.php index e9ff509..f205c30 100644 --- a/kirby/src/Query/Expression.php +++ b/kirby/src/Query/Expression.php @@ -62,7 +62,7 @@ class Expression return preg_split( '/\s+([\?\:]+)\s+|' . Arguments::OUTSIDE . '/', trim($string), - flags: PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY + flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ); } diff --git a/kirby/src/Query/Query.php b/kirby/src/Query/Query.php index 5469116..ab6381c 100644 --- a/kirby/src/Query/Query.php +++ b/kirby/src/Query/Query.php @@ -9,6 +9,7 @@ use Kirby\Cms\File; use Kirby\Cms\Page; use Kirby\Cms\Site; use Kirby\Cms\User; +use Kirby\Image\QrCode; use Kirby\Toolkit\I18n; /** @@ -120,6 +121,10 @@ Query::$entries['page'] = function (string $id): Page|null { return App::instance()->page($id); }; +Query::$entries['qr'] = function (string $data): QrCode { + return new QrCode($data); +}; + Query::$entries['site'] = function (): Site { return App::instance()->site(); }; diff --git a/kirby/src/Query/Segments.php b/kirby/src/Query/Segments.php index 5d2d009..d2af470 100644 --- a/kirby/src/Query/Segments.php +++ b/kirby/src/Query/Segments.php @@ -61,7 +61,7 @@ class Segments extends Collection return preg_split( '/(\??\.)|(\(([^()]+|(?2))*+\))(*SKIP)(*FAIL)/', trim($string), - flags: PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY + flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ); } diff --git a/kirby/src/Sane/DomHandler.php b/kirby/src/Sane/DomHandler.php index 960e549..2fe2e09 100644 --- a/kirby/src/Sane/DomHandler.php +++ b/kirby/src/Sane/DomHandler.php @@ -16,6 +16,8 @@ use Kirby\Toolkit\Dom; * @link https://getkirby.com * @copyright Bastian Allgeier * @license https://opensource.org/licenses/MIT + * + * @SuppressWarnings(PHPMD.LongVariable) */ class DomHandler extends Handler { @@ -43,6 +45,13 @@ class DomHandler extends Handler */ public static array|bool $allowedDomains = true; + /** + * Whether URLs that begin with `/` should be allowed even if the + * site index URL is in a subfolder (useful when using the HTML + * `` element where the sanitized code will be rendered) + */ + public static bool $allowHostRelativeUrls = true; + /** * Names of allowed XML processing instructions */ @@ -57,27 +66,34 @@ class DomHandler extends Handler /** * Sanitizes the given string * + * @param bool $isExternal Whether the string is from an external file + * that may be accessed directly + * * @throws \Kirby\Exception\InvalidArgumentException If the file couldn't be parsed */ - public static function sanitize(string $string): string + public static function sanitize(string $string, bool $isExternal = false): string { $dom = static::parse($string); - $dom->sanitize(static::options()); + $dom->sanitize(static::options($isExternal)); return $dom->toString(); } /** * Validates file contents * + * @param bool $isExternal Whether the string is from an external file + * that may be accessed directly + * * @throws \Kirby\Exception\InvalidArgumentException If the file couldn't be parsed * @throws \Kirby\Exception\InvalidArgumentException If the file didn't pass validation */ - public static function validate(string $string): void + public static function validate(string $string, bool $isExternal = false): void { - $dom = static::parse($string); - $errors = $dom->sanitize(static::options()); + $dom = static::parse($string); + $errors = $dom->sanitize(static::options($isExternal)); + + // there may be multiple errors, we can only throw one of them at a time if (count($errors) > 0) { - // there may be multiple errors, we can only throw one of them at a time throw $errors[0]; } } @@ -88,7 +104,7 @@ class DomHandler extends Handler * * @return array Array with exception objects for each modification */ - public static function sanitizeAttr(DOMAttr $attr): array + public static function sanitizeAttr(DOMAttr $attr, array $options): array { // to be extended in child classes return []; @@ -100,7 +116,7 @@ class DomHandler extends Handler * * @return array Array with exception objects for each modification */ - public static function sanitizeElement(DOMElement $element): array + public static function sanitizeElement(DOMElement $element, array $options): array { // to be extended in child classes return []; @@ -110,7 +126,7 @@ class DomHandler extends Handler * Custom callback for additional doctype validation * @internal */ - public static function validateDoctype(DOMDocumentType $doctype): void + public static function validateDoctype(DOMDocumentType $doctype, array $options): void { // to be extended in child classes } @@ -118,17 +134,29 @@ class DomHandler extends Handler /** * Returns the sanitization options for the handler * (to be extended in child classes) + * + * @param bool $isExternal Whether the string is from an external file + * that may be accessed directly */ - protected static function options(): array + protected static function options(bool $isExternal): array { - return [ - 'allowedDataUris' => static::$allowedDataUris, - 'allowedDomains' => static::$allowedDomains, - 'allowedPIs' => static::$allowedPIs, - 'attrCallback' => [static::class, 'sanitizeAttr'], - 'doctypeCallback' => [static::class, 'validateDoctype'], - 'elementCallback' => [static::class, 'sanitizeElement'], + $options = [ + 'allowedDataUris' => static::$allowedDataUris, + 'allowedDomains' => static::$allowedDomains, + 'allowHostRelativeUrls' => static::$allowHostRelativeUrls, + 'allowedPIs' => static::$allowedPIs, + 'attrCallback' => [static::class, 'sanitizeAttr'], + 'doctypeCallback' => [static::class, 'validateDoctype'], + 'elementCallback' => [static::class, 'sanitizeElement'], ]; + + // never allow host-relative URLs in external files as we + // cannot set a `` element for them when accessed directly + if ($isExternal === true) { + $options['allowHostRelativeUrls'] = false; + } + + return $options; } /** diff --git a/kirby/src/Sane/Handler.php b/kirby/src/Sane/Handler.php index 5b97044..7dfcd98 100644 --- a/kirby/src/Sane/Handler.php +++ b/kirby/src/Sane/Handler.php @@ -21,8 +21,11 @@ abstract class Handler { /** * Sanitizes the given string + * + * @param bool $isExternal Whether the string is from an external file + * that may be accessed directly */ - abstract public static function sanitize(string $string): string; + abstract public static function sanitize(string $string, bool $isExternal = false): string; /** * Sanitizes the contents of a file by overwriting @@ -33,17 +36,21 @@ abstract class Handler */ public static function sanitizeFile(string $file): void { - $sanitized = static::sanitize(static::readFile($file)); + $content = static::readFile($file); + $sanitized = static::sanitize($content, isExternal: true); F::write($file, $sanitized); } /** * Validates file contents * + * @param bool $isExternal Whether the string is from an external file + * that may be accessed directly + * * @throws \Kirby\Exception\InvalidArgumentException If the file didn't pass validation * @throws \Kirby\Exception\Exception On other errors */ - abstract public static function validate(string $string): void; + abstract public static function validate(string $string, bool $isExternal = false): void; /** * Validates the contents of a file @@ -54,7 +61,8 @@ abstract class Handler */ public static function validateFile(string $file): void { - static::validate(static::readFile($file)); + $content = static::readFile($file); + static::validate($content, isExternal: true); } /** diff --git a/kirby/src/Sane/Html.php b/kirby/src/Sane/Html.php index c0b00aa..0766e36 100644 --- a/kirby/src/Sane/Html.php +++ b/kirby/src/Sane/Html.php @@ -107,10 +107,13 @@ class Html extends DomHandler /** * Returns the sanitization options for the handler + * + * @param bool $isExternal Whether the string is from an external file + * that may be accessed directly */ - protected static function options(): array + protected static function options(bool $isExternal): array { - return array_merge(parent::options(), [ + return array_merge(parent::options($isExternal), [ 'allowedAttrPrefixes' => static::$allowedAttrPrefixes, 'allowedAttrs' => static::$allowedAttrs, 'allowedNamespaces' => [], diff --git a/kirby/src/Sane/Sane.php b/kirby/src/Sane/Sane.php index d52ea65..c079e2a 100644 --- a/kirby/src/Sane/Sane.php +++ b/kirby/src/Sane/Sane.php @@ -36,10 +36,10 @@ class Sane * All registered handlers */ public static array $handlers = [ - 'html' => 'Kirby\Sane\Html', - 'svg' => 'Kirby\Sane\Svg', - 'svgz' => 'Kirby\Sane\Svgz', - 'xml' => 'Kirby\Sane\Xml', + 'html' => Html::class, + 'svg' => Svg::class, + 'svgz' => Svgz::class, + 'xml' => Xml::class, ]; /** @@ -49,16 +49,19 @@ class Sane * * @throws \Kirby\Exception\NotFoundException If no handler was found and `$lazy` was set to `false` */ - public static function handler(string $type, bool $lazy = false): Handler|null - { + public static function handler( + string $type, + bool $lazy = false + ): Handler|null { // normalize the type $type = mb_strtolower($type); // find a handler or alias - $alias = static::$aliases[$type] ?? null; - $handler = - static::$handlers[$type] ?? - ($alias ? static::$handlers[$alias] ?? null : null); + $handler = static::$handlers[$type] ?? null; + + if ($alias = static::$aliases[$type] ?? null) { + $handler ??= static::$handlers[$alias] ?? null; + } if (empty($handler) === false && class_exists($handler) === true) { return new $handler(); @@ -74,10 +77,13 @@ class Sane /** * Sanitizes the given string with the specified handler * @since 3.6.0 + * + * @param bool $isExternal Whether the string is from an external file + * that may be accessed directly */ - public static function sanitize(string $string, string $type): string + public static function sanitize(string $string, string $type, bool $isExternal = false): string { - return static::handler($type)->sanitize($string); + return static::handler($type)->sanitize($string, $isExternal); } /** @@ -96,8 +102,10 @@ class Sane * @throws \Kirby\Exception\NotFoundException If the handler was not found * @throws \Kirby\Exception\Exception On other errors */ - public static function sanitizeFile(string $file, string|bool $typeLazy = false): void - { + public static function sanitizeFile( + string $file, + string|bool $typeLazy = false + ): void { if (is_string($typeLazy) === true) { static::handler($typeLazy)->sanitizeFile($file); return; @@ -126,13 +134,16 @@ class Sane /** * Validates file contents with the specified handler * + * @param bool $isExternal Whether the string is from an external file + * that may be accessed directly + * * @throws \Kirby\Exception\InvalidArgumentException If the file didn't pass validation * @throws \Kirby\Exception\NotFoundException If the handler was not found * @throws \Kirby\Exception\Exception On other errors */ - public static function validate(string $string, string $type): void + public static function validate(string $string, string $type, bool $isExternal = false): void { - static::handler($type)->validate($string); + static::handler($type)->validate($string, $isExternal); } /** @@ -148,14 +159,18 @@ class Sane * @throws \Kirby\Exception\NotFoundException If the handler was not found * @throws \Kirby\Exception\Exception On other errors */ - public static function validateFile(string $file, string|bool $typeLazy = false): void - { + public static function validateFile( + string $file, + string|bool $typeLazy = false + ): void { if (is_string($typeLazy) === true) { static::handler($typeLazy)->validateFile($file); return; } - foreach (static::handlersForFile($file, $typeLazy === true) as $handler) { + $handlers = static::handlersForFile($file, $typeLazy === true); + + foreach ($handlers as $handler) { $handler->validateFile($file); } } @@ -167,8 +182,10 @@ class Sane * @param bool $lazy If set to `true`, undefined handlers are skipped * @return array<\Kirby\Sane\Handler> */ - protected static function handlersForFile(string $file, bool $lazy = false): array - { + protected static function handlersForFile( + string $file, + bool $lazy = false + ): array { $handlers = $handlerClasses = []; // all values that can be used for the handler search; @@ -180,7 +197,10 @@ class Sane $handlerClass = $handler ? get_class($handler) : null; // ensure that each handler class is only returned once - if ($handler && in_array($handlerClass, $handlerClasses) === false) { + if ( + $handler && + in_array($handlerClass, $handlerClasses) === false + ) { $handlers[] = $handler; $handlerClasses[] = $handlerClass; } diff --git a/kirby/src/Sane/Svg.php b/kirby/src/Sane/Svg.php index 2910272..1a947ec 100644 --- a/kirby/src/Sane/Svg.php +++ b/kirby/src/Sane/Svg.php @@ -392,7 +392,7 @@ class Svg extends Xml * * @return array Array with exception objects for each modification */ - public static function sanitizeAttr(DOMAttr $attr): array + public static function sanitizeAttr(DOMAttr $attr, array $options): array { $element = $attr->ownerElement; $name = $attr->name; @@ -406,8 +406,9 @@ class Svg extends Xml Str::startsWith($value, '#') === true ) { // find the target (used element) - $id = str_replace('"', '', mb_substr($value, 1)); - $target = (new DOMXPath($attr->ownerDocument))->query('//*[@id="' . $id . '"]')->item(0); + $id = str_replace('"', '', mb_substr($value, 1)); + $path = new DOMXPath($attr->ownerDocument); + $target = $path->query('//*[@id="' . $id . '"]')->item(0); // the target must not contain any other elements if ( @@ -431,14 +432,14 @@ class Svg extends Xml * * @return array Array with exception objects for each modification */ - public static function sanitizeElement(DOMElement $element): array + public static function sanitizeElement(DOMElement $element, array $options): array { $errors = []; // check for URLs inside