* add support of markdown in notes (using Parsedown library)

This commit is contained in:
Max Pozdeev 2021-07-13 12:49:07 +03:00
parent 63acc1a2e8
commit 59fe50b31d
10 changed files with 1945 additions and 51 deletions

View file

@ -10,6 +10,7 @@ set_error_handler('myErrorHandler');
set_exception_handler('myExceptionHandler');
require_once('./init.php');
require_once(MTTINC. 'markup.php');
$db = DBConnection::instance();
@ -227,7 +228,7 @@ elseif(isset($_GET['editNote']))
$db->dq("UPDATE {$db->prefix}todolist SET note=?,d_edited=? WHERE id=$id", array($note, time()) );
$t = array();
$t['total'] = 1;
$t['list'][] = array('id'=>$id, 'note'=>mttMarkup_v1($note), 'noteText'=>(string)$note);
$t['list'][] = array('id'=>$id, 'note'=> noteMarkup($note), 'noteText'=>(string)$note);
jsonExit($t);
}
elseif(isset($_GET['editTask']))
@ -543,7 +544,7 @@ function prepareTaskRow($r)
'dateCompletedInlineTitle' => htmlarray(sprintf($lang->get('taskdate_inline_completed'), $dCompleted)),
'compl' => (int)$r['compl'],
'prio' => $r['prio'],
'note' => mttMarkup_v1($r['note']),
'note' => noteMarkup($r['note']),
'noteText' => (string)$r['note'],
'ow' => (int)$r['ow'],
'tags' => htmlarray($r['tags']),

View file

@ -7,6 +7,7 @@
<title><?php mttinfo('title'); ?></title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" type="text/css" href="<?php mttinfo('template_url'); ?>style.css?v=<?php mttinfo('version'); ?>" media="all" />
<link rel="stylesheet" type="text/css" href="<?php mttinfo('template_url'); ?>markdown.css?v=<?php mttinfo('version'); ?>" media="all" />
<link rel="stylesheet" type="text/css" href="<?php mttinfo('template_url'); ?>print.css?v=<?php mttinfo('version'); ?>" media="print" />
<?php if(Config::get('rtl')): ?>
<link rel="stylesheet" type="text/css" href="<?php mttinfo('template_url'); ?>style_rtl.css?v=<?php mttinfo('version'); ?>" media="all" />

View file

@ -0,0 +1,123 @@
/* Markdown notes */
.markdown-note {
line-height: 1.3;
}
/*
.markdown-note > *:first-child { margin-top: 0 !important; }
.markdown-note > *:last-child { margin-bottom: 0 !important; }
*/
.markdown-note h1 { font-size:2rem; }
.markdown-note h2 { font-size:1.5rem; }
.markdown-note h3 { font-size:1.2rem; }
.markdown-note h4 { font-size:1rem; }
.markdown-note h5 { font-size:1rem; }
.markdown-note h6 { font-size:1rem; }
.markdown-note hr {
height: 1px;
border: 0;
background-color: #ccc;
}
.markdown-note p,
.markdown-note blockquote,
.markdown-note ul,
.markdown-note ol,
.markdown-note dl,
.markdown-note table,
.markdown-note pre {
margin: 10px 0;
}
.markdown-note ul,
.markdown-note ol {
padding-left: 20px;
}
.markdown-note ul.no-list,
.markdown-note ol.no-list {
list-style-type: none;
padding: 0;
}
.markdown-note blockquote {
margin:15px 0;
border-left: 4px solid #DDD;
padding: 0 15px;
color: #777;
}
.markdown-note blockquote > :first-child {
margin-top: 0px;
}
.markdown-note blockquote > :last-child {
margin-bottom: 0px;
}
.markdown-note table {
width: 100%;
overflow: auto;
display: block;
border-spacing: 0;
border-collapse: collapse;
}
.markdown-note table th {
font-weight: bold;
}
.markdown-note table th,
.markdown-note table td {
border: 1px solid #ddd;
padding: 6px 13px;
}
.markdown-note table tr {
border-top: 1px solid #ccc;
background-color: #fff;
}
.markdown-note table tr:nth-child(2n) {
background-color: #f8f8f8;
}
.markdown-note code, /* inline code */
.markdown-note tt {
font-size: 0.95rem;
margin: 0 2px;
padding: 0px 5px;
border: 1px solid #ddd;
background-color: #f8f8f8;
border-radius: 3px;
}
.markdown-note code {
white-space: nowrap;
}
.markdown-note pre {
font-size: 0.95rem;
line-height: 1.2rem;
padding: 6px 10px;
background-color: #f8f8f8;
border: 1px solid #ddd;
overflow: auto;
border-radius: 3px;
}
.markdown-note pre code, /* block of code */
.markdown-note pre tt {
margin: 0;
padding: 0;
background-color: transparent;
border: none;
}
.markdown-note pre > code {
white-space: pre;
}
/* narrow screens */
@media only screen and (max-width: 600px) {
.markdown-note { padding-top: 0.5rem; }
.markdown-note pre,
.markdown-note code,
.markdown-note tt {
font-size: 0.9rem;
}
}

View file

@ -157,25 +157,29 @@ a { color:#0000ff; cursor:pointer; text-decoration:underline; }
}
.task-toggle::after { content:'0'; color:transparent; } /* for baseline */
.task-toggle:hover { opacity:0.7; }
li.task-has-note .task-toggle { visibility:visible; }
li.task-expanded .task-toggle { transform:rotate(90deg); }
li.task-row.task-has-note .task-toggle { visibility:visible; }
li.task-row.task-expanded .task-toggle { transform:rotate(90deg); }
/* #tasklist input[type="checkbox"] { vertical-align:-1px; } /* Chrome */
#tasklist { list-style-type: none; margin: 0; padding: 0;}
#tasklist li.task-row {
padding:1px 2px;
padding:0px 2px;
border:1px solid transparent; /* allocate space for expanded border */
border-bottom:1px solid #DEDEDE;
margin-bottom: 4px;
min-height:20px;
background-color:#fff;
position:relative; /* for z-index */
}
#tasklist li.task-row:hover,
#tasklist li.task-row.menu-active {
background-color:#f6f6f6;
/* box-shadow:1px 0 2px rgba(0,0,0,0.2), -1px 0 2px rgba(0,0,0,0.2); z-index: 1; */
box-shadow: 1px 0 3px 1px rgba(0,0,0,0.2);
/* transition: box-shadow .1s ease-in; */
z-index: 1;
}
#tasklist .task-container { display:flex; justify-content:flex-start; align-items:baseline; }
#tasklist li.task-row.task-expanded { border:1px solid #DEDEDE; border-radius: 3px; }
.task-left { display:inline-block; }
.task-middle { flex-grow:1; margin:0.6rem 0px; margin-left:5px; min-width:0; /*for long text*/ flex-basis:0; /* IE11 */}
.task-middle-top { display:flex; align-items:baseline; }
@ -185,8 +189,8 @@ li.task-expanded .task-toggle { transform:rotate(90deg); }
.task-date { color:#999999; font-size:0.8rem; margin-left:4px; display:none; }
.task-date-completed { color:#999999; display:none; margin-left:5px; }
.show-inline-date .task-date { display:inline; }
.show-inline-date li.task-completed .task-date-completed { display:inline; }
.show-inline-date li.task-completed .task-date { display:none; }
.show-inline-date li.task-row.task-completed .task-date-completed { display:inline; }
.show-inline-date li.task-row.task-completed .task-date { display:none; }
.task-through { overflow:hidden; flex-grow:1; }
.task-title a { color:#000000; }
.task-title a:hover { color:#af0000; }
@ -196,15 +200,15 @@ li.task-expanded .task-toggle { transform:rotate(90deg); }
.task-tags .tag { font-size:0.9rem; font-weight:bold; color:#333333; text-decoration:underline; }
.duedate { color:#333333; padding:0px; padding-left:1px; margin-left:5px; white-space:nowrap; }
.duedate:before { content:'\2192\20'; }
li.task-completed .duedate { /*font-size:0.8rem;*/ display:none; }
li.task-row.task-completed .duedate { /*font-size:0.8rem;*/ display:none; }
#tasklist li.task-row.soon .duedate { color:#008000; }
#tasklist li.task-row.today .duedate { color:#FF0000; }
#tasklist li.task-row.past .duedate { color:#A52A2A; }
li.task-completed .task-middle { color:#777777;}
li.task-completed .task-through { text-decoration:line-through; }
li.task-completed .task-title a { color:#777777; }
#tasklist li.task-completed { opacity:0.6; }
#tasklist li.task-completed:hover { opacity:1.0; }
li.task-row.ask-completed .task-middle { color:#777777;}
li.task-row.task-completed .task-through { text-decoration:line-through; }
li.task-row.task-completed .task-title a { color:#777777; }
#tasklist li.task-row.task-completed { opacity:0.6; }
#tasklist li.task-row.task-completed:hover { opacity:1.0; }
#tasklist li.task-row.not-in-tagpreview { opacity:0.1; }
#tasklist .mtt-task-placeholder {
min-height:0px; padding:0px; height:18px; line-height:18px;
@ -228,9 +232,9 @@ li.task-row:hover .taskactionbtn { visibility:visible; }
#tasklist.filter-soon li.task-row.soon {
display:block;
}
#tasklist.filter-past li.task-completed,
#tasklist.filter-today li.task-completed,
#tasklist.filter-soon li.task-completed {
#tasklist.filter-past li.task-row.task-completed,
#tasklist.filter-today li.task-row.task-completed,
#tasklist.filter-soon li.task-row.task-completed {
display:none;
}
@ -246,9 +250,8 @@ li.task-row:hover .taskactionbtn { visibility:visible; }
white-space:normal;
word-wrap:break-word;
}
li.task-expanded .task-note-block { display:block; }
/*li.task-expanded { box-shadow:1px 0 2px rgba(0,0,0,0.2), -1px 0 2px rgba(0,0,0,0.2); }*/
li.task-completed .task-note-block .task-note { text-decoration:line-through; }
li.task-row.task-expanded .task-note-block { display:block; }
li.task-row.task-completed .task-note-block .task-note { text-decoration:line-through; }
.task-note-area { display:none; margin-bottom:5px; }
.task-note-area textarea { color:#999999; width:100%; display:block; height:65px; }
.task-note-actions { font-size:0.8rem; }
@ -464,8 +467,8 @@ li.mtt-item-hidden { display:none; }
#tasklist li.task-row .task-through { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
#tasklist li.task-row:hover { background-color:#ffffff; }
#tasklist li.task-expanded .task-note-block { display:none; }
#tasklist li.task-row.clicked { /*background-color:#f6f6f6;*/ box-shadow:1px 0 2px rgba(0,0,0,0.2), -1px 0 2px rgba(0,0,0,0.2); z-index: 1; }
#tasklist li.task-row.task-expanded .task-note-block { display:none; }
#tasklist li.task-row.clicked { border:1px solid #DEDEDE; border-radius: 3px; }
#tasklist li.task-row.clicked .task-actions { display:block; }
#tasklist li.task-row.clicked .task-through { white-space:normal; display:inline; }
#tasklist li.task-row.clicked.task-has-note .task-note-block { display:block; }

View file

@ -54,4 +54,7 @@ $config['dateformatshort'] = 'j M';
# Show task date in list
$config['showdate'] = 0;
# Use Markdown syntax for notes. Set to 'v1' to use old v1.6 syntax.
$config['markup'] = 'markdown';
?>

View file

@ -115,6 +115,7 @@ class Config
'dateformatshort' => array('default'=>'j M', 'type'=>'s'),
'template' => array('default'=>'default', 'type'=>'s'),
'showdate' => array('default'=>0, 'type'=>'i'),
'markup' => array('default'=>'markdown', 'type'=>'s'),
);
public static $config;
@ -215,29 +216,6 @@ function url_dir($url, $onlyPath = 1)
return '/';
}
// Convert note's raw text to html with allowed elements (b,i,u,s and raw urls)
function mttMarkup_v1($s)
{
//hide allowed elements from escaping
$c1 = chr(1);
$c2 = chr(2);
$s = preg_replace("~<b>([\s\S]*?)</b>~i", "${c1}b${c2}\$1${c1}/b${c2}", $s);
$s = preg_replace("~<i>([\s\S]*?)</i>~i", "${c1}i${c2}\$1${c1}/i${c2}", $s);
$s = preg_replace("~<u>([\s\S]*?)</u>~i", "${c1}u${c2}\$1${c1}/u${c2}", $s);
$s = preg_replace("~<s>([\s\S]*?)</s>~i", "${c1}s${c2}\$1${c1}/s${c2}", $s);
$s = htmlspecialchars($s); //escape all elements, except above
$s = str_replace( [$c1, $c2], ['<','>'], $s ); //unhide
$s = nl2br($s);
// make links from text starting with 'www.'
$s = preg_replace( "/(^|\s|>)(www\.([\w\#$%&~\/.\-\+;:=,\?\[\]@]+?))(,|\.|:|)?(?=\s|&quot;|&lt;|&gt;|\"|<|>|$)/i" , '$1<a href="http://$2" target="_blank">$2</a>$4' , $s );
// make link from text starting with protocol like 'http://'
$s = preg_replace( "/(^|\s|>)([a-z]+:\/\/([\w\#$%&~\/.\-\+;:=,\?\[\]@]+?))(,|\.|:|)?(?=\s|&quot;|&lt;|&gt;|\"|<|>|$)/i" , '$1<a href="$2" target="_blank">$2</a>$4' , $s);
return $s;
}
/* found in comments on http://www.php.net/manual/en/function.uniqid.php#94959 */
function generateUUID()
{

47
src/includes/markup.php Normal file
View file

@ -0,0 +1,47 @@
<?php
require_once(MTTINC. 'parsedown/Parsedown.php');
function noteMarkup($note)
{
if (Config::get('markup') == 'v1') {
return mttMarkup_v1($note);
}
return markdownToHtml($note);
}
// Markdown converter (Parsedown)
function markdownToHtml($s)
{
$parser = Parsedown::instance();
$parser->setSafeMode(true);
//$parser->setBreaksEnabled(true);
return $parser->text($s);
}
// Convert note's raw text to html with allowed elements (b,i,u,s and raw urls)
function mttMarkup_v1($s)
{
//hide allowed elements from escaping
$c1 = chr(1);
$c2 = chr(2);
$s = preg_replace("~<b>([\s\S]*?)</b>~i", "${c1}b${c2}\$1${c1}/b${c2}", $s);
$s = preg_replace("~<i>([\s\S]*?)</i>~i", "${c1}i${c2}\$1${c1}/i${c2}", $s);
$s = preg_replace("~<u>([\s\S]*?)</u>~i", "${c1}u${c2}\$1${c1}/u${c2}", $s);
$s = preg_replace("~<s>([\s\S]*?)</s>~i", "${c1}s${c2}\$1${c1}/s${c2}", $s);
$s = htmlspecialchars($s); //escape all elements, except above
$s = str_replace( [$c1, $c2], ['<','>'], $s ); //unhide
$s = nl2br($s);
// make links from text starting with 'www.'
$s = preg_replace( "/(^|\s|>)(www\.([\w\#$%&~\/.\-\+;:=,\?\[\]@]+?))(,|\.|:|)?(?=\s|&quot;|&lt;|&gt;|\"|<|>|$)/i" , '$1<a href="http://$2" target="_blank">$2</a>$4' , $s );
// make link from text starting with protocol like 'http://'
$s = preg_replace( "/(^|\s|>)([a-z]+:\/\/([\w\#$%&~\/.\-\+;:=,\?\[\]@]+?))(,|\.|:|)?(?=\s|&quot;|&lt;|&gt;|\"|<|>|$)/i" , '$1<a href="$2" target="_blank">$2</a>$4' , $s);
return $s;
}
?>

View file

@ -61,7 +61,8 @@ var mytinytodo = window.mytinytodo = _mtt = {
firstdayofweek: 1,
touchDevice: false,
calendarIcon: 'calendar.png', // need templateUrl+icon
history: true
history: true,
markdown: true,
},
timers: {
@ -125,7 +126,6 @@ var mytinytodo = window.mytinytodo = _mtt = {
delete options.mttUrl;
}
if (options.hasOwnProperty('db')) {
this.db = new options.db(this);
delete options.db;
}
if (options.hasOwnProperty('homeUrl')) {
@ -699,6 +699,10 @@ var mytinytodo = window.mytinytodo = _mtt = {
//TODO: handle unknown list
}
if (_mtt.options.markdown == true) {
$('#mtt_body').addClass('markdown-enabled');
}
_mtt.options.openList = 0;
$('#lists ul').html(ti);
$('#lists').show();
@ -970,6 +974,8 @@ _mtt.prepareTaskStr = prepareTaskStr;
function prepareTaskBlocks(item)
{
var id = item.id;
var markdown = '';
if (_mtt.options.markdown == true) markdown = 'markdown-note';
return ''+
'<div class="task-left">' +
'<div class="task-toggle"></div>' +
@ -990,7 +996,7 @@ function prepareTaskBlocks(item)
'</div>' +
'<div class="task-note-block">' +
'<div id="tasknote' + id + '" class="task-note">' + prepareTaskNoteInlineHtml(item.note) + '</div>' +
'<div id="tasknote' + id + '" class="task-note ' + markdown + '">' + prepareTaskNoteInlineHtml(item.note, item.noteText) + '</div>' +
'<div id="tasknotearea'+id+'" class="task-note-area"><textarea id="notetext'+id+'"></textarea>'+
'<span class="task-note-actions"><a href="#" class="mtt-action-note-save">'+_mtt.lang.get('actionNoteSave') +
'</a> | <a href="#" class="mtt-action-note-cancel">'+_mtt.lang.get('actionNoteCancel')+'</a></span>' +
@ -1009,7 +1015,7 @@ function prepareTaskTitleInlineHtml(s)
}
_mtt.prepareTaskTitleInlineHtml = prepareTaskTitleInlineHtml;
function prepareTaskNoteInlineHtml(s)
function prepareTaskNoteInlineHtml(s, rawText)
{
// Task note is already escaped on php back-end
return s;
@ -1468,7 +1474,7 @@ function saveTaskNote(id)
var item = json.list[0];
taskList[id].note = item.note;
taskList[id].noteText = item.noteText;
$('#tasknote'+id).html(prepareTaskNoteInlineHtml(item.note));
$('#tasknote'+id).html(prepareTaskNoteInlineHtml(item.note, item.noteText));
if(item.note == '') $('#taskrow_'+id).removeClass('task-has-note task-expanded');
else $('#taskrow_'+id).addClass('task-has-note task-expanded');
cancelTaskNote(id);

View file

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2013-2018 Emanuil Rusev, erusev.com
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

File diff suppressed because it is too large Load diff