Repository: phpDocumentor/phpDocumentor (language: php)

Issue:
Some source code popups do not work
In some cases, not sure why, source code popup does nothing when clicked.

## Expected behavior
Source code working always.


## Actual behavior
Source code popup does not always open

## Steps to reproduce the problem

I created doc with `--sourcecode` set and uploaded it to a server. Mostly it works (apart of #3270), but I just noticed, sometimes the source code simply does not work. The popup will not open.

To see the issue, please head to [this page](https://www.docs.tukutoi.com/packages/TukuToi-CPDirectoryIntegration.html) and click on any of the `<>` on this page.
None will work.
Now head to [this page](https://www.docs.tukutoi.com/classes/Cp-Plgn-Drctry-Admin.html) or any other, and all `<>` will work


Discussion / hints:
I think I found the issue; it is actually two-fold:

1. The include for the source-modal.html.twig is missing in the namespace, package and file base templates.
2. The include doesn't work in the namespace and package context because it assumes that the file associated with the current node is your target source path; this is not true for namespaces and classes because they are collections.

The first is easily fixed; the second needs some rework as the source file that you want to see should be provided to the source modal instead of the source modal pulling it from the root template
Hmm, bugger. Found another annoying issue. If you click on view source it will do a request for each element on the page. Thus making many duplicate requests. Don't know how I missed that during the initial implementation 😅 

Fix patch:
diff --git a/data/templates/default/components/element-found-in.html.twig b/data/templates/default/components/element-found-in.html.twig
index 6d0c2b1d59..bc09118b9c 100644
--- a/data/templates/default/components/element-found-in.html.twig
+++ b/data/templates/default/components/element-found-in.html.twig
@@ -4,6 +4,6 @@
     <span class="phpdocumentor-element-found-in__line">{{ node.line }}</span>
 
     {% if node.file.source %}
-        <a href="{{ target_path }}#source-view.{{ node.line }}"  class="phpdocumentor-element-found-in__source" data-line="{{ node.line }}" data-modal="source-view"></a>
+        <a href="{{ target_path }}#source-view.{{ node.line }}" class="phpdocumentor-element-found-in__source" data-line="{{ node.line }}" data-modal="source-view" data-src="{{ path('files/' ~ node.path ~ '.txt')|raw }}"></a>
     {% endif %}
 </aside>
diff --git a/data/templates/default/components/source-modal.css.twig b/data/templates/default/components/source-modal.css.twig
index 5c051112a0..f4eb8f8e17 100644
--- a/data/templates/default/components/source-modal.css.twig
+++ b/data/templates/default/components/source-modal.css.twig
@@ -10,6 +10,7 @@
     display: flex;
     align-items: center;
     justify-content: center;
+    z-index: 1;
 }
 
 .phpdocumentor-modal__open {
diff --git a/data/templates/default/components/source-modal.html.twig b/data/templates/default/components/source-modal.html.twig
index 62df277804..795f1f4f6d 100644
--- a/data/templates/default/components/source-modal.html.twig
+++ b/data/templates/default/components/source-modal.html.twig
@@ -1,74 +1,95 @@
-{% if node.file.source %}
+{% block sourceview_modal %}
 <div class="phpdocumentor-modal" id="source-view">
     <div class="phpdocumentor-modal-bg" data-exit-button></div>
     <div class="phpdocumentor-modal-container">
         <div class="phpdocumentor-modal-content">
-        <pre style="max-height: 500px; overflow-y: scroll" data-src="{{ path('files/' ~ node.path ~ '.txt')|raw }}" class="language-php line-numbers linkable-line-numbers"></pre>
+            <pre style="max-height: 500px; overflow-y: scroll" data-src="{{ node.path ? (path('files/' ~ node.path ~ '.txt')|raw) }}" class="language-php line-numbers linkable-line-numbers"></pre>
         </div>
-        <button data-exit-button class="phpdocumentor-modal__close">X</button>
+        <button data-exit-button class="phpdocumentor-modal__close">&times;</button>
     </div>
 </div>
-{% endif %}
+{% endblock %}
 
-{% block javascripts %}
+{% block modals_script %}
     <script type="text/javascript">
-        function loadExternalCodeSnippets(line) {
-            Array.prototype.slice.call(document.querySelectorAll('pre[data-src]')).forEach((pre) => {
-                var src = pre.getAttribute('data-src').replace( /\\/g, '/');
-                var extension = (src.match(/\.(\w+)$/) || [, ''])[1];
-                var language = 'php';
-
-                var code = document.createElement('code');
-                code.className = 'language-' + language;
-
-                pre.textContent = '';
+        (function () {
+            function loadExternalCodeSnippet(el, url, line) {
+                Array.prototype.slice.call(el.querySelectorAll('pre[data-src]')).forEach((pre) => {
+                    const src = url || pre.getAttribute('data-src').replace(/\\/g, '/');
+                    const language = 'php';
 
-                pre.setAttribute('data-line', line)
-                code.textContent = 'Loading…';
+                    const code = document.createElement('code');
+                    code.className = 'language-' + language;
+                    pre.textContent = '';
+                    pre.setAttribute('data-line', line)
+                    code.textContent = 'Loading…';
+                    pre.appendChild(code);
 
-                pre.appendChild(code);
+                    var xhr = new XMLHttpRequest();
 
-                var xhr = new XMLHttpRequest();
+                    xhr.open('GET', src, true);
 
-                xhr.open('GET', src, true);
-
-                xhr.onreadystatechange = function () {
-                    if (xhr.readyState == 4) {
+                    xhr.onreadystatechange = function () {
+                        if (xhr.readyState !== 4) {
+                            return;
+                        }
 
                         if (xhr.status < 400 && xhr.responseText) {
                             code.textContent = xhr.responseText;
-
                             Prism.highlightElement(code);
+                            return;
                         }
-                        else if (xhr.status >= 400) {
-                            code.textContent = '✖ Error ' + xhr.status + ' while fetching file: ' + xhr.statusText;
+
+                        if (xhr.status === 404) {
+                            code.textContent = '✖ Error: File could not be found';
+                            return;
                         }
-                        else {
-                            code.textContent = '✖ Error: File does not exist, is empty or trying to view from localhost';
+
+                        if (xhr.status >= 400) {
+                            code.textContent = '✖ Error ' + xhr.status + ' while fetching file: ' + xhr.statusText;
+                            return;
                         }
-                    }
-                };
 
-                xhr.send(null);
-            });
-        }
+                        code.textContent = '✖ Error: An unknown error occurred';
+                    };
+
+                    xhr.send(null);
+                });
+            }
+
+            const modalButtons = document.querySelectorAll("[data-modal]");
+            const openedAsLocalFile = window.location.protocol === 'file:';
+            if (modalButtons.length > 0 && openedAsLocalFile) {
+                console.warn(
+                    'Viewing the source code is unavailable because you are opening this page from the file:// scheme; ' +
+                    'browsers block XHR requests when a page is opened this way'
+                );
+            }
 
-        var modals = document.querySelectorAll("[data-modal]");
+            modalButtons.forEach(function (trigger) {
+                if (openedAsLocalFile) {
+                    trigger.setAttribute("hidden", "hidden");
+                }
+
+                trigger.addEventListener("click", function (event) {
+                    event.preventDefault();
+                    const modal = document.getElementById(trigger.dataset.modal);
+                    if (!modal) {
+                        console.error(`Modal with id "${trigger.dataset.modal}" could not be found`);
+                        return;
+                    }
+                    modal.classList.add("phpdocumentor-modal__open");
 
-        modals.forEach(function (trigger) {
-            trigger.addEventListener("click", function (event) {
-                //event.preventDefault();
-                const modal = document.getElementById(trigger.dataset.modal);
-                modal.classList.add("phpdocumentor-modal__open");
-                loadExternalCodeSnippets(trigger.dataset.line)
-                const exits = modal.querySelectorAll("[data-exit-button]");
-                exits.forEach(function (exit) {
-                    exit.addEventListener("click", function (event) {
-                        event.preventDefault();
-                        modal.classList.remove("phpdocumentor-modal__open");
+                    loadExternalCodeSnippet(modal, trigger.dataset.src || null, trigger.dataset.line)
+                    const exits = modal.querySelectorAll("[data-exit-button]");
+                    exits.forEach(function (exit) {
+                        exit.addEventListener("click", function (event) {
+                            event.preventDefault();
+                            modal.classList.remove("phpdocumentor-modal__open");
+                        });
                     });
                 });
             });
-        });
+        })();
     </script>
 {% endblock %}
diff --git a/data/templates/default/css/normalize.css.twig b/data/templates/default/css/normalize.css.twig
index 579152b07f..653dc00aaa 100644
--- a/data/templates/default/css/normalize.css.twig
+++ b/data/templates/default/css/normalize.css.twig
@@ -76,7 +76,7 @@ audio:not([controls]) {
 
 [hidden],
 template {
-    display: none;
+    display: none !important;
 }
 
 /* Links
diff --git a/data/templates/default/file.html.twig b/data/templates/default/file.html.twig
index d549a762f7..6932291d12 100644
--- a/data/templates/default/file.html.twig
+++ b/data/templates/default/file.html.twig
@@ -9,5 +9,6 @@
 
         {{ include('components/constants.html.twig') }}
         {{ include('components/functions.html.twig') }}
+        {{ include('components/source-modal.html.twig') }}
     </article>
 {% endblock %}
diff --git a/data/templates/default/namespace.html.twig b/data/templates/default/namespace.html.twig
index 8f57944208..d77d342631 100644
--- a/data/templates/default/namespace.html.twig
+++ b/data/templates/default/namespace.html.twig
@@ -8,5 +8,6 @@
         {{ include('components/table-of-contents.html.twig', {'namespaces': node.children}) }}
         {{ include('components/constants.html.twig') }}
         {{ include('components/functions.html.twig') }}
+        {{ include('components/source-modal.html.twig') }}
     </article>
 {% endblock %}
diff --git a/data/templates/default/package.html.twig b/data/templates/default/package.html.twig
index 2b021f1e85..ae4dd107ef 100644
--- a/data/templates/default/package.html.twig
+++ b/data/templates/default/package.html.twig
@@ -8,5 +8,6 @@
         {{ include('components/table-of-contents.html.twig', {'packages': node.children}) }}
         {{ include('components/constants.html.twig') }}
         {{ include('components/functions.html.twig') }}
+        {{ include('components/source-modal.html.twig') }}
     </article>
 {% endblock %}


Test patch: