diff --git a/composer.json b/composer.json index eb28ea2..2fe695f 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ "source": "http://cgit.drupalcode.org/linkit" }, "require" : { - "drupal/core": "^8.7.7 || ^9" + "drupal/core": "^9 || ^10" }, "license": "GPL-2.0-or-later" } diff --git a/config/schema/linkit.schema.yml b/config/schema/linkit.schema.yml index c6e3f1d..d5e45e7 100644 --- a/config/schema/linkit.schema.yml +++ b/config/schema/linkit.schema.yml @@ -112,3 +112,19 @@ ckeditor.plugin.drupallink: linkit_profile: type: string label: 'Linkit profile' + +ckeditor5.plugin.linkit_extension: + type: mapping + label: Linkit + constraints: + Callback: [\Drupal\linkit\Plugin\CKEditor5Plugin\Linkit, requireProfileIfEnabled] + mapping: + linkit_enabled: + type: boolean + label: 'Use Linkit' + linkit_profile: + type: string + label: 'Linkit profile' + constraints: + Choice: + callback: \Drupal\linkit\Plugin\CKEditor5Plugin\Linkit::validChoices diff --git a/js/build/linkit.js b/js/build/linkit.js new file mode 100644 index 0000000..c7104b5 --- /dev/null +++ b/js/build/linkit.js @@ -0,0 +1 @@ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.CKEditor5=e():(t.CKEditor5=t.CKEditor5||{},t.CKEditor5.linkit=e())}(self,(()=>(()=>{var t={"ckeditor5/src/core.js":(t,e,i)=>{t.exports=i("dll-reference CKEditor5.dll")("./src/core.js")},"ckeditor5/src/typing.js":(t,e,i)=>{t.exports=i("dll-reference CKEditor5.dll")("./src/typing.js")},"dll-reference CKEditor5.dll":t=>{"use strict";t.exports=CKEditor5.dll}},e={};function i(n){var o=e[n];if(void 0!==o)return o.exports;var s=e[n]={exports:{}};return t[n](s,s.exports,i),s.exports}i.d=(t,e)=>{for(var n in e)i.o(e,n)&&!i.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:e[n]})},i.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e);var n={};return(()=>{"use strict";i.d(n,{default:()=>u});var t=i("ckeditor5/src/core.js"),e=i("ckeditor5/src/typing.js");class o extends t.Plugin{init(){this.attrs=["data-entity-type","data-entity-uuid","data-entity-substitution"],this._allowAndConvertExtraAttributes(),this._removeExtraAttributesOnUnlinkCommandExecute(),this._refreshExtraAttributeValues(),this._addExtraAttributesOnLinkCommandExecute()}_allowAndConvertExtraAttributes(){const t=this.editor;t.model.schema.extend("$text",{allowAttributes:this.attrs}),this.attrs.forEach((e=>{t.conversion.for("downcast").attributeToElement({model:e,view:(t,{writer:i})=>{const n=i.createAttributeElement("a",{[e]:t},{priority:5});return i.setCustomProperty("link",!0,n),n}}),t.conversion.for("upcast").elementToAttribute({view:{name:"a",attributes:{[e]:!0}},model:{key:e,value:t=>t.getAttribute(e)}})}))}_addExtraAttributesOnLinkCommandExecute(){const t=this.editor,e=t.commands.get("link");let i=!1;e.on("execute",((e,n)=>{if(n.length<3)return;if(i)return void(i=!1);e.stop(),i=!0;const o=n[n.length-1],s=this.editor.model,r=s.document.selection;s.change((e=>{t.execute("link",...n);const i=r.getFirstPosition();this.attrs.forEach((t=>{if(r.isCollapsed){const n=i.textNode||i.nodeBefore;o[t]?e.setAttribute(t,o[t],e.createRangeOn(n)):e.removeAttribute(t,e.createRangeOn(n)),e.removeSelectionAttribute(t)}else{const i=s.schema.getValidRanges(r.getRanges(),t);for(const n of i)o[t]?e.setAttribute(t,o[t],n):e.removeAttribute(t,n)}}))}))}),{priority:"high"})}_removeExtraAttributesOnUnlinkCommandExecute(){const t=this.editor,i=t.commands.get("unlink"),n=this.editor.model,o=n.document.selection;let s=!1;i.on("execute",(i=>{s||(i.stop(),n.change((()=>{s=!0,t.execute("unlink"),s=!1,n.change((t=>{let i;this.attrs.forEach((s=>{i=o.isCollapsed?[(0,e.findAttributeRange)(o.getFirstPosition(),s,o.getAttribute(s),n)]:n.schema.getValidRanges(o.getRanges(),s);for(const e of i)t.removeAttribute(s,e)}))}))})))}),{priority:"high"})}_refreshExtraAttributeValues(){const t=this.editor,e=this.attrs,i=t.commands.get("link"),n=this.editor.model,o=n.document.selection;e.forEach((t=>{i.set(t,null)})),n.document.on("change",(()=>{e.forEach((t=>{i[t]=o.getAttribute(t)}))}))}static get pluginName(){return"LinkitEditing"}}const s=jQuery;function r(t,e){var i=s("
',
+ ],
+ ],
+ ];
+ FilterFormat::create([
+ 'format' => 'linkit_disabled',
+ 'name' => 'Linkit disabled',
+ 'filters' => $filter_config,
+ ])->setSyncing(TRUE)->save();
+ FilterFormat::create([
+ 'format' => 'linkit_enabled_misconfigured_format',
+ 'name' => 'Linkit enabled on a misconfigured format',
+ 'filters' => $filter_config,
+ ])->setSyncing(TRUE)->save();
+ FilterFormat::create([
+ 'format' => 'linkit_enabled',
+ 'name' => 'Linkit enabled on a well-configured format',
+ 'filters' => [
+ 'filter_html' => [
+ 'status' => 1,
+ 'settings' => [
+ 'allowed_html' => '
',
+ ],
+ ],
+ ],
+ ])->setSyncing(TRUE)->save();
+
+ $generate_editor_settings = function (array $linkit_cke4_settings) {
+ return [
+ 'toolbar' => [
+ 'rows' => [
+ 0 => [
+ [
+ 'name' => 'Basic Formatting',
+ 'items' => [
+ 'Bold',
+ 'Format',
+ 'DrupalLink'
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'plugins' => [
+ 'drupallink' => $linkit_cke4_settings,
+ ],
+ ];
+ };
+
+ Editor::create([
+ 'format' => 'linkit_disabled',
+ 'editor' => 'ckeditor',
+ 'settings' => $generate_editor_settings([
+ 'linkit_enabled' => FALSE,
+ 'linkit_profile' => '',
+ ]),
+ ])->setSyncing(TRUE)->save();
+ Editor::create([
+ 'format' => 'linkit_enabled_misconfigured_format',
+ 'editor' => 'ckeditor',
+ 'settings' => $generate_editor_settings([
+ 'linkit_enabled' => TRUE,
+ 'linkit_profile' => 'default',
+ ]),
+ ])->setSyncing(TRUE)->save();
+ Editor::create([
+ 'format' => 'linkit_enabled',
+ 'editor' => 'ckeditor',
+ 'settings' => $generate_editor_settings([
+ 'linkit_enabled' => TRUE,
+ 'linkit_profile' => 'default',
+ ]),
+ ])->setSyncing(TRUE)->save();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function provider() {
+ $expected_ckeditor5_toolbar = [
+ 'items' => [
+ 'bold',
+ 'link',
+ ],
+ ];
+
+ yield "linkit disabled" => [
+ 'format_id' => 'linkit_disabled',
+ 'filters_to_drop' => [],
+ 'expected_ckeditor5_settings' => [
+ 'toolbar' => $expected_ckeditor5_toolbar,
+ 'plugins' => [],
+ ],
+ 'expected_superset' => '',
+ 'expected_fundamental_compatibility_violations' => [],
+ 'expected_db_logs' => [],
+ 'expected_messages' => [],
+ ];
+
+ yield "linkit enabled on a misconfigured text format" => [
+ 'format_id' => 'linkit_enabled_misconfigured_format',
+ 'filters_to_drop' => [],
+ 'expected_ckeditor5_settings' => [
+ 'toolbar' => $expected_ckeditor5_toolbar,
+ 'plugins' => [
+ 'linkit_extension' => [
+ 'linkit_enabled' => TRUE,
+ 'linkit_profile' => 'default',
+ ],
+ ],
+ ],
+ 'expected_superset' => '',
+ 'expected_fundamental_compatibility_violations' => [],
+ 'expected_db_logs' => [],
+ 'expected_messages' => [
+ 'warning' => [
+ 'Updating to CKEditor 5 added support for some previously unsupported tags/attributes. A plugin introduced support for the following: These attributes: data-entity-type (for <a>), data-entity-uuid (for <a>), data-entity-substitution (for <a>); Additional details are available in your logs.',
+ ],
+ ],
+ ];
+
+ yield "linkit enabled on a well-configured text format" => [
+ 'format_id' => 'linkit_enabled',
+ 'filters_to_drop' => [],
+ 'expected_ckeditor5_settings' => [
+ 'toolbar' => $expected_ckeditor5_toolbar,
+ 'plugins' => [
+ 'linkit_extension' => [
+ 'linkit_enabled' => TRUE,
+ 'linkit_profile' => 'default',
+ ],
+ ],
+ ],
+ 'expected_superset' => '',
+ 'expected_fundamental_compatibility_violations' => [],
+ 'expected_db_logs' => [],
+ 'expected_messages' => [],
+ ];
+
+ // Verify that none of the core test cases are broken; especially important
+ // for Linkit since it extends the behavior of Drupal core.
+ foreach (parent::provider() as $label => $case) {
+ yield $label => $case;
+ }
+ }
+
+}
diff --git a/tests/src/Kernel/LinkitEditorLinkDialogTest.php b/tests/src/Kernel/LinkitEditorLinkDialogTest.php
index 0a1963b..16d0645 100644
--- a/tests/src/Kernel/LinkitEditorLinkDialogTest.php
+++ b/tests/src/Kernel/LinkitEditorLinkDialogTest.php
@@ -71,6 +71,7 @@ class LinkitEditorLinkDialogTest extends LinkitKernelTestBase {
$format->save();
// Set up editor.
+ $ckeditor = $this->container->get('plugin.manager.editor')->createInstance('ckeditor');
$this->editor = Editor::create([
'format' => 'filtered_html',
'editor' => 'ckeditor',
@@ -82,7 +83,7 @@ class LinkitEditorLinkDialogTest extends LinkitKernelTestBase {
'linkit_profile' => $this->linkitProfile->id(),
],
],
- ]);
+ ] + $ckeditor->getDefaultSettings());
$this->editor->save();
}
diff --git a/tests/src/Kernel/ValidatorsTest.php b/tests/src/Kernel/ValidatorsTest.php
new file mode 100644
index 0000000..9d7fa61
--- /dev/null
+++ b/tests/src/Kernel/ValidatorsTest.php
@@ -0,0 +1,136 @@
+installConfig(['linkit']);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function provider(): array {
+ $linkit_test_cases_toolbar_settings = ['items' => ['link']];
+
+ $data = [];
+ $data['VALID: installing the linkit module without configuring the existing text editors'] = [
+ 'settings' => [
+ 'toolbar' => $linkit_test_cases_toolbar_settings,
+ 'plugins' => [],
+ ],
+ 'violations' => [],
+ ];
+ $data['INVALID: linkit — invalid manually created configuration'] = [
+ 'settings' => [
+ 'toolbar' => $linkit_test_cases_toolbar_settings,
+ 'plugins' => [
+ 'linkit_extension' => [
+ 'linkit_enabled' => 'no',
+ ],
+ ],
+ ],
+ 'violations' => [
+ 'settings.plugins.linkit_extension.linkit_enabled' => 'This value should be of the correct primitive type.',
+ ],
+ ];
+ $data['VALID: linkit off'] = [
+ 'settings' => [
+ 'toolbar' => $linkit_test_cases_toolbar_settings,
+ 'plugins' => [
+ 'linkit_extension' => [
+ 'linkit_enabled' => FALSE,
+ ],
+ ],
+ ],
+ 'violations' => [],
+ ];
+ $data['VALID: linkit off, profile selected'] = [
+ 'settings' => [
+ 'toolbar' => $linkit_test_cases_toolbar_settings,
+ 'plugins' => [
+ 'linkit_extension' => [
+ 'linkit_enabled' => TRUE,
+ 'linkit_profile' => 'default',
+ ],
+ ],
+ ],
+ 'violations' => [],
+ ];
+ $data['INVALID: linkit on, no profile selected'] = [
+ 'settings' => [
+ 'toolbar' => $linkit_test_cases_toolbar_settings,
+ 'plugins' => [
+ 'linkit_extension' => [
+ 'linkit_enabled' => TRUE,
+ ],
+ ],
+ ],
+ 'violations' => [
+ 'settings.plugins.linkit_extension.linkit_profile' => 'Linkit is enabled, please select the Linkit profile you wish to use.',
+ ],
+ ];
+ $data['INVALID: linkit on, non-existent profile selected'] = [
+ 'settings' => [
+ 'toolbar' => $linkit_test_cases_toolbar_settings,
+ 'plugins' => [
+ 'linkit_extension' => [
+ 'linkit_enabled' => TRUE,
+ 'linkit_profile' => 'nonexistent',
+ ],
+ ],
+ ],
+ 'violations' => [
+ 'settings.plugins.linkit_extension.linkit_profile' => 'The value you selected is not a valid choice.',
+ ],
+ ];
+ $data['VALID: linkit on, existing profile selected'] = [
+ 'settings' => [
+ 'toolbar' => $linkit_test_cases_toolbar_settings,
+ 'plugins' => [
+ 'linkit_extension' => [
+ 'linkit_enabled' => TRUE,
+ 'linkit_profile' => 'default',
+ ],
+ ],
+ ],
+ 'violations' => [],
+ ];
+ return $data;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function providerPair(): array {
+ // Linkit is 100% independent of the text format, so no need for this test.
+ return [];
+ }
+
+}
diff --git a/webpack.config.js b/webpack.config.js
new file mode 100644
index 0000000..afb1bbc
--- /dev/null
+++ b/webpack.config.js
@@ -0,0 +1,57 @@
+const path = require('path');
+const fs = require('fs');
+const webpack = require('webpack');
+const TerserPlugin = require('terser-webpack-plugin');
+
+function getDirectories(srcpath) {
+ return fs
+ .readdirSync(srcpath)
+ .filter((item) => fs.statSync(path.join(srcpath, item)).isDirectory());
+}
+
+module.exports = [];
+// Loop through every subdirectory in src, each a different plugin, and build
+// each one in ./build.
+getDirectories('./js/ckeditor5_plugins').forEach((dir) => {
+ const bc = {
+ mode: 'production',
+ optimization: {
+ minimize: true,
+ minimizer: [
+ new TerserPlugin({
+ terserOptions: {
+ format: {
+ comments: false,
+ },
+ },
+ test: /\.js(\?.*)?$/i,
+ extractComments: false,
+ }),
+ ],
+ moduleIds: 'named',
+ },
+ entry: {
+ path: path.resolve(__dirname, 'js/ckeditor5_plugins', dir, 'src/index.js')
+ },
+ output: {
+ path: path.resolve(__dirname, './js/build'),
+ filename: `${dir}.js`,
+ library: ['CKEditor5', dir],
+ libraryTarget: 'umd',
+ libraryExport: 'default'
+ },
+ plugins: [
+ new webpack.DllReferencePlugin({
+ manifest: require('./node_modules/ckeditor5/build/ckeditor5-dll.manifest.json'), // eslint-disable-line global-require, import/no-unresolved
+ scope: 'ckeditor5/src',
+ name: 'CKEditor5.dll',
+ }),
+ ],
+ module: {
+ rules: [{ test: /\.svg$/, use: 'raw-loader' }],
+ },
+ devtool: false,
+ };
+
+ module.exports.push(bc);
+});