mirror of
https://github.com/flynx/pWiki.git
synced 2025-12-25 20:31:58 +00:00
Compare commits
No commits in common. "ea00679b9fe3ba938f0da81dc4af8c2c394ea0d1" and "53ad5723697603a0d51eb729f01490f95c164d09" have entirely different histories.
ea00679b9f
...
53ad572369
@ -54,23 +54,13 @@
|
|||||||
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: 1em var(--outline-padding);
|
padding-left: var(--outline-padding);
|
||||||
padding-bottom: 1.2em
|
padding-right: var(--outline-padding);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 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;
|
||||||
@ -120,7 +110,6 @@
|
|||||||
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,
|
||||||
@ -138,7 +127,6 @@
|
|||||||
}
|
}
|
||||||
/* 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;
|
||||||
}
|
}
|
||||||
@ -150,7 +138,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.07);
|
background: rgba(0,0,0,0.1);
|
||||||
}
|
}
|
||||||
.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);
|
||||||
@ -465,11 +453,6 @@ 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 ---*/
|
||||||
@ -481,13 +464,11 @@ 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,303 +25,6 @@ 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...
|
||||||
@ -338,32 +41,6 @@ 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(){
|
||||||
@ -424,13 +101,12 @@ 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 ?
|
||||||
outline
|
node
|
||||||
: node.parentElement === outline ?
|
: node?.parentElement?.parentElement }
|
||||||
outline
|
|
||||||
: node.parentElement.parentElement }
|
|
||||||
var children = function(node){
|
var children = function(node){
|
||||||
return node === outline ?
|
return node === outline ?
|
||||||
[...node.children]
|
[...node.children]
|
||||||
@ -554,13 +230,13 @@ var Outline = {
|
|||||||
indent = node
|
indent = node
|
||||||
node = 'focused' }
|
node = 'focused' }
|
||||||
var cur = this.get(node)
|
var cur = this.get(node)
|
||||||
if(!cur){
|
if(!cur){
|
||||||
return }
|
return }
|
||||||
var siblings = this.get(node, 'siblings')
|
var siblings = this.get(node, 'siblings')
|
||||||
// deindent...
|
// deindent...
|
||||||
if(!indent){
|
if(!indent){
|
||||||
var parent = this.get(node, 'parent')
|
var parent = this.get(node, 'parent')
|
||||||
if(parent != this.outline){
|
if(!parent.classList.contains('.outline')){
|
||||||
var children = siblings
|
var children = siblings
|
||||||
.slice(siblings.indexOf(cur)+1)
|
.slice(siblings.indexOf(cur)+1)
|
||||||
parent.after(cur)
|
parent.after(cur)
|
||||||
@ -636,50 +312,115 @@ 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 run = function(stage, text){
|
var style = function(style, code=undefined){
|
||||||
var meth = {
|
style = [style].flat()
|
||||||
pre: '__pre_parse__',
|
that.__styles = [...new Set([
|
||||||
main: '__parse__',
|
...(that.__styles ?? []),
|
||||||
post: '__post_parse__',
|
...style,
|
||||||
}[stage]
|
])]
|
||||||
return that.threadPlugins(meth, text, that, elem) }
|
return function(_, text){
|
||||||
|
elem.style ??= []
|
||||||
// stage: pre...
|
elem.style.push(...style)
|
||||||
var text = run('pre',
|
return code
|
||||||
// pre-sanitize...
|
?? text } }
|
||||||
code.replace(/\x00/g, ''))
|
var quoteText = function(text){
|
||||||
// split text into parsable and non-parsable sections...
|
return text
|
||||||
var sections = text
|
.replace(/(?<!\\)&/g, '&')
|
||||||
// split fomat:
|
.replace(/(?<!\\)</g, '<')
|
||||||
// [ text <match> <type> <body>, ... ]
|
.replace(/(?<!\\)>/g, '>')
|
||||||
.split(/(<(pre|code)(?:|\s[^>]*)>((?:\n|.)*)<\/\2>)/g)
|
.replace(/\\(?!`)/g, '\\\\') }
|
||||||
// sort out the sections...
|
var quote = function(_, code){
|
||||||
var parsable = []
|
return `<code>${quoteText(code)}</code>` }
|
||||||
var quoted = []
|
var pre = function(_, language, code){
|
||||||
while(sections.length > 0){
|
language = language ?
|
||||||
var [section, match] = sections.splice(0, 4)
|
'language-'+language
|
||||||
parsable.push(section)
|
: language
|
||||||
quoted.push(match) }
|
return `<pre><code class="${language}">${ quoteText(code) }</code></pre>` }
|
||||||
// stage: main...
|
var table = function(_, body){
|
||||||
text = run('main',
|
return `<table><tr><td>${
|
||||||
// parse only the parsable sections...
|
body
|
||||||
parsable.join('\x00'))
|
.replace(/\s*\|\s*\n\s*\|\s*/gm, '</td></tr>\n<tr><td>')
|
||||||
.split(/\x00/g)
|
.replace(/\s*\|\s*/gm, '</td><td>')
|
||||||
// merge the quoted sections back in...
|
}</td></td></table>` }
|
||||||
.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, ...)...
|
||||||
@ -741,7 +482,7 @@ var Outline = {
|
|||||||
text = text
|
text = text
|
||||||
.replace(/^\s*\n/, '')
|
.replace(/^\s*\n/, '')
|
||||||
text = ('\n' + text)
|
text = ('\n' + text)
|
||||||
.split(/\n(\s*)(?:- |-\s*$)/gm)
|
.split(/\n(\s*)- /g)
|
||||||
.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=[]){
|
||||||
@ -1003,19 +744,9 @@ 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')){
|
||||||
@ -1038,15 +769,24 @@ var Outline = {
|
|||||||
if(elem.getAttribute('tabindex')){
|
if(elem.getAttribute('tabindex')){
|
||||||
elem.querySelector('.code').focus() }
|
elem.querySelector('.code').focus() }
|
||||||
|
|
||||||
that.runPlugins('__click__', evt, that, elem) })
|
// toggle checkbox...
|
||||||
// keyboard handling...
|
if(elem.type == 'checkbox'){
|
||||||
|
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(){
|
||||||
@ -1055,28 +795,6 @@ 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){
|
||||||
@ -1107,8 +825,11 @@ 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',
|
||||||
@ -1149,8 +870,6 @@ 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,7 +26,11 @@ 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>
|
||||||
@ -52,46 +56,19 @@ var setup = function(){
|
|||||||
an we'll not get here...
|
an we'll not get here...
|
||||||
-
|
-
|
||||||
- ## ToDo:
|
- ## ToDo:
|
||||||
- ASAP: editor: backsapce/del at start/end of a block should join it with prev/next
|
- ASAP: editor: bksapce/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...
|
||||||
- ASAP: need to reach checkboxes via keyboard
|
- on item click, place the cursor where it was clicked before the code expanded...
|
||||||
- FEATURE: read-only mode
|
- ~editor: semi-live update styles~
|
||||||
- FEATURE: `collapse-children:: true` block option -- when loading collapse all immediate children
|
- need to reach checkboxes via keyboard
|
||||||
- FF: figure out a way to draw expand/collapse bullets without the use of CSS' `:has(..)`
|
- persistent empty first/last node (a button to create a new node)
|
||||||
- Code blocks and bullets:
|
- add completion percentage to blocks with todo's nested
|
||||||
- ```
|
- _...use `[%]`, `%%`, or something similar..._
|
||||||
code
|
- read-only mode
|
||||||
```
|
- 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
|
||||||
@ -101,11 +78,8 @@ 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???
|
||||||
- empty item height is a bit off...
|
- FF: figure out a way to draw expand/collapse bullets without the use of CSS' `:has(..)`
|
||||||
- ~Q: can we edit code in a code block directly? (a-la Logseq)~
|
- Nerd fonts (options?)
|
||||||
- ~"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~
|
||||||
@ -115,29 +89,17 @@ 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
|
||||||
@ -199,13 +161,12 @@ 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