Comment: | Fix some bugs, improve details in web page editing |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | trunk | stable |
Files: | files | file ages | folders |
SHA3-256: |
c06d471a14d7ba50878a7158f57c77da |
User & Date: | bohwaz on 2023-03-21 15:26:57 |
Other Links: | manifest | tags |
2023-03-21
| ||
15:36 | Move onload for iframe so that we are sure it is triggered as soon as possible check-in: 9c90d7416e user: bohwaz tags: trunk, stable | |
15:26 | Fix some bugs, improve details in web page editing check-in: c06d471a14 user: bohwaz tags: trunk, stable | |
14:17 | Page encryption: fix multiple password prompts, handle images and files check-in: 19ed3d39d0 user: bohwaz tags: trunk | |
Modified src/include/lib/Garradin/Entities/Web/Page.php from [f7c6bc20c5] to [21fb3dd712].
︙ | ︙ | |||
46 47 48 49 50 51 52 | 'published' => 'DateTime', 'modified' => 'DateTime', 'content' => 'string', ]; const FORMATS_LIST = [ Render::FORMAT_MARKDOWN => 'MarkDown', | < > | 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | 'published' => 'DateTime', 'modified' => 'DateTime', 'content' => 'string', ]; const FORMATS_LIST = [ Render::FORMAT_MARKDOWN => 'MarkDown', Render::FORMAT_ENCRYPTED => 'Chiffré', Render::FORMAT_SKRIV => 'SkrivML', ]; const STATUS_ONLINE = 'online'; const STATUS_DRAFT = 'draft'; const STATUS_LIST = [ self::STATUS_ONLINE => 'En ligne', |
︙ | ︙ | |||
313 314 315 316 317 318 319 | if (!empty($source['encryption']) ) { $this->set('format', Render::FORMAT_ENCRYPTED); } elseif (empty($source['format'])) { $this->set('format', Render::FORMAT_MARKDOWN); } | | | 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 | if (!empty($source['encryption']) ) { $this->set('format', Render::FORMAT_ENCRYPTED); } elseif (empty($source['format'])) { $this->set('format', Render::FORMAT_MARKDOWN); } $this->set('status', empty($source['status']) ? self::STATUS_ONLINE : $source['status']); return parent::importForm($source); } public function getBreadcrumbs(): array { $sql = ' |
︙ | ︙ |
Modified src/include/lib/Garradin/Web/Render/Encrypted.php from [42fbb7e0fc] to [742c645396].
1 2 3 4 5 6 7 8 | <?php namespace Garradin\Web\Render; use Garradin\Entities\Files\File; use Garradin\Template; use const Garradin\ADMIN_URL; | > | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <?php namespace Garradin\Web\Render; use Garradin\Entities\Files\File; use Garradin\Template; use const Garradin\ADMIN_URL; use const Garradin\WWW_URL; class Encrypted extends AbstractRender { public function render(?string $content = null): string { $tpl = Template::getInstance(); $file = $this->file; $content = $content ?? $file->fetch(); $tpl->assign('admin_url', ADMIN_URL); $tpl->assign('url', WWW_URL . $this->current_path); $tpl->assign(compact('file', 'content')); return $tpl->fetch('common/files/_file_render_encrypted.tpl'); } } |
Modified src/include/lib/Garradin/Web/Render/Render.php from [7f37b07c32] to [75beee0f1f].
︙ | ︙ | |||
19 20 21 22 23 24 25 | static public function getRenderer(string $format, ?File $file, string $link_prefix = null) { if ($format == self::FORMAT_SKRIV) { return new Skriv($file, $link_prefix); } else if ($format == self::FORMAT_ENCRYPTED) { | | | 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | static public function getRenderer(string $format, ?File $file, string $link_prefix = null) { if ($format == self::FORMAT_SKRIV) { return new Skriv($file, $link_prefix); } else if ($format == self::FORMAT_ENCRYPTED) { return new Encrypted($file, $link_prefix); } else if ($format == self::FORMAT_MARKDOWN) { return new Markdown($file, $link_prefix); } else { throw new \LogicException('Invalid format: ' . $format); } |
︙ | ︙ |
Modified src/templates/common/files/_file_render_encrypted.tpl from [66113ac8f7] to [00452ede02].
1 2 3 4 5 6 7 8 9 10 11 | <noscript> <div class="error"> Vous dever activer javascript pour pouvoir déchiffrer cette page. </div> </noscript> <script type="text/javascript" src="{$admin_url}static/scripts/web_encryption.js"></script> <div id="web_encrypted_message"> <p class="block alert">Cette page est chiffrée. <input type="button" onclick="return pleaseDecrypt();" value="Entrer le mot de passe" /> </p> </div> | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <noscript> <div class="error"> Vous dever activer javascript pour pouvoir déchiffrer cette page. </div> </noscript> <script type="text/javascript" src="{$admin_url}static/scripts/web_encryption.js"></script> <div id="web_encrypted_message"> <p class="block alert">Cette page est chiffrée. <input type="button" onclick="return pleaseDecrypt();" value="Entrer le mot de passe" /> </p> </div> <div class="web-content" style="display: none;" id="web_encrypted_content" data-url="{$url}"> {$content} </div> |
Modified src/templates/common/files/edit_web.tpl from [6780cd027e] to [5785ce5cc0].
1 2 | {include file="admin/_head.tpl" title="Édition de fichier" custom_js=['web_editor.js']} | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | {include file="admin/_head.tpl" title="Édition de fichier" custom_js=['web_editor.js']} <form method="post" action="{$self_url}" data-focus="textarea"> <p class="textEditor"> {input type="textarea" name="content" cols="70" rows="30" default=$content data-preview-url="!common/files/_preview.php?f=%s"|local_url|args:$path data-fullscreen="1" data-attachments="0" data-savebtn="1" data-format=$format} </p> <p class="submit"> {csrf_field key=$csrf_key} {button type="submit" name="save" label="Enregistrer" shape="right" class="main"} </p> </form> {include file="admin/_foot.tpl"} |
Modified src/templates/web/edit.tpl from [57eed5d57b] to [8ddcf4f5f3].
1 2 3 4 5 6 7 8 9 | {include file="admin/_head.tpl" title="Édition : %s"|args:$page.title current="web" hide_title=true} {form_errors} {if $show_diff} <h3>Modifications entre votre version et la nouvelle version</h3> {diff old=$old_content new=$new_content} {/if} | | | < < < < < < | > > | 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 | {include file="admin/_head.tpl" title="Édition : %s"|args:$page.title current="web" hide_title=true} {form_errors} {if $show_diff} <h3>Modifications entre votre version et la nouvelle version</h3> {diff old=$old_content new=$new_content} {/if} <form method="post" action="{$self_url}" class="web-edit" data-focus="#f_content"> <fieldset class="header"> <legend>Modification : {$page.title}</legend> <p>{input type="text" name="title" source=$page required=true class="full-width" placeholder="Titre" title="Modifier le titre"}</p> <div> <dl>{input type="list" name="parent" label="Catégorie" default=$parent target="!web/_selector.php?current=%s&parent=%s"|args:$page.path,$page.parent required=true}</dl> <dl>{input type="datetime" name="date" label="Date" required=true default=$page.published}</dl> <dl>{input type="select" name="format" required=true options=$formats source=$page label="Format"}</dl> <dl>{input type="checkbox" name="status" value=$page::STATUS_DRAFT label="Brouillon" source=$page}</dl> </ul> </fieldset> <fieldset class="editor"> <div class="textEditor"> {input type="textarea" name="content" cols="70" rows="20" default=$new_content data-attachments=1 data-savebtn=2 data-preview-url="!common/files/_preview.php?w=%s"|local_url|args:$page.path data-format="#f_format"} </div> </fieldset> {* <fieldset class="content"> {$page->render()|raw} </fieldset> <div class="block"> </div> *} <fieldset class="properties"> {* <nav class="tabs"> <ul> <li><a id="toggleVisualEditor">Éditeur visuel</a></li> <li class="current"><a id="toggleTextEditor">Éditeur texte</a></li> |
︙ | ︙ |
Modified src/www/admin/static/scripts/web_editor.css from [8621224b2d] to [d40ba89e00].
1 2 | .textEditor { background: var(--gLightBackgroundColor); | < > | | | | < < < | > > > > < | 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 | .textEditor { background: var(--gLightBackgroundColor); position: relative; height: 100%; } .textEditor textarea { margin: 0; height: calc(100% - 3.5em); width: 98%; } nav.te { margin-bottom: .5em; height: 2em; } nav.te .bold, nav.te .italic, nav.te .title, nav.te .link { font-family: Georgia, "Times New Roman", serif; } nav.te .bold { font-weight: bold; } nav.te .italic { font-style: italic; } nav.te .link { text-decoration: underline; color: var(--gLinkColor); } nav.te .fullscreen { text-indent: -70em; width: 32px; overflow: hidden; } nav.te .image { margin-left: 2em; } nav.te .save, nav.te .fullscreen, nav.te .help, nav.te .preview { float: right; } nav.te .fullscreen { background-repeat: no-repeat; background-position: center center; background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAADFBMVEUAAABMTFFOTlBOTlCQ1uMHAAAAA3RSTlMAOcKBmOr4AAAAQUlEQVQI12P4/4D7P8N/B0Yo8XsC23+GZw+4pzM4TmBzYsAGgBKODNcecM9m+D+B7T3DPwfG/Qz/G5iABlzg+g8ANzMax/3kkQoAAAAASUVORK5CYII="); } nav.te .help, nav.te .save { margin-left: 2em; } nav.te .save-label { font-weight: bold; } .textEditor.fullscreen nav.te .fullscreen { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAADFBMVEUAAABNTVFOTlBOTlBLB/faAAAAA3RSTlMAOsPdsomtAAAAQElEQVQI12NIEWCRZNi5gTePIe8D/08G6Q/8Txj0P/B/YVj/gf8fAzawHyQhD1ICJvI/8O9k2FnAl8eQosAiCQCgixb13aKGIwAAAABJRU5ErkJggg=="); } .textEditor nav button.close { display: none; width: 95%; } .textEditor.fullscreen { position: fixed; top: 0; left: 0; |
︙ | ︙ | |||
86 87 88 89 90 91 92 | } .textEditor.iframe nav button { display: none; } .textEditor.iframe nav button.close { | | > > | 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | } .textEditor.iframe nav button { display: none; } .textEditor.iframe nav button.close { display: block; margin: 0 auto; } .textEditor iframe { border: none; background: rgba(var(--gLightBackgroundColor), 0.5); border: 1px solid var(--gLightBorderColor); border-radius: .5em; width: 100%; } .textEditor iframe.hidden { visibility: hidden; width: 0px; |
︙ | ︙ | |||
180 181 182 183 184 185 186 | font-size: 0.8em; margin: .5em; opacity: 0.8; } #confirm_saved { position: absolute; | < | > > | | 182 183 184 185 186 187 188 189 190 191 192 193 194 | font-size: 0.8em; margin: .5em; opacity: 0.8; } #confirm_saved { position: absolute; right: -.1em; top: -.1em; margin: 0; text-align: center; transition: opacity .5s; } |
Modified src/www/admin/static/scripts/web_editor.js from [6022b134db] to [da3e9adcd7].
1 2 3 4 5 6 7 8 | (function () { g.style('scripts/web_editor.css'); function showSaved() { let c = document.createElement('p'); c.className = 'block confirm'; c.id = 'confirm_saved'; c.innerText = 'Enregistré'; | | | | | | 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 | (function () { g.style('scripts/web_editor.css'); function showSaved() { let c = document.createElement('p'); c.className = 'block confirm'; c.id = 'confirm_saved'; c.innerText = 'Enregistré'; c.style.opacity = 0; document.querySelector('#f_content').parentNode.appendChild(c); window.setTimeout(() => { c.style.opacity = 1; }, 100); window.setTimeout(() => { c.style.opacity = 0; }, 3000); window.setTimeout(() => { c.remove(); }, 3500); } g.script('scripts/lib/text_editor.min.js', function () { var t = new textEditor('f_content'); t.parent = t.textarea.parentNode; var config = { |
︙ | ︙ | |||
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 | if (config.fullscreen) { toggleFullscreen(); } var openPreview = function () { openIFrame(''); var form = document.createElement('form'); form.appendChild(t.textarea.cloneNode(true)); form.firstChild.value = t.textarea.value; let f = document.createElement('input'); f.type = 'hidden'; f.name = 'format'; f.value = config.format; form.appendChild(f); form.target = 'editorFrame'; form.action = config.preview_url; form.style.display = 'none'; form.method = 'post'; document.body.appendChild(form); form.submit(); return true; }; var openSyntaxHelp = function () { let url = config.format == 'markdown' ? 'markdown.html' : 'skriv.html'; url = g.admin_url + 'static/doc/' + url; g.openFrameDialog(url); }; var openFileInsert = function () { let args = new URLSearchParams(window.location.search); var uri = args.get('p'); | > > > > > > > > > > > > | > > > > > > > > > | 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 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | if (config.fullscreen) { toggleFullscreen(); } var openPreview = function () { var pos = t.textarea.scrollTop / t.textarea.scrollHeight; openIFrame(''); var form = document.createElement('form'); form.appendChild(t.textarea.cloneNode(true)); form.firstChild.value = t.textarea.value; let f = document.createElement('input'); f.type = 'hidden'; f.name = 'format'; f.value = config.format; form.appendChild(f); form.target = 'editorFrame'; form.action = config.preview_url; form.style.display = 'none'; form.method = 'post'; document.body.appendChild(form); t.iframe.style.opacity = .2; form.submit(); // Sync scrolling t.iframe.onload = () => { t.iframe.style.opacity = 1; var scroll = pos * t.iframe.contentWindow.document.body.scrollHeight; t.iframe.contentWindow.scrollTo(0, scroll); }; return true; }; var openSyntaxHelp = function () { let url = config.format == 'markdown' ? 'markdown.html' : 'skriv.html'; url = g.admin_url + 'static/doc/' + url; g.openFrameDialog(url); return true; }; var openFileInsert = function () { let args = new URLSearchParams(window.location.search); var uri = args.get('p'); g.openFrameDialog(g.admin_url + 'web/_attach.php?files&_dialog&p=' + uri); return true; }; var openImageInsert = function () { let args = new URLSearchParams(window.location.search); var uri = args.get('p'); g.openFrameDialog(g.admin_url + 'web/_attach.php?images&_dialog&p=' + uri); return true; }; window.te_insertFile = function (file) { var tag = '<<file|'+file+'>>'; t.insertAtPosition(t.getSelection().start, tag); |
︙ | ︙ | |||
266 267 268 269 270 271 272 | let createToolbar = () => { appendButton('title', "Titre", applyHeader ); appendButton('bold', 'Gras', applyBold ); appendButton('italic', "Italique", applyItalic ); appendButton('link', "Lien", insertURL); | < < < > | > > > > > > > > > > > < < < < < < | 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 | let createToolbar = () => { appendButton('title', "Titre", applyHeader ); appendButton('bold', 'Gras', applyBold ); appendButton('italic', "Italique", applyItalic ); appendButton('link', "Lien", insertURL); if (config.attachments) { appendButton('image', "🖻", openImageInsert, 'Insérer image'); appendButton('file', "📎", openFileInsert, 'Insérer fichier'); t.shortcuts.push({ctrl: true, shift: true, key: 'i', callback: openFileInsert}); } if (config.savebtn == 1) { appendButton('ext save save-label', 'Enregistrer', save, 'Enregistrer'); } else if (config.savebtn == 2) { appendButton('ext save', '⇑', save, 'Enregistrer sans fermer'); } appendButton('ext preview', '👁', openPreview, 'Prévisualiser'); appendButton('ext help', '❓', openSyntaxHelp, 'Aide sur la syntaxe'); if (!config.fullscreen) { appendButton('ext fullscreen', 'Plein écran', toggleFullscreen, 'Plein écran'); } appendButton('ext close', 'Retour à l\'édition', closeIFrame); t.parent.insertBefore(toolbar, t.parent.firstChild); } let toggleFormat = (format) => { |
︙ | ︙ |
Modified src/www/admin/static/scripts/web_encryption.js from [7d94d4ce81] to [e0da77cbde].
︙ | ︙ | |||
93 94 95 96 97 98 99 | // Links content = content.replace(/\[{2}([^\|\]\n]+?)\|([^\]\n]+?)\]{2}/g, linkTag); content = content.replace(/\[{2}(([^\]]+?))\]{2}/g, linkTag); content = content.replace(/<(((?:https?|mailto):[^>]+?))>/g, linkTag); content = content.replace(/\[([^\]]+?)\]\(([^\)]+?)\)/g, linkTag); // Extensions | | < | | > | | | | 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | // Links content = content.replace(/\[{2}([^\|\]\n]+?)\|([^\]\n]+?)\]{2}/g, linkTag); content = content.replace(/\[{2}(([^\]]+?))\]{2}/g, linkTag); content = content.replace(/<(((?:https?|mailto):[^>]+?))>/g, linkTag); content = content.replace(/\[([^\]]+?)\]\(([^\)]+?)\)/g, linkTag); // Extensions content = content.replace(/<<(\w+)([\| ]([^&]+))?>>/g, (match, name, separator, params) => { params = params.split('|'); if (name == 'image') { var src = params[0]; var align = params[1] || 'center'; var caption = 2 in params ? '<figcaption>' + params[2] + '</figcaption>' : ''; var size = align == 'center' ? '500px' : '200px'; return `<figure class="image img-${align}"><a href="${base_url + src}" class="internal-image" target="_image"><img src="${base_url + src}?${size}" alt="" /></a>${caption}</figure>`; } else if (name == 'file') { var src = params[0]; var ext = (a = src.lastIndexOf('.')) && a > 0 ? src.substr(a+1).toUpperCase() : ''; var caption = params[1] || src.replace(/\.[^\.]+$/, ''); return `<aside class="file" data-type="${ext}"><a href="${base_url + src}" class="internal-file"><b>${caption}</b> <small>${ext}</small></a></aside>`; } else { return match; } }); // nl2br |
︙ | ︙ | |||
247 248 249 250 251 252 253 | } if (!edit) { elm.style.display = 'block'; document.getElementById('web_encrypted_message').style.display = 'none'; base_url = elm.dataset.url.replace(/\/$/, '') + '/'; | > | > > > > | 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 | } if (!edit) { elm.style.display = 'block'; document.getElementById('web_encrypted_message').style.display = 'none'; base_url = elm.dataset.url.replace(/\/$/, '') + '/'; content = formatContent(content); elm.innerHTML = content; if (content.match(/<img/) && typeof window.enableImageGallery != 'undefined') { enableImageGallery(); } } else { elm.value = content; } }; |
︙ | ︙ |
Modified src/www/admin/static/scripts/web_gallery.js from [5fb6dc7088] to [ac9bac10c3].
︙ | ︙ | |||
18 19 20 21 22 23 24 25 26 27 28 29 30 31 | a.onclick = function (e) { e.preventDefault(); openImageBrowser(items, this.getAttribute('data-pos')); return false; }; } }; function openImageBrowser(items, pos) { div = document.createElement('div'); div.className = 'imageBrowser'; var fig = document.createElement('figure'); | > > | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | a.onclick = function (e) { e.preventDefault(); openImageBrowser(items, this.getAttribute('data-pos')); return false; }; } }; window.enableImageGallery = enableGallery; function openImageBrowser(items, pos) { div = document.createElement('div'); div.className = 'imageBrowser'; var fig = document.createElement('figure'); |
︙ | ︙ |
Modified src/www/admin/static/styles/web.css from [e5fbbda856] to [5fa3360726].
1 2 3 4 5 6 7 8 | .web-edit { display: grid; grid-template-columns: 1fr 30%; grid-template-rows: .1fr 1fr .1fr; grid-gap: 1em; height: 100vh; } | | | > > | 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 | .web-edit { display: grid; grid-template-columns: 1fr 30%; grid-template-rows: .1fr 1fr .1fr; grid-gap: 1em; height: 100vh; } .web-edit .editor textarea[disabled] { filter: blur(3px); } .web-edit-visual .editor { display: none; } .web-edit nav ul { border: none; } .web-edit .properties { display: flex; flex-direction: row; font-size: .9em; } .web-edit nav ul { display: flex; flex-direction: row; flex-wrap: nowrap; } .web-edit nav ul li a { border-radius: 0 0 .5em .5em; } .web-edit fieldset { margin: 0; } .web-edit .submit { grid-row: 3; grid-column: 2; text-align: right; } fieldset.header { grid-column: span 2; margin-top: 1em; } |
︙ | ︙ |
Modified src/www/admin/web/_attach.php from [a81a5ef39c] to [78e24e4bd5].
︙ | ︙ | |||
32 33 34 35 36 37 38 | }, $csrf_key); $form->runIf('upload', function () use ($page) { $new_file = File::uploadMultiple(Utils::dirname($page->file_path), 'file'); }, $csrf_key); | > > > > | > > > | > > | 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 | }, $csrf_key); $form->runIf('upload', function () use ($page) { $new_file = File::uploadMultiple(Utils::dirname($page->file_path), 'file'); }, $csrf_key); $files = null; $images = null; if (isset($_GET['files'])) { $files = $page->getAttachmentsGallery(true); } if (isset($_GET['images'])) { $images = $page->getImageGallery(true); } $max_size = Utils::getMaxUploadSize(); $tpl->assign(compact('page', 'files', 'images', 'max_size', 'csrf_key')); $tpl->assign('custom_js', ['web_files.js']); $tpl->assign('custom_css', ['!static/scripts/web_editor.css']); $tpl->display('web/_attach.tpl'); |