xref: /plugin/mermaid/action.php (revision 1da12d6e8ab019f8bc2014b3dc7bf0b52095a071)
1<?php
2/**
3 * DokuWiki Plugin mermaid (Action Component)
4 *
5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6 * @author  Robert Weinmeister <develop@weinmeister.org>
7 */
8
9class action_plugin_mermaid extends \dokuwiki\Extension\ActionPlugin
10{
11    /** @inheritDoc */
12    public function register(Doku_Event_Handler $controller)
13    {
14        $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'load');
15    }
16
17    public function load(Doku_Event $event, $param)
18    {
19        // Can be changed for debugging Mermaid
20        // https://mermaid.js.org/config/directives.html#changing-loglevel-via-directive
21        define("MERMAIDLOGLEVEL", "error");
22
23        $theme = $this->getConf('theme');
24        $init = "mermaid.initialize({startOnLoad: true, logLevel: '".MERMAIDLOGLEVEL."', theme: '".$theme."'});";
25        $location = $this->getConf('location');
26
27        switch ($location) {
28            case 'local':
29                $event->data['script'][] = array
30                (
31                    'type'    => 'text/javascript',
32                    'charset' => 'utf-8',
33                    'src' => DOKU_BASE.'lib/plugins/mermaid/mermaid.min.js'
34                );
35                break;
36            case 'latest':
37            case 'remote1091':
38            // options remote108, remote106, remote104, remote103, remote102, remote101, remote100 are depreciated and only included for backward compatibility
39            case 'remote108':
40            case 'remote106':
41            case 'remote104':
42            case 'remote103':
43            case 'remote102':
44            case 'remote101':
45            case 'remote100':
46                $versions = array(
47                    'latest' => '',
48                    'remote1091' => '@10.9.1',
49                    'remote108' => '@10.8.0',
50                    'remote106' => '@10.6.1',
51                    'remote104' => '@10.4.0',
52                    'remote103' => '@10.3.1',
53                    'remote102' => '@10.2.4',
54                    'remote101' => '@10.1.0',
55                    'remote100' => '@10.0.2'
56                );
57                $data = "import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid".$versions[$location]."/dist/mermaid.esm.min.mjs';".$init;
58                $event->data['script'][] = array
59                (
60                    'type'    => 'module',
61                    'charset' => 'utf-8',
62                    '_data' => $data
63                );
64                break;
65            // option remote94 is depreciated and only included for backward compatibility
66            case 'remote94':
67            case 'remote943':
68                $event->data['script'][] = array
69                (
70                    'type'    => 'text/javascript',
71                    'charset' => 'utf-8',
72                    'src' => 'https://cdn.jsdelivr.net/npm/mermaid@9.4.3/dist/mermaid.min.js'
73                );
74                break;
75            // option remote93 is depreciated and only included for backward compatibility
76            case 'remote93':
77                $event->data['script'][] = array
78                (
79                    'type'    => 'text/javascript',
80                    'charset' => 'utf-8',
81                    'src' => 'https://cdn.jsdelivr.net/npm/mermaid@9.3.0/dist/mermaid.min.js'
82                );
83                break;
84            default:
85        }
86
87        $event->data['link'][] = array
88        (
89            'rel'     => 'stylesheet',
90            'type'    => 'text/css',
91            'href'    => DOKU_BASE."lib/plugins/mermaid/mermaid.css",
92        );
93
94        switch ($location) {
95            case 'local':
96            case 'remote943':
97            // options remote94 and remote93 are depreciated and only included for backward compatibility
98            case 'remote94':
99            case 'remote93':
100                $event->data['script'][] = array
101                (
102                    'type'    => 'text/javascript',
103                    'charset' => 'utf-8',
104                    '_data'   => $init
105                );
106                break;
107            default:
108        }
109
110        // remove the search highlight from DokuWiki as it interferes with the Mermaid parsing/rendering
111        $event->data['script'][] = array
112        (
113            'type'    => 'text/javascript',
114            'charset' => 'utf-8',
115            '_data' => "document.addEventListener('DOMContentLoaded', function() {
116                            jQuery('.mermaid').each(function() {
117                                var modifiedContent = jQuery(this).html().replace(/<span class=\"search_hit\">(.+?)<\/span>/g, '$1');
118                                jQuery(this).html(modifiedContent);
119                             })
120                        });"
121        );
122
123        // adds image-save capability
124        // First: Wait until the DOM content is fully loaded
125        // Second: Wait until Mermaid has changed the dokuwiki content to an svg
126        $event->data['script'][] = array
127        (
128            'type'    => 'text/javascript',
129            'charset' => 'utf-8',
130            '_data' => "
131document.addEventListener('DOMContentLoaded', function() {
132     var config = {
133        childList: true,
134        subtree: true,
135        characterData: true
136    };
137
138    jQuery('.mermaid').each(function(index, element) {
139        var observer = new MutationObserver(function(mutations) {
140            mutations.forEach(function(mutation) {
141                if (mutation.type === 'childList' && element.innerHTML.startsWith('<svg')) {
142                    document.getElementById('mermaidContainer' + index).addEventListener('mouseenter', function() {
143                        document.getElementById('mermaidButton' + index).style.display = 'block';
144                    });
145                    document.getElementById('mermaidContainer' + index).addEventListener('mouseleave', function() {
146                        document.getElementById('mermaidButton' + index).style.display = 'none';
147                    });
148
149                    document.getElementById('mermaidButton' + index).addEventListener('click', () => {
150                        var svgContent = element.innerHTML.trim();
151                        var blob = new Blob([svgContent], { type: 'image/svg+xml' });
152                        var link = document.createElement('a');
153                        link.href = URL.createObjectURL(blob);
154                        link.download = 'mermaid' + index + '.svg';
155                        link.click();
156                        URL.revokeObjectURL(link.href);
157                    });
158                }
159            });
160        });
161
162        observer.observe(element, config);
163    });
164});"
165        );
166    }
167}
168
169