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('allow_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 if (in_array($hid,$keys)) { 57 // get max section editing level config 58 $maxsec = $conf['maxseclevel']; 59 // set start position 60 // first, check whether first heading 61 if ($hid == $keys[0]) { 62 $start = 1; 63 } else { 64 $lineStart = $this->headingInfo[$hid]['startline'] - 1; 65 // since CommonMark library finds heading marks, we have to declare 66 $start = $this->linePosition[$lineStart] + 1; 67 } 68 // find end key & location; proceed while max level or less arrived 69 $endlevel = 52; 70 $index = array_search($hid,$keys); 71 $end = 0; // 0 means end of document 72 $stop = false; 73 while($stop == false) { 74 if (isset($keys[$index+1])) { // check for non-last element 75 $endlevel = $this->headingInfo[$keys[$index+1]]['level']; 76 $lineEnd = $this->headingInfo[$keys[$index+1]]['startline'] - 1; // go one line up 77 $end = $this->linePosition[$lineEnd]; 78 if($maxsec>=$endlevel) { $stop = true; } 79 } else { 80 $end = 0; 81 $stop = true; 82 } 83 $index = $index + 1; 84 } 85 if($end == 0) { 86 $event->data['range'] = (string)$start.'-'; 87 } else { 88 $event->data['range'] = (string)$start.'-'.$end; 89 } 90 91 } 92 // example: $event->data['range'] = '1-2'; 93 } 94 95 public function _commonmarkparse(Doku_Event $event, $param) { 96 $markdown = $event->data; 97 // check force_commonmark option; if 1, ignore doctype 98 if ($this->getConf('force_commonmark')) { 99 $markdown = ltrim($markdown); 100 $result = Commonmark::RendtoDW($markdown, $this->getConf('frontmatter_tag')); 101 } 102 elseif (preg_match('/\A<!DOCTYPE markdown>/',$markdown)) { 103 $markdown = preg_replace('/\A<!DOCTYPE markdown>\n/','',$markdown); 104 $markdown = ltrim($markdown); 105 $result = Commonmark::RendtoDW($markdown, $this->getConf('frontmatter_tag')); 106 $event->data = $result['text']; 107 } 108 $event->data = $result['text']; 109 if ($this->firstRun == true) { 110 // get position of each line 111 $lastPos = 0; 112 $this->linePosition[] = $lastPos; 113 while(($lastPos = strpos($markdown,PHP_EOL,$lastPos)) !== false){ 114 $this->linePosition[] = $lastPos; 115 $lastPos = $lastPos + strlen(PHP_EOL); 116 } 117 $this->headingInfo = $this->CleanHeadingInfo($result['heading']); 118 $this->FixHeadingLine($markdown); 119 $this->firstRun = false; 120 } 121 } 122 123 public function CleanHeadingInfo(array $input): array { 124 $keys = array_keys($input); 125 foreach($keys as $key) { 126 $check = false; 127 $new_key = sectionId($key, $check); 128 if($new_key != $key) { 129 $input[$new_key] = $input[$key]; 130 unset($input[$key]); 131 } 132 } 133 uasort($input, fn($a, $b) => $a['startline'] <=> $b['startline']); 134 return $input; 135 } 136 137 public function FixHeadingLine(string $markdown) { 138 $arr = explode(PHP_EOL, $markdown); 139 foreach($this->headingInfo as &$element) { 140 $target = $arr[$element['startline'] - 1]; 141 if (preg_match('/^#{1,6}(?:[ \t]+|$)/', $target) == 1) { 142 $element['endline'] = $element['startline']; 143 } else { 144 $element['startline'] = $element['startline'] - 1; 145 $element['endline'] = $element['endline'] - 1; 146 } 147 } 148 } 149} 150