From 58f6219cb3cf937fef59d03d4420de1ba64df436 Mon Sep 17 00:00:00 2001
From: Dan Brown <ssddanbrown@googlemail.com>
Date: Sun, 31 Mar 2024 14:33:08 +0100
Subject: [PATCH 1/5] Code: Fixed highlighting issues when no code language set

For #4917
---
 resources/js/code/index.mjs | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/resources/js/code/index.mjs b/resources/js/code/index.mjs
index d252f8f41..ab31e3f74 100644
--- a/resources/js/code/index.mjs
+++ b/resources/js/code/index.mjs
@@ -48,14 +48,16 @@ function highlightElem(elem) {
     const content = elem.textContent.trimEnd();
 
     let langName = '';
+    let innerCodeDirection = '';
     if (innerCodeElem !== null) {
         langName = innerCodeElem.className.replace('language-', '');
+        innerCodeDirection = innerCodeElem.getAttribute('dir');
     }
 
     const wrapper = document.createElement('div');
     elem.parentNode.insertBefore(wrapper, elem);
 
-    const direction = innerCodeElem.getAttribute('dir') || elem.getAttribute('dir') || '';
+    const direction = innerCodeDirection || elem.getAttribute('dir') || '';
     if (direction) {
         wrapper.setAttribute('dir', direction);
     }

From a33dbcb04a7c64bb5198bd6a7cc421f50e0cc541 Mon Sep 17 00:00:00 2001
From: Dan Brown <ssddanbrown@googlemail.com>
Date: Mon, 1 Apr 2024 17:08:53 +0100
Subject: [PATCH 2/5] References: Fixed references count/list recycle bin
 interaction

Count and reference list would get references then attempt to load
entities, which could fail to load if in the recycle bin.
This updates the queries to effectively ignore references for items we
can't see (in recycle bin).
Added test to cover.

For #4918
---
 app/References/ReferenceFetcher.php |  3 ++-
 tests/References/ReferencesTest.php | 26 +++++++++++++++++++++++++-
 2 files changed, 27 insertions(+), 2 deletions(-)

diff --git a/app/References/ReferenceFetcher.php b/app/References/ReferenceFetcher.php
index 655ea7c09..1c9664f45 100644
--- a/app/References/ReferenceFetcher.php
+++ b/app/References/ReferenceFetcher.php
@@ -41,7 +41,8 @@ class ReferenceFetcher
     {
         $baseQuery = Reference::query()
             ->where('to_type', '=', $entity->getMorphClass())
-            ->where('to_id', '=', $entity->id);
+            ->where('to_id', '=', $entity->id)
+            ->whereHas('from');
 
         return $this->permissions->restrictEntityRelationQuery(
             $baseQuery,
diff --git a/tests/References/ReferencesTest.php b/tests/References/ReferencesTest.php
index 715f71435..f8698d028 100644
--- a/tests/References/ReferencesTest.php
+++ b/tests/References/ReferencesTest.php
@@ -271,7 +271,31 @@ class ReferencesTest extends TestCase
         }
     }
 
-    protected function createReference(Model $from, Model $to)
+    public function test_reference_from_deleted_item_does_not_count_or_show_in_references_page()
+    {
+        $page = $this->entities->page();
+        $referencingPageA = $this->entities->page();
+        $referencingPageB = $this->entities->page();
+
+        $this->asEditor();
+        $this->createReference($referencingPageA, $page);
+        $this->createReference($referencingPageB, $page);
+
+        $resp = $this->get($page->getUrl());
+        $resp->assertSee('Referenced by 2 items');
+
+        $this->delete($referencingPageA->getUrl());
+
+        $resp = $this->get($page->getUrl());
+        $resp->assertSee('Referenced by 1 item');
+
+        $resp = $this->get($page->getUrl('/references'));
+        $resp->assertOk();
+        $resp->assertSee($referencingPageB->getUrl());
+        $resp->assertDontSee($referencingPageA->getUrl());
+    }
+
+    protected function createReference(Model $from, Model $to): void
     {
         (new Reference())->forceFill([
             'from_type' => $from->getMorphClass(),

From 19f78dbe6c1198a0b7072340d1422efaf0cd8bd0 Mon Sep 17 00:00:00 2001
From: Dan Brown <ssddanbrown@googlemail.com>
Date: Wed, 3 Apr 2024 16:46:53 +0100
Subject: [PATCH 3/5] WYSIWYG descriptions: Allowed anchor target attrs

Allowed since this is a control in the editor UI, but would previously
be stripped by editor config & server-side filtering.
For #4925
---
 app/Util/HtmlDescriptionFilter.php | 2 +-
 resources/js/wysiwyg/config.js     | 2 +-
 tests/Entity/BookTest.php          | 4 ++--
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/app/Util/HtmlDescriptionFilter.php b/app/Util/HtmlDescriptionFilter.php
index 7287586d1..cb091b869 100644
--- a/app/Util/HtmlDescriptionFilter.php
+++ b/app/Util/HtmlDescriptionFilter.php
@@ -20,7 +20,7 @@ class HtmlDescriptionFilter
      */
     protected static array $allowedAttrsByElements = [
         'p' => [],
-        'a' => ['href', 'title'],
+        'a' => ['href', 'title', 'target'],
         'ol' => [],
         'ul' => [],
         'li' => [],
diff --git a/resources/js/wysiwyg/config.js b/resources/js/wysiwyg/config.js
index e5a780d18..1666aa500 100644
--- a/resources/js/wysiwyg/config.js
+++ b/resources/js/wysiwyg/config.js
@@ -348,7 +348,7 @@ export function buildForInput(options) {
         toolbar: 'bold italic link bullist numlist',
         content_style: getContentStyle(options),
         file_picker_types: 'file',
-        valid_elements: 'p,a[href|title],ol,ul,li,strong,em,br',
+        valid_elements: 'p,a[href|title|target],ol,ul,li,strong,em,br',
         file_picker_callback: filePickerCallback,
         init_instance_callback(editor) {
             addCustomHeadContent(editor.getDoc());
diff --git a/tests/Entity/BookTest.php b/tests/Entity/BookTest.php
index 04dff293f..51bf65d10 100644
--- a/tests/Entity/BookTest.php
+++ b/tests/Entity/BookTest.php
@@ -266,8 +266,8 @@ class BookTest extends TestCase
     {
         $book = $this->entities->book();
 
-        $input = '<h1>Test</h1><p id="abc" href="beans">Content<a href="#cat" data-a="b">a</a><section>Hello</section></p>';
-        $expected = '<p>Content<a href="#cat">a</a></p>';
+        $input = '<h1>Test</h1><p id="abc" href="beans">Content<a href="#cat" target="_blank" data-a="b">a</a><section>Hello</section></p>';
+        $expected = '<p>Content<a href="#cat" target="_blank">a</a></p>';
 
         $this->asEditor()->put($book->getUrl(), [
             'name' => $book->name,

From b9e2d33ed40d8bc154d7609ee68349e5958a5601 Mon Sep 17 00:00:00 2001
From: Dan Brown <ssddanbrown@googlemail.com>
Date: Fri, 5 Apr 2024 15:06:08 +0100
Subject: [PATCH 4/5] Page Content: Aligned max-width across viewer and editors

For #4916
---
 resources/sass/_forms.scss   | 5 +++--
 resources/sass/_tinymce.scss | 1 +
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/resources/sass/_forms.scss b/resources/sass/_forms.scss
index 8c277c2b5..e480531fc 100644
--- a/resources/sass/_forms.scss
+++ b/resources/sass/_forms.scss
@@ -128,8 +128,9 @@
   body {
     display: block;
     background-color: #fff;
-    padding-inline-start: 16px;
-    padding-inline-end: 16px;
+    padding-inline-start: 12px;
+    padding-inline-end: 12px;
+    max-width: 864px;
   }
   [drawio-diagram]:hover {
     outline: 2px solid var(--color-primary);
diff --git a/resources/sass/_tinymce.scss b/resources/sass/_tinymce.scss
index 7e443ff5a..132c3ce7f 100644
--- a/resources/sass/_tinymce.scss
+++ b/resources/sass/_tinymce.scss
@@ -21,6 +21,7 @@
   padding-block-end: 1rem;
   outline: 0;
   display: block;
+  max-width: calc(870px);
 }
 
 .wysiwyg-input.mce-content-body {

From 3e23f456fe1280749060654357b2e8a12e65f899 Mon Sep 17 00:00:00 2001
From: Dan Brown <ssddanbrown@googlemail.com>
Date: Fri, 5 Apr 2024 15:18:58 +0100
Subject: [PATCH 5/5] CSS: Removed redundant calc

---
 resources/sass/_tinymce.scss | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/resources/sass/_tinymce.scss b/resources/sass/_tinymce.scss
index 132c3ce7f..29843e424 100644
--- a/resources/sass/_tinymce.scss
+++ b/resources/sass/_tinymce.scss
@@ -21,7 +21,7 @@
   padding-block-end: 1rem;
   outline: 0;
   display: block;
-  max-width: calc(870px);
+  max-width: 870px;
 }
 
 .wysiwyg-input.mce-content-body {