Overview
Comment: | Add markdown support, start of implementing web blocks |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | dev |
Files: | files | file ages | folders |
SHA3-256: |
9773a33ba42bc128a64a272b4802c900 |
User & Date: | bohwaz on 2021-05-02 12:18:44 |
Other Links: | branch diff | manifest | tags |
Context
2021-05-20
| ||
03:31 | Add markdown support (with TOC) check-in: 2965992701 user: bohwaz tags: trunk | |
2021-05-02
| ||
12:41 | Don't allow rename of transactions and users files check-in: 36d8a33144 user: bohwaz tags: dev | |
12:18 | Add markdown support, start of implementing web blocks check-in: 9773a33ba4 user: bohwaz tags: dev | |
2021-04-30
| ||
23:11 | Rename tables and columns to english check-in: 3ba8ecbcc0 user: bohwaz tags: dev | |
Changes
Modified src/Makefile from [605948c091] to [4afd2ef457].
︙ | ︙ | |||
8 9 10 11 12 13 14 15 16 17 18 19 20 21 | wget ${KD2_FILE} -O ${TMP_KD2}/kd2.zip rm -rf "include/lib/KD2" unzip "${TMP_KD2}/kd2.zip" -d include/lib rm -rf ${TMP_KD2} dev-server: php -S localhost:8082 -t www www/_route.php test: find . -name '*.php' -print0 | xargs -0 -n1 php -l > /dev/null phpstan: | > > > | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | wget ${KD2_FILE} -O ${TMP_KD2}/kd2.zip rm -rf "include/lib/KD2" unzip "${TMP_KD2}/kd2.zip" -d include/lib rm -rf ${TMP_KD2} wget -O "include/lib/Parsedown.php" "https://raw.githubusercontent.com/erusev/parsedown/1.7.x/Parsedown.php" wget -O "include/lib/ParsedownExtra.php" "https://raw.githubusercontent.com/erusev/parsedown-extra/0.8.x/ParsedownExtra.php" dev-server: php -S localhost:8082 -t www www/_route.php test: find . -name '*.php' -print0 | xargs -0 -n1 php -l > /dev/null phpstan: |
︙ | ︙ |
Modified src/include/lib/Garradin/Entities/Web/Page.php from [c5b7261d79] to [7794458169].
︙ | ︙ | |||
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | 'published' => 'DateTime', 'modified' => 'DateTime', 'content' => 'string', ]; const FORMAT_SKRIV = 'skriv'; const FORMAT_ENCRYPTED = 'skriv/encrypted'; const FORMATS_LIST = [ self::FORMAT_SKRIV => 'SkrivML', self::FORMAT_ENCRYPTED => 'Chiffré', ]; const STATUS_ONLINE = 'online'; const STATUS_DRAFT = 'draft'; const STATUS_LIST = [ self::STATUS_ONLINE => 'En ligne', | > > | 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | 'published' => 'DateTime', 'modified' => 'DateTime', 'content' => 'string', ]; const FORMAT_SKRIV = 'skriv'; const FORMAT_ENCRYPTED = 'skriv/encrypted'; const FORMAT_MARKDOWN = 'markdown'; const FORMATS_LIST = [ self::FORMAT_SKRIV => 'SkrivML', self::FORMAT_ENCRYPTED => 'Chiffré', self::FORMAT_MARKDOWN => 'Markdown', ]; const STATUS_ONLINE = 'online'; const STATUS_DRAFT = 'draft'; const STATUS_LIST = [ self::STATUS_ONLINE => 'En ligne', |
︙ | ︙ |
Added src/include/lib/Garradin/Web/Render/AttachmentAwareTrait.php version [5455ee42ef].
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | <?php namespace Garradin\Web\Render; use Garradin\Utils; use Garradin\Entities\Files\File; trait AttachmentAwareTrait { protected $current_path; protected $context; protected $link_prefix; protected $link_suffix; protected function resolveAttachment(string $uri) { $prefix = $this->current_path; $pos = strpos($uri, '/'); // "Image.jpg" if ($pos === false) { return WWW_URL . $prefix . '/' . $uri; } // "bla/Image.jpg" outside of web context elseif ($this->context !== File::CONTEXT_WEB && $pos !== 0) { return WWW_URL . $this->context . '/' . $uri; } // "bla/Image.jpg" in web context or absolute link, eg. "/transactions/2442/42.jpg" else { return WWW_URL . ltrim($uri, '/'); } } protected function resolveLink(string $uri) { $first = substr($uri, 0, 1); if ($first == '/' || $first == '!') { return Utils::getLocalURL($uri); } if (strpos(Utils::basename($uri), '.') === false) { $uri .= $this->link_suffix; } return $this->link_prefix . $uri; } public function isRelativeTo(File $file) { $this->current_path = Utils::dirname($file->path); $this->context = strtok($this->current_path, '/'); $this->link_suffix = ''; if ($this->context === File::CONTEXT_WEB) { $this->link_prefix = WWW_URL . '/'; $this->current_path = Utils::basename(Utils::dirname($file->path)); } else { $this->link_prefix = $options['prefix'] ?? sprintf(ADMIN_URL . 'common/files/preview.php?p=%s/', $this->context); $this->link_suffix = '.skriv'; } } } |
Added src/include/lib/Garradin/Web/Render/Blocks.php version [7866908ee6].
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | <?php namespace Garradin\Web\Render; use Garradin\Entities\Files\File; use Garradin\Squelette_Filtres; use Garradin\Plugin; use Garradin\Utils; use Garradin\Files\Files; use Garradin\UserTemplate\CommonModifiers; use Parsedown; use Parsedown_Extra; use const Garradin\{ADMIN_URL, WWW_URL}; /* display: grid; grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); grid-gap: 10px; */ class Blocks { static protected $parsedown; protected $_stack = []; public function render(?File $file, ?string $content = null, array $options = []): string { $str = $content ?? $file->fetch(); $out = '<div class="web-blocks">'; // Skip page metadata strtok($str, "\n\n----\n\n"); while ($block = strtok("\n\n----\n\n")) { $out .= $this->block($block); } if ($block = strtok('')) { $out .= $this->block($block); } strtok('', ''); // Free memory foreach ($this->_stack as $type) { $out .= '</article></section>'; } $out .= '</div>'; return $out; } protected function block(string $block): string { $header = strtok($block, "\n\n"); $content = strtok(''); strtok('', ''); // Free memory $out = ''; $content = trim($content, "\n"); $class = sprintf('web-block-%s', $type); switch ($type) { case 'columns': if (array_pop($this->_stack)) { $out .= '</article></section>'; } $out .= '<section class="web-columns">'; return $out; case 'column': if (array_pop($this->_stack)) { $out .= '</article>'; } $out .= '<article class="web-column">'; $this->_stack[] = 'column'; return $out; case 'code': return sprintf('<pre class="%s">%s</pre>', $class, htmlspecialchars($content)); case 'markdown': $md = new Markdown; return sprintf('<div class="web-content %s">%s</div>', $class, $md->render($content)); case 'skriv': $skriv = new Skriv; return sprintf('<div class="web-content %s">%s</div>', $class, $skriv->render($content)); case 'image': return sprintf('<div class="%s">%s</div>', $this->image($content)); case 'gallery': return sprintf('<div class="%s">%s</div>', $this->gallery($content)); case 'heading': return sprintf('<h2 class="%s">%s</h2>', $class, htmlspecialchars($content)); case 'quote': return sprintf('<blockquote class="%s"><p>%s</p></blockquote>', $class, nl2br(htmlspecialchars($content))); case 'video': return sprintf('<iframe class="%s" src="%s" frameborder="0" allow="accelerometer; encrypted-media; gyroscope" allowfullscreen></iframe>', $class, htmlspecialchars($content)); default: throw new \LogicException('Unknown type: ' . $type); } } } |
Modified src/include/lib/Garradin/Web/Render/EncryptedSkriv.php from [8b803e3b49] to [d0d2a69554].
1 2 3 4 5 6 7 8 9 10 | <?php namespace Garradin\Web\Render; use Garradin\Entities\Files\File; use Garradin\Template; use const Garradin\ADMIN_URL; class EncryptedSkriv { | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <?php namespace Garradin\Web\Render; use Garradin\Entities\Files\File; use Garradin\Template; use const Garradin\ADMIN_URL; class EncryptedSkriv { public function render(File $file, ?string $content = null): string { $tpl = Template::getInstance(); $content = $content ?? $file->fetch(); $tpl->assign('admin_url', ADMIN_URL); $tpl->assign(compact('file', 'content')); return $tpl->fetch('common/files/_file_render_encrypted.tpl'); } } |
Added src/include/lib/Garradin/Web/Render/Markdown.php version [7e10d16363].
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | <?php namespace Garradin\Web\Render; use Garradin\Entities\Files\File; use Garradin\Squelette_Filtres; use Garradin\Plugin; use Garradin\Utils; use Garradin\Files\Files; use Garradin\UserTemplate\CommonModifiers; use Parsedown; use Parsedown_Extra; use const Garradin\{ADMIN_URL, WWW_URL}; class Markdown { static protected $parsedown; public function render(?File $file, ?string $content = null, array $options = []): string { if (!self::$parsedown) { self::$parsedown = new ParsedownExtra; self::$parsedown->setBreaksEnabled(true); self::$parsedown->setUrlsLinked(true); self::$parsedown->setSafeMode(true); } $str = $content ?? $file->fetch(); $str = self::$parsedown->text($str); $str = CommonModifiers::typo($str); $str = preg_replace_callback(';<a href="((?!https?://|\w+:).+)">;i', function ($matches) { return sprintf('<a href="%s" target="_parent">', $this->resolveLink($matches[1])); }, $str); return sprintf('<div class="web-content">%s</div>', $str); } } |
Modified src/include/lib/Garradin/Web/Render/Skriv.php from [0996e6e8e0] to [0ea4ef11c0].
1 2 3 4 5 6 | <?php namespace Garradin\Web\Render; use Garradin\Entities\Files\File; | < < < | | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | < < < | < < < < < < < < | | | | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | <?php namespace Garradin\Web\Render; use Garradin\Entities\Files\File; use Garradin\Plugin; use Garradin\UserTemplate\CommonModifiers; use KD2\SkrivLite; use const Garradin\{ADMIN_URL, WWW_URL}; class Skriv { use AttachmentAwareTrait; static protected $skriv; public function render(?File $file, ?string $content = null, array $options = []): string { if (!self::$skriv) { self::$skriv = new \KD2\SkrivLite; self::$skriv->registerExtension('file', [self::class, 'SkrivFile']); self::$skriv->registerExtension('image', [self::class, 'SkrivImage']); // Enregistrer d'autres extensions éventuellement Plugin::fireSignal('skriv.init', ['skriv' => self::$skriv]); } $skriv =& self::$skriv; if ($file) { $this->isRelativeTo($file); } $str = $content ?? $file->fetch(); $str = preg_replace_callback('/#file:\[([^\]\h]+)\]/', function ($match) { return $this->resolveAttachment($match[1]); }, $str); $str = $skriv->render($str); $str = CommonModifiers::typo($str); $str = preg_replace_callback(';<a href="((?!https?://|\w+:).+)">;i', function ($matches) { return sprintf('<a href="%s" target="_parent">', $this->resolveLink($matches[1])); }, $str); return sprintf('<div class="web-content">%s</div>', $str); } /** * Callback utilisé pour l'extension <<file>> dans le wiki-texte * @param array $args Arguments passés à l'extension * @param string $content Contenu éventuel (en mode bloc) * @param SkrivLite $skriv Objet SkrivLite */ static public function SkrivFile(array $args, ?string $content, SkrivLite $skriv): string { $name = $args[0] ?? null; $caption = $args[1] ?? null; if (!$name || !$this->current_path) { return $skriv->parseError('/!\ Tag file : aucun nom de fichier indiqué.'); } if (empty($caption)) { $caption = $name; } $url = $this->resolveAttachment($name); $ext = substr($name, strrpos($name, '.')+1); return sprintf( '<aside class="file" data-type="%s"><a href="%s" class="internal-file">%s</a> <small>(%s)</small></aside>', htmlspecialchars($ext), htmlspecialchars($url), htmlspecialchars($caption), htmlspecialchars(strtoupper($ext)) ); } |
︙ | ︙ | |||
140 141 142 143 144 145 146 | { static $align_values = ['left', 'right', 'center']; $name = $args[0] ?? null; $align = $args[1] ?? null; $caption = $args[2] ?? null; | | | | 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | { static $align_values = ['left', 'right', 'center']; $name = $args[0] ?? null; $align = $args[1] ?? null; $caption = $args[2] ?? null; if (!$name || !$this->current_path) { return $skriv->parseError('/!\ Tag image : aucun nom de fichier indiqué.'); } $url = $this->resolveAttachment($name); $thumb_url = sprintf('%s?%dpx', $url, $align == 'center' ? 500 : 200); $out = sprintf('<a href="%s" class="internal-image" target="_image"><img src="%s" alt="%s" loading="lazy" /></a>', htmlspecialchars($url), htmlspecialchars($thumb_url), htmlspecialchars($caption ?? $name) ); |
︙ | ︙ |
Added src/www/admin/static/scripts/web_editor.js version [882b005b1b].
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | (function () { const TYPES = ['code', ]; window.WebEditor = class { constructor(textarea) { this.textarea = textarea; this.parse(textarea.value) this.editor = } build() { } parse(text) { var blocks = text.split("\n\n----\n\n"); this.blocks = []; for (var i = 0; i < blocks.length; i++) { let block = blocks[i]; if ((i % 1) == 0) { this.blocks.push(this.parseMeta(block)); } else { this.blocks[this.blocks.length - 1].content = block.trim("\r\n"); } } } parseMeta(text) { var lines = text.split("\n"); var meta = {}; for (var i = 0; i < lines.length; i++) { var line = lines[i].split(':', 2); if (line.length != 2) { continue; } meta[line[0].trim().toLowerCase()] = line[1].trim(); } return meta; } } })(); |