From aac547934cf9300ad449ea460e3074c4ddb633d8 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 13 Jun 2025 15:28:11 +0100 Subject: [PATCH 01/12] Deps: Bumped composer php package versions --- composer.lock | 194 +++++++++++++++++++++++--------------------------- 1 file changed, 90 insertions(+), 104 deletions(-) diff --git a/composer.lock b/composer.lock index c8e4d16b6..b7369550a 100644 --- a/composer.lock +++ b/composer.lock @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.343.22", + "version": "3.344.6", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "174cc187df3bde52c21e9c00a4e99610a08732a3" + "reference": "eb0bc621472592545539329499961a15a3f9f6dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/174cc187df3bde52c21e9c00a4e99610a08732a3", - "reference": "174cc187df3bde52c21e9c00a4e99610a08732a3", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/eb0bc621472592545539329499961a15a3f9f6dc", + "reference": "eb0bc621472592545539329499961a15a3f9f6dc", "shasum": "" }, "require": { @@ -153,9 +153,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.343.22" + "source": "https://github.com/aws/aws-sdk-php/tree/3.344.6" }, - "time": "2025-05-30T18:11:02+00:00" + "time": "2025-06-12T18:03:59+00:00" }, { "name": "bacon/bacon-qr-code", @@ -1740,16 +1740,16 @@ }, { "name": "laravel/framework", - "version": "v11.45.0", + "version": "v11.45.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "d0730deb427632004d24801be7ca1ed2c10fbc4e" + "reference": "b09ba32795b8e71df10856a2694706663984a239" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/d0730deb427632004d24801be7ca1ed2c10fbc4e", - "reference": "d0730deb427632004d24801be7ca1ed2c10fbc4e", + "url": "https://api.github.com/repos/laravel/framework/zipball/b09ba32795b8e71df10856a2694706663984a239", + "reference": "b09ba32795b8e71df10856a2694706663984a239", "shasum": "" }, "require": { @@ -1951,7 +1951,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-05-20T15:15:58+00:00" + "time": "2025-06-03T14:01:40+00:00" }, { "name": "laravel/prompts", @@ -3285,16 +3285,16 @@ }, { "name": "nesbot/carbon", - "version": "3.9.1", + "version": "3.10.0", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "ced71f79398ece168e24f7f7710462f462310d4d" + "reference": "c1397390dd0a7e0f11660f0ae20f753d88c1f3d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/ced71f79398ece168e24f7f7710462f462310d4d", - "reference": "ced71f79398ece168e24f7f7710462f462310d4d", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/c1397390dd0a7e0f11660f0ae20f753d88c1f3d9", + "reference": "c1397390dd0a7e0f11660f0ae20f753d88c1f3d9", "shasum": "" }, "require": { @@ -3302,9 +3302,9 @@ "ext-json": "*", "php": "^8.1", "psr/clock": "^1.0", - "symfony/clock": "^6.3 || ^7.0", + "symfony/clock": "^6.3.12 || ^7.0", "symfony/polyfill-mbstring": "^1.0", - "symfony/translation": "^4.4.18 || ^5.2.1|| ^6.0 || ^7.0" + "symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0" }, "provide": { "psr/clock-implementation": "1.0" @@ -3312,14 +3312,13 @@ "require-dev": { "doctrine/dbal": "^3.6.3 || ^4.0", "doctrine/orm": "^2.15.2 || ^3.0", - "friendsofphp/php-cs-fixer": "^3.57.2", + "friendsofphp/php-cs-fixer": "^3.75.0", "kylekatarnls/multi-tester": "^2.5.3", - "ondrejmirtes/better-reflection": "^6.25.0.4", "phpmd/phpmd": "^2.15.0", - "phpstan/extension-installer": "^1.3.1", - "phpstan/phpstan": "^1.11.2", - "phpunit/phpunit": "^10.5.20", - "squizlabs/php_codesniffer": "^3.9.0" + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.17", + "phpunit/phpunit": "^10.5.46", + "squizlabs/php_codesniffer": "^3.13.0" }, "bin": [ "bin/carbon" @@ -3387,7 +3386,7 @@ "type": "tidelift" } ], - "time": "2025-05-01T19:51:51+00:00" + "time": "2025-06-12T10:24:28+00:00" }, { "name": "nette/schema", @@ -3453,16 +3452,16 @@ }, { "name": "nette/utils", - "version": "v4.0.6", + "version": "v4.0.7", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "ce708655043c7050eb050df361c5e313cf708309" + "reference": "e67c4061eb40b9c113b218214e42cb5a0dda28f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/ce708655043c7050eb050df361c5e313cf708309", - "reference": "ce708655043c7050eb050df361c5e313cf708309", + "url": "https://api.github.com/repos/nette/utils/zipball/e67c4061eb40b9c113b218214e42cb5a0dda28f2", + "reference": "e67c4061eb40b9c113b218214e42cb5a0dda28f2", "shasum": "" }, "require": { @@ -3533,9 +3532,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.0.6" + "source": "https://github.com/nette/utils/tree/v4.0.7" }, - "time": "2025-03-30T21:06:30+00:00" + "time": "2025-06-03T04:55:08+00:00" }, { "name": "nikic/php-parser", @@ -4775,20 +4774,20 @@ }, { "name": "ramsey/uuid", - "version": "4.7.6", + "version": "4.8.1", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "91039bc1faa45ba123c4328958e620d382ec7088" + "reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/91039bc1faa45ba123c4328958e620d382ec7088", - "reference": "91039bc1faa45ba123c4328958e620d382ec7088", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28", + "reference": "fdf4dd4e2ff1813111bd0ad58d7a1ddbb5b56c28", "shasum": "" }, "require": { - "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12", + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13", "ext-json": "*", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" @@ -4797,26 +4796,23 @@ "rhumsaa/uuid": "self.version" }, "require-dev": { - "captainhook/captainhook": "^5.10", + "captainhook/captainhook": "^5.25", "captainhook/plugin-composer": "^5.3", - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "doctrine/annotations": "^1.8", - "ergebnis/composer-normalize": "^2.15", - "mockery/mockery": "^1.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "ergebnis/composer-normalize": "^2.47", + "mockery/mockery": "^1.6", "paragonie/random-lib": "^2", - "php-mock/php-mock": "^2.2", - "php-mock/php-mock-mockery": "^1.3", - "php-parallel-lint/php-parallel-lint": "^1.1", - "phpbench/phpbench": "^1.0", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-mockery": "^1.1", - "phpstan/phpstan-phpunit": "^1.1", - "phpunit/phpunit": "^8.5 || ^9", - "ramsey/composer-repl": "^1.4", - "slevomat/coding-standard": "^8.4", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "^4.9" + "php-mock/php-mock": "^2.6", + "php-mock/php-mock-mockery": "^1.5", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpbench/phpbench": "^1.2.14", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6", + "slevomat/coding-standard": "^8.18", + "squizlabs/php_codesniffer": "^3.13" }, "suggest": { "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", @@ -4851,19 +4847,9 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.7.6" + "source": "https://github.com/ramsey/uuid/tree/4.8.1" }, - "funding": [ - { - "url": "https://github.com/ramsey", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", - "type": "tidelift" - } - ], - "time": "2024-04-27T21:32:50+00:00" + "time": "2025-06-01T06:28:46+00:00" }, { "name": "robrichards/xmlseclibs", @@ -7906,16 +7892,16 @@ }, { "name": "filp/whoops", - "version": "2.18.0", + "version": "2.18.2", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e" + "reference": "89dabca1490bc77dbcab41c2b20968c7e44bf7c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e", - "reference": "a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e", + "url": "https://api.github.com/repos/filp/whoops/zipball/89dabca1490bc77dbcab41c2b20968c7e44bf7c3", + "reference": "89dabca1490bc77dbcab41c2b20968c7e44bf7c3", "shasum": "" }, "require": { @@ -7965,7 +7951,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.18.0" + "source": "https://github.com/filp/whoops/tree/2.18.2" }, "funding": [ { @@ -7973,7 +7959,7 @@ "type": "github" } ], - "time": "2025-03-15T12:00:00+00:00" + "time": "2025-06-11T20:42:19+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -8145,16 +8131,16 @@ }, { "name": "larastan/larastan", - "version": "v3.4.0", + "version": "v3.4.2", "source": { "type": "git", "url": "https://github.com/larastan/larastan.git", - "reference": "1042fa0c2ee490bb6da7381f3323f7292ad68222" + "reference": "36706736a0c51d3337478fab9c919d78d2e03404" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/larastan/larastan/zipball/1042fa0c2ee490bb6da7381f3323f7292ad68222", - "reference": "1042fa0c2ee490bb6da7381f3323f7292ad68222", + "url": "https://api.github.com/repos/larastan/larastan/zipball/36706736a0c51d3337478fab9c919d78d2e03404", + "reference": "36706736a0c51d3337478fab9c919d78d2e03404", "shasum": "" }, "require": { @@ -8222,7 +8208,7 @@ ], "support": { "issues": "https://github.com/larastan/larastan/issues", - "source": "https://github.com/larastan/larastan/tree/v3.4.0" + "source": "https://github.com/larastan/larastan/tree/v3.4.2" }, "funding": [ { @@ -8230,7 +8216,7 @@ "type": "github" } ], - "time": "2025-04-22T09:44:59+00:00" + "time": "2025-06-10T09:34:58+00:00" }, { "name": "mockery/mockery", @@ -8377,23 +8363,23 @@ }, { "name": "nunomaduro/collision", - "version": "v8.8.0", + "version": "v8.8.1", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "4cf9f3b47afff38b139fb79ce54fc71799022ce8" + "reference": "44ccb82e3e21efb5446748d2a3c81a030ac22bd5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/4cf9f3b47afff38b139fb79ce54fc71799022ce8", - "reference": "4cf9f3b47afff38b139fb79ce54fc71799022ce8", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/44ccb82e3e21efb5446748d2a3c81a030ac22bd5", + "reference": "44ccb82e3e21efb5446748d2a3c81a030ac22bd5", "shasum": "" }, "require": { - "filp/whoops": "^2.18.0", - "nunomaduro/termwind": "^2.3.0", + "filp/whoops": "^2.18.1", + "nunomaduro/termwind": "^2.3.1", "php": "^8.2.0", - "symfony/console": "^7.2.5" + "symfony/console": "^7.3.0" }, "conflict": { "laravel/framework": "<11.44.2 || >=13.0.0", @@ -8401,15 +8387,15 @@ }, "require-dev": { "brianium/paratest": "^7.8.3", - "larastan/larastan": "^3.2", - "laravel/framework": "^11.44.2 || ^12.6", - "laravel/pint": "^1.21.2", - "laravel/sail": "^1.41.0", - "laravel/sanctum": "^4.0.8", + "larastan/larastan": "^3.4.2", + "laravel/framework": "^11.44.2 || ^12.18", + "laravel/pint": "^1.22.1", + "laravel/sail": "^1.43.1", + "laravel/sanctum": "^4.1.1", "laravel/tinker": "^2.10.1", - "orchestra/testbench-core": "^9.12.0 || ^10.1", - "pestphp/pest": "^3.8.0", - "sebastian/environment": "^7.2.0 || ^8.0" + "orchestra/testbench-core": "^9.12.0 || ^10.4", + "pestphp/pest": "^3.8.2", + "sebastian/environment": "^7.2.1 || ^8.0" }, "type": "library", "extra": { @@ -8472,7 +8458,7 @@ "type": "patreon" } ], - "time": "2025-04-03T14:33:09+00:00" + "time": "2025-06-11T01:04:21+00:00" }, { "name": "phar-io/manifest", @@ -8975,16 +8961,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.21", + "version": "11.5.23", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "d565e2cdc21a7db9dc6c399c1fc2083b8010f289" + "reference": "86ebcd8a3dbcd1857d88505109b2a2b376501cde" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d565e2cdc21a7db9dc6c399c1fc2083b8010f289", - "reference": "d565e2cdc21a7db9dc6c399c1fc2083b8010f289", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/86ebcd8a3dbcd1857d88505109b2a2b376501cde", + "reference": "86ebcd8a3dbcd1857d88505109b2a2b376501cde", "shasum": "" }, "require": { @@ -9056,7 +9042,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.21" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.23" }, "funding": [ { @@ -9080,7 +9066,7 @@ "type": "tidelift" } ], - "time": "2025-05-21T12:35:00+00:00" + "time": "2025-06-13T05:47:49+00:00" }, { "name": "sebastian/cli-parser", @@ -10022,16 +10008,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.13.0", + "version": "3.13.1", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "65ff2489553b83b4597e89c3b8b721487011d186" + "reference": "1b71b4dd7e7ef651ac749cea67e513c0c832f4bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/65ff2489553b83b4597e89c3b8b721487011d186", - "reference": "65ff2489553b83b4597e89c3b8b721487011d186", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/1b71b4dd7e7ef651ac749cea67e513c0c832f4bd", + "reference": "1b71b4dd7e7ef651ac749cea67e513c0c832f4bd", "shasum": "" }, "require": { @@ -10102,7 +10088,7 @@ "type": "thanks_dev" } ], - "time": "2025-05-11T03:36:00+00:00" + "time": "2025-06-12T15:04:34+00:00" }, { "name": "ssddanbrown/asserthtml", From fda242d3da40b1352298bf6ddf3fc902b7690e3b Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 13 Jun 2025 15:58:59 +0100 Subject: [PATCH 02/12] Lexical: Fixed tiny image resizer on image insert Added specific focus on image insert, and updated resize handler to watch for load events and toggle a resize once loaded. --- .../js/wysiwyg/ui/defaults/buttons/objects.ts | 1 + .../ui/framework/helpers/node-resizer.ts | 60 ++++++++++++------- 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/resources/js/wysiwyg/ui/defaults/buttons/objects.ts b/resources/js/wysiwyg/ui/defaults/buttons/objects.ts index 4eb4c5a4e..41f6061b6 100644 --- a/resources/js/wysiwyg/ui/defaults/buttons/objects.ts +++ b/resources/js/wysiwyg/ui/defaults/buttons/objects.ts @@ -92,6 +92,7 @@ export const image: EditorButtonDefinition = { context.editor.update(() => { const link = $createLinkedImageNodeFromImageData(image); $insertNodes([link]); + link.select(); }); }) }); diff --git a/resources/js/wysiwyg/ui/framework/helpers/node-resizer.ts b/resources/js/wysiwyg/ui/framework/helpers/node-resizer.ts index fa8ff48be..934ac5763 100644 --- a/resources/js/wysiwyg/ui/framework/helpers/node-resizer.ts +++ b/resources/js/wysiwyg/ui/framework/helpers/node-resizer.ts @@ -12,17 +12,21 @@ function isNodeWithSize(node: LexicalNode): node is NodeHasSize&LexicalNode { class NodeResizer { protected context: EditorUiContext; - protected dom: HTMLElement|null = null; + protected resizerDOM: HTMLElement|null = null; + protected targetDOM: HTMLElement|null = null; protected scrollContainer: HTMLElement; protected mouseTracker: MouseDragTracker|null = null; protected activeSelection: string = ''; + protected loadAbortController = new AbortController(); constructor(context: EditorUiContext) { this.context = context; this.scrollContainer = context.scrollDOM; this.onSelectionChange = this.onSelectionChange.bind(this); + this.onTargetDOMLoad = this.onTargetDOMLoad.bind(this); + context.manager.onSelectionChange(this.onSelectionChange); } @@ -47,56 +51,68 @@ class NodeResizer { } } + onTargetDOMLoad(): void { + this.updateResizerPosition(); + } + teardown() { this.context.manager.offSelectionChange(this.onSelectionChange); this.hide(); } - protected showForNode(node: NodeHasSize&LexicalNode, dom: HTMLElement) { - this.dom = this.buildDOM(); + protected showForNode(node: NodeHasSize&LexicalNode, targetDOM: HTMLElement) { + this.resizerDOM = this.buildDOM(); + this.targetDOM = targetDOM; let ghost = el('span', {class: 'editor-node-resizer-ghost'}); if ($isImageNode(node)) { - ghost = el('img', {src: dom.getAttribute('src'), class: 'editor-node-resizer-ghost'}); + ghost = el('img', {src: targetDOM.getAttribute('src'), class: 'editor-node-resizer-ghost'}); } - this.dom.append(ghost); + this.resizerDOM.append(ghost); - this.context.scrollDOM.append(this.dom); - this.updateDOMPosition(dom); + this.context.scrollDOM.append(this.resizerDOM); + this.updateResizerPosition(); - this.mouseTracker = this.setupTracker(this.dom, node, dom); + this.mouseTracker = this.setupTracker(this.resizerDOM, node, targetDOM); this.activeSelection = node.getKey(); + + if (targetDOM.matches('img, embed, iframe, object')) { + this.loadAbortController = new AbortController(); + targetDOM.addEventListener('load', this.onTargetDOMLoad, { signal: this.loadAbortController.signal }); + } } - protected updateDOMPosition(nodeDOM: HTMLElement) { - if (!this.dom) { + protected updateResizerPosition() { + if (!this.resizerDOM || !this.targetDOM) { return; } const scrollAreaRect = this.scrollContainer.getBoundingClientRect(); - const nodeRect = nodeDOM.getBoundingClientRect(); + const nodeRect = this.targetDOM.getBoundingClientRect(); const top = nodeRect.top - (scrollAreaRect.top - this.scrollContainer.scrollTop); const left = nodeRect.left - scrollAreaRect.left; - this.dom.style.top = `${top}px`; - this.dom.style.left = `${left}px`; - this.dom.style.width = nodeRect.width + 'px'; - this.dom.style.height = nodeRect.height + 'px'; + this.resizerDOM.style.top = `${top}px`; + this.resizerDOM.style.left = `${left}px`; + this.resizerDOM.style.width = nodeRect.width + 'px'; + this.resizerDOM.style.height = nodeRect.height + 'px'; } protected updateDOMSize(width: number, height: number): void { - if (!this.dom) { + if (!this.resizerDOM) { return; } - this.dom.style.width = width + 'px'; - this.dom.style.height = height + 'px'; + this.resizerDOM.style.width = width + 'px'; + this.resizerDOM.style.height = height + 'px'; } protected hide() { this.mouseTracker?.teardown(); - this.dom?.remove(); + this.resizerDOM?.remove(); + this.targetDOM = null; this.activeSelection = ''; + this.loadAbortController.abort(); } protected buildDOM() { @@ -140,7 +156,7 @@ class NodeResizer { return new MouseDragTracker(container, '.editor-node-resizer-handle', { down(event: MouseEvent, handle: HTMLElement) { - _this.dom?.classList.add('active'); + _this.resizerDOM?.classList.add('active'); _this.context.editor.getEditorState().read(() => { const domRect = nodeDOM.getBoundingClientRect(); startingWidth = node.getWidth() || domRect.width; @@ -165,10 +181,10 @@ class NodeResizer { node.setHeight(hasHeight ? size.height : 0); _this.context.manager.triggerLayoutUpdate(); requestAnimationFrame(() => { - _this.updateDOMPosition(nodeDOM); + _this.updateResizerPosition(); }) }); - _this.dom?.classList.remove('active'); + _this.resizerDOM?.classList.remove('active'); } }); } From 717b5163414124422899d4270e41db4267e316c9 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 13 Jun 2025 16:38:53 +0100 Subject: [PATCH 03/12] Lexical: Made table resize handles more efficent & less buggy Fine mouse movement and handles will now only be active when actually within a table, otherwise less frequent mouseovers are used to track if in/out a table. Hides handles when out of a table, preventing a range of issues with stray handles floating about. --- .../ui/framework/helpers/table-resizer.ts | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/resources/js/wysiwyg/ui/framework/helpers/table-resizer.ts b/resources/js/wysiwyg/ui/framework/helpers/table-resizer.ts index 4256fdafc..ef1d3cb5f 100644 --- a/resources/js/wysiwyg/ui/framework/helpers/table-resizer.ts +++ b/resources/js/wysiwyg/ui/framework/helpers/table-resizer.ts @@ -15,6 +15,7 @@ class TableResizer { protected targetCell: HTMLElement|null = null; protected xMarkerAtStart : boolean = false; protected yMarkerAtStart : boolean = false; + protected activeInTable: boolean = false; constructor(editor: LexicalEditor, editScrollContainer: HTMLElement) { this.editor = editor; @@ -33,9 +34,10 @@ class TableResizer { } protected setupListeners() { + this.onTableMouseOver = this.onTableMouseOver.bind(this); this.onCellMouseMove = this.onCellMouseMove.bind(this); this.onScrollOrResize = this.onScrollOrResize.bind(this); - this.editScrollContainer.addEventListener('mousemove', this.onCellMouseMove); + this.editScrollContainer.addEventListener('mouseover', this.onTableMouseOver, { passive: true }); window.addEventListener('scroll', this.onScrollOrResize, {capture: true, passive: true}); window.addEventListener('resize', this.onScrollOrResize, {passive: true}); } @@ -44,8 +46,26 @@ class TableResizer { this.updateCurrentMarkerTargetPosition(); } + protected onTableMouseOver(event: MouseEvent): void { + if (this.dragging) { + return; + } + + const table = (event.target as HTMLElement).closest('table') as HTMLElement|null; + + if (table && !this.activeInTable) { + this.editScrollContainer.addEventListener('mousemove', this.onCellMouseMove, { passive: true }); + this.onCellMouseMove(event); + this.activeInTable = true; + } else if (!table && this.activeInTable) { + this.editScrollContainer.removeEventListener('mousemove', this.onCellMouseMove); + this.hideMarkers(); + this.activeInTable = false; + } + } + protected onCellMouseMove(event: MouseEvent) { - const cell = (event.target as HTMLElement).closest('td,th') as HTMLElement; + const cell = (event.target as HTMLElement).closest('td,th') as HTMLElement|null; if (!cell || this.dragging) { return; } @@ -66,10 +86,16 @@ class TableResizer { protected updateMarkersTo(cell: HTMLElement, xPos: number, yPos: number) { const markers: MarkerDomRecord = this.getMarkers(); const table = cell.closest('table') as HTMLElement; + const caption: HTMLTableCaptionElement|null = table.querySelector('caption'); const tableRect = table.getBoundingClientRect(); const editBounds = this.editScrollContainer.getBoundingClientRect(); - const maxTop = Math.max(tableRect.top, editBounds.top); + let tableTop = tableRect.top; + if (caption) { + tableTop = caption.getBoundingClientRect().bottom; + } + + const maxTop = Math.max(tableTop, editBounds.top); const maxBottom = Math.min(tableRect.bottom, editBounds.bottom); const maxHeight = maxBottom - maxTop; markers.x.style.left = xPos + 'px'; @@ -85,6 +111,13 @@ class TableResizer { markers.x.hidden = tableRect.top > editBounds.bottom || tableRect.bottom < editBounds.top; } + protected hideMarkers(): void { + if (this.markerDom) { + this.markerDom.x.hidden = true; + this.markerDom.y.hidden = true; + } + } + protected updateCurrentMarkerTargetPosition(): void { if (!this.targetCell) { return; From 8b062d47951825e04f9a65ea3b09d9c70ee8b59d Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 13 Jun 2025 19:40:13 +0100 Subject: [PATCH 04/12] Lexical: Fixed strange paragraph formatting behaviour Formatting was not persisted on empty paragraphs, and was instead based upon last format encountered in selection. This was due to overly-hasty removal of other formatting code, which this got caught it. Restored required parts from prior codebase. Also updated inline format button active indicator to reflect formats using the above, so correct buttons are shown as active even when just in an empty paragraph. --- resources/js/wysiwyg/index.ts | 2 +- .../js/wysiwyg/lexical/core/LexicalEvents.ts | 1 + .../core/nodes/LexicalParagraphNode.ts | 25 ++++++++++++++++++- resources/js/wysiwyg/utils/selection.ts | 11 ++++++-- 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/resources/js/wysiwyg/index.ts b/resources/js/wysiwyg/index.ts index ffdc7d7e8..7ecf91d23 100644 --- a/resources/js/wysiwyg/index.ts +++ b/resources/js/wysiwyg/index.ts @@ -84,7 +84,7 @@ export function createPageEditorInstance(container: HTMLElement, htmlContent: st // @ts-ignore window.debugEditorState = () => { - console.log(editor.getEditorState().toJSON()); + return editor.getEditorState().toJSON(); }; registerCommonNodeMutationListeners(context); diff --git a/resources/js/wysiwyg/lexical/core/LexicalEvents.ts b/resources/js/wysiwyg/lexical/core/LexicalEvents.ts index c70a906a0..26cf25a80 100644 --- a/resources/js/wysiwyg/lexical/core/LexicalEvents.ts +++ b/resources/js/wysiwyg/lexical/core/LexicalEvents.ts @@ -355,6 +355,7 @@ function onSelectionChange( lastNode instanceof ParagraphNode && lastNode.getChildrenSize() === 0 ) { + selection.format = lastNode.getTextFormat(); selection.style = lastNode.getTextStyle(); } else { selection.format = 0; diff --git a/resources/js/wysiwyg/lexical/core/nodes/LexicalParagraphNode.ts b/resources/js/wysiwyg/lexical/core/nodes/LexicalParagraphNode.ts index f6f57c91c..e8d044b21 100644 --- a/resources/js/wysiwyg/lexical/core/nodes/LexicalParagraphNode.ts +++ b/resources/js/wysiwyg/lexical/core/nodes/LexicalParagraphNode.ts @@ -19,7 +19,7 @@ import type { LexicalNode, NodeKey, } from '../LexicalNode'; -import type {RangeSelection} from 'lexical'; +import {RangeSelection, TEXT_TYPE_TO_FORMAT, TextFormatType} from 'lexical'; import { $applyNodeReplacement, @@ -36,6 +36,7 @@ import {CommonBlockNode, copyCommonBlockProperties, SerializedCommonBlockNode} f export type SerializedParagraphNode = Spread< { + textFormat: number; textStyle: string; }, SerializedCommonBlockNode @@ -45,10 +46,12 @@ export type SerializedParagraphNode = Spread< export class ParagraphNode extends CommonBlockNode { ['constructor']!: KlassConstructor; /** @internal */ + __textFormat: number; __textStyle: string; constructor(key?: NodeKey) { super(key); + this.__textFormat = 0; this.__textStyle = ''; } @@ -56,6 +59,22 @@ export class ParagraphNode extends CommonBlockNode { return 'paragraph'; } + getTextFormat(): number { + const self = this.getLatest(); + return self.__textFormat; + } + + setTextFormat(type: number): this { + const self = this.getWritable(); + self.__textFormat = type; + return self; + } + + hasTextFormat(type: TextFormatType): boolean { + const formatFlag = TEXT_TYPE_TO_FORMAT[type]; + return (this.getTextFormat() & formatFlag) !== 0; + } + getTextStyle(): string { const self = this.getLatest(); return self.__textStyle; @@ -73,6 +92,7 @@ export class ParagraphNode extends CommonBlockNode { afterCloneFrom(prevNode: this) { super.afterCloneFrom(prevNode); + this.__textFormat = prevNode.__textFormat; this.__textStyle = prevNode.__textStyle; copyCommonBlockProperties(prevNode, this); } @@ -125,12 +145,14 @@ export class ParagraphNode extends CommonBlockNode { static importJSON(serializedNode: SerializedParagraphNode): ParagraphNode { const node = $createParagraphNode(); deserializeCommonBlockNode(serializedNode, node); + node.setTextFormat(serializedNode.textFormat); return node; } exportJSON(): SerializedParagraphNode { return { ...super.exportJSON(), + textFormat: this.getTextFormat(), textStyle: this.getTextStyle(), type: 'paragraph', version: 1, @@ -144,6 +166,7 @@ export class ParagraphNode extends CommonBlockNode { restoreSelection: boolean, ): ParagraphNode { const newElement = $createParagraphNode(); + newElement.setTextFormat(rangeSelection.format); newElement.setTextStyle(rangeSelection.style); const direction = this.getDirection(); newElement.setDirection(direction); diff --git a/resources/js/wysiwyg/utils/selection.ts b/resources/js/wysiwyg/utils/selection.ts index 167ab32ad..e4b5bf2dc 100644 --- a/resources/js/wysiwyg/utils/selection.ts +++ b/resources/js/wysiwyg/utils/selection.ts @@ -3,7 +3,7 @@ import { $createParagraphNode, $createRangeSelection, $getRoot, $getSelection, $isBlockElementNode, $isDecoratorNode, - $isElementNode, + $isElementNode, $isParagraphNode, $isTextNode, $setSelection, BaseSelection, DecoratorNode, @@ -60,12 +60,19 @@ export function $selectionContainsTextFormat(selection: BaseSelection | null, fo return false; } - for (const node of selection.getNodes()) { + // Check text nodes + const nodes = selection.getNodes(); + for (const node of nodes) { if ($isTextNode(node) && node.hasFormat(format)) { return true; } } + // If we're in an empty paragraph, check the paragraph format + if (nodes.length === 1 && $isParagraphNode(nodes[0]) && nodes[0].hasTextFormat(format)) { + return true; + } + return false; } From 77a88618c21d66cf663bb37979260539df7528e3 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 14 Jun 2025 14:50:10 +0100 Subject: [PATCH 05/12] Lexical: Fixed double-bold text, updated tests Double bold was due to text field exporting wrapping the output in tags when the main tag would already be strong. --- .../lexical/core/__tests__/unit/LexicalEditor.test.ts | 2 ++ .../lexical/core/__tests__/unit/LexicalEditorState.test.ts | 3 ++- .../lexical/core/__tests__/unit/LexicalSerialization.test.ts | 4 ++-- resources/js/wysiwyg/lexical/core/nodes/LexicalTextNode.ts | 5 +++-- .../core/nodes/__tests__/unit/LexicalParagraphNode.test.ts | 1 + .../core/nodes/__tests__/unit/LexicalTextNode.test.ts | 2 +- .../table/__tests__/unit/LexicalTableSelection.test.ts | 1 + 7 files changed, 12 insertions(+), 6 deletions(-) diff --git a/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalEditor.test.ts b/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalEditor.test.ts index 5d7632919..a54d33ca4 100644 --- a/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalEditor.test.ts +++ b/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalEditor.test.ts @@ -1069,6 +1069,7 @@ describe('LexicalEditor tests', () => { __prev: null, __size: 1, __style: '', + __textFormat: 0, __textStyle: '', __type: 'paragraph', }); @@ -1149,6 +1150,7 @@ describe('LexicalEditor tests', () => { __prev: null, __size: 1, __style: '', + __textFormat: 0, __textStyle: '', __type: 'paragraph', }); diff --git a/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalEditorState.test.ts b/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalEditorState.test.ts index 97b634503..12fcfbf73 100644 --- a/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalEditorState.test.ts +++ b/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalEditorState.test.ts @@ -76,6 +76,7 @@ describe('LexicalEditorState tests', () => { __prev: null, __size: 1, __style: '', + __textFormat: 0, __textStyle: '', __type: 'paragraph', }); @@ -111,7 +112,7 @@ describe('LexicalEditorState tests', () => { }); expect(JSON.stringify(editor.getEditorState().toJSON())).toEqual( - `{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Hello world","type":"text","version":1}],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"root","version":1}}`, + `{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Hello world","type":"text","version":1}],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"root","version":1}}`, ); }); diff --git a/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalSerialization.test.ts b/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalSerialization.test.ts index e08547c13..23b5ffdd5 100644 --- a/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalSerialization.test.ts +++ b/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalSerialization.test.ts @@ -107,7 +107,7 @@ describe('LexicalSerialization tests', () => { }); const stringifiedEditorState = JSON.stringify(editor.getEditorState()); - const expectedStringifiedEditorState = `{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Welcome to the playground","type":"text","version":1}],"direction":null,"type":"heading","version":1,"id":"","alignment":"","inset":0,"tag":"h1"},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"In case you were wondering what the black box at the bottom is – it's the debug view, showing the current state of the editor. You can disable it by pressing on the settings control in the bottom-left of your screen and toggling the debug view setting.","type":"text","version":1}],"direction":null,"type":"quote","version":1,"id":"","alignment":"","inset":0},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"The playground is a demo environment built with ","type":"text","version":1},{"detail":0,"format":16,"mode":"normal","style":"","text":"@lexical/react","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":". Try typing in ","type":"text","version":1},{"detail":0,"format":1,"mode":"normal","style":"","text":"some text","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":" with ","type":"text","version":1},{"detail":0,"format":2,"mode":"normal","style":"","text":"different","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":" formats.","type":"text","version":1}],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Make sure to check out the various plugins in the toolbar. You can also use #hashtags or @-mentions too!","type":"text","version":1}],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"If you'd like to find out more about Lexical, you can:","type":"text","version":1}],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""},{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Visit the ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Lexical website","type":"text","version":1}],"direction":null,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://lexical.dev/"},{"detail":0,"format":0,"mode":"normal","style":"","text":" for documentation and more information.","type":"text","version":1}],"direction":null,"type":"listitem","version":1,"value":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Check out the code on our ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"GitHub repository","type":"text","version":1}],"direction":null,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://github.com/facebook/lexical"},{"detail":0,"format":0,"mode":"normal","style":"","text":".","type":"text","version":1}],"direction":null,"type":"listitem","version":1,"value":2},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Playground code can be found ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"here","type":"text","version":1}],"direction":null,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://github.com/facebook/lexical/tree/main/packages/lexical-playground"},{"detail":0,"format":0,"mode":"normal","style":"","text":".","type":"text","version":1}],"direction":null,"type":"listitem","version":1,"value":3},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Join our ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Discord Server","type":"text","version":1}],"direction":null,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://discord.com/invite/KmG4wQnnD9"},{"detail":0,"format":0,"mode":"normal","style":"","text":" and chat with the team.","type":"text","version":1}],"direction":null,"type":"listitem","version":1,"value":4}],"direction":null,"type":"list","version":1,"listType":"bullet","start":1,"tag":"ul","id":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Lastly, we're constantly adding cool new features to this playground. So make sure you check back here when you next get a chance :).","type":"text","version":1}],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""},{"children":[{"children":[{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":3,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1,"styles":{},"alignment":""}],"direction":null,"type":"tablerow","version":1,"styles":{},"height":0},{"children":[{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""}],"direction":null,"type":"tablerow","version":1,"styles":{},"height":0},{"children":[{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""}],"direction":null,"type":"tablerow","version":1,"styles":{},"height":0},{"children":[{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""}],"direction":null,"type":"tablerow","version":1,"styles":{},"height":0},{"children":[{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""}],"direction":null,"type":"tablerow","version":1,"styles":{},"height":0}],"direction":null,"type":"table","version":1,"id":"","alignment":"","inset":0,"colWidths":[],"styles":{}}],"direction":null,"type":"root","version":1}}`; + const expectedStringifiedEditorState = `{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Welcome to the playground","type":"text","version":1}],"direction":null,"type":"heading","version":1,"id":"","alignment":"","inset":0,"tag":"h1"},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"In case you were wondering what the black box at the bottom is – it's the debug view, showing the current state of the editor. You can disable it by pressing on the settings control in the bottom-left of your screen and toggling the debug view setting.","type":"text","version":1}],"direction":null,"type":"quote","version":1,"id":"","alignment":"","inset":0},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"The playground is a demo environment built with ","type":"text","version":1},{"detail":0,"format":16,"mode":"normal","style":"","text":"@lexical/react","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":". Try typing in ","type":"text","version":1},{"detail":0,"format":1,"mode":"normal","style":"","text":"some text","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":" with ","type":"text","version":1},{"detail":0,"format":2,"mode":"normal","style":"","text":"different","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":" formats.","type":"text","version":1}],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Make sure to check out the various plugins in the toolbar. You can also use #hashtags or @-mentions too!","type":"text","version":1}],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"If you'd like to find out more about Lexical, you can:","type":"text","version":1}],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""},{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Visit the ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Lexical website","type":"text","version":1}],"direction":null,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://lexical.dev/"},{"detail":0,"format":0,"mode":"normal","style":"","text":" for documentation and more information.","type":"text","version":1}],"direction":null,"type":"listitem","version":1,"value":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Check out the code on our ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"GitHub repository","type":"text","version":1}],"direction":null,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://github.com/facebook/lexical"},{"detail":0,"format":0,"mode":"normal","style":"","text":".","type":"text","version":1}],"direction":null,"type":"listitem","version":1,"value":2},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Playground code can be found ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"here","type":"text","version":1}],"direction":null,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://github.com/facebook/lexical/tree/main/packages/lexical-playground"},{"detail":0,"format":0,"mode":"normal","style":"","text":".","type":"text","version":1}],"direction":null,"type":"listitem","version":1,"value":3},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Join our ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Discord Server","type":"text","version":1}],"direction":null,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://discord.com/invite/KmG4wQnnD9"},{"detail":0,"format":0,"mode":"normal","style":"","text":" and chat with the team.","type":"text","version":1}],"direction":null,"type":"listitem","version":1,"value":4}],"direction":null,"type":"list","version":1,"listType":"bullet","start":1,"tag":"ul","id":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Lastly, we're constantly adding cool new features to this playground. So make sure you check back here when you next get a chance :).","type":"text","version":1}],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""},{"children":[{"children":[{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":3,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1,"styles":{},"alignment":""}],"direction":null,"type":"tablerow","version":1,"styles":{},"height":0},{"children":[{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""}],"direction":null,"type":"tablerow","version":1,"styles":{},"height":0},{"children":[{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""}],"direction":null,"type":"tablerow","version":1,"styles":{},"height":0},{"children":[{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""}],"direction":null,"type":"tablerow","version":1,"styles":{},"height":0},{"children":[{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""}],"direction":null,"type":"tablerow","version":1,"styles":{},"height":0}],"direction":null,"type":"table","version":1,"id":"","alignment":"","inset":0,"colWidths":[],"styles":{}}],"direction":null,"type":"root","version":1}}`; expect(stringifiedEditorState).toBe(expectedStringifiedEditorState); @@ -116,7 +116,7 @@ describe('LexicalSerialization tests', () => { const otherStringifiedEditorState = JSON.stringify(editorState); expect(otherStringifiedEditorState).toBe( - `{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Welcome to the playground","type":"text","version":1}],"direction":null,"type":"heading","version":1,"id":"","alignment":"","inset":0,"tag":"h1"},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"In case you were wondering what the black box at the bottom is – it's the debug view, showing the current state of the editor. You can disable it by pressing on the settings control in the bottom-left of your screen and toggling the debug view setting.","type":"text","version":1}],"direction":null,"type":"quote","version":1,"id":"","alignment":"","inset":0},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"The playground is a demo environment built with ","type":"text","version":1},{"detail":0,"format":16,"mode":"normal","style":"","text":"@lexical/react","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":". Try typing in ","type":"text","version":1},{"detail":0,"format":1,"mode":"normal","style":"","text":"some text","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":" with ","type":"text","version":1},{"detail":0,"format":2,"mode":"normal","style":"","text":"different","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":" formats.","type":"text","version":1}],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Make sure to check out the various plugins in the toolbar. You can also use #hashtags or @-mentions too!","type":"text","version":1}],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"If you'd like to find out more about Lexical, you can:","type":"text","version":1}],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""},{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Visit the ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Lexical website","type":"text","version":1}],"direction":null,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://lexical.dev/"},{"detail":0,"format":0,"mode":"normal","style":"","text":" for documentation and more information.","type":"text","version":1}],"direction":null,"type":"listitem","version":1,"value":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Check out the code on our ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"GitHub repository","type":"text","version":1}],"direction":null,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://github.com/facebook/lexical"},{"detail":0,"format":0,"mode":"normal","style":"","text":".","type":"text","version":1}],"direction":null,"type":"listitem","version":1,"value":2},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Playground code can be found ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"here","type":"text","version":1}],"direction":null,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://github.com/facebook/lexical/tree/main/packages/lexical-playground"},{"detail":0,"format":0,"mode":"normal","style":"","text":".","type":"text","version":1}],"direction":null,"type":"listitem","version":1,"value":3},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Join our ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Discord Server","type":"text","version":1}],"direction":null,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://discord.com/invite/KmG4wQnnD9"},{"detail":0,"format":0,"mode":"normal","style":"","text":" and chat with the team.","type":"text","version":1}],"direction":null,"type":"listitem","version":1,"value":4}],"direction":null,"type":"list","version":1,"listType":"bullet","start":1,"tag":"ul","id":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Lastly, we're constantly adding cool new features to this playground. So make sure you check back here when you next get a chance :).","type":"text","version":1}],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""},{"children":[{"children":[{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":3,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1,"styles":{},"alignment":""}],"direction":null,"type":"tablerow","version":1,"styles":{},"height":0},{"children":[{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""}],"direction":null,"type":"tablerow","version":1,"styles":{},"height":0},{"children":[{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""}],"direction":null,"type":"tablerow","version":1,"styles":{},"height":0},{"children":[{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""}],"direction":null,"type":"tablerow","version":1,"styles":{},"height":0},{"children":[{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""}],"direction":null,"type":"tablerow","version":1,"styles":{},"height":0}],"direction":null,"type":"table","version":1,"id":"","alignment":"","inset":0,"colWidths":[],"styles":{}}],"direction":null,"type":"root","version":1}}`, + `{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Welcome to the playground","type":"text","version":1}],"direction":null,"type":"heading","version":1,"id":"","alignment":"","inset":0,"tag":"h1"},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"In case you were wondering what the black box at the bottom is – it's the debug view, showing the current state of the editor. You can disable it by pressing on the settings control in the bottom-left of your screen and toggling the debug view setting.","type":"text","version":1}],"direction":null,"type":"quote","version":1,"id":"","alignment":"","inset":0},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"The playground is a demo environment built with ","type":"text","version":1},{"detail":0,"format":16,"mode":"normal","style":"","text":"@lexical/react","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":". Try typing in ","type":"text","version":1},{"detail":0,"format":1,"mode":"normal","style":"","text":"some text","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":" with ","type":"text","version":1},{"detail":0,"format":2,"mode":"normal","style":"","text":"different","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":" formats.","type":"text","version":1}],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Make sure to check out the various plugins in the toolbar. You can also use #hashtags or @-mentions too!","type":"text","version":1}],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"If you'd like to find out more about Lexical, you can:","type":"text","version":1}],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""},{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Visit the ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Lexical website","type":"text","version":1}],"direction":null,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://lexical.dev/"},{"detail":0,"format":0,"mode":"normal","style":"","text":" for documentation and more information.","type":"text","version":1}],"direction":null,"type":"listitem","version":1,"value":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Check out the code on our ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"GitHub repository","type":"text","version":1}],"direction":null,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://github.com/facebook/lexical"},{"detail":0,"format":0,"mode":"normal","style":"","text":".","type":"text","version":1}],"direction":null,"type":"listitem","version":1,"value":2},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Playground code can be found ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"here","type":"text","version":1}],"direction":null,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://github.com/facebook/lexical/tree/main/packages/lexical-playground"},{"detail":0,"format":0,"mode":"normal","style":"","text":".","type":"text","version":1}],"direction":null,"type":"listitem","version":1,"value":3},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Join our ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Discord Server","type":"text","version":1}],"direction":null,"type":"link","version":1,"rel":null,"target":null,"title":null,"url":"https://discord.com/invite/KmG4wQnnD9"},{"detail":0,"format":0,"mode":"normal","style":"","text":" and chat with the team.","type":"text","version":1}],"direction":null,"type":"listitem","version":1,"value":4}],"direction":null,"type":"list","version":1,"listType":"bullet","start":1,"tag":"ul","id":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Lastly, we're constantly adding cool new features to this playground. So make sure you check back here when you next get a chance :).","type":"text","version":1}],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""},{"children":[{"children":[{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":3,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":1,"rowSpan":1,"styles":{},"alignment":""}],"direction":null,"type":"tablerow","version":1,"styles":{},"height":0},{"children":[{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""}],"direction":null,"type":"tablerow","version":1,"styles":{},"height":0},{"children":[{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""}],"direction":null,"type":"tablerow","version":1,"styles":{},"height":0},{"children":[{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""}],"direction":null,"type":"tablerow","version":1,"styles":{},"height":0},{"children":[{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":2,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""},{"children":[{"children":[],"direction":null,"type":"paragraph","version":1,"id":"","alignment":"","inset":0,"textFormat":0,"textStyle":""}],"direction":null,"type":"tablecell","version":1,"backgroundColor":null,"colSpan":1,"headerState":0,"rowSpan":1,"styles":{},"alignment":""}],"direction":null,"type":"tablerow","version":1,"styles":{},"height":0}],"direction":null,"type":"table","version":1,"id":"","alignment":"","inset":0,"colWidths":[],"styles":{}}],"direction":null,"type":"root","version":1}}`, ); }); }); diff --git a/resources/js/wysiwyg/lexical/core/nodes/LexicalTextNode.ts b/resources/js/wysiwyg/lexical/core/nodes/LexicalTextNode.ts index 7f1b4f305..9a4867494 100644 --- a/resources/js/wysiwyg/lexical/core/nodes/LexicalTextNode.ts +++ b/resources/js/wysiwyg/lexical/core/nodes/LexicalTextNode.ts @@ -620,6 +620,7 @@ export class TextNode extends LexicalNode { // HTML content and not have the ability to use CSS classes. exportDOM(editor: LexicalEditor): DOMExportOutput { let {element} = super.exportDOM(editor); + const originalElementName = (element?.nodeName || '').toLowerCase() invariant( element !== null && isHTMLElement(element), 'Expected TextNode createDOM to always return a HTMLElement', @@ -649,8 +650,8 @@ export class TextNode extends LexicalNode { // This is the only way to properly add support for most clients, // even if it's semantically incorrect to have to resort to using // , , , elements. - if (this.hasFormat('bold')) { - element = wrapElementWith(element, 'b'); + if (this.hasFormat('bold') && originalElementName !== 'strong') { + element = wrapElementWith(element, 'strong'); } if (this.hasFormat('italic')) { element = wrapElementWith(element, 'em'); diff --git a/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalParagraphNode.test.ts b/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalParagraphNode.test.ts index 7bf485ca1..00db14e85 100644 --- a/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalParagraphNode.test.ts +++ b/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalParagraphNode.test.ts @@ -53,6 +53,7 @@ describe('LexicalParagraphNode tests', () => { direction: null, id: '', inset: 0, + textFormat: 0, textStyle: '', type: 'paragraph', version: 1, diff --git a/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalTextNode.test.ts b/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalTextNode.test.ts index c54760ff2..0dbf9b94e 100644 --- a/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalTextNode.test.ts +++ b/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalTextNode.test.ts @@ -839,7 +839,7 @@ describe('LexicalTextNode tests', () => { paragraph.append(textNode); const html = $generateHtmlFromNodes($getEditor(), null); - expect(html).toBe('

hello

'); + expect(html).toBe('

hello

'); }); }); diff --git a/resources/js/wysiwyg/lexical/table/__tests__/unit/LexicalTableSelection.test.ts b/resources/js/wysiwyg/lexical/table/__tests__/unit/LexicalTableSelection.test.ts index 1548216cf..b28f2958e 100644 --- a/resources/js/wysiwyg/lexical/table/__tests__/unit/LexicalTableSelection.test.ts +++ b/resources/js/wysiwyg/lexical/table/__tests__/unit/LexicalTableSelection.test.ts @@ -123,6 +123,7 @@ describe('table selection', () => { __prev: null, __size: 1, __style: '', + __textFormat: 0, __textStyle: '', __type: 'paragraph', }); From 8d4b8ff4f375c014d36ccbf034dfc77bb8481804 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 15 Jun 2025 13:55:42 +0100 Subject: [PATCH 06/12] Lexical: Fixed media resize handling - Updating height/width setting to clear any inline CSS width/height rules which would override and prevent resizes showing. This was common when switching media from old editor. Added test to cover. - Updated resizer to track node so that it is retained & displayed across node DOM changes, which was previously causing the resizer/focus to disappear. --- .../lexical/core/__tests__/utils/index.ts | 2 + .../lexical/rich-text/LexicalMediaNode.ts | 34 ++++++++++---- .../__tests__/unit/LexicalMediaNode.test.ts | 31 +++++++++++++ .../ui/framework/helpers/node-resizer.ts | 45 ++++++++++++------- resources/js/wysiwyg/utils/dom.ts | 22 ++++++++- resources/sass/_editor.scss | 2 +- 6 files changed, 107 insertions(+), 29 deletions(-) create mode 100644 resources/js/wysiwyg/lexical/rich-text/__tests__/unit/LexicalMediaNode.test.ts diff --git a/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts b/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts index 6a8e45724..e18ef9756 100644 --- a/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts +++ b/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts @@ -38,6 +38,7 @@ import {DetailsNode} from "@lexical/rich-text/LexicalDetailsNode"; import {EditorUiContext} from "../../../../ui/framework/core"; import {EditorUIManager} from "../../../../ui/framework/manager"; import {ImageNode} from "@lexical/rich-text/LexicalImageNode"; +import {MediaNode} from "@lexical/rich-text/LexicalMediaNode"; type TestEnv = { readonly container: HTMLDivElement; @@ -487,6 +488,7 @@ export function createTestContext(): EditorUiContext { theme: {}, nodes: [ ImageNode, + MediaNode, ] }); diff --git a/resources/js/wysiwyg/lexical/rich-text/LexicalMediaNode.ts b/resources/js/wysiwyg/lexical/rich-text/LexicalMediaNode.ts index 1dd159f51..6e9c24717 100644 --- a/resources/js/wysiwyg/lexical/rich-text/LexicalMediaNode.ts +++ b/resources/js/wysiwyg/lexical/rich-text/LexicalMediaNode.ts @@ -8,7 +8,7 @@ import { } from 'lexical'; import type {EditorConfig} from "lexical/LexicalEditor"; -import {el, setOrRemoveAttribute, sizeToPixels} from "../../utils/dom"; +import {el, setOrRemoveAttribute, sizeToPixels, styleMapToStyleString, styleStringToStyleMap} from "../../utils/dom"; import { CommonBlockAlignment, deserializeCommonBlockNode, setCommonBlockPropsFromElement, @@ -46,6 +46,19 @@ function filterAttributes(attributes: Record): Record, styleName: string): Record { + const attrCopy = Object.assign({}, attributes); + if (!attributes.style) { + return attrCopy; + } + + const map = styleStringToStyleMap(attributes.style); + map.delete(styleName); + + attrCopy.style = styleMapToStyleString(map); + return attrCopy; +} + function domElementToNode(tag: MediaNodeTag, element: HTMLElement): MediaNode { const node = $createMediaNode(tag); @@ -118,7 +131,7 @@ export class MediaNode extends ElementNode { getAttributes(): Record { const self = this.getLatest(); - return self.__attributes; + return Object.assign({}, self.__attributes); } setSources(sources: MediaNodeSource[]) { @@ -132,7 +145,7 @@ export class MediaNode extends ElementNode { } setSrc(src: string): void { - const attrs = Object.assign({}, this.getAttributes()); + const attrs = this.getAttributes(); if (this.__tag ==='object') { attrs.data = src; } else { @@ -142,11 +155,13 @@ export class MediaNode extends ElementNode { } setWidthAndHeight(width: string, height: string): void { - const attrs = Object.assign( - {}, + let attrs: Record = Object.assign( this.getAttributes(), {width, height}, ); + + attrs = removeStyleFromAttributes(attrs, 'width'); + attrs = removeStyleFromAttributes(attrs, 'height'); this.setAttributes(attrs); } @@ -185,8 +200,8 @@ export class MediaNode extends ElementNode { return; } - const attrs = Object.assign({}, this.getAttributes(), {height}); - this.setAttributes(attrs); + const attrs = Object.assign(this.getAttributes(), {height}); + this.setAttributes(removeStyleFromAttributes(attrs, 'height')); } getHeight(): number { @@ -195,8 +210,9 @@ export class MediaNode extends ElementNode { } setWidth(width: number): void { - const attrs = Object.assign({}, this.getAttributes(), {width}); - this.setAttributes(attrs); + const existingAttrs = this.getAttributes(); + const attrs: Record = Object.assign(existingAttrs, {width}); + this.setAttributes(removeStyleFromAttributes(attrs, 'width')); } getWidth(): number { diff --git a/resources/js/wysiwyg/lexical/rich-text/__tests__/unit/LexicalMediaNode.test.ts b/resources/js/wysiwyg/lexical/rich-text/__tests__/unit/LexicalMediaNode.test.ts new file mode 100644 index 000000000..c55ae669e --- /dev/null +++ b/resources/js/wysiwyg/lexical/rich-text/__tests__/unit/LexicalMediaNode.test.ts @@ -0,0 +1,31 @@ +import {createTestContext} from "lexical/__tests__/utils"; +import {$createMediaNode} from "@lexical/rich-text/LexicalMediaNode"; + + +describe('LexicalMediaNode', () => { + + test('setWidth/setHeight/setWidthAndHeight functions remove relevant styles', () => { + const {editor} = createTestContext(); + editor.updateAndCommit(() => { + const mediaMode = $createMediaNode('video'); + const defaultStyles = {style: 'width:20px;height:40px;color:red'}; + + mediaMode.setAttributes(defaultStyles); + mediaMode.setWidth(60); + expect(mediaMode.getWidth()).toBe(60); + expect(mediaMode.getAttributes().style).toBe('height:40px;color:red'); + + mediaMode.setAttributes(defaultStyles); + mediaMode.setHeight(77); + expect(mediaMode.getHeight()).toBe(77); + expect(mediaMode.getAttributes().style).toBe('width:20px;color:red'); + + mediaMode.setAttributes(defaultStyles); + mediaMode.setWidthAndHeight('6', '7'); + expect(mediaMode.getWidth()).toBe(6); + expect(mediaMode.getHeight()).toBe(7); + expect(mediaMode.getAttributes().style).toBe('color:red'); + }); + }); + +}); \ No newline at end of file diff --git a/resources/js/wysiwyg/ui/framework/helpers/node-resizer.ts b/resources/js/wysiwyg/ui/framework/helpers/node-resizer.ts index 934ac5763..8b0a1d2d7 100644 --- a/resources/js/wysiwyg/ui/framework/helpers/node-resizer.ts +++ b/resources/js/wysiwyg/ui/framework/helpers/node-resizer.ts @@ -13,7 +13,7 @@ function isNodeWithSize(node: LexicalNode): node is NodeHasSize&LexicalNode { class NodeResizer { protected context: EditorUiContext; protected resizerDOM: HTMLElement|null = null; - protected targetDOM: HTMLElement|null = null; + protected targetNode: LexicalNode|null = null; protected scrollContainer: HTMLElement; protected mouseTracker: MouseDragTracker|null = null; @@ -38,12 +38,7 @@ class NodeResizer { if (nodes.length === 1 && isNodeWithSize(nodes[0])) { const node = nodes[0]; - const nodeKey = node.getKey(); - let nodeDOM = this.context.editor.getElementByKey(nodeKey); - - if (nodeDOM && nodeDOM.nodeName === 'SPAN') { - nodeDOM = nodeDOM.firstElementChild as HTMLElement; - } + let nodeDOM = this.getTargetDOM(node) if (nodeDOM) { this.showForNode(node, nodeDOM); @@ -51,7 +46,19 @@ class NodeResizer { } } - onTargetDOMLoad(): void { + protected getTargetDOM(targetNode: LexicalNode|null): HTMLElement|null { + if (targetNode == null) { + return null; + } + + let nodeDOM = this.context.editor.getElementByKey(targetNode.__key) + if (nodeDOM && nodeDOM.nodeName === 'SPAN') { + nodeDOM = nodeDOM.firstElementChild as HTMLElement; + } + return nodeDOM; + } + + protected onTargetDOMLoad(): void { this.updateResizerPosition(); } @@ -62,7 +69,7 @@ class NodeResizer { protected showForNode(node: NodeHasSize&LexicalNode, targetDOM: HTMLElement) { this.resizerDOM = this.buildDOM(); - this.targetDOM = targetDOM; + this.targetNode = node; let ghost = el('span', {class: 'editor-node-resizer-ghost'}); if ($isImageNode(node)) { @@ -83,12 +90,13 @@ class NodeResizer { } protected updateResizerPosition() { - if (!this.resizerDOM || !this.targetDOM) { + const targetDOM = this.getTargetDOM(this.targetNode); + if (!this.resizerDOM || !targetDOM) { return; } const scrollAreaRect = this.scrollContainer.getBoundingClientRect(); - const nodeRect = this.targetDOM.getBoundingClientRect(); + const nodeRect = targetDOM.getBoundingClientRect(); const top = nodeRect.top - (scrollAreaRect.top - this.scrollContainer.scrollTop); const left = nodeRect.left - scrollAreaRect.left; @@ -110,7 +118,7 @@ class NodeResizer { protected hide() { this.mouseTracker?.teardown(); this.resizerDOM?.remove(); - this.targetDOM = null; + this.targetNode = null; this.activeSelection = ''; this.loadAbortController.abort(); } @@ -126,7 +134,7 @@ class NodeResizer { }, handleElems); } - setupTracker(container: HTMLElement, node: NodeHasSize, nodeDOM: HTMLElement): MouseDragTracker { + setupTracker(container: HTMLElement, node: NodeHasSize&LexicalNode, nodeDOM: HTMLElement): MouseDragTracker { let startingWidth: number = 0; let startingHeight: number = 0; let startingRatio: number = 0; @@ -179,10 +187,13 @@ class NodeResizer { _this.context.editor.update(() => { node.setWidth(size.width); node.setHeight(hasHeight ? size.height : 0); - _this.context.manager.triggerLayoutUpdate(); - requestAnimationFrame(() => { - _this.updateResizerPosition(); - }) + }, { + onUpdate: () => { + requestAnimationFrame(() => { + _this.context.manager.triggerLayoutUpdate(); + _this.updateResizerPosition(); + }); + } }); _this.resizerDOM?.classList.remove('active'); } diff --git a/resources/js/wysiwyg/utils/dom.ts b/resources/js/wysiwyg/utils/dom.ts index bbb07cb41..8bacd1002 100644 --- a/resources/js/wysiwyg/utils/dom.ts +++ b/resources/js/wysiwyg/utils/dom.ts @@ -52,12 +52,19 @@ export type StyleMap = Map; /** * Creates a map from an element's styles. * Uses direct attribute value string handling since attempting to iterate - * over .style will expand out any shorthand properties (like 'padding') making + * over .style will expand out any shorthand properties (like 'padding') * rather than being representative of the actual properties set. */ export function extractStyleMapFromElement(element: HTMLElement): StyleMap { - const map: StyleMap = new Map(); const styleText= element.getAttribute('style') || ''; + return styleStringToStyleMap(styleText); +} + +/** + * Convert string-formatted styles into a StyleMap. + */ +export function styleStringToStyleMap(styleText: string): StyleMap { + const map: StyleMap = new Map(); const rules = styleText.split(';'); for (const rule of rules) { @@ -72,6 +79,17 @@ export function extractStyleMapFromElement(element: HTMLElement): StyleMap { return map; } +/** + * Convert a StyleMap into inline string style text. + */ +export function styleMapToStyleString(map: StyleMap): string { + const parts = []; + for (const [style, value] of map.entries()) { + parts.push(`${style}:${value}`); + } + return parts.join(';'); +} + export function setOrRemoveAttribute(element: HTMLElement, name: string, value: string|null|undefined) { if (value) { element.setAttribute(name, value); diff --git a/resources/sass/_editor.scss b/resources/sass/_editor.scss index 4112f6288..633fa78a6 100644 --- a/resources/sass/_editor.scss +++ b/resources/sass/_editor.scss @@ -454,7 +454,7 @@ body.editor-is-fullscreen { .editor-media-wrap { display: inline-block; cursor: not-allowed; - iframe { + iframe, video { pointer-events: none; } &.align-left { From 1611b0399f2ccfcf5f261642a12076cde91b9600 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 15 Jun 2025 15:22:27 +0100 Subject: [PATCH 07/12] Lexical: Added a media toolbar, improved toolbars and media selection - Updated toolbars to auto-refresh ui if it attempts to update targeting a DOM element which no longer exists. - Removed MediaNode dom specific click handling which was causing selection issues, and did not seem to be needed now. --- .../js/wysiwyg/lexical/rich-text/LexicalMediaNode.ts | 8 +------- resources/js/wysiwyg/ui/defaults/toolbars.ts | 4 ++++ resources/js/wysiwyg/ui/framework/toolbars.ts | 6 +++++- resources/js/wysiwyg/ui/index.ts | 6 +++++- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/resources/js/wysiwyg/lexical/rich-text/LexicalMediaNode.ts b/resources/js/wysiwyg/lexical/rich-text/LexicalMediaNode.ts index 6e9c24717..4f4b1d82a 100644 --- a/resources/js/wysiwyg/lexical/rich-text/LexicalMediaNode.ts +++ b/resources/js/wysiwyg/lexical/rich-text/LexicalMediaNode.ts @@ -238,15 +238,9 @@ export class MediaNode extends ElementNode { createDOM(_config: EditorConfig, _editor: LexicalEditor) { const media = this.createInnerDOM(); - const wrap = el('span', { + return el('span', { class: media.className + ' editor-media-wrap', }, [media]); - - wrap.addEventListener('click', e => { - _editor.update(() => $selectSingleNode(this)); - }); - - return wrap; } updateDOM(prevNode: MediaNode, dom: HTMLElement): boolean { diff --git a/resources/js/wysiwyg/ui/defaults/toolbars.ts b/resources/js/wysiwyg/ui/defaults/toolbars.ts index b09a7530f..cdc451d08 100644 --- a/resources/js/wysiwyg/ui/defaults/toolbars.ts +++ b/resources/js/wysiwyg/ui/defaults/toolbars.ts @@ -224,6 +224,10 @@ export function getImageToolbarContent(): EditorUiElement[] { return [new EditorButton(image)]; } +export function getMediaToolbarContent(): EditorUiElement[] { + return [new EditorButton(media)]; +} + export function getLinkToolbarContent(): EditorUiElement[] { return [ new EditorButton(link), diff --git a/resources/js/wysiwyg/ui/framework/toolbars.ts b/resources/js/wysiwyg/ui/framework/toolbars.ts index b4e49af95..de2255444 100644 --- a/resources/js/wysiwyg/ui/framework/toolbars.ts +++ b/resources/js/wysiwyg/ui/framework/toolbars.ts @@ -34,7 +34,11 @@ export class EditorContextToolbar extends EditorContainerUiElement { dom.hidden = !showing; - if (!showing) { + if (!this.target.isConnected) { + // If our target is no longer in the DOM, tell the manager an update is needed. + this.getContext().manager.triggerFutureStateRefresh(); + return; + } else if (!showing) { return; } diff --git a/resources/js/wysiwyg/ui/index.ts b/resources/js/wysiwyg/ui/index.ts index fda37085e..e7ec6adbc 100644 --- a/resources/js/wysiwyg/ui/index.ts +++ b/resources/js/wysiwyg/ui/index.ts @@ -3,7 +3,7 @@ import { getCodeToolbarContent, getDetailsToolbarContent, getImageToolbarContent, getLinkToolbarContent, - getMainEditorFullToolbar, getTableToolbarContent + getMainEditorFullToolbar, getMediaToolbarContent, getTableToolbarContent } from "./defaults/toolbars"; import {EditorUIManager} from "./framework/manager"; import {EditorUiContext} from "./framework/core"; @@ -44,6 +44,10 @@ export function buildEditorUI(container: HTMLElement, element: HTMLElement, scro selector: 'img:not([drawio-diagram] img)', content: getImageToolbarContent(), }); + manager.registerContextToolbar('media', { + selector: '.editor-media-wrap', + content: getMediaToolbarContent(), + }); manager.registerContextToolbar('link', { selector: 'a', content: getLinkToolbarContent(), From b913ae703deb29c0e4bca42a0d8938587dbc2e75 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 15 Jun 2025 20:00:28 +0100 Subject: [PATCH 08/12] Lexical: Media form improvements - Allowed re-editing of existing embed HTML code. - Handled "src" form field when video is using child source tags. --- .../lexical/rich-text/LexicalMediaNode.ts | 13 +++++++++++-- .../__tests__/unit/LexicalMediaNode.test.ts | 15 +++++++++++++++ .../js/wysiwyg/ui/defaults/forms/objects.ts | 19 ++++++++++++++++--- resources/js/wysiwyg/ui/framework/forms.ts | 5 ++++- 4 files changed, 46 insertions(+), 6 deletions(-) diff --git a/resources/js/wysiwyg/lexical/rich-text/LexicalMediaNode.ts b/resources/js/wysiwyg/lexical/rich-text/LexicalMediaNode.ts index 4f4b1d82a..a7acc4ad3 100644 --- a/resources/js/wysiwyg/lexical/rich-text/LexicalMediaNode.ts +++ b/resources/js/wysiwyg/lexical/rich-text/LexicalMediaNode.ts @@ -14,7 +14,6 @@ import { setCommonBlockPropsFromElement, updateElementWithCommonBlockProps } from "lexical/nodes/common"; -import {$selectSingleNode} from "../../utils/selection"; import {SerializedCommonBlockNode} from "lexical/nodes/CommonBlockNode"; export type MediaNodeTag = 'iframe' | 'embed' | 'object' | 'video' | 'audio'; @@ -141,16 +140,26 @@ export class MediaNode extends ElementNode { getSources(): MediaNodeSource[] { const self = this.getLatest(); - return self.__sources; + return self.__sources.map(s => Object.assign({}, s)) } setSrc(src: string): void { const attrs = this.getAttributes(); + const sources = this.getSources(); + if (this.__tag ==='object') { attrs.data = src; + } if (this.__tag === 'video' && sources.length > 0) { + sources[0].src = src; + delete attrs.src; + if (sources.length > 1) { + sources.splice(1, sources.length - 1); + } + this.setSources(sources); } else { attrs.src = src; } + this.setAttributes(attrs); } diff --git a/resources/js/wysiwyg/lexical/rich-text/__tests__/unit/LexicalMediaNode.test.ts b/resources/js/wysiwyg/lexical/rich-text/__tests__/unit/LexicalMediaNode.test.ts index c55ae669e..b142e95a0 100644 --- a/resources/js/wysiwyg/lexical/rich-text/__tests__/unit/LexicalMediaNode.test.ts +++ b/resources/js/wysiwyg/lexical/rich-text/__tests__/unit/LexicalMediaNode.test.ts @@ -28,4 +28,19 @@ describe('LexicalMediaNode', () => { }); }); + test('setSrc on video uses sources if existing', () => { + const {editor} = createTestContext(); + editor.updateAndCommit(() => { + const mediaMode = $createMediaNode('video'); + mediaMode.setAttributes({src: 'z'}); + mediaMode.setSources([{src: 'a', type: 'video'}, {src: 'b', type: 'video'}]); + + mediaMode.setSrc('c'); + + expect(mediaMode.getAttributes().src).toBeUndefined(); + expect(mediaMode.getSources()).toHaveLength(1); + expect(mediaMode.getSources()[0].src).toBe('c'); + }); + }); + }); \ No newline at end of file diff --git a/resources/js/wysiwyg/ui/defaults/forms/objects.ts b/resources/js/wysiwyg/ui/defaults/forms/objects.ts index 0effdc171..cdf464cb4 100644 --- a/resources/js/wysiwyg/ui/defaults/forms/objects.ts +++ b/resources/js/wysiwyg/ui/defaults/forms/objects.ts @@ -192,11 +192,17 @@ export function $showMediaForm(media: MediaNode|null, context: EditorUiContext): let formDefaults = {}; if (media) { const nodeAttrs = media.getAttributes(); + const nodeDOM = media.exportDOM(context.editor).element; + const nodeHtml = (nodeDOM instanceof HTMLElement) ? nodeDOM.outerHTML : ''; + formDefaults = { - src: nodeAttrs.src || nodeAttrs.data || '', + src: nodeAttrs.src || nodeAttrs.data || media.getSources()[0]?.src || '', width: nodeAttrs.width, height: nodeAttrs.height, - embed: '', + embed: nodeHtml, + + // This is used so we can check for edits against the embed field on submit + embed_check: nodeHtml, } } @@ -214,7 +220,8 @@ export const media: EditorFormDefinition = { })); const embedCode = (formData.get('embed') || '').toString().trim(); - if (embedCode) { + const embedCheck = (formData.get('embed_check') || '').toString().trim(); + if (embedCode && embedCode !== embedCheck) { context.editor.update(() => { const node = $createMediaNodeFromHtml(embedCode); if (selectedNode && node) { @@ -236,6 +243,7 @@ export const media: EditorFormDefinition = { if (selectedNode) { selectedNode.setSrc(src); selectedNode.setWidthAndHeight(width, height); + context.manager.triggerFutureStateRefresh(); return; } @@ -281,6 +289,11 @@ export const media: EditorFormDefinition = { name: 'embed', type: 'textarea', }, + { + label: '', + name: 'embed_check', + type: 'hidden', + }, ], } ]) diff --git a/resources/js/wysiwyg/ui/framework/forms.ts b/resources/js/wysiwyg/ui/framework/forms.ts index 08edb214e..b12d9f692 100644 --- a/resources/js/wysiwyg/ui/framework/forms.ts +++ b/resources/js/wysiwyg/ui/framework/forms.ts @@ -11,7 +11,7 @@ import {el} from "../../utils/dom"; export interface EditorFormFieldDefinition { label: string; name: string; - type: 'text' | 'select' | 'textarea' | 'checkbox'; + type: 'text' | 'select' | 'textarea' | 'checkbox' | 'hidden'; } export interface EditorSelectFormFieldDefinition extends EditorFormFieldDefinition { @@ -67,6 +67,9 @@ export class EditorFormField extends EditorUiElement { input = el('textarea', {id, name: this.definition.name, class: 'editor-form-field-input'}); } else if (this.definition.type === 'checkbox') { input = el('input', {id, name: this.definition.name, type: 'checkbox', class: 'editor-form-field-input-checkbox', value: 'true'}); + } else if (this.definition.type === 'hidden') { + input = el('input', {id, name: this.definition.name, type: 'hidden'}); + return el('div', {hidden: 'true'}, [input]); } else { input = el('input', {id, name: this.definition.name, class: 'editor-form-field-input'}); } From 0208f066c5fdac676cfd7dc7fd9476db68e55609 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 17 Jun 2025 13:42:25 +0100 Subject: [PATCH 09/12] Comments: Fixed update notification text For #5642 --- resources/js/components/page-comment.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/js/components/page-comment.ts b/resources/js/components/page-comment.ts index 0c3e19f4b..a0bb7a55b 100644 --- a/resources/js/components/page-comment.ts +++ b/resources/js/components/page-comment.ts @@ -40,7 +40,7 @@ export class PageComment extends Component { this.commentId = this.$opts.commentId; this.commentLocalId = this.$opts.commentLocalId; this.deletedText = this.$opts.deletedText; - this.deletedText = this.$opts.deletedText; + this.updatedText = this.$opts.updatedText; this.archiveText = this.$opts.archiveText; // Editor reference and text options From f518a3be373500b0382b49ef814b9c5ee76c3d8e Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 17 Jun 2025 13:59:28 +0100 Subject: [PATCH 10/12] Search: Updated indexer to handle non-breaking-spaces Related to #5640 --- app/Search/SearchIndex.php | 4 +++- tests/Search/SearchIndexingTest.php | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/Search/SearchIndex.php b/app/Search/SearchIndex.php index 36f71f6cc..844e3584b 100644 --- a/app/Search/SearchIndex.php +++ b/app/Search/SearchIndex.php @@ -160,7 +160,9 @@ class SearchIndex /** @var DOMNode $child */ foreach ($doc->getBodyChildren() as $child) { $nodeName = $child->nodeName; - $termCounts = $this->textToTermCountMap(trim($child->textContent)); + $text = trim($child->textContent); + $text = str_replace("\u{00A0}", ' ', $text); + $termCounts = $this->textToTermCountMap($text); foreach ($termCounts as $term => $count) { $scoreChange = $count * ($elementScoreAdjustmentMap[$nodeName] ?? 1); $scoresByTerm[$term] = ($scoresByTerm[$term] ?? 0) + $scoreChange; diff --git a/tests/Search/SearchIndexingTest.php b/tests/Search/SearchIndexingTest.php index 64779dec6..d2bbb2905 100644 --- a/tests/Search/SearchIndexingTest.php +++ b/tests/Search/SearchIndexingTest.php @@ -106,4 +106,14 @@ class SearchIndexingTest extends TestCase $this->assertNull($scoreByTerm->get($term), "Failed asserting that \"$term\" is not indexed"); } } + + public function test_non_breaking_spaces_handled_as_spaces() + { + $page = $this->entities->newPage(['html' => '

a tigerbadger is a dangerous animal

']); + + $scoreByTerm = $page->searchTerms()->pluck('score', 'term'); + $this->assertNotNull($scoreByTerm->get('tigerbadger')); + $this->assertNotNull($scoreByTerm->get('dangerous')); + $this->assertNotNull($scoreByTerm->get('animal')); + } } From a5751a584c09934cd7b214e2e812276ddfd669e2 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 17 Jun 2025 15:16:25 +0100 Subject: [PATCH 11/12] Updated translations with latest Crowdin changes (#5637) --- lang/cs/common.php | 4 ++-- lang/cs/entities.php | 22 +++++++++++----------- lang/de/common.php | 4 ++-- lang/de/entities.php | 22 +++++++++++----------- lang/de_informal/common.php | 4 ++-- lang/de_informal/entities.php | 22 +++++++++++----------- lang/es/common.php | 4 ++-- lang/es/entities.php | 22 +++++++++++----------- lang/es_AR/entities.php | 2 +- lang/et/entities.php | 2 +- lang/fi/errors.php | 26 +++++++++++++------------- lang/it/common.php | 4 ++-- lang/it/entities.php | 20 ++++++++++---------- lang/ja/common.php | 4 ++-- lang/ja/entities.php | 22 +++++++++++----------- lang/nl/common.php | 4 ++-- lang/nl/entities.php | 24 ++++++++++++------------ lang/pt_BR/common.php | 4 ++-- lang/pt_BR/entities.php | 22 +++++++++++----------- lang/zh_TW/activities.php | 2 +- lang/zh_TW/errors.php | 2 +- 21 files changed, 121 insertions(+), 121 deletions(-) diff --git a/lang/cs/common.php b/lang/cs/common.php index 32d8f4b61..25c32b039 100644 --- a/lang/cs/common.php +++ b/lang/cs/common.php @@ -30,8 +30,8 @@ return [ 'create' => 'Vytvořit', 'update' => 'Aktualizovat', 'edit' => 'Upravit', - 'archive' => 'Archive', - 'unarchive' => 'Un-Archive', + 'archive' => 'Archivovat', + 'unarchive' => 'Od-Archivovat', 'sort' => 'Seřadit', 'move' => 'Přesunout', 'copy' => 'Kopírovat', diff --git a/lang/cs/entities.php b/lang/cs/entities.php index 3af8f389b..eaeac76c5 100644 --- a/lang/cs/entities.php +++ b/lang/cs/entities.php @@ -248,7 +248,7 @@ return [ 'pages_edit_switch_to_markdown_stable' => '(Stabilní obsah)', 'pages_edit_switch_to_wysiwyg' => 'Přepnout na WYSIWYG Editor', 'pages_edit_switch_to_new_wysiwyg' => 'Přepnout na nový WYSIWYG', - 'pages_edit_switch_to_new_wysiwyg_desc' => '(In Beta Testing)', + 'pages_edit_switch_to_new_wysiwyg_desc' => '(V beta testování)', 'pages_edit_set_changelog' => 'Nastavit protokol změn', 'pages_edit_enter_changelog_desc' => 'Zadejte stručný popis změn, které jste provedli', 'pages_edit_enter_changelog' => 'Zadejte protokol změn', @@ -392,11 +392,11 @@ return [ 'comment' => 'Komentář', 'comments' => 'Komentáře', 'comment_add' => 'Přidat komentář', - 'comment_none' => 'No comments to display', + 'comment_none' => 'Žádné komentáře k zobrazení', 'comment_placeholder' => 'Zde zadejte komentář', - 'comment_thread_count' => ':count Comment Thread|:count Comment Threads', - 'comment_archived_count' => ':count Archived', - 'comment_archived_threads' => 'Archived Threads', + 'comment_thread_count' => ':count vlákno komentáře|:count vláken komentářů', + 'comment_archived_count' => ':count archivováno', + 'comment_archived_threads' => 'Archivovaná vlákna', 'comment_save' => 'Uložit komentář', 'comment_new' => 'Nový komentář', 'comment_created' => 'komentováno :createDiff', @@ -405,14 +405,14 @@ return [ 'comment_deleted_success' => 'Komentář odstraněn', 'comment_created_success' => 'Komentář přidán', 'comment_updated_success' => 'Komentář aktualizován', - 'comment_archive_success' => 'Comment archived', - 'comment_unarchive_success' => 'Comment un-archived', - 'comment_view' => 'View comment', - 'comment_jump_to_thread' => 'Jump to thread', + 'comment_archive_success' => 'Komentář archivován', + 'comment_unarchive_success' => 'Komentář od-archivován', + 'comment_view' => 'Zobrazit komentář', + 'comment_jump_to_thread' => 'Přejít na vlákno', 'comment_delete_confirm' => 'Opravdu chcete odstranit tento komentář?', 'comment_in_reply_to' => 'Odpověď na :commentId', - 'comment_reference' => 'Reference', - 'comment_reference_outdated' => '(Outdated)', + 'comment_reference' => 'Odkaz', + 'comment_reference_outdated' => '(Zastaralý)', 'comment_editor_explain' => 'Zde jsou komentáře, které zůstaly na této stránce. Komentáře lze přidat a spravovat při prohlížení uložené stránky.', // Revision diff --git a/lang/de/common.php b/lang/de/common.php index bbf8d8f23..4f2ae54d2 100644 --- a/lang/de/common.php +++ b/lang/de/common.php @@ -30,8 +30,8 @@ return [ 'create' => 'Erstellen', 'update' => 'Aktualisieren', 'edit' => 'Bearbeiten', - 'archive' => 'Archive', - 'unarchive' => 'Un-Archive', + 'archive' => 'Archivieren', + 'unarchive' => 'Nicht mehr archivieren', 'sort' => 'Sortieren', 'move' => 'Verschieben', 'copy' => 'Kopieren', diff --git a/lang/de/entities.php b/lang/de/entities.php index 2b8657745..144a93636 100644 --- a/lang/de/entities.php +++ b/lang/de/entities.php @@ -248,7 +248,7 @@ return [ 'pages_edit_switch_to_markdown_stable' => '(Stabiler Inhalt)', 'pages_edit_switch_to_wysiwyg' => 'Zum WYSIWYG-Editor wechseln', 'pages_edit_switch_to_new_wysiwyg' => 'Zum neuen WYSIWYG wechseln', - 'pages_edit_switch_to_new_wysiwyg_desc' => '(In Beta Testing)', + 'pages_edit_switch_to_new_wysiwyg_desc' => '(Im Beta-Test)', 'pages_edit_set_changelog' => 'Änderungsprotokoll hinzufügen', 'pages_edit_enter_changelog_desc' => 'Bitte geben Sie eine kurze Zusammenfassung Ihrer Änderungen ein', 'pages_edit_enter_changelog' => 'Änderungsprotokoll eingeben', @@ -392,11 +392,11 @@ return [ 'comment' => 'Kommentar', 'comments' => 'Kommentare', 'comment_add' => 'Kommentieren', - 'comment_none' => 'No comments to display', + 'comment_none' => 'Keine Kommentare vorhanden', 'comment_placeholder' => 'Geben Sie hier Ihre Kommentare ein', - 'comment_thread_count' => ':count Comment Thread|:count Comment Threads', - 'comment_archived_count' => ':count Archived', - 'comment_archived_threads' => 'Archived Threads', + 'comment_thread_count' => ':count Thema|:count Themen', + 'comment_archived_count' => ':count archiviert', + 'comment_archived_threads' => 'Archivierte Themen', 'comment_save' => 'Kommentar speichern', 'comment_new' => 'Neuer Kommentar', 'comment_created' => ':createDiff kommentiert', @@ -405,14 +405,14 @@ return [ 'comment_deleted_success' => 'Kommentar gelöscht', 'comment_created_success' => 'Kommentar hinzugefügt', 'comment_updated_success' => 'Kommentar aktualisiert', - 'comment_archive_success' => 'Comment archived', - 'comment_unarchive_success' => 'Comment un-archived', - 'comment_view' => 'View comment', - 'comment_jump_to_thread' => 'Jump to thread', + 'comment_archive_success' => 'Kommentar archiviert', + 'comment_unarchive_success' => 'Kommentar nicht mehr archiviert', + 'comment_view' => 'Kommentar ansehen', + 'comment_jump_to_thread' => 'Zum Thema springen', 'comment_delete_confirm' => 'Möchten Sie diesen Kommentar wirklich löschen?', 'comment_in_reply_to' => 'Antwort auf :commentId', - 'comment_reference' => 'Reference', - 'comment_reference_outdated' => '(Outdated)', + 'comment_reference' => 'Referenz', + 'comment_reference_outdated' => '(Veraltet)', 'comment_editor_explain' => 'Hier sind die Kommentare, die auf dieser Seite hinterlassen wurden. Kommentare können hinzugefügt und verwaltet werden, wenn die gespeicherte Seite angezeigt wird.', // Revision diff --git a/lang/de_informal/common.php b/lang/de_informal/common.php index 47ba26834..d07fa056f 100644 --- a/lang/de_informal/common.php +++ b/lang/de_informal/common.php @@ -30,8 +30,8 @@ return [ 'create' => 'Anlegen', 'update' => 'Aktualisieren', 'edit' => 'Bearbeiten', - 'archive' => 'Archive', - 'unarchive' => 'Un-Archive', + 'archive' => 'Archivieren', + 'unarchive' => 'Nicht mehr archivieren', 'sort' => 'Sortieren', 'move' => 'Verschieben', 'copy' => 'Kopieren', diff --git a/lang/de_informal/entities.php b/lang/de_informal/entities.php index 12a3dd8fe..d78addf2a 100644 --- a/lang/de_informal/entities.php +++ b/lang/de_informal/entities.php @@ -248,7 +248,7 @@ return [ 'pages_edit_switch_to_markdown_stable' => '(Stabiler Inhalt)', 'pages_edit_switch_to_wysiwyg' => 'Zum WYSIWYG-Editor wechseln', 'pages_edit_switch_to_new_wysiwyg' => 'Zum neuen WYSIWYG wechseln', - 'pages_edit_switch_to_new_wysiwyg_desc' => '(In Beta Testing)', + 'pages_edit_switch_to_new_wysiwyg_desc' => '(Im Beta-Test)', 'pages_edit_set_changelog' => 'Änderungsprotokoll hinzufügen', 'pages_edit_enter_changelog_desc' => 'Bitte gib eine kurze Zusammenfassung deiner Änderungen ein', 'pages_edit_enter_changelog' => 'Änderungsprotokoll eingeben', @@ -392,11 +392,11 @@ return [ 'comment' => 'Kommentar', 'comments' => 'Kommentare', 'comment_add' => 'Kommentieren', - 'comment_none' => 'No comments to display', + 'comment_none' => 'Keine Kommentare vorhanden', 'comment_placeholder' => 'Gib hier deine Kommentare ein', - 'comment_thread_count' => ':count Comment Thread|:count Comment Threads', - 'comment_archived_count' => ':count Archived', - 'comment_archived_threads' => 'Archived Threads', + 'comment_thread_count' => ':count Thema|:count Themen', + 'comment_archived_count' => ':count archiviert', + 'comment_archived_threads' => 'Archivierte Themen', 'comment_save' => 'Kommentar speichern', 'comment_new' => 'Neuer Kommentar', 'comment_created' => ':createDiff kommentiert', @@ -405,14 +405,14 @@ return [ 'comment_deleted_success' => 'Kommentar gelöscht', 'comment_created_success' => 'Kommentar hinzugefügt', 'comment_updated_success' => 'Kommentar aktualisiert', - 'comment_archive_success' => 'Comment archived', - 'comment_unarchive_success' => 'Comment un-archived', - 'comment_view' => 'View comment', - 'comment_jump_to_thread' => 'Jump to thread', + 'comment_archive_success' => 'Kommentar archiviert', + 'comment_unarchive_success' => 'Kommentar nicht mehr archiviert', + 'comment_view' => 'Kommentar ansehen', + 'comment_jump_to_thread' => 'Zum Thema springen', 'comment_delete_confirm' => 'Möchtst du diesen Kommentar wirklich löschen?', 'comment_in_reply_to' => 'Antwort auf :commentId', - 'comment_reference' => 'Reference', - 'comment_reference_outdated' => '(Outdated)', + 'comment_reference' => 'Referenz', + 'comment_reference_outdated' => '(Veraltet)', 'comment_editor_explain' => 'Hier sind die Kommentare, die auf dieser Seite hinterlassen wurden. Kommentare können hinzugefügt und verwaltet werden, wenn die gespeicherte Seite angezeigt wird.', // Revision diff --git a/lang/es/common.php b/lang/es/common.php index cff4fced6..399d1d862 100644 --- a/lang/es/common.php +++ b/lang/es/common.php @@ -30,8 +30,8 @@ return [ 'create' => 'Crear', 'update' => 'Actualizar', 'edit' => 'Editar', - 'archive' => 'Archive', - 'unarchive' => 'Un-Archive', + 'archive' => 'Archivar', + 'unarchive' => 'Desarchivar', 'sort' => 'Ordenar', 'move' => 'Mover', 'copy' => 'Copiar', diff --git a/lang/es/entities.php b/lang/es/entities.php index bff9ff47e..303730f03 100644 --- a/lang/es/entities.php +++ b/lang/es/entities.php @@ -248,7 +248,7 @@ return [ 'pages_edit_switch_to_markdown_stable' => '(Contenido Estable)', 'pages_edit_switch_to_wysiwyg' => 'Cambiar a Editor WYSIWYG', 'pages_edit_switch_to_new_wysiwyg' => 'Cambiar a nuevo editor WYSIWYG', - 'pages_edit_switch_to_new_wysiwyg_desc' => '(In Beta Testing)', + 'pages_edit_switch_to_new_wysiwyg_desc' => '(En prueba beta)', 'pages_edit_set_changelog' => 'Ajustar Log de cambios', 'pages_edit_enter_changelog_desc' => 'Introduzca una breve descripción de los cambios que ha realizado', 'pages_edit_enter_changelog' => 'Entrar al Log de cambios', @@ -392,11 +392,11 @@ return [ 'comment' => 'Comentario', 'comments' => 'Comentarios', 'comment_add' => 'Añadir Comentario', - 'comment_none' => 'No comments to display', + 'comment_none' => 'No hay comentarios para mostrar', 'comment_placeholder' => 'Introduzca su comentario aquí', - 'comment_thread_count' => ':count Comment Thread|:count Comment Threads', - 'comment_archived_count' => ':count Archived', - 'comment_archived_threads' => 'Archived Threads', + 'comment_thread_count' => ':count hilo de comentarios|:count hilos de comentarios', + 'comment_archived_count' => ':count Archivados', + 'comment_archived_threads' => 'Hilos archivados', 'comment_save' => 'Guardar comentario', 'comment_new' => 'Nuevo Comentario', 'comment_created' => 'comentado :createDiff', @@ -405,14 +405,14 @@ return [ 'comment_deleted_success' => 'Comentario borrado', 'comment_created_success' => 'Comentario añadido', 'comment_updated_success' => 'Comentario actualizado', - 'comment_archive_success' => 'Comment archived', - 'comment_unarchive_success' => 'Comment un-archived', - 'comment_view' => 'View comment', - 'comment_jump_to_thread' => 'Jump to thread', + 'comment_archive_success' => 'Comentario archivado', + 'comment_unarchive_success' => 'Comentario desarchivado', + 'comment_view' => 'Ver comentario', + 'comment_jump_to_thread' => 'Ir al hilo', 'comment_delete_confirm' => '¿Está seguro de que quiere borrar este comentario?', 'comment_in_reply_to' => 'En respuesta a :commentId', - 'comment_reference' => 'Reference', - 'comment_reference_outdated' => '(Outdated)', + 'comment_reference' => 'Referencia', + 'comment_reference_outdated' => '(obsoleto)', 'comment_editor_explain' => 'Estos son los comentarios que se han escrito en esta página. Los comentarios se pueden añadir y administrar cuando se ve la página guardada.', // Revision diff --git a/lang/es_AR/entities.php b/lang/es_AR/entities.php index bb21300c2..729d19027 100644 --- a/lang/es_AR/entities.php +++ b/lang/es_AR/entities.php @@ -248,7 +248,7 @@ return [ 'pages_edit_switch_to_markdown_stable' => '(Contenido Estable)', 'pages_edit_switch_to_wysiwyg' => 'Cambiar a Editor WYSIWYG', 'pages_edit_switch_to_new_wysiwyg' => 'Cambiar a nuevo editor WYSIWYG', - 'pages_edit_switch_to_new_wysiwyg_desc' => '(In Beta Testing)', + 'pages_edit_switch_to_new_wysiwyg_desc' => '(En prueba beta)', 'pages_edit_set_changelog' => 'Establecer cambios de registro', 'pages_edit_enter_changelog_desc' => 'Introduzca una breve descripción de los cambios que ha realizado', 'pages_edit_enter_changelog' => 'Entrar en cambio de registro', diff --git a/lang/et/entities.php b/lang/et/entities.php index dc263081b..cc34d0a77 100644 --- a/lang/et/entities.php +++ b/lang/et/entities.php @@ -248,7 +248,7 @@ return [ 'pages_edit_switch_to_markdown_stable' => '(Stabiilne sisu)', 'pages_edit_switch_to_wysiwyg' => 'Kasuta WYSIWYG redaktorit', 'pages_edit_switch_to_new_wysiwyg' => 'Kasuta uut tekstiredaktorit', - 'pages_edit_switch_to_new_wysiwyg_desc' => '(In Beta Testing)', + 'pages_edit_switch_to_new_wysiwyg_desc' => '(beetatestimisel)', 'pages_edit_set_changelog' => 'Muudatuste logi', 'pages_edit_enter_changelog_desc' => 'Sisesta tehtud muudatuste lühikirjeldus', 'pages_edit_enter_changelog' => 'Salvesta muudatuste logi', diff --git a/lang/fi/errors.php b/lang/fi/errors.php index d948b556b..9af7490d5 100644 --- a/lang/fi/errors.php +++ b/lang/fi/errors.php @@ -10,7 +10,7 @@ return [ // Auth 'error_user_exists_different_creds' => 'Sähköpostiosoite :email on jo käytössä toisessa käyttäjätunnuksessa.', - 'auth_pre_register_theme_prevention' => 'User account could not be registered for the provided details', + 'auth_pre_register_theme_prevention' => 'Käyttäjätiliä ei voitu rekisteröidä annetuille tiedoille', 'email_already_confirmed' => 'Sähköposti on jo vahvistettu, yritä kirjautua sisään.', 'email_confirmation_invalid' => 'Tämä vahvistuslinkki ei ole voimassa tai sitä on jo käytetty, yritä rekisteröityä uudelleen.', 'email_confirmation_expired' => 'Vahvistuslinkki on vanhentunut, uusi vahvistussähköposti on lähetetty.', @@ -38,7 +38,7 @@ Sovellus ei tunnista ulkoisen todennuspalvelun pyyntöä. Ongelman voi aiheuttaa 'social_driver_not_found' => 'Sosiaalisen median tilin ajuria ei löytynyt', 'social_driver_not_configured' => ':socialAccount-tilin asetuksia ei ole määritetty oikein.', 'invite_token_expired' => 'Tämä kutsulinkki on vanhentunut. Voit sen sijaan yrittää palauttaa tilisi salasanan.', - 'login_user_not_found' => 'A user for this action could not be found.', + 'login_user_not_found' => 'Käyttäjää tälle toiminnolle ei löytynyt.', // System 'path_not_writable' => 'Tiedostopolkuun :filePath ei voitu ladata tiedostoa. Tarkista polun kirjoitusoikeudet.', @@ -79,7 +79,7 @@ Sovellus ei tunnista ulkoisen todennuspalvelun pyyntöä. Ongelman voi aiheuttaa // Users 'users_cannot_delete_only_admin' => 'Ainoaa ylläpitäjää ei voi poistaa', 'users_cannot_delete_guest' => 'Vieraskäyttäjää ei voi poistaa', - 'users_could_not_send_invite' => 'Could not create user since invite email failed to send', + 'users_could_not_send_invite' => 'Käyttäjää ei voitu luoda kutsun lähettämisen jälkeen', // Roles 'role_cannot_be_edited' => 'Tätä roolia ei voi muokata', @@ -107,16 +107,16 @@ Sovellus ei tunnista ulkoisen todennuspalvelun pyyntöä. Ongelman voi aiheuttaa 'back_soon' => 'Se palautetaan pian.', // Import - 'import_zip_cant_read' => 'Could not read ZIP file.', - 'import_zip_cant_decode_data' => 'Could not find and decode ZIP data.json content.', - 'import_zip_no_data' => 'ZIP file data has no expected book, chapter or page content.', - 'import_validation_failed' => 'Import ZIP failed to validate with errors:', - 'import_zip_failed_notification' => 'Failed to import ZIP file.', - 'import_perms_books' => 'You are lacking the required permissions to create books.', - 'import_perms_chapters' => 'You are lacking the required permissions to create chapters.', - 'import_perms_pages' => 'You are lacking the required permissions to create pages.', - 'import_perms_images' => 'You are lacking the required permissions to create images.', - 'import_perms_attachments' => 'You are lacking the required permission to create attachments.', + 'import_zip_cant_read' => 'ZIP-tiedostoa ei voitu lukea.', + 'import_zip_cant_decode_data' => 'ZIP-tiedoston data.json sisältöä ei löydy eikä sitä voitu purkaa.', + 'import_zip_no_data' => 'ZIP-tiedostoilla ei ole odotettua kirjaa, lukua tai sivun sisältöä.', + 'import_validation_failed' => 'Tuonti ZIP epäonnistui virheiden kanssa:', + 'import_zip_failed_notification' => 'ZIP-tiedoston tuominen epäonnistui.', + 'import_perms_books' => 'Sinulla ei ole tarvittavia oikeuksia luoda kirjoja.', + 'import_perms_chapters' => 'Sinulla ei ole tarvittavia oikeuksia luoda kappaleita.', + 'import_perms_pages' => 'Sinulla ei ole tarvittavia oikeuksia luoda sivuja.', + 'import_perms_images' => 'Sinulla ei ole tarvittavia oikeuksia luoda kuvia.', + 'import_perms_attachments' => 'Sinulla ei ole tarvittavaa lupaa luoda liitteitä.', // API errors 'api_no_authorization_found' => 'Pyynnöstä ei löytynyt valtuutuskoodia', diff --git a/lang/it/common.php b/lang/it/common.php index fbbac2be7..9eba738a4 100644 --- a/lang/it/common.php +++ b/lang/it/common.php @@ -30,8 +30,8 @@ return [ 'create' => 'Crea', 'update' => 'Aggiorna', 'edit' => 'Modifica', - 'archive' => 'Archive', - 'unarchive' => 'Un-Archive', + 'archive' => 'Archivia', + 'unarchive' => 'Ripristina', 'sort' => 'Ordina', 'move' => 'Sposta', 'copy' => 'Copia', diff --git a/lang/it/entities.php b/lang/it/entities.php index b032997d6..d615b0c06 100644 --- a/lang/it/entities.php +++ b/lang/it/entities.php @@ -248,7 +248,7 @@ return [ 'pages_edit_switch_to_markdown_stable' => '(Contenuto stabile)', 'pages_edit_switch_to_wysiwyg' => 'Passa all\'editor WYSIWYG', 'pages_edit_switch_to_new_wysiwyg' => 'Passa al nuovo WYSIWYG', - 'pages_edit_switch_to_new_wysiwyg_desc' => '(In Beta Testing)', + 'pages_edit_switch_to_new_wysiwyg_desc' => '(In Beta Test)', 'pages_edit_set_changelog' => 'Imposta changelog', 'pages_edit_enter_changelog_desc' => 'Inserisci una breve descrizione dei cambiamenti che hai apportato', 'pages_edit_enter_changelog' => 'Inserisci changelog', @@ -392,11 +392,11 @@ return [ 'comment' => 'Commento', 'comments' => 'Commenti', 'comment_add' => 'Aggiungi commento', - 'comment_none' => 'No comments to display', + 'comment_none' => 'Nessun commento da visualizzare', 'comment_placeholder' => 'Scrivi un commento', 'comment_thread_count' => ':count Comment Thread|:count Comment Threads', - 'comment_archived_count' => ':count Archived', - 'comment_archived_threads' => 'Archived Threads', + 'comment_archived_count' => ':count Archiviato', + 'comment_archived_threads' => 'Discussioni Archiviate', 'comment_save' => 'Salva commento', 'comment_new' => 'Nuovo commento', 'comment_created' => 'ha commentato :createDiff', @@ -405,14 +405,14 @@ return [ 'comment_deleted_success' => 'Commento eliminato', 'comment_created_success' => 'Commento aggiunto', 'comment_updated_success' => 'Commento aggiornato', - 'comment_archive_success' => 'Comment archived', - 'comment_unarchive_success' => 'Comment un-archived', - 'comment_view' => 'View comment', - 'comment_jump_to_thread' => 'Jump to thread', + 'comment_archive_success' => 'Commento archiviato', + 'comment_unarchive_success' => 'Commento ripristinato', + 'comment_view' => 'Visualizza commento', + 'comment_jump_to_thread' => 'Vai al thread', 'comment_delete_confirm' => 'Sei sicuro di voler eliminare questo commento?', 'comment_in_reply_to' => 'In risposta a :commentId', - 'comment_reference' => 'Reference', - 'comment_reference_outdated' => '(Outdated)', + 'comment_reference' => 'Riferimento', + 'comment_reference_outdated' => '(Obsoleto)', 'comment_editor_explain' => 'Ecco i commenti che sono stati lasciati in questa pagina. I commenti possono essere aggiunti e gestiti quando si visualizza la pagina salvata.', // Revision diff --git a/lang/ja/common.php b/lang/ja/common.php index 73799db5b..e888c43f6 100644 --- a/lang/ja/common.php +++ b/lang/ja/common.php @@ -30,8 +30,8 @@ return [ 'create' => '作成', 'update' => '更新', 'edit' => '編集', - 'archive' => 'Archive', - 'unarchive' => 'Un-Archive', + 'archive' => 'アーカイブ', + 'unarchive' => 'アーカイブ解除', 'sort' => '並び順', 'move' => '移動', 'copy' => 'コピー', diff --git a/lang/ja/entities.php b/lang/ja/entities.php index c6449bb34..cdb04b7b0 100644 --- a/lang/ja/entities.php +++ b/lang/ja/entities.php @@ -248,7 +248,7 @@ return [ 'pages_edit_switch_to_markdown_stable' => '(安定したコンテンツ)', 'pages_edit_switch_to_wysiwyg' => 'WYSIWYGエディタに切り替え', 'pages_edit_switch_to_new_wysiwyg' => '新しいWYSIWYGエディタに切り替える', - 'pages_edit_switch_to_new_wysiwyg_desc' => '(In Beta Testing)', + 'pages_edit_switch_to_new_wysiwyg_desc' => '(ベータテスト版)', 'pages_edit_set_changelog' => '編集内容についての説明', 'pages_edit_enter_changelog_desc' => 'どのような変更を行ったのかを記録してください', 'pages_edit_enter_changelog' => '編集内容を入力', @@ -393,11 +393,11 @@ return [ 'comment' => 'コメント', 'comments' => 'コメント', 'comment_add' => 'コメント追加', - 'comment_none' => 'No comments to display', + 'comment_none' => '表示するコメントがありません', 'comment_placeholder' => 'コメントを記入してください', - 'comment_thread_count' => ':count Comment Thread|:count Comment Threads', - 'comment_archived_count' => ':count Archived', - 'comment_archived_threads' => 'Archived Threads', + 'comment_thread_count' => ':count 個のコメントスレッド|:count 個のコメントスレッド', + 'comment_archived_count' => ':count 個のアーカイブ', + 'comment_archived_threads' => 'アーカイブされたスレッド', 'comment_save' => 'コメントを保存', 'comment_new' => '新規コメント作成', 'comment_created' => 'コメントを作成しました :createDiff', @@ -406,14 +406,14 @@ return [ 'comment_deleted_success' => 'コメントを削除しました', 'comment_created_success' => 'コメントを追加しました', 'comment_updated_success' => 'コメントを更新しました', - 'comment_archive_success' => 'Comment archived', - 'comment_unarchive_success' => 'Comment un-archived', - 'comment_view' => 'View comment', - 'comment_jump_to_thread' => 'Jump to thread', + 'comment_archive_success' => 'コメントをアーカイブしました', + 'comment_unarchive_success' => 'コメントのアーカイブを解除しました', + 'comment_view' => 'コメントを表示', + 'comment_jump_to_thread' => 'スレッドにジャンプ', 'comment_delete_confirm' => '本当にこのコメントを削除しますか?', 'comment_in_reply_to' => ':commentIdへ返信', - 'comment_reference' => 'Reference', - 'comment_reference_outdated' => '(Outdated)', + 'comment_reference' => '参照箇所', + 'comment_reference_outdated' => '(以前の記述)', 'comment_editor_explain' => 'ここにはページに付けられたコメントを表示します。 コメントの追加と管理は保存されたページの表示時に行うことができます。', // Revision diff --git a/lang/nl/common.php b/lang/nl/common.php index 0feddcd5d..47d8703ca 100644 --- a/lang/nl/common.php +++ b/lang/nl/common.php @@ -30,8 +30,8 @@ return [ 'create' => 'Aanmaken', 'update' => 'Bijwerken', 'edit' => 'Bewerk', - 'archive' => 'Archive', - 'unarchive' => 'Un-Archive', + 'archive' => 'Archiveer', + 'unarchive' => 'Terughalen', 'sort' => 'Sorteer', 'move' => 'Verplaats', 'copy' => 'Kopieer', diff --git a/lang/nl/entities.php b/lang/nl/entities.php index 28f0ef3b9..a3f03a16c 100644 --- a/lang/nl/entities.php +++ b/lang/nl/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Bijgewerkt: :timeLength', 'meta_updated_name' => 'Bijgewerkt: :timeLength door :user', 'meta_owned_name' => 'Eigendom van :user', - 'meta_reference_count' => 'Gerefereerd door :count item|Gerefereerd door :count items', + 'meta_reference_count' => 'Verwijzing in :count item|Verwijzing in :count items', 'entity_select' => 'Entiteit selecteren', 'entity_select_lack_permission' => 'Je hebt niet de vereiste machtiging om dit item te selecteren', 'images' => 'Afbeeldingen', @@ -248,7 +248,7 @@ return [ 'pages_edit_switch_to_markdown_stable' => '(Stabiele Inhoud)', 'pages_edit_switch_to_wysiwyg' => 'Schakel naar de WYSIWYG Bewerker', 'pages_edit_switch_to_new_wysiwyg' => 'Schakel naar de nieuwe WYSIWYG', - 'pages_edit_switch_to_new_wysiwyg_desc' => '(In Beta Testing)', + 'pages_edit_switch_to_new_wysiwyg_desc' => '(In Beta-testfase)', 'pages_edit_set_changelog' => 'Logboek instellen', 'pages_edit_enter_changelog_desc' => 'Geef een korte omschrijving van de wijzigingen die je gemaakt hebt', 'pages_edit_enter_changelog' => 'Voeg toe aan logboek', @@ -392,11 +392,11 @@ return [ 'comment' => 'Reactie', 'comments' => 'Reacties', 'comment_add' => 'Reactie toevoegen', - 'comment_none' => 'No comments to display', + 'comment_none' => 'Geen opmerkingen om weer te geven', 'comment_placeholder' => 'Laat hier een reactie achter', - 'comment_thread_count' => ':count Comment Thread|:count Comment Threads', - 'comment_archived_count' => ':count Archived', - 'comment_archived_threads' => 'Archived Threads', + 'comment_thread_count' => ':count Reactie Thread|:count Reactie Threads', + 'comment_archived_count' => ':count Gearchiveerd', + 'comment_archived_threads' => 'Gearchiveerde Threads', 'comment_save' => 'Sla reactie op', 'comment_new' => 'Nieuwe reactie', 'comment_created' => 'reactie gegeven :createDiff', @@ -405,14 +405,14 @@ return [ 'comment_deleted_success' => 'Reactie verwijderd', 'comment_created_success' => 'Reactie toegevoegd', 'comment_updated_success' => 'Reactie bijgewerkt', - 'comment_archive_success' => 'Comment archived', - 'comment_unarchive_success' => 'Comment un-archived', - 'comment_view' => 'View comment', - 'comment_jump_to_thread' => 'Jump to thread', + 'comment_archive_success' => 'Opmerking gearchiveerd', + 'comment_unarchive_success' => 'Opmerking teruggehaald', + 'comment_view' => 'Opmerking weergeven', + 'comment_jump_to_thread' => 'Ga naar thread', 'comment_delete_confirm' => 'Weet je zeker dat je deze reactie wilt verwijderen?', 'comment_in_reply_to' => 'Als antwoord op :commentId', - 'comment_reference' => 'Reference', - 'comment_reference_outdated' => '(Outdated)', + 'comment_reference' => 'Verwijzing', + 'comment_reference_outdated' => '(Verouderd)', 'comment_editor_explain' => 'Hier zijn de opmerkingen die zijn achtergelaten op deze pagina. Opmerkingen kunnen worden toegevoegd en beheerd wanneer u de opgeslagen pagina bekijkt.', // Revision diff --git a/lang/pt_BR/common.php b/lang/pt_BR/common.php index 4529d1a4e..b4ff95e99 100644 --- a/lang/pt_BR/common.php +++ b/lang/pt_BR/common.php @@ -35,8 +35,8 @@ return [ 'create' => 'Criar', 'update' => 'Atualizar', 'edit' => 'Editar', - 'archive' => 'Archive', - 'unarchive' => 'Un-Archive', + 'archive' => 'Arquivar', + 'unarchive' => 'Desarquivar', 'sort' => 'Ordenar', 'move' => 'Mover', 'copy' => 'Copiar', diff --git a/lang/pt_BR/entities.php b/lang/pt_BR/entities.php index 6fc5d6775..3d3c7e78a 100644 --- a/lang/pt_BR/entities.php +++ b/lang/pt_BR/entities.php @@ -248,7 +248,7 @@ return [ 'pages_edit_switch_to_markdown_stable' => '(Conteúdo Estável)', 'pages_edit_switch_to_wysiwyg' => 'Alternar para o Editor WYSIWYG', 'pages_edit_switch_to_new_wysiwyg' => 'Mudar para o novo WYSIWYG', - 'pages_edit_switch_to_new_wysiwyg_desc' => '(In Beta Testing)', + 'pages_edit_switch_to_new_wysiwyg_desc' => '(Em teste beta)', 'pages_edit_set_changelog' => 'Relatar Alterações', 'pages_edit_enter_changelog_desc' => 'Digite uma breve descrição das alterações efetuadas por você', 'pages_edit_enter_changelog' => 'Insira Alterações', @@ -392,11 +392,11 @@ return [ 'comment' => 'Comentário', 'comments' => 'Comentários', 'comment_add' => 'Adicionar Comentário', - 'comment_none' => 'No comments to display', + 'comment_none' => 'Nenhum comentário para exibir', 'comment_placeholder' => 'Digite seus comentários aqui', - 'comment_thread_count' => ':count Comment Thread|:count Comment Threads', - 'comment_archived_count' => ':count Archived', - 'comment_archived_threads' => 'Archived Threads', + 'comment_thread_count' => ':count Tópico de Comentário|:count Tópicos de Comentários', + 'comment_archived_count' => ':count Arquivado', + 'comment_archived_threads' => 'Tópicos Arquivados', 'comment_save' => 'Salvar comentário', 'comment_new' => 'Novo Comentário', 'comment_created' => 'comentado :createDiff', @@ -405,14 +405,14 @@ return [ 'comment_deleted_success' => 'Comentário removido', 'comment_created_success' => 'Comentário adicionado', 'comment_updated_success' => 'Comentário editado', - 'comment_archive_success' => 'Comment archived', - 'comment_unarchive_success' => 'Comment un-archived', - 'comment_view' => 'View comment', - 'comment_jump_to_thread' => 'Jump to thread', + 'comment_archive_success' => 'Comentário arquivado', + 'comment_unarchive_success' => 'Comentário desarquivado', + 'comment_view' => 'Ver comentário', + 'comment_jump_to_thread' => 'Ir para o tópico', 'comment_delete_confirm' => 'Você tem certeza de que deseja excluir este comentário?', 'comment_in_reply_to' => 'Em resposta à :commentId', - 'comment_reference' => 'Reference', - 'comment_reference_outdated' => '(Outdated)', + 'comment_reference' => 'Referência', + 'comment_reference_outdated' => '(Desatualizado)', 'comment_editor_explain' => 'Aqui estão os comentários que foram deixados nesta página. Comentários podem ser adicionados e gerenciados ao visualizar a página salva.', // Revision diff --git a/lang/zh_TW/activities.php b/lang/zh_TW/activities.php index d33e4d4b7..65ba0a565 100644 --- a/lang/zh_TW/activities.php +++ b/lang/zh_TW/activities.php @@ -47,7 +47,7 @@ return [ 'bookshelf_update' => '更新書棧', 'bookshelf_update_notification' => '書棧已更新', 'bookshelf_delete' => '刪除書棧', - 'bookshelf_delete_notification' => '書棧已刪除', + 'bookshelf_delete_notification' => '書架已刪除', // Revisions 'revision_restore' => '還原的版本', diff --git a/lang/zh_TW/errors.php b/lang/zh_TW/errors.php index 7a42ce511..d4239ed6c 100644 --- a/lang/zh_TW/errors.php +++ b/lang/zh_TW/errors.php @@ -67,7 +67,7 @@ return [ // Entities 'entity_not_found' => '找不到實體', - 'bookshelf_not_found' => '未找到書棧', + 'bookshelf_not_found' => '未找到書架', 'book_not_found' => '找不到書本', 'page_not_found' => '找不到頁面', 'chapter_not_found' => '找不到章節', From c4839c783ae0ac678c5a3a3eb7f47ac360793816 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 17 Jun 2025 15:29:12 +0100 Subject: [PATCH 12/12] Updated translator & dependency attribution before release v25.05.1 --- .github/translators.txt | 2 ++ dev/licensing/php-library-licenses.txt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/translators.txt b/.github/translators.txt index aab08d764..01260990a 100644 --- a/.github/translators.txt +++ b/.github/translators.txt @@ -487,3 +487,5 @@ jellium :: French Qxlkdr :: Swedish Hari (muhhari) :: Indonesian 仙君御 (xjy) :: Chinese Simplified +TapioM :: Finnish +lingb58 :: Chinese Traditional diff --git a/dev/licensing/php-library-licenses.txt b/dev/licensing/php-library-licenses.txt index 86e40a055..30b74ece0 100644 --- a/dev/licensing/php-library-licenses.txt +++ b/dev/licensing/php-library-licenses.txt @@ -486,7 +486,7 @@ Link: https://github.com/ramsey/collection.git ramsey/uuid License: MIT License File: vendor/ramsey/uuid/LICENSE -Copyright: Copyright (c) 2012-2023 Ben Ramsey <***@*********.***> +Copyright: Copyright (c) 2012-2025 Ben Ramsey <***@*********.***> Source: https://github.com/ramsey/uuid.git Link: https://github.com/ramsey/uuid.git -----------