Scratchpads V1
This commit is contained in:
183
syntax/scratchpad.php
Normal file
183
syntax/scratchpad.php
Normal file
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
use dokuwiki\Extension\SyntaxPlugin;
|
||||
use dokuwiki\plugin\luxtools\Path;
|
||||
use dokuwiki\plugin\luxtools\ScratchpadMap;
|
||||
|
||||
/**
|
||||
* luxtools Plugin: Scratchpad syntax.
|
||||
*
|
||||
* Renders the contents of a configured file as wikitext and provides a minimal
|
||||
* inline editor that saves directly to that file (no page revisions).
|
||||
*/
|
||||
class syntax_plugin_luxtools_scratchpad extends SyntaxPlugin
|
||||
{
|
||||
/** @inheritdoc */
|
||||
public function getType()
|
||||
{
|
||||
return 'substition';
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function getPType()
|
||||
{
|
||||
return 'block';
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function getSort()
|
||||
{
|
||||
// After most formatting, similar to other luxtools syntaxes
|
||||
return 223;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function connectTo($mode)
|
||||
{
|
||||
$this->Lexer->addSpecialPattern('\{\{scratchpad>.+?\}\}', $mode, 'plugin_luxtools_scratchpad');
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function handle($match, $state, $pos, Doku_Handler $handler)
|
||||
{
|
||||
global $INPUT;
|
||||
|
||||
// Do not allow the syntax in discussion plugin comments
|
||||
if (!$this->getConf('allow_in_comments') && $INPUT->has('comment')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$match = substr($match, strlen('{{scratchpad>'), -2);
|
||||
[$path,] = array_pad(explode('&', $match, 2), 2, '');
|
||||
|
||||
return [
|
||||
'path' => trim((string)$path),
|
||||
];
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function render($format, Doku_Renderer $renderer, $data)
|
||||
{
|
||||
if ($data === false) return false;
|
||||
if (!is_array($data)) return false;
|
||||
|
||||
if ($format !== 'xhtml' && $format !== 'odt') return false;
|
||||
|
||||
// Always disable caching: the scratchpad is external to page revisions.
|
||||
$renderer->nocache();
|
||||
|
||||
$rawPad = (string)($data['path'] ?? '');
|
||||
if ($rawPad === '') {
|
||||
$this->renderError($renderer, 'scratchpad_err_nopath');
|
||||
return true;
|
||||
}
|
||||
|
||||
$pathInfo = $this->getScratchpadPathInfoSafe($rawPad, $renderer);
|
||||
if ($pathInfo === false) return true;
|
||||
|
||||
$filePath = (string)($pathInfo['path'] ?? '');
|
||||
if ($filePath === '' || str_ends_with($filePath, '/')) {
|
||||
$this->renderError($renderer, 'scratchpad_err_badpath');
|
||||
return true;
|
||||
}
|
||||
|
||||
// Never allow writing/reading within DokuWiki-controlled paths
|
||||
if (Path::isWikiControlled(Path::cleanPath($filePath, false))) {
|
||||
$this->renderError($renderer, 'error_outsidejail');
|
||||
return true;
|
||||
}
|
||||
|
||||
$text = '';
|
||||
if (@is_file($filePath) && @is_readable($filePath)) {
|
||||
$text = (string)io_readFile($filePath, false);
|
||||
}
|
||||
|
||||
if ($format === 'odt') {
|
||||
$renderer->cdata($text);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @var Doku_Renderer_xhtml $renderer */
|
||||
$endpoint = DOKU_BASE . 'lib/plugins/luxtools/scratchpad.php';
|
||||
|
||||
global $ID;
|
||||
$pageId = (string)$ID;
|
||||
$canEdit = function_exists('auth_quickaclcheck') ? (auth_quickaclcheck($pageId) >= AUTH_EDIT) : false;
|
||||
|
||||
$renderer->doc .= '<div class="luxtools-plugin luxtools-scratchpad"'
|
||||
. ' data-luxtools-scratchpad="1"'
|
||||
. ' data-endpoint="' . hsc($endpoint) . '"'
|
||||
. ' data-pad="' . hsc($rawPad) . '"'
|
||||
. ' data-pageid="' . hsc($pageId) . '"'
|
||||
. '>';
|
||||
|
||||
$renderer->doc .= '<div class="luxtools-scratchpad-bar">';
|
||||
if ($canEdit) {
|
||||
$label = (string)$this->getLang('scratchpad_edit');
|
||||
if ($label === '') $label = 'Edit';
|
||||
$renderer->doc .= '<a href="#" class="luxtools-scratchpad-edit" title="' . hsc($label) . '" aria-label="' . hsc($label) . '">✎</a>';
|
||||
}
|
||||
$renderer->doc .= '</div>';
|
||||
|
||||
$renderer->doc .= '<div class="luxtools-scratchpad-view">';
|
||||
$renderer->doc .= $this->renderWikitextFragment($text);
|
||||
$renderer->doc .= '</div>';
|
||||
|
||||
if ($canEdit) {
|
||||
$renderer->doc .= '<div class="luxtools-scratchpad-editor" hidden>';
|
||||
$renderer->doc .= '<textarea class="luxtools-scratchpad-text" rows="10" spellcheck="true"></textarea>';
|
||||
$renderer->doc .= '<div class="luxtools-scratchpad-actions">'
|
||||
. '<button type="button" class="button luxtools-scratchpad-save">' . hsc((string)$this->getLang('scratchpad_save') ?: 'Save') . '</button>'
|
||||
. '<button type="button" class="button luxtools-scratchpad-cancel">' . hsc((string)$this->getLang('scratchpad_cancel') ?: 'Cancel') . '</button>'
|
||||
. '<span class="luxtools-scratchpad-status" aria-live="polite"></span>'
|
||||
. '</div>';
|
||||
$renderer->doc .= '</div>';
|
||||
}
|
||||
|
||||
$renderer->doc .= '</div>';
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function renderWikitextFragment(string $text): string
|
||||
{
|
||||
// Render wikitext to XHTML and return it as a string
|
||||
$info = ['cache' => false];
|
||||
$instructions = p_get_instructions($text);
|
||||
return (string)p_render('xhtml', $instructions, $info);
|
||||
}
|
||||
|
||||
protected function renderError(Doku_Renderer $renderer, string $langKey): void
|
||||
{
|
||||
$msg = (string)$this->getLang($langKey);
|
||||
if ($msg === '') $msg = $langKey;
|
||||
|
||||
if ($renderer instanceof Doku_Renderer_xhtml) {
|
||||
$renderer->doc .= '<div class="luxtools-plugin luxtools-scratchpad"><div class="luxtools-scratchpad-error">'
|
||||
. hsc($msg)
|
||||
. '</div></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
$renderer->cdata('[n/a: ' . $msg . ']');
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a scratchpad alias to its file path using the configured scratchpad_paths setting.
|
||||
*
|
||||
* @param string $pad
|
||||
* @param Doku_Renderer $renderer
|
||||
* @return array|false
|
||||
*/
|
||||
protected function getScratchpadPathInfoSafe(string $pad, Doku_Renderer $renderer)
|
||||
{
|
||||
try {
|
||||
$map = new ScratchpadMap((string)$this->getConf('scratchpad_paths'));
|
||||
return [
|
||||
'path' => $map->resolve($pad),
|
||||
];
|
||||
} catch (Exception $e) {
|
||||
$this->renderError($renderer, 'scratchpad_err_unknown');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user