diff --git a/pwiki/page.js b/pwiki/page.js index 4610512..a0f008d 100755 --- a/pwiki/page.js +++ b/pwiki/page.js @@ -558,25 +558,16 @@ object.Constructor('BasePage', { // sorting... // - // XXX should this be page-level (current) store level??? - // XXX when this is async, should this return a promise???? - sort: async function(cmp){ - // not sorting single pages... - //if(this.length <= 1){ - if(!this.isPattern){ - return this } - // sort... + sort: async function(...cmp){ + // normalize to path... this.metadata = - { order: (await this.each()) - .sort(...arguments) - .map(function(p){ - return p.path }) } + { order: await this.store.sort(this.path, ...cmp) } return this }, reverse: async function(){ // not sorting single pages... if(this.length <= 1){ return this } - this.metadata = { order: (await this.match()).reverse() } + this.sort('reverse') return this }, // diff --git a/pwiki/store/base.js b/pwiki/store/base.js index 141d374..e4186d4 100755 --- a/pwiki/store/base.js +++ b/pwiki/store/base.js @@ -643,6 +643,7 @@ module.BaseStore = { //*/ + // // .exists() // -> @@ -689,6 +690,83 @@ module.BaseStore = { return false } return pwpath.joinArgs(res, args) }, //*/ + + // XXX EXPERIMENTAL... + // XXX should we handle undefined attr values??? + // XXX BUG: chains still not working correctly... + sort: function(paths, ...by){ + var that = this + paths = + (paths.includes('*') + || paths.includes('**')) ? + this.match(paths).iter() + : paths + var _async = false + return paths + // pre-fetch all the needed data... + // XXX we can try and make this lazy and only get the data + // we need when we need it (in sort)... + // ...not sure if this is worth it... + .map(function(p, i){ + var d + var res = [] + for(var cmp of by){ + res.push( + (cmp == 'path' + || cmp == 'location') ? + p + : cmp == 'dir' ? + pwpath.dirname(p) + : cmp == 'name' ? + pwpath.basename(p) + : cmp == 'title' ? + pwpath.decodeElem( + pwpath.basename(p)) + : cmp == 'depth' ? + pwpath.split(p).length + // async attr... + : typeof(cmp) == 'string' ? + // NOTE: we only get page data once per page... + (d = d ?? that.get(p)) + .then(function(data){ + return data[cmp] }) + : null) } + _async = _async || !!d + return d ? + // wait for data to resolve... + Promise.all([i, p, Promise.all(res)]) + : [i, p, res] }) + // NOTE: if one of the sort attrs is async we need to wrap the + // whole thing in a promise... + .run(function(){ + return _async ? + Promise.all(this).iter() + : this }) + .sort(function([ia, a, ca], [ib, b, cb]){ + for(var [i, cmp] of by.entries()){ + var res = + typeof(cmp) == 'string' ? + ((ca[i] == null + && cb[i] == null) ? + 0 + : (cb[i] == null + || ca[i] < cb[i]) ? + -1 + : (ca[i] == null + || ca[i] > cb[i]) ? + 1 + : 0) + : typeof(cmp) == 'function' ? + cmp(a, b) + : 0 + // got a non equal... + if(res != 0){ + return res } } + // keep positions if all comparisons are equal... + return ia - ib }) + .map(function([_, p]){ + return p }) }, + // find the closest existing alternative path... find: async function(path, strict=false){ var {path, args} = pwpath.splitArgs(path) @@ -709,7 +787,6 @@ module.BaseStore = { : '/'+p if(pages.has(p)){ return p+args } } }, - // // Resolve page for path // .match() @@ -802,19 +879,20 @@ module.BaseStore = { // pattern match * / ** if(path.includes('*') || path.includes('**')){ - var order = (this.metadata(path) ?? {}).order || [] - var {path, args} = pwpath.splitArgs(path) + path = pwpath.sanitize(path) + var all = args.all + var sort = args.sort var test = await this.__match_args(args) args = pwpath.joinArgs('', args) + var order = (await this.metadata(path) ?? {}).order || [] + // NOTE: we are matching full paths only here so leading and // trainling '/' are optional... var pattern = new RegExp(`^\\/?` - +RegExp.quoteRegExp( - // remove leading/trailing '/' - path.replace(/^\/|\/$/g, '')) + +RegExp.quoteRegExp(path) // pattern: ** .replace(/\\\*\\\*/g, '(.*)') // pattern: * @@ -860,7 +938,13 @@ module.BaseStore = { m[0].slice(1) : m[0]) return res }, new Set())] - .sortAs(order) + // handle live sort... + .run(function(){ + return (sort && sort !== true) ? + that + .sort(this, ...sort.split(/\s*[,\s]+/g)) + :this + .sortAs(order) }) .map(function(p){ return p+args })} // direct search... diff --git a/pwiki2.js b/pwiki2.js index 044f4f5..fd52a2e 100755 --- a/pwiki2.js +++ b/pwiki2.js @@ -36,8 +36,12 @@ * - * * -* XXX sort: define a generic sort path argument... -* ...this will unify all interfaces (macros/patterns/etc) +* XXX sort: define a generic sort path argument... DONE-ish +* XXX sort chains still not totally correct... +* This correctly shows Doc and WikiHome first +* await pwiki.store.sort('*', 'tags') +* This mixes them back up: +* await pwiki.store.sort('*', 'tags', 'ctime') * XXX macros: else/default macro args essentially mean the same thing, should we * unify them to use the same name??? * XXX parser: error handling: revise page quoting...