mirror of
https://github.com/flynx/pWiki.git
synced 2025-12-25 04:11:56 +00:00
Compare commits
4 Commits
53ad572369
...
ea00679b9f
| Author | SHA1 | Date | |
|---|---|---|---|
| ea00679b9f | |||
| 9a1d851112 | |||
| 34105a355c | |||
| 4690bc0770 |
@ -54,13 +54,23 @@
|
|||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
/* XXX do a better calculation... */
|
|
||||||
width: calc(100% - var(--button-size) - var(--outline-padding) * 2);
|
width: calc(100% - var(--button-size) - var(--outline-padding) * 2);
|
||||||
|
|
||||||
padding-left: var(--outline-padding);
|
padding: 1em var(--outline-padding);
|
||||||
padding-right: var(--outline-padding);
|
padding-bottom: 1.2em
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* virtual empty block... */
|
||||||
|
.editor .outline:empty:after {
|
||||||
|
content: "Empty";
|
||||||
|
display: block;
|
||||||
|
font-style: italic;
|
||||||
|
color: rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
.editor .outline:empty:hover:after {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.editor .outline .block {
|
.editor .outline .block {
|
||||||
position: relative;
|
position: relative;
|
||||||
outline: none;
|
outline: none;
|
||||||
@ -110,6 +120,7 @@
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
/* block hover... */
|
/* block hover... */
|
||||||
|
.editor .outline:empty:hover:after,
|
||||||
.editor .outline .block:hover>.view {
|
.editor .outline .block:hover>.view {
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
90deg,
|
90deg,
|
||||||
@ -127,6 +138,7 @@
|
|||||||
}
|
}
|
||||||
/* clickable things in view */
|
/* clickable things in view */
|
||||||
.editor .outline .block>.view a,
|
.editor .outline .block>.view a,
|
||||||
|
.editor .outline .block>.view pre,
|
||||||
.editor .outline .block>.view input {
|
.editor .outline .block>.view input {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
@ -138,7 +150,7 @@ editor .outline .block:focus {
|
|||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
.editor .outline .block:focus>.text {
|
.editor .outline .block:focus>.text {
|
||||||
background: rgba(0,0,0,0.1);
|
background: rgba(0,0,0,0.07);
|
||||||
}
|
}
|
||||||
.editor .outline .block.focused:not(:focus)>.text {
|
.editor .outline .block.focused:not(:focus)>.text {
|
||||||
background: rgba(0,0,0,0.01);
|
background: rgba(0,0,0,0.01);
|
||||||
@ -453,6 +465,11 @@ editor .outline .block:focus {
|
|||||||
-1 * var(--checkbox-size)
|
-1 * var(--checkbox-size)
|
||||||
- var(--checkbox-margin));
|
- var(--checkbox-margin));
|
||||||
}
|
}
|
||||||
|
/* status... */
|
||||||
|
.editor .outline .block>.view .completion[completion]:before {
|
||||||
|
content: "(" attr(completion) ")";
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*---------------------------------------------------------- Code ---*/
|
/*---------------------------------------------------------- Code ---*/
|
||||||
@ -464,11 +481,13 @@ editor .outline .block:focus {
|
|||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
background: rgba(0,0,0,0.07);
|
background: rgba(0,0,0,0.07);
|
||||||
border-radius: 0.2em;
|
border-radius: 0.2em;
|
||||||
|
outline: none;
|
||||||
}
|
}
|
||||||
.editor .outline .block>.view pre>code {
|
.editor .outline .block>.view pre>code {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 0.6em 0.6em;
|
padding: 0.6em 0.6em;
|
||||||
padding-bottom: 0.8em;
|
padding-bottom: 0.8em;
|
||||||
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -25,6 +25,303 @@ var atLine = function(elem, index){
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------
|
||||||
|
|
||||||
|
var plugin = {
|
||||||
|
// XXX make this more generic...
|
||||||
|
style: function(editor, elem, style, code=undefined){
|
||||||
|
style = [style].flat()
|
||||||
|
editor.__styles = [...new Set([
|
||||||
|
...(editor.__styles ?? []),
|
||||||
|
...style,
|
||||||
|
])]
|
||||||
|
return function(_, text){
|
||||||
|
elem.style ??= []
|
||||||
|
elem.style.push(...style)
|
||||||
|
return code
|
||||||
|
?? text } },
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
var attributes = {
|
||||||
|
__proto__: plugin,
|
||||||
|
|
||||||
|
__pre_parse__: function(text, editor, elem){
|
||||||
|
return text
|
||||||
|
// hidden attributes...
|
||||||
|
// XXX make this generic...
|
||||||
|
// collapsed...
|
||||||
|
.replace(/(\n|^)\s*collapsed::\s*(.*)\s*(\n|$)/,
|
||||||
|
function(_, value){
|
||||||
|
elem.collapsed = value.trim() == 'true'
|
||||||
|
return '' })
|
||||||
|
// id...
|
||||||
|
.replace(/(\n|^)\s*id::\s*(.*)\s*(\n|$)/,
|
||||||
|
function(_, value){
|
||||||
|
elem.id = value.trim()
|
||||||
|
return '' }) },
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
var blocks = {
|
||||||
|
__proto__: plugin,
|
||||||
|
|
||||||
|
__pre_parse__: function(text, editor, elem){
|
||||||
|
return text
|
||||||
|
// markdown...
|
||||||
|
// style: headings...
|
||||||
|
.replace(/^(?<!\\)######\s+(.*)$/m, this.style(editor, elem, 'heading-6'))
|
||||||
|
.replace(/^(?<!\\)#####\s+(.*)$/m, this.style(editor, elem, 'heading-5'))
|
||||||
|
.replace(/^(?<!\\)####\s+(.*)$/m, this.style(editor, elem, 'heading-4'))
|
||||||
|
.replace(/^(?<!\\)###\s+(.*)$/m, this.style(editor, elem, 'heading-3'))
|
||||||
|
.replace(/^(?<!\\)##\s+(.*)$/m, this.style(editor, elem, 'heading-2'))
|
||||||
|
.replace(/^(?<!\\)#\s+(.*)$/m, this.style(editor, elem, 'heading-1'))
|
||||||
|
// style: list...
|
||||||
|
//.replace(/^(?<!\\)[-\*]\s+(.*)$/m, style('list-item'))
|
||||||
|
.replace(/^\s*(.*)(?<!\\):\s*$/m, this.style(editor, elem, 'list'))
|
||||||
|
.replace(/^\s*(.*)(?<!\\)#\s*$/m, this.style(editor, elem, 'numbered-list'))
|
||||||
|
// style: misc...
|
||||||
|
.replace(/^\s*(?<!\\)>\s+(.*)$/m, this.style(editor, elem, 'quote'))
|
||||||
|
.replace(/^\s*(?<!\\)((\/\/|;)\s+.*)$/m, this.style(editor, elem, 'comment'))
|
||||||
|
.replace(/^\s*(?<!\\)NOTE:?\s*(.*)$/m, this.style(editor, elem, 'NOTE'))
|
||||||
|
.replace(/^\s*(?<!\\)XXX\s+(.*)$/m, this.style(editor, elem, 'XXX'))
|
||||||
|
.replace(/^(.*)\s*(?<!\\)XXX$/m, this.style(editor, elem, 'XXX')) } ,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
// XXX add actions...
|
||||||
|
var quoted = {
|
||||||
|
__proto__: plugin,
|
||||||
|
|
||||||
|
encode: function(text){
|
||||||
|
return text
|
||||||
|
.replace(/(?<!\\)&/g, '&')
|
||||||
|
.replace(/(?<!\\)</g, '<')
|
||||||
|
.replace(/(?<!\\)>/g, '>')
|
||||||
|
.replace(/\\(?!`)/g, '\\\\') },
|
||||||
|
|
||||||
|
// can be used in:
|
||||||
|
// <string>.replace(quoted.pattern, quoted.handler)
|
||||||
|
quote_pattern: /(?<!\\)`(?=[^\s])(([^`]|\\`)*[^\s])(?<!\\)`/gm,
|
||||||
|
quote: function(_, code){
|
||||||
|
return `<code>${ this.encode(code) }</code>` },
|
||||||
|
|
||||||
|
pre_pattern: /(?<!\\)```(.*\s*\n)((\n|.)*?)\h*(?<!\\)```/g,
|
||||||
|
pre: function(_, language, code){
|
||||||
|
language = language.trim()
|
||||||
|
language = language ?
|
||||||
|
'language-'+language
|
||||||
|
: language
|
||||||
|
return `<pre>`
|
||||||
|
+`<code contenteditable="true" class="${language}">${
|
||||||
|
this.encode(code)
|
||||||
|
}</code>`
|
||||||
|
+`</pre>` },
|
||||||
|
|
||||||
|
map: function(text, func){
|
||||||
|
return text.replace(this.pre_pattern, func) },
|
||||||
|
replace: function(text, index, updated){
|
||||||
|
return this.map(text,
|
||||||
|
function(match, language, code){
|
||||||
|
return index-- != 0 ?
|
||||||
|
match
|
||||||
|
: ('```'+language
|
||||||
|
+ (typeof(updated) == 'function' ?
|
||||||
|
updated(code)
|
||||||
|
: updated)
|
||||||
|
+'```') }) },
|
||||||
|
toHTML: function(text){
|
||||||
|
return this.map(text, this.handler) },
|
||||||
|
|
||||||
|
__pre_parse__: function(text, editor, elem){
|
||||||
|
return text
|
||||||
|
.replace(this.pre_pattern, this.pre.bind(this))
|
||||||
|
.replace(this.quote_pattern, this.quote.bind(this)) },
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
// XXX Hackish...
|
||||||
|
var syntax = {
|
||||||
|
__proto__: plugin,
|
||||||
|
|
||||||
|
update: function(){
|
||||||
|
window.hljs
|
||||||
|
&& hljs.highlightAll()
|
||||||
|
return this },
|
||||||
|
|
||||||
|
__setup__: function(editor){
|
||||||
|
return this.update() },
|
||||||
|
// XXX make a local update...
|
||||||
|
__changed__: function(evt, editor, node){
|
||||||
|
return this.update() },
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
var tables = {
|
||||||
|
__proto__: plugin,
|
||||||
|
|
||||||
|
__parse__: function(text, editor, elem){
|
||||||
|
return text
|
||||||
|
.replace(/^\s*(?<!\\)\|\s*((.|\n)*)\s*\|\s*$/,
|
||||||
|
function(_, body){
|
||||||
|
return `<table><tr><td>${
|
||||||
|
body
|
||||||
|
.replace(/\s*\|\s*\n\s*\|\s*/gm, '</td></tr>\n<tr><td>')
|
||||||
|
.replace(/\s*\|\s*/gm, '</td><td>')
|
||||||
|
}</td></td></table>` }) },
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
var styling = {
|
||||||
|
__proto__: plugin,
|
||||||
|
|
||||||
|
__parse__: function(text, editor, elem){
|
||||||
|
return text
|
||||||
|
// markers...
|
||||||
|
.replace(/(\s*)(?<!\\)(FEATURE:|Q:|Question:|Note:)(\s*)/gm,
|
||||||
|
'$1<b class="$2">$2</b>$3')
|
||||||
|
.replace(/(\s*)(?<!\\)(ASAP|BUG|FIX|HACK|STUB|WARNING|CAUTION)(\s*)/gm,
|
||||||
|
'$1<span class="highlight $2">$2</span>$3')
|
||||||
|
// elements...
|
||||||
|
.replace(/(\n|^)(?<!\\)---*\h*(\n|$)/m, '$1<hr>')
|
||||||
|
// basic styling...
|
||||||
|
// XXX revise...
|
||||||
|
.replace(/(?<!\\)\*(?=[^\s*])(([^*]|\\\*)*[^\s*])(?<!\\)\*/gm, '<b>$1</b>')
|
||||||
|
.replace(/(?<!\\)~(?=[^\s~])(([^~]|\\~)*[^\s~])(?<!\\)~/gm, '<s>$1</s>')
|
||||||
|
.replace(/(?<!\\)_(?=[^\s_])(([^_]|\\_)*[^\s_])(?<!\\)_/gm, '<i>$1</i>')
|
||||||
|
// code/quoting...
|
||||||
|
//.replace(/(?<!\\)`(?=[^\s])(([^`]|\\`)*[^\s])(?<!\\)`/gm, quote)
|
||||||
|
// XXX support "\==" in mark...
|
||||||
|
.replace(/(?<!\\)==(?=[^\s])(.*[^\s])(?<!\\)==/gm, '<mark>$1</mark>')
|
||||||
|
// links...
|
||||||
|
.replace(/(?<!\\)\[([^\]]*)\]\(([^)]*)\)/g, '<a href="$2">$1</a>')
|
||||||
|
.replace(/((?:https?:|ftps?:)[^\s]*)(\s*)/g, '<a href="$1">$1</a>$2') },
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
// XXX use ligatures for these???
|
||||||
|
var symbols = {
|
||||||
|
__proto__: plugin,
|
||||||
|
|
||||||
|
__parse__: function(text, editor, elem){
|
||||||
|
return text
|
||||||
|
// characters...
|
||||||
|
.replace(/(?<!\\)\(i\)/gm, '🛈')
|
||||||
|
.replace(/(?<!\\)\(c\)/gm, '©')
|
||||||
|
.replace(/(?<!\\)\/!\\/gm, '⚠')
|
||||||
|
.replace(/(?<!\\)---(?!-)/gm, '—')
|
||||||
|
.replace(/(?<!\\)--(?!-)/gm, '–') },
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
var escaping = {
|
||||||
|
__proto__: plugin,
|
||||||
|
|
||||||
|
__post_parse__: function(text, editor, elem){
|
||||||
|
return text
|
||||||
|
// quoting...
|
||||||
|
// NOTE: this must be last...
|
||||||
|
.replace(/(?<!\\)\\(.)/gm, '$1') },
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
// XXX add actions...
|
||||||
|
var tasks = {
|
||||||
|
__proto__: plugin,
|
||||||
|
|
||||||
|
updateStatus: function(editor, node){
|
||||||
|
node = editor.get(node)
|
||||||
|
if(node == null){
|
||||||
|
return this }
|
||||||
|
var state = node
|
||||||
|
.querySelector('.view')
|
||||||
|
.querySelector('.completion')
|
||||||
|
if(state){
|
||||||
|
var c =
|
||||||
|
((node.querySelectorAll('input[type=checkbox]:checked').length
|
||||||
|
/ node.querySelectorAll('input[type=checkbox]').length)
|
||||||
|
* 100)
|
||||||
|
.toFixed(0)
|
||||||
|
!isNaN(c)
|
||||||
|
&& state.setAttribute('completion', c +'%') }
|
||||||
|
return this },
|
||||||
|
updateBranch: function(editor, node){
|
||||||
|
if(!node){
|
||||||
|
return this }
|
||||||
|
var outline = editor.outline
|
||||||
|
var p = node
|
||||||
|
while(p !== outline){
|
||||||
|
this.updateStatus(editor, p)
|
||||||
|
p = editor.get(p, 'parent') }
|
||||||
|
return this },
|
||||||
|
updateAll: function(editor){
|
||||||
|
for(var e of [...editor.outline.querySelectorAll('.block>.view .completion')]){
|
||||||
|
this.updateStatus(editor, e) }
|
||||||
|
return this },
|
||||||
|
|
||||||
|
|
||||||
|
__setup__: function(editor){
|
||||||
|
return this.updateAll(editor) },
|
||||||
|
__parse__: function(text, editor, elem){
|
||||||
|
return text
|
||||||
|
// block checkboxes...
|
||||||
|
// NOTE: these are separate as we need to align block text
|
||||||
|
// to leading chekbox...
|
||||||
|
.replace(/^\s*(?<!\\)\[[_ ]\]\s*/m,
|
||||||
|
this.style(editor, elem, 'todo', '<input type="checkbox">'))
|
||||||
|
.replace(/^\s*(?<!\\)\[[Xx]\]\s*/m,
|
||||||
|
this.style(editor, elem, 'todo', '<input type="checkbox" checked>'))
|
||||||
|
// inline checkboxes...
|
||||||
|
.replace(/\s*(?<!\\)\[[_ ]\]\s*/gm,
|
||||||
|
this.style(editor, elem, 'check', '<input type="checkbox">'))
|
||||||
|
.replace(/\s*(?<!\\)\[[Xx]\]\s*/gm,
|
||||||
|
this.style(editor, elem, 'check', '<input type="checkbox" checked>'))
|
||||||
|
// completion...
|
||||||
|
// XXX add support for being like a todo checkbox...
|
||||||
|
.replace(/(?<!\\)\[[%]\]/gm, '<span class="completion"></span>') },
|
||||||
|
__changed__: function(evt, editor, node){
|
||||||
|
return this.updateBranch(editor, node) },
|
||||||
|
__click__: function(evt, editor, elem){
|
||||||
|
// toggle checkbox...
|
||||||
|
if(elem.type == 'checkbox'){
|
||||||
|
var node = editor.get(elem)
|
||||||
|
var text = node.querySelector('.code')
|
||||||
|
// get the checkbox order...
|
||||||
|
var i = [...node.querySelectorAll('input[type=checkbox]')].indexOf(elem)
|
||||||
|
var to = elem.checked ?
|
||||||
|
'[X]'
|
||||||
|
: '[_]'
|
||||||
|
var toggle = function(m){
|
||||||
|
return i-- == 0 ?
|
||||||
|
to
|
||||||
|
: m }
|
||||||
|
text.value = text.value.replace(/\[[Xx_]\]/g, toggle)
|
||||||
|
// update status...
|
||||||
|
this.updateBranch(editor, node) }
|
||||||
|
return this },
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//---------------------------------------------------------------------
|
//---------------------------------------------------------------------
|
||||||
|
|
||||||
// XXX experiment with a concatinative model...
|
// XXX experiment with a concatinative model...
|
||||||
@ -41,6 +338,32 @@ var Outline = {
|
|||||||
carot_jump_edge_then_block: false,
|
carot_jump_edge_then_block: false,
|
||||||
|
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
attributes,
|
||||||
|
blocks,
|
||||||
|
quoted,
|
||||||
|
styling,
|
||||||
|
tables,
|
||||||
|
symbols,
|
||||||
|
syntax,
|
||||||
|
tasks,
|
||||||
|
|
||||||
|
// keep this last...
|
||||||
|
// XXX revise -- should this be external???
|
||||||
|
escaping,
|
||||||
|
],
|
||||||
|
runPlugins: function(method, ...args){
|
||||||
|
for(var plugin of this.plugins){
|
||||||
|
method in plugin
|
||||||
|
&& plugin[method](...args) }
|
||||||
|
return this },
|
||||||
|
threadPlugins: function(method, value, ...args){
|
||||||
|
for(var plugin of this.plugins){
|
||||||
|
method in plugin
|
||||||
|
&& (value = plugin[method](value, ...args)) }
|
||||||
|
return value },
|
||||||
|
|
||||||
|
|
||||||
get code(){
|
get code(){
|
||||||
return this.dom.querySelector('.code') },
|
return this.dom.querySelector('.code') },
|
||||||
get outline(){
|
get outline(){
|
||||||
@ -101,12 +424,13 @@ var Outline = {
|
|||||||
// groups defaulting to .focused as base...
|
// groups defaulting to .focused as base...
|
||||||
if(['parent', 'next', 'prev', 'children', 'siblings'].includes(node)){
|
if(['parent', 'next', 'prev', 'children', 'siblings'].includes(node)){
|
||||||
return this.get('focused', node) }
|
return this.get('focused', node) }
|
||||||
|
|
||||||
// helpers...
|
// helpers...
|
||||||
var parent = function(node){
|
var parent = function(node){
|
||||||
return node === outline ?
|
return node === outline ?
|
||||||
node
|
outline
|
||||||
: node?.parentElement?.parentElement }
|
: node.parentElement === outline ?
|
||||||
|
outline
|
||||||
|
: node.parentElement.parentElement }
|
||||||
var children = function(node){
|
var children = function(node){
|
||||||
return node === outline ?
|
return node === outline ?
|
||||||
[...node.children]
|
[...node.children]
|
||||||
@ -236,7 +560,7 @@ var Outline = {
|
|||||||
// deindent...
|
// deindent...
|
||||||
if(!indent){
|
if(!indent){
|
||||||
var parent = this.get(node, 'parent')
|
var parent = this.get(node, 'parent')
|
||||||
if(!parent.classList.contains('.outline')){
|
if(parent != this.outline){
|
||||||
var children = siblings
|
var children = siblings
|
||||||
.slice(siblings.indexOf(cur)+1)
|
.slice(siblings.indexOf(cur)+1)
|
||||||
parent.after(cur)
|
parent.after(cur)
|
||||||
@ -312,115 +636,50 @@ var Outline = {
|
|||||||
var elem = {
|
var elem = {
|
||||||
collapsed: false,
|
collapsed: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
// only whitespace -> keep element blank...
|
// only whitespace -> keep element blank...
|
||||||
if(code.trim() == ''){
|
if(code.trim() == ''){
|
||||||
elem.text = ''
|
elem.text = ''
|
||||||
return elem }
|
return elem }
|
||||||
|
|
||||||
// helpers...
|
// helpers...
|
||||||
var style = function(style, code=undefined){
|
var run = function(stage, text){
|
||||||
style = [style].flat()
|
var meth = {
|
||||||
that.__styles = [...new Set([
|
pre: '__pre_parse__',
|
||||||
...(that.__styles ?? []),
|
main: '__parse__',
|
||||||
...style,
|
post: '__post_parse__',
|
||||||
])]
|
}[stage]
|
||||||
return function(_, text){
|
return that.threadPlugins(meth, text, that, elem) }
|
||||||
elem.style ??= []
|
|
||||||
elem.style.push(...style)
|
// stage: pre...
|
||||||
return code
|
var text = run('pre',
|
||||||
?? text } }
|
// pre-sanitize...
|
||||||
var quoteText = function(text){
|
code.replace(/\x00/g, ''))
|
||||||
return text
|
// split text into parsable and non-parsable sections...
|
||||||
.replace(/(?<!\\)&/g, '&')
|
var sections = text
|
||||||
.replace(/(?<!\\)</g, '<')
|
// split fomat:
|
||||||
.replace(/(?<!\\)>/g, '>')
|
// [ text <match> <type> <body>, ... ]
|
||||||
.replace(/\\(?!`)/g, '\\\\') }
|
.split(/(<(pre|code)(?:|\s[^>]*)>((?:\n|.)*)<\/\2>)/g)
|
||||||
var quote = function(_, code){
|
// sort out the sections...
|
||||||
return `<code>${quoteText(code)}</code>` }
|
var parsable = []
|
||||||
var pre = function(_, language, code){
|
var quoted = []
|
||||||
language = language ?
|
while(sections.length > 0){
|
||||||
'language-'+language
|
var [section, match] = sections.splice(0, 4)
|
||||||
: language
|
parsable.push(section)
|
||||||
return `<pre><code class="${language}">${ quoteText(code) }</code></pre>` }
|
quoted.push(match) }
|
||||||
var table = function(_, body){
|
// stage: main...
|
||||||
return `<table><tr><td>${
|
text = run('main',
|
||||||
body
|
// parse only the parsable sections...
|
||||||
.replace(/\s*\|\s*\n\s*\|\s*/gm, '</td></tr>\n<tr><td>')
|
parsable.join('\x00'))
|
||||||
.replace(/\s*\|\s*/gm, '</td><td>')
|
.split(/\x00/g)
|
||||||
}</td></td></table>` }
|
// merge the quoted sections back in...
|
||||||
|
.map(function(section){
|
||||||
|
return [section, quoted.shift() ?? ''] })
|
||||||
|
.flat()
|
||||||
|
.join('')
|
||||||
|
// stage: post...
|
||||||
|
elem.text = run('post', text)
|
||||||
|
|
||||||
elem.text = code
|
|
||||||
// hidden attributes...
|
|
||||||
// XXX make this generic...
|
|
||||||
// collapsed...
|
|
||||||
.replace(/(\n|^)\s*collapsed::\s*(.*)\s*(\n|$)/,
|
|
||||||
function(_, value){
|
|
||||||
elem.collapsed = value.trim() == 'true'
|
|
||||||
return '' })
|
|
||||||
// id...
|
|
||||||
.replace(/(\n|^)\s*id::\s*(.*)\s*(\n|$)/,
|
|
||||||
function(_, value){
|
|
||||||
elem.id = value.trim()
|
|
||||||
return '' })
|
|
||||||
// markdown...
|
|
||||||
// style: headings...
|
|
||||||
.replace(/^(?<!\\)######\s+(.*)$/m, style('heading-6'))
|
|
||||||
.replace(/^(?<!\\)#####\s+(.*)$/m, style('heading-5'))
|
|
||||||
.replace(/^(?<!\\)####\s+(.*)$/m, style('heading-4'))
|
|
||||||
.replace(/^(?<!\\)###\s+(.*)$/m, style('heading-3'))
|
|
||||||
.replace(/^(?<!\\)##\s+(.*)$/m, style('heading-2'))
|
|
||||||
.replace(/^(?<!\\)#\s+(.*)$/m, style('heading-1'))
|
|
||||||
// style: list...
|
|
||||||
//.replace(/^(?<!\\)[-\*]\s+(.*)$/m, style('list-item'))
|
|
||||||
.replace(/^\s*(.*)(?<!\\):\s*$/m, style('list'))
|
|
||||||
.replace(/^\s*(.*)(?<!\\)#\s*$/m, style('numbered-list'))
|
|
||||||
// style: misc...
|
|
||||||
.replace(/^\s*(?<!\\)>\s+(.*)$/m, style('quote'))
|
|
||||||
.replace(/^\s*(?<!\\)((\/\/|;)\s+.*)$/m, style('comment'))
|
|
||||||
.replace(/^\s*(?<!\\)NOTE:?\s*(.*)$/m, style('NOTE'))
|
|
||||||
.replace(/^\s*(?<!\\)XXX\s+(.*)$/m, style('XXX'))
|
|
||||||
.replace(/^(.*)\s*(?<!\\)XXX$/m, style('XXX'))
|
|
||||||
.replace(/(\s*)(?<!\\)(ASAP|BUG|FIX|HACK|STUB|WARNING|CAUTION)(\s*)/gm,
|
|
||||||
'$1<span class="highlight $2">$2</span>$3')
|
|
||||||
// elements...
|
|
||||||
.replace(/(\n|^)(?<!\\)---*\h*(\n|$)/m, '$1<hr>')
|
|
||||||
// ToDo...
|
|
||||||
// NOTE: these are separate as we need to align block text
|
|
||||||
// to leading chekbox...
|
|
||||||
.replace(/^\s*(?<!\\)\[[_ ]\]\s*/m,
|
|
||||||
style('todo', '<input type="checkbox">'))
|
|
||||||
.replace(/^\s*(?<!\\)\[[Xx]\]\s*/m,
|
|
||||||
style('todo', '<input type="checkbox" checked>'))
|
|
||||||
// inline checkboxes...
|
|
||||||
.replace(/\s*(?<!\\)\[[_ ]\]\s*/gm,
|
|
||||||
style('check', '<input type="checkbox">'))
|
|
||||||
.replace(/\s*(?<!\\)\[[Xx]\]\s*/gm,
|
|
||||||
style('check', '<input type="checkbox" checked>'))
|
|
||||||
// tables...
|
|
||||||
.replace(/^\s*(?<!\\)\|\s*((.|\n)*)\s*\|\s*$/, table)
|
|
||||||
// basic styling...
|
|
||||||
// XXX revise...
|
|
||||||
.replace(/(?<!\\)\*(?=[^\s*])(([^*]|\\\*)*[^\s*])(?<!\\)\*/gm, '<b>$1</b>')
|
|
||||||
.replace(/(?<!\\)~(?=[^\s~])(([^~]|\\~)*[^\s~])(?<!\\)~/gm, '<s>$1</s>')
|
|
||||||
.replace(/(?<!\\)_(?=[^\s_])(([^_]|\\_)*[^\s_])(?<!\\)_/gm, '<i>$1</i>')
|
|
||||||
// code/quoting...
|
|
||||||
.replace(/(?<!\\)```(.*)\s*\n((\n|.)*)\h*(?<!\\)```\s*/g, pre)
|
|
||||||
.replace(/(?<!\\)`(?=[^\s])(([^`]|\\`)*[^\s])(?<!\\)`/gm, quote)
|
|
||||||
// XXX support "\==" in mark...
|
|
||||||
.replace(/(?<!\\)==(?=[^\s])(.*[^\s])(?<!\\)==/gm, '<mark>$1</mark>')
|
|
||||||
// links...
|
|
||||||
.replace(/(?<!\\)\[([^\]]*)\]\(([^)]*)\)/g, '<a href="$2">$1</a>')
|
|
||||||
.replace(/((?:https?:|ftps?:)[^\s]*)(\s*)/g, '<a href="$1">$1</a>$2')
|
|
||||||
// characters...
|
|
||||||
// XXX use ligatures for these???
|
|
||||||
.replace(/(?<!\\)\(i\)/gm, '🛈')
|
|
||||||
.replace(/(?<!\\)\(c\)/gm, '©')
|
|
||||||
.replace(/(?<!\\)\/!\\/gm, '⚠')
|
|
||||||
.replace(/(?<!\\)---(?!-)/gm, '—')
|
|
||||||
.replace(/(?<!\\)--(?!-)/gm, '–')
|
|
||||||
// quoting...
|
|
||||||
// NOTE: this must be last...
|
|
||||||
.replace(/(?<!\\)\\(.)/gm, '$1')
|
|
||||||
return elem },
|
return elem },
|
||||||
// XXX essentially here we need to remove service stuff like some
|
// XXX essentially here we need to remove service stuff like some
|
||||||
// attributes (collapsed, id, ...)...
|
// attributes (collapsed, id, ...)...
|
||||||
@ -482,7 +741,7 @@ var Outline = {
|
|||||||
text = text
|
text = text
|
||||||
.replace(/^\s*\n/, '')
|
.replace(/^\s*\n/, '')
|
||||||
text = ('\n' + text)
|
text = ('\n' + text)
|
||||||
.split(/\n(\s*)- /g)
|
.split(/\n(\s*)(?:- |-\s*$)/gm)
|
||||||
.slice(1)
|
.slice(1)
|
||||||
var tab = ' '.repeat(this.tab_size || 8)
|
var tab = ' '.repeat(this.tab_size || 8)
|
||||||
var level = function(lst, prev_sep=undefined, parent=[]){
|
var level = function(lst, prev_sep=undefined, parent=[]){
|
||||||
@ -744,9 +1003,19 @@ var Outline = {
|
|||||||
function(evt){
|
function(evt){
|
||||||
var elem = evt.target
|
var elem = evt.target
|
||||||
|
|
||||||
|
// prevent focusing parent by clicking between blocks...
|
||||||
if(elem.classList.contains('children')){
|
if(elem.classList.contains('children')){
|
||||||
return }
|
return }
|
||||||
|
|
||||||
|
// empty outline -> create new eleemnt...
|
||||||
|
if(elem.classList.contains('outline')
|
||||||
|
&& elem.children.length == 0){
|
||||||
|
// create new eleemnt and edit it...
|
||||||
|
var block = that.Block()
|
||||||
|
that.outline.append(block)
|
||||||
|
that.edit(block)
|
||||||
|
return }
|
||||||
|
|
||||||
// expand/collapse
|
// expand/collapse
|
||||||
if(elem.classList.contains('view')
|
if(elem.classList.contains('view')
|
||||||
&& elem.parentElement.getAttribute('tabindex')){
|
&& elem.parentElement.getAttribute('tabindex')){
|
||||||
@ -769,24 +1038,15 @@ var Outline = {
|
|||||||
if(elem.getAttribute('tabindex')){
|
if(elem.getAttribute('tabindex')){
|
||||||
elem.querySelector('.code').focus() }
|
elem.querySelector('.code').focus() }
|
||||||
|
|
||||||
// toggle checkbox...
|
that.runPlugins('__click__', evt, that, elem) })
|
||||||
if(elem.type == 'checkbox'){
|
// keyboard handling...
|
||||||
var node = that.get(elem)
|
|
||||||
var text = node.querySelector('.code')
|
|
||||||
// get the checkbox order...
|
|
||||||
var i = [...node.querySelectorAll('input[type=checkbox]')].indexOf(elem)
|
|
||||||
var to = elem.checked ?
|
|
||||||
'[X]'
|
|
||||||
: '[_]'
|
|
||||||
var toggle = function(m){
|
|
||||||
return i-- == 0 ?
|
|
||||||
to
|
|
||||||
: m }
|
|
||||||
text.value = text.value.replace(/\[[Xx_]\]/g, toggle) } })
|
|
||||||
// heboard handling...
|
|
||||||
outline.addEventListener('keydown',
|
outline.addEventListener('keydown',
|
||||||
function(evt){
|
function(evt){
|
||||||
var elem = evt.target
|
var elem = evt.target
|
||||||
|
// code editing...
|
||||||
|
if(elem.nodeName == 'CODE'
|
||||||
|
&& elem.getAttribute('contenteditable') == 'true'){
|
||||||
|
return }
|
||||||
// update element state...
|
// update element state...
|
||||||
if(elem.nodeName == 'TEXTAREA'){
|
if(elem.nodeName == 'TEXTAREA'){
|
||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
@ -795,6 +1055,28 @@ var Outline = {
|
|||||||
// handle keyboard...
|
// handle keyboard...
|
||||||
evt.key in that.keyboard
|
evt.key in that.keyboard
|
||||||
&& that.keyboard[evt.key].call(that, evt) })
|
&& that.keyboard[evt.key].call(that, evt) })
|
||||||
|
// update code block...
|
||||||
|
outline.addEventListener('keyup',
|
||||||
|
function(evt){
|
||||||
|
var elem = evt.target
|
||||||
|
// editable code...
|
||||||
|
if(elem.nodeName == 'CODE'
|
||||||
|
&& elem.getAttribute('contenteditable') == 'true'){
|
||||||
|
// XXX should we clear the syntax???
|
||||||
|
// XXX do this only if things changed...
|
||||||
|
delete elem.dataset.highlighted
|
||||||
|
|
||||||
|
var block = that.get(elem)
|
||||||
|
var code = block.querySelector('.code')
|
||||||
|
|
||||||
|
var update = elem.innerText
|
||||||
|
var i = [...block
|
||||||
|
.querySelectorAll('.view code[contenteditable=true]')]
|
||||||
|
.indexOf(elem)
|
||||||
|
// update element content...
|
||||||
|
code.value = quoted.replace(code.value, i, update)
|
||||||
|
|
||||||
|
return } })
|
||||||
// toggle view/code of nodes...
|
// toggle view/code of nodes...
|
||||||
outline.addEventListener('focusin',
|
outline.addEventListener('focusin',
|
||||||
function(evt){
|
function(evt){
|
||||||
@ -825,11 +1107,8 @@ var Outline = {
|
|||||||
if(node.nodeName == 'TEXTAREA'
|
if(node.nodeName == 'TEXTAREA'
|
||||||
&& node?.nextElementSibling?.nodeName == 'SPAN'){
|
&& node?.nextElementSibling?.nodeName == 'SPAN'){
|
||||||
var block = node.parentElement
|
var block = node.parentElement
|
||||||
that.update(block, { text: node.value }) }
|
that.update(block, { text: node.value })
|
||||||
|
that.runPlugins('__changed__', evt, that, node) } })
|
||||||
// XXX do a plugin...
|
|
||||||
window.hljs
|
|
||||||
&& hljs.highlightAll() })
|
|
||||||
// update .code...
|
// update .code...
|
||||||
var update_code_timeout
|
var update_code_timeout
|
||||||
outline.addEventListener('change',
|
outline.addEventListener('change',
|
||||||
@ -870,6 +1149,8 @@ var Outline = {
|
|||||||
.replace(/>/g, '>'))
|
.replace(/>/g, '>'))
|
||||||
console.log(`Parse: ${Date.now() - t}ms`)}
|
console.log(`Parse: ${Date.now() - t}ms`)}
|
||||||
|
|
||||||
|
this.runPlugins('__setup__', this)
|
||||||
|
|
||||||
return this },
|
return this },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -26,11 +26,7 @@ var setup = function(){
|
|||||||
window.editor = {
|
window.editor = {
|
||||||
__proto__: Outline,
|
__proto__: Outline,
|
||||||
}.setup(
|
}.setup(
|
||||||
document.querySelector('.editor'))
|
document.querySelector('.editor')) }
|
||||||
|
|
||||||
// XXX make this a plugin...
|
|
||||||
window.hljs
|
|
||||||
&& hljs.highlightAll() }
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
@ -56,19 +52,46 @@ var setup = function(){
|
|||||||
an we'll not get here...
|
an we'll not get here...
|
||||||
-
|
-
|
||||||
- ## ToDo:
|
- ## ToDo:
|
||||||
- ASAP: editor: bksapce/del at start/end of a block should join it with prev/next
|
- ASAP: editor: backsapce/del at start/end of a block should join it with prev/next
|
||||||
- ASAP: editor: pressing enter in text edit mode should split text into two blocks
|
- ASAP: editor: pressing enter in text edit mode should split text into two blocks
|
||||||
- ASAP: editor: shifting nodes up/down
|
- ASAP: editor: shifting nodes up/down
|
||||||
- ASAP: use \\t for indent...
|
|
||||||
- ASAP: scroll into view is bad...
|
- ASAP: scroll into view is bad...
|
||||||
- on item click, place the cursor where it was clicked before the code expanded...
|
- ASAP: need to reach checkboxes via keyboard
|
||||||
- ~editor: semi-live update styles~
|
- FEATURE: read-only mode
|
||||||
- need to reach checkboxes via keyboard
|
- FEATURE: `collapse-children:: true` block option -- when loading collapse all immediate children
|
||||||
- persistent empty first/last node (a button to create a new node)
|
- FF: figure out a way to draw expand/collapse bullets without the use of CSS' `:has(..)`
|
||||||
- add completion percentage to blocks with todo's nested
|
- Code blocks and bullets:
|
||||||
- _...use `[%]`, `%%`, or something similar..._
|
- ```
|
||||||
- read-only mode
|
code
|
||||||
- should bulets be on the same level as nodes or offset??
|
```
|
||||||
|
- _bullet should be either in the middle of the block or at the first line of code (preferred)..._
|
||||||
|
- editor as a custom element...
|
||||||
|
- Nerd fonts (option???)
|
||||||
|
- multiple node selection
|
||||||
|
- copy/paste nodes/trees
|
||||||
|
- undo
|
||||||
|
collapsed:: true
|
||||||
|
- delete node
|
||||||
|
- indent/deindent
|
||||||
|
- edit node
|
||||||
|
- FEATURE? block templates...
|
||||||
|
collapsed:: true
|
||||||
|
- something like: `TPL: [_] <editable/> -- <editable/>`
|
||||||
|
- `TPL:` -- template marker
|
||||||
|
- `<editable/>` -- field marker
|
||||||
|
- each child node will copy the template and allow editing of only fields
|
||||||
|
- not clear how to handle template changes...
|
||||||
|
- Q: can we get the caret line in a textarea???
|
||||||
|
- _...this will fix a lot of issues with moving between blocks in edit mode..._
|
||||||
|
- Q: do we use \\t for indent? (option???)
|
||||||
|
- Q: can we place the cursor on item click where it was clicked before before the code expanded?
|
||||||
|
collapsed:: true
|
||||||
|
- for example
|
||||||
|
- #### Click in this line and see where the cursor goes
|
||||||
|
- _not sure how..._
|
||||||
|
- Q: persistent empty first/last node (a button to create a new node)?
|
||||||
|
- Q: should bullets be on the same level as nodes or offset??
|
||||||
|
collapsed:: true
|
||||||
- A) justified to bullet:
|
- A) justified to bullet:
|
||||||
* list item
|
* list item
|
||||||
* list item
|
* list item
|
||||||
@ -78,8 +101,11 @@ var setup = function(){
|
|||||||
* list item
|
* list item
|
||||||
block text
|
block text
|
||||||
- NOTE: this is only a problem if making list-items manually -- disable???
|
- NOTE: this is only a problem if making list-items manually -- disable???
|
||||||
- FF: figure out a way to draw expand/collapse bullets without the use of CSS' `:has(..)`
|
- empty item height is a bit off...
|
||||||
- Nerd fonts (options?)
|
- ~Q: can we edit code in a code block directly? (a-la Logseq)~
|
||||||
|
- ~"percentage complete" in parent blocks with todo's nested~
|
||||||
|
- ~`.editor .outline:empty` view and behavior...~
|
||||||
|
- ~editor: semi-live update styles~
|
||||||
- ~do a better expand/collapse icons~
|
- ~do a better expand/collapse icons~
|
||||||
- ~loading from DOM -- fill textarea~
|
- ~loading from DOM -- fill textarea~
|
||||||
- ~focus management~
|
- ~focus management~
|
||||||
@ -89,17 +115,29 @@ var setup = function(){
|
|||||||
- ~shift subtree up/down~
|
- ~shift subtree up/down~
|
||||||
- ~create node~
|
- ~create node~
|
||||||
- ~edit node~
|
- ~edit node~
|
||||||
- multiple node selection
|
|
||||||
- copy/paste nodes/trees
|
|
||||||
- undo
|
|
||||||
collapsed:: true
|
|
||||||
- delete node
|
|
||||||
- indent/deindent
|
|
||||||
- edit node
|
|
||||||
- empty item height is a bit off...
|
|
||||||
- ~serialize/deserialize~
|
- ~serialize/deserialize~
|
||||||
- ~add optional text styling to nodes~
|
- ~add optional text styling to nodes~
|
||||||
-
|
-
|
||||||
|
- ## Refactoring:
|
||||||
|
- Plugin architecture
|
||||||
|
- Item parser (`.__code2html__(..)`)
|
||||||
|
- ~split out~
|
||||||
|
- ~define~/doc api
|
||||||
|
- ~define a way to extend/stack parsers~
|
||||||
|
- Format parser/generator
|
||||||
|
- split out
|
||||||
|
- define api
|
||||||
|
- experiment with clean markdown as format
|
||||||
|
- CSS
|
||||||
|
- separate out theming
|
||||||
|
- separate out settings
|
||||||
|
- Actions -- move user actions (code in `.keyboard`) into methods
|
||||||
|
- Move to `keyboard.js`
|
||||||
|
- Plugin architecture
|
||||||
|
- Q: do we need `features.js` and/or `actions.js`
|
||||||
|
- Q: do we need a concatenative API??
|
||||||
|
- `<block>.get() -> <block>`
|
||||||
|
-
|
||||||
- ## TEST
|
- ## TEST
|
||||||
- ### Formatting:
|
- ### Formatting:
|
||||||
- Styles
|
- Styles
|
||||||
@ -161,12 +199,13 @@ var setup = function(){
|
|||||||
- ---
|
- ---
|
||||||
- Markers: ASAP, BUG, FIX, HACK, STUB, WARNING, and CAUTION
|
- Markers: ASAP, BUG, FIX, HACK, STUB, WARNING, and CAUTION
|
||||||
- Basic task management
|
- Basic task management
|
||||||
|
- [%] Completion status
|
||||||
- Inline [X] checkboxes [_]
|
- Inline [X] checkboxes [_]
|
||||||
- To do items/blocks
|
- To do items/blocks
|
||||||
- [_] undone item
|
- [_] undone item
|
||||||
_(clicking the checkbox updates the item)_
|
_(clicking the checkbox updates the item)_
|
||||||
- [X] done item
|
- [X] done item
|
||||||
- [_] we can also add inline [x] checkboxes
|
- [_] we can also add inline [x] checkboxes and states: [%]
|
||||||
- links
|
- links
|
||||||
- [example](about:blank)
|
- [example](about:blank)
|
||||||
- https://example.com
|
- https://example.com
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user