refactoring + a several util methods in tags.js

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2018-12-16 02:54:32 +03:00
parent 158da70086
commit 1667632227
2 changed files with 224 additions and 108 deletions

View File

@ -132,9 +132,8 @@ if(typeof(sha1) != 'undefined'){
/*********************************************************************/ /*********************************************************************/
// Data...
// Data class methods and API...
//
var DataClassPrototype = { var DataClassPrototype = {
// NOTE: we consider the input list sorted... // NOTE: we consider the input list sorted...
fromArray: function(list){ fromArray: function(list){
@ -157,12 +156,6 @@ var DataClassPrototype = {
}, },
} }
/*********************************************************************/
// Data object methods and API...
//
var DataPrototype = { var DataPrototype = {
get version(){ get version(){
@ -231,6 +224,9 @@ var DataPrototype = {
// //
/*****************************************************************/ /*****************************************************************/
ribbon_order: null,
ribbons: null,
get current(){ get current(){
return this.__current = this.__current return this.__current = this.__current
|| this.getImages(this.ribbon_order[0])[0] || this.getImages(this.ribbon_order[0])[0]
@ -238,11 +234,12 @@ var DataPrototype = {
set current(value){ set current(value){
this.focusImage(value) }, this.focusImage(value) },
// XXX should this default to top or bottom ribbon???
get base(){ get base(){
return this.__base || this.ribbon_order[0] }, return this.__base || this.ribbon_order[0] },
set base(value){ set base(value){
this.__base = value }, this.__base = value in this.ribbons ?
this.getRibbon(value)
: value },
get order(){ get order(){
return this.__order }, return this.__order },
@ -427,7 +424,6 @@ var DataPrototype = {
return res return res
}, },
// Remove duplicate items from list in-place... // Remove duplicate items from list in-place...
// //
// NOTE: only the first occurrence is kept... // NOTE: only the first occurrence is kept...
@ -1554,10 +1550,7 @@ var DataPrototype = {
// This is signature compatible with .getRibbon(..), see it for more // This is signature compatible with .getRibbon(..), see it for more
// info... // info...
setBase: function(target, offset){ setBase: function(target, offset){
var base = this.getRibbon(target, offset) this.base = this.getRibbon(target, offset)
if(base in this.ribbons){
this.base = base
}
return this return this
}, },
@ -2858,7 +2851,6 @@ var DataPrototype = {
var that = this var that = this
data = typeof(data) == typeof('str') ? JSON.parse(data) : data data = typeof(data) == typeof('str') ? JSON.parse(data) : data
data = formats.updateData(data, DATA_VERSION) data = formats.updateData(data, DATA_VERSION)
this.base = data.base
this.order = data.order.slice() this.order = data.order.slice()
this.ribbon_order = data.ribbon_order.slice() this.ribbon_order = data.ribbon_order.slice()
@ -2876,6 +2868,7 @@ var DataPrototype = {
}) })
this.current = data.current this.current = data.current
this.base = data.base
// extra data... // extra data...
!clean !clean
@ -2926,8 +2919,17 @@ var DataPrototype = {
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/*********************************************************************/ var BaseData =
module.BaseData =
object.makeConstructor('BaseData',
DataClassPrototype,
DataPrototype)
//---------------------------------------------------------------------
// XXX make a API compatible replacement to the above -- to access // XXX make a API compatible replacement to the above -- to access
// compatibility and performance... // compatibility and performance...
@ -3220,7 +3222,17 @@ var DataWithTagsPrototype = {
/*********************************************************************/ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
var DataWithTags =
module.DataWithTags =
object.makeConstructor('DataWithTags',
DataClassPrototype,
DataWithTagsPrototype)
//---------------------------------------------------------------------
// Proxy Data API to one of the target data objects... // Proxy Data API to one of the target data objects...
var DataProxyPrototype = { var DataProxyPrototype = {
@ -3236,28 +3248,13 @@ var DataProxyPrototype = {
} }
/*********************************************************************/
// Main Data object...
var BaseData =
module.BaseData =
object.makeConstructor('BaseData',
DataClassPrototype,
DataPrototype)
var DataWithTags =
module.DataWithTags =
object.makeConstructor('DataWithTags',
DataClassPrototype,
DataWithTagsPrototype)
//---------------------------------------------------------------------
var Data = var Data =
module.Data = DataWithTags module.Data = DataWithTags
/********************************************************************** /**********************************************************************
* vim:set ts=4 sw=4 : */ return module }) * vim:set ts=4 sw=4 : */ return module })

View File

@ -49,7 +49,7 @@ var normalizeSplit = function(args){
/*********************************************************************/ /*********************************************************************/
var TagsClassPrototype = { var BaseTagsClassPrototype = {
// Utils... // Utils...
// //
// .normalize(tag) // .normalize(tag)
@ -207,74 +207,42 @@ var TagsClassPrototype = {
// ...there are two ways to think of this: // ...there are two ways to think of this:
// 1) both (a-la flickr) -- keep both, use normalized internally // 1) both (a-la flickr) -- keep both, use normalized internally
// 2) only normalized -- simpler but may surprise the user and not be as pretty... // 2) only normalized -- simpler but may surprise the user and not be as pretty...
var TagsPrototype = { // XXX should we split out the non-basic stuff???
// like:
// .makePathsPersistent()
// .optimizeTags()
// ...
var BaseTagsPrototype = {
config: { config: {
tagRemovedChars: '[\\s-_]', tagRemovedChars: '[\\s-_]',
}, },
// data... // Tag index...
// //
// Format:
// Set([ <tag>, ... ])
//
// XXX Q: should these be normalized???
__persistent_tags: null,
// Format:
// {
// <alias>: <normalized-tag>,
// }
//
// XXX need introspection for this...
// ...should this be .aliases ???
__aliases: null,
// Format: // Format:
// { // {
// <tag>: [ <item>, ... ], // <tag>: [ <item>, ... ],
// ... // ...
// } // }
//
__index: null, __index: null,
// Persistent tags...
//
// Format:
// Set([ <tag>, ... ])
//
persistent: null,
// XXX EXPERIMENTAL... // Tag aliases...
// XXX need a way to edit the compound tag... //
__special_tag_handlers__: { // Format:
'*persistent*': function(action, tag, value){ // {
// XXX remove the tag... // <alias>: <normalized-tag>,
// XXX add the tag to .__persistent_tags // }
// XXX return the new tag for normal handling... //
}, aliases: null,
},
handleSpecialTag: function(action, tag, value){
var that = this
var handlers = this.__special_tag_handlers__ || {}
// get the matching handler key...
var key = Object.keys(handlers)
.filter(function(k){
return that.match(k, tag) })
// XXX should we handle multiple matches???
.shift()
// resolve handler aliases...
var match = key
do {
match = handlers[match]
} while(!(match instanceof Function) && match in handlers)
// no handler...
if(!(match instanceof Function)){
// XXX
return false
}
// XXX remove key from tag...
return match.call(this, action, tag, value)
},
// Utils... // Utils...
@ -569,6 +537,41 @@ var TagsPrototype = {
//return that.directMatch(query, t, cmp) }) }, //return that.directMatch(query, t, cmp) }) },
return that.match(query, t, cmp) }) }, return that.match(query, t, cmp) }) },
// Keep only the longest matching paths...
//
// .uniquePaths(path, ..)
// .uniquePaths([path, ..])
// -> paths
//
// Algorithm:
// - sort by path length descending
// - take top path (head)
// - remove all paths that match it after it (tail)
// - next path
//
uniquePaths: function(...list){
var that = this
return that.normalize(normalizeSplit(list))
// sort by number of path elements (longest first)...
.map(function(p){ return p.split(/[\\\/]/g) })
.sort(function(a, b){ return b.length - a.length })
.map(function(p){ return p.join('/') })
// remove all paths in tail that match the current...
.map(function(p, i, list){
// skip []...
!(p instanceof Array)
&& list
// only handle the tail...
.slice(i+1)
.forEach(function(o, j){
// skip []...
!(p instanceof Array)
&& that.directMatch(o, p)
// match -> replace the matching elem with []
&& (list[i+j+1] = []) })
return p })
.flat() },
// Introspection and Access API... // Introspection and Access API...
// //
@ -611,7 +614,7 @@ var TagsPrototype = {
// -> bool // -> bool
// //
// //
// NOTE: this includes all the .persistent tags as well as all the // NOTE: this includes all the persistent tags as well as all the
// tags actually used. // tags actually used.
// //
// XXX should this return split values??? // XXX should this return split values???
@ -641,7 +644,7 @@ var TagsPrototype = {
// get all tags... // get all tags...
} else { } else {
return Object.keys(this.__index || {}) return Object.keys(this.__index || {})
.concat([...(this.__persistent_tags || [])] .concat([...(this.persistent || [])]
.map(function(t){ .map(function(t){
return that.normalize(t) })) return that.normalize(t) }))
.unique() .unique()
@ -700,7 +703,7 @@ var TagsPrototype = {
// -> this // -> this
// //
alias: function(tag, value){ alias: function(tag, value){
var aliases = this.__aliases = this.__aliases || {} var aliases = this.aliases = this.aliases || {}
// XXX this seems a bit ugly... // XXX this seems a bit ugly...
var resolve = function(tag, seen){ var resolve = function(tag, seen){
seen = seen || [] seen = seen || []
@ -903,8 +906,8 @@ var TagsPrototype = {
tags = normalizeSplit(tags) tags = normalizeSplit(tags)
var persistent = var persistent =
this.__persistent_tags = this.persistent =
this.__persistent_tags || new Set() this.persistent || new Set()
return this.normalize(tags) return this.normalize(tags)
.map(function(tag){ .map(function(tag){
@ -1004,9 +1007,9 @@ var TagsPrototype = {
// rename actual data... // rename actual data...
} else { } else {
patchSet(this.__persistent_tags || []) patchSet(this.persistent || [])
patchObj(this.__index || {}) patchObj(this.__index || {})
patchObj(this.__aliases || {}, true) patchObj(this.aliases || {}, true)
} }
return this return this
@ -1051,8 +1054,45 @@ var TagsPrototype = {
return res return res
}, },
// Keep only the longest tag paths per value...
//
// Optimize tags for all values...
// .optimizeTags()
// -> values
//
// Optimize tags for specific values...
// .optimizeTags(value, ..)
// .optimizeTags([value, ..])
// -> values
//
//
// Example:
// var ts = new Tags()
// ts.tag(['a/b/c', 'a/c'], x)
//
// ts.optimizeTags() // will remove 'a/c' form x as it is fully
// // contained within 'a/b/c'...
//
// XXX should this be done on .tag(..) and friends???
optimizeTags: function(...values){
var that = this
return (normalizeSplit(values) || this.values())
.filter(function(value){
var tags = new Set(that.paths(value))
tags = [...tags.subtract(that.uniquePaths(...tags))]
tags.length > 0
&& that.untag(tags, value)
return tags.length > 0 }) },
// Tags - Tags API... // Make all paths persistent...
//
// NOTE: this will add only longest unique paths (see: .uniquePaths(..))
makePathsPersistent: function(){
this.persistent = new Set(this.uniquePaths(this.paths()))
return this },
// Tags-Tags API...
// //
// Join 1 or more Tags objects... // Join 1 or more Tags objects...
// //
@ -1379,12 +1419,12 @@ var TagsPrototype = {
var res = {} var res = {}
// aliases... // aliases...
this.__aliases && Object.keys(this.__aliases).length > 0 this.aliases && Object.keys(this.aliases).length > 0
&& (res.aliases = Object.assign({}, this.__aliases)) && (res.aliases = Object.assign({}, this.aliases))
// persistent tags... // persistent tags...
this.__persistent_tags && this.__persistent_tags.size > 0 this.persistent && this.persistent.size > 0
&& (res.persistent = [...this.__persistent_tags]) && (res.persistent = [...this.persistent])
// tags... // tags...
res.tags = {} res.tags = {}
@ -1400,11 +1440,11 @@ var TagsPrototype = {
// aliases... // aliases...
json.aliases json.aliases
&& (this.__aliases = Object.assign({}, json.aliases)) && (this.aliases = Object.assign({}, json.aliases))
// persistent tags... // persistent tags...
json.persistent json.persistent
&& (this.__persistent_tags = new Set(json.persistent)) && (this.persistent = new Set(json.persistent))
// tags... // tags...
json.tags json.tags
@ -1424,11 +1464,90 @@ var TagsPrototype = {
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
var Tags = var BaseTags =
module.Tags = module.BaseTags =
object.makeConstructor('Tags', object.makeConstructor('BaseTags',
TagsClassPrototype, BaseTagsClassPrototype,
TagsPrototype) BaseTagsPrototype)
//---------------------------------------------------------------------
// XXX EXPERIMENTAL...
var TagsWithHandlersPrototype = {
__proto__: BaseTagsPrototype,
// XXX docs...
__special_tag_handlers__: {
// print and remove tag...
'test': function(tag, action, value){
console.log('TEST TAG:', tag, action, value)
return this.removeTag('test', tag)[0]
},
// terminate handling...
'stop': function(tag, action, value){
console.log('STOP:', tag, action, value)
return false
},
// print the tag...
'*': function(tag, action, value){
console.log('TAG:', tag, action, value)
return tag
},
},
// NOTE: handlers are called in order of handler occurrence and not
// in the order the tags are in the given chain/path...
handleSpecialTag: function(tag, ...args){
var that = this
var handlers = this.__special_tag_handlers__
tag = this.normalize(tag)
return Object.keys(handlers)
.filter(function(p){
// keep only valid tag patterns...
// NOTE: this enables us to create special handlers
// that will not be used for matching but are more
// mnemonic...
return p == that.normalize(p)
// get the matching handler keys...
&& that.directMatch(p, tag)
})
// resolve handler aliases...
.map(function(match){
do {
match = handlers[match]
} while(!(match instanceof Function) && match in handlers)
return match instanceof Function ?
match
// no handler...
: [] })
.flat()
// call the handlers...
// NOTE: we are threading tag through the handlers...
.reduce(function(tag, handler){
return tag && handler.call(that, tag, ...args) }, tag)
// no handlers -> return as-is...
|| tag },
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
var TagsWithHandlers =
module.TagsWithHandlers =
object.makeConstructor('TagsWithHandlers',
BaseTagsClassPrototype,
TagsWithHandlersPrototype)
//---------------------------------------------------------------------
var Tags =
module.Tags = TagsWithHandlers