mirror of
https://github.com/flynx/pWiki.git
synced 2025-10-29 10:00:08 +00:00
479 lines
11 KiB
JavaScript
Executable File
479 lines
11 KiB
JavaScript
Executable File
/**********************************************************************
|
|
*
|
|
*
|
|
*
|
|
**********************************************************************/
|
|
|
|
//var DEBUG = DEBUG != null ? DEBUG : true
|
|
|
|
|
|
/*********************************************************************/
|
|
|
|
// XXX not sure about these...
|
|
var BaseData = {
|
|
'System/title': function(){
|
|
var o = Object.create(this)
|
|
o.location = o.dir
|
|
return o.title
|
|
},
|
|
'System/path': function(){
|
|
return this.dir },
|
|
'System/dir': function(){
|
|
return normalizePath(path2lst(this.dir).slice(0, -1)) },
|
|
'System/location': function(){
|
|
return this.dir },
|
|
'System/resolved': function(){
|
|
var o = Object.create(this)
|
|
o.location = o.dir
|
|
return o.acquire(o.dir, o.title)
|
|
},
|
|
|
|
'System/list': function(){
|
|
var p = this.dir
|
|
|
|
return Object.keys(this.__wiki_data)
|
|
.map(function(k){
|
|
if(k.indexOf(p) == 0){
|
|
return path2lst(k.slice(p.length)).shift()
|
|
}
|
|
return null
|
|
})
|
|
.filter(e => e != null)
|
|
.sort()
|
|
.map(e => '['+ e +']')
|
|
.join('<br>')
|
|
},
|
|
'System/tree': function(){
|
|
var p = this.dir
|
|
|
|
return Object.keys(this.__wiki_data)
|
|
.map(function(k){
|
|
if(k.indexOf(p) == 0){
|
|
return k
|
|
}
|
|
return null
|
|
})
|
|
.filter(e => e != null)
|
|
.sort()
|
|
.map(e => '['+ e +']')
|
|
.join('<br>')
|
|
},
|
|
'System/links': function(){
|
|
var that = this
|
|
var p = this.dir
|
|
|
|
var res = []
|
|
|
|
var wiki = this.__wiki_data
|
|
Object.keys(wiki).forEach(function(k){
|
|
(wiki[k].links || []).forEach(function(l){
|
|
(l == p || that.acquire(path2lst(l).slice(0, -1), path2lst(l).pop()) == p)
|
|
&& res.push([l, k])
|
|
})
|
|
})
|
|
|
|
return res
|
|
//.map(e => '['+ e[0] +'] <i>from page: ['+ e[1] +']</i>')
|
|
.map(e => '['+ e[1] +'] <i>-> ['+ e[0] +']</i>')
|
|
.sort()
|
|
.join('<br>')
|
|
},
|
|
|
|
// XXX this needs a redirect...
|
|
'System/delete': function(){
|
|
var p = this.dir
|
|
delete this.__wiki_data[p]
|
|
},
|
|
}
|
|
|
|
|
|
// data store...
|
|
// Format:
|
|
// {
|
|
// <path>: {
|
|
// text: <text>,
|
|
//
|
|
// links: [
|
|
// <offset>: <link>,
|
|
// ],
|
|
// }
|
|
// }
|
|
var data = {
|
|
'Templates/EmptyPage': {
|
|
text: 'This page is empty.<br><br>WikiHome',
|
|
},
|
|
}
|
|
data.__proto__ = BaseData
|
|
|
|
|
|
|
|
/*********************************************************************/
|
|
|
|
var path2lst = path =>
|
|
(path instanceof Array ? path : path.split(/[\\\/]+/g))
|
|
// handle '..' (lookahead) and trim path elements...
|
|
// NOTE: this will not touch the leading '.' or '..'
|
|
.map((p, i, l) =>
|
|
(i > 0 && (p.trim() == '..' || p.trim() == '.')
|
|
|| (l[i+1] || '').trim() == '..') ?
|
|
null
|
|
: p.trim())
|
|
// cleanup and clear '.'...
|
|
.filter(p =>
|
|
p != null && p != '')
|
|
|
|
var normalizePath = path =>
|
|
path2lst(path).join('/')
|
|
|
|
|
|
|
|
/*********************************************************************/
|
|
|
|
var Wiki = {
|
|
__wiki_data: data,
|
|
|
|
__home_page__: 'WikiHome',
|
|
__default_page__: 'EmptyPage',
|
|
__acquesition_order__: [
|
|
'Templates',
|
|
],
|
|
// XXX should this be read only???
|
|
__system__: 'System',
|
|
//__redirect_template__: 'RedirectTemplate',
|
|
|
|
__wiki_link__: RegExp('('+[
|
|
'(\\./|\\.\\./|[A-Z][a-z0-9]+[A-Z/])[a-zA-Z0-9/]*',
|
|
'\\[[^\\]]+\\]',
|
|
].join('|') +')', 'g'),
|
|
|
|
|
|
// Resolve '.' and '..' relative to current page...
|
|
//
|
|
// NOTE: '.' is relative to .path and not to .dir
|
|
// NOTE: this is here as it needs the context to resolve...
|
|
resolveDotPath: function(path){
|
|
path = normalizePath(path)
|
|
// '.' or './*'
|
|
return path == '.' || /^\.\//.test(path) ?
|
|
//path.replace(/^\./, this.dir)
|
|
path.replace(/^\./, this.path)
|
|
// '..' or '../*'
|
|
: path == '..' || /^\.\.\//.test(path) ?
|
|
//path.replace(/^\.\./,
|
|
// normalizePath(path2lst(this.dir).slice(0, -1)))
|
|
path.replace(/^\.\./, this.dir)
|
|
: path
|
|
},
|
|
|
|
|
|
// current location...
|
|
get location(){
|
|
return this.__location || this.__home_page__ },
|
|
set location(value){
|
|
this.__location = this.resolveDotPath(value) },
|
|
|
|
// page path...
|
|
//
|
|
// Format:
|
|
// <dir>/<title>
|
|
//
|
|
// NOTE: changing this will move the page to the new path and change
|
|
// .location acordingly...
|
|
// NOTE: same applies to path parts below...
|
|
//
|
|
// XXX use a template for the redirect page...
|
|
get path(){
|
|
return this.location },
|
|
set path(value){
|
|
value = this.resolveDotPath(value)
|
|
|
|
var l = this.location
|
|
|
|
if(value == l){
|
|
return
|
|
}
|
|
|
|
// old...
|
|
var otitle = this.title
|
|
var odir = this.dir
|
|
|
|
if(this.exists(l)){
|
|
this.__wiki_data[value] = this.__wiki_data[l]
|
|
}
|
|
this.location = value
|
|
|
|
// new...
|
|
var ntitle = this.title
|
|
var ndir = this.dir
|
|
|
|
var redirect = false
|
|
|
|
// update links to this page...
|
|
this.pages(function(page){
|
|
// skip the old page...
|
|
if(page.location == l){
|
|
return
|
|
}
|
|
page.text = page.text.replace(page.__wiki_link__, function(lnk){
|
|
var from = lnk[0] == '[' ? lnk.slice(1, -1) : lnk
|
|
|
|
// get path/title...
|
|
var p = path2lst(from)
|
|
var t = p.pop()
|
|
p = normalizePath(p)
|
|
|
|
var target = page.acquire(p, t)
|
|
// page target changed...
|
|
// NOTE: this can happen either when a link was an orphan
|
|
// or if the new page path shadowed the original
|
|
// target...
|
|
// XXX should we report the exact condition here???
|
|
if(target == value){
|
|
console.log('Link target changed:', lnk, '->', value)
|
|
return lnk
|
|
|
|
// skip links that do not resolve to target...
|
|
} else if(page.acquire(p, t) != l){
|
|
return lnk
|
|
}
|
|
|
|
// format the new link...
|
|
var to = p == '' ? ntitle : p +'/'+ ntitle
|
|
to = lnk[0] == '[' ? '['+to+'}' : to
|
|
|
|
// explicit link change -- replace...
|
|
if(from == l){
|
|
//console.log(lnk, '->', to)
|
|
return to
|
|
|
|
// path did not change -- change the title...
|
|
} else if(ndir == odir){
|
|
// conflict: the new link will not resolve to the
|
|
// target page...
|
|
if(page.acquire(p, ntitle) != value){
|
|
console.log('ERR:', lnk, '->', to,
|
|
'is shadowed by:', page.acquire(p, ntitle))
|
|
// XXX should we add a note to the link???
|
|
redirect = true
|
|
|
|
// replace title...
|
|
} else {
|
|
//console.log(lnk, '->', to)
|
|
return to
|
|
}
|
|
|
|
// path changed -- keep link + add redirect page...
|
|
} else {
|
|
redirect = true
|
|
}
|
|
|
|
// no change...
|
|
return lnk
|
|
})
|
|
})
|
|
|
|
// redirect...
|
|
//
|
|
// XXX should we use a template here???
|
|
// ...might be a good idea to set a .redirect attr and either
|
|
// do an internal/transparent redirect or show a redirect
|
|
// template
|
|
// ...might also be good to add an option to fix the link from
|
|
// the redirect page...
|
|
if(redirect){
|
|
console.log('CREATING REDIRECT PAGE:', l, '->', value, '')
|
|
this.__wiki_data[l].text = 'REDIRECT TO: ' + value
|
|
+'<br>'
|
|
+'<br><i>NOTE: This page was created when renaming the target '
|
|
+'page that resulted new link being broken (i.e. resolved '
|
|
+'to a different page from the target)</i>'
|
|
this.__wiki_data[l].redirect = value
|
|
|
|
// cleaup...
|
|
} else {
|
|
delete this.__wiki_data[l]
|
|
}
|
|
},
|
|
|
|
// path parts: directory...
|
|
//
|
|
// NOTE: see .path for details...
|
|
get dir(){
|
|
return path2lst(this.location).slice(0, -1).join('/') },
|
|
set dir(value){
|
|
this.path = value +'/'+ this.title },
|
|
|
|
// path parts: title...
|
|
//
|
|
// NOTE: see .path for details...
|
|
get title(){
|
|
return path2lst(this.location).pop() },
|
|
set title(value){
|
|
this.path = this.dir +'/'+ value },
|
|
|
|
|
|
// page content...
|
|
//
|
|
// Test acquesition order:
|
|
// - explicit path
|
|
// - for each level in path
|
|
// - .title explicitly in path
|
|
// - .title in templates
|
|
// - .title in system
|
|
// - aquire empty page (same order as above)
|
|
//
|
|
get text(){
|
|
var data = this.acquireData()
|
|
return data instanceof Function ? data.call(this, this)
|
|
: typeof(data) == typeof('str') ? data
|
|
: data != null ? data.text
|
|
: ''
|
|
},
|
|
set text(value){
|
|
var l = this.location
|
|
|
|
// prevent overwriting actions...
|
|
if(this.acquireData(l) instanceof Function){
|
|
return
|
|
}
|
|
|
|
this.__wiki_data[l] = this.__wiki_data[l] || {}
|
|
this.__wiki_data[l].text = value
|
|
|
|
// cache links...
|
|
delete this.__wiki_data[l].links
|
|
this.__wiki_data[l].links = this.links
|
|
},
|
|
|
|
// NOTE: this is set by setting .text
|
|
get links(){
|
|
var data = this.acquireData() || {}
|
|
var links = data.links = data.links
|
|
|| (this.text.match(this.__wiki_link__) || [])
|
|
// unwrap explicit links...
|
|
.map(e => e[0] == '[' ? e.slice(1, -1) : e)
|
|
// unique...
|
|
.filter((e, i, l) => l.slice(0, i).indexOf(e) == -1)
|
|
return links
|
|
},
|
|
|
|
// XXX list children/sub-pages...
|
|
get list(){
|
|
},
|
|
|
|
|
|
// navigation...
|
|
get parent(){
|
|
return this.get(this.dir)
|
|
},
|
|
get: function(path){
|
|
var o = Object.create(this)
|
|
o.location = path
|
|
return o
|
|
},
|
|
|
|
|
|
exists: function(path){
|
|
return normalizePath(path) in this.__wiki_data },
|
|
// get title from dir and then go up the tree...
|
|
_acquire: function(title){
|
|
title = title || this.__default_page__
|
|
var acquire_from = this.__acquesition_order__
|
|
var data = this.__wiki_data
|
|
var that = this
|
|
|
|
var path = path2lst(this.dir)
|
|
|
|
var _res = function(p){
|
|
p = normalizePath(p)
|
|
return that.__wiki_data[p] && p
|
|
}
|
|
|
|
while(true){
|
|
// get title from path...
|
|
var p = path.concat([title])
|
|
if(this.exists(p)){
|
|
return _res(p)
|
|
}
|
|
|
|
// get title from special paths in path...
|
|
for(var i=0; i < acquire_from.length; i++){
|
|
var p = path.concat([acquire_from[i], title])
|
|
if(this.exists(p)){
|
|
return _res(p)
|
|
}
|
|
}
|
|
|
|
if(path.length == 0){
|
|
break
|
|
}
|
|
|
|
path.pop()
|
|
}
|
|
|
|
// system path...
|
|
if(this.__system__){
|
|
var p = [this.__system__, title]
|
|
if(this.exists(p)){
|
|
return _res(p)
|
|
}
|
|
}
|
|
},
|
|
acquire: function(path, title){
|
|
path = path && normalizePath(path) || this.path
|
|
title = title || this.title
|
|
var wiki = this.__wiki_data
|
|
|
|
// get the page directly...
|
|
return (this.exists(path +'/'+ title) && path +'/'+ title)
|
|
// acquire the page from path...
|
|
|| this._acquire(title)
|
|
// acquire the default page...
|
|
|| this._acquire(this.__default_page__)
|
|
// nothing found...
|
|
|| null
|
|
},
|
|
|
|
// shorthand...
|
|
acquireData: function(path, title){
|
|
var page = this.acquire(path, title)
|
|
return page ? this.__wiki_data[page] : null
|
|
},
|
|
|
|
|
|
// serialization...
|
|
json: function(path){
|
|
return path == null ? JSON.parse(JSON.stringify(this.__wiki_data))
|
|
: path == '.' ? {
|
|
path: this.location,
|
|
text: this.text,
|
|
}
|
|
: {
|
|
path: path,
|
|
text: (this.__wiki_data[path] || {}).text,
|
|
}
|
|
},
|
|
// XXX should we inherit from the default???
|
|
load: function(json){
|
|
this.__wiki_data = json
|
|
},
|
|
|
|
|
|
// iteration...
|
|
// XXX this is not page specific, might need refactoring...
|
|
pages: function(callback){
|
|
var that = this
|
|
Object.keys(this.__wiki_data).forEach(function(location){
|
|
// XXX not sure if this is the right way to go...
|
|
var o = Object.create(that)
|
|
o.location = location
|
|
callback.call(o, o)
|
|
})
|
|
return this
|
|
},
|
|
}
|
|
|
|
|
|
/**********************************************************************
|
|
* vim:set ts=4 sw=4 : */
|