typo3-typescript-sources 13.4.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/sync-and-publish.yml +265 -0
- package/.last-synced-tag +1 -0
- package/LICENSE.txt +339 -0
- package/README-TYPO3.md +166 -0
- package/README.md +28 -0
- package/adminpanel/admin-panel.ts +375 -0
- package/adminpanel/modules/cache.ts +27 -0
- package/adminpanel/modules/preview.ts +75 -0
- package/backend/action-button/abstract-action.ts +22 -0
- package/backend/action-button/deferred-action.ts +41 -0
- package/backend/action-button/immediate-action.ts +31 -0
- package/backend/action-dispatcher.ts +104 -0
- package/backend/ajax-data-handler/message-interface.ts +18 -0
- package/backend/ajax-data-handler/response-interface.ts +20 -0
- package/backend/ajax-data-handler.ts +214 -0
- package/backend/backend-exception.ts +22 -0
- package/backend/broadcast-message.ts +50 -0
- package/backend/broadcast-service.ts +52 -0
- package/backend/browse-database.ts +38 -0
- package/backend/clear-cache.ts +88 -0
- package/backend/clipboard-panel.ts +240 -0
- package/backend/close-current-window.ts +2 -0
- package/backend/code-editor/autocomplete/completion-result.ts +105 -0
- package/backend/code-editor/autocomplete/ts-code-completion.ts +142 -0
- package/backend/code-editor/autocomplete/ts-parser.ts +485 -0
- package/backend/code-editor/autocomplete/ts-ref.ts +164 -0
- package/backend/code-editor/element/code-mirror-element.ts +282 -0
- package/backend/code-editor/language/typoscript.ts +181 -0
- package/backend/code-editor/stream-parser/typoscript.ts +1600 -0
- package/backend/color-picker.ts +158 -0
- package/backend/color-scheme-switch.ts +179 -0
- package/backend/column-selector-button.ts +302 -0
- package/backend/context-help.ts +68 -0
- package/backend/context-menu-actions.ts +322 -0
- package/backend/context-menu.ts +751 -0
- package/backend/copy-to-clipboard.ts +79 -0
- package/backend/date-time-picker.ts +270 -0
- package/backend/document-save-actions.ts +159 -0
- package/backend/drag-tooltip.ts +285 -0
- package/backend/drag-uploader.ts +914 -0
- package/backend/element/alert-element.ts +116 -0
- package/backend/element/dispatch-modal-button.ts +50 -0
- package/backend/element/draggable-resizable-element.ts +249 -0
- package/backend/element/editable-page-title.ts +306 -0
- package/backend/element/icon-element.ts +84 -0
- package/backend/element/immediate-action-element.ts +89 -0
- package/backend/element/pagination.ts +59 -0
- package/backend/element/progress-bar-element.ts +172 -0
- package/backend/element/progress-tracker-element.ts +119 -0
- package/backend/element/pseudo-button.ts +58 -0
- package/backend/element/spinner-element.ts +51 -0
- package/backend/element/thumbnail-element.ts +80 -0
- package/backend/element-browser.ts +182 -0
- package/backend/enum/data-transfer-types.ts +21 -0
- package/backend/enum/icon-types.ts +31 -0
- package/backend/enum/key-types.ts +27 -0
- package/backend/enum/severity.ts +20 -0
- package/backend/enum/viewport/scaffold-identifier.ts +24 -0
- package/backend/event/client-request.ts +25 -0
- package/backend/event/consumable.ts +18 -0
- package/backend/event/consumer-scope.ts +54 -0
- package/backend/event/event-dispatcher.ts +26 -0
- package/backend/event/interaction-request-assignment.ts +24 -0
- package/backend/event/interaction-request-map.ts +75 -0
- package/backend/event/interaction-request.ts +48 -0
- package/backend/event/trigger-request.ts +56 -0
- package/backend/form/submit-interceptor.ts +66 -0
- package/backend/form-engine/container/files-control-container.ts +716 -0
- package/backend/form-engine/container/flex-form-container-container.ts +166 -0
- package/backend/form-engine/container/flex-form-section-container.ts +210 -0
- package/backend/form-engine/container/inline-control-container.ts +1230 -0
- package/backend/form-engine/container/site-language-container.ts +616 -0
- package/backend/form-engine/element/abstract-sortable-select-items.ts +186 -0
- package/backend/form-engine/element/category-element.ts +160 -0
- package/backend/form-engine/element/color-element.ts +81 -0
- package/backend/form-engine/element/datetime-element.ts +66 -0
- package/backend/form-engine/element/extra/char-counter.ts +112 -0
- package/backend/form-engine/element/extra/select-box-filter.ts +85 -0
- package/backend/form-engine/element/folder-element.ts +66 -0
- package/backend/form-engine/element/group-element.ts +47 -0
- package/backend/form-engine/element/json-element.ts +52 -0
- package/backend/form-engine/element/link-element.ts +121 -0
- package/backend/form-engine/element/mfa-info-element.ts +169 -0
- package/backend/form-engine/element/modifier/resizable.ts +27 -0
- package/backend/form-engine/element/modifier/tabbable.ts +29 -0
- package/backend/form-engine/element/online-media-form-element.ts +65 -0
- package/backend/form-engine/element/password-element.ts +71 -0
- package/backend/form-engine/element/select-multiple-side-by-side-element.ts +83 -0
- package/backend/form-engine/element/select-single-element.ts +82 -0
- package/backend/form-engine/element/select-tree-element.ts +148 -0
- package/backend/form-engine/element/select-tree-toolbar.ts +118 -0
- package/backend/form-engine/element/select-tree.ts +210 -0
- package/backend/form-engine/element/slug-element.ts +243 -0
- package/backend/form-engine/element/suggest/result-container.ts +118 -0
- package/backend/form-engine/element/suggest/result-item.ts +90 -0
- package/backend/form-engine/element/table-permission-element.ts +101 -0
- package/backend/form-engine/element/table-wizard-element.ts +427 -0
- package/backend/form-engine/element/text-element.ts +52 -0
- package/backend/form-engine/element/text-table-element.ts +31 -0
- package/backend/form-engine/field-control/add-record.ts +40 -0
- package/backend/form-engine/field-control/edit-popup.ts +64 -0
- package/backend/form-engine/field-control/insert-clipboard.ts +50 -0
- package/backend/form-engine/field-control/link-popup.ts +53 -0
- package/backend/form-engine/field-control/list-module.ts +40 -0
- package/backend/form-engine/field-control/password-generator.ts +102 -0
- package/backend/form-engine/field-control/reset-selection.ts +48 -0
- package/backend/form-engine/field-wizard/localization-state-selector.ts +67 -0
- package/backend/form-engine/field-wizard/value-picker.ts +123 -0
- package/backend/form-engine/field-wizard/value-slider.ts +86 -0
- package/backend/form-engine/inline-relation/ajax-dispatcher.ts +124 -0
- package/backend/form-engine/inline-relation/inline-response-interface.ts +41 -0
- package/backend/form-engine-link-browser-adapter.ts +129 -0
- package/backend/form-engine-review.ts +155 -0
- package/backend/form-engine-suggest.ts +133 -0
- package/backend/form-engine-validation.ts +686 -0
- package/backend/form-engine.ts +1391 -0
- package/backend/global-event-handler.ts +252 -0
- package/backend/grid-editor.ts +1077 -0
- package/backend/hashing/md5.ts +252 -0
- package/backend/helper.ts +24 -0
- package/backend/hotkeys/hotkey-storage.ts +63 -0
- package/backend/hotkeys.ts +216 -0
- package/backend/icons.ts +236 -0
- package/backend/image-manipulation.ts +885 -0
- package/backend/info-window.ts +46 -0
- package/backend/input/clearable.ts +128 -0
- package/backend/input/index.d.ts +8 -0
- package/backend/java-script-module-import-event-handler.ts +19 -0
- package/backend/key-bindings.ts +42 -0
- package/backend/layout-module/drag-drop.ts +359 -0
- package/backend/layout-module/paste.ts +165 -0
- package/backend/link-browser.ts +89 -0
- package/backend/live-search/element/backend-search.ts +34 -0
- package/backend/live-search/element/hint.ts +33 -0
- package/backend/live-search/element/provider/default-result-item.ts +52 -0
- package/backend/live-search/element/provider/page-provider-result-item.ts +52 -0
- package/backend/live-search/element/result/item/action/action-container.ts +135 -0
- package/backend/live-search/element/result/item/action/action.ts +56 -0
- package/backend/live-search/element/result/item/item-container.ts +224 -0
- package/backend/live-search/element/result/item/item.ts +74 -0
- package/backend/live-search/element/result/result-container.ts +110 -0
- package/backend/live-search/element/result/result-detail-container.ts +50 -0
- package/backend/live-search/element/result/result-pagination.ts +122 -0
- package/backend/live-search/element/search-option-item.ts +70 -0
- package/backend/live-search/live-search-configurator.ts +54 -0
- package/backend/live-search/live-search-shortcut.ts +29 -0
- package/backend/live-search/result-types/backend-module-result-type.ts +8 -0
- package/backend/live-search/result-types/default-result-type.ts +21 -0
- package/backend/live-search/result-types/page-result-type.ts +23 -0
- package/backend/localization.ts +390 -0
- package/backend/login-refresh.ts +525 -0
- package/backend/login.ts +179 -0
- package/backend/mail-link-handler.ts +45 -0
- package/backend/modal-interface.ts +19 -0
- package/backend/modal.ts +685 -0
- package/backend/module/iframe.ts +125 -0
- package/backend/module/router.ts +275 -0
- package/backend/module-menu.ts +550 -0
- package/backend/module.ts +133 -0
- package/backend/multi-record-selection-action.ts +44 -0
- package/backend/multi-record-selection-delete-action.ts +108 -0
- package/backend/multi-record-selection-edit-action.ts +56 -0
- package/backend/multi-record-selection.ts +468 -0
- package/backend/multi-step-wizard.ts +568 -0
- package/backend/new-content-element-wizard-button.ts +51 -0
- package/backend/new-multiple-pages.ts +108 -0
- package/backend/new-record-wizard.ts +680 -0
- package/backend/notification.ts +366 -0
- package/backend/offset.ts +40 -0
- package/backend/online-media.ts +110 -0
- package/backend/page-actions.ts +91 -0
- package/backend/page-link-handler.ts +51 -0
- package/backend/pagetsconfig/pagetsconfig-includes.ts +77 -0
- package/backend/popover.ts +165 -0
- package/backend/record-download-button.ts +104 -0
- package/backend/record-link-handler.ts +31 -0
- package/backend/record-search.ts +44 -0
- package/backend/recordlist.ts +230 -0
- package/backend/resource/resource.ts +35 -0
- package/backend/security/element/csp-reports.ts +406 -0
- package/backend/security/element/sudo-mode.ts +249 -0
- package/backend/security/sudo-mode-interceptor.ts +19 -0
- package/backend/settings/editor/editable-setting.ts +231 -0
- package/backend/settings/editor.ts +451 -0
- package/backend/settings/enum/settings-mode.enum.ts +24 -0
- package/backend/settings/type/base.ts +200 -0
- package/backend/settings/type/bool.ts +56 -0
- package/backend/settings/type/color.ts +64 -0
- package/backend/settings/type/int.ts +85 -0
- package/backend/settings/type/number.ts +61 -0
- package/backend/settings/type/page.ts +100 -0
- package/backend/settings/type/string.ts +77 -0
- package/backend/settings/type/stringlist.ts +87 -0
- package/backend/severity.ts +88 -0
- package/backend/site-inline-actions.ts +32 -0
- package/backend/sortable-table.ts +208 -0
- package/backend/storage/abstract-client-storage.ts +76 -0
- package/backend/storage/browser-session.ts +28 -0
- package/backend/storage/client.ts +28 -0
- package/backend/storage/module-state-storage.ts +118 -0
- package/backend/storage/persistent.ts +204 -0
- package/backend/switch-user.ts +82 -0
- package/backend/tabs.ts +72 -0
- package/backend/telephone-link-handler.ts +40 -0
- package/backend/tests/backend/storage/browser-storage-test.ts +87 -0
- package/backend/tests/backend/storage/client-test.ts +87 -0
- package/backend/tests/backend/utility/dom-helper-test.ts +51 -0
- package/backend/tests/backend-exception-test.ts +28 -0
- package/backend/tests/element/immediate-action-element-test.ts +81 -0
- package/backend/tests/form-engine-validation-test.ts +304 -0
- package/backend/tests/grid-editor-test.ts +29 -0
- package/backend/tests/hashing/md5Test.ts +28 -0
- package/backend/tests/icons-test.ts +23 -0
- package/backend/tests/notification-test.ts +161 -0
- package/backend/tests/popover-test.ts +83 -0
- package/backend/toolbar/clear-cache-menu.ts +88 -0
- package/backend/toolbar/live-search.ts +284 -0
- package/backend/toolbar/shortcut-menu.ts +254 -0
- package/backend/toolbar/system-information-menu.ts +161 -0
- package/backend/toolbar.ts +41 -0
- package/backend/tree/file-storage-browser.ts +194 -0
- package/backend/tree/file-storage-tree-container.ts +465 -0
- package/backend/tree/file-storage-tree.ts +42 -0
- package/backend/tree/page-browser.ts +272 -0
- package/backend/tree/page-tree-element.ts +602 -0
- package/backend/tree/page-tree.ts +152 -0
- package/backend/tree/tree-module-state.ts +159 -0
- package/backend/tree/tree-node-toggle.ts +31 -0
- package/backend/tree/tree-node.ts +97 -0
- package/backend/tree/tree-toolbar.ts +117 -0
- package/backend/tree/tree.ts +1595 -0
- package/backend/url-link-handler.ts +36 -0
- package/backend/user-pass-login.ts +255 -0
- package/backend/user-settings-manager.ts +189 -0
- package/backend/utility/collapse-state-persister.ts +205 -0
- package/backend/utility/collapse-state-search.ts +183 -0
- package/backend/utility/dom-helper.ts +91 -0
- package/backend/utility/drag-drop-utility.ts +47 -0
- package/backend/utility/message-utility.ts +45 -0
- package/backend/utility/top-level-module-import.ts +32 -0
- package/backend/utility.ts +194 -0
- package/backend/viewport/abstract-container.ts +22 -0
- package/backend/viewport/content-container.ts +98 -0
- package/backend/viewport/loader.ts +28 -0
- package/backend/viewport/navigation-container.ts +119 -0
- package/backend/viewport/resizable-navigation.ts +176 -0
- package/backend/viewport/toolbar.ts +27 -0
- package/backend/viewport/topbar.ts +39 -0
- package/backend/viewport.ts +46 -0
- package/backend/window-manager.ts +84 -0
- package/backend/wizard/move-content-element.ts +107 -0
- package/backend/wizard/move-page.ts +74 -0
- package/backend/wizard.ts +331 -0
- package/belog/backend-log.ts +88 -0
- package/beuser/backend-user-listing.ts +40 -0
- package/beuser/permissions.ts +291 -0
- package/core/ajax/ajax-request-types.ts +15 -0
- package/core/ajax/ajax-request.ts +202 -0
- package/core/ajax/ajax-response.ts +57 -0
- package/core/ajax/input-transformer.ts +92 -0
- package/core/ajax/simple-response-interface.ts +5 -0
- package/core/authentication/mfa-provider/totp.ts +54 -0
- package/core/cache/dedupe-async-task.ts +95 -0
- package/core/directive/markdown.ts +82 -0
- package/core/directive/nl2br.ts +16 -0
- package/core/document-service.ts +35 -0
- package/core/event/debounce-event.ts +40 -0
- package/core/event/event-interface.ts +24 -0
- package/core/event/regular-event.ts +58 -0
- package/core/event/request-animation-frame-event.ts +42 -0
- package/core/event/throttle-event.ts +54 -0
- package/core/java-script-item-handler.ts +29 -0
- package/core/java-script-item-processor.ts +171 -0
- package/core/lit-helper.ts +126 -0
- package/core/literals.ts +17 -0
- package/core/referrer-refresh.ts +2 -0
- package/core/security-utility.ts +89 -0
- package/core/tests/ajax/ajax-request-test.ts +290 -0
- package/core/tests/ajax/input-transformer-test.ts +64 -0
- package/core/tests/cache/dedupe-async-task-test.ts +152 -0
- package/core/tests/event/throttle-event-test.ts +47 -0
- package/core/tests/literals-test.ts +24 -0
- package/core/tests/security-utility-test.ts +50 -0
- package/core/utility/types.ts +9 -0
- package/dashboard/chart-initializer.ts +136 -0
- package/dashboard/dashboard.ts +1487 -0
- package/extensionmanager/distribution-image.ts +55 -0
- package/extensionmanager/main.ts +291 -0
- package/extensionmanager/repository.ts +195 -0
- package/extensionmanager/update.ts +118 -0
- package/extensionmanager/upload-form.ts +42 -0
- package/filelist/browse-files.ts +148 -0
- package/filelist/browse-folders.ts +94 -0
- package/filelist/context-menu-actions.ts +335 -0
- package/filelist/create-folder.ts +69 -0
- package/filelist/file-delete.ts +69 -0
- package/filelist/file-list-actions.ts +132 -0
- package/filelist/file-list-dragdrop.ts +139 -0
- package/filelist/file-list-rename-handler.ts +128 -0
- package/filelist/file-list-transfer-handler.ts +138 -0
- package/filelist/file-list.ts +396 -0
- package/filelist/file-replace.ts +37 -0
- package/filelist/linkbrowser-file-handler.ts +94 -0
- package/filelist/linkbrowser-folder-handler.ts +74 -0
- package/filelist/rename-file.ts +109 -0
- package/form/backend/form-editor/component/property-grid-editor.ts +340 -0
- package/form/backend/form-editor/core.ts +2104 -0
- package/form/backend/form-editor/helper.ts +226 -0
- package/form/backend/form-editor/inspector-component.ts +2323 -0
- package/form/backend/form-editor/mediator.ts +941 -0
- package/form/backend/form-editor/modals-component.ts +433 -0
- package/form/backend/form-editor/stage-component.ts +908 -0
- package/form/backend/form-editor/tree-component.ts +548 -0
- package/form/backend/form-editor/view-model.ts +1343 -0
- package/form/backend/form-editor.ts +846 -0
- package/form/backend/form-manager/main.ts +39 -0
- package/form/backend/form-manager/view-model.ts +789 -0
- package/form/backend/form-manager.ts +180 -0
- package/form/backend/helper.ts +60 -0
- package/form/frontend/date-picker.ts +38 -0
- package/frontend/default_frontend.ts +82 -0
- package/impexp/context-menu-actions.ts +50 -0
- package/impexp/import-export.ts +62 -0
- package/indexed-search/pagination.ts +26 -0
- package/indexed-search/statistics.ts +50 -0
- package/info/translation-status.ts +48 -0
- package/install/ajax/ajax-queue.ts +83 -0
- package/install/init-install.ts +2 -0
- package/install/init-installer.ts +2 -0
- package/install/install.ts +25 -0
- package/install/installer.ts +458 -0
- package/install/message-interface.ts +18 -0
- package/install/module/abstract-inline-module.ts +27 -0
- package/install/module/abstract-interactable-module.ts +107 -0
- package/install/module/environment/environment-check.ts +92 -0
- package/install/module/environment/folder-structure.ts +145 -0
- package/install/module/environment/image-processing.ts +141 -0
- package/install/module/environment/mail-test.ts +127 -0
- package/install/module/environment/php-info.ts +50 -0
- package/install/module/environment/system-information.ts +50 -0
- package/install/module/maintenance/cache.ts +59 -0
- package/install/module/maintenance/clear-tables.ts +134 -0
- package/install/module/maintenance/clear-typo3temp-files.ts +137 -0
- package/install/module/maintenance/create-admin.ts +126 -0
- package/install/module/maintenance/database-analyzer.ts +266 -0
- package/install/module/maintenance/dump-autoload.ts +58 -0
- package/install/module/maintenance/language-packs.ts +338 -0
- package/install/module/maintenance/reset-backend-user-uc.ts +58 -0
- package/install/module/password-strength.ts +56 -0
- package/install/module/settings/change-install-tool-password.ts +101 -0
- package/install/module/settings/extension-configuration.ts +224 -0
- package/install/module/settings/features.ts +102 -0
- package/install/module/settings/local-configuration.ts +316 -0
- package/install/module/settings/presets.ts +147 -0
- package/install/module/settings/system-maintainer.ts +111 -0
- package/install/module/upgrade/core-update.ts +255 -0
- package/install/module/upgrade/extension-compat-tester.ts +195 -0
- package/install/module/upgrade/extension-scanner.ts +381 -0
- package/install/module/upgrade/tca-ext-tables-check.ts +93 -0
- package/install/module/upgrade/tca-migrations-check.ts +101 -0
- package/install/module/upgrade/upgrade-docs.ts +310 -0
- package/install/module/upgrade/upgrade-wizards.ts +612 -0
- package/install/renderable/clearable.ts +113 -0
- package/install/renderable/flash-message.ts +64 -0
- package/install/renderable/info-box.ts +66 -0
- package/install/renderable/language-packs.ts +304 -0
- package/install/renderable/offset-group.ts +38 -0
- package/install/renderable/severity.ts +49 -0
- package/install/renderable/wrap-group.ts +36 -0
- package/install/router.ts +468 -0
- package/linkvalidator/linkvalidator-modal.ts +45 -0
- package/linkvalidator/linkvalidator.ts +101 -0
- package/lowlevel/query-generator.ts +132 -0
- package/lowlevel/reference-index.ts +44 -0
- package/opendocs/toolbar/opendocs-menu.ts +117 -0
- package/package.json +34 -0
- package/recycler/recycler.ts +539 -0
- package/redirects/event-handler.ts +108 -0
- package/redirects/form-engine-evaluation.ts +34 -0
- package/redirects/redirects-module.ts +58 -0
- package/rte-ckeditor/ckeditor5.ts +402 -0
- package/rte-ckeditor/css-prefixer.ts +94 -0
- package/rte-ckeditor/legacy/ckeditor5-bundle.ts +17 -0
- package/rte-ckeditor/legacy/ckeditor5-inspector.ts +2 -0
- package/rte-ckeditor/plugin/typo3-link.ts +810 -0
- package/rte-ckeditor/plugin/whitespace.ts +114 -0
- package/rte-ckeditor/rte-link-browser.ts +112 -0
- package/rte-ckeditor/tests/css-prefixer-test.ts +314 -0
- package/scheduler/scheduler-add-group.ts +119 -0
- package/scheduler/scheduler-editable-group-name.ts +307 -0
- package/scheduler/scheduler-sortable-groups.ts +59 -0
- package/scheduler/scheduler.ts +466 -0
- package/setup/setup-module.ts +122 -0
- package/styleguide/checkbox-demo.ts +5 -0
- package/styleguide/element/theme-switcher-element.ts +101 -0
- package/styleguide/filter.ts +22 -0
- package/styleguide/find-icons.ts +78 -0
- package/styleguide/processing-indicator.ts +78 -0
- package/styleguide/render-notifications.ts +64 -0
- package/sys-note/element/delete-button.ts +78 -0
- package/tstemplate/constant-editor.ts +64 -0
- package/tstemplate/information-module.ts +31 -0
- package/tstemplate/template-analyzer.ts +77 -0
- package/viewpage/main.ts +253 -0
- package/workspaces/backend.ts +979 -0
- package/workspaces/preview.ts +232 -0
- package/workspaces/renderable/comment-view.ts +79 -0
- package/workspaces/renderable/diff-view.ts +58 -0
- package/workspaces/renderable/history-view.ts +97 -0
- package/workspaces/renderable/record-information.ts +118 -0
- package/workspaces/renderable/record-table.ts +355 -0
- package/workspaces/renderable/send-to-stage-form.ts +108 -0
- package/workspaces/toolbar/workspaces-menu.ts +190 -0
- package/workspaces/utility/icon-helper.ts +57 -0
- package/workspaces/workspaces.ts +136 -0
|
@@ -0,0 +1,810 @@
|
|
|
1
|
+
import * as UI from '@ckeditor/ckeditor5-ui';
|
|
2
|
+
import * as Core from '@ckeditor/ckeditor5-core';
|
|
3
|
+
import * as Engine from '@ckeditor/ckeditor5-engine';
|
|
4
|
+
import * as Typing from '@ckeditor/ckeditor5-typing';
|
|
5
|
+
import * as Widget from '@ckeditor/ckeditor5-widget';
|
|
6
|
+
import * as Utils from '@ckeditor/ckeditor5-utils';
|
|
7
|
+
import * as Link from '@ckeditor/ckeditor5-link';
|
|
8
|
+
import { LinkUtils } from '@ckeditor/ckeditor5-link';
|
|
9
|
+
import { default as modalObject, type ModalElement } from '@typo3/backend/modal';
|
|
10
|
+
import type { ViewAttributeElement, ViewElement, ModelSchema, ModelWriter } from '@ckeditor/ckeditor5-engine';
|
|
11
|
+
import type { GeneralHtmlSupport, DataFilter } from '@ckeditor/ckeditor5-html-support';
|
|
12
|
+
import type { GHSViewAttributes } from '@ckeditor/ckeditor5-html-support/src/utils';
|
|
13
|
+
import { IconLink, IconPencil, IconUnlink } from '@ckeditor/ckeditor5-icons';
|
|
14
|
+
|
|
15
|
+
export const LINK_ALLOWED_ATTRIBUTES = ['href', 'title', 'class', 'target', 'rel'];
|
|
16
|
+
|
|
17
|
+
export function addLinkPrefix(attribute: string): string {
|
|
18
|
+
const capitalizedAttribute = attribute.charAt(0).toUpperCase() + attribute.slice(1);
|
|
19
|
+
return 'link' + capitalizedAttribute;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function removeLinkPrefix(attribute: string): string {
|
|
23
|
+
if (attribute.startsWith('link') && attribute.length >= 5) {
|
|
24
|
+
return attribute.charAt(4).toLowerCase() + attribute.slice(5);
|
|
25
|
+
}
|
|
26
|
+
return attribute;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface Typo3LinkConfig {
|
|
30
|
+
routeUrl: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface Typo3LinkDict {
|
|
34
|
+
attrs?: {
|
|
35
|
+
linkTitle?: string;
|
|
36
|
+
linkClass?: string;
|
|
37
|
+
linkTarget?: string;
|
|
38
|
+
linkRel?: string;
|
|
39
|
+
};
|
|
40
|
+
linkText?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Inspired by @ckeditor/ckeditor5-link/src/linkcommand.js
|
|
45
|
+
*/
|
|
46
|
+
export class Typo3LinkCommand extends Core.Command {
|
|
47
|
+
public override value: string | undefined;
|
|
48
|
+
public attrs: Record<string, string> = {};
|
|
49
|
+
|
|
50
|
+
public override refresh(): void {
|
|
51
|
+
const model = this.editor.model;
|
|
52
|
+
const selection = model.document.selection;
|
|
53
|
+
const selectedElement = selection.getSelectedElement() || Utils.first(selection.getSelectedBlocks());
|
|
54
|
+
|
|
55
|
+
// A check for any integration that allows linking elements (e.g. `LinkImage`).
|
|
56
|
+
// Currently, the selection reads attributes from text nodes only. See #7429 and #7465.
|
|
57
|
+
const sourceSelection = LinkUtils.isLinkableElement(selectedElement, model.schema) ? selectedElement : selection;
|
|
58
|
+
if (sourceSelection === selectedElement) {
|
|
59
|
+
this.value = selectedElement.getAttribute('linkHref') as string;
|
|
60
|
+
this.isEnabled = model.schema.checkAttribute(selectedElement, 'linkHref');
|
|
61
|
+
} else {
|
|
62
|
+
this.value = selection.getAttribute('linkHref') as string;
|
|
63
|
+
this.isEnabled = model.schema.checkAttributeInSelection(selection, 'linkHref');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const htmlSupport: GeneralHtmlSupport = this.editor.plugins.get('GeneralHtmlSupport');
|
|
67
|
+
const ghsAttributeName = htmlSupport.getGhsAttributeNameForElement('a');
|
|
68
|
+
const attrs: Record<string, string> = {};
|
|
69
|
+
for (const attribute of this.getLinkAttributesAllowedOnText(model.schema)) {
|
|
70
|
+
if (attribute === 'linkHref') {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (attribute === ghsAttributeName) {
|
|
75
|
+
const value: GHSViewAttributes = sourceSelection.getAttribute(attribute);
|
|
76
|
+
if (value?.classes && value.classes.length !== 0) {
|
|
77
|
+
attrs.class = value.classes.join(' ');
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
const value = sourceSelection.getAttribute(attribute) as string | undefined;
|
|
81
|
+
if (value !== undefined) {
|
|
82
|
+
attrs[removeLinkPrefix(attribute)] = value;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
this.attrs = attrs;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
public override execute(href: string, linkAttr: Typo3LinkDict = {}): void {
|
|
90
|
+
const model = this.editor.model;
|
|
91
|
+
const selection = model.document.selection;
|
|
92
|
+
|
|
93
|
+
model.change(writer => {
|
|
94
|
+
// If selection is collapsed then update selected link or insert new one at the place of caret.
|
|
95
|
+
if (selection.isCollapsed) {
|
|
96
|
+
const position = selection.getFirstPosition();
|
|
97
|
+
|
|
98
|
+
// When selection is inside text with `linkHref` attribute.
|
|
99
|
+
if (selection.hasAttribute('linkHref')) {
|
|
100
|
+
// Then update `linkHref` value.
|
|
101
|
+
const linkRange = Typing.findAttributeRange(position, 'linkHref', selection.getAttribute('linkHref') as string, model);
|
|
102
|
+
writer.setAttribute('linkHref', href, linkRange);
|
|
103
|
+
for (const [attribute, value] of Object.entries(this.composeLinkAttributes(linkAttr))) {
|
|
104
|
+
if (value !== null) {
|
|
105
|
+
writer.setAttribute(attribute, value, linkRange);
|
|
106
|
+
} else {
|
|
107
|
+
writer.removeAttribute(attribute, linkRange);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Put the selection at the end of the updated link.
|
|
111
|
+
writer.setSelection(writer.createPositionAfter(linkRange.end.nodeBefore));
|
|
112
|
+
|
|
113
|
+
} else if (href !== '') {
|
|
114
|
+
// If not then insert text node with `linkHref` attribute in place of caret.
|
|
115
|
+
// However, since selection is collapsed, attribute value will be used as data for text node.
|
|
116
|
+
// So, if `href` is empty, do not create text node.
|
|
117
|
+
const attributes = Utils.toMap(selection.getAttributes() as any);
|
|
118
|
+
attributes.set('linkHref', href);
|
|
119
|
+
for (const [attribute, value] of Object.entries(this.composeLinkAttributes(linkAttr))) {
|
|
120
|
+
if (value !== null) {
|
|
121
|
+
attributes.set(attribute, value);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const { end: positionAfter } = model.insertContent(writer.createText(href, attributes as any), position);
|
|
125
|
+
// Put the selection at the end of the inserted link.
|
|
126
|
+
// Using end of range returned from insertContent in case nodes with the same attributes got merged.
|
|
127
|
+
writer.setSelection(positionAfter);
|
|
128
|
+
}
|
|
129
|
+
// Remove the `linkHref` attribute and all link decorators from the selection.
|
|
130
|
+
// It stops adding a new content into the link element.
|
|
131
|
+
this.removeLinkAttributesFromSelection(writer, this.getLinkAttributesAllowedOnText(model.schema));
|
|
132
|
+
} else {
|
|
133
|
+
// If selection has non-collapsed ranges, we change attribute on nodes inside those ranges
|
|
134
|
+
// omitting nodes where the `linkHref` attribute is disallowed.
|
|
135
|
+
const ranges = model.schema.getValidRanges((selection.getRanges() as unknown) as any[], 'linkHref');
|
|
136
|
+
|
|
137
|
+
// But for the first, check whether the `linkHref` attribute is allowed on selected blocks (e.g. the "image" element).
|
|
138
|
+
const allowedRanges = [];
|
|
139
|
+
|
|
140
|
+
for (const element of selection.getSelectedBlocks()) {
|
|
141
|
+
if (model.schema.checkAttribute(element, 'linkHref')) {
|
|
142
|
+
allowedRanges.push(writer.createRangeOn(element));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Ranges that accept the `linkHref` attribute. Since we will iterate over `allowedRanges`, let's clone it.
|
|
147
|
+
const rangesToUpdate = allowedRanges.slice();
|
|
148
|
+
|
|
149
|
+
// For all selection ranges we want to check whether given range is inside an element that accepts the `linkHref` attribute.
|
|
150
|
+
// If so, we don't want to propagate applying the attribute to its children.
|
|
151
|
+
for (const range of ranges) {
|
|
152
|
+
if (this.isRangeToUpdate(range, allowedRanges)) {
|
|
153
|
+
rangesToUpdate.push(range);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
for (const range of rangesToUpdate) {
|
|
157
|
+
writer.setAttribute('linkHref', href, range);
|
|
158
|
+
for (const [attribute, value] of Object.entries(this.composeLinkAttributes(linkAttr))) {
|
|
159
|
+
if (value !== null) {
|
|
160
|
+
writer.setAttribute(attribute, value, range);
|
|
161
|
+
} else {
|
|
162
|
+
writer.removeAttribute(attribute, range);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private getLinkAttributesAllowedOnText(schema: ModelSchema): Array<string> {
|
|
171
|
+
const textAttributes = schema.getDefinition('$text').allowAttributes;
|
|
172
|
+
return textAttributes.filter(attribute => attribute.startsWith('link') || attribute === 'htmlA');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private removeLinkAttributesFromSelection(writer: ModelWriter, linkAttributes: Array<string>): void {
|
|
176
|
+
writer.removeSelectionAttribute('linkHref');
|
|
177
|
+
|
|
178
|
+
for (const attribute of linkAttributes) {
|
|
179
|
+
writer.removeSelectionAttribute(attribute);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private composeLinkAttributes(linkAttr: Typo3LinkDict): Record<string, GHSViewAttributes|string|null> {
|
|
184
|
+
const attrs: Record<string, GHSViewAttributes|string> = {};
|
|
185
|
+
for (const [attribute, value] of Object.entries(linkAttr.attrs)) {
|
|
186
|
+
if (attribute === 'linkClass') {
|
|
187
|
+
const htmlSupport: GeneralHtmlSupport = this.editor.plugins.get('GeneralHtmlSupport');
|
|
188
|
+
const ghsAttributeName = htmlSupport.getGhsAttributeNameForElement('a');
|
|
189
|
+
const selection = this.editor.model.document.selection;
|
|
190
|
+
let htmlA: GHSViewAttributes;
|
|
191
|
+
if (selection.hasAttribute(ghsAttributeName)) {
|
|
192
|
+
htmlA = { ...(selection.getAttribute(ghsAttributeName) as GHSViewAttributes) };
|
|
193
|
+
} else {
|
|
194
|
+
htmlA = {};
|
|
195
|
+
}
|
|
196
|
+
const classes = value.replace(/\s+/g, ' ').trim();
|
|
197
|
+
if (classes !== '') {
|
|
198
|
+
htmlA.classes = classes.split(' ');
|
|
199
|
+
} else if ('classes' in htmlA) {
|
|
200
|
+
delete htmlA.classes;
|
|
201
|
+
}
|
|
202
|
+
attrs[ghsAttributeName] = Object.keys(htmlA).length !== 0 ? htmlA : null;
|
|
203
|
+
} else {
|
|
204
|
+
attrs[attribute] = value !== '' ? value : null;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return attrs;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private isRangeToUpdate(range: Engine.ModelRange, allowedRanges: Engine.ModelRange[]) {
|
|
211
|
+
for (const allowedRange of allowedRanges) {
|
|
212
|
+
// A range is inside an element that will have the `linkHref` attribute. Do not modify its nodes.
|
|
213
|
+
if (allowedRange.containsRange(range)) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Inspired by @ckeditor/ckeditor5-link/src/unlinkcommand.js
|
|
223
|
+
*/
|
|
224
|
+
export class Typo3UnlinkCommand extends Core.Command {
|
|
225
|
+
public override refresh(): void {
|
|
226
|
+
const model = this.editor.model;
|
|
227
|
+
const selection = model.document.selection;
|
|
228
|
+
const selectedElement = selection.getSelectedElement();
|
|
229
|
+
|
|
230
|
+
if (LinkUtils.isLinkableElement(selectedElement, model.schema)) {
|
|
231
|
+
this.isEnabled = model.schema.checkAttribute(selectedElement, 'linkHref');
|
|
232
|
+
} else {
|
|
233
|
+
this.isEnabled = model.schema.checkAttributeInSelection(selection, 'linkHref');
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
public override execute(): void {
|
|
238
|
+
const model = this.editor.model;
|
|
239
|
+
const selection = model.document.selection;
|
|
240
|
+
|
|
241
|
+
model.change(writer => {
|
|
242
|
+
// Get ranges to unlink.
|
|
243
|
+
const rangesToUnlink = selection.isCollapsed
|
|
244
|
+
? [Typing.findAttributeRange(
|
|
245
|
+
selection.getFirstPosition(),
|
|
246
|
+
'linkHref',
|
|
247
|
+
selection.getAttribute('linkHref') as string,
|
|
248
|
+
model
|
|
249
|
+
)]
|
|
250
|
+
: model.schema.getValidRanges((selection.getRanges() as unknown) as any[], 'linkHref');
|
|
251
|
+
// Remove `linkHref` attribute from specified ranges.
|
|
252
|
+
for (const range of rangesToUnlink) {
|
|
253
|
+
writer.removeAttribute('linkHref', range);
|
|
254
|
+
writer.removeAttribute('linkTarget', range);
|
|
255
|
+
writer.removeAttribute('linkTitle', range);
|
|
256
|
+
writer.removeAttribute('linkRel', range);
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export class Typo3LinkEditing extends Core.Plugin {
|
|
263
|
+
static readonly pluginName = 'Typo3LinkEditing';
|
|
264
|
+
|
|
265
|
+
init(): void {
|
|
266
|
+
const editor = this.editor;
|
|
267
|
+
// @todo: Why is this needed? Remove.
|
|
268
|
+
(window as any).editor = editor;
|
|
269
|
+
|
|
270
|
+
editor.model.schema.extend('$text', { allowAttributes: ['linkTitle', 'linkTarget', 'linkRel', 'linkDataRteError'] });
|
|
271
|
+
|
|
272
|
+
const ghsDataFilter: DataFilter = editor.plugins.get('DataFilter');
|
|
273
|
+
ghsDataFilter.loadAllowedConfig([{ name: 'a', classes: true }]);
|
|
274
|
+
|
|
275
|
+
// linkDataRteError <=> data-rte-error
|
|
276
|
+
// This is used for marking broken links (e.g. by linkvalidator) when editing in RTE.
|
|
277
|
+
// Broken links are styled differently. This will not get persisted to the database.
|
|
278
|
+
editor.conversion.for('downcast').attributeToElement({
|
|
279
|
+
model: 'linkDataRteError',
|
|
280
|
+
view: (value: string | null, { writer }) => {
|
|
281
|
+
const linkElement = writer.createAttributeElement('a', { 'data-rte-error': value }, { priority: 5 });
|
|
282
|
+
writer.setCustomProperty('linkDataRteError', true, linkElement);
|
|
283
|
+
return linkElement;
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
editor.conversion.for('upcast').elementToAttribute({
|
|
287
|
+
view: { name: 'a', attributes: { 'data-rte-error': true } },
|
|
288
|
+
model: {
|
|
289
|
+
key: 'linkDataRteError',
|
|
290
|
+
value: (viewElement: ViewElement) => viewElement.getAttribute('data-rte-error')
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// linkTitle <=> title
|
|
295
|
+
editor.conversion.for('downcast').attributeToElement({
|
|
296
|
+
model: 'linkTitle',
|
|
297
|
+
view: (value: string | null, { writer }) => {
|
|
298
|
+
const linkElement = writer.createAttributeElement('a', { title: value }, { priority: 5 });
|
|
299
|
+
writer.setCustomProperty('linkTitle', true, linkElement);
|
|
300
|
+
return linkElement;
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
editor.conversion.for('upcast').elementToAttribute({
|
|
304
|
+
view: { name: 'a', attributes: { title: true } },
|
|
305
|
+
model: { key: 'linkTitle', value: (viewElement: ViewElement) => viewElement.getAttribute('title') }
|
|
306
|
+
});
|
|
307
|
+
// linkTarget <=> target
|
|
308
|
+
editor.conversion.for('downcast').attributeToElement({
|
|
309
|
+
model: 'linkTarget',
|
|
310
|
+
view: (value, { writer }) => {
|
|
311
|
+
const linkElement = writer.createAttributeElement('a', { target: value }, { priority: 5 });
|
|
312
|
+
writer.setCustomProperty('linkTarget', true, linkElement);
|
|
313
|
+
return linkElement;
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
editor.conversion.for('upcast').elementToAttribute({
|
|
317
|
+
view: { name: 'a', attributes: { target: true } },
|
|
318
|
+
model: { key: 'linkTarget', value: (viewElement: ViewElement) => viewElement.getAttribute('target') }
|
|
319
|
+
});
|
|
320
|
+
// linkRel <=> rel
|
|
321
|
+
editor.conversion.for('downcast').attributeToElement({
|
|
322
|
+
model: 'linkRel',
|
|
323
|
+
view: (value, { writer }) => {
|
|
324
|
+
const linkElement = writer.createAttributeElement('a', { rel: value }, { priority: 5 });
|
|
325
|
+
writer.setCustomProperty('linkRel', true, linkElement);
|
|
326
|
+
return linkElement;
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
editor.conversion.for('upcast').elementToAttribute({
|
|
330
|
+
view: { name: 'a', attributes: { rel: true } },
|
|
331
|
+
model: { key: 'linkRel', value: (viewElement: ViewElement) => viewElement.getAttribute('rel') }
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// overrides 'link' command, 'unlink' command is taken from CKEditor 5's `LinkEditing`
|
|
335
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
336
|
+
// @ts-ignore
|
|
337
|
+
editor.commands.add('link', new Typo3LinkCommand(editor));
|
|
338
|
+
editor.commands.add('unlink', new Typo3UnlinkCommand(editor));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export class Typo3LinkPreviewButtonView extends UI.ButtonView {
|
|
343
|
+
declare public href: string | undefined;
|
|
344
|
+
|
|
345
|
+
constructor(locale?: Utils.Locale) {
|
|
346
|
+
super(locale);
|
|
347
|
+
|
|
348
|
+
const bind = this.bindTemplate;
|
|
349
|
+
this.set({
|
|
350
|
+
href: undefined,
|
|
351
|
+
withText: true
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
this.setTemplate({
|
|
355
|
+
tag: 'span',
|
|
356
|
+
attributes: {
|
|
357
|
+
class: ['ck-link-toolbar__preview'],
|
|
358
|
+
title: bind.to('href'),
|
|
359
|
+
},
|
|
360
|
+
children: [{ text: bind.to('href') }]
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const VISUAL_SELECTION_MARKER_NAME = 'link-ui';
|
|
366
|
+
|
|
367
|
+
export class Typo3LinkUI extends Core.Plugin {
|
|
368
|
+
toolbarView: UI.ToolbarView;
|
|
369
|
+
balloon: UI.ContextualBalloon;
|
|
370
|
+
|
|
371
|
+
static get requires(): typeof Core.Plugin[] {
|
|
372
|
+
return [UI.ContextualBalloon, Link.LinkEditing] as const;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
static get pluginName(): string {
|
|
376
|
+
return 'Typo3LinkUI';
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
init() {
|
|
380
|
+
const editor = this.editor;
|
|
381
|
+
editor.editing.view.addObserver(Engine.ClickObserver);
|
|
382
|
+
|
|
383
|
+
this.balloon = editor.plugins.get(UI.ContextualBalloon);
|
|
384
|
+
|
|
385
|
+
this.createToolbarLinkButtons();
|
|
386
|
+
this.enableUserBalloonInteractions();
|
|
387
|
+
|
|
388
|
+
// Renders a fake visual selection marker on an expanded selection.
|
|
389
|
+
editor.conversion.for('editingDowncast').markerToHighlight({
|
|
390
|
+
model: VISUAL_SELECTION_MARKER_NAME,
|
|
391
|
+
view: {
|
|
392
|
+
classes: ['ck-fake-link-selection']
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// Renders a fake visual selection marker on a collapsed selection.
|
|
397
|
+
editor.conversion.for('editingDowncast').markerToElement({
|
|
398
|
+
model: VISUAL_SELECTION_MARKER_NAME,
|
|
399
|
+
view: {
|
|
400
|
+
name: 'span',
|
|
401
|
+
classes: ['ck-fake-link-selection', 'ck-fake-link-selection_collapsed']
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
private createToolbarView(): UI.ToolbarView {
|
|
407
|
+
const editor = this.editor;
|
|
408
|
+
const toolbarView = new UI.ToolbarView(editor.locale);
|
|
409
|
+
const toolbarItems = editor.config.get('link.toolbar');
|
|
410
|
+
toolbarView.fillFromConfig(toolbarItems, editor.ui.componentFactory);
|
|
411
|
+
// Close the panel on esc key press when the **link toolbar have focus**.
|
|
412
|
+
toolbarView.keystrokes.set('Esc', (data, cancel) => {
|
|
413
|
+
this.hideUI();
|
|
414
|
+
cancel();
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
// Register the toolbar, so it becomes available for Alt+F10 and Esc navigation.
|
|
418
|
+
// TODO this should be registered earlier to be able to open this toolbar without previously opening it by click or Ctrl+K
|
|
419
|
+
editor.ui.addToolbar(toolbarView, {
|
|
420
|
+
isContextual: true,
|
|
421
|
+
beforeFocus: () => {
|
|
422
|
+
if (this.getSelectedLinkElement() && !this.isToolbarVisible()) {
|
|
423
|
+
this.showUI();
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
afterBlur: () => {
|
|
427
|
+
this.hideUI();
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
return toolbarView;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
private createToolbarLinkButtons() {
|
|
434
|
+
const editor = this.editor;
|
|
435
|
+
const linkCommand = editor.commands.get('link');
|
|
436
|
+
const t = editor.t;
|
|
437
|
+
|
|
438
|
+
// Handle the `Ctrl+K` keystroke and show the panel.
|
|
439
|
+
editor.keystrokes.set(LinkUtils.LINK_KEYSTROKE, (keyEvtData, cancel) => {
|
|
440
|
+
// Prevent focusing the search bar in FF, Chrome and Edge. See https://github.com/ckeditor/ckeditor5/issues/4811.
|
|
441
|
+
cancel();
|
|
442
|
+
if (linkCommand.isEnabled) {
|
|
443
|
+
this.showUI();
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
// re-uses 'Link' plugin name -> original plugin 'Link' needs to be removed during runtime
|
|
448
|
+
editor.ui.componentFactory.add('link', locale => {
|
|
449
|
+
const linkButton = new UI.ButtonView(locale);
|
|
450
|
+
linkButton.isEnabled = true;
|
|
451
|
+
linkButton.label = t('Link');
|
|
452
|
+
linkButton.icon = IconLink;
|
|
453
|
+
linkButton.keystroke = LinkUtils.LINK_KEYSTROKE;
|
|
454
|
+
linkButton.tooltip = true;
|
|
455
|
+
linkButton.isToggleable = true;
|
|
456
|
+
linkButton.bind('isEnabled').to(linkCommand, 'isEnabled');
|
|
457
|
+
linkButton.bind('isOn').to(linkCommand, 'value', value => !!value);
|
|
458
|
+
this.listenTo(linkButton, 'execute', () => this.showUI());
|
|
459
|
+
return linkButton;
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
editor.ui.componentFactory.add('linkPreview', locale => {
|
|
463
|
+
const button = new Typo3LinkPreviewButtonView(locale);
|
|
464
|
+
const allowedProtocols = editor.config.get('link.allowedProtocols');
|
|
465
|
+
const linkCommand = editor.commands.get('link');
|
|
466
|
+
button.bind('isEnabled').to(linkCommand, 'value', href => !!href);
|
|
467
|
+
button.bind('href').to(linkCommand, 'value', href => {
|
|
468
|
+
return href && LinkUtils.ensureSafeUrl(href, allowedProtocols);
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
button.icon = undefined;
|
|
472
|
+
|
|
473
|
+
const setHref = (href: string) => {
|
|
474
|
+
if (!href) {
|
|
475
|
+
button.label = undefined;
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
button.label = href;
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
setHref(linkCommand.value);
|
|
483
|
+
this.listenTo(linkCommand, 'change:value', (evt, name, href) => {
|
|
484
|
+
setHref(href);
|
|
485
|
+
});
|
|
486
|
+
return button;
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
editor.ui.componentFactory.add('unlink', locale => {
|
|
490
|
+
const unlinkCommand = editor.commands.get('unlink');
|
|
491
|
+
const button = new UI.ButtonView(locale);
|
|
492
|
+
const t = locale.t;
|
|
493
|
+
button.set({
|
|
494
|
+
label: t('Unlink'),
|
|
495
|
+
icon: IconUnlink,
|
|
496
|
+
tooltip: true
|
|
497
|
+
});
|
|
498
|
+
button.bind('isEnabled').to(unlinkCommand);
|
|
499
|
+
this.listenTo(button, 'execute', () => {
|
|
500
|
+
editor.execute('unlink');
|
|
501
|
+
this.hideUI();
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
return button;
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
editor.ui.componentFactory.add('editLink', locale => {
|
|
508
|
+
const linkCommand = editor.commands.get('link');
|
|
509
|
+
const button = new UI.ButtonView(locale);
|
|
510
|
+
const t = locale.t;
|
|
511
|
+
button.set({
|
|
512
|
+
label: t('Edit link'),
|
|
513
|
+
icon: IconPencil,
|
|
514
|
+
tooltip: true
|
|
515
|
+
});
|
|
516
|
+
button.bind('isEnabled').to(linkCommand);
|
|
517
|
+
this.listenTo(button, 'execute', () => {
|
|
518
|
+
this.openLinkBrowser(editor);
|
|
519
|
+
});
|
|
520
|
+
return button;
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
private enableUserBalloonInteractions() {
|
|
525
|
+
const viewDocument = this.editor.editing.view.document;
|
|
526
|
+
|
|
527
|
+
this.listenTo(viewDocument, 'click', () => {
|
|
528
|
+
const parentLink = this.getSelectedLinkElement();
|
|
529
|
+
if (parentLink) {
|
|
530
|
+
this.showUI();
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
this.editor.keystrokes.set('Esc', (data, cancel) => {
|
|
535
|
+
if (this.isUIVisible()) {
|
|
536
|
+
this.hideUI();
|
|
537
|
+
cancel();
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
private addToolbarView(): void {
|
|
543
|
+
if (!this.toolbarView) {
|
|
544
|
+
this.toolbarView = this.createToolbarView();
|
|
545
|
+
}
|
|
546
|
+
if (this.isToolbarInPanel()) {
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
this.balloon.add({
|
|
551
|
+
view: this.toolbarView,
|
|
552
|
+
position: this.getBalloonPositionData(),
|
|
553
|
+
balloonClassName: 'ck-toolbar-container',
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
private hideUI(updateFocus: boolean = true): void {
|
|
558
|
+
const editor = this.editor;
|
|
559
|
+
this.stopListening(editor.ui, 'update');
|
|
560
|
+
this.stopListening(this.balloon, 'change:visibleView');
|
|
561
|
+
|
|
562
|
+
if (updateFocus) {
|
|
563
|
+
editor.editing.view.focus();
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (this.isToolbarInPanel()) {
|
|
567
|
+
this.balloon.remove(this.toolbarView);
|
|
568
|
+
}
|
|
569
|
+
this.hideFakeVisualSelection();
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
private showUI(): void {
|
|
573
|
+
if (!this.getSelectedLinkElement()) {
|
|
574
|
+
this.showFakeVisualSelection();
|
|
575
|
+
this.openLinkBrowser(this.editor);
|
|
576
|
+
} else {
|
|
577
|
+
this.addToolbarView();
|
|
578
|
+
this.balloon.showStack('main');
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
this.startUpdatingUI();
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
private startUpdatingUI(): void {
|
|
585
|
+
const editor = this.editor;
|
|
586
|
+
const viewDocument = editor.editing.view.document;
|
|
587
|
+
|
|
588
|
+
let prevSelectedLink = this.getSelectedLinkElement();
|
|
589
|
+
let prevSelectionParent = getSelectionParent();
|
|
590
|
+
|
|
591
|
+
const update = () => {
|
|
592
|
+
const selectedLink = this.getSelectedLinkElement();
|
|
593
|
+
const selectionParent = getSelectionParent();
|
|
594
|
+
|
|
595
|
+
if ((prevSelectedLink && !selectedLink) ||
|
|
596
|
+
(!prevSelectedLink && selectionParent !== prevSelectionParent)) {
|
|
597
|
+
this.hideUI();
|
|
598
|
+
}
|
|
599
|
+
else if (this.isUIVisible()) {
|
|
600
|
+
this.balloon.updatePosition(this.getBalloonPositionData());
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
prevSelectedLink = selectedLink;
|
|
604
|
+
prevSelectionParent = selectionParent;
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
function getSelectionParent() {
|
|
608
|
+
return viewDocument.selection.focus.getAncestors()
|
|
609
|
+
.reverse()
|
|
610
|
+
.find(node => node.is('element'));
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
this.listenTo(editor.ui, 'update', update);
|
|
614
|
+
this.listenTo(this.balloon, 'change:visibleView', update);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
private isToolbarInPanel(): boolean {
|
|
618
|
+
return !!this.toolbarView && this.balloon.hasView(this.toolbarView);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
private isToolbarVisible(): boolean {
|
|
622
|
+
return !!this.toolbarView && this.balloon.visibleView === this.toolbarView;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
private isUIVisible(): boolean {
|
|
626
|
+
return this.isToolbarVisible();
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
private getBalloonPositionData(): any {
|
|
630
|
+
const view = this.editor.editing.view;
|
|
631
|
+
const model = this.editor.model;
|
|
632
|
+
const viewDocument = view.document;
|
|
633
|
+
let target = null;
|
|
634
|
+
|
|
635
|
+
if (model.markers.has(VISUAL_SELECTION_MARKER_NAME)) {
|
|
636
|
+
// There are cases when we highlight selection using a marker (#7705, #4721).
|
|
637
|
+
const markerViewElements = Array.from(this.editor.editing.mapper.markerNameToElements(VISUAL_SELECTION_MARKER_NAME));
|
|
638
|
+
const newRange = view.createRange(
|
|
639
|
+
view.createPositionBefore(markerViewElements[0]),
|
|
640
|
+
view.createPositionAfter(markerViewElements[markerViewElements.length - 1])
|
|
641
|
+
);
|
|
642
|
+
|
|
643
|
+
target = view.domConverter.viewRangeToDom(newRange);
|
|
644
|
+
} else {
|
|
645
|
+
// Make sure the target is calculated on demand at the last moment because a cached DOM range
|
|
646
|
+
// (which is very fragile) can desynchronize with the state of the editing view if there was
|
|
647
|
+
// any rendering done in the meantime. This can happen, for instance, when an inline widget
|
|
648
|
+
// gets unlinked.
|
|
649
|
+
target = () => {
|
|
650
|
+
const targetLink = this.getSelectedLinkElement();
|
|
651
|
+
|
|
652
|
+
return targetLink ?
|
|
653
|
+
// When selection is inside link element, then attach panel to this element.
|
|
654
|
+
view.domConverter.mapViewToDom(targetLink) :
|
|
655
|
+
// Otherwise attach panel to the selection.
|
|
656
|
+
view.domConverter.viewRangeToDom(viewDocument.selection.getFirstRange());
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
return { target };
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
private getSelectedLinkElement(): ViewAttributeElement | null {
|
|
664
|
+
const view = this.editor.editing.view;
|
|
665
|
+
const selection = view.document.selection;
|
|
666
|
+
const selectedElement = selection.getSelectedElement();
|
|
667
|
+
|
|
668
|
+
// The selection is collapsed or some widget is selected (especially inline widget).
|
|
669
|
+
if (selection.isCollapsed || selectedElement && Widget.isWidget(selectedElement)) {
|
|
670
|
+
return this.findLinkElementAncestor(selection.getFirstPosition());
|
|
671
|
+
} else {
|
|
672
|
+
// The range for fully selected link is usually anchored in adjacent text nodes.
|
|
673
|
+
// Trim it to get closer to the actual link element.
|
|
674
|
+
const range = selection.getFirstRange().getTrimmed();
|
|
675
|
+
const startLink = this.findLinkElementAncestor(range.start);
|
|
676
|
+
const endLink = this.findLinkElementAncestor(range.end);
|
|
677
|
+
|
|
678
|
+
if (!startLink || startLink != endLink) {
|
|
679
|
+
return null;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Check if the link element is fully selected.
|
|
683
|
+
if (view.createRangeIn(startLink).getTrimmed().isEqual(range)) {
|
|
684
|
+
return startLink;
|
|
685
|
+
} else {
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
private showFakeVisualSelection(): void {
|
|
692
|
+
const model = this.editor.model;
|
|
693
|
+
|
|
694
|
+
model.change(writer => {
|
|
695
|
+
const range = model.document.selection.getFirstRange();
|
|
696
|
+
|
|
697
|
+
if (model.markers.has(VISUAL_SELECTION_MARKER_NAME)) {
|
|
698
|
+
writer.updateMarker(VISUAL_SELECTION_MARKER_NAME, { range });
|
|
699
|
+
} else {
|
|
700
|
+
if (range.start.isAtEnd) {
|
|
701
|
+
const startPosition = range.start.getLastMatchingPosition(
|
|
702
|
+
({ item }) => !model.schema.isContent(item),
|
|
703
|
+
{
|
|
704
|
+
startPosition: null,
|
|
705
|
+
boundaries: range
|
|
706
|
+
}
|
|
707
|
+
);
|
|
708
|
+
|
|
709
|
+
writer.addMarker(VISUAL_SELECTION_MARKER_NAME, {
|
|
710
|
+
usingOperation: false,
|
|
711
|
+
affectsData: false,
|
|
712
|
+
range: writer.createRange(startPosition, range.end)
|
|
713
|
+
});
|
|
714
|
+
} else {
|
|
715
|
+
writer.addMarker(VISUAL_SELECTION_MARKER_NAME, {
|
|
716
|
+
usingOperation: false,
|
|
717
|
+
affectsData: false,
|
|
718
|
+
range
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
private hideFakeVisualSelection() {
|
|
726
|
+
const model = this.editor.model;
|
|
727
|
+
if (model.markers.has(VISUAL_SELECTION_MARKER_NAME)) {
|
|
728
|
+
model.change(writer => {
|
|
729
|
+
writer.removeMarker(VISUAL_SELECTION_MARKER_NAME);
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
private findLinkElementAncestor(position: any) {
|
|
735
|
+
return position.getAncestors().find((ancestor: any) => LinkUtils.isLinkElement(ancestor));
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
private openLinkBrowser(editor: Core.Editor): void {
|
|
739
|
+
const linkCommand = editor.commands.get('link') as unknown as Typo3LinkCommand;
|
|
740
|
+
let additionalParameters = '';
|
|
741
|
+
|
|
742
|
+
if (linkCommand.value) {
|
|
743
|
+
additionalParameters += '&P[curUrl][url]=' + encodeURIComponent(linkCommand.value);
|
|
744
|
+
for (const [attr, value] of Object.entries(linkCommand.attrs)) {
|
|
745
|
+
additionalParameters += '&P[curUrl][' + encodeURIComponent(attr) + ']=' + encodeURIComponent(value);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
this.openElementBrowser(
|
|
749
|
+
editor,
|
|
750
|
+
'Link',
|
|
751
|
+
this.makeUrlFromModulePath(
|
|
752
|
+
editor,
|
|
753
|
+
editor.config.get('typo3link')?.routeUrl,
|
|
754
|
+
additionalParameters
|
|
755
|
+
));
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
private makeUrlFromModulePath(editor: Core.Editor, routeUrl: string, parameters: string) {
|
|
759
|
+
return routeUrl
|
|
760
|
+
+ (routeUrl.indexOf('?') === -1 ? '?' : '&')
|
|
761
|
+
+ '&contentsLanguage=' + 'en'// editor.config.contentsLanguage
|
|
762
|
+
+ '&editorId=' + '123' // editor.id
|
|
763
|
+
+ (parameters ? parameters : '');
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
private openElementBrowser(editor: Core.Editor, title: string, url: string) {
|
|
767
|
+
const modal = modalObject.advanced({
|
|
768
|
+
type: modalObject.types.iframe,
|
|
769
|
+
title: title,
|
|
770
|
+
content: url,
|
|
771
|
+
size: modalObject.sizes.large,
|
|
772
|
+
callback: (currentModal: ModalElement) => {
|
|
773
|
+
// Add the instance to the iframe itself
|
|
774
|
+
currentModal.userData.editor = editor;
|
|
775
|
+
currentModal.userData.selectionStartPosition = editor.model.document.selection.getFirstPosition();
|
|
776
|
+
currentModal.userData.selectionEndPosition = editor.model.document.selection.getLastPosition();
|
|
777
|
+
|
|
778
|
+
// @todo: is this used at all?
|
|
779
|
+
// should maybe be a regular modal attribute then
|
|
780
|
+
currentModal.querySelector('.t3js-modal-body')?.setAttribute('id', '123' /*editor.id*/);
|
|
781
|
+
}
|
|
782
|
+
});
|
|
783
|
+
modal.addEventListener('typo3-modal-hide', (): void => {
|
|
784
|
+
this.hideUI(false);
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
export class Typo3Link extends Core.Plugin {
|
|
790
|
+
static readonly overrides?: Array<typeof Core.Plugin> = [Link.Link];
|
|
791
|
+
|
|
792
|
+
static get requires(): Array<typeof Core.Plugin|string> {
|
|
793
|
+
return ['GeneralHtmlSupport', Link.LinkEditing, Link.AutoLink, Typo3LinkEditing, Typo3LinkUI] as const;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
static get pluginName(): string {
|
|
797
|
+
return 'Typo3Link';
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
declare module '@ckeditor/ckeditor5-core' {
|
|
804
|
+
interface EditorConfig {
|
|
805
|
+
typo3link?: Typo3LinkConfig;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// Provided for backwards compatibility
|
|
810
|
+
export default Typo3Link;
|