@@ -9,6 +9,7 @@
{ id : 'to_do' , name : 'To-do' , icon : '☐' } , { id : 'toggle' , name : 'Toggle' , icon : '▶' } , { id : 'quote' , name : 'Quote' , icon : '❝' } ,
{ id : 'callout' , name : 'Callout' , icon : 'lightbulb' } , { id : 'table_of_contents' , name : 'Table of contents' , icon : '☰' } , { id : 'math' , name : 'Math equation' , icon : '∑' } ,
{ id : 'columns' , name : 'Column list' , icon : '▮' } , { id : 'divider' , name : 'Divider' , icon : '—' } , { id : 'code' , name : 'Code block' , icon : '<>' } ,
{ id : 'table' , name : 'Table' , icon : '⊞' } ,
] } , { name : 'MEDIA' , items : [ { id : 'image' , name : 'Image' , icon : 'image' } ] } ] ;
let _bid = 0 ; function genId ( ) { return 'b' + ( ++ _bid ) + '_' + Date . now ( ) . toString ( 36 ) ; }
@@ -19,6 +20,120 @@
const CALLOUT _ICONS = [ '💡' , '💡' , '📌' , '⚠️' , 'ℹ ️' , '✅' , '❌' , '🚀' , '🔔' , '📣' , '⭐' , '❤️' , '🔥' , '💬' , '👀' , '🧠' , '🎯' , '🔑' , '📅' , '💰' , '💡' , '📚' , '🧪' , '🌍' ] ;
/* ═══════════ Simple Table block (v4.8.0) ═══════════ */
function _tblSplitLine ( s ) { s = ( s || '' ) . trim ( ) ; if ( s . charAt ( 0 ) === '|' ) s = s . slice ( 1 ) ; if ( s . charAt ( s . length - 1 ) === '|' ) s = s . slice ( 0 , - 1 ) ; return s . split ( '|' ) . map ( function ( x ) { return x . trim ( ) ; } ) ; }
function _tblIsSep ( s ) { s = ( s || '' ) . trim ( ) ; if ( ! s || s . charAt ( 0 ) !== '|' ) return false ; var cells = _tblSplitLine ( s ) ; if ( ! cells . length ) return false ; return cells . every ( function ( c ) { return /^:?-{1,}:?$/ . test ( c ) ; } ) ; }
function _tblIsData ( s ) { s = ( s || '' ) . trim ( ) ; return /^\|/ . test ( s ) && s . indexOf ( '|' , 1 ) >= 0 ; }
function _tblNew ( rows , cols ) { var out = [ ] ; for ( var i = 0 ; i < rows ; i ++ ) { var r = new Array ( cols ) ; for ( var j = 0 ; j < cols ; j ++ ) r [ j ] = '' ; out . push ( r ) ; } return out ; }
function _tblClone ( b ) { b . rows = ( b . rows || [ ] ) . map ( function ( r ) { return ( r || [ ] ) . slice ( ) ; } ) ; return b ; }
function _tblAddCol ( b , index ) { _tblClone ( b ) ; var w = b . rows . length ? b . rows [ 0 ] . length : 1 ; if ( index < 0 ) index = 0 ; if ( index > w ) index = w ; b . rows . forEach ( function ( r ) { r . splice ( index , 0 , '' ) ; } ) ; }
function _tblAddRow ( b , index ) { _tblClone ( b ) ; var w = b . rows . length && b . rows [ 0 ] ? b . rows [ 0 ] . length : 1 ; if ( index < 0 ) index = 0 ; if ( index > b . rows . length ) index = b . rows . length ; b . rows . splice ( index , _tblNew ( 1 , w ) [ 0 ] ) ; }
function _tblDupCol ( b , index ) { _tblClone ( b ) ; b . rows . forEach ( function ( r ) { var v = ( index < r . length ) ? r [ index ] : '' ; var at = index + 1 > r . length ? r . length : index + 1 ; r . splice ( at , 0 , v ) ; } ) ; }
function _tblDupRow ( b , index ) { _tblClone ( b ) ; if ( index < 0 || index > b . rows . length ) return ; b . rows . splice ( index + 1 , 0 , ( b . rows [ index ] || [ ] ) . slice ( ) ) ; }
function _tblClearCol ( b , index ) { _tblClone ( b ) ; b . rows . forEach ( function ( r ) { if ( r && index < r . length ) r [ index ] = '' ; } ) ; }
function _tblClearRow ( b , index ) { _tblClone ( b ) ; var w = b . rows [ 0 ] ? b . rows [ 0 ] . length : 1 ; if ( index >= 0 && index < b . rows . length ) b . rows [ index ] = _tblNew ( 1 , w ) [ 0 ] ; }
function _tblDelCol ( b , index ) { _tblClone ( b ) ; if ( ! b . rows . length ) return ; if ( b . rows [ 0 ] . length <= 1 ) return ; b . rows . forEach ( function ( r ) { if ( index >= 0 && index < r . length ) r . splice ( index , 1 ) ; } ) ; }
function _tblDelRow ( b , index ) { _tblClone ( b ) ; if ( b . rows . length <= 1 ) return ; if ( index >= 0 && index < b . rows . length ) b . rows . splice ( index , 1 ) ; }
function _tblGrip ( ) { return '<svg viewBox="0 0 16 16" width="12" height="12" style="display:block"><circle cx="4" cy="3" r="1.4"/><circle cx="11" cy="3" r="1.4"/><circle cx="4" cy="8" r="1.4"/><circle cx="11" cy="8" r="1.4"/><circle cx="4" cy="13" r="1.4"/><circle cx="11" cy="13" r="1.4"/></svg>' ; }
function _tblDefault ( ) { return { rows : _tblNew ( 3 , 3 ) } ; }
function _tblFocus ( bid , r , c ) { var sel = document . querySelector ( '.ftable-editor[data-bid="' + bid + '"]' ) ; if ( ! sel ) return ; var q = '[data-c="' + c + '"]' ; var cell = sel . querySelector ( q ) ; if ( ! cell ) { var first = sel . querySelector ( '.ftable-cell[data-r]' ) ; if ( first ) { first . focus ( ) ; ce ( first ) ; } return ; } cell . focus ( ) ; if ( r === undefined ) cs ( cell ) ; else ce ( cell ) ; }
function renderTableBlock ( b , idx ) {
var rows = ( b && Array . isArray ( b . rows ) && b . rows . length ) ? b . rows : _tblNew ( 2 , 2 ) ;
var w = 0 ; rows . forEach ( function ( r ) { if ( r && r . length > w ) w = r . length ; } ) ; if ( w < 1 ) w = 2 ;
var bid = b . id ; var h = '' ;
h += '<div class="block-wrapper"><div class="block-content ftable-editor" data-bid="' + bid + '">' ;
h += '<div class="ftable-colbar">' ;
for ( var c = 0 ; c < w ; c ++ ) {
h += '<div class="ftable-colcell"><button type="button" class="ftable-colgrip" data-tbl="colmenu" data-bid="' + bid + '" data-col="' + c + '" title="Column options">' + _tblGrip ( ) + '</button></div>' ;
}
h += '</div>' ;
h += '<table class="ftable ftable-edit"><tbody>' ;
for ( var ri = 0 ; ri < rows . length ; ri ++ ) {
var isHead = ( ri === 0 ) ; var tag = isHead ? 'th' : 'td' ;
h += '<tr class="ftable-row">' ;
for ( var ci = 0 ; ci < w ; ci ++ ) {
var v = rows [ ri ] && ci < rows [ ri ] . length ? rows [ ri ] [ ci ] : '' ;
h += '<' + tag + ' class="ftable-cell' + ( isHead ? ' head' : '' ) + '" contenteditable="true" data-r="' + ri + '" data-c="' + ci + '" spellcheck="false">' + esc ( v ) + '</' + tag + '>' ;
}
h += '</tr>' ;
}
h += '</tbody></table>' ;
h += '<div class="ftable-rowbar"><button type="button" class="ftable-addrow" data-tbl="rowadd" data-bid="' + bid + '" title="Click to add a row">+ Row</button></div>' ;
h += '</div></div>' ;
return h ;
}
function _tblBlockById ( bid ) { var e = window . E ; if ( ! e || ! e . blocks ) return null ; for ( var i = 0 ; i < e . blocks . length ; i ++ ) { if ( e . blocks [ i ] . id === bid ) return e . blocks [ i ] ; } return null ; }
function _tblIdxByBid ( bid ) { var e = window . E ; if ( ! e || ! e . blocks ) return - 1 ; for ( var i = 0 ; i < e . blocks . length ; i ++ ) { if ( e . blocks [ i ] . id === bid ) return i ; } return - 1 ; }
function _tblMenuClose ( ) { var m = document . getElementById ( '_tblMenu' ) ; if ( m ) { m . parentNode && m . parentNode . removeChild ( m ) ; } if ( window . _tblCur ) window . _tblCur = null ; }
function _tblMenuOpen ( x , y , bid , kind , index ) {
_tblMenuClose ( ) ;
var items = [ ] ;
if ( kind === 'col' ) {
items = [ [ 'col-insert-left' , 'Insert left' ] , [ 'col-insert-right' , 'Insert right' ] , [ 'col-dup' , 'Duplicate' ] , null , [ 'col-clear' , 'Clear contents' ] , [ 'col-del' , 'Delete column' , 1 ] , null , [ 'tbl-del' , 'Delete table' , 1 ] ] ;
} else {
items = [ [ 'row-insert-above' , 'Insert above' ] , [ 'row-insert-below' , 'Insert below' ] , [ 'row-dup' , 'Duplicate' ] , null , [ 'row-clear' , 'Clear row' ] , [ 'row-del' , 'Delete row' , 1 ] , null , [ 'tbl-del' , 'Delete table' , 1 ] ] ;
}
var d = document . createElement ( 'div' ) ; d . className = 'ftable-menu' ; d . id = '_tblMenu' ; d . style . cssText = 'position:fixed;z-index:3000;' ;
var html = '' ;
items . forEach ( function ( it ) {
if ( ! it ) { html += '<div class="ftable-menu-sep"></div>' ; return ; }
html += '<div class="ftable-menu-item' + ( it [ 2 ] ? ' danger' : '' ) + '" data-tbl-act="' + it [ 0 ] + '">' + it [ 1 ] + '</div>' ;
} ) ;
d . innerHTML = html ;
document . body . appendChild ( d ) ;
var r = d . getBoundingClientRect ( ) ;
x = Math . min ( Math . max ( 8 , x ) , window . innerWidth - r . width - 8 ) ;
y = Math . min ( Math . max ( 8 , y ) , window . innerHeight - r . height - 8 ) ;
d . style . left = x + 'px' ; d . style . top = y + 'px' ;
window . _tblCur = { bid : bid , kind : kind , index : index } ;
}
function _tblDo ( action , cur ) {
if ( ! cur ) return ;
var bid = cur . bid , index = cur . index , b = _tblBlockById ( bid ) , e = window . E ;
if ( ! b || ! e ) return ;
e . sync ( ) ;
if ( ! ( b . rows && b . rows . length ) ) b . rows = _tblNew ( 2 , 2 ) ;
var focusR = 1 , focusC = 0 ;
if ( action === 'col-insert-left' ) { _tblAddCol ( b , index ) ; focusC = index ; }
else if ( action === 'col-insert-right' ) { _tblAddCol ( b , index + 1 ) ; focusC = index + 1 ; }
else if ( action === 'col-dup' ) { _tblDupCol ( b , index ) ; focusC = index + 1 ; }
else if ( action === 'col-clear' ) { _tblClearCol ( b , index ) ; focusC = index ; }
else if ( action === 'col-del' ) { _tblDelCol ( b , index ) ; focusC = Math . min ( index , b . rows [ 0 ] . length - 1 ) ; }
else if ( action === 'row-insert-above' ) { _tblAddRow ( b , index ) ; focusR = index ; }
else if ( action === 'row-insert-below' ) { _tblAddRow ( b , index + 1 ) ; focusR = index + 1 ; }
else if ( action === 'row-dup' ) { _tblDupRow ( b , index ) ; focusR = index + 1 ; }
else if ( action === 'row-clear' ) { _tblClearRow ( b , index ) ; focusR = index ; }
else if ( action === 'row-del' ) { _tblDelRow ( b , index ) ; focusR = Math . min ( index , b . rows . length - 1 ) ; if ( focusR < 1 ) focusR = 1 ; }
else if ( action === 'tbl-del' ) { _tblMenuClose ( ) ; var bi = _tblIdxByBid ( bid ) ; if ( bi >= 0 ) e . removeBlock ( bi ) ; return ; }
e . render ( ) ; e . dirty = true ; e . autoSave ( ) ;
_tblFocus ( bid , focusR , focusC ) ;
}
function _tblInitDelegate ( ) {
if ( window . _tblDelegate ) return ; window . _tblDelegate = true ;
document . addEventListener ( 'click' , function ( ev ) {
if ( ev . target . closest && ev . target . closest ( '#_tblMenu' ) ) { var it = ev . target . closest ( '.ftable-menu-item' ) ; if ( it ) { var act = it . getAttribute ( 'data-tbl-act' ) ; _tblDo ( act , window . _tblCur ) ; } return ; }
var g = ev . target . closest ? ev . target . closest ( '[data-tbl]' ) : null ;
if ( g ) { var act = g . getAttribute ( 'data-tbl' ) , bid = g . getAttribute ( 'data-bid' ) , col = parseInt ( g . getAttribute ( 'data-col' ) || '0' , 10 ) ;
var gr = g . getBoundingClientRect ? g . getBoundingClientRect ( ) : null ;
if ( act === 'colmenu' ) { _tblMenuOpen ( gr ? gr . left : ev . clientX , ( gr ? gr . bottom : ev . clientY ) + 4 , bid , 'col' , col ) ; ev . preventDefault ( ) ; }
else if ( act === 'colplus' ) { var b = _tblBlockById ( bid ) ; if ( b ) { window . E . sync ( ) ; _tblAddCol ( b , col + 1 ) ; window . E . render ( ) ; window . E . dirty = true ; window . E . autoSave ( ) ; _tblFocus ( bid , 1 , col + 1 ) ; } }
else if ( act === 'rowadd' ) { var b2 = _tblBlockById ( bid ) ; if ( b2 ) { window . E . sync ( ) ; var n = b2 . rows . length ; _tblAddRow ( b2 , n ) ; window . E . render ( ) ; window . E . dirty = true ; window . E . autoSave ( ) ; _tblFocus ( bid , n , 0 ) ; } }
return ;
}
if ( document . getElementById ( '_tblMenu' ) && ! ( ev . target . closest && ev . target . closest ( '[data-tbl]' ) ) ) _tblMenuClose ( ) ;
} ) ;
document . addEventListener ( 'contextmenu' , function ( ev ) {
var cell = ev . target . closest ? ev . target . closest ( '.ftable-cell' ) : null ;
if ( ! cell ) return ;
var w = cell . closest ( '.ftable-editor' ) ; if ( ! w ) return ;
var bid = w . getAttribute ( 'data-bid' ) , r = parseInt ( cell . getAttribute ( 'data-r' ) || '0' , 10 ) , c = parseInt ( cell . getAttribute ( 'data-c' ) || '0' , 10 ) ;
ev . preventDefault ( ) ;
if ( r === 0 ) _tblMenuOpen ( ev . clientX , ev . clientY , bid , 'col' , c ) ;
else _tblMenuOpen ( ev . clientX , ev . clientY , bid , 'row' , r ) ;
} ) ;
document . addEventListener ( 'mousedown' , function ( ev ) { if ( document . getElementById ( '_tblMenu' ) && ! ev . target . closest ( '#_tblMenu' ) ) _tblMenuClose ( ) ; } ) ;
}
const SM = { _o : false , _i : - 1 , _s : 0 , _q : '' , _it : [ ] ,
init ( ) { const e = document . getElementById ( '_slashMenu' ) ; if ( ! e ) return ; e . innerHTML = '' ;
const inp = document . createElement ( 'input' ) ; inp . type = 'text' ; inp . placeholder = 'Filter...' ;
@@ -54,6 +169,7 @@
get isOpen ( ) { return SM . _o ; } } ;
function renderBlock ( b , idx ) {
if ( b . type === 'table' ) return renderTableBlock ( b , idx ) ;
if ( b . type === 'divider' ) return ` <div class="block-wrapper"><hr class="block-divider"></div> ` ;
if ( b . type === 'image' ) return ` <div class="block-wrapper"><div class="block-content block-image" style="text-align:center;padding:12px 0;"><img src=" ${ b . src || '' } " alt=" ${ esc ( b . alt || '' ) } " style="max-width:100%;max-height:70vh;border-radius:8px;display:block;margin:0 auto;" onerror="this.style.display='none';this.parentElement.innerHTML='<div style= \\ 'padding:40px;color:var(--text-tertiary) \\ '>Image not found</div>'"></div></div> ` ;
if ( b . type === 'table_of_contents' ) return renderTOC ( ) ;
@@ -170,7 +286,9 @@
else if ( raw && raw . trim ( ) ) this . blocks = this . md2b ( raw ) ; } catch ( e ) { } }
if ( ! this . blocks . length ) this . blocks = [ this . mkB ( 'paragraph' , '' ) ] ;
this . loadInlineDBs ( ) ;
window . E = this ; SM . init ( ) ; this . render ( ) ;
window . E = this ;
this . insertTable = function ( ) { this . sync ( ) ; var nb = { id : genId ( ) , type : 'table' , content : '' , rows : _tblNew ( 3 , 3 ) } ; this . blocks . push ( nb ) ; this . dirty = true ; this . autoSave ( ) ; this . render ( ) ; setTimeout ( function ( ) { _tblFocus ( nb . id , 0 , 0 ) ; } , 60 ) ; } ;
SM . init ( ) ; _tblInitDelegate ( ) ; this . render ( ) ;
setTimeout ( ( ) => { this . focusBlock ( ) ; const ct = document . getElementById ( '_blocksCt' ) ; if ( ! ct ) return ;
ct . addEventListener ( 'keydown' , e => this . onKd ( e ) ) ;
ct . addEventListener ( 'paste' , e => { e . preventDefault ( ) ; document . execCommand ( 'insertText' , false , ( e . clipboardData || window . clipboardData ) . getData ( 'text/plain' ) ) ; this . dirty = true ; this . autoSave ( ) ; } ) ;
@@ -205,8 +323,9 @@
getIdx ( bid ) { return this . blocks . findIndex ( b => b . id === bid ) ; } ,
getActiveBlock ( ) { const el = document . activeElement ; if ( ! el || ! el . hasAttribute ( 'data-bid' ) ) return null ; return { el , bid : el . dataset . bid , idx : this . getIdx ( el . dataset . bid ) } ; } ,
focusBlock ( ) { const b = this . blocks [ 0 ] ; if ( ! b ) return ; const el = this . getEl ( b . id ) ; if ( el ) { el . focus ( ) ; ce ( el ) ; } } ,
onCtClick ( e ) { if ( e . target === document . getElementById ( '_blocksCt' ) || e . target ? . classList ? . contains ( 'blocks-empty' ) ) { if ( ! this . blocks . length ) this . addAt ( 0 ) ; else { const b = this . blocks [ this . blocks . length - 1 ] ; const el = b && this . getEl ( b . id ) ; if ( el ) { el . focus ( ) ; ce ( el ) ; } } } } ,
sync ( ) { const ct = document . getElementById ( '_blocksCt' ) ; if ( ! ct ) return ; for ( const b of this . blocks ) { if ( b . type === 'divider' || b . type === 'image' || b . type === 'embed' || b . type === 'table_of_contents' ) continue ;
onCtClick ( e ) { if ( e . target === document . getElementById ( '_blocksCt' ) || e . target ? . classList ? . contains ( 'blocks-empty' ) ) { if ( ! this . blocks . length ) this . addAt ( 0 ) ; else { const b = this . blocks [ this . blocks . length - 1 ] ; if ( b && ( b . type === 'table' || b . type === 'divider' || b . type === 'image' || b . type === 'embed' ) ) { this . addAt ( this . blocks . length , 'paragraph' ) ; } else { const el = b && this . getEl ( b . id ) ; if ( el ) { el . focus ( ) ; ce ( el ) ; } } } } } ,
sync ( ) { const ct = document . getElementById ( '_blocksCt' ) ; if ( ! ct ) return ; for ( const b of this . blocks ) { if ( b . type === 'table' ) { const wrap = ct . querySelector ( ` [data-bid=" ${ b . id } "].ftable-editor ` ) ; if ( wrap ) { const rows = [ ] ; wrap . querySelectorAll ( 'tr.ftable-row' ) . forEach ( function ( tr ) { const cells = tr . querySelectorAll ( 'th.ftable-cell,td.ftable-cell' ) ; if ( ! cells . length ) return ; const rowarr = [ ] ; cells . forEach ( function ( cell ) { rowarr . push ( cell . textContent || '' ) ; } ) ; rows . push ( rowarr ) ; } ) ; if ( rows . length ) b . rows = rows ; } continue ; }
if ( b . type === 'divider' || b . type === 'image' || b . type === 'embed' || b . type === 'table_of_contents' ) continue ;
if ( b . type === 'math' ) { const te = ct . querySelector ( ` [data-math-bid=" ${ b . id } "] ` ) ; if ( te ) b . content = te . value || '' ; continue ; }
const el = ct . querySelector ( ` [data-bid=" ${ b . id } "] ` ) ; if ( el && ! el . classList . contains ( 'toggle-title' ) ) b . content = el . textContent || '' ; else if ( el ) b . content = el . textContent || '' ;
if ( b . type === 'toggle' && b . children ) b . children . forEach ( function ( ch ) { const che = ct . querySelector ( ` [data-bid=" ${ ch . id } "] ` ) ; if ( che ) ch . content = che . textContent || '' ; } ) ;
@@ -509,9 +628,10 @@
const idx = SM . _i ; if ( idx < 0 || idx >= this . blocks . length ) return ;
const block = this . blocks [ idx ] ; this . sync ( ) ; block . content = '' ;
if ( id === 'divider' ) { block . type = 'divider' ; this . addAfter ( idx , 'paragraph' ) ; }
else { block . type = id ; if ( id === 'toggle' ) { block . expanded = true ; block . children = [ ] ; } if ( id === 'to_do' ) block . checked = false ; if ( id === 'columns' ) block . children = [ { id : genId ( ) , type : 'paragraph' , content : '' } ] ; if ( id === 'code' ) block . language = 'Plain Text' ; }
else { block . type = id ; if ( id === 'toggle' ) { block . expanded = true ; block . children = [ ] ; } if ( id === 'to_do' ) block . checked = false ; if ( id === 'columns' ) block . children = [ { id : genId ( ) , type : 'paragraph' , content : '' } ] ; if ( id === 'code' ) block . language = 'Plain Text' ; if ( id === 'table' ) { block . rows = _tblNew ( 3 , 3 ) ; block . content = '' ; } }
this . render ( ) ; this . dirty = true ; this . autoSave ( ) ;
if ( id === 'math ' ) { setTimeout ( ( ) => { const ne = document . querySelector ( ` [data-math-bid=" ${ block . id } "] ` ) ; if ( ne ) ne . focus ( ) ; } , 60 ) ; }
if ( id === 'table ' ) { setTimeout ( ( ) => _tblFocus ( block . id , 1 , 0 ) , 60 ) ; }
else if ( id === 'math' ) { setTimeout ( ( ) => { const ne = document . querySelector ( ` [data-math-bid=" ${ block . id } "] ` ) ; if ( ne ) ne . focus ( ) ; } , 60 ) ; }
else if ( id === 'columns' ) { setTimeout ( ( ) => { const ne = this . getEl ( block . children [ 0 ] . id ) ; if ( ne ) ne . focus ( ) ; } , 60 ) ; }
else { setTimeout ( ( ) => { const ne = this . getEl ( block . id ) ; if ( ne ) ne . focus ( ) ; } , 60 ) ; }
} ,
@@ -533,13 +653,27 @@
const t = document . getElementById ( '_titleEl' ) ; if ( t ) this . pageTitle = t . textContent ? . trim ( ) || 'New page' ;
this . saving = true ;
const csrf = document . cookie . match ( /csrf_token=([^;]+)/ ) ;
fetch ( ` /board/api/pages/ ${ this . pid } /blocks ` , { method : 'POST' , headers : { 'Content-Type' : 'application/json' , 'X-CSRF-Token' : csrf ? csrf [ 1 ] : '' } , body : JSON . stringify ( { title : this . pageTitle , blocks : this . blocks . map ( b => { const c = { id : b . id , type : b . type , content : b . content } ; [ 'checked' , 'expanded' , 'language' , 'icon' , 'src' , 'alt' , 'style' , 'embed_type' , 'collection_id' , 'dbs' , 'file_name' , 'file_size' , 'file_mime' , 'children' ] . forEach ( k => { if ( b [ k ] != null ) c [ k ] = b [ k ] ; } ) ; return c ; } ) } ) } )
fetch ( ` /board/api/pages/ ${ this . pid } /blocks ` , { method : 'POST' , headers : { 'Content-Type' : 'application/json' , 'X-CSRF-Token' : csrf ? csrf [ 1 ] : '' } , body : JSON . stringify ( { title : this . pageTitle , blocks : this . blocks . map ( b => { const c = { id : b . id , type : b . type , content : b . content } ; [ 'checked' , 'expanded' , 'language' , 'icon' , 'src' , 'alt' , 'style' , 'embed_type' , 'collection_id' , 'dbs' , 'file_name' , 'file_size' , 'file_mime' , 'children' , 'rows' , 'align' , 'has_header' ] . forEach ( k => { if ( b [ k ] != null ) c [ k ] = b [ k ] ; } ) ; return c ; } ) } ) } )
. then ( r => r . json ( ) ) . then ( ( ) => { this . saving = false ; this . dirty = false ; this . lastSaved = new Date ( ) . toLocaleTimeString ( ) ;
document . querySelectorAll ( ` .sidebar-item[data-page-id="page/ ${ this . pid } "] ` ) . forEach ( item => { const n = item . querySelector ( '.page-name' ) ; if ( n ) n . textContent = this . pageTitle || 'New page' ; } ) ;
if ( cb ) cb ( ) ;
} ) . catch ( ( ) => { this . saving = false ; } ) ;
} ,
md2b ( md ) { const bl = [ ] ; let cb = null ; for ( const line of md . split ( '\n' ) ) { if ( cb ) { if ( line . trim ( ) === '```' ) { bl . push ( cb ) ; cb = null ; } else cb . content += ( cb . content ? '\n' : '' ) + line ; continue ; } const t = line . trim ( ) ; if ( ! t ) continue ; if ( t . startsWith ( '```' ) ) { cb = this . mkB ( 'code' , '' , { language : t . substring ( 3 ) . trim ( ) || 'Plain Text' } ) ; continue ; } if ( t . startsWith ( '# ' ) && ! t . startsWith ( '## ' ) ) bl . push ( this . mkB ( 'heading_1' , t . substring ( 2 ) ) ) ; else if ( t . startsWith ( '## ' ) && ! t . startsWith ( '### ' ) ) bl . push ( this . mkB ( 'heading_2' , t . substring ( 3 ) ) ) ; else if ( t . startsWith ( '### ' ) && ! t . startsWith ( '#### ' ) ) bl . push ( this . mkB ( 'heading_3' , t . substring ( 4 ) ) ) ; else if ( t . startsWith ( '#### ' ) ) bl . push ( this . mkB ( 'heading_4' , t . substring ( 5 ) ) ) ; else if ( t === '---' || t === '***' ) bl . push ( this . mkB ( 'divider' , '' ) ) ; else if ( /^[-*+] / . test ( t ) ) bl . push ( this . mkB ( 'bulleted_list' , t . substring ( 2 ) ) ) ; else if ( /^\d+\. / . test ( t ) ) bl . push ( this . mkB ( 'numbered_list' , t . replace ( /^\d+\. / , '' ) ) ) ; else if ( t . startsWith ( '- [ ] ' ) || t . startsWith ( '* [ ] ' ) ) bl . push ( this . mkB ( 'to_do' , t . substring ( 6 ) ) ) ; else if ( t . startsWith ( '- [x] ' ) || t . startsWith ( '* [x] ' ) ) bl . push ( this . mkB ( 'to_do' , t . substring ( 6 ) , { checked : true } ) ) ; else if ( t . startsWith ( '> ' ) ) bl . push ( this . mkB ( 'quote' , t . substring ( 2 ) ) ) ; else bl . push ( this . mkB ( 'paragraph' , t ) ) ; } if ( cb ) bl . push ( cb ) ; return bl ; } ,
md2b ( md ) { const bl = [ ] ; const lines = String ( md ) . split ( '\n' ) ; let i = 0 ; for ( ; i < lines . length ; i ++ ) { const raw = lines [ i ] ; const t = raw . trim ( ) ; if ( ! t ) continue ;
if ( t . startsWith ( '```' ) ) { const lang = t . substring ( 3 ) . trim ( ) || 'Plain Text' ; let code = '' ; i ++ ; for ( ; i < lines . length ; i ++ ) { if ( lines [ i ] . trim ( ) === '```' ) break ; code += ( code ? '\n' : '' ) + lines [ i ] ; } bl . push ( this . mkB ( 'code' , code , { language : lang } ) ) ; continue ; }
if ( _tblIsData ( t ) && i + 1 < lines . length && _tblIsSep ( lines [ i + 1 ] ) ) { const header = _tblSplitLine ( t ) ; const rows = [ header ] ; let j = i + 2 ; while ( j < lines . length && _tblIsData ( lines [ j ] ) ) { rows . push ( _tblSplitLine ( lines [ j ] ) ) ; j ++ ; } let w = 0 ; rows . forEach ( function ( r ) { if ( r . length > w ) w = r . length ; } ) ; if ( w < 1 ) w = 1 ; const rows2 = rows . map ( function ( r ) { const a = r . slice ( ) ; while ( a . length < w ) a . push ( '' ) ; return a ; } ) ; bl . push ( this . mkB ( 'table' , '' , { rows : rows2 } ) ) ; i = j - 1 ; continue ; }
if ( t . startsWith ( '# ' ) && ! t . startsWith ( '## ' ) ) bl . push ( this . mkB ( 'heading_1' , t . substring ( 2 ) ) ) ;
else if ( t . startsWith ( '## ' ) && ! t . startsWith ( '### ' ) ) bl . push ( this . mkB ( 'heading_2' , t . substring ( 3 ) ) ) ;
else if ( t . startsWith ( '### ' ) && ! t . startsWith ( '#### ' ) ) bl . push ( this . mkB ( 'heading_3' , t . substring ( 4 ) ) ) ;
else if ( t . startsWith ( '#### ' ) ) bl . push ( this . mkB ( 'heading_4' , t . substring ( 5 ) ) ) ;
else if ( t === '---' || t === '***' ) bl . push ( this . mkB ( 'divider' , '' ) ) ;
else if ( /^[-*+] / . test ( t ) ) bl . push ( this . mkB ( 'bulleted_list' , t . substring ( 2 ) ) ) ;
else if ( /^\d+\. / . test ( t ) ) bl . push ( this . mkB ( 'numbered_list' , t . replace ( /^\d+\. / , '' ) ) ) ;
else if ( t . startsWith ( '- [ ] ' ) || t . startsWith ( '* [ ] ' ) ) bl . push ( this . mkB ( 'to_do' , t . substring ( 6 ) ) ) ;
else if ( t . startsWith ( '- [x] ' ) || t . startsWith ( '* [x] ' ) ) bl . push ( this . mkB ( 'to_do' , t . substring ( 6 ) , { checked : true } ) ) ;
else if ( t . startsWith ( '> ' ) ) bl . push ( this . mkB ( 'quote' , t . substring ( 2 ) ) ) ;
else bl . push ( this . mkB ( 'paragraph' , t ) ) ;
} return bl ; } ,
} ;
}
document . addEventListener ( 'mouseup' , ( ) => { setTimeout ( ( ) => { const s = window . getSelection ( ) ; if ( ! s || s . isCollapsed ) return ; const ed = document . querySelector ( '.page-editor-wrapper' ) ; if ( ! ed ? . _ _x ) return ; const d = ed . _ _x . $data ; const ct = document . getElementById ( '_blocksCt' ) ; if ( ! ct ? . contains ( s . anchorNode ) ) return ; const bel = s . anchorNode . parentElement ? . closest ( '[data-bid]' ) ; if ( bel ) { const idx = d . getIdx ( bel . dataset . bid ) ; if ( idx >= 0 ) d . showFmt ( idx ) ; } } , 80 ) ; } ) ;
@@ -560,6 +694,7 @@
case 'math' : return '$$\n' + c + '\n$$' ;
case 'table_of_contents' : return '[TOC]' ;
case 'columns' : return ( b . children || [ ] ) . map ( blocksToMarkdown ) . join ( '\n\n' ) ;
case 'table' : { var rows = b . rows || [ ] ; if ( ! rows . length ) return '' ; var w = 0 ; rows . forEach ( function ( r ) { if ( r && r . length > w ) w = r . length ; } ) ; var rl = function ( r ) { var cells = [ ] ; for ( var k = 0 ; k < w ; k ++ ) cells . push ( ( r && k < r . length ) ? String ( r [ k ] == null ? '' : r [ k ] ) : '' ) ; return '| ' + cells . join ( ' | ' ) + ' |' ; } ; var out = rl ( rows [ 0 ] ) ; var seps = [ ] ; for ( var k = 0 ; k < w ; k ++ ) seps . push ( '---' ) ; out += '\n| ' + seps . join ( ' | ' ) + ' |' ; for ( var ri = 1 ; ri < rows . length ; ri ++ ) out += '\n' + rl ( rows [ ri ] ) ; return out ; }
default : return c ;
}
}