xref: /plugin/commonmark/action.php (revision 3bfa6366100b306102fdfd6fed907ee4069abb8c)
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);
569b5593feSSungbin 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            }
919b5593feSSungbin 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        }
109*3bfa6366Sgerilu        else {
110*3bfa6366Sgerilu            return;
111*3bfa6366Sgerilu        }
11280734199SSungbin Jeon        $event->data = $result['text'];
11380734199SSungbin Jeon        if ($this->firstRun == true) {
11480734199SSungbin Jeon            // get position of each line
11580734199SSungbin Jeon            $lastPos = 0;
1164c91f333SSungbin Jeon            $this->linePosition[] = $lastPos;
1174c91f333SSungbin Jeon            while(($lastPos = strpos($markdown,PHP_EOL,$lastPos)) !== false){
1184c91f333SSungbin Jeon                $this->linePosition[] = $lastPos;
1194c91f333SSungbin Jeon                $lastPos = $lastPos + strlen(PHP_EOL);
12080734199SSungbin Jeon            }
12180734199SSungbin Jeon            $this->headingInfo = $this->CleanHeadingInfo($result['heading']);
1224511252fSSungbin Jeon            $this->FixHeadingLine($markdown);
12380734199SSungbin Jeon            $this->firstRun = false;
12480734199SSungbin Jeon        }
12580734199SSungbin Jeon    }
12680734199SSungbin Jeon
12780734199SSungbin Jeon    public function CleanHeadingInfo(array $input): array {
12880734199SSungbin Jeon        $keys = array_keys($input);
12980734199SSungbin Jeon        foreach($keys as $key) {
1304c91f333SSungbin Jeon            $check = false;
1314c91f333SSungbin Jeon            $new_key = sectionId($key, $check);
13280734199SSungbin Jeon            if($new_key != $key) {
13380734199SSungbin Jeon                $input[$new_key] = $input[$key];
13480734199SSungbin Jeon                unset($input[$key]);
13580734199SSungbin Jeon            }
13680734199SSungbin Jeon        }
1374c91f333SSungbin Jeon        uasort($input, fn($a, $b) => $a['startline'] <=> $b['startline']);
13880734199SSungbin Jeon        return $input;
139ad615f37SSungbin Jeon    }
1404511252fSSungbin Jeon
1414511252fSSungbin Jeon    public function FixHeadingLine(string $markdown) {
1424511252fSSungbin Jeon        $arr = explode(PHP_EOL, $markdown);
1434511252fSSungbin Jeon        foreach($this->headingInfo as &$element) {
1444511252fSSungbin Jeon            $target = $arr[$element['startline'] - 1];
1454511252fSSungbin Jeon            if (preg_match('/^#{1,6}(?:[ \t]+|$)/', $target) == 1) {
1464511252fSSungbin Jeon                $element['endline'] = $element['startline'];
1474511252fSSungbin Jeon            } else {
1484511252fSSungbin Jeon                $element['startline'] = $element['startline'] - 1;
1494511252fSSungbin Jeon                $element['endline'] = $element['endline'] - 1;
1504511252fSSungbin Jeon            }
1514511252fSSungbin Jeon        }
1524511252fSSungbin Jeon    }
153ad615f37SSungbin Jeon}
154