1ad615f37SSungbin Jeon<?php 273971cfeSSungbin Jeon/* 373971cfeSSungbin Jeon * This file is part of the clockoon/dokuwiki-commonmark-plugin package. 473971cfeSSungbin Jeon * 573971cfeSSungbin Jeon * (c) Sungbin Jeon <clockoon@gmail.com> 673971cfeSSungbin Jeon * 773971cfeSSungbin Jeon * Original code based on the followings: 873971cfeSSungbin Jeon * - CommonMark JS reference parser (https://bitly.com/commonmark-js) (c) John MacFarlane 973971cfeSSungbin Jeon * - league/commonmark (https://github.com/thephpleague/commonmark) (c) Colin O'Dell <colinodell@gmail.com> 1073971cfeSSungbin Jeon * 1173971cfeSSungbin Jeon * For the full copyright and license information, please view the LICENSE 1273971cfeSSungbin Jeon * file that was distributed with this source code. 13ad615f37SSungbin Jeon */ 148ec9a8f2SSungbin Jeon 158ec9a8f2SSungbin Jeon require_once __DIR__.'/src/bootstrap.php'; 168ec9a8f2SSungbin Jeon 178ec9a8f2SSungbin Jeon use Dokuwiki\Plugin\Commonmark\Commonmark; 1880734199SSungbin Jeon use Nette\Utils\Strings; 19ad615f37SSungbin Jeon 20ad615f37SSungbin Jeon if(!defined('DOKU_INC')) die(); 21ad615f37SSungbin Jeon 22ad615f37SSungbin Jeon class action_plugin_commonmark extends DokuWiki_Action_Plugin { 2380734199SSungbin Jeon // array for heading positions 2480734199SSungbin Jeon // [hid] => {[depth], [startline], [endline]} 2580734199SSungbin Jeon public $headingInfo = []; 2680734199SSungbin Jeon // positions of newline 2780734199SSungbin Jeon public $linePosition = []; 2880734199SSungbin Jeon // flag for checking first run only 2980734199SSungbin Jeon public $firstRun = true; 3080734199SSungbin Jeon 31ad615f37SSungbin Jeon /** 32ad615f37SSungbin Jeon * pass text to Commonmark parser before DW parser 33ad615f37SSungbin Jeon */ 34ad615f37SSungbin Jeon public function register(Doku_Event_Handler $controller) { 35ad615f37SSungbin Jeon $controller->register_hook('PARSER_WIKITEXT_PREPROCESS', 'BEFORE', $this, 36ad615f37SSungbin Jeon '_commonmarkparse'); 370e097a8fSSungbin Jeon if($this->getConf('fix_secedit')) { 3880734199SSungbin Jeon $controller->register_hook('HTML_SECEDIT_BUTTON', 'BEFORE', $this, 3980734199SSungbin Jeon '_editbutton'); 4080734199SSungbin Jeon } 4114e3b956SSungbin Jeon } 4280734199SSungbin Jeon 4314e3b956SSungbin Jeon /** 4414e3b956SSungbin Jeon * override edit button range correspond to MD 4514e3b956SSungbin Jeon */ 4680734199SSungbin Jeon public function _editbutton(Doku_Event $event, $param) { 474511252fSSungbin Jeon //echo(print_r($this->headingInfo)); 4859e044f9SSungbin Jeon //echo(print_r($this->linePosition).'<br />'); 4980734199SSungbin Jeon global $conf; 5080734199SSungbin Jeon 5180734199SSungbin Jeon // get hid 5280734199SSungbin Jeon $hid = $event->data['hid']; 5380734199SSungbin Jeon // fetch range on original md 5480734199SSungbin Jeon // check hid match 5580734199SSungbin Jeon $keys = array_keys($this->headingInfo); 56*9b5593feSSungbin Jeon //echo(print_r($keys)); 574c91f333SSungbin Jeon if (in_array($hid,$keys)) { 5880734199SSungbin Jeon // get max section editing level config 5980734199SSungbin Jeon $maxsec = $conf['maxseclevel']; 6080734199SSungbin Jeon // set start position 6180734199SSungbin Jeon // first, check whether first heading 6280734199SSungbin Jeon if ($hid == $keys[0]) { 6380734199SSungbin Jeon $start = 1; 6480734199SSungbin Jeon } else { 654511252fSSungbin Jeon $lineStart = $this->headingInfo[$hid]['startline'] - 1; 6680734199SSungbin Jeon // since CommonMark library finds heading marks, we have to declare 674511252fSSungbin Jeon $start = $this->linePosition[$lineStart] + 1; 6880734199SSungbin Jeon } 6980734199SSungbin Jeon // find end key & location; proceed while max level or less arrived 7080734199SSungbin Jeon $endlevel = 52; 7180734199SSungbin Jeon $index = array_search($hid,$keys); 7280734199SSungbin Jeon $end = 0; // 0 means end of document 7380734199SSungbin Jeon $stop = false; 7480734199SSungbin Jeon while($stop == false) { 7580734199SSungbin Jeon if (isset($keys[$index+1])) { // check for non-last element 7680734199SSungbin Jeon $endlevel = $this->headingInfo[$keys[$index+1]]['level']; 7780734199SSungbin Jeon $lineEnd = $this->headingInfo[$keys[$index+1]]['startline'] - 1; // go one line up 780e097a8fSSungbin Jeon $end = $this->linePosition[$lineEnd] + 1; // apply newline 794511252fSSungbin Jeon if($maxsec>=$endlevel) { $stop = true; } 8080734199SSungbin Jeon } else { 8180734199SSungbin Jeon $end = 0; 8280734199SSungbin Jeon $stop = true; 8380734199SSungbin Jeon } 8480734199SSungbin Jeon $index = $index + 1; 8580734199SSungbin Jeon } 8680734199SSungbin Jeon if($end == 0) { 8780734199SSungbin Jeon $event->data['range'] = (string)$start.'-'; 8880734199SSungbin Jeon } else { 8980734199SSungbin Jeon $event->data['range'] = (string)$start.'-'.$end; 9080734199SSungbin Jeon } 91*9b5593feSSungbin Jeon //echo($lineStart . ' (' . $start . '), ' . $lineEnd . ' (' . $end . ')<br />'); 9280734199SSungbin Jeon } 9380734199SSungbin Jeon // example: $event->data['range'] = '1-2'; 94ad615f37SSungbin Jeon } 95ad615f37SSungbin Jeon 96ad615f37SSungbin Jeon public function _commonmarkparse(Doku_Event $event, $param) { 9780734199SSungbin Jeon $markdown = $event->data; 98532432a2SSungbin Jeon // check force_commonmark option; if 1, ignore doctype 99532432a2SSungbin Jeon if ($this->getConf('force_commonmark')) { 1007569cca4SSungbin Jeon $markdown = ltrim($markdown); 10180734199SSungbin Jeon $result = Commonmark::RendtoDW($markdown, $this->getConf('frontmatter_tag')); 102ad615f37SSungbin Jeon } 10380734199SSungbin Jeon elseif (preg_match('/\A<!DOCTYPE markdown>/',$markdown)) { 10480734199SSungbin Jeon $markdown = preg_replace('/\A<!DOCTYPE markdown>\n/','',$markdown); 10580734199SSungbin Jeon $markdown = ltrim($markdown); 10680734199SSungbin Jeon $result = Commonmark::RendtoDW($markdown, $this->getConf('frontmatter_tag')); 10780734199SSungbin Jeon $event->data = $result['text']; 10880734199SSungbin Jeon } 10980734199SSungbin Jeon $event->data = $result['text']; 11080734199SSungbin Jeon if ($this->firstRun == true) { 11180734199SSungbin Jeon // get position of each line 11280734199SSungbin Jeon $lastPos = 0; 1134c91f333SSungbin Jeon $this->linePosition[] = $lastPos; 1144c91f333SSungbin Jeon while(($lastPos = strpos($markdown,PHP_EOL,$lastPos)) !== false){ 1154c91f333SSungbin Jeon $this->linePosition[] = $lastPos; 1164c91f333SSungbin Jeon $lastPos = $lastPos + strlen(PHP_EOL); 11780734199SSungbin Jeon } 11880734199SSungbin Jeon $this->headingInfo = $this->CleanHeadingInfo($result['heading']); 1194511252fSSungbin Jeon $this->FixHeadingLine($markdown); 12080734199SSungbin Jeon $this->firstRun = false; 12180734199SSungbin Jeon } 12280734199SSungbin Jeon } 12380734199SSungbin Jeon 12480734199SSungbin Jeon public function CleanHeadingInfo(array $input): array { 12580734199SSungbin Jeon $keys = array_keys($input); 12680734199SSungbin Jeon foreach($keys as $key) { 1274c91f333SSungbin Jeon $check = false; 1284c91f333SSungbin Jeon $new_key = sectionId($key, $check); 12980734199SSungbin Jeon if($new_key != $key) { 13080734199SSungbin Jeon $input[$new_key] = $input[$key]; 13180734199SSungbin Jeon unset($input[$key]); 13280734199SSungbin Jeon } 13380734199SSungbin Jeon } 1344c91f333SSungbin Jeon uasort($input, fn($a, $b) => $a['startline'] <=> $b['startline']); 13580734199SSungbin Jeon return $input; 136ad615f37SSungbin Jeon } 1374511252fSSungbin Jeon 1384511252fSSungbin Jeon public function FixHeadingLine(string $markdown) { 1394511252fSSungbin Jeon $arr = explode(PHP_EOL, $markdown); 1404511252fSSungbin Jeon foreach($this->headingInfo as &$element) { 1414511252fSSungbin Jeon $target = $arr[$element['startline'] - 1]; 1424511252fSSungbin Jeon if (preg_match('/^#{1,6}(?:[ \t]+|$)/', $target) == 1) { 1434511252fSSungbin Jeon $element['endline'] = $element['startline']; 1444511252fSSungbin Jeon } else { 1454511252fSSungbin Jeon $element['startline'] = $element['startline'] - 1; 1464511252fSSungbin Jeon $element['endline'] = $element['endline'] - 1; 1474511252fSSungbin Jeon } 1484511252fSSungbin Jeon } 1494511252fSSungbin Jeon } 150ad615f37SSungbin Jeon} 151