shrub/app/collections.hoon

731 lines
19 KiB
Plaintext
Raw Normal View History

::
:::: /app/collections/hoon
::
2018-07-31 07:00:49 +03:00
/? 309
/- hall
/+ collections
::
:: cols:
::
:: run collections-item renderer on children of /web/collections
2018-09-07 02:39:02 +03:00
:: combine with a bunted config in a +collection structure defined in
:: /lib/collections because the top level collection has no config file
::
2018-09-07 02:39:02 +03:00
:: whenever any of the clay files that compose this renderer change, this app
:: will recompile and the +prep arm will fire. we then check which files
:: changed and notify the corresponding hall circle of that change
::
2018-07-31 07:00:49 +03:00
/= cols
/^ collection:collections
/; |= a=(map knot item:collections)
[*config:collections a]
2018-08-01 02:31:30 +03:00
/: /===/web/collections /_ /collections-item/
2017-12-02 21:45:47 +03:00
::
2018-07-31 07:00:49 +03:00
=, collections
=, space:userlib
2017-12-02 21:45:47 +03:00
::
2018-07-31 07:00:49 +03:00
|%
+= move [bone card]
+= card
$% [%info wire ship toro:clay]
[%poke wire dock poke]
[%perm wire ship desk path rite:clay]
==
+= poke
$% [%hall-action action:hall]
[%collections-action action:collections]
2018-07-31 07:00:49 +03:00
[%json json]
==
--
2017-12-14 03:04:45 +03:00
::
:: state:
::
2018-09-07 02:39:02 +03:00
:: stores the collection built by above by :cols so that we can compare old
:: and new versions whenever the rendered data changes
::
2018-07-31 07:00:49 +03:00
|_ [bol=bowl:gall state=collection]
2017-12-14 03:04:45 +03:00
::
:: +this: app core subject
::
2018-07-31 07:00:49 +03:00
++ this .
2017-12-02 21:45:47 +03:00
::
:: +prep:
::
:: on initial boot, create top level hall circle for collections, called %c
::
:: on subsequent compiles, call +ta-update:ta on the old collection data,
:: then update state to store the new collection data
::
2018-07-31 07:00:49 +03:00
++ prep
|= old=(unit *)
^- (quip move _this)
?~ old
=< ta-done
(ta-hall-create-circle:ta /c 'collections')
=/ old-col ((soft collection) u.old)
?~ old-col
[~ this(state cols)]
=^ mow this
=< ta-done
(ta-update:ta u.old-col)
[mow this(state cols)]
2017-12-02 21:45:47 +03:00
::
2018-09-07 02:39:02 +03:00
:: +mack:
::
:: recieve acknowledgement for permissions changes, print error if it failed
2017-12-02 21:45:47 +03:00
::
2018-07-31 07:00:49 +03:00
++ mack
|= [wir=wire err=(unit tang)]
^- (quip move _this)
?~ err
[~ this]
(mean u.err)
2018-03-06 04:35:33 +03:00
::
:: +coup: recieve acknowledgement for poke, print error if it failed
::
++ coup
|= [wir=wire err=(unit tang)]
^- (quip move _this)
?~ err
[~ this]
(mean u.err)
::
:: +path-to-circle:
::
:: takes a clay path and returns a hall circle
:: for a path /foo/bar it returns a circle with a :name %c-foo-bar
2017-12-02 21:45:47 +03:00
::
2018-07-31 07:00:49 +03:00
++ path-to-circle
|= pax=path
^- circle:hall
=. pax
?: ?=([%web %collections *] pax)
(weld /c (slag 2 `path`pax))
?: ?=([%collections *] pax)
(weld /c (slag 1 `path`pax))
?: ?=([%c *] pax)
`path`pax
`path`(weld /c pax)
=/ nam=term
%+ roll `(list @ta)`pax
|= [seg=@ta out=term]
%^ cat 3
?:(=(%$ out) out (cat 3 out '-'))
((hard @tas) seg)
[our.bol nam]
::
:: +allowed-by: checks if ship :who is allowed by the permission rules in :dic
::
2018-07-31 07:00:49 +03:00
++ allowed-by
|= [who=@p dic=dict:clay]
^- ?
?: =(who our.bol) &
2018-07-31 07:00:49 +03:00
=/ in-list=?
?| (~(has in p.who.rul.dic) who)
::
%- ~(rep by q.who.rul.dic)
|= [[@ta cru=crew:clay] out=_|]
?: out &
(~(has in cru) who)
==
?: =(%black mod.rul.dic)
!in-list
in-list
2018-06-04 20:09:04 +03:00
::
:: +collection-notify: XX
::
2018-07-31 07:00:49 +03:00
++ collection-notify
|= [pax=path conf=config]
^- json
%- pairs:enjs:format
:~ ['owner' [%s (crip (scow %p owner.conf))]]
['path' [%a (turn pax |=(a=@ta `json`[%s a]))]]
['name' [%s name.conf]]
['date' [%s (crip (scow %da last-modified.conf))]]
['type' [%s type.conf]]
2017-12-13 05:48:57 +03:00
==
2018-07-31 07:00:49 +03:00
::
:: +item-notify: XX
::
2018-07-31 07:00:49 +03:00
++ item-notify
|= [pax=path raw=raw-item]
^- json
=/ owner (fall (~(get by meta.raw) %owner) ~.anon)
=/ dat (fall (~(get by meta.raw) %last-modified) (scot %da now.bol))
=/ nom (fall (~(get by meta.raw) %name) ~.no-title)
=/ typ (fall (~(get by meta.raw) %type) ~.no-type)
%- pairs:enjs:format
:~ ['owner' [%s owner]]
['path' [%a (turn pax |=(a=@ta `json`[%s a]))]]
['name' [%s nom]]
['date' [%s dat]]
['type' [%s typ]]
['content' [%s data.raw]]
==
2017-12-13 05:48:57 +03:00
::
:: +front-to-wain: XX
::
++ front-to-wain
|= a=(map knot cord)
^- wain
=/ entries=wain
%+ turn ~(tap by a)
|= b=[knot cord]
2018-08-21 00:16:57 +03:00
=/ c=[term cord] ((hard ,[term cord]) b)
(crip " [{<-.c>} {<+.c>}]")
::
?~ entries ~
;: weld
[':- :~' ~]
entries
[' ==' ~]
==
::
:: +update-umd-front: XX
::
++ update-umd-front
|= [fro=(map knot cord) umd=@t]
^- @t
%- of-wain:format
2018-08-03 04:04:52 +03:00
=/ tum (trip umd)
=/ id (find ";>" tum)
?~ id
%+ weld (front-to-wain fro)
(to-wain:format (crip (weld ";>\0a" tum)))
%+ weld (front-to-wain fro)
(to-wain:format (crip (slag u.id tum)))
::
:: +poke-collections-action:
2018-07-31 07:00:49 +03:00
::
2018-09-07 03:31:06 +03:00
:: the main interface for creating and deleting collections and items
2018-07-31 07:00:49 +03:00
::
++ poke-collections-action
2018-07-31 07:00:49 +03:00
|= act=action:collections
^- (quip move _this)
2018-08-21 00:16:57 +03:00
?: =(who.act our.bol)
ta-done:(ta-act:ta act)
:: forward poke if its not meant for us
::
2018-08-21 00:16:57 +03:00
:_ this
:_ ~
:* ost.bol %poke
/forward-collections-action
[who.act %collections]
%collections-action act
2017-12-02 21:45:47 +03:00
==
2018-07-31 07:00:49 +03:00
::
2018-09-05 04:01:17 +03:00
:: +poke-noun
::
:: utility for setting whether or not to display the onboarding page
::
++ poke-noun
|= onb=?
^- (quip move _this)
=< ta-done
(ta-write:ta /web/landscape/onboard/atom [%atom !>(onb)])
::
:: +ta: main event core for collections
2017-12-02 21:45:47 +03:00
::
++ ta
2018-07-31 07:00:49 +03:00
|_ moves=(list move)
::
:: +ta-this: ta core subject
2018-07-31 07:00:49 +03:00
::
2017-12-16 05:22:33 +03:00
++ ta-this .
::
2018-09-07 03:31:06 +03:00
:: +ta-done:
::
:: flop :moves for finalization, since moves are prepended to the list
::
2018-07-31 07:00:49 +03:00
++ ta-done [(flop moves) this]
::
:: +ta-emit: add a +move to :moves
::
2018-07-31 07:00:49 +03:00
++ ta-emit
|= mov=move
%_ ta-this
moves [mov moves]
==
::
:: +ta-emil: add a list of +move to :moves
::
2018-07-31 07:00:49 +03:00
++ ta-emil
|= mos=(list move)
%_ ta-this
moves (welp (flop mos) moves)
==
2017-12-02 21:45:47 +03:00
::
:: +ta-act: process collection-action
2018-07-31 07:00:49 +03:00
::
++ ta-act
|= act=action:collections
^+ ta-this
::
:: iterate through list of +sub-action of +action
::
2018-07-31 07:00:49 +03:00
|-
?~ acts.act ta-this
=* a i.acts.act
2018-08-21 00:16:57 +03:00
::
=/ sap (en-beam:format [byk.bol (flop (path +<.a))])
=/ now-id=@da (sub now.bol (div (dis now.bol ~s0..fffe) 2))
=/ dat (scot %da now-id)
::
2018-07-31 07:00:49 +03:00
=. ta-this
2018-08-21 00:16:57 +03:00
?- -.a
%write
=/ perms .^([dict:clay dict:clay] %cp sap)
?: (allowed-by src.bol +.perms)
2018-08-03 04:04:52 +03:00
?- -.for.a
%umd (ta-write pax.a `cage`[-.for.a !>(+.for.a)])
%collections-config (ta-write pax.a `cage`[-.for.a !>(+.for.a)])
==
2018-08-21 00:16:57 +03:00
ta-this
::
%delete
=/ perms .^([dict:clay dict:clay] %cp sap)
?: (allowed-by src.bol +.perms)
(ta-remove pax.a)
ta-this
::
%perms
?: =(src.bol our.bol) :: XX admin privileges for other users?
(ta-set-permissions pax.a r.a w.a)
ta-this
::
::
:: XX some of this is redunant
::
%collection
2018-09-07 03:31:06 +03:00
=/ perms
.^([dict:clay dict:clay] %cp (weld sap /[dat]/collections-config))
2018-08-21 00:16:57 +03:00
?. (allowed-by src.bol +.perms)
ta-this
=/ conf=config
:* [byk.bol (flop (weld pax.a /[dat]/collections-config))]
name.a
desc.a
our.bol
now-id
now-id
2018-08-21 00:16:57 +03:00
type.a
comments.a
~
visible.a
==
=. ta-this
%+ ta-write (weld pax.a /[dat]/collections-config)
[%collections-config !>(conf)]
:: restrict permissions on config file
=. ta-this
%^ ta-set-permissions (weld pax.a /[dat]/collections-config)
2018-09-07 03:31:06 +03:00
[%white ((set whom:clay) [[& src.bol] ~ ~])] :: read
2018-08-21 00:16:57 +03:00
[%white ((set whom:clay) [[& src.bol] ~ ~])] :: write
:: open permissions on collection items
=. ta-this
%^ ta-set-permissions (weld pax.a /[dat])
[%black ((set whom:clay) ~)] :: read
[%black ((set whom:clay) ~)] :: write
ta-this
::
%post
=/ perms .^([dict:clay dict:clay] %cp (weld sap /[dat]/umd))
?. (allowed-by src.bol +.perms)
ta-this
=. content.a (crip (weld (trip content.a) "\0a"))
=/ front=(map knot cord)
%- my
:~ [%name name.a]
[%comments ?:(comments.a ~..y ~..n)]
[%owner (scot %p src.bol)]
[%date-created dat]
[%last-modified dat]
2018-08-21 00:16:57 +03:00
[%type type.a]
==
=. ta-this
%+ ta-write (weld pax.a /[dat]/umd)
[%umd !>((update-umd-front front content.a))]
:: restrict permissions on umd file
=. ta-this
%^ ta-set-permissions (weld pax.a /[dat]/umd)
[%black ((set whom:clay) ~)] :: read
[%white ((set whom:clay) [[& src.bol] ~ ~])] :: write
:: open permissions on comments
=. ta-this
%^ ta-set-permissions (weld pax.a /[dat])
[%black ((set whom:clay) ~)] :: read
[%black ((set whom:clay) ~)] :: write
ta-this
::
%comment
=/ perms .^([dict:clay dict:clay] %cp (weld sap /[dat]/umd))
?. (allowed-by src.bol +.perms)
ta-this
=. content.a (crip (weld (trip content.a) "\0a"))
=/ front=(map knot cord)
%- my
:~ [%owner (scot %p src.bol)]
[%date-created dat]
[%last-modified dat]
[%type %comments]
==
=. ta-this
%+ ta-write (weld pax.a /[dat]/umd)
[%umd !>((update-umd-front front content.a))]
:: restrict permissions on umd file
=. ta-this
%^ ta-set-permissions (weld pax.a /[dat]/umd)
[%black ((set whom:clay) ~)] :: read
[%white ((set whom:clay) [[& src.bol] ~ ~])] :: write
ta-this
::
2017-12-16 05:06:29 +03:00
==
2018-07-31 07:00:49 +03:00
$(acts.act t.acts.act)
2017-12-16 05:06:29 +03:00
::
:: +ta-update:
::
::
2018-07-31 07:00:49 +03:00
::
++ ta-update
|= old=collection
^+ ta-this
?: =(old cols)
ta-this
(ta-update-collection old cols /web/collections)
::
++ ta-insert-item
|= [new=item pax=path]
^+ ta-this
=/ parent-path (scag (dec (lent pax)) pax)
::
?- -.new
::
%collection
=. ta-this
2018-09-07 03:31:06 +03:00
%^ ta-hall-json parent-path 'new collection'
(collection-notify pax meta.col.new)
2018-07-31 07:00:49 +03:00
::
=. ta-this (ta-hall-create-circle pax description.meta.col.new)
=/ items=(list [nom=@ta =item]) ~(tap by data.col.new)
|-
?~ items ta-this
=. ta-this (ta-insert-item item.i.items (weld pax [nom.i.items ~]))
$(items t.items)
::
%both
2018-08-31 00:13:40 +03:00
=. ta-this (ta-hall-create-circle pax description.meta.col.new)
2018-07-31 07:00:49 +03:00
=/ items=(list [nom=@ta =item]) ~(tap by data.col.new)
=. ta-this
|-
?~ items ta-this
=. ta-this (ta-insert-item item.i.items (weld pax [nom.i.items ~]))
$(items t.items)
::
ta-this
::
%raw
=. ta-this
(ta-hall-json parent-path 'new item' (item-notify pax raw.new))
?: ?& (~(has by meta.raw.new) %comments)
=('.y' (~(got by meta.raw.new) %comments))
==
(ta-generate-comments pax)
ta-this
::
==
::
++ ta-remove-item
|= [old=item pax=path]
^+ ta-this
:: flush permissions
:: notify parent of deletion
=/ parent (scag (dec (lent pax)) pax)
:: recurse for children
?- -.old
::
%collection
=. ta-this
2018-09-07 03:31:06 +03:00
%^ ta-hall-json parent 'deleted collection'
(collection-notify pax meta.col.old)
=. ta-this (ta-flush-permissions (weld pax /collections-config))
2018-07-31 07:00:49 +03:00
=/ items=(list [nom=@ta =item]) ~(tap by data.col.old)
|-
?~ items ta-this
=. ta-this (ta-remove-item item.i.items (weld pax [nom.i.items ~]))
$(items t.items)
::
%both
=. ta-this (ta-flush-permissions pax)
=. ta-this (ta-flush-permissions (weld pax /collections-config))
2018-07-31 07:00:49 +03:00
=/ items=(list [nom=@ta =item]) ~(tap by data.col.old)
|-
?~ items ta-this
=. ta-this (ta-remove-item item.i.items (weld pax [nom.i.items ~]))
$(items t.items)
::
%raw
=. ta-this (ta-flush-permissions pax)
(ta-hall-json parent 'deleted item' (item-notify pax raw.old))
::
==
::
::
::
++ ta-update-item
:: always make sure removals happen first and insertions happen last
:: because removals flush permissions and insertions set them
::
|= [old=item new=item pax=path]
^+ ta-this
?: =(old new)
ta-this
::
:: check for changes in item type
?: &(?=(%collection -.old) ?=(%collection -.new))
(ta-update-collection col.old col.new pax)
?: &(?=(%raw -.old) ?=(%raw -.new))
(ta-update-raw-item raw.old raw.new pax)
?: &(?=(%both -.old) ?=(%both -.new))
:: update raw item
=. ta-this (ta-update-collection col.old col.new pax)
(ta-update-raw-item raw.old raw.new pax)
::
?: &(?=(%collection -.old) ?=(%raw -.new))
:: remove collection
:: insert raw item
=. ta-this (ta-remove-item old pax)
(ta-insert-item new pax)
::
?: &(?=(%collection -.old) ?=(%both -.new))
:: insert raw item
:: update-collection
=. ta-this (ta-update-collection col.old col.new pax)
(ta-insert-item new pax)
::
?: &(?=(%raw -.old) ?=(%collection -.new))
:: remove raw item
:: insert collection
=. ta-this (ta-remove-item old pax)
(ta-insert-item new pax)
::
?: &(?=(%raw -.old) ?=(%both -.new))
:: insert collection
:: update raw item
=. ta-this (ta-update-raw-item raw.old raw.new pax)
(ta-insert-item new pax)
::
?: &(?=(%both -.old) ?=(%raw -.new))
:: remove collection
:: update raw item
=. ta-this (ta-remove-item [%collection col.old] pax)
(ta-update-raw-item raw.old raw.new pax)
::
?: &(?=(%both -.old) ?=(%collection -.new))
:: remove raw item
:: update collection
=. ta-this (ta-remove-item [%raw raw.old] pax)
(ta-update-collection col.old col.new pax)
::
ta-this
::
++ ta-update-raw-item
|= [old=raw-item new=raw-item pax=path]
^+ ta-this
?: =(old new)
ta-this
::
=? ta-this !=(data.old data.new)
=/ parent-path (scag (dec (lent pax)) pax)
(ta-hall-json parent-path 'edited item' (item-notify pax new)) :: XX fil
::
=? ta-this
?& =('.y' (fall (~(get by meta.new) %comments) '.n'))
=('.n' (fall (~(get by meta.old) %comments) '.n'))
2018-03-10 04:34:29 +03:00
==
2018-07-31 07:00:49 +03:00
:: create comments
(ta-generate-comments pax)
::
=? ta-this
?& =('.n' (fall (~(get by meta.new) %comments) '.n'))
=('.y' (fall (~(get by meta.old) %comments) '.n'))
==
:: delete comments
(ta-remove (weld pax /collections-config))
2018-07-31 07:00:49 +03:00
::
:: check if file has been modified
:: and if so update last modified field
=/ told (trip data.old)
=/ newt (trip data.new)
=/ old-con (slag (need (find ";>" told)) told)
=/ new-con (slag (need (find ";>" newt)) newt)
=? ta-this !=(old-con new-con)
=/ contents=@t
%+ update-umd-front
(~(put by meta.new) %last-modified (scot %da now.bol))
data.new
(ta-write (weld pax /umd) %umd !>(contents))
::
ta-this
::
++ ta-update-collection
|= $: old=collection
new=collection
pax=path
==
^+ ta-this
::
=? ta-this !=(meta.old meta.new)
=/ parent-path (scag (dec (lent pax)) pax)
2018-09-07 03:31:06 +03:00
%^ ta-hall-json parent-path 'edited collection'
(collection-notify pax meta.new)
2018-07-31 07:00:49 +03:00
::
?: =(data.old data.new)
ta-this
::
:: new values of all changed items
=/ upd-new (~(dif in (~(int by data.old) data.new)) data.old)
:: old values of all changed items
=/ upd-old (~(dif in (~(int by data.new) data.old)) data.new)
:: all totally new entries
=/ ins-new (~(dif by data.new) data.old)
:: all deleted entries
=/ del-old (~(dif by data.old) data.new)
::
=/ upd-new=(list [nom=knot =item]) ~(tap by upd-new)
=/ upd-old=(list [nom=knot =item]) ~(tap by upd-old)
=/ ins-new=(list [nom=knot =item]) ~(tap by ins-new)
=/ del-old=(list [nom=knot =item]) ~(tap by del-old)
::
=/ lam |=([[a=knot item] out=(list path)] [(weld pax [a ~]) out])
::
=. ta-this |-
?~ upd-new
ta-this
?< ?=(~ upd-old)
=* new-item i.upd-new
=* old-item i.upd-old
=/ new-pax (weld pax [nom.new-item ~])
=. ta-this (ta-update-item item.old-item item.new-item new-pax)
::
%= $
upd-new t.upd-new
upd-old t.upd-old
==
::
=. ta-this |-
?~ ins-new
ta-this
=* new-item i.ins-new
=/ new-pax (weld pax [nom.new-item ~])
=. ta-this (ta-insert-item +.new-item (weld pax [-.new-item ~]))
$(ins-new t.ins-new)
::
=. ta-this |-
?~ del-old
ta-this
=* old-item i.del-old
=/ old-pax (weld pax [nom.old-item ~])
=. ta-this (ta-remove-item +.old-item (weld pax [-.old-item ~]))
$(del-old t.del-old)
::
ta-this
::
++ ta-generate-comments
|= pax=path
^+ ta-this
=/ sup=path [%collections-config (flop pax)]
2018-07-31 07:00:49 +03:00
=/ pat (en-beam:format [byk.bol sup])
2018-09-07 02:39:02 +03:00
=/ dat=@da (slav %da (snag 0 (flop pax)))
2018-07-31 07:00:49 +03:00
=/ cay=config
:* [byk.bol sup]
'comments'
'comments'
our.bol
2018-09-07 02:39:02 +03:00
dat
dat
2018-07-31 07:00:49 +03:00
%comments
|
~
|
==
(ta-write (flop sup) %collections-config !>(cay))
2018-07-31 07:00:49 +03:00
::
:: writing files
::
++ ta-write
|= [pax=path cay=cage]
^+ ta-this
=. pax (en-beam:format byk.bol (flop pax))
%+ ta-emit ost.bol
[%info (weld /ta-write pax) our.bol (foal pax cay)]
2017-12-13 05:48:57 +03:00
::
2017-12-16 05:22:33 +03:00
++ ta-remove
2018-07-31 07:00:49 +03:00
|= pax=path
=. pax (en-beam:format byk.bol (flop pax))
^+ ta-this
2017-12-16 05:22:33 +03:00
%+ ta-emit ost.bol
2018-07-31 07:00:49 +03:00
[%info (weld /ta-remove pax) our.bol (fray pax)]
2017-12-16 05:22:33 +03:00
::
2018-07-31 07:00:49 +03:00
:: permissions
::
2018-07-31 07:00:49 +03:00
++ ta-set-permissions
|= [pax=path r=rule:clay w=rule:clay]
^+ ta-this
%+ ta-emit ost.bol
[%perm (weld /perms pax) our.bol q.byk.bol pax [%rw `r `w]]
::
2018-07-31 07:00:49 +03:00
++ ta-flush-permissions
|= pax=path
^+ ta-this
%+ ta-emit ost.bol
[%perm (weld /perms pax) our.bol q.byk.bol pax [%rw ~ ~]]
::
2018-07-31 07:00:49 +03:00
:: hall
::
++ ta-hall-action
|= act=action:hall
^+ ta-this
%+ ta-emit ost.bol
[%poke /col-hall-action [our.bol %hall] %hall-action act]
::
++ ta-hall-actions
|= act=(list $?(~ action:hall))
^+ ta-this
?~ act ta-this
?~ i.act $(act t.act)
%= $
ta-this (ta-hall-action i.act)
act t.act
==
2017-12-02 21:45:47 +03:00
::
2018-07-31 07:00:49 +03:00
++ ta-hall-create-circle ::
|= [pax=path description=@t]
^+ ta-this
=/ circ=circle:hall (path-to-circle pax)
=/ parent=circle:hall
?: =(nom.circ %c)
[our.bol %inbox]
(path-to-circle (scag (dec (lent pax)) pax))
%- ta-hall-actions
:~ [%create nom.circ description %journal]
[%source nom.parent & (sy `source:hall`[circ ~] ~)]
==
::
2018-07-31 07:00:49 +03:00
++ ta-hall-lin
|= [pax=path msg=cord]
^+ ta-this
=/ circ=circle:hall (path-to-circle pax)
%- ta-hall-action
2018-07-31 07:00:49 +03:00
[%phrase [circ ~ ~] [%lin | msg]~]
2017-12-13 05:48:57 +03:00
::
2018-07-31 07:00:49 +03:00
++ ta-hall-json
|= [pax=path header=@t jon=json]
^+ ta-this
=/ circ=circle:hall (path-to-circle pax)
%- ta-hall-action
:+ %phrase [circ ~ ~]
[%fat [%text ~[header]] [%lin | (crip (en-json:html jon))]]~
2017-12-13 05:48:57 +03:00
::
2018-07-31 07:00:49 +03:00
--
2017-12-02 21:45:47 +03:00
--