mirror of
https://github.com/flynx/pWiki.git
synced 2025-10-30 02:20:08 +00:00
logseq-like format parsing + markdown (stub) + ff support...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
parent
697a9d8347
commit
af3f10e35b
@ -52,13 +52,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* show/hide node's view/code... */
|
/* show/hide node's view/code... */
|
||||||
.editor .outline [tabindex]>span+textarea:not(:focus),
|
|
||||||
.editor .outline [tabindex]:has(>span+textarea:focus)>span:first-child {
|
|
||||||
position: absolute;
|
|
||||||
opacity: 0;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor .outline [tabindex]>textarea:focus+span,
|
.editor .outline [tabindex]>textarea:focus+span,
|
||||||
.editor .outline [tabindex]>textarea:not(:focus) {
|
.editor .outline [tabindex]>textarea:not(:focus) {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@ -82,19 +82,6 @@ var Outline = {
|
|||||||
return this.dom.querySelector('.toolbar') },
|
return this.dom.querySelector('.toolbar') },
|
||||||
|
|
||||||
|
|
||||||
// XXX revise name...
|
|
||||||
Block: function(place=none){
|
|
||||||
var block = document.createElement('div')
|
|
||||||
block.setAttribute('tabindex', '0')
|
|
||||||
block.append(
|
|
||||||
document.createElement('textarea')
|
|
||||||
.autoUpdateSize(),
|
|
||||||
document.createElement('span'))
|
|
||||||
var cur = this.get()
|
|
||||||
place && cur
|
|
||||||
&& cur[place](block)
|
|
||||||
return block },
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// .get([<offset>])
|
// .get([<offset>])
|
||||||
// .get('focused'[, <offset>])
|
// .get('focused'[, <offset>])
|
||||||
@ -287,28 +274,47 @@ var Outline = {
|
|||||||
return this },
|
return this },
|
||||||
|
|
||||||
// block serialization...
|
// block serialization...
|
||||||
|
// XXX these should be symetrical...
|
||||||
__code2html__: function(code){
|
__code2html__: function(code){
|
||||||
return code
|
var elem = {
|
||||||
.replace(/\n\s*/g, '<br>')
|
collapsed: false,
|
||||||
|
}
|
||||||
|
elem.text = code
|
||||||
|
// attributes...
|
||||||
|
// XXX make this generic...
|
||||||
|
.replace(/\n\s*collapsed::\s*(.*)\s*$/,
|
||||||
|
function(_, value){
|
||||||
|
elem.collapsed = value.trim() == 'true'
|
||||||
|
return '' })
|
||||||
|
// markdown...
|
||||||
// XXX STUB...
|
// XXX STUB...
|
||||||
.replace(/^# (.*)\s*$/g, '<h1>$1</h1>')
|
.replace(/^#\s*(.*)\s*(\n|$)/, '<h1>$1</h1>')
|
||||||
.replace(/^## (.*)\s*$/g, '<h2>$1</h2>')
|
.replace(/^##\s*(.*)\s*(\n|$)/, '<h2>$1</h2>')
|
||||||
.replace(/^### (.*)\s*$/g, '<h3>$1</h3>')
|
.replace(/^###\s*(.*)\s*(\n|$)/, '<h3>$1</h3>')
|
||||||
.replace(/^#### (.*)\s*$/g, '<h4>$1</h4>')
|
.replace(/^####\s*(.*)\s*(\n|$)/, '<h4>$1</h4>')
|
||||||
|
.replace(/^#####\s*(.*)\s*(\n|$)/, '<h5>$1</h5>')
|
||||||
|
.replace(/^######\s*(.*)\s*(\n|$)/, '<h6>$1</h6>')
|
||||||
.replace(/\*(.*)\*/g, '<b>$1</b>')
|
.replace(/\*(.*)\*/g, '<b>$1</b>')
|
||||||
.replace(/~([^~]*)~/g, '<s>$1</s>')
|
.replace(/~([^~]*)~/g, '<s>$1</s>')
|
||||||
.replace(/_([^_]*)_/g, '<i>$1</i>') },
|
.replace(/_([^_]*)_/g, '<i>$1</i>')
|
||||||
|
.replace(/(\n|^)---*\h*(\n|$)/, '$1<hr>')
|
||||||
|
.replace(/\n/g, '<br>\n')
|
||||||
|
return elem },
|
||||||
__html2code__: function(html){
|
__html2code__: function(html){
|
||||||
return html
|
return html
|
||||||
.replace(/<br>\s*/g, '\n')
|
|
||||||
// XXX STUB...
|
// XXX STUB...
|
||||||
.replace(/^<h1>(.*)<\/h1>\s*$/g, '# $1')
|
.replace(/<hr>$/, '---')
|
||||||
.replace(/^<h2>(.*)<\/h2>\s*$/g, '## $1')
|
.replace(/<hr>/, '---\n')
|
||||||
.replace(/^<h3>(.*)<\/h3>\s*$/g, '### $1')
|
.replace(/^<h1>(.*)<\/h1>\s*(.*)$/g, '# $1\n$2')
|
||||||
.replace(/^<h4>(.*)<\/h4>\s*$/g, '#### $1')
|
.replace(/^<h2>(.*)<\/h2>\s*(.*)$/g, '## $1\n$2')
|
||||||
|
.replace(/^<h3>(.*)<\/h3>\s*(.*)$/g, '### $1\n$2')
|
||||||
|
.replace(/^<h4>(.*)<\/h4>\s*(.*)$/g, '#### $1\n$2')
|
||||||
|
.replace(/^<h5>(.*)<\/h5>\s*(.*)$/g, '##### $1\n$2')
|
||||||
|
.replace(/^<h6>(.*)<\/h6>\s*(.*)$/g, '###### $1\n$2')
|
||||||
.replace(/<b>(.*)<\/b>/g, '*$1*')
|
.replace(/<b>(.*)<\/b>/g, '*$1*')
|
||||||
.replace(/<s>(.*)<\/s>/g, '~$1~')
|
.replace(/<s>(.*)<\/s>/g, '~$1~')
|
||||||
.replace(/<i>(.*)<\/i>/g, '_$1_') },
|
.replace(/<i>(.*)<\/i>/g, '_$1_')
|
||||||
|
.replace(/<br>\s*/g, '\n') },
|
||||||
|
|
||||||
// serialization...
|
// serialization...
|
||||||
json: function(node){
|
json: function(node){
|
||||||
@ -341,23 +347,73 @@ var Outline = {
|
|||||||
+ elem.text
|
+ elem.text
|
||||||
.replace(/\n/g, '\n'+ level +' ')
|
.replace(/\n/g, '\n'+ level +' ')
|
||||||
+'\n'
|
+'\n'
|
||||||
|
+ (elem.collapsed ?
|
||||||
|
level+' ' + 'collapsed:: true\n'
|
||||||
|
: '')
|
||||||
+ this.text(elem.children || [], indent, level+indent) }
|
+ this.text(elem.children || [], indent, level+indent) }
|
||||||
return text },
|
return text },
|
||||||
|
|
||||||
|
parse: function(text){
|
||||||
|
text = ('\n' + text)
|
||||||
|
.split(/\n(\s*)- /g)
|
||||||
|
.slice(1)
|
||||||
|
var level = function(lst, prev_sep=undefined, parent=[]){
|
||||||
|
while(lst.length > 0){
|
||||||
|
sep = lst[0]
|
||||||
|
// deindent...
|
||||||
|
if(prev_sep != null
|
||||||
|
&& sep.length < prev_sep.length){
|
||||||
|
break }
|
||||||
|
prev_sep ??= sep
|
||||||
|
// same level...
|
||||||
|
if(sep.length == prev_sep.length){
|
||||||
|
var [_, block] = lst.splice(0, 2)
|
||||||
|
var collapsed = false
|
||||||
|
block = block
|
||||||
|
.replace(/\n\s*collapsed::\s*(.*)\s*$/,
|
||||||
|
function(_, value){
|
||||||
|
collapsed = value == 'true'
|
||||||
|
return '' })
|
||||||
|
parent.push({
|
||||||
|
text: block,
|
||||||
|
collapsed,
|
||||||
|
children: [],
|
||||||
|
})
|
||||||
|
// indent...
|
||||||
|
} else {
|
||||||
|
parent.at(-1).children = level(lst, sep) } }
|
||||||
|
return parent }
|
||||||
|
return level(text) },
|
||||||
|
|
||||||
|
// XXX revise name...
|
||||||
|
Block: function(data={}, place=null){
|
||||||
|
if(typeof(data) != 'object'){
|
||||||
|
place = data
|
||||||
|
data = {} }
|
||||||
|
var block = document.createElement('div')
|
||||||
|
block.setAttribute('tabindex', '0')
|
||||||
|
data.collapsed
|
||||||
|
&& block.setAttribute('collapsed', '')
|
||||||
|
var text
|
||||||
|
var html
|
||||||
|
block.append(
|
||||||
|
text = document.createElement('textarea')
|
||||||
|
.autoUpdateSize(),
|
||||||
|
html = document.createElement('span'))
|
||||||
|
if(data.text){
|
||||||
|
text.value = data.text
|
||||||
|
html.innerHTML = this.__code2html__ ?
|
||||||
|
this.__code2html__(data.text)
|
||||||
|
: data.text }
|
||||||
|
var cur = this.get()
|
||||||
|
place && cur
|
||||||
|
&& cur[place](block)
|
||||||
|
return block },
|
||||||
// XXX use .__code2html__(..)
|
// XXX use .__code2html__(..)
|
||||||
load: function(data){
|
load: function(data){
|
||||||
// text...
|
data = typeof(data) == 'string' ?
|
||||||
if(typeof(data) == 'string'){
|
this.parse(data)
|
||||||
// XXX
|
: data
|
||||||
data = data.split(/\n(\s*)- /g)
|
|
||||||
var level = function(lst){
|
|
||||||
while(lst.length > 0){
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// json...
|
|
||||||
// XXX
|
|
||||||
|
|
||||||
// generate dom...
|
// generate dom...
|
||||||
// XXX
|
// XXX
|
||||||
return this },
|
return this },
|
||||||
@ -372,6 +428,8 @@ var Outline = {
|
|||||||
if(edited){
|
if(edited){
|
||||||
if(!atLine(edited, 0)){
|
if(!atLine(edited, 0)){
|
||||||
return }
|
return }
|
||||||
|
/*/
|
||||||
|
//*/
|
||||||
state = 'edited' }
|
state = 'edited' }
|
||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
this.get(state, -1)?.focus() },
|
this.get(state, -1)?.focus() },
|
||||||
@ -381,16 +439,22 @@ var Outline = {
|
|||||||
if(edited){
|
if(edited){
|
||||||
if(!atLine(edited, -1)){
|
if(!atLine(edited, -1)){
|
||||||
return }
|
return }
|
||||||
//window.getSelection()
|
|
||||||
state = 'edited' }
|
state = 'edited' }
|
||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
this.get(state, 1)?.focus() },
|
this.get(state, 1)?.focus() },
|
||||||
|
|
||||||
// horizontal navigation / collapse...
|
// horizontal navigation / collapse...
|
||||||
// XXX if at start/end of element move to prev/next...
|
|
||||||
ArrowLeft: function(evt){
|
ArrowLeft: function(evt){
|
||||||
if(this.outline.querySelector('textarea:focus')){
|
var edited = this.get('edited')
|
||||||
// XXX if at end of element move to next...
|
if(edited){
|
||||||
|
// move caret to prev element...
|
||||||
|
if(edited.selectionStart == edited.selectionEnd
|
||||||
|
&& edited.selectionStart == 0){
|
||||||
|
evt.preventDefault()
|
||||||
|
edited = this.get('edited', 'prev')
|
||||||
|
edited.focus()
|
||||||
|
edited.selectionStart =
|
||||||
|
edited.selectionEnd = edited.value.length + 1 }
|
||||||
return }
|
return }
|
||||||
;((this.left_key_collapses
|
;((this.left_key_collapses
|
||||||
|| evt.shiftKey)
|
|| evt.shiftKey)
|
||||||
@ -399,8 +463,16 @@ var Outline = {
|
|||||||
this.toggleCollapse(true)
|
this.toggleCollapse(true)
|
||||||
: this.get('parent')?.focus() },
|
: this.get('parent')?.focus() },
|
||||||
ArrowRight: function(evt){
|
ArrowRight: function(evt){
|
||||||
if(this.outline.querySelector('textarea:focus')){
|
var edited = this.get('edited')
|
||||||
// XXX if at end of element move to next...
|
if(edited){
|
||||||
|
// move caret to next element...
|
||||||
|
if(edited.selectionStart == edited.selectionEnd
|
||||||
|
&& edited.selectionStart == edited.value.length){
|
||||||
|
evt.preventDefault()
|
||||||
|
edited = this.get('edited', 'next')
|
||||||
|
edited.focus()
|
||||||
|
edited.selectionStart =
|
||||||
|
edited.selectionEnd = 0 }
|
||||||
return }
|
return }
|
||||||
if(this.right_key_expands){
|
if(this.right_key_expands){
|
||||||
this.toggleCollapse(false)
|
this.toggleCollapse(false)
|
||||||
@ -499,10 +571,13 @@ var Outline = {
|
|||||||
var node = evt.target
|
var node = evt.target
|
||||||
if(node.nodeName == 'TEXTAREA'
|
if(node.nodeName == 'TEXTAREA'
|
||||||
&& node?.nextElementSibling?.nodeName == 'SPAN'){
|
&& node?.nextElementSibling?.nodeName == 'SPAN'){
|
||||||
node.nextElementSibling.innerHTML =
|
if(that.__code2html__){
|
||||||
that.__code2html__ ?
|
var data = that.__code2html__(node.value)
|
||||||
that.__code2html__(node.value)
|
node.nextElementSibling.innerHTML = data.text
|
||||||
: node.value } })
|
data.collapsed
|
||||||
|
&& node.parentElement.setAttribute('collapsed', '')
|
||||||
|
} else {
|
||||||
|
node.nextElementSibling.innerHTML = node.value } } })
|
||||||
|
|
||||||
// toolbar...
|
// toolbar...
|
||||||
var toolbar = this.toolbar
|
var toolbar = this.toolbar
|
||||||
|
|||||||
@ -15,20 +15,52 @@ HTMLTextAreaElement.prototype.autoUpdateSize = function(){
|
|||||||
that.updateSize() })
|
that.updateSize() })
|
||||||
return this }
|
return this }
|
||||||
|
|
||||||
|
// calculate number of lines in text area (both wrapped and actual lines)
|
||||||
|
Object.defineProperty(HTMLTextAreaElement.prototype, 'heightLines', {
|
||||||
|
enumerable: false,
|
||||||
|
get: function(){
|
||||||
|
var style = getComputedStyle(this)
|
||||||
|
return Math.floor(
|
||||||
|
(this.scrollHeight
|
||||||
|
- parseFloat(style.paddingTop)
|
||||||
|
- parseFloat(style.paddingBottom))
|
||||||
|
/ (parseFloat(style.lineHeight)
|
||||||
|
|| parseFloat(style.fontSize))) }, })
|
||||||
|
Object.defineProperty(HTMLTextAreaElement.prototype, 'lines', {
|
||||||
|
enumerable: false,
|
||||||
|
get: function(){
|
||||||
|
return this.value
|
||||||
|
.split(/\n/g)
|
||||||
|
.length }, })
|
||||||
|
// XXX this does not account for wrapping...
|
||||||
Object.defineProperty(HTMLTextAreaElement.prototype, 'caretLine', {
|
Object.defineProperty(HTMLTextAreaElement.prototype, 'caretLine', {
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
get: function(){
|
get: function(){
|
||||||
var offset = this.selectionStart
|
var offset = this.selectionStart
|
||||||
console.log('---', this)
|
|
||||||
return offset != null ?
|
return offset != null ?
|
||||||
this.value
|
this.value
|
||||||
.slice(0, offset)
|
.slice(0, offset)
|
||||||
.split(/\n/g)
|
.split(/\n/g)
|
||||||
.length
|
.length
|
||||||
: undefined },
|
: undefined }, })
|
||||||
|
|
||||||
|
|
||||||
|
Object.defineProperty(HTMLTextAreaElement.prototype, 'caretOffset', {
|
||||||
|
enumerable: false,
|
||||||
|
get: function(){
|
||||||
|
var offset = this.selectionStart
|
||||||
|
var r = document.createRange()
|
||||||
|
r.setStart(this, offset)
|
||||||
|
r.setEnd(this, offset)
|
||||||
|
var rect = r.getBoundingClientRect()
|
||||||
|
return {
|
||||||
|
top: rect.top,
|
||||||
|
left: rect.left,
|
||||||
|
} },
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**********************************************************************
|
/**********************************************************************
|
||||||
* vim:set ts=4 sw=4 : */
|
* vim:set ts=4 sw=4 : */
|
||||||
|
|||||||
@ -69,6 +69,10 @@ var setup = function(){
|
|||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
TODO:
|
TODO:
|
||||||
|
- caret
|
||||||
|
- <s>go to next/prev element's start/end when moving off last/first char</s>
|
||||||
|
- handle up/down on wrapped blocks
|
||||||
|
<i>...can't seem to get caret line in a non-hacky way</i>
|
||||||
- persistent empty first/last node (a button to create a new node)
|
- persistent empty first/last node (a button to create a new node)
|
||||||
- loading from DOM -- fill textarea
|
- loading from DOM -- fill textarea
|
||||||
- Firefox compatibility -- remove ':has(..)'
|
- Firefox compatibility -- remove ':has(..)'
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user