1<?php 2/* 3 * This file is part of the clockoon/dokuwiki-commonmark-plugin package. 4 * 5 * (c) Sungbin Jeon <clockoon@gmail.com> 6 * 7 * Original code based on the followings: 8 * - CommonMark JS reference parser (https://bitly.com/commonmark-js) (c) John MacFarlane 9 * - league/commonmark (https://github.com/thephpleague/commonmark) (c) Colin O'Dell <colinodell@gmail.com> 10 * 11 * For the full copyright and license information, please view the LICENSE 12 * file that was distributed with this source code. 13 */ 14 15 require_once __DIR__.'/src/bootstrap.php'; 16 17 use Dokuwiki\Plugin\Commonmark\Commonmark; 18 use Nette\Utils\Strings; 19 20 if(!defined('DOKU_INC')) die(); 21 22 class action_plugin_commonmark extends DokuWiki_Action_Plugin { 23 // array for heading positions 24 // [hid] => {[depth], [startline], [endline]} 25 public $headingInfo = []; 26 // positions of newline 27 public $linePosition = []; 28 // flag for checking first run only 29 public $firstRun = true; 30 31 /** 32 * pass text to Commonmark parser before DW parser 33 */ 34 public function register(Doku_Event_Handler $controller) { 35 $controller->register_hook('PARSER_WIKITEXT_PREPROCESS', 'BEFORE', $this, 36 '_commonmarkparse'); 37 if($this->getConf('fix_secedit')) { 38 $controller->register_hook('HTML_SECEDIT_BUTTON', 'BEFORE', $this, 39 '_editbutton'); 40 } 41 } 42 43 /** 44 * override edit button range correspond to MD 45 */ 46 public function _editbutton(Doku_Event $event, $param) { 47 //echo(print_r($this->headingInfo)); 48 //echo(print_r($this->linePosition).'<br />'); 49 global $conf; 50 51 // get hid 52 $hid = $event->data['hid']; 53 // fetch range on original md 54 // check hid match 55 $keys = array_keys($this->headingInfo); 56 //echo(print_r($keys)); 57 if (in_array($hid,$keys)) { 58 // get max section editing level config 59 $maxsec = $conf['maxseclevel']; 60 // set start position 61 // first, check whether first heading 62 if ($hid == $keys[0]) { 63 $start = 1; 64 } else { 65 $lineStart = $this->headingInfo[$hid]['startline'] - 1; 66 // since CommonMark library finds heading marks, we have to declare 67 $start = $this->linePosition[$lineStart] + 1; 68 } 69 // find end key & location; proceed while max level or less arrived 70 $endlevel = 52; 71 $index = array_search($hid,$keys); 72 $end = 0; // 0 means end of document 73 $stop = false; 74 while($stop == false) { 75 if (isset($keys[$index+1])) { // check for non-last element 76 $endlevel = $this->headingInfo[$keys[$index+1]]['level']; 77 $lineEnd = $this->headingInfo[$keys[$index+1]]['startline'] - 1; // go one line up 78 $end = $this->linePosition[$lineEnd] + 1; // apply newline 79 if($maxsec>=$endlevel) { $stop = true; } 80 } else { 81 $end = 0; 82 $stop = true; 83 } 84 $index = $index + 1; 85 } 86 if($end == 0) { 87 $event->data['range'] = (string)$start.'-'; 88 } else { 89 $event->data['range'] = (string)$start.'-'.$end; 90 } 91 //echo($lineStart . ' (' . $start . '), ' . $lineEnd . ' (' . $end . ')<br />'); 92 } 93 // example: $event->data['range'] = '1-2'; 94 } 95 96 public function _commonmarkparse(Doku_Event $event, $param) { 97 $markdown = $event->data; 98 // check force_commonmark option; if 1, ignore doctype 99 if ($this->getConf('force_commonmark')) { 100 $markdown = ltrim($markdown); 101 $result = Commonmark::RendtoDW($markdown, $this->getConf('frontmatter_tag')); 102 } 103 elseif (preg_match('/\A<!DOCTYPE markdown>/',$markdown)) { 104 $markdown = preg_replace('/\A<!DOCTYPE markdown>\n/','',$markdown); 105 $markdown = ltrim($markdown); 106 $result = Commonmark::RendtoDW($markdown, $this->getConf('frontmatter_tag')); 107 } 108 else { 109 return; 110 } 111 $event->data = $result['text']; 112 if ($this->firstRun == true) { 113 // get position of each line 114 $lastPos = 0; 115 $this->linePosition[] = $lastPos; 116 while(($lastPos = strpos($markdown,PHP_EOL,$lastPos)) !== false){ 117 $this->linePosition[] = $lastPos; 118 $lastPos = $lastPos + strlen(PHP_EOL); 119 } 120 $this->headingInfo = $this->CleanHeadingInfo($result['heading']); 121 $this->FixHeadingLine($markdown); 122 $this->firstRun = false; 123 } 124 } 125 126 public function CleanHeadingInfo(array $input): array { 127 $keys = array_keys($input); 128 foreach($keys as $key) { 129 $check = false; 130 $new_key = sectionId($key, $check); 131 if($new_key != $key) { 132 $input[$new_key] = $input[$key]; 133 unset($input[$key]); 134 } 135 } 136 uasort($input, fn($a, $b) => $a['startline'] <=> $b['startline']); 137 return $input; 138 } 139 140 public function FixHeadingLine(string $markdown) { 141 $arr = explode(PHP_EOL, $markdown); 142 foreach($this->headingInfo as &$element) { 143 $target = $arr[$element['startline'] - 1]; 144 if (preg_match('/^#{1,6}(?:[ \t]+|$)/', $target) == 1) { 145 $element['endline'] = $element['startline']; 146 } else { 147 $element['startline'] = $element['startline'] - 1; 148 $element['endline'] = $element['endline'] - 1; 149 } 150 } 151 } 152} 153