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 9ea08b541SRobert Weinmeisterif (!defined('DOKU_INC')) die(); 10ea08b541SRobert 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 { 88ea08b541SRobert Weinmeister // only load mermaid if it is needed 89ea08b541SRobert Weinmeister if(strpos(rawWiki(getID()), '<mermaid') === false) 90ea08b541SRobert Weinmeister { 91ea08b541SRobert Weinmeister return; 92ea08b541SRobert Weinmeister } 93ea08b541SRobert Weinmeister 944c8bd9ffSRobert Weinmeister $theme = $this->getConf('theme'); 95*55e3db93SRobert Weinmeister $look = $this->getConf('look'); 96*55e3db93SRobert Weinmeister $logLevel = $this->getConf('logLevel'); 97*55e3db93SRobert Weinmeister $init = "mermaid.initialize({startOnLoad: true, logLevel: '".$logLevel."', theme: '".$theme."', look: '".$look."'});"; 986fcac025SRobert Weinmeister $location = $this->getConf('location'); 994c8bd9ffSRobert Weinmeister 1006fcac025SRobert Weinmeister switch ($location) { 1012d4b7fc2SRobert Weinmeister case 'local': 10246a60b4fSRobertWeinmeister $event->data['script'][] = array 10346a60b4fSRobertWeinmeister ( 10446a60b4fSRobertWeinmeister 'type' => 'text/javascript', 10546a60b4fSRobertWeinmeister 'charset' => 'utf-8', 1062d4b7fc2SRobert Weinmeister 'src' => DOKU_BASE.'lib/plugins/mermaid/mermaid.min.js' 10746a60b4fSRobertWeinmeister ); 1082d4b7fc2SRobert Weinmeister break; 1092d4b7fc2SRobert Weinmeister case 'latest': 110a788b843SRobert Weinmeister case 'remote1091': 111a788b843SRobert Weinmeister // options remote108, remote106, remote104, remote103, remote102, remote101, remote100 are depreciated and only included for backward compatibility 1126fcac025SRobert Weinmeister case 'remote108': 113a612c7d6SRobert Weinmeister case 'remote106': 1148eaa3f3bSRobert Weinmeister case 'remote104': 1157d8a2661SRobert Weinmeister case 'remote103': 1164df3d176SRobert Weinmeister case 'remote102': 1175f50b169SRobert Weinmeister case 'remote101': 1186e5341c6SRobert Weinmeister case 'remote100': 1196fcac025SRobert Weinmeister $versions = array( 1206fcac025SRobert Weinmeister 'latest' => '', 121a788b843SRobert Weinmeister 'remote1091' => '@10.9.1', 1226fcac025SRobert Weinmeister 'remote108' => '@10.8.0', 1236fcac025SRobert Weinmeister 'remote106' => '@10.6.1', 1246fcac025SRobert Weinmeister 'remote104' => '@10.4.0', 1256fcac025SRobert Weinmeister 'remote103' => '@10.3.1', 1266fcac025SRobert Weinmeister 'remote102' => '@10.2.4', 1276fcac025SRobert Weinmeister 'remote101' => '@10.1.0', 1286fcac025SRobert Weinmeister 'remote100' => '@10.0.2' 1296fcac025SRobert Weinmeister ); 1306fcac025SRobert Weinmeister $data = "import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid".$versions[$location]."/dist/mermaid.esm.min.mjs';".$init; 1316e5341c6SRobert Weinmeister $event->data['script'][] = array 1326e5341c6SRobert Weinmeister ( 1336e5341c6SRobert Weinmeister 'type' => 'module', 1346e5341c6SRobert Weinmeister 'charset' => 'utf-8', 1356fcac025SRobert Weinmeister '_data' => $data 1366e5341c6SRobert Weinmeister ); 1376e5341c6SRobert Weinmeister break; 138a788b843SRobert Weinmeister // option remote94 is depreciated and only included for backward compatibility 1396e5341c6SRobert Weinmeister case 'remote94': 140a788b843SRobert Weinmeister case 'remote943': 1416e5341c6SRobert Weinmeister $event->data['script'][] = array 1426e5341c6SRobert Weinmeister ( 1436e5341c6SRobert Weinmeister 'type' => 'text/javascript', 1446e5341c6SRobert Weinmeister 'charset' => 'utf-8', 1454df3d176SRobert Weinmeister 'src' => 'https://cdn.jsdelivr.net/npm/mermaid@9.4.3/dist/mermaid.min.js' 1466e5341c6SRobert Weinmeister ); 1476e5341c6SRobert Weinmeister break; 148a788b843SRobert Weinmeister // option remote93 is depreciated and only included for backward compatibility 1492d4b7fc2SRobert Weinmeister case 'remote93': 1502d4b7fc2SRobert Weinmeister $event->data['script'][] = array 1512d4b7fc2SRobert Weinmeister ( 1522d4b7fc2SRobert Weinmeister 'type' => 'text/javascript', 1532d4b7fc2SRobert Weinmeister 'charset' => 'utf-8', 1544df3d176SRobert Weinmeister 'src' => 'https://cdn.jsdelivr.net/npm/mermaid@9.3.0/dist/mermaid.min.js' 1552d4b7fc2SRobert Weinmeister ); 1562d4b7fc2SRobert Weinmeister break; 1572d4b7fc2SRobert Weinmeister default: 1582d4b7fc2SRobert Weinmeister } 15946a60b4fSRobertWeinmeister 16046a60b4fSRobertWeinmeister $event->data['link'][] = array 16146a60b4fSRobertWeinmeister ( 16246a60b4fSRobertWeinmeister 'rel' => 'stylesheet', 16346a60b4fSRobertWeinmeister 'type' => 'text/css', 16446a60b4fSRobertWeinmeister 'href' => DOKU_BASE."lib/plugins/mermaid/mermaid.css", 16546a60b4fSRobertWeinmeister ); 16646a60b4fSRobertWeinmeister 1676fcac025SRobert Weinmeister switch ($location) { 1684df3d176SRobert Weinmeister case 'local': 169a788b843SRobert Weinmeister case 'remote943': 170a788b843SRobert Weinmeister // options remote94 and remote93 are depreciated and only included for backward compatibility 1716e5341c6SRobert Weinmeister case 'remote94': 1726e5341c6SRobert Weinmeister case 'remote93': 17346a60b4fSRobertWeinmeister $event->data['script'][] = array 17446a60b4fSRobertWeinmeister ( 17546a60b4fSRobertWeinmeister 'type' => 'text/javascript', 17646a60b4fSRobertWeinmeister 'charset' => 'utf-8', 1774c8bd9ffSRobert Weinmeister '_data' => $init 17846a60b4fSRobertWeinmeister ); 1796e5341c6SRobert Weinmeister break; 1806e5341c6SRobert Weinmeister default: 1816e5341c6SRobert Weinmeister } 1826fcac025SRobert Weinmeister 1836fcac025SRobert Weinmeister // remove the search highlight from DokuWiki as it interferes with the Mermaid parsing/rendering 1846fcac025SRobert Weinmeister $event->data['script'][] = array 1856fcac025SRobert Weinmeister ( 1866fcac025SRobert Weinmeister 'type' => 'text/javascript', 1876fcac025SRobert Weinmeister 'charset' => 'utf-8', 1886fcac025SRobert Weinmeister '_data' => "document.addEventListener('DOMContentLoaded', function() { 1896fcac025SRobert Weinmeister jQuery('.mermaid').each(function() { 1906fcac025SRobert Weinmeister var modifiedContent = jQuery(this).html().replace(/<span class=\"search_hit\">(.+?)<\/span>/g, '$1'); 1916fcac025SRobert Weinmeister jQuery(this).html(modifiedContent); 1926fcac025SRobert Weinmeister }) 1936fcac025SRobert Weinmeister });" 1946fcac025SRobert Weinmeister ); 1951da12d6eSRobert Weinmeister 1961da12d6eSRobert Weinmeister // adds image-save capability 1971da12d6eSRobert Weinmeister // First: Wait until the DOM content is fully loaded 1981da12d6eSRobert Weinmeister // Second: Wait until Mermaid has changed the dokuwiki content to an svg 1991da12d6eSRobert Weinmeister $event->data['script'][] = array 2001da12d6eSRobert Weinmeister ( 2011da12d6eSRobert Weinmeister 'type' => 'text/javascript', 2021da12d6eSRobert Weinmeister 'charset' => 'utf-8', 2031da12d6eSRobert Weinmeister '_data' => " 2041da12d6eSRobert Weinmeisterdocument.addEventListener('DOMContentLoaded', function() { 2051da12d6eSRobert Weinmeister var config = { 2061da12d6eSRobert Weinmeister childList: true, 2071da12d6eSRobert Weinmeister subtree: true, 2081da12d6eSRobert Weinmeister characterData: true 2091da12d6eSRobert Weinmeister }; 2101da12d6eSRobert Weinmeister 211172fa282SRobert Weinmeister function callDokuWikiPHP(mode, index, mermaidRaw, mermaidSvg) { 212172fa282SRobert Weinmeister jQuery.post( 213172fa282SRobert Weinmeister DOKU_BASE + 'lib/exe/ajax.php', 214172fa282SRobert Weinmeister { 215172fa282SRobert Weinmeister call: 'plugin_mermaid', 216172fa282SRobert Weinmeister mode: mode, 217172fa282SRobert Weinmeister mermaidindex: index, 218172fa282SRobert Weinmeister pageid: '".getID()."', 219172fa282SRobert Weinmeister svg: encodeURIComponent(mermaidSvg) 220172fa282SRobert Weinmeister }, 221172fa282SRobert Weinmeister function(response) { 222172fa282SRobert Weinmeister if(response.status == 'success') { 223172fa282SRobert Weinmeister location.reload(true); 224172fa282SRobert Weinmeister } 225172fa282SRobert Weinmeister else { 226172fa282SRobert Weinmeister alert(response.data[0]); 227172fa282SRobert Weinmeister } 228172fa282SRobert Weinmeister }, 229172fa282SRobert Weinmeister 'json' 230172fa282SRobert Weinmeister )}; 231172fa282SRobert Weinmeister 232172fa282SRobert Weinmeister jQuery('.mermaidlocked, .mermaid').each(function(index, element) { 2331da12d6eSRobert Weinmeister document.getElementById('mermaidContainer' + index).addEventListener('mouseenter', function() { 234172fa282SRobert Weinmeister document.getElementById('mermaidFieldset' + index).style.display = 'flex'; 2351da12d6eSRobert Weinmeister }); 2361da12d6eSRobert Weinmeister document.getElementById('mermaidContainer' + index).addEventListener('mouseleave', function() { 237172fa282SRobert Weinmeister document.getElementById('mermaidFieldset' + index).style.display = 'none'; 2381da12d6eSRobert Weinmeister }); 2391da12d6eSRobert Weinmeister 240172fa282SRobert Weinmeister if(jQuery(element).hasClass('mermaidlocked')) { 241172fa282SRobert Weinmeister document.getElementById('mermaidButtonSave' + index).addEventListener('click', () => { 2421da12d6eSRobert Weinmeister var svgContent = element.innerHTML.trim(); 2431da12d6eSRobert Weinmeister var blob = new Blob([svgContent], { type: 'image/svg+xml' }); 2441da12d6eSRobert Weinmeister var link = document.createElement('a'); 2451da12d6eSRobert Weinmeister link.href = URL.createObjectURL(blob); 2461da12d6eSRobert Weinmeister link.download = 'mermaid' + index + '.svg'; 2471da12d6eSRobert Weinmeister link.click(); 2481da12d6eSRobert Weinmeister URL.revokeObjectURL(link.href); 2491da12d6eSRobert Weinmeister }); 250172fa282SRobert Weinmeister 251172fa282SRobert Weinmeister document.getElementById('mermaidButtonPermanent' + index).addEventListener('click', () => { 252172fa282SRobert Weinmeister if(confirm('Unlock Mermaid diagram?')) { 253172fa282SRobert Weinmeister callDokuWikiPHP('unlock', index, originalMermaidContent, element.innerHTML.trim()); 254172fa282SRobert Weinmeister } 255172fa282SRobert Weinmeister }); 256172fa282SRobert Weinmeister } 257172fa282SRobert Weinmeister 258172fa282SRobert Weinmeister if(jQuery(element).hasClass('mermaid')) { 259172fa282SRobert Weinmeister var originalMermaidContent = element.innerHTML; 260172fa282SRobert Weinmeister var observer = new MutationObserver(function(mutations) { 261172fa282SRobert Weinmeister mutations.forEach(function(mutation) { 262172fa282SRobert Weinmeister if (mutation.type === 'childList' && element.innerHTML.startsWith('<svg')) { 263172fa282SRobert Weinmeister document.getElementById('mermaidButtonSave' + index).addEventListener('click', () => { 264172fa282SRobert Weinmeister var svgContent = element.innerHTML.trim(); 265172fa282SRobert Weinmeister var blob = new Blob([svgContent], { type: 'image/svg+xml' }); 266172fa282SRobert Weinmeister var link = document.createElement('a'); 267172fa282SRobert Weinmeister link.href = URL.createObjectURL(blob); 268172fa282SRobert Weinmeister link.download = 'mermaid' + index + '.svg'; 269172fa282SRobert Weinmeister link.click(); 270172fa282SRobert Weinmeister URL.revokeObjectURL(link.href); 271172fa282SRobert Weinmeister }); 272172fa282SRobert Weinmeister 273172fa282SRobert Weinmeister document.getElementById('mermaidButtonPermanent' + index).addEventListener('click', () => { 274172fa282SRobert Weinmeister if(confirm('Lock Mermaid diagram? [experimental]')) { 275172fa282SRobert Weinmeister callDokuWikiPHP('lock', index, originalMermaidContent, element.innerHTML.trim()); 276172fa282SRobert Weinmeister } 277172fa282SRobert Weinmeister }); 2781da12d6eSRobert Weinmeister } 2791da12d6eSRobert Weinmeister }); 2801da12d6eSRobert Weinmeister }); 2811da12d6eSRobert Weinmeister observer.observe(element, config); 282172fa282SRobert Weinmeister } 2831da12d6eSRobert Weinmeister }); 2841da12d6eSRobert Weinmeister});" 2851da12d6eSRobert Weinmeister ); 28646a60b4fSRobertWeinmeister } 28746a60b4fSRobertWeinmeister} 2881da12d6eSRobert Weinmeister 289