Remplacer simpleMDE par un éditeur maison #110
|
@ -2,9 +2,11 @@ from app import app
|
|||
from app.utils.filters.markdown import md
|
||||
from flask import request, abort
|
||||
from werkzeug.exceptions import BadRequestKeyError
|
||||
from app import csrf
|
||||
|
||||
class API():
|
||||
@app.route("/api/markdown", methods=["POST"])
|
||||
@csrf.exempt
|
||||
def api_markdown():
|
||||
try:
|
||||
markdown = request.get_json()['text']
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
.editor .btn-group {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-end;
|
||||
}
|
||||
.editor .btn-group .filler {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.editor .btn-group button {
|
||||
padding: 6px;
|
||||
background-color: var(--background);
|
||||
}
|
||||
.editor .btn-group button:hover {
|
||||
background: var(--background-hover);
|
||||
}
|
||||
.editor .btn-group button > svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
.editor .btn-group button > svg > path,
|
||||
.editor .btn-group button > svg > rect {
|
||||
fill: var(--icons);
|
||||
}
|
||||
.editor .btn-group button,
|
||||
.editor .btn-group .separator {
|
||||
margin: 0 8px 8px 0;
|
||||
height: 32px;
|
||||
position: relative;
|
||||
}
|
||||
.editor .btn-group > a {
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
.editor .btn-group .separator {
|
||||
display: inline-block;
|
||||
width: 0;
|
||||
border-left: 1px solid #d9d9d9;
|
||||
border-right: 1px solid #ffffff;
|
||||
color: transparent;
|
||||
text-indent: -10px;
|
||||
}
|
||||
.editor textarea {
|
||||
min-height: 15rem;
|
||||
}
|
||||
.editor #editor_content_preview {
|
||||
padding: 5px;
|
||||
margin-top: 10px;
|
||||
border: var(--border);
|
||||
background-color: rgba(0,0,0,0.2);
|
||||
}
|
||||
.editor .modal {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
width: auto;
|
||||
min-width: 30vw;
|
||||
text-align: left;
|
||||
right: inherit;
|
||||
background: var(--background-hover);
|
||||
border: var(--border);
|
||||
color: var(--text);
|
||||
padding: .2rem;
|
||||
top: 3.2rem;
|
||||
z-index: 100;
|
||||
list-style-position: initial;
|
||||
list-style-type: none;
|
||||
}
|
||||
.editor .modal > div {
|
||||
margin: 0.8rem;
|
||||
margin-top: 0.4rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.editor .modal > div label {
|
||||
margin-top: 0.4rem;
|
||||
}
|
||||
@media screen and (max-width:849px) {
|
||||
.editor .modal {
|
||||
width: 80vw;
|
||||
position: fixed;
|
||||
left: 50vw;
|
||||
transform: translateX(-50%);
|
||||
top: 50vh;
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@
|
|||
.form input[type='date'],
|
||||
.form input[type='password'],
|
||||
.form input[type='search'],
|
||||
.form input[type='url'],
|
||||
.form textarea,
|
||||
.form select {
|
||||
display: block;
|
||||
|
@ -38,6 +39,7 @@
|
|||
.form input[type='date']:focus,
|
||||
.form input[type='password']:focus,
|
||||
.form input[type='search']:focus,
|
||||
.form input[type='url']:focus,
|
||||
.form textarea:focus,
|
||||
.form select:focus {
|
||||
border-color: var(--border-focused);
|
||||
|
@ -48,6 +50,7 @@
|
|||
.form input[type='date']:focus-within,
|
||||
.form input[type='password']:focus-within,
|
||||
.form input[type='search']:focus-within,
|
||||
.form input[type='url']:focus-within,
|
||||
.form textarea:focus-within,
|
||||
.form select:focus-within {
|
||||
outline: none;
|
||||
|
@ -134,4 +137,4 @@ form .dynamic-tag-selector .tags-selected .tag {
|
|||
background: rgba(0,0,0,.05);
|
||||
padding: 1px 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,6 +78,7 @@ section h2 {
|
|||
color: var(--text-light);
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
button,
|
||||
.button,
|
||||
input[type="button"],
|
||||
input[type="submit"] {
|
||||
|
@ -88,9 +89,11 @@ input[type="submit"] {
|
|||
font-weight: 400;
|
||||
border: 0;
|
||||
}
|
||||
button:hover,
|
||||
.button:hover,
|
||||
input[type="button"]:hover,
|
||||
input[type="submit"]:hover,
|
||||
button:focus,
|
||||
.button:focus,
|
||||
input[type="button"]:focus,
|
||||
input[type="submit"]:focus {
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
/* SimpleMDE overwrite that allows us to customize from themes */
|
||||
|
||||
div.editor-toolbar {
|
||||
border-color: var(--border);
|
||||
}
|
||||
|
||||
div.editor-toolbar > a {
|
||||
color: var(--text) !important;
|
||||
}
|
||||
div.editor-toolbar > a.active,
|
||||
div.editor-toolbar > a:hover {
|
||||
background: var(--background-light);
|
||||
border-color: var(--background-light);
|
||||
}
|
||||
|
||||
div.editor-toolbar > i.separator {
|
||||
border-right-color: transparent;
|
||||
border-left-color: var(--separator);
|
||||
}
|
||||
|
||||
div.editor-toolbar.disabled-for-preview a:not(.no-disable) {
|
||||
background: none;
|
||||
color: var(--text-disabled) !important;
|
||||
}
|
||||
div.editor-toolbar.disabled-for-preview > i.separator {
|
||||
border-left-color: var(--text-disabled);
|
||||
}
|
||||
|
||||
div.CodeMirror,
|
||||
div.editor-preview {
|
||||
background: var(--background);
|
||||
color: var(--text);
|
||||
border-color: var(--border);
|
||||
}
|
||||
div.editor-preview {
|
||||
background: var(--background-preview);
|
||||
}
|
||||
|
||||
div.editor-preview table th,
|
||||
div.editor-preview-side table th,
|
||||
div.editor-preview table td,
|
||||
div.editor-preview-side table td {
|
||||
border: inherit;
|
||||
padding: inherit;
|
||||
}
|
||||
|
||||
div.editor-preview table.codehilitetable pre,
|
||||
div.editor-preview-side table.codehilitetable pre {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
div.CodeMirror .CodeMirror-selected,
|
||||
div.CodeMirror .CodeMirror-selectedtext {
|
||||
background: var(--background-light);
|
||||
}
|
||||
div.CodeMirror .CodeMirror-focused .CodeMirror-selected,
|
||||
div.CodeMirror .CodeMirror-focused .CodeMirror-selectedtext,
|
||||
div.CodeMirror .CodeMirror-line::selection,
|
||||
div.CodeMirror .CodeMirror-line > span::selection,
|
||||
div.CodeMirror .CodeMirror-line > span > span::selection {
|
||||
background: var(--background-light);
|
||||
}
|
||||
div.CodeMirror .CodeMirror-line::-moz-selection,
|
||||
div.CodeMirror .CodeMirror-line > span::-moz-selection,
|
||||
div.CodeMirror .CodeMirror-line > span > span::-moz-selection {
|
||||
background: var(--background-light);
|
||||
}
|
||||
|
||||
div.CodeMirror-cursor {
|
||||
border-color: var(--text);
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -43,10 +43,12 @@
|
|||
}
|
||||
|
||||
.editor button {
|
||||
--background: #ffffff;
|
||||
--text: #000000;
|
||||
--border: 1px solid rgba(0, 0, 0, 0);
|
||||
--border-focused: 1px solid rgba(0, 0, 0, .5);
|
||||
--background: #1d2326;
|
||||
--text: #ffffff;
|
||||
--background-hover: #262c2f;
|
||||
}
|
||||
.editor svg {
|
||||
--icons: #eeeeee;
|
||||
}
|
||||
|
||||
#light-menu {
|
||||
|
|
|
@ -38,10 +38,9 @@
|
|||
}
|
||||
|
||||
.editor button {
|
||||
--background: #ffffff;
|
||||
--background: #eee;
|
||||
--text: #030303;
|
||||
--border: 1px solid rgba(0, 0, 0, 0);
|
||||
--border-focused: 1px solid rgba(0, 0, 0, .5);
|
||||
--background-hover: #ddd;
|
||||
}
|
||||
|
||||
#light-menu {
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
/* Some colors, variables etc. to be used as theme */
|
||||
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--text: #000000;
|
||||
--text-light: #101010;
|
||||
--background: #fff;
|
||||
--text: #000;
|
||||
--text-light: #111;
|
||||
|
||||
--links: #c61a1a;
|
||||
|
||||
--ok: #149641;
|
||||
--ok-text: #ffffff;
|
||||
--ok-text: #fff;
|
||||
--ok-active: #0f7331;
|
||||
|
||||
--warn: #f59f25;
|
||||
--warn-text: #ffffff;
|
||||
--warn-text: #fff;
|
||||
--warn-active: #ea9720;
|
||||
|
||||
--error: #d23a2f;
|
||||
--error-text: #ffffff;
|
||||
--error-text: #fff;
|
||||
--error-active: #b32a20;
|
||||
|
||||
--info: #2e7aec;
|
||||
--info-text: #ffffff;
|
||||
--info-text: #fff;
|
||||
--info-active: #215ab0;
|
||||
|
||||
--hr-border: 1px solid #d8d8d8;
|
||||
|
@ -33,23 +33,22 @@ table tr:nth-child(odd) {
|
|||
--background: rgba(0, 0, 0, .1);
|
||||
}
|
||||
table th {
|
||||
--background: #e0e0e0;
|
||||
--border: #d0d0d0;
|
||||
--background: #eee;
|
||||
--border: #ddd;
|
||||
}
|
||||
|
||||
.form {
|
||||
--background: #ffffff;
|
||||
--text: #000000;
|
||||
--background: #fff;
|
||||
--text: #000;
|
||||
--border: 1px solid #c8c8c8;
|
||||
--border-focused: #7cade0;
|
||||
--shadow-focused: rgba(87, 143, 228, 0.5);
|
||||
}
|
||||
|
||||
.editor button {
|
||||
--background: #ffffff;
|
||||
--text: #000000;
|
||||
--border: 1px solid rgba(0, 0, 0, 0);
|
||||
--border-focused: 1px solid rgba(0, 0, 0, .5);
|
||||
--background: #eee;
|
||||
--text: #000;
|
||||
--background-hover: #ddd;
|
||||
}
|
||||
|
||||
#light-menu {
|
||||
|
@ -82,13 +81,13 @@ header {
|
|||
|
||||
footer {
|
||||
--background: #ffffff;
|
||||
--text: #a0a0a0;
|
||||
--border: #d0d0d0;
|
||||
--text: #aaa;
|
||||
--border: #ddd;
|
||||
}
|
||||
|
||||
.flash {
|
||||
--background: #ffffff;
|
||||
--text: #212121;
|
||||
--background: #fff;
|
||||
--text: #222;
|
||||
--shadow: 0 1px 12px rgba(0, 0, 0, 0.3);
|
||||
|
||||
/* Uncomment to inherit :root values
|
||||
|
@ -98,31 +97,27 @@ footer {
|
|||
--info: #2e7aec; */
|
||||
|
||||
--btn-bg: rgba(0, 0, 0, 0);
|
||||
--btn-text: #000000;
|
||||
--btn-text: #000;
|
||||
--btn-bg-active: rgba(0, 0, 0, .15);
|
||||
}
|
||||
|
||||
.profile-xp {
|
||||
--background: #e0e0e0;
|
||||
--border: 1px solid #c0c0c0;
|
||||
--background-xp: #f85555;
|
||||
--background-xp-100: #d03333;
|
||||
--border-xp: 1px solid #d03333;
|
||||
--background: #eee;
|
||||
--border: 1px solid #ccc;
|
||||
--background-xp: #f55;
|
||||
--background-xp-100: #d33;
|
||||
--border-xp: 1px solid #d33;
|
||||
}
|
||||
|
||||
.context-menu {
|
||||
--background: #ffffff;
|
||||
--shadow: 0 0 12px -9px #000000;
|
||||
--border: #d0d0d0;
|
||||
--background-light: #f0f0f0;
|
||||
--background: #fff;
|
||||
--shadow: 0 0 12px -9px #000;
|
||||
--border: #ddd;
|
||||
--background-light: #fff;
|
||||
}
|
||||
|
||||
div.editor-toolbar, div.CodeMirror {
|
||||
--border: #c0c0c0;
|
||||
--background-light: #d9d9d9;
|
||||
--background-preview: #f4f4f6;
|
||||
--separator: #a0a0a0;
|
||||
--text-disabled: #c0c0c0;
|
||||
.editor svg {
|
||||
--icons: #000;
|
||||
}
|
||||
|
||||
.dl-button {
|
||||
|
@ -140,7 +135,7 @@ div.editor-toolbar, div.CodeMirror {
|
|||
|
||||
/* Extra style on top of the Pygments style */
|
||||
table.codehilitetable td.linenos {
|
||||
color: #808080;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.tag {
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
@import "vars";
|
||||
|
||||
.editor {
|
||||
.btn-group {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-end;
|
||||
|
||||
.filler {
|
||||
flex-grow: 1;
|
||||
}
|
||||
button {
|
||||
/* This centers the 20x20 SVG in the button */
|
||||
padding: 6px;
|
||||
background-color: var(--background);
|
||||
|
||||
&:hover {
|
||||
background: var(--background-hover);
|
||||
}
|
||||
|
||||
& > svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
||||
& > path, & > rect {
|
||||
fill: var(--icons);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button, .separator {
|
||||
margin: 0 8px 8px 0;
|
||||
height: 32px;
|
||||
|
||||
position: relative;
|
||||
}
|
||||
& > a {
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
// From gitea
|
||||
.separator {
|
||||
display: inline-block;
|
||||
width: 0;
|
||||
border-left: 1px solid #d9d9d9;
|
||||
border-right: 1px solid #fff;
|
||||
color: transparent;
|
||||
text-indent: -10px;
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 15rem;
|
||||
}
|
||||
|
||||
#editor_content_preview {
|
||||
padding: 5px;
|
||||
margin-top: 10px;
|
||||
border: var(--border);
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.modal {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
width: auto;
|
||||
min-width: 30vw;
|
||||
text-align: left;
|
||||
right: inherit;
|
||||
|
||||
@media screen and (max-width: @tiny) {
|
||||
width: 80vw;
|
||||
position: fixed;
|
||||
left: 50vw;
|
||||
transform: translateX(-50%);
|
||||
top: 50vh;
|
||||
}
|
||||
|
||||
background: var(--background-hover);
|
||||
border: var(--border);
|
||||
color: var(--text);
|
||||
padding: .2rem;
|
||||
top: 3.2rem;
|
||||
z-index: 100;
|
||||
list-style-position: initial;
|
||||
list-style-type: none;
|
||||
|
||||
& > div {
|
||||
margin: 0.8rem;
|
||||
margin-top: 0.4rem;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
label {
|
||||
margin-top: 0.4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -32,6 +32,7 @@
|
|||
input[type='date'],
|
||||
input[type='password'],
|
||||
input[type='search'],
|
||||
input[type='url'],
|
||||
textarea,
|
||||
select {
|
||||
display: block;
|
||||
|
|
|
@ -80,7 +80,7 @@ section {
|
|||
}
|
||||
|
||||
/* Buttons */
|
||||
.button, input[type="button"], input[type="submit"] {
|
||||
button, .button, input[type="button"], input[type="submit"] {
|
||||
padding: 6px 10px; border-radius: 2px;
|
||||
cursor: pointer;
|
||||
font-family: 'DejaVu Sans', sans-serif; font-weight: 400;
|
||||
|
|
|
@ -1,113 +1,353 @@
|
|||
/* Add callbacks on text formatting buttons */
|
||||
|
||||
function edit(e, type) {
|
||||
function inline(type, str, repeat, insert) {
|
||||
// Characters used to format inline blocs
|
||||
// repeat: if true, add one more char to the longest suite found
|
||||
// insert: insert <insert> between char and str (before and after)
|
||||
var chars = {
|
||||
'bold': '*',
|
||||
'italic': '/',
|
||||
'underline': '_',
|
||||
'strikethrough': '~',
|
||||
'inline-code': '`',
|
||||
'h1': '===',
|
||||
'h2': '---',
|
||||
'h3': '...',
|
||||
}
|
||||
/* Locate the editor associated to an edition event.
|
||||
event: Global event emitted by one of the editor buttons
|
||||
Returns [the div.editor, the button, the textarea] */
|
||||
function editor_event_source(event)
|
||||
{
|
||||
let button = undefined;
|
||||
let editor = undefined;
|
||||
|
||||
if (repeat) {
|
||||
// Detect longest suite of similar chars
|
||||
var n = 1; var tmp = 1;
|
||||
for(var i = 0; i < str.length; i++) {
|
||||
if(str[i] == chars[type]) tmp++;
|
||||
else tmp = 1;
|
||||
n = (tmp > n) ? tmp : n;
|
||||
}
|
||||
return chars[type].repeat(n) + insert + str + insert + chars[type].repeat(n);
|
||||
}
|
||||
/* Grab the button and the parent editor block. The onclick event itself
|
||||
usually reports the SVG in the button as the source */
|
||||
let node = event.target || event.srcElement;
|
||||
while(node != document.body) {
|
||||
if(node.tagName == "BUTTON" && !button) {
|
||||
button = node;
|
||||
}
|
||||
if(node.classList.contains("editor") && !editor) {
|
||||
editor = node;
|
||||
break;
|
||||
}
|
||||
node = node.parentNode;
|
||||
}
|
||||
if(!button || !editor) return;
|
||||
|
||||
return chars[type] + insert + str + insert + chars[type];
|
||||
}
|
||||
|
||||
function list(type, str) {
|
||||
switch(type) {
|
||||
case 'list-bulleted':
|
||||
return '* ' + str.replaceAll('\n', '\n* ');
|
||||
break;
|
||||
case 'list-numbered':
|
||||
return '1. ' + str;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var ta = e.parentNode.parentNode.querySelector('textarea');
|
||||
var start = ta.selectionStart;
|
||||
var end = ta.selectionEnd;
|
||||
|
||||
switch(type) {
|
||||
case 'bold':
|
||||
case 'italic':
|
||||
case 'underline':
|
||||
case 'strikethrough':
|
||||
case 'inline-code':
|
||||
ta.value = ta.value.substring(0, start)
|
||||
+ inline(type, ta.value.substring(start, end), true, '')
|
||||
+ ta.value.substring(end);
|
||||
break;
|
||||
case 'h1':
|
||||
case 'h2':
|
||||
case 'h3':
|
||||
ta.value = ta.value.substring(0, start)
|
||||
+ inline(type, ta.value.substring(start, end), false, ' ')
|
||||
+ ta.value.substring(end);
|
||||
break;
|
||||
case 'list-bulleted':
|
||||
case 'list-numbered':
|
||||
ta.value = ta.value.substring(0, start)
|
||||
+ list(type, ta.value.substring(start, end))
|
||||
+ ta.value.substring(end);
|
||||
break;
|
||||
}
|
||||
const ta = editor.querySelector("textarea");
|
||||
return [editor, button, ta];
|
||||
}
|
||||
|
||||
function pre(type, str, multiline) {
|
||||
/* Replace the range [start:end) with the new contents, and returns the new
|
||||
interval [start:end) (ie. the range where the contents are now located). */
|
||||
function editor_replace_range(textarea, start, end, contents)
|
||||
{
|
||||
ta.value = ta.value.substring(0, start)
|
||||
+ contents
|
||||
+ ta.value.substring(end);
|
||||
|
||||
return [start, start + contents.length];
|
||||
}
|
||||
|
||||
/* Event handler that inserts specified tokens around the selection.
|
||||
after token is the same as before if not specified */
|
||||
function editor_insert_around(event, before="", after=null)
|
||||
{
|
||||
const [editor, button, ta] = editor_event_source(event);
|
||||
ta.focus();
|
||||
let indexStart = ta.selectionStart;
|
||||
let indexEnd = ta.selectionEnd;
|
||||
|
||||
if(after === null) {
|
||||
after = before;
|
||||
}
|
||||
|
||||
let [start, end] = editor_replace_range(ta, indexStart, indexEnd,
|
||||
before + ta.value.substring(indexStart, indexEnd) + after);
|
||||
|
||||
/* Restore selection */
|
||||
if(indexStart != indexEnd) {
|
||||
ta.selectionStart = start;
|
||||
ta.selectionEnd = end;
|
||||
}
|
||||
else {
|
||||
ta.selectionStart = ta.selectionEnd = start + before.length;
|
||||
}
|
||||
|
||||
preview();
|
||||
}
|
||||
|
||||
/* Event handler that modifies each line within the selection through a
|
||||
generic function. */
|
||||
function editor_act_on_lines(event, fn)
|
||||
{
|
||||
const [editor, button, ta] = editor_event_source(event);
|
||||
ta.focus();
|
||||
let indexStart = ta.selectionStart;
|
||||
let indexEnd = ta.selectionEnd;
|
||||
|
||||
let firstLineIndex = ta.value.substring(0, indexStart).lastIndexOf('\n');
|
||||
if(firstLineIndex < 0)
|
||||
firstLineIndex = 0;
|
||||
else
|
||||
firstLineIndex += 1;
|
||||
|
||||
let lastLineIndex = ta.value.substring(indexEnd).indexOf('\n');
|
||||
if(lastLineIndex < 0)
|
||||
lastLineIndex = ta.value.length;
|
||||
else
|
||||
lastLineIndex += indexEnd;
|
||||
|
||||
let lines = ta.value.substring(firstLineIndex, lastLineIndex).split('\n');
|
||||
|
||||
for(let i = 0; i < lines.length; i++)
|
||||
lines[i] = fn(lines[i]);
|
||||
|
||||
let [start, end] = editor_replace_range(ta, firstLineIndex, lastLineIndex,
|
||||
lines.join('\n'));
|
||||
|
||||
ta.selectionStart = start;
|
||||
ta.selectionEnd = end;
|
||||
|
||||
preview();
|
||||
}
|
||||
|
||||
function editor_clear_modals(event, close = true)
|
||||
{
|
||||
// Stop the propagation of the event
|
||||
event.stopPropagation()
|
||||
|
||||
// Reset all modal inputs
|
||||
document.getElementById('media-alt-input').value = '';
|
||||
document.getElementById('media-link-input').value = '';
|
||||
document.getElementById('link-desc-input').value = '';
|
||||
document.getElementById('link-desc-input').value = '';
|
||||
const media_type = document.getElementsByName("media-type");
|
||||
for(i = 0; i < media_type.length; i++) {
|
||||
media_type[i].checked = false;
|
||||
}
|
||||
|
||||
// Close all modal if requested
|
||||
if (!close) { return }
|
||||
const modals = document.getElementsByClassName('modal');
|
||||
for (const i of modals) {i.style.display = 'none'};
|
||||
}
|
||||
|
||||
|
||||
function bold(e) {
|
||||
var ta = e.parentNode.parentNode.querySelector('textarea');
|
||||
var indexStart = ta.selectionStart;
|
||||
var indexEnd = ta.selectionEnd;
|
||||
var txt = ta.value.substring(indexStart, indexEnd);
|
||||
ta.value += '\n' + inline('bold', txt);
|
||||
/* End-user functions */
|
||||
function editor_inline(event, type)
|
||||
{
|
||||
tokens = {
|
||||
bold: "**",
|
||||
italic: "*",
|
||||
underline: "__",
|
||||
strike: "~~",
|
||||
inlinecode: "`",
|
||||
};
|
||||
|
||||
if(type in tokens){
|
||||
editor_insert_around(event, tokens[type]);
|
||||
}
|
||||
|
||||
preview();
|
||||
}
|
||||
|
||||
function editor_display_link_modal(event) {
|
||||
const [editor, button, ta] = editor_event_source(event);
|
||||
let indexStart = ta.selectionStart;
|
||||
let indexEnd = ta.selectionEnd;
|
||||
let selection = ta.value.substring(indexStart, indexEnd);
|
||||
|
||||
// Assuming it's a link
|
||||
if(selection.match(/^https?:\/\/\S+/)) {
|
||||
event.currentTarget.querySelector("#link-link-input").value = selection;
|
||||
}
|
||||
// Or text
|
||||
else if(selection != "") {
|
||||
event.currentTarget.querySelector("#link-desc-input").value = selection;
|
||||
}
|
||||
// Or nothing selected
|
||||
else {
|
||||
event.currentTarget.querySelector("#link-desc-input").value = "";
|
||||
event.currentTarget.querySelector("#link-link-input").value = "";
|
||||
}
|
||||
|
||||
event.currentTarget.children[1].style = {'display': 'block'};
|
||||
}
|
||||
|
||||
function editor_insert_link(event, link_id, text_id, media = false)
|
||||
{
|
||||
const [editor, button, ta] = editor_event_source(event);
|
||||
ta.focus();
|
||||
let indexStart = ta.selectionStart;
|
||||
let indexEnd = ta.selectionEnd;
|
||||
|
||||
const link = document.getElementById(link_id).value;
|
||||
const text = document.getElementById(text_id).value;
|
||||
let media_type = "";
|
||||
|
||||
const media_selector = document.getElementsByName("media-type");
|
||||
for(i = 0; i < media_selector.length; i++) {
|
||||
if(media_selector[i].checked) {
|
||||
media_type = `{type=${media_selector[i].value}}`;
|
||||
}
|
||||
}
|
||||
|
||||
editor_clear_modals(event);
|
||||
|
||||
let [start, end] = editor_replace_range(ta, indexStart, indexEnd,
|
||||
`[${text.length === 0 ? ta.value.substring(indexStart, indexEnd) : text}]${media ? "!" : ""}(${link})${media ? media_type : ""}`);
|
||||
|
||||
/* Restore selection */
|
||||
if(indexStart != indexEnd) {
|
||||
ta.selectionStart = start;
|
||||
ta.selectionEnd = end;
|
||||
}
|
||||
else {
|
||||
ta.selectionStart = ta.selectionEnd = start + 1;
|
||||
}
|
||||
|
||||
preview();
|
||||
}
|
||||
|
||||
function editor_title(event, level, diff)
|
||||
{
|
||||
editor_act_on_lines(event, function(line) {
|
||||
/* Strip all the initial # (and count them) */
|
||||
let count = 0;
|
||||
while(count < line.length && line[count] == '#') count++;
|
||||
|
||||
let contents_index = count;
|
||||
if(count < line.length && line[count] == ' ') contents_index++;
|
||||
let contents = line.slice(contents_index);
|
||||
|
||||
if(level > 0 || count == 1 && diff == -1) {
|
||||
/* Remove the title if the corresponding level is re-requested */
|
||||
if(count == level || count == 1 && diff == -1)
|
||||
return contents;
|
||||
/* Otherwise, add it */
|
||||
else
|
||||
return '#'.repeat(level) + ' ' + contents;
|
||||
}
|
||||
else if(count > 0) {
|
||||
/* Apply the difference */
|
||||
let new_level = Math.max(1, Math.min(6, count + diff));
|
||||
return '#'.repeat(new_level) + ' ' + contents;
|
||||
}
|
||||
return line;
|
||||
});
|
||||
}
|
||||
|
||||
function editor_quote(event)
|
||||
{
|
||||
editor_act_on_lines(event, function(line) {
|
||||
/* Strip all the initial > (and count them) */
|
||||
let count = 0;
|
||||
while(count < line.length && line[count] == '>') count++;
|
||||
|
||||
let contents_index = count;
|
||||
if(count < line.length && line[count] == ' ') contents_index++;
|
||||
let contents = line.slice(contents_index);
|
||||
|
||||
/* Apply the difference */
|
||||
return '>'.repeat(count + 1) + ' ' + contents;
|
||||
});
|
||||
}
|
||||
|
||||
function editor_bullet_list(event)
|
||||
{
|
||||
editor_act_on_lines(event, function(line) {
|
||||
let ident_match = line.match(/^[\t]+/m) ?? [''];
|
||||
let ident = ident_match[0];
|
||||
let count = ident.length;
|
||||
|
||||
const contents = line.slice(count);
|
||||
if((count < line.length || count == 0) && line[count] != '-') return '- ' + contents;
|
||||
|
||||
return ident + "\t" + contents;
|
||||
});
|
||||
}
|
||||
|
||||
function editor_numbered_list(event)
|
||||
{
|
||||
editor_act_on_lines(event, function(line) {
|
||||
let ident_match = line.match(/^[\t]+/m) ?? [''];
|
||||
let ident = ident_match[0];
|
||||
let count = ident.length;
|
||||
|
||||
const contents = line.slice(count);
|
||||
if((count < line.length || count == 0) && isNaN(line[count])) return '1. ' + contents;
|
||||
|
||||
return ident + "\t" + contents;
|
||||
});
|
||||
}
|
||||
|
||||
function editor_table(event) {
|
||||
let table = `| Column 1 | Column 2 | Column 3 |
|
||||
| -------- | -------- | -------- |
|
||||
| Text | Text | Text |`;
|
||||
|
||||
editor_insert_around(event, "", table);
|
||||
}
|
||||
|
||||
function editor_separator(event) {
|
||||
editor_insert_around(event, "", "\n---\n");
|
||||
}
|
||||
|
||||
|
||||
// Tab insert some spaces
|
||||
// Ctrl+Enter send the form
|
||||
previewTimeout = null;
|
||||
ta = document.querySelector(".editor textarea");
|
||||
ta.addEventListener('keydown', function(e) {
|
||||
var keyCode = e.keyCode || e.which;
|
||||
if (keyCode == 9) {
|
||||
e.preventDefault();
|
||||
// Tab insert some spaces
|
||||
let keyCode = e.keyCode || e.which;
|
||||
if (keyCode == 9) {
|
||||
// TODO Add one tab to selected text without replacing it
|
||||
e.preventDefault();
|
||||
|
||||
let start = e.target.selectionStart;
|
||||
let end = e.target.selectionEnd;
|
||||
// set textarea value to: text before caret + tab + text after caret
|
||||
e.target.value = e.target.value.substring(0, start) + "\t" + e.target.value.substring(end);
|
||||
e.target.selectionEnd = start + 1;
|
||||
}
|
||||
|
||||
// TODO
|
||||
// Ctrl+B adds bold
|
||||
// Ctrl+I adds italic
|
||||
// Ctrl+U adds underline
|
||||
|
||||
var start = e.target.selectionStart;
|
||||
var end = e.target.selectionEnd;
|
||||
// set textarea value to: text before caret + tab + text after caret
|
||||
e.target.value = e.target.value.substring(0, start) + "\t" + e.target.value.substring(end);
|
||||
e.target.selectionEnd = start + 1;
|
||||
}
|
||||
if (e.ctrlKey && keyCode == 13) {
|
||||
var e = e.target;
|
||||
while(! (e instanceof HTMLFormElement)) {
|
||||
e = e.parentNode;
|
||||
}
|
||||
try {
|
||||
e.submit();
|
||||
} catch(exception) {
|
||||
e.submit.click();
|
||||
}
|
||||
}
|
||||
// Ctrl+Enter send the form
|
||||
if (e.ctrlKey && keyCode == 13) {
|
||||
let t = e.target;
|
||||
while(! (t instanceof HTMLFormElement)) {
|
||||
t = t.parentNode;
|
||||
}
|
||||
try {
|
||||
t.submit();
|
||||
} catch(exception) {
|
||||
t.submit.click();
|
||||
}
|
||||
}
|
||||
|
||||
// Set a timeout for refreshing the preview
|
||||
if (previewTimeout != null) {
|
||||
clearTimeout(previewTimeout);
|
||||
}
|
||||
previewTimeout = setTimeout(preview, 3000);
|
||||
});
|
||||
|
||||
|
||||
function preview() {
|
||||
const previewArea = document.querySelector("#editor_content_preview");
|
||||
|
||||
const textarea = document.querySelector(".editor textarea");
|
||||
const payload = {text: ta.value};
|
||||
|
||||
const headers = new Headers();
|
||||
headers.append("Content-Type", "application/json");
|
||||
|
||||
const params = {
|
||||
method: "POST",
|
||||
body: JSON.stringify(payload),
|
||||
headers
|
||||
};
|
||||
|
||||
fetch("/api/markdown", params).then(
|
||||
(response) => {
|
||||
response.text().then(
|
||||
(text) => {
|
||||
previewArea.innerHTML = text;
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
function entropy(password) {
|
||||
var chars = [
|
||||
let chars = [
|
||||
"abcdefghijklmnopqrstuvwxyz",
|
||||
"ABCDFEGHIJKLMNOPQRSTUVWXYZ",
|
||||
"0123456789",
|
||||
|
@ -19,9 +19,9 @@ function entropy(password) {
|
|||
}
|
||||
|
||||
function update_entropy(ev) {
|
||||
var i = document.querySelector(".entropy").previousElementSibling;
|
||||
var p = document.querySelector(".entropy");
|
||||
var e = entropy(i.value);
|
||||
let i = document.querySelector(".entropy").previousElementSibling;
|
||||
let p = document.querySelector(".entropy");
|
||||
let e = entropy(i.value);
|
||||
|
||||
p.classList.remove('low');
|
||||
p.classList.remove('medium');
|
||||
|
|
|
@ -8,7 +8,7 @@ const patterns = [
|
|||
|
||||
function* lex(str) {
|
||||
while(str = str.trim()) {
|
||||
var t = T.ERR, best = undefined;
|
||||
let t = T.ERR, best = undefined;
|
||||
|
||||
for(const i in patterns) {
|
||||
const m = str.match(patterns[i]);
|
||||
|
@ -86,7 +86,7 @@ class Parser {
|
|||
return e;
|
||||
}
|
||||
|
||||
var e = {
|
||||
let e = {
|
||||
type: "Atom",
|
||||
field: this.expect(T.NAME),
|
||||
op: this.expect(T.COMP),
|
||||
|
@ -124,8 +124,8 @@ function filter_update(input) {
|
|||
const th = t.querySelectorAll("tr:first-child > th");
|
||||
|
||||
/* Generate the names of fields from the header */
|
||||
var fields = {};
|
||||
for(var i = 0; i < th.length; i++) {
|
||||
let fields = {};
|
||||
for(let i = 0; i < th.length; i++) {
|
||||
const name = th[i].dataset.filter;
|
||||
if(name) fields[name] = i;
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
function setCookie(name, value) {
|
||||
var end = new Date();
|
||||
let end = new Date();
|
||||
end.setTime( end.getTime() + 3600 * 1000 );
|
||||
var str=name+"="+escape(value)+"; expires="+end.toGMTString()+"; path=/; Secure; SameSite=lax";
|
||||
let str=name+"="+escape(value)+"; expires="+end.toGMTString()+"; path=/; Secure; SameSite=lax";
|
||||
document.cookie = str;
|
||||
}
|
||||
function getCookie(name) {
|
||||
var debut = document.cookie.indexOf(name);
|
||||
let debut = document.cookie.indexOf(name);
|
||||
if( debut == -1 ) return null;
|
||||
var end = document.cookie.indexOf( ";", debut+name.length+1 );
|
||||
let end = document.cookie.indexOf( ";", debut+name.length+1 );
|
||||
if( end == -1 ) end = document.cookie.length;
|
||||
return unescape( document.cookie.substring( debut+name.length+1, end ) );
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,11 +1,11 @@
|
|||
/* Smartphone patch for menu */
|
||||
/* It don't work if links haven't any href attribute */
|
||||
|
||||
var w = Math.max(document.documentElement.clientWidth, window.innerWidth || 0)
|
||||
let w = Math.max(document.documentElement.clientWidth, window.innerWidth || 0)
|
||||
|
||||
if(w < 700) {
|
||||
var buttons = document.getElementById('light-menu').getElementsByTagName('li');
|
||||
for(var i = 0; i < buttons.length; i++) {
|
||||
let buttons = document.getElementById('light-menu').getElementsByTagName('li');
|
||||
for(let i = 0; i < buttons.length; i++) {
|
||||
buttons[i].getElementsByTagName('a')[0].setAttribute('href', '#');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
/* Trigger actions for the menu */
|
||||
|
||||
/* Initialization */
|
||||
var b = document.querySelectorAll('#light-menu a');
|
||||
for(var i = 1; i < b.length; i++) {
|
||||
let b = document.querySelectorAll('#light-menu a');
|
||||
for(let i = 1; i < b.length; i++) {
|
||||
b[i].setAttribute('onfocus', "this.setAttribute('f', 'true');");
|
||||
b[i].setAttribute('onblur', "this.setAttribute('f', 'false');");
|
||||
b[i].removeAttribute('href');
|
||||
}
|
||||
|
||||
var trigger_menu = function(active) {
|
||||
var display = function(element) {
|
||||
let trigger_menu = function(active) {
|
||||
let display = function(element) {
|
||||
element.classList.add('opened');
|
||||
}
|
||||
var hide = function(element) {
|
||||
let hide = function(element) {
|
||||
element.classList.remove('opened');
|
||||
}
|
||||
|
||||
var menu = document.querySelector('#menu');
|
||||
var buttons = document.querySelectorAll('#light-menu li');
|
||||
var menus = document.querySelectorAll('#menu > div');
|
||||
let menu = document.querySelector('#menu');
|
||||
let buttons = document.querySelectorAll('#light-menu li');
|
||||
let menus = document.querySelectorAll('#menu > div');
|
||||
|
||||
if(active == -1 || buttons[active].classList.contains('opened')) {
|
||||
hide(menu);
|
||||
|
@ -39,12 +39,12 @@ var trigger_menu = function(active) {
|
|||
}
|
||||
}
|
||||
|
||||
var mouse_trigger = function(event) {
|
||||
var menu = document.querySelector('#menu');
|
||||
var buttons = document.querySelectorAll('#light-menu li');
|
||||
let mouse_trigger = function(event) {
|
||||
let menu = document.querySelector('#menu');
|
||||
let buttons = document.querySelectorAll('#light-menu li');
|
||||
|
||||
if(!menu.contains(event.target)) {
|
||||
var active = -1;
|
||||
let active = -1;
|
||||
|
||||
for(i = 0; i < buttons.length; i++) {
|
||||
if(buttons[i].contains(event.target))
|
||||
|
@ -56,12 +56,12 @@ var mouse_trigger = function(event) {
|
|||
}
|
||||
}
|
||||
|
||||
var keyboard_trigger = function(event) {
|
||||
var menu = document.getElementById('menu');
|
||||
var buttons = document.querySelectorAll('#light-menu li');
|
||||
let keyboard_trigger = function(event) {
|
||||
let menu = document.getElementById('menu');
|
||||
let buttons = document.querySelectorAll('#light-menu li');
|
||||
|
||||
if(event.keyCode == 13) {
|
||||
for(var i = 0; i < buttons.length; i++) {
|
||||
for(let i = 0; i < buttons.length; i++) {
|
||||
if(buttons[i].querySelector('a').getAttribute('f') == 'true') {
|
||||
trigger_menu(i);
|
||||
}
|
||||
|
|
|
@ -1,68 +1,173 @@
|
|||
{% macro text_editor(field, label=True, autofocus=false) %}
|
||||
{{ field.label if label }}
|
||||
{{ field() }}
|
||||
<script>
|
||||
window.addEventListener("load", function(){
|
||||
var simplemde = new SimpleMDE({
|
||||
element: document.getElementById("{{field.name}}"),
|
||||
autofocus: {{ "true" if autofocus else "false" }},
|
||||
autosave: {
|
||||
enabled: true,
|
||||
uniqueId: "{{ request.path }}+{{ field.name }}",
|
||||
delay: 1000,
|
||||
},
|
||||
hideIcons: ["guide", "side-by-side", "fullscreen", "heading"],
|
||||
showIcons: ["code", "table", "horizontal-rule", "ordered-list",
|
||||
"unordered-list", "heading-1", "heading-2", "heading-3",
|
||||
"strikethrough"
|
||||
],
|
||||
insertTexts: {
|
||||
image: ["![](https://", ")"],
|
||||
link: ["[", "](https://)"],
|
||||
},
|
||||
previewRender: function(plainText, preview) { // Async method
|
||||
data = {text: plainText};
|
||||
fetch('{{ url_for("api_markdown") }}', {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': '{{ csrf_token() }}'
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(data => preview.innerHTML = data);
|
||||
return "Chargement…";
|
||||
},
|
||||
spellChecker: false, // Uses CDN jsdelivr.net
|
||||
forceSync: true,
|
||||
tabSize: 4,
|
||||
shortcuts: {
|
||||
toggleFullScreen: null,
|
||||
},
|
||||
status: false,
|
||||
});
|
||||
<div class="editor">
|
||||
<!-- Buttons for the text editor -->
|
||||
<div class="btn-group">
|
||||
<!-- Underline, Bold, Italic, Strikethrough -->
|
||||
<button type="button" onclick="editor_inline(event, 'underline')" title="__Souligné__">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M12 17c3.31 0 6-2.69 6-6V3h-2.5v8c0 1.93-1.57 3.5-3.5 3.5S8.5 12.93 8.5 11V3H6v8c0 3.31 2.69 6 6 6zm-7 2v2h14v-2H5z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" onclick="editor_inline(event, 'bold')" title="**Gras**">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M15.6 10.79c.97-.67 1.65-1.77 1.65-2.79 0-2.26-1.75-4-4-4H7v14h7.04c2.09 0 3.71-1.7 3.71-3.79 0-1.52-.86-2.82-2.15-3.42zM10 6.5h3c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5h-3v-3zm3.5 9H10v-3h3.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" onclick="editor_inline(event, 'italic')" title="*Italique*">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M10 4v3h2.21l-3.42 8H6v3h8v-3h-2.21l3.42-8H18V4z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" onclick="editor_inline(event, 'strike')" title="~~Barré~~">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M6.85,7.08C6.85,4.37,9.45,3,12.24,3c1.64,0,3,0.49,3.9,1.28c0.77,0.65,1.46,1.73,1.46,3.24h-3.01 c0-0.31-0.05-0.59-0.15-0.85c-0.29-0.86-1.2-1.28-2.25-1.28c-1.86,0-2.34,1.02-2.34,1.7c0,0.48,0.25,0.88,0.74,1.21 C10.97,8.55,11.36,8.78,12,9H7.39C7.18,8.66,6.85,8.11,6.85,7.08z M21,12v-2H3v2h9.62c1.15,0.45,1.96,0.75,1.96,1.97 c0,1-0.81,1.67-2.28,1.67c-1.54,0-2.93-0.54-2.93-2.51H6.4c0,0.55,0.08,1.13,0.24,1.58c0.81,2.29,3.29,3.3,5.67,3.3 c2.27,0,5.3-0.89,5.3-4.05c0-0.3-0.01-1.16-0.48-1.94H21V12z"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
// Ctrl+Enter submits form
|
||||
ta = document.querySelector("div.CodeMirror");
|
||||
ta.addEventListener('keydown', function(e) {
|
||||
var keyCode = e.keyCode || e.which;
|
||||
if (e.ctrlKey && keyCode == 13) {
|
||||
var e = e.target;
|
||||
while(! (e instanceof HTMLFormElement)) {
|
||||
e = e.parentNode;
|
||||
}
|
||||
try {
|
||||
e.submit();
|
||||
} catch(exception) {
|
||||
e.submit.click();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
<span class="separator"></span>
|
||||
<!-- Headers/Titles -->
|
||||
<button type="button" onclick="editor_title(event, 1, 0)" title="# Titre 1">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M3,4H5V10H9V4H11V18H9V12H5V18H3V4M14,18V16H16V6.31L13.5,7.75V5.44L16,4H18V16H20V18H14Z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" onclick="editor_title(event, 2, 0)" title="## Titre 2">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M3,4H5V10H9V4H11V18H9V12H5V18H3V4M21,18H15A2,2 0 0,1 13,16C13,15.47 13.2,15 13.54,14.64L18.41,9.41C18.78,9.05 19,8.55 19,8A2,2 0 0,0 17,6A2,2 0 0,0 15,8H13A4,4 0 0,1 17,4A4,4 0 0,1 21,8C21,9.1 20.55,10.1 19.83,10.83L15,16H21V18Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" onclick="editor_title(event, 3, 0)" title="### Titre 3">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M3,4H5V10H9V4H11V18H9V12H5V18H3V4M15,4H19A2,2 0 0,1 21,6V16A2,2 0 0,1 19,18H15A2,2 0 0,1 13,16V15H15V16H19V12H15V10H19V6H15V7H13V6A2,2 0 0,1 15,4Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" onclick="editor_title(event, 0, -1)" title="Réduire le niveau de titre">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M4,4H6V10H10V4H12V18H10V12H6V18H4V4M20.42,7.41L16.83,11L20.42,14.59L19,16L14,11L19,6L20.42,7.41Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" onclick="editor_title(event, 0, +1)" title="Augmenter le niveau de titre">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M4,4H6V10H10V4H12V18H10V12H6V18H4V4M14.59,7.41L18.17,11L14.59,14.59L16,16L21,11L16,6L14.59,7.41Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
</script>
|
||||
{% for error in field.errors %}
|
||||
<span class="msgerror">{{ error }}</span>
|
||||
{% endfor %}
|
||||
<span class="separator"></span>
|
||||
<!-- Code, Citations -->
|
||||
<button type="button" onclick="editor_inline(event, 'inlinecode')" title="`code`">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" onclick="editor_quote(event)" title="> Citation">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M6 17h3l2-4V7H5v6h3zm8 0h3l2-4V7h-6v6h3z"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<span class="separator"></span>
|
||||
|
||||
<!-- Lists -->
|
||||
<button type="button" onclick="editor_bullet_list(event)" title="- Liste">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M4 10.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-6c-.83 0-1.5.67-1.5 1.5S3.17 7.5 4 7.5 5.5 6.83 5.5 6 4.83 4.5 4 4.5zm0 12c-.83 0-1.5.68-1.5 1.5s.68 1.5 1.5 1.5 1.5-.68 1.5-1.5-.67-1.5-1.5-1.5zM7 19h14v-2H7v2zm0-6h14v-2H7v2zm0-8v2h14V5H7z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" onclick="editor_numbered_list(event)" title="1. Liste numéroté">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M2 17h2v.5H3v1h1v.5H2v1h3v-4H2v1zm1-9h1V4H2v1h1v3zm-1 3h1.8L2 13.1v.9h3v-1H3.2L5 10.9V10H2v1zm5-6v2h14V5H7zm0 14h14v-2H7v2zm0-6h14v-2H7v2z"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<span class="separator"></span>
|
||||
<!-- Table, Separators, Images, Link, Upload -->
|
||||
<!-- I need to find a way to replace the LF with \n -->
|
||||
<button type="button" onclick="editor_table(event)" title="Tableau">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M3 3v18h18V3H3zm8 16H5v-6h6v6zm0-8H5V5h6v6zm8 8h-6v-6h6v6zm0-8h-6V5h6v6z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" onclick="editor_separator(event)" title="Séparateur">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<rect fill-rule="evenodd" height="2" width="16" x="4" y="11"/>
|
||||
</svg>
|
||||
</button>
|
||||
<!-- This button is for an upgrade, it will be an emoji selector.
|
||||
<button type="button" onclick="editor_btn_type()" title="Emoji">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm3.5-9c.83 0 1.5-.67 1.5-1.5S16.33 8 15.5 8 14 8.67 14 9.5s.67 1.5 1.5 1.5zm-7 0c.83 0 1.5-.67 1.5-1.5S9.33 8 8.5 8 7 8.67 7 9.5 7.67 11 8.5 11zm3.5 6.5c2.33 0 4.31-1.46 5.11-3.5H6.89c.8 2.04 2.78 3.5 5.11 3.5z"/>
|
||||
</svg>
|
||||
</button>
|
||||
-->
|
||||
<button type="button" onclick="editor_display_link_modal(event)" title="Lien">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"/>
|
||||
</svg>
|
||||
<div class="modal" style="display: none">
|
||||
<div>
|
||||
<label for="link-desc-input">Description</label>
|
||||
<input type="text" id="link-desc-input" name="link-desc-input">
|
||||
<label for="link-link-input">Lien</label>
|
||||
<input type="url" id="link-link-input" name="link-link-input">
|
||||
</div>
|
||||
<div>
|
||||
<a type="button" class="button bg-ok" onclick="editor_insert_link(event, 'link-link-input', 'link-desc-input')">Valider</a>
|
||||
<a type="button" class="button bg-error" onclick="editor_clear_modals(event)">Annuler</a>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<button type="button" onclick="event.currentTarget.children[1].style = {'display': 'block'}" title="Image">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
|
||||
</svg>
|
||||
<div class="modal" style="display: none">
|
||||
<div>
|
||||
<label for="media-alt-input">Texte alternatif (sera affiché en cas d'erreur de chargement)</label>
|
||||
<input type="text" id="media-alt-input" name="media-alt-input">
|
||||
<label for="media-link-input">Lien vers le média</label>
|
||||
<input type="url" id="media-link-input" name="media-link-input">
|
||||
<fieldset title="Optionel mais peut être important si le type de média n'est pas correctement détecté">
|
||||
<legend>
|
||||
Type de média
|
||||
</legend>
|
||||
<label for="media-type-video" title="Vidéo">Vidéo</label>
|
||||
<input type="radio" id="media-type-video" name="media-type" value="video" title="Vidéo" />
|
||||
<label for="media-type-image" title="Image">Image</label>
|
||||
<input type="radio" id="media-type-image" name="media-type" value="image" title="Image" />
|
||||
</fieldset>
|
||||
</div>
|
||||
<div>
|
||||
<a type="button" class="button bg-ok" onclick="editor_insert_link(event, 'media-link-input', 'media-alt-input', true)">Valider</a>
|
||||
<a type="button" class="button bg-error" onclick="editor_clear_modals(event)">Annuler</a>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<!-- Disabled because we have something similar under the form
|
||||
<button type="button" onclick="editor_btn_type()" title="Joindre un fichier">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z"/>
|
||||
</svg>
|
||||
</button>
|
||||
-->
|
||||
<div class="filler"></div>
|
||||
<a href="#">Aide</a>
|
||||
</div>
|
||||
|
||||
<!-- Field & desc -->
|
||||
{{ field.label if label }}
|
||||
{{ field() }}
|
||||
|
||||
<div id="editor_content_preview">
|
||||
</div>
|
||||
|
||||
<!-- Display errors -->
|
||||
{% for error in field.errors %}
|
||||
<span class="msgerror">{{ error }}</span>
|
||||
{% endfor %}
|
||||
|
||||
<!-- Load the script -->
|
||||
<script>
|
||||
window.addEventListener("load", function(){});
|
||||
</script>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
|
|
@ -67,7 +67,7 @@
|
|||
{{ post_actions(c) }}
|
||||
</div>
|
||||
|
||||
{{ c.text|md }}
|
||||
{{ c.text|md(c.id) }}
|
||||
{{ widget_attachments.attachments(c) }}
|
||||
|
||||
{% if c.author.signature %}
|
||||
|
|
|
@ -3,7 +3,7 @@ from markupsafe import Markup
|
|||
from markdown import markdown
|
||||
from markdown.extensions.codehilite import CodeHiliteExtension
|
||||
from markdown.extensions.footnotes import FootnoteExtension
|
||||
from markdown.extensions.toc import TocExtension
|
||||
from markdown.extensions.toc import TocExtension, slugify_unicode
|
||||
from bleach import clean
|
||||
from app.utils.bleach_allowlist import markdown_tags, markdown_attrs
|
||||
|
||||
|
@ -15,8 +15,14 @@ from app.utils.markdown_extensions.media import MediaExtension
|
|||
from app.utils.markdown_extensions.gallery import GalleryExtension
|
||||
|
||||
|
||||
def slug(prefix, text, sep):
|
||||
if prefix is None:
|
||||
return slugify_unicode(text, sep)
|
||||
else:
|
||||
return str(prefix) + sep + slugify_unicode(text, sep)
|
||||
|
||||
@app.template_filter('md')
|
||||
def md(text):
|
||||
def md(text, prefix=None):
|
||||
"""
|
||||
Converts markdown to html5
|
||||
"""
|
||||
|
@ -33,7 +39,7 @@ def md(text):
|
|||
FootnoteExtension(UNIQUE_IDS=True),
|
||||
HardBreakExtension(),
|
||||
LinkifyExtension(),
|
||||
TocExtension(baselevel=2),
|
||||
TocExtension(baselevel=2, slugify=lambda *args: slug(prefix, *args)),
|
||||
PCLinkExtension(),
|
||||
MediaExtension(),
|
||||
GalleryExtension(),
|
||||
|
|
|
@ -16,19 +16,18 @@ def render(*args, styles=[], scripts=[], **kwargs):
|
|||
'css/flash.css',
|
||||
'css/table.css',
|
||||
'css/pagination.css',
|
||||
'css/simplemde.min.css',
|
||||
'css/simplemde-override.css',
|
||||
'css/debugger.css',
|
||||
'css/programs.css',
|
||||
'css/editor.css',
|
||||
]
|
||||
scripts_ = [
|
||||
'scripts/trigger_menu.js',
|
||||
'scripts/pc-utils.js',
|
||||
'scripts/smartphone_patch.js',
|
||||
'scripts/simplemde.min.js',
|
||||
'scripts/gallery.js',
|
||||
'scripts/filter.js',
|
||||
'scripts/tag_selector.js',
|
||||
'scripts/editor.js',
|
||||
]
|
||||
|
||||
# Apply theme from user settings
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
flask db init
|
||||
flask db migrate -m "initialisation"
|
||||
flask db upgrade
|
|
@ -1,3 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
flask db migrate -m $1
|
||||
flask db upgrade
|
|
@ -1,2 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
flask run
|
Loading…
Reference in New Issue