146a60b4fSRobertWeinmeister<?php 246a60b4fSRobertWeinmeister/** 346a60b4fSRobertWeinmeister * DokuWiki Plugin mermaid (Action Component) 446a60b4fSRobertWeinmeister * 546a60b4fSRobertWeinmeister * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 646a60b4fSRobertWeinmeister * @author Robert Weinmeister <develop@weinmeister.org> 746a60b4fSRobertWeinmeister */ 846a60b4fSRobertWeinmeister 9*ea08b541SRobert Weinmeisterif (!defined('DOKU_INC')) die(); 10*ea08b541SRobert Weinmeister 1146a60b4fSRobertWeinmeisterclass action_plugin_mermaid extends \dokuwiki\Extension\ActionPlugin 1246a60b4fSRobertWeinmeister{ 1346a60b4fSRobertWeinmeister /** @inheritDoc */ 1446a60b4fSRobertWeinmeister public function register(Doku_Event_Handler $controller) 1546a60b4fSRobertWeinmeister { 1646a60b4fSRobertWeinmeister $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'load'); 17172fa282SRobert Weinmeister $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handleAjaxRequest'); 18172fa282SRobert Weinmeister } 19172fa282SRobert Weinmeister 20172fa282SRobert Weinmeister public function handleAjaxRequest(Doku_Event $event, $param) { 21172fa282SRobert Weinmeister if ($event->data !== 'plugin_mermaid') 22172fa282SRobert Weinmeister { 23172fa282SRobert Weinmeister return; 24172fa282SRobert Weinmeister } 25172fa282SRobert Weinmeister 26172fa282SRobert Weinmeister $event->stopPropagation(); 27172fa282SRobert Weinmeister $event->preventDefault(); 28172fa282SRobert Weinmeister 29172fa282SRobert Weinmeister $ID = cleanID(urldecode($_REQUEST['pageid'])); 30172fa282SRobert Weinmeister 31172fa282SRobert Weinmeister if(auth_quickaclcheck($ID) < AUTH_EDIT) 32172fa282SRobert Weinmeister { 33172fa282SRobert Weinmeister echo json_encode(['status' => 'failure', 'data' => ['You do not have permission to edit this file.\nAccess was denied.']]); 34172fa282SRobert Weinmeister exit(); 35172fa282SRobert Weinmeister } 36172fa282SRobert Weinmeister 37172fa282SRobert Weinmeister if(checklock($ID)) 38172fa282SRobert Weinmeister { 39172fa282SRobert Weinmeister echo json_encode(['status' => 'failure', 'data' => ['The page is currently locked.\nTry again later.']]); 40172fa282SRobert Weinmeister exit(); 41172fa282SRobert Weinmeister } 42172fa282SRobert Weinmeister 43172fa282SRobert Weinmeister $wikitext = rawWiki($ID); 44172fa282SRobert Weinmeister $newWikitext = $wikitext; 45172fa282SRobert Weinmeister 46172fa282SRobert Weinmeister if($_REQUEST['mode'] == 'lock') 47172fa282SRobert Weinmeister { 48172fa282SRobert Weinmeister preg_match_all('/<mermaid.*?>(.*?)<\/mermaid>/s', $wikitext, $matches, PREG_OFFSET_CAPTURE); 49172fa282SRobert Weinmeister 50172fa282SRobert Weinmeister if(is_array($matches) && count($matches[0]) > $_REQUEST['mermaidindex']) 51172fa282SRobert Weinmeister { 52172fa282SRobert Weinmeister $whereToInsert = $matches[1][$_REQUEST['mermaidindex']][1]; 53172fa282SRobert Weinmeister $newWikitext = substr($wikitext, 0, $whereToInsert) . "\n%%" . urldecode($_REQUEST['svg']) . "\n" . substr($wikitext, $whereToInsert); 54172fa282SRobert Weinmeister } 55172fa282SRobert Weinmeister else 56172fa282SRobert Weinmeister { 57172fa282SRobert Weinmeister echo json_encode(['status' => 'failure', 'data' => ['Could not lock the Mermaid diagram as the request could not be matched.']]); 58172fa282SRobert Weinmeister exit(); 59172fa282SRobert Weinmeister } 60172fa282SRobert Weinmeister } 61172fa282SRobert Weinmeister 62172fa282SRobert Weinmeister if($_REQUEST['mode'] == 'unlock') 63172fa282SRobert Weinmeister { 64172fa282SRobert Weinmeister $newWikitext = str_replace("\n%%" . urldecode($_REQUEST['svg']) . "\n", '', $wikitext, $count); 65172fa282SRobert Weinmeister if($count != 1) 66172fa282SRobert Weinmeister { 67172fa282SRobert Weinmeister echo json_encode(['status' => 'failure', 'data' => ['Could not unlock the Mermaid diagram as the request could not be matched.']]); 68172fa282SRobert Weinmeister exit(); 69172fa282SRobert Weinmeister } 70172fa282SRobert Weinmeister } 71172fa282SRobert Weinmeister 72172fa282SRobert Weinmeister if(strlen($newWikitext) > 0 && $newWikitext != $wikitext) 73172fa282SRobert Weinmeister { 74172fa282SRobert Weinmeister lock($ID); 75172fa282SRobert Weinmeister saveWikiText($ID, $newWikitext, $_REQUEST['mode'] . ' Mermaid diagram', $minoredit = true); 76172fa282SRobert Weinmeister unlock($ID); 77172fa282SRobert Weinmeister 78172fa282SRobert Weinmeister echo json_encode(['status' => 'success', 'data' => []]); 79172fa282SRobert Weinmeister exit(); 80172fa282SRobert Weinmeister } 81172fa282SRobert Weinmeister 82172fa282SRobert Weinmeister echo json_encode(['status' => 'failure', 'data' => ['Could not '.$_REQUEST['mode'].' the Mermaid diagram.']]); 83172fa282SRobert Weinmeister exit(); 8446a60b4fSRobertWeinmeister } 8546a60b4fSRobertWeinmeister 8646a60b4fSRobertWeinmeister public function load(Doku_Event $event, $param) 8746a60b4fSRobertWeinmeister { 88*ea08b541SRobert Weinmeister // only load mermaid if it is needed 89*ea08b541SRobert Weinmeister if(strpos(rawWiki(getID()), '<mermaid') === false) 90*ea08b541SRobert Weinmeister { 91*ea08b541SRobert Weinmeister return; 92*ea08b541SRobert Weinmeister } 93*ea08b541SRobert Weinmeister 946e5341c6SRobert Weinmeister // Can be changed for debugging Mermaid 956e5341c6SRobert Weinmeister // https://mermaid.js.org/config/directives.html#changing-loglevel-via-directive 966e5341c6SRobert Weinmeister define("MERMAIDLOGLEVEL", "error"); 976e5341c6SRobert Weinmeister 984c8bd9ffSRobert Weinmeister $theme = $this->getConf('theme'); 994c8bd9ffSRobert Weinmeister $init = "mermaid.initialize({startOnLoad: true, logLevel: '".MERMAIDLOGLEVEL."', theme: '".$theme."'});"; 1006fcac025SRobert Weinmeister $location = $this->getConf('location'); 1014c8bd9ffSRobert Weinmeister 1026fcac025SRobert Weinmeister switch ($location) { 1032d4b7fc2SRobert Weinmeister case 'local': 10446a60b4fSRobertWeinmeister $event->data['script'][] = array 10546a60b4fSRobertWeinmeister ( 10646a60b4fSRobertWeinmeister 'type' => 'text/javascript', 10746a60b4fSRobertWeinmeister 'charset' => 'utf-8', 1082d4b7fc2SRobert Weinmeister 'src' => DOKU_BASE.'lib/plugins/mermaid/mermaid.min.js' 10946a60b4fSRobertWeinmeister ); 1102d4b7fc2SRobert Weinmeister break; 1112d4b7fc2SRobert Weinmeister case 'latest': 112a788b843SRobert Weinmeister case 'remote1091': 113a788b843SRobert Weinmeister // options remote108, remote106, remote104, remote103, remote102, remote101, remote100 are depreciated and only included for backward compatibility 1146fcac025SRobert Weinmeister case 'remote108': 115a612c7d6SRobert Weinmeister case 'remote106': 1168eaa3f3bSRobert Weinmeister case 'remote104': 1177d8a2661SRobert Weinmeister case 'remote103': 1184df3d176SRobert Weinmeister case 'remote102': 1195f50b169SRobert Weinmeister case 'remote101': 1206e5341c6SRobert Weinmeister case 'remote100': 1216fcac025SRobert Weinmeister $versions = array( 1226fcac025SRobert Weinmeister 'latest' => '', 123a788b843SRobert Weinmeister 'remote1091' => '@10.9.1', 1246fcac025SRobert Weinmeister 'remote108' => '@10.8.0', 1256fcac025SRobert Weinmeister 'remote106' => '@10.6.1', 1266fcac025SRobert Weinmeister 'remote104' => '@10.4.0', 1276fcac025SRobert Weinmeister 'remote103' => '@10.3.1', 1286fcac025SRobert Weinmeister 'remote102' => '@10.2.4', 1296fcac025SRobert Weinmeister 'remote101' => '@10.1.0', 1306fcac025SRobert Weinmeister 'remote100' => '@10.0.2' 1316fcac025SRobert Weinmeister ); 1326fcac025SRobert Weinmeister $data = "import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid".$versions[$location]."/dist/mermaid.esm.min.mjs';".$init; 1336e5341c6SRobert Weinmeister $event->data['script'][] = array 1346e5341c6SRobert Weinmeister ( 1356e5341c6SRobert Weinmeister 'type' => 'module', 1366e5341c6SRobert Weinmeister 'charset' => 'utf-8', 1376fcac025SRobert Weinmeister '_data' => $data 1386e5341c6SRobert Weinmeister ); 1396e5341c6SRobert Weinmeister break; 140a788b843SRobert Weinmeister // option remote94 is depreciated and only included for backward compatibility 1416e5341c6SRobert Weinmeister case 'remote94': 142a788b843SRobert Weinmeister case 'remote943': 1436e5341c6SRobert Weinmeister $event->data['script'][] = array 1446e5341c6SRobert Weinmeister ( 1456e5341c6SRobert Weinmeister 'type' => 'text/javascript', 1466e5341c6SRobert Weinmeister 'charset' => 'utf-8', 1474df3d176SRobert Weinmeister 'src' => 'https://cdn.jsdelivr.net/npm/mermaid@9.4.3/dist/mermaid.min.js' 1486e5341c6SRobert Weinmeister ); 1496e5341c6SRobert Weinmeister break; 150a788b843SRobert Weinmeister // option remote93 is depreciated and only included for backward compatibility 1512d4b7fc2SRobert Weinmeister case 'remote93': 1522d4b7fc2SRobert Weinmeister $event->data['script'][] = array 1532d4b7fc2SRobert Weinmeister ( 1542d4b7fc2SRobert Weinmeister 'type' => 'text/javascript', 1552d4b7fc2SRobert Weinmeister 'charset' => 'utf-8', 1564df3d176SRobert Weinmeister 'src' => 'https://cdn.jsdelivr.net/npm/mermaid@9.3.0/dist/mermaid.min.js' 1572d4b7fc2SRobert Weinmeister ); 1582d4b7fc2SRobert Weinmeister break; 1592d4b7fc2SRobert Weinmeister default: 1602d4b7fc2SRobert Weinmeister } 16146a60b4fSRobertWeinmeister 16246a60b4fSRobertWeinmeister $event->data['link'][] = array 16346a60b4fSRobertWeinmeister ( 16446a60b4fSRobertWeinmeister 'rel' => 'stylesheet', 16546a60b4fSRobertWeinmeister 'type' => 'text/css', 16646a60b4fSRobertWeinmeister 'href' => DOKU_BASE."lib/plugins/mermaid/mermaid.css", 16746a60b4fSRobertWeinmeister ); 16846a60b4fSRobertWeinmeister 1696fcac025SRobert Weinmeister switch ($location) { 1704df3d176SRobert Weinmeister case 'local': 171a788b843SRobert Weinmeister case 'remote943': 172a788b843SRobert Weinmeister // options remote94 and remote93 are depreciated and only included for backward compatibility 1736e5341c6SRobert Weinmeister case 'remote94': 1746e5341c6SRobert Weinmeister case 'remote93': 17546a60b4fSRobertWeinmeister $event->data['script'][] = array 17646a60b4fSRobertWeinmeister ( 17746a60b4fSRobertWeinmeister 'type' => 'text/javascript', 17846a60b4fSRobertWeinmeister 'charset' => 'utf-8', 1794c8bd9ffSRobert Weinmeister '_data' => $init 18046a60b4fSRobertWeinmeister ); 1816e5341c6SRobert Weinmeister break; 1826e5341c6SRobert Weinmeister default: 1836e5341c6SRobert Weinmeister } 1846fcac025SRobert Weinmeister 1856fcac025SRobert Weinmeister // remove the search highlight from DokuWiki as it interferes with the Mermaid parsing/rendering 1866fcac025SRobert Weinmeister $event->data['script'][] = array 1876fcac025SRobert Weinmeister ( 1886fcac025SRobert Weinmeister 'type' => 'text/javascript', 1896fcac025SRobert Weinmeister 'charset' => 'utf-8', 1906fcac025SRobert Weinmeister '_data' => "document.addEventListener('DOMContentLoaded', function() { 1916fcac025SRobert Weinmeister jQuery('.mermaid').each(function() { 1926fcac025SRobert Weinmeister var modifiedContent = jQuery(this).html().replace(/<span class=\"search_hit\">(.+?)<\/span>/g, '$1'); 1936fcac025SRobert Weinmeister jQuery(this).html(modifiedContent); 1946fcac025SRobert Weinmeister }) 1956fcac025SRobert Weinmeister });" 1966fcac025SRobert Weinmeister ); 1971da12d6eSRobert Weinmeister 1981da12d6eSRobert Weinmeister // adds image-save capability 1991da12d6eSRobert Weinmeister // First: Wait until the DOM content is fully loaded 2001da12d6eSRobert Weinmeister // Second: Wait until Mermaid has changed the dokuwiki content to an svg 2011da12d6eSRobert Weinmeister $event->data['script'][] = array 2021da12d6eSRobert Weinmeister ( 2031da12d6eSRobert Weinmeister 'type' => 'text/javascript', 2041da12d6eSRobert Weinmeister 'charset' => 'utf-8', 2051da12d6eSRobert Weinmeister '_data' => " 2061da12d6eSRobert Weinmeisterdocument.addEventListener('DOMContentLoaded', function() { 2071da12d6eSRobert Weinmeister var config = { 2081da12d6eSRobert Weinmeister childList: true, 2091da12d6eSRobert Weinmeister subtree: true, 2101da12d6eSRobert Weinmeister characterData: true 2111da12d6eSRobert Weinmeister }; 2121da12d6eSRobert Weinmeister 213172fa282SRobert Weinmeister function callDokuWikiPHP(mode, index, mermaidRaw, mermaidSvg) { 214172fa282SRobert Weinmeister jQuery.post( 215172fa282SRobert Weinmeister DOKU_BASE + 'lib/exe/ajax.php', 216172fa282SRobert Weinmeister { 217172fa282SRobert Weinmeister call: 'plugin_mermaid', 218172fa282SRobert Weinmeister mode: mode, 219172fa282SRobert Weinmeister mermaidindex: index, 220172fa282SRobert Weinmeister pageid: '".getID()."', 221172fa282SRobert Weinmeister svg: encodeURIComponent(mermaidSvg) 222172fa282SRobert Weinmeister }, 223172fa282SRobert Weinmeister function(response) { 224172fa282SRobert Weinmeister if(response.status == 'success') { 225172fa282SRobert Weinmeister location.reload(true); 226172fa282SRobert Weinmeister } 227172fa282SRobert Weinmeister else { 228172fa282SRobert Weinmeister alert(response.data[0]); 229172fa282SRobert Weinmeister } 230172fa282SRobert Weinmeister }, 231172fa282SRobert Weinmeister 'json' 232172fa282SRobert Weinmeister )}; 233172fa282SRobert Weinmeister 234172fa282SRobert Weinmeister jQuery('.mermaidlocked, .mermaid').each(function(index, element) { 2351da12d6eSRobert Weinmeister document.getElementById('mermaidContainer' + index).addEventListener('mouseenter', function() { 236172fa282SRobert Weinmeister document.getElementById('mermaidFieldset' + index).style.display = 'flex'; 2371da12d6eSRobert Weinmeister }); 2381da12d6eSRobert Weinmeister document.getElementById('mermaidContainer' + index).addEventListener('mouseleave', function() { 239172fa282SRobert Weinmeister document.getElementById('mermaidFieldset' + index).style.display = 'none'; 2401da12d6eSRobert Weinmeister }); 2411da12d6eSRobert Weinmeister 242172fa282SRobert Weinmeister if(jQuery(element).hasClass('mermaidlocked')) { 243172fa282SRobert Weinmeister document.getElementById('mermaidButtonSave' + index).addEventListener('click', () => { 2441da12d6eSRobert Weinmeister var svgContent = element.innerHTML.trim(); 2451da12d6eSRobert Weinmeister var blob = new Blob([svgContent], { type: 'image/svg+xml' }); 2461da12d6eSRobert Weinmeister var link = document.createElement('a'); 2471da12d6eSRobert Weinmeister link.href = URL.createObjectURL(blob); 2481da12d6eSRobert Weinmeister link.download = 'mermaid' + index + '.svg'; 2491da12d6eSRobert Weinmeister link.click(); 2501da12d6eSRobert Weinmeister URL.revokeObjectURL(link.href); 2511da12d6eSRobert Weinmeister }); 252172fa282SRobert Weinmeister 253172fa282SRobert Weinmeister document.getElementById('mermaidButtonPermanent' + index).addEventListener('click', () => { 254172fa282SRobert Weinmeister if(confirm('Unlock Mermaid diagram?')) { 255172fa282SRobert Weinmeister callDokuWikiPHP('unlock', index, originalMermaidContent, element.innerHTML.trim()); 256172fa282SRobert Weinmeister } 257172fa282SRobert Weinmeister }); 258172fa282SRobert Weinmeister } 259172fa282SRobert Weinmeister 260172fa282SRobert Weinmeister if(jQuery(element).hasClass('mermaid')) { 261172fa282SRobert Weinmeister var originalMermaidContent = element.innerHTML; 262172fa282SRobert Weinmeister var observer = new MutationObserver(function(mutations) { 263172fa282SRobert Weinmeister mutations.forEach(function(mutation) { 264172fa282SRobert Weinmeister if (mutation.type === 'childList' && element.innerHTML.startsWith('<svg')) { 265172fa282SRobert Weinmeister document.getElementById('mermaidButtonSave' + index).addEventListener('click', () => { 266172fa282SRobert Weinmeister var svgContent = element.innerHTML.trim(); 267172fa282SRobert Weinmeister var blob = new Blob([svgContent], { type: 'image/svg+xml' }); 268172fa282SRobert Weinmeister var link = document.createElement('a'); 269172fa282SRobert Weinmeister link.href = URL.createObjectURL(blob); 270172fa282SRobert Weinmeister link.download = 'mermaid' + index + '.svg'; 271172fa282SRobert Weinmeister link.click(); 272172fa282SRobert Weinmeister URL.revokeObjectURL(link.href); 273172fa282SRobert Weinmeister }); 274172fa282SRobert Weinmeister 275172fa282SRobert Weinmeister document.getElementById('mermaidButtonPermanent' + index).addEventListener('click', () => { 276172fa282SRobert Weinmeister if(confirm('Lock Mermaid diagram? [experimental]')) { 277172fa282SRobert Weinmeister callDokuWikiPHP('lock', index, originalMermaidContent, element.innerHTML.trim()); 278172fa282SRobert Weinmeister } 279172fa282SRobert Weinmeister }); 2801da12d6eSRobert Weinmeister } 2811da12d6eSRobert Weinmeister }); 2821da12d6eSRobert Weinmeister }); 2831da12d6eSRobert Weinmeister observer.observe(element, config); 284172fa282SRobert Weinmeister } 2851da12d6eSRobert Weinmeister }); 2861da12d6eSRobert Weinmeister});" 2871da12d6eSRobert Weinmeister ); 28846a60b4fSRobertWeinmeister } 28946a60b4fSRobertWeinmeister} 2901da12d6eSRobert Weinmeister 291