Merge pull request #47 from urbit/chat

Messenger -> revert-role-conversions
This commit is contained in:
~hanfel-dovned 2024-06-13 11:05:55 -07:00 committed by GitHub
commit d133fc1f18
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 1415 additions and 89 deletions

View File

@ -282,7 +282,7 @@
++ on-ack
|= =ack:neo
=/ dst=name:neo (de-pith:name:neo p.p.ack)
?> =(src.bowl ship.dst)
:: ?> =(src.bowl ship.dst)
?: =(sys-pith p.p.ack)
%. run
?~ q.ack
@ -577,7 +577,18 @@
++ meat .
++ new
|= =yuga:neo
=. yuga.deli yuga
=. yuga.deli
%- gas-yuga
%+ murn ~(tap by ~(tar of:neo yuga))
|= [=pith:neo =aeon:neo]
=/ res (look %x pith)
?: ?=(?(~ [~ ~]) res)
`[pith aeon]
?~ rot=(~(get of:neo u.u.res) ~)
`[pith aeon]
?: =(p.exe.p.p.u.rot p.exe.p.aeon)
~
`[pith aeon]
~& new-yuga/yuga
meat
++ fetched
@ -718,6 +729,7 @@
++ abet run
++ rent .
++ get-ward (~(gut of:neo city) pith *ward:neo)
++ has-kid !=(~ kid:(~(dip of:neo city) pith))
++ put-ward |=(=ward:neo rent(city (~(put of:neo city) pith ward)))
++ fact
|= [=care:neo paxs=(set pith:neo)]
@ -738,7 +750,6 @@
^- yuga:neo
?~ pic=(need (look care p/our.bowl pith))
*yuga:neo
~& epic/u.pic
(epic-to-yuga u.pic)
::
++ stop
@ -791,6 +802,8 @@
=. rent (fact %z zed.war)
?~ nex=(dif:pith:neo pith until)
rent
?. has-kid
rent
$(pith (snoc pith i.nex))
--
++ rage
@ -825,7 +838,8 @@
=/ =rave:neo (~(gut of:neo riot) here *rave:neo)
=? run =(here change)
(spaz exe.rave %x change)
=? run =(here (~(parent of:neo tide) change))
=/ par (~(parent of:neo loam) change)
=? run =(`here par)
(spaz why.rave %y change)
=. run
(spaz zed.rave %z change)
@ -865,6 +879,7 @@
++ free
|= =hunt:neo
^+ run
~& resolved/hunt
:: XX: weird shadowing, be careful
=/ =rave:neo (~(gut of:neo riot) pith.hunt *rave:neo)
=. rave
@ -1272,7 +1287,6 @@
!=(txt q.q.u.pal)
++ read-txt
|= pax=path
~& reading-txt/pax
=+ .^(src=@t %cx `path`(welp root pax))
=. pax (snip pax)
=. run (write-txt pax src)
@ -1282,7 +1296,6 @@
++ read-file
|= pax=path
^+ run
~& reading/pax
?. =((rear pax) %hoon)
(read-txt pax)
=+ .^(src=@t %cx `path`(welp root pax))
@ -1298,7 +1311,6 @@
(mean p.res)
%- mule |.
(scan (trip src) (rein:ford:neo [our.bowl (pave:neo (snip pax))]))
~& [lib=lib pro=pro]:file
=/ has-imports=?
?& (levy pro.file |=(pro:ford:neo ~(exists pro stud)))
(levy lib.file |=(lib:ford:neo ~(exists lib stud)))
@ -1813,28 +1825,40 @@
work
::
++ jazz
=| bad=(set term)
|= [=conf:neo =deps:neo]
^- [bad=(set term) block=(set tour:neo)]
%+ roll ~(tap by deps)
|= [[=term required=? =quay:neo] bad=(set term) block=(set hunt:neo)]
^+ [bad arvo]
=/ deps ~(tap by deps)
|- ^+ [bad arvo]
?~ deps
[bad arvo]
=/ [=term required=? =quay:neo] i.deps
=/ =care:neo (get-care:quay:neo quay)
?: &(required !(~(has by conf) term))
:_(block (~(put in bad) term))
=. bad (~(put in bad) term)
$(deps t.deps)
?: &(!required !(~(has by conf) term))
[bad block]
$(deps t.deps)
=/ pit=pith:neo (~(got by conf) term)
=/ res (look care pit)
=/ nam=name:neo (de-pith:name:neo pit)
?~ res
?: =(our.bowl ship.nam)
?. required
[bad block]
:_(block (~(put in bad) term))
[bad (~(put in block) care pit)]
?~ u.res
:_(block (~(put in bad) term))
[bad block] ::
::
$(deps t.deps)
=. bad (~(put in bad) term)
$(deps t.deps)
=? get.block required
(~(put in get.block) care pit)
=. run abet:(~(start sale pit) [p/our.bowl here] care)
=? run !required
(stalk:rage care^pit %rely term here)
$(deps t.deps)
?^ u.res
$(deps t.deps)
=. bad (~(put in bad) term)
$(deps t.deps)
::
++ dance
|= [=crew:neo =band:neo]
^+ arvo
@ -1877,7 +1901,7 @@
|= [src=stud:neo init=(unit pail:neo) =crew:neo]
=/ =wave:neo [src ~(dock husk src) crew]
=. tide (~(put of:neo tide) here wave)
=^ bad=(set term) get.block
=^ bad=(set term) arvo
(jazz crew deps:~(kook husk src))
?. =(~ get.block)
arvo
@ -2002,7 +2026,8 @@
?~ ack
same
(slog (print-quit:neo u.ack))
?: =(p.pail %rely) !!
?: ?=(%rely p.pail)
((slog leaf/"no support for %rely" ~) su-core)
(mean leaf/"no support for {<p.pail>}" ~)
=/ [caz=(list card:neo) new=pail:neo]
(poke:su-form pail)
@ -2094,9 +2119,7 @@
^- $-(vase vase)
?~ as.peer |=(=vase vase)
.^(tube:clay %cc (welp /(scot %p our.bowl)/[r.p.u.p.syn]/(scot %da now.bowl) /(rear kid)/[u.as.peer]))
~& res/~(key by ~(tar of res))
=. res (~(dip of res) path.peer)
~& res/~(key by ~(tar of res))
=/ =note:neo [%poke %clay-res !>(`res:clay:neo`[hand case res])]
~& sending-to/src
=/ =move:neo [[p/our.bowl #/$/clay] src note]
@ -2536,13 +2559,22 @@
=/ lom (~(dip of:neo loam) prefix)
=/ fam (~(dip of:neo farm) prefix)
=/ rav (~(dip of:neo riot) prefix)
=/ ton (~(dip of:neo town) prefix)
=/ cit (~(dip of:neo city) (tail prefix))
:- >fam<
:- >rav<
:- >ton<
:- >cit<
%- zing
%+ turn ~(tap by ~(tar of:neo lom))
|= [=pith:neo =soil:neo]
:~ >pith<
>~(key by soil)<
?~ val=(ram:on:soil:neo soil)
leaf/"No data"
?~ q.val.u.val
leaf/"~"
(sell q.u.q.val.u.val)
==
::
++ print-dbug

View File

@ -0,0 +1,54 @@
/- neo
|%
:: ++ dyad (pair @ @)
++ scam
=< scam
|%
++ on ((^on case:neo case:neo) lte)
+$ scam ((mop case:neo case:neo) lte)
--
:: ++ push
++ scum
=< scum
|%
++ on ((^on case:neo (map pith:neo case:neo)) lte)
+$ scum ((mop case:neo (map pith:neo case:neo)) lte)
--
+$ muck
$: =soil
=scum
why=scam
zed=scam
==
++ soil
=< a
^~
|%
++ on ((^on case:neo muck:neo) lte)
+$ a ((mop case:neo muck:neo) lte)
--
++ plot
:: $dirt: Layer 1 of the namespace
++ dirt
|%
+$ card (pair pith note)
+$ note
$% [%grow =pail case=(unit case) =oath]
[%cull ~]
==
+$ loam (axal soil)
+$ gift dust
--
++ frog
[=pail =
++ till
|_ =loam
++ plow
|=
++ grew
|= [=pail:neo case=@ud]
++ grow
|= [=pail:neo

View File

@ -395,7 +395,9 @@
=| gifts=(list gift:dirt:neo)
|= =epic:neo
^+ [gifts loam farm]
=/ pic ~(tap of:neo epic)
=/ pic
%+ sort ~(tap of:neo epic)
|=([[a=pith:neo *] [b=pith:neo *]] (lte-pith:neo a b))
|-
?~ pic
:+ gifts loam
@ -453,11 +455,64 @@
~
`[pit %dif]
==
++ fallback-peek-kids
|= [=care:neo =pith:neo fam=farm:neo]
^- (list pith:neo)
?: ?=(%x care) ~
?^ fil.fam
=/ [=land:neo =plot:neo] u.fil.fam
?~ kid=(ram:on:plan:neo ?:(?=(%y care) by-kids-mut.plot by-desc-mut.plot))
~
~(tap in val.u.kid)
%- zing
%+ turn ~(tap by kid.fam)
|= [=iota f=farm:neo]
^- (list pith:neo)
(fallback-peek-kids care (snoc pith iota) f)
++ fallback-peek-x
|= =pith:neo
^- (unit saga:neo)
=/ sol (~(gut of:neo loam) pith *soil:neo)
?~ val=(ram:on:soil:neo sol)
~
?~ q.val.u.val
~
`[*aeon:neo u.q.val.u.val]
::
++ fallback-peek
|= [=care:neo =pith:neo]
^- (axal:neo saga:neo)
%- gas
%+ welp
?~ rot=(fallback-peek-x pith)
~
[pith u.rot]^~
%+ murn (fallback-peek-kids care pith (~(dip of:neo farm) pith))
|= p=pith:neo
^- (unit [pith:neo saga:neo])
=/ kid (welp pith p)
?~ sag=(fallback-peek-x kid)
~
`[kid u.sag]
++ piek
|= [=care:neo =pith:neo]
^- (unit (unit (axal:neo saga:neo)))
~& peek-no-once/[care pith]
``(fallback-peek care pith)
::
++ peek
|= [=care:neo =pith:neo]
^- (unit (unit (axal:neo saga:neo)))
?~ nce=(now-once care pith)
~& peek-no-once/[care pith]
~
:: =/ res
:: (fallback-peek care pith)
:: ~? !=(res [~ ~])
:: res/res
:: ``(fallback-peek care pith)
(look care u.nce pith)
++ look-x
|= [=case:neo =pith:neo]
@ -467,6 +522,14 @@
?: ?=($@(~ [~ ~]) res)
res
`(~(get of:neo u.u.res) ~)
++ gas
=| axe=(axal:neo saga:neo)
|= res=(list (pair pith:neo saga:neo))
^+ axe
?~ res
axe
$(axe (~(put of:neo axe) p.i.res q.i.res), res t.res)
::
++ look
|= [=care:neo =once:neo =pith:neo]
@ -485,12 +548,6 @@
%y read-y
%z read-z
==
++ gas
|= res=(list (pair pith:neo saga:neo))
^+ axe
?~ res
axe
$(axe (~(put of:neo axe) p.i.res q.i.res), res t.res)
++ read-x (gas read-x-raw)
++ read-x-raw
^- (list (pair pith:neo saga:neo))

View File

@ -1,14 +1,14 @@
::
/- neo
|_ =slip:neo
|_ =ack:neo
::
++ grad %noun
++ grab :: convert from
|%
++ noun slip:neo
++ noun ack:neo
--
++ grow
|%
++ noun slip
++ noun ack
--
--

View File

@ -0,0 +1,20 @@
/@ message
/- feather-icons
/- messages
:- [%ship %$ %htmx]
|= =ship
|= =bowl:neo
^- manx
;div.p2
=label "Chat"
;+ script:messages
;+ style:messages
;div.ma.fe.g2.wf
=style "max-width: 650px;"
;div.fc.g2.wf
=id "children"
;+ (render-messages:messages bowl)
==
;+ (render-sender:messages [bowl /pub])
==
==

View File

@ -0,0 +1,21 @@
/@ message
/@ groupchat
/- feather-icons
/- messages
:- [%groupchat %$ %htmx]
|= =groupchat
|= =bowl:neo
^- manx
;div.p2
=label "Chat"
;+ script:messages
;+ style:messages
;div.ma.fe.g2.wf
=style "max-width: 650px;"
;div.fc.g2.wf
=id "children"
;+ (render-messages:messages bowl)
==
;+ (render-sender:messages [bowl /pub])
==
==

View File

@ -0,0 +1,216 @@
/@ groupchat
/@ ship
/- feather-icons
/- messages
:- [%sig %$ %htmx]
|= ~
|= =bowl:neo
|^
view
::
++ view
^- manx
;div.p2.fc.ac.view.g2.ma
;style: {style}
;+ make-chat
;+ all-chats
==
::
++ pith-tape
|= =pith
^- tape
(en-tape:pith:neo pith)
::
++ style
^~
%- trip
'''
.view {
max-width: 650px;
padding-bottom: 50vh;
padding-top: 30px;
}
input[type="text"]:hover {
cursor: text;
}
input:focus {
outline: none;
}
.w70{
width: 70%;
}
.bc1{
border: solid var(--b2) 1px;
}
'''
::
++ make-chat
=/ oninput
"""
this.setAttribute("value", this.value); this.nextElementSibling.nextElementSibling.setAttribute('hx-get', '/neo/hawk{(pith-tape here.bowl)}/dms/' + this.value); htmx.process(document.body);
"""
;form.fr.jc.g1.w70
=hx-post "/neo/hawk{(pith-tape here.bowl)}?stud=messenger-diff"
=head "new-dm"
=hx-swap "none"
=hx-on-htmx-after-request "$(this).find('.redirect').emit('messenger-created')"
;input.grow.bc1.p2.br1
=name "invites"
=type "text"
=required ""
=placeholder "Start chat (e.g. ~sampel-palnet)"
=oninput oninput
=autocomplete "off"
;
==
;input.hidden.grow.bc1.p2.br1
=type "text"
=name "name"
=placeholder "chat name"
=oninput (trip 'this.setAttribute("value", this.value);')
;
==
;div.redirect.hidden
=hx-target "closest .hawk"
=hx-swap "outerHTML"
=hx-trigger "messenger-created"
;
==
;button.loader.br1.hover.p2.b0.bc1
;span.loaded; >
;span.loading
;+ loading.feather-icons
==
==
==
::
++ all-chats
=/ kids
%+ skid ~(tap of:neo kids.bowl)
|= [=pith =idea:neo]
=(p.pail.idea %groupchat)
^- manx
;div.fc.as.g1.w70
;div.fc.ac.g1.wf
;* %+ turn q.kids
|= [=pith =idea:neo]
?~ pith
;span.hidden: no dms
?: (lte 3 (lent pith))
;h1.hidden: {<pith>}
~& > pith
=/ ship
?. ?=([%p @p] (rear pith)) *@p
+:;;([%p @p] (rear pith))
~& > ship
^- manx
;a.br1.hover.b0.fr.jb.wf.bc1
=href "/neo/hawk{(pith-tape here.bowl)}{(pith-tape pith)}"
=hx-swap "innerHTML"
;h3.s-1.p2: {<ship>}
==
==
;div.fc.ac.g1.wf
;* %+ turn p.kids
|= [=pith =idea:neo]
?~ pith ;span.hidden: no groupchats
(chat pith idea)
==
==
::
++ chat
|= [=pith =idea:neo]
=/ org=@p +:;;([%p @p] (snag 1 `(list iota)`pith))
=/ members=(list ship) ~(tap in members:!<(groupchat q.pail.idea))
=/ chat +:;;([%t @t] (rear pith))
~& > ['members' members]
^- manx
;div.wf.br1.fc.g1
;div.fr.g1
;a.br1.hover.fr.jb.g2.wf.bc1.b0
=href "/neo/hawk{(pith-tape here.bowl)}{(pith-tape pith)}"
=hx-swap "innerHTML"
;h3.s-1.p2: {(trip chat)}
;h3.s-1.p2: {<org>}
==
;button.br1.hover.bc1.b0.hidden
=onclick (weld (trip 'this.parentNode.parentNode.classList.toggle("border"); this.parentNode.parentNode.classList.toggle("p2"); this.previousSibling.classList.toggle("border"); this.classList.toggle("border"); ') (span-toggle "v" "^"))
;span: v
==
==
;div.fc.hidden
;div.fc.g2
;div.fr.je.g2
;+ ?. =(our.bowl org)
;span.hidden: ~
;button.hover.br1.b0.bc1
=onclick (span-toggle "+" "x")
;span: +
==
==
;+ (add-member chat)
==
;* %+ turn `(list ship)`members
|= =ship
?: =(our.bowl ship)
;div.fr.jb.g1
;span.wf.br1.p2: {<ship>}
==
;div.fr.jb.g1
;span.wf.br1.p1: {<ship>}
;button.hover.b0.br1.loader.bc1
=hx-post "/neo/hawk{(pith-tape here.bowl)}?stud=groupchat-diff"
=hx-target "find .loading"
=hx-swap "outerHTML"
=head "remove"
=ship (scow %p ship)
=hx-on--after-request "this.parentNode.remove();"
=onmouseover "this.previousSibling.classList.add('b1');"
=onmouseout "this.previousSibling.classList.remove('b1');"
;span.loaded: x
;span.loading
;+ loading.feather-icons
==
==
==
==
==
++ add-member
|= chat=cord
;form.fr.gb.g1.hidden
=hx-post "/neo/hawk{(pith-tape here.bowl)}?stud=messenger-diff"
=head "invite-to-groupchat"
=hx-target "find button .loading"
=hx-swap "outerHTML"
;input.hidden
=type "text"
=name "name"
=value (trip chat)
;
==
;input.grow.bc1.p2.br1
=name "ship"
=type "text"
=placeholder "~zod, ~bus"
=oninput "this.setAttribute('value', this.value);"
=autocomplete "off"
;
==
;button.hidden
=onclick "this.parentNode.appendChild(document.createElement('input')) "
;span: more
==
;button.loader.hover.br1.b0.bc1
;span.loaded: +
;span.loading
;+ loading.feather-icons
==
==
==
::
++ span-toggle
|= [from=tape to=tape]
^- tape
"this.parentNode.nextElementSibling.classList.toggle('hidden'); if (this.parentNode.nextElementSibling.classList.contains(\"hidden\"))\{this.innerHTML=\"<span>{from}</span>\"} else \{this.innerHTML=\"<span>{to}</span>\"}"
--

View File

@ -0,0 +1,17 @@
/@ node
/@ groupchat-diff
/- manx-utils
:- [%node %$ %groupchat-diff]
|= nod=node
^- groupchat-diff
=/ mu ~(. manx-utils nod)
=/ head (@tas (got:mu %head))
~& >>> head
%- groupchat-diff
?+ head
~| [%unknown-head head]
!!
%remove
=/ =ship `@p`(slav %p (got:mu %pith))
[head ship]
==

View File

@ -0,0 +1,10 @@
/@ node
/@ message
/- manx-utils
:- [%node %$ %message]
|= nod=node
^- message
=/ text (~(vol manx-utils nod) "text")
=/ ship (slav %p (~(vol manx-utils nod) "ship"))
=/ date (slav %da (~(vol manx-utils nod) "date"))
[ship date text]

View File

@ -0,0 +1,45 @@
/@ node
/@ messenger-diff
/- manx-utils
:- [%node %$ %messenger-diff]
|= nod=node
^- messenger-diff
=/ mu ~(. manx-utils nod)
=/ head (@tas (got:mu %head))
~& >>> head
%- messenger-diff
?+ head
~| [%unknown-head head]
!!
%new-dm
=/ partner `@p`(slav %p (vol:mu "invites"))
[head partner]
::
%new-groupchat
=/ invites=tape (need (val:mu "invites"))
=/ parsed-invites=(set @p)
%- silt
%+ scan (weld " " invites)
%- star
;~ pose
;~ pfix (jest ' ~')
fed:ag
==
;~ pfix (jest ', ~')
fed:ag
==
==
~& > parsed-invites
=/ value (val:mu "name")
=/ name=cord
:: TODO: if invites are longer than some amount of character
?~ value (crip invites)
(crip (need value))
~& > name
[head name parsed-invites]
::
%invite-to-groupchat
~& > (vol:mu "name")
=/ name (vol:mu "name")
[head name ~]
==

View File

@ -4,5 +4,4 @@
:- [%node %$ %txt]
|= nod=node
^- txt
(~(vol manx-utils nod) "text")
(~(vol manx-utils nod) "text")

View File

@ -0,0 +1,632 @@
# Chapter 3: Messenger
The last major aspect of shrubbery that we need to cover is the dependency system. This consists of the `+deps` arm and the `%rely` poke.
By the end of this tutorial youll understand how dependencies work and how to use them. You should also start to see how you can design functionality that involves multiple shrubs interacting across multiple ships.
Well take a look at Messenger. Messenger is one shrub, located at `/imp/messenger.hoon`, but it relies on five other shrubs to work.
- `/imp/messenger` is the high-level interface and “service provider” that enables the user to create groupchats and 1-to-1 DMs, and invite other ships to them.
- `/imp/message` is a `~` stub that allows us to create messages in the namespace
- `/imp/message-pub` is a shrub that takes a poke to store messages in the namespace as its kids.
- `/imp/message-sub` mirrors messages from a `/imp/message-pub` shrub into its own state.
- `/imp/groupchat` creates a publisher/subscriber pair, and can invite other ships to post/subscribe to the `/imp/message-pub` shrub.
- `/imp/dm` negotiates a two-way pub/sub relationship and mirrors state between both parties.
One motivation behind this design is to split off functionality into simple, reusable shrubs. `/imp/dm` neednt just be the DM shrub for `/messenger`, it could also be used off-the-shelf for 1-to-1 chats in shrubs like `/chess`, `/twitter`, or `/ebay`.
We'll skip covering the Messenger frontend. While there are new ideas here, it's very similar to the Tasks tutorial which is the better context for more frontend. This tutorial will focus on several shrubs interoperating to form one tool. Let's look at `/imp/message-sub`, then the whole system that Messenger uses to manage chats.
## /imp/message-sub
```hoon
/@ message
^- kook:neo
|%
++ state pro/%sig
++ poke (silt %rely ~)
++ kids
:+ ~ %y
%- ~(gas by *lads:neo)
:~ :- [|/%da |]
[pro/%message ~]
==
++ deps
%- ~(gas by *deps:neo)
:~ :- %pub
:+ req=| [pro/%sig (sy %sig ~)]
:+ ~ %y
%- ~(gas by *lads:neo)
:~ :- [|/%da |]
[pro/%message ~]
==
==
++ form
^- form:neo
|_ [=bowl:neo =aeon:neo state=pail:neo]
++ init
|= old=(unit pail:neo)
^- (quip card:neo pail:neo)
[~ sig/!>(~)]
::
++ poke
|= [=stud:neo vax=vase]
^- (quip card:neo pail:neo)
?> =(%rely stud)
:_ state
=+ !<([=term =leaf:neo] vax)
:: only get new kids
%+ murn
~(tap by ~(tar of:neo q:(~(got by deps.bowl) %pub)))
|= [=pith:neo =idea:neo]
^- (unit card:neo)
~& pith/pith
?. ?=([[%da @] ~] pith)
~
?: (~(has of:neo kids.bowl) pith)
~
?. =(%message p.pail.idea)
~
~& making/~
`[(welp here.bowl pith) %make %message `pail.idea ~]
--
--
```
## The +deps arm
The only part of this system that needs to define its dependencies is `/imp/message-sub`.
```hoon
::
:: define dependencies
++ deps
:: deps:neo is (map term fief:neo)
:: a map of a tag to a fief:neo, one for
:: each dependency we want to type here
%- ~(gas by *deps:neo)
:~ ::
:: %pub is an arbitrary tag which refers to
:: this dependency within this +deps arm
:* %pub
:: fief:neo is [required=? =quay]
req=|
:: quay:neo is (pair lash:neo (unit port:neo))
:: lash:neo defines the shrub's state and pokes
[[%pro %sig] (sy %sig ~)]
%- some
:: port:neo constrains the shrub's kids
:- %y
%- ~(gas by *lads:neo)
:~ :- [[%.n %da] |]
[[%pro %message] ~]
==
==
==
```
With regards to the lifecycle of the shrub, the `+deps` arm types the shrubs whose names and locations are passed in as an argument in the `%make` card when this shrub is created by `/imp/groupchat`.
```hoon
[%make %message-sub ~ (malt ~[[%pub (snoc host.poke %pub)]])]
```
In this card, `+malt` creates a `(map term pith:neo)` where the `term` is `%pub` and the path represented by the `pith` is `/foo/bar/pub`. At `/foo/bar/pub`, theres a shrub which is the canonical “publisher” or “host” of a chat, whether thats a group chat or a DM. Whenever theres a state change in the publisher shrub, the `/imp/message-sub` created in this card will be notified about it, but well cover that in more detail in the next section.
The `%pub` term will act as the key in the map of dependencies, corresponding to the`%pub` key that `/imp/message-sub` used as a tag in its `+deps` arm. This term allows us to differentiate between shrubs being given to us in the `%make` cards `conf:neo`.
The `required` flag specifies that when this shrub is made, it must be passed in a `conf:neo` that contains paths to existing shrubs. If we don't pass in a conf, or if no publisher exists at `/foo/bar/pub`, the `/imp/message-sub` were trying to `%make` here will fail to build. It can only exist with reference to the publisher shrub.
The last “new” idea here is `%sig`. This is a special case of `stud:neo` which tells `/app/neo` that the shrub has no state. We can do the same thing for pokes, as above with `(sy %sig ~)`.
If you look at `/imp/sig.hoon`, its just a `~` stub like the `%txt` implementation. `%sig` imps are not special and are not treated differently to any other stub, theyre just a stylistic convention to say that we dont care about the state of the shrub in question; it could be anything, we wont constrain it at all. Theres also a `/pro/sig.hoon` which lets us do the same thing for pokes.
## Handling state changes in our dependencies
Unlike a Gall agent, a shrub does not send out `%facts` to subscribers in the event of changes to its state, all of which has to be manually implemented by that agents developer. Instead, when its state changes `/app/neo` automatically sends a `%rely` poke to shrubs that have declared the shrub as a dependency. The developer of the listener shrub is the only one who has to write the logic to handle this.
In its `+deps` arm, the `/imp/message-sub` shrub declares the type and behaviour of the shrubs it will accept as dependencies. Shrubs that conform to that type, like `/imp/message-pub`, can be passed in through the `%make` card via the `conf` and `/imp/message-sub` will listen to those shrubs for state changes.
```hoon
++ deps
%- ~(gas by *deps:neo)
:~ :* %pub
req=|
[[%pro %sig] (sy %sig ~)]
%- some
:- %y
%- ~(gas by *lads:neo)
:~ :- [[%.n %da] %.n]
[[%pro %message] ~]
==
==
==
```
Like we saw in the `+kids` arm in the previous tutorial, the `+deps` arm specifies this `%y` constant. This is a `care:neo`.
In the `+kids` arm the `%y` care declares that this shrub is constraining its immediate children, and the `%z` care that its recursively constraining all of its descendants in the namespace.
In `+deps`, the `%y` care declares that were only listening for state changes in the dependency shrubs immediate children. If it were `%z`, wed be subscribing to state changes for all of the dependency shrubs descendants.
The other `care:neo` youll commonly see is `%x`, which refers to a single shrub. You wouldnt use this in `+kids`, but you might use it in `+deps`.
### Handling %rely pokes
Lets see how `/imp/message-sub` handles the `%rely` pokes it recevives from dependencies.
```hoon
++ poke
::
:: we receive a stud and vase from the publisher shrub when
:: there's a state change we've declared we care about
|= [=stud:neo vax=vase]
^- (quip card:neo pail:neo)
:: we only handle one poke, %rely
?> =(%rely stud)
:: we don't change our own state
:_ state
:: the vase we receive from the publisher is a (pair term leaf:neo)
=+ !<([=term =leaf:neo] vax)
:: only get new kids
%+ murn
~(tap by ~(tar of:neo q:(~(got by deps.bowl) %pub)))
|= [=pith:neo =idea:neo]
^- (unit card:neo)
~& pith/pith
?. ?=([[%da @] ~] pith)
~
?: (~(has of:neo kids.bowl) pith)
~
?. =(%message p.pail.idea)
~
~& making/~
`[(welp here.bowl pith) %make %message `pail.idea ~]
```
The above is mostly self-explanatory, but its worth expanding on `stem:neo` and `mode:neo`.
```hoon
+$ stem
$~ [*ever %x %stud *vase]
%+ pair ever
$% [%x =pail]
[%y =pail kids=(map pith [=ever =mode =pail])]
[%z =pail kids=(map pith [=ever =mode =pail])]
==
```
The `stem` is head-tagged with a `care:neo`. Even if we're listening to a shrub with a `%z` care, we can still distinguish between stems relating to the dependency, its kids, or other descendants.
Stems tagged with a `%y` or `%z` care come with an `ever`, which contains version numbers for the dependency and its descendants.
Stems tagged with `%x` come with a `pail` with the new state of the dependency. Stems tagged with `%y` and `%z` come with a `pail:neo` for the dependency, and a map containing all state changes for the descendants of that dependency. The `pith` keys in that map give us the location of each descendant that has changed, and the `[=ever =mode =pail]` values give us those descendants' version numbers, their `mode`, and their current state.
The `mode` of these kids is either `%add`, `%dif`, or `%del`. If its `%add`, the dependency is telling us its a new kid. If `%dif`, the kid isnt new but its state has changed. If `%del`, its telling us the kid was deleted and giving us its final state.
## Messenger: Overview
There are several shrubs working in tandem here to provide groupchat and DM functionality. Now that we know how `/imp/message-sub` works, let's look at the overall structure.
### /imp/message-pub
The only part of the Messenger backend left to consider is `/imp/message-pub`. This only imports `/pro/txt` and `/pro/message`.
#### kook:neo
`/imp/message-pub` only has one job, and that's to `%make` `%message`s as kids. Shrubs don't know anything about the shrubs above them that they weren't explicitly told in their `%make` card, so `/imp/message-pub` doesn't know or care whether it's being used to publish DMs or groupchat messages.
```hoon
++ state [%pro %sig]
++ poke (sy %message %txt ~)
++ kids
%- some
:- %y
%- ~(gas by *lads:neo)
:~ :- [[%.n %da] %.n]
[[%pro %message] (sy %sig ~)]
==
++ deps *deps:neo
++ form
...
```
#### +init
Like `/imp/messenger`, `/imp/message-pub` takes no state.
```hoon
++ init
|= old=(unit pail:neo)
^- (quip card:neo pail:neo)
[~ [%sig !>(~)]]
```
#### +poke
All that happens in the `+poke` arm is this shrub creating `%message`s below it in the namespace. The `%txt` is not actually necessary, but as a primitive it might be nice for `/imp/message-pub` to be able to construct messages without the developer having to specify the metadata.
```hoon
++ poke
|= [=stud:neo vax=vase]
^- (quip card:neo pail:neo)
?> =(our ship.src):bowl
:_ state
?+ stud !!
%message
=/ msg !<(message vax)
:~ :- (welp here.bowl ~[da/now.msg])
[%make %message `message/vax ~]
==
%txt
=/ contents=@t !<(txt vax)
:~ :- (welp here.bowl ~[da/now.bowl])
[%make %message `message/!>([ship.src.bowl now.bowl contents]) ~]
==
==
```
### /imp/dm
If you wanted to implement 1-on-1 DMs in your own shrub, you could just `%make` an `/imp/dm`. If that doesn't do waht you need, you could base your own DM functionality on this.
#### kook:neo
```hoon
++ state [%pro %ship] :: who I'm chatting with
++ poke (sy %dm-diff ~)
++ kids
%- some
:- %y
%- ~(gas by *lads:neo)
:~ :- [[%.n %theirs] %.n]
[[%pro %message-pub] (sy %sig ~)]
:- [[%.n %mine] %.n]
[[%pro %message-sub] (sy %sig ~)]
==
++ deps *deps:neo
++ form
...
```
A DM shrub only stores one ship, the `@p` of whoever you're chatting with. It only has two kids: `/path/to/this/dm/theirs` and `/path/to/this/dm/mine`. At these two paths it uses the `/imp/message-pub` and `/imp/message-sub` primitives to store the state of `/theirs` and `/mine`.
```hoon
$% [%initiate partner=ship provider=pith]
[%invited partner=ship dm=pith]
::
[%acked dm=pith]
::
[%post text=@t]
==
```
`/imp/dm` takes four pokes: `%initiate`, `%invited`, `%acked`, and `%post`.
- `%initiate`: we initiate a DM, specifying the other ship and the `/message-pub` primitive we want to use.
- `%invited`: someone invites us to a DM, giving their `@p` and the `pith` for us to send DMs to them.
- `%acked`: acknowledge creation of a DM.
- `%post`: send a DM.
#### curb:neo
Cells like `[%pro %ship]` and `[%pro %message-pub]` are examples of `$curb:neo`. This is a powerful type that's beyond the remit of these tutorials, but it's worth clarifying what these cells mean.
```hoon
+$ curb
$~ [%pro %$]
$% [%or p=(list curb)]
[%only p=stud]
[%rol p=stud q=curb]
[%not p=curb q=curb]
[%pro p=stud]
[%any ~]
==
```
In all of the shrubs we've looked at in these tutorials we could replace every `%pro` curb with the likes of `[%only %ship]` and `[%only %message-pub]` and lose none of the functionality we've looked at. The `[%only %ship]` curb just declares that the state is exclusively a `ship`. However, the `[%pro ship]` curb says that the state can be any type *which can be converted to a `ship` through an available `/con` file*. This has implications for interoperability and state transitions we have not yet fully explored.
#### form:neo
When `/imp/dm` is first created with a `%make` card, it needs to be created with some pre-defined state. The intial state it accepts has to be a `%dm-diff`. Taking a poke type as the initial state type is an unusual choice that was done as an experiment, but the result is essentially the same as a Gall agent sending a poke to itself `+on-init`.
```hoon
++ init
|= old=(unit pail:neo)
^- (quip card:neo pail:neo)
?~ old !!
?> =(%dm-diff p.u.old)
=/ poke !<(dm-diff q.u.old)
?+ -.poke !!
:: create me with a pith to a service provider
:: to start a new DM with them
%initiate
:_ ship/!>(partner.poke)
:~ :- (snoc here.bowl %pub)
[%make %message-pub ~ ~]
::
:- provider.poke
[%poke dm-diff/!>([%invited our.bowl here.bowl])]
==
::
:: create me with a pith to an inviter's dm
:: to accept their DM request
%invited
:_ ship/!>(partner.poke)
:~ :- (snoc here.bowl %pub)
[%make %message-pub ~ ~]
::
:- (snoc here.bowl %sub)
[%make %message-sub ~ (malt ~[[%pub (snoc dm.poke %pub)]])]
::
:- dm.poke
[%poke dm-diff/!>([%acked here.bowl])]
==
==
```
The `+poke` arm handles `%acked` and `%post` pokes.
DM state is symmetrical: both ships are publishing to each other and subscribed to each other. When we receive an `%acked` poke, we create an `/imp/message-sub` to subscribe to DMs from the "publisher", which is whoever we're going to talk to. When we receive a `%post` poke, we add that new post to our `/pub` shrub and the other ship's `/imp/message-sub` will mirror it in its own state.
```hoon
++ poke
|= [=stud:neo vax=vase]
^- (quip card:neo pail:neo)
?> =(%dm-diff stud)
=/ poke !<(dm-diff vax)
?+ -.poke !!
:: invitee pokes me with a pith to their DM
:: to finalize the negotiation
%acked
=/ partner !<(ship q.state)
?> =(partner ship.src.bowl)
:_ state
:~ :- (snoc here.bowl %sub)
[%make %message-sub ~ (malt ~[[%pub (snoc dm.poke %pub)]])]
==
::
%post
?> =(our ship.src):bowl
:_ state
:~ :- (snoc here.bowl %pub)
[%poke txt/!>(text.poke)]
==
==
```
### /imp/groupchat
`/imp/groupchat` uses exactly the same primitives as `/imp/dm` for publishing and subscribing to messages. The only difference is that it's negotiating state between several ships using a one-to-many flow, rather than mirroring state between two ships.
#### kook:neo
Like `/imp/dm`, `/imp/groupchat` just defines two kids at `/.../pub` and `/.../sub` to do most of the heavy lifting for it.
```hoon
++ state [%pro %groupchat]
++ poke (sy %groupchat-diff ~)
++ kids
%- some
:- %y
%- ~(gas by *lads:neo)
:~ :- [[%.n %pub] %.n]
[[%pro %message-pub] (sy %sig ~)]
:- [[%.n %sub] %.n]
[[%pro %message-sub] (sy %sig ~)]
==
++ deps *deps:neo
++ form
...
```
The state is a `groupchat`, which is just a set of members, pending members, and a `pith` for the location of this chat in the namespace.
```hoon
$: members=(set ship)
pending=(set ship)
host=pith
==
```
`/imp/groupchat` takes six pokes:
- `%invite`: Invite someone to the groupchat.
- `%remove`: Kick someone from the groupchat. (Currently only removes their ability to post.)
- `%invited`: Receive an invite to a groupchat.
- `%acked`: Acknowledge acceptance of a groupchat invite.
- `%post-to-host`: Send a post to the groupchat's host.
- `%host-to-pub`: Send a post from the groupchat's host to their publisher shrub.
```hoon
$% [%invite =ship provider=pith]
[%remove =ship]
[%invited host=pith]
[%acked ~]
[%post-to-host text=@t]
[%host-to-pub text=@t]
==
```
#### +init
When it's initialized, `/imp/groupchat` has either been created by a host on their own ship, or it's been created in response to an invitation from the host.
If it has no state, it creates the new chat with `%message-pub` and `%message-sub` providers. If it does have some initial state, it assumes it's being created by a foreign host ship and takes that state to be the chat history. It only needs to create a `%message-sub` to receive new messages from the publisher.
```hoon
++ init
|= old=(unit pail:neo)
^- (quip card:neo pail:neo)
:: default case: make new groupchat with self as only member,
:: and subscribe to that publisher
?~ old
:_ :- %groupchat
!>([(sy our.bowl ~) ~ here.bowl])
:~ :- (snoc here.bowl %pub)
[%make %message-pub ~ ~]
::
:- (snoc here.bowl %sub)
[%make %message-sub ~ (malt ~[[%pub (snoc here.bowl %pub)]])]
==
:: otherwise, I've been created as an invitee to
:: someone else's groupchat
?> =(%groupchat-diff p.u.old)
=/ poke !<(groupchat-diff q.u.old)
?+ -.poke !!
%invited
:_ groupchat/!>([~ ~ host.poke])
:~ :- (snoc here.bowl %sub)
[%make %message-sub ~ (malt ~[[%pub (snoc host.poke %pub)]])]
::
:- host.poke
[%poke groupchat-diff/!>([%acked ~])]
==
==
```
#### +poke
In the `+poke` arm we handle `%invite`, `%remove`, `%acked`, and `%post-to-host`, which is mostly a wrapper around `%host-to-pub`.
Even though it doesn't handle messages — just access control — `/imp/groupchat` has some state to manage. Moreso than anything we've seen before, the below should look a lot like a Gall agent.
Now is a good time to address when developers should store data inside a shrub's state vs. storing it as a kid. There's no right answer, but a good rule of thumb would be "would any other shrub care about this piece of data?" If so, it's more readily available at its own node in the namespace; if not, there's no downside to storing it in the shrub's internal state. You don't want to have to handle kids' state change just to do basic bookkeeping.
```hoon
++ poke
|= [=stud:neo vax=vase]
^- (quip card:neo pail:neo)
?> =(%groupchat-diff stud)
=/ sta !<(groupchat q.state)
=/ poke !<(groupchat-diff vax)
?+ -.poke !!
:: if I'm the host, poke someone's provider to invite them to chat
%invite
?> =(our ship.src):bowl
?< (~(has in members.sta) ship.poke)
:: ?> =(our.bowl ->.host.sta) :: XX need @p, have @t ?
:_ :- %groupchat
!>(sta(pending (~(put in pending.sta) ship.poke)))
:~ :- provider.poke
[%poke groupchat-diff/!>([%invited here.bowl])]
==
::
:: remove someone from chat. this only removes their ability to post;
:: they'll still be receiving new messages!
%remove
?> =(our ship.src):bowl
?> (~(has in members.sta) ship.poke)
:- ~
:- %groupchat
!> %= sta
pending (~(del in pending.sta) ship.src.bowl)
members (~(del in members.sta) ship.src.bowl)
==
::
:: when invitee acks, remove them from pending
:: and add them to pub's permissions
%acked
?> (~(has in pending.sta) ship.src.bowl)
:- ~
:- %groupchat
!> %= sta
pending (~(del in pending.sta) ship.src.bowl)
members (~(put in members.sta) ship.src.bowl)
==
::
%post-to-host
:_ state
:~ :- host.sta
[%poke groupchat-diff/!>([%host-to-pub text.poke])]
==
::
%host-to-pub
?> (~(has in members.sta) ship.src.bowl)
:_ state
:~ :- (snoc here.bowl %pub)
[%poke message/!>([ship.src.bowl now.bowl text.poke])]
==
==
```
### /imp/messenger
`/imp/messenger` is the top-level interface through which users can create, post in, and manage groupchats and DMs. This is the shrub that corresponds to the main "Messenger" UI within Sky.
This is a nice way to handle groupchats and DMs all in one place, but it's also a requirement of the way this system is built. There's a chicken-and-egg problem with DMs where ~tex can't invite ~mex to a DM chat unless ~mex already has a DM chat (`/imp/dm`) with ~tex in which to receieve that poke, so DMs rely on `/imp/messenger` to negotiate that with the `%new-dm` poke.
#### kook:neo
Messenger has no state. This shrub is just an interface for creating groupchats and DMs, which are its kids. If those kids are `/imp/dm`s they take `%dm-diff`s, and if they're `/imp/groupchat`s they take `%groupchat-diff`s.
```hoon
++ state [%pro %sig]
++ poke (sy %dm-diff %groupchat-diff %messenger-diff ~)
++ kids
%- some
:- %y
%- ~(gas by *lads:neo)
:~ :- [[%.y %dms] [%.n %p] %.n]
[[%pro %dm] (sy %dm-diff ~)]
:- [[%.y %groupchats] [%.n %p] [%.n %t] %.n]
[[%pro %groupchat] (sy %groupchat-diff ~)]
==
++ deps *deps:neo
++ form
...
```
Messenger only supports three user actions: creating a new DM, creating a new groupchat, and inviting someone to a groupchat. Everything else is handled by this shrub's kids.
```hoon
$% [%new-dm partner=ship]
[%new-groupchat name=@t invites=(set ship)]
[%invite-to-groupchat name=@t invites=(set ship)]
==
```
#### +init
Messenger does nothing much on `+init`. Here's `%sig` again, this time head-tagging an empty `pail:neo`. This shrub stores no state, so we can't inject some when we initialize it.
```hoon
++ init
|= old=(unit pail:neo)
^- (quip card:neo pail:neo)
[~ [%sig !>(~)]]
```
#### +poke
`/imp/messenger` takes `%messenger-diff`s, but it also takes `%dm-diff`s and `%groupchat-diff`s and, if they're invites to those chats, `%make`s the chat at the right location in the namespace.
```hoon
++ poke
|= [=stud:neo vax=vase]
^- (quip card:neo pail:neo)
~& >> stud
?+ stud !!
%dm-diff
~& >>> 'got dm diff'
=/ poke !<(dm-diff vax)
?> =(%invited -.poke)
:_ state
:~ :- (welp here.bowl ~[%dms p/ship.src.bowl])
[%make %dm `dm-diff/vax ~]
==
::
%groupchat-diff
=/ poke !<(groupchat-diff vax)
?+ -.poke !!
%invited
:_ state
:~ :- (welp here.bowl ~[%groupchats p/ship.src.bowl (rear host.poke)])
[%make %groupchat `groupchat-diff/vax ~]
==
==
::
%messenger-diff
?> =(our ship.src):bowl
=/ poke !<(messenger-diff vax)
?- -.poke
%new-dm
?: (~(has of:neo kids.bowl) ~[%dms p/partner.poke])
[~ state]
=/ provider ~[p/partner.poke %home %messenger]
:_ state
:~ :- (welp here.bowl ~[%dms p/partner.poke])
[%make %dm `dm-diff/!>([%initiate partner.poke provider]) ~]
==
::
%new-groupchat
=/ location
(welp here.bowl ~[%groupchats p/our.bowl t/name.poke])
:_ state
:- [location [%make %groupchat ~ ~]]
(send-invites invites.poke location)
::
%invite-to-groupchat
=/ location
(welp here.bowl ~[%groupchats p/our.bowl t/name.poke])
:_ state
(send-invites invites.poke location)
==
==
```

View File

@ -58,8 +58,8 @@
++ init
|= old=(unit pail:neo)
^- (quip card:neo pail:neo)
=/ width 10
=/ height 10
=/ width 3
=/ height 3
:_ accel/!>([width height])
(make-cells bowl 1 width 1 height %in)
::

View File

@ -16,7 +16,7 @@
^- form:neo
|_ [=bowl:neo =aeon:neo state=pail:neo]
++ init
|= old=(unit pail:neo)
|= old=(unit pail:neo)
^- (quip card:neo pail:neo)
?~ old !!
?> =(%dm-diff p.u.old)
@ -29,21 +29,21 @@
:~ :- (snoc here.bowl %pub)
[%make %message-pub ~ ~]
::
:- provider.poke
[%poke dm-diff/!>([%invited here.bowl])]
:- provider.poke
[%poke dm-diff/!>([%invited our.bowl here.bowl])]
==
::
:: create me with a pith to an inviter's dm
:: to accept their DM request
%invited
:_ ship/!>(partner.poke)
:~ :- (snoc here.bowl %pub)
:~ :- (snoc here.bowl %pub)
[%make %message-pub ~ ~]
::
:- (snoc here.bowl %sub)
:- (snoc here.bowl %sub)
[%make %message-sub ~ (malt ~[[%pub (snoc dm.poke %pub)]])]
::
:- dm.poke
:- dm.poke
[%poke dm-diff/!>([%acked here.bowl])]
==
==
@ -59,7 +59,7 @@
=/ partner !<(ship q.state)
?> =(partner ship.src.bowl)
:_ state
:~ :- (snoc here.bowl %sub)
:~ :- (snoc here.bowl %sub)
[%make %message-sub ~ (malt ~[[%pub (snoc dm.poke %pub)]])]
==
::
@ -71,4 +71,4 @@
==
==
--
--
--

View File

@ -21,14 +21,13 @@
^- (quip card:neo pail:neo)
:: default case: make new groupchat with self as only member,
:: and subscribe to that publisher
:: XX - maybe move ordering is unpredictable here
?~ old
:_ :- %groupchat
!>([(sy our.bowl ~) ~ (snoc here.bowl %pub)])
:~ :- (snoc here.bowl %pub)
!>([(sy our.bowl ~) ~ here.bowl])
:~ :- (snoc here.bowl %pub)
[%make %message-pub ~ ~]
::
:- (snoc here.bowl %sub)
:- (snoc here.bowl %sub)
[%make %message-sub ~ (malt ~[[%pub (snoc here.bowl %pub)]])]
==
:: otherwise, I've been created as an invitee to
@ -38,10 +37,10 @@
?+ -.poke !!
%invited
:_ groupchat/!>([~ ~ host.poke])
:~ :- (snoc here.bowl %sub)
:~ :- (snoc here.bowl %sub)
[%make %message-sub ~ (malt ~[[%pub (snoc host.poke %pub)]])]
::
:- host.poke
:- host.poke
[%poke groupchat-diff/!>([%acked ~])]
==
==
@ -100,4 +99,4 @@
==
==
--
--
--

View File

@ -419,4 +419,4 @@
==
==
--
--
--

View File

@ -29,6 +29,7 @@
[#/[p/our.bowl]/home/circle %make %circle ~ ~]
[#/[p/our.bowl]/home/files %make %folder ~ ~]
[#/[p/our.bowl]/home/planner %make %planner ~ ~]
[#/[p/our.bowl]/home/messenger %make %messenger ~ ~]
[#/[p/our.bowl]/home/docs %make %folder `folder/!>([%diary %feather ~]) ~]
[#/[p/our.bowl]/home/docs/feather %make %sail `sail/!>([feather-intro 'prose p-page mw-page ma' ~]) ~]
[#/[p/our.bowl]/home/docs/diary %make %sail `sail/!>([diary 'prose p-page mw-page ma' ~]) ~]

View File

@ -25,7 +25,7 @@
^- (quip card:neo pail:neo)
?> =(our ship.src):bowl
:_ state
?+ stud !!
?+ stud !!
%message
=/ msg !<(message vax)
:~ :- (welp here.bowl ~[da/now.msg])
@ -38,4 +38,4 @@
==
==
--
--
--

View File

@ -2,7 +2,7 @@
^- kook:neo
|%
++ state pro/%sig
++ poke ~
++ poke (silt %rely ~)
++ kids
:+ ~ %y
%- ~(gas by *lads:neo)
@ -12,7 +12,7 @@
++ deps
%- ~(gas by *deps:neo)
:~ :- %pub
:+ req=& [pro/%sig (sy %sig ~)]
:+ req=| [pro/%sig (sy %sig ~)]
:+ ~ %y
%- ~(gas by *lads:neo)
:~ :- [|/%da |]
@ -32,20 +32,20 @@
^- (quip card:neo pail:neo)
?> =(%rely stud)
:_ state
=+ !<([=term =stem:neo] vax)
?> ?=(%y -.q.stem)
=+ !<([=term =leaf:neo] vax)
:: only get new kids
=/ kids
%+ skim
~(val by kids.q.stem)
|= [=ever:neo =mode:neo =pail:neo]
=(%add mode)
?: =(~ kids)
%+ murn
~(tap by ~(tar of:neo q:(~(got by deps.bowl) %pub)))
|= [=pith:neo =idea:neo]
^- (unit card:neo)
~& pith/pith
?. ?=([[%da @] ~] pith)
~
=/ pai=pail:neo pail:(snag 0 kids)
=/ mes !<(message q.pai)
:~ :- (welp here.bowl ~[da/now.mes])
[%make [%message `pai ~]]
==
?: (~(has of:neo kids.bowl) pith)
~
?. =(%message p.pail.idea)
~
~& making/~
`[(welp here.bowl pith) %make %message `pail.idea ~]
--
--
--

View File

@ -2,15 +2,28 @@
/@ groupchat-diff
/@ messenger-diff
^- kook:neo
=>
|%
++ send-invites
|= [invites=(set ship) location=pith]
%+ turn
~(tap in invites)
|= =ship
:- location
=/ provider ~[p/ship %home %messenger]
~& provider
[%poke groupchat-diff/!>([%invite ship provider])]
--
::
|%
++ state pro/%sig
++ poke (sy %dm-diff %groupchat-diff ~)
++ poke (sy %dm-diff %groupchat-diff %messenger-diff ~)
++ kids
:+ ~ %y
%- ~(gas by *lads:neo)
:~ :- [|/%t |]
:~ :- [&/%dms |/%p |]
[pro/%dm (sy %dm-diff ~)]
:- [|/%t |]
:- [&/%groupchats |/%p |/%t |]
[pro/%groupchat (sy %groupchat-diff ~)]
==
++ deps *deps:neo
@ -25,12 +38,14 @@
++ poke
|= [=stud:neo vax=vase]
^- (quip card:neo pail:neo)
~& >> stud
?+ stud !!
%dm-diff
~& >>> 'got dm diff'
=/ poke !<(dm-diff vax)
?> =(%invited -.poke)
:_ state
:~ :- (snoc here.bowl p/ship.src.bowl)
:~ :- (welp here.bowl ~[%dms p/ship.src.bowl])
[%make %dm `dm-diff/vax ~]
==
::
@ -39,7 +54,7 @@
?+ -.poke !!
%invited
:_ state
:~ :- (snoc here.bowl (rear host.poke))
:~ :- (welp here.bowl ~[%groupchats p/ship.src.bowl (rear host.poke)])
[%make %groupchat `groupchat-diff/vax ~]
==
==
@ -49,25 +64,27 @@
=/ poke !<(messenger-diff vax)
?- -.poke
%new-dm
?: (~(has of:neo kids.bowl) ~[%dms p/partner.poke])
[~ state]
=/ provider ~[p/partner.poke %home %messenger]
:_ state
:~ :- (snoc here.bowl p/partner.poke)
:~ :- (welp here.bowl ~[%dms p/partner.poke])
[%make %dm `dm-diff/!>([%initiate partner.poke provider]) ~]
==
::
%new-groupchat
=/ location
(welp here.bowl ~[%groupchats p/our.bowl t/name.poke])
:_ state
:~ :- (snoc here.bowl t/name.poke)
[%make %groupchat ~ ~]
==
:- [location [%make %groupchat ~ ~]]
(send-invites invites.poke location)
::
%invite-to-groupchat
=/ provider ~[p/ship.poke %home %messenger]
=/ location
(welp here.bowl ~[%groupchats p/our.bowl t/name.poke])
:_ state
:~ :- (snoc here.bowl t/name.poke)
[%poke groupchat-diff/!>([%invite ship.poke provider])]
==
(send-invites invites.poke location)
==
==
--
--
--

View File

@ -0,0 +1,157 @@
/@ message
/- feather-icons
|%
:: This arm expects messages to be stored at
:: ~[%pub [%da ~2024.1.1]] or ~[%sub [%da ~2024.1.1]]
:: relative to here.bowl.
++ pith-tape
|= =pith
^- tape
(en-tape:pith:neo pith)
::
++ style
;style
;+ ;/ %- trip
'''
.fe {
height: 100vh;
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: flex-end
}
.msg{
max-width: 70%;
min-width: 40%;
}
'''
==
::::
++ script
;script
;+ ;/ %- trip
'''
document.getElementById("render-sender").scrollIntoView({
block: "center",
inline: "start",
behavior: "instant"});
'''
==
::
++ refresher
|= =bowl:neo
;div.absolute
=style "top: 1em; left: 1em;"
;div.loader.refresher
=hx-get "{(en-tape:pith:neo :(weld /neo/hawk here.bowl))}?no-save"
=hx-trigger "every 7s, refresh"
=hx-target "closest .top"
=hx-select ".top"
=hx-swap "morph"
;span.loaded;
;span.loading
;+ loading.feather-icons
==
==
==
::
++ render-messages
|= =bowl:neo
^- manx
;div.fc.g2.p1.top
=label "Messages"
=id "messages"
;+ (refresher bowl)
;*
%+ turn
%+ sort
%+ murn
~(tap of:neo kids.bowl)
|= [=pith =idea:neo]
?~ pith ~
?. =(%message p.pail.idea) ~
`[pith idea]
|= [a=[=pith *] b=[=pith *]]
=/ adate +:(snag 1 pith.a)
=/ bdate +:(snag 1 pith.b)
(lth adate bdate)
|= [pax=pith =idea:neo]
(render-message pax idea bowl)
==
::
++ render-message
|= [pax=pith =idea:neo =bowl:neo]
=/ msg !<(message q.pail.idea)
~& > msg
^- manx
?: =(our.bowl from.msg)
(render-our-message msg)
::;div.fc.g2.border.p3.br1.grow.msg
;div.fc.g2.grow.msg
=style "align-self: flex-start;"
;div.fr.ac.jb
;p.s-2.f3: {(scow %p from.msg)}
;p.s-2.f3: {(scow %da now.msg)}
==
;div.border.br1.p3
;p: {(trip contents.msg)}
==
==
::
++ render-our-message
|= msg=message
;div.fc.g2.grow.msg
=style "align-self: flex-end;"
;div.fr.ac.jb
;p.s-2.f3: {(scow %p from.msg)}
;p.s-2.f3: {(scow %da now.msg)}
==
;div.fr.je.border.br1.b1.p3
;p: {(trip contents.msg)}
==
==
::
++ render-sender
|= [=bowl:neo location=pith]
:: =-
:: =/ that -
:: ?~ location
:: that(a.g [[%hx-post "/neo/hawk{(pith-tape here.bowl)}?stud=groupchat-diff"] [%head "post-to-host"] a.g.that])
:: that(a.g [[%hx-post "/neo/hawk{(pith-tape (welp here.bowl location))}?stud=message"] [%head "msg"] a.g.that])
^- manx
;form.fc.g2.wf
=hx-post "/neo/hawk{(pith-tape (welp here.bowl location))}?stud=message"
=hx-swap "beforeend swap:1s"
=hx-on-submit "this.reset()"
=hx-target "previous #messages"
=head "msg"
=id "render-sender"
;textarea.p2.border.br1
=name "text"
=placeholder ". . ."
=oninput "this.setAttribute('value', this.value)"
=rows "4"
=required ""
=autocomplete "off"
=maxlength "2048"
;
==
;input.hidden
=name "ship"
=value (scow %p our.bowl)
;
==
;input.hidden
=name "date"
=value (scow %da now.bowl)
;
==
;button.p2.b1.br1.bd1.wfc.hover.loader
;span.loaded.s2: send
;span.loading
;+ loading.feather-icons
==
==
==
::
--

View File

@ -1,4 +1,4 @@
$% [%new-dm partner=ship]
[%new-groupchat name=@t]
[%invite-to-groupchat name=@t =ship]
[%new-groupchat name=@t invites=(set ship)]
[%invite-to-groupchat name=@t invites=(set ship)]
==

View File

@ -2222,6 +2222,29 @@
#/std
[p/ship.disk term.disk ~]
--
++ lte-pith
|= [a=pith b=pith]
^- ?
?~ a &
?~ b |
?. =(i.a i.b)
%+ lte-dime
?^(i.a i.a [%tas i.a])
?^(i.b i.b [%tas i.b])
$(a t.a, b t.b)
::
++ lte-dime
|= [a=dime b=dime]
^- ?
?. =(p.a p.b)
(aor -.a -.b)
?+ p.a (lte q.a q.b)
%rd (lte:rd q.a q.b)
%rh (lte:rh q.a q.b)
%rq (lte:rq q.a q.b)
%rs (lte:rs q.a q.b)
%s !=(--1 (cmp:si q.a q.b))
?(%t %ta %tas) (aor q.a q.b)
==
::
--

26
tests/lib/neo-two.hoon Normal file
View File

@ -0,0 +1,26 @@
/- neo
/+ aux=neo-two
/+ *test
=| [=farm:neo =loam:dirt:neo]
=* sta -
|%
++ take-dirt-card
|= =card:dirt:neo
^+ sta
=^ gifts=(list gift:dirt:neo) loam
(~(call plow:aux loam) card)
=. farm (~(take till:aux [loam farm]) gifts)
sta
++ oat *oath:neo
:: ++ reset
++ test-grow
=. sta (take-dirt-card #/foo %grow atom/!>(1) ~ *oath:neo)
=. sta (take-dirt-card #/foo/bar %grow atom/!>(1) ~ *oath:neo)
=. sta (take-dirt-card #/foo/bar/baz %grow atom/!>(1) ~ *oath:neo)
=. sta (take-dirt-card #/foo/bar %grow atom/!>(1) ~ *oath:neo)
=. sta (take-dirt-card #/foo %grow atom/!>(2) ~ *oath:neo)
=/ want (~(piek till:aux [loam farm]) %z #/foo)
=/ have (~(peek till:aux [loam farm]) %z #/foo)
~& sta
(expect-eq !>(want) !>(have))
--