added basic tag support...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2022-10-26 17:06:06 +03:00
parent 0df1919259
commit f58b5fbd56
3 changed files with 146 additions and 41 deletions

View File

@ -76,6 +76,8 @@ object.Constructor('BasePage', {
type: true,
ctime: true,
mtime: true,
// XXX TAGS HACK -- should this be a list???
tags: 'tagstr',
},
// These actions will be default get :$ARGS appended if no args are
// explicitly given...
@ -337,6 +339,28 @@ object.Constructor('BasePage', {
} else {
this.__update__(value) } },
get tags(){ return async function(){
return (await this.data).tags ?? [] }.call(this) },
set tags(value){ return async function(){
this.data = {
...(await this.data),
tags: [...value],
} }.call(this) },
// XXX TAGS HACK -- should this be a list???
get tagstr(){ return async function(){
return JSON.stringify(await this.tags ?? []) }.call(this) },
tag: async function(...tags){
this.tags = [...new Set([
...(await this.tags),
...tags,
])]
return this },
untag: async function(...tags){
this.tags = (await this.tags)
.filter(function(tag){
return !tags.includes(tag) })
return this },
// metadata...
//
// NOTE: in the general case this is the same as .data but in also allows
@ -1263,7 +1287,6 @@ object.Constructor('Page', BasePage, {
var that = this
var name = args.name //?? args[0]
var src = args.src
var all = args.all
var base = this.get(this.path.split(/\*/).shift())
var sort = (args.sort ?? '')
.split(/\s+/g)
@ -1973,7 +1996,7 @@ module.System = {
<a href="#@source(s ../../path)/list">&#x21D1;</a>
@source(../path)
</slot>
<macro src="../*:@(all)" join="@source(line-separator)">
<macro src="../*:$ARGS" join="@source(line-separator)">
<a href="#@source(s ./path)">@source(./name)</a>
<sup>
<macro src="./isAction">
@ -1991,7 +2014,7 @@ module.System = {
tree: {
text: object.doc`
<slot title/>
<macro src="../*:@(all)">
<macro src="../*:$ARGS">
<div>
<div class="item">
<a class="tree-page-title" href="#@source(s ./path)">@source(./title)</a>
@ -1999,12 +2022,12 @@ module.System = {
<a class="show-on-hover" href="#@source(s ./path)/delete">&times;</a>
</div>
<div style="padding-left: 30px">
@include("./tree:@(all)")
@include("./tree:$ARGS")
</div>
</div>
</macro>` },
all: {
text: `@include("../**/path:@(all)" join="@source(line-separator)")`},
text: `@include("../**/path:$ARGS" join="@source(line-separator)")`},
info: {
text: object.doc`
<slot pre>
@ -2022,6 +2045,8 @@ module.System = {
type: @source(../type)<br>
tags: @source(../tags)<br>
ctime: @source(../ctime)<br>
mtime: @source(../mtime)<br>
@ -2187,7 +2212,7 @@ module.Templates = {
text: object.doc`
<slot title/>
<slot header><content/><a href="#./$NOW/edit">&#128462;</a></slot>
<macro src="*:@(all)" join="<br>">
<macro src="*:$ARGS" join="<br>">
<div class="item">
<a href="#@source(s ./path)/edit">@source(./title)</a>
<a class="show-on-hover" href="#@source(s ./path)/info">&#128712;</a>

View File

@ -124,16 +124,22 @@ module.BaseStore = {
index: async function(action='get', ...args){
return index.index(this, ...arguments) },
// XXX INDEX...
//
// Format:
// [
// <path>,
// ...
// ]
//
__paths__: async function(){
return Object.keys(this.data) },
// XXX unique???
__paths_merge__: async function(data){
return (await data)
.concat((this.next
&& 'paths' in this.next) ?
await this.next.paths
: []) },
: [])
.unique() },
__paths_isvalid__: function(t){
var changed =
!!this.__paths_next_exists != !!this.next
@ -159,6 +165,16 @@ module.BaseStore = {
get paths(){
return this.__paths() },
//
// Format:
// {
// <name>: [
// <path>,
// ...
// ],
// ...
// }
//
__names_isvalid__: function(t){
return this.__paths_isvalid__(t) },
// NOTE: this is built from .paths so there is no need to define a
@ -197,8 +213,76 @@ module.BaseStore = {
return this.__names() },
// XXX tags
//
// Format:
// {
// tags: {
// <tag>: Set([
// <path>,
// ...
// ]),
// ...
// },
// paths: {
// <path>: Set([
// <tag>,
// ...
// ]),
// ...
// }
// }
//
// XXX should this be here???
parseTags: function(str){
return str
.split(/\s*(?:([a-zA-Z1-9_-]+)|"(.+)"|'(.+)')\s*/g)
.filter(function(t){
return t
&& t != ''
&& t != ',' }) },
// XXX do we need these???
// ...the question is if we have .__tags__(..) how do we
// partially .__tags_merge__(..) things???
//__tags__: function(){ },
//__tags_merge__: function(data){ },
__tags_isvalid__: function(t){
return this.__paths_isvalid__(t) },
__tags: index.makeIndex('tags',
async function(){
var tags = {}
var paths = {}
for(var path of (await this.paths)){
var t = (await this.get(path)).tags
if(!t){
continue }
paths[path] = new Set(t)
for(var tag of t){
;(tags[tag] =
tags[tag] ?? new Set([]))
.add(path) } }
return {tags, paths} }, {
update: async function(data, path, update){
if(!('tags' in update)){
return data }
var {tags, paths} = await data
// remove obsolete tags...
this.__tags.options.remove.call(this, data, path)
// add...
paths[path] = new Set(update.tags)
for(var tag of update.tags ?? []){
;(tags[tag] =
tags[tag] ?? new Set([]))
.add(path) }
return data },
remove: async function(data, path){
var {tags, paths} = await data
for(var tag of paths[path]){
tags[tag].delete(path) }
return data }, }),
get tags(){
return this.__tags() },
// XXX text search index
// XXX text search index (???)
//
@ -293,9 +377,16 @@ module.BaseStore = {
if(path.includes('*')
|| path.includes('**')){
var order = (this.metadata(path) ?? {}).order || []
var {path, args} = pwpath.splitArgs(path)
var all = args.all
var tags = args.tags
tags = typeof(tags) == 'string' ?
this.parseTags(tags)
: false
tags && await this.tags
args = pwpath.joinArgs('', args)
// NOTE: we are matching full paths only here so leading and
// trainling '/' are optional...
var pattern = new RegExp(`^\\/?`
@ -317,6 +408,14 @@ module.BaseStore = {
// skip metadata paths...
if(p.includes('*')){
return res }
// skip untagged pages...
if(tags){
var t = that.tags.paths[p]
if(!t){
return res }
for(var tag of tags){
if(!t || !t.has(tag)){
return res } } }
var m = [...p.matchAll(pattern)]
m.length > 0
&& (!all ?
@ -363,7 +462,6 @@ module.BaseStore = {
if(path.includes('*')
|| path.includes('**')){
var p = pwpath.splitArgs(path)
var all = p.args.all
var args = pwpath.joinArgs('', p.args)
p = pwpath.split(p.path)
var tail = []
@ -372,12 +470,11 @@ module.BaseStore = {
tail = tail.join('/')
if(tail.length > 0){
return (await this.match(
p.join('/') + (all ? ':all' : ''),
p.join('/') + args,
strict))
.map(function(p){
all &&
(p = p.replace(/:all/, ''))
return pwpath.join(p, tail) + args }) } }
var {path, args} = pwpath.splitArgs(p)
return pwpath.joinArgs(pwpath.join(path, tail), args) }) } }
// direct...
return this.match(path, strict) },
//

View File

@ -17,17 +17,18 @@
* - CLI
*
*
* XXX TAGS should ./tags (i.e. .tagstr) return a list of tags???
* XXX TAGS
* - add tags to page -- macro/filter
* - <page>.text -> <page>.tags (cached on .update(..))
* - manual
* - a way to list tags -- folder like? - ???
* - tag cache <store>.tags - DONE
* - tag-path filtering... - DONE
* XXX TAGS add a more advanced query -- e.g. "/**:tagged=y,z:untagged=x" ???
* XXX INDEX DOC can index validation be async???
* ...likely no
* XXX INDEX add option to set default action (get/lazy/cached)
* XXX BUG: when editing the root page of a substore the page's .cache is
* not reset for some reason...
* ...the problem is in that .names() cache is not reset when a new
* page is created...
* ...this does not appear to affect normal pages...
* ...the root issue is that .__cache_add(..)/.__cache_remove(..)
* are called relative to the nested store and not the root...
* ...feels like we need to rethink the cache/index strategy globally...
* XXX CachedStore seems to be broken (see: pwiki/store/base.js:837)
* XXX might be a good idea to create memory store (sandbox) from the
* page API -- action??
@ -106,24 +107,6 @@
* - count + elem-offset
* - from + to
* XXX revise/update sort...
* XXX FEATURE tags: might be a good idea to add a .__match__(..) hook
* to enable store-level matching optimization...
* ...not trivial to route to alk the stores...
* XXX FEATURE tags and accompanying API...
* - add tags to page -- macro/filter
* - <page>.text -> <page>.tags (cached on .update(..))
* - manual
* - a way to list tags -- folder like?
* - tag cache <store>.tags
* format:
* {
* <tag>: [<path>, ...],
* }
* - tag-path filtering...
* i.e. only show tags within a specific path/pattern...
* - path integration...
* i.e. a way to pass tags through path...
* /some/path:tags=a,b,c
* XXX FEATURE images...
* XXX async/live render...
* might be fun to push the async parts of the render to the dom...