%hawk
/lib/hawk-ui/hoon
/- *hawk, hu=hawk-upgrade /+ *hawk, multipart, html-utils, manx-utils |% ++ redir :: |= [status=cord pax=path =card] =/ pate (welp (spud pax) ?~(pax "" "/")) =/ pam=tape %- tail:en-purl:html %+ murn ^- (list [cord path]) :~ ['view' /sys/view] ['scroll' /sys/scroll] == |= [name=cord rib=path] ?~ val=(~(reb iu card) rib) ~ :+ ~ name q.u.val :: =/ loc :(welp "/~~" pate pam) :~ ['X-Status' status] ['Location' (crip loc)] ['HX-Push-Url' 'false'] ::['HX-Push-Url' (crip loc)] == ++ rewrite :: |= [pax=path =card] =/ pam=tape %- tail:en-purl:html %+ murn ^- (list [cord path]) :~ ['view' /view] ['scroll' /scroll] == |= [name=cord rib=path] ?~ val=(~(reb iu card) rib) ~ `[name q.u.val] :: =/ pate (welp (spud pax) ?~(pax "" "/")) =/ loc :(welp "/~~" pate pam) :~ ['HX-Push-Url' (crip loc)] == :: ++ http-cards :: |= [id=@ta headers=(list [@t @t]) body=@t] =/ rath [/http-response/[id]]~ =/ page (as-octs:mimes:html `@t`body) =/ status (slav %ud (~(gut by (malt headers)) 'X-Status' '200')) :~ [%give %fact rath %http-response-header !>([status headers])] [%give %fact rath %http-response-data !>(`page)] [%give %kick rath ~] == :: ++ part-error :: |= =tang ;div.error.fc.p4.f1.mono.pre.s-2.b-1.scroll-y =style "min-height: 150px; max-height: 250px;" ;* %+ turn tang |= =tank ;div ;* %+ turn (wash [0 80] tank) |= =tape ;div.s-2: {tape} == == ++ empty-manx :: |= m=manx ?: =(m *manx) %.y ?: =(m ;/("")) %.y %.n :: ++ pams-to-card :: |= pams=(map @t @t) ^- card %- ~(gas of *(axal dime)) %+ turn ~(tap by pams) |= [key=@t val=@t] ^- [path dime] :- q:(dime-location key) :: XX dont use dime-location anymore =; =coin ?+ -.coin [%tas '%many and %blob unsupported'] %$ p.coin == %+ fall (slay val) [%$ %tas val] :: ++ parse-dime :: |= =cord ^- (unit dime) %- mole |. (dime +:(slap !>(..zuse) (ream cord))) :: ++ server |_ [=bowl:gall state=state-6:hu] +* that . ++ tree tree.state ++ can-peek :: |= =path ^- ? ?: =(src our):bowl %.y =/ f ~(. fu tree) .= `f/%.y (reb:(m:f path) /peek/public) :: ++ can-poke :: |= =path ^- ? ?: =(src our):bowl %.y =/ f ~(. fu tree) .= `f/%.y (reb:(m:f path) /poke/public) :: ++ is-ancestor :: |= [elder=(list @t) younger=(list @t)] ^- flag |- ?~ elder %.y ?~ younger %.n ?. =(i.elder i.younger) %.n $(elder t.elder, younger t.younger) :: ++ crashless-stab :: |= input=cord ^- path ?^ try=(mole |.((stab input))) u.try [(scot %t input) ~] :: ++ lax-stab :: :: like stab, but ignores trailing slash |= =cord ^- path =/ stabbed =< - %+ rash cord ;~ plug ;~(pfix fas (more fas smeg:de-purl:html)) yque:de-purl:html == ?. ?& (gth (lent stabbed) 0) =((rear stabbed) ~) == stabbed (snip stabbed) :: ++ flatten-path :: :: turn a path into tape with heps instead of fas' |= p=path ^- tape ?~ p "root" %+ join '-' `(list cord)`p :: ++ process-classes :: ::XX this is too slow |= [p=path m=manx] ^- manx %- fall :_ ;div: nothing %- ~(prune manx-utils m) |= n=manx ^- ? =/ cp (can-poke p) =/ sc %- sy %- classes:parsers:html-utils (gut:~(at mx:html-utils n) %class "") ?| ?& !=(our.bowl src.bowl) (~(has in sc) "need-admin") == ?& =(our.bowl src.bowl) (~(has in sc) "unless-admin") == ?& !cp (~(has in sc) "need-poke") == ?& cp (~(has in sc) "unless-poke") == :: == :: ++ login-page :: |= =path =/ pate (welp (spud path) ?~(path "" "/")) =/ current ?: (is-valid-rank:ames src.bowl `@ubC`16) "anonymous" (scow %p src.bowl) :^ ~ ~ ~ %+ doc "login" ;div.wf.hf.grow.fc.ac.jc.scroll-y.g3.mono =hx-boost "false" ;div.fr.g2.o8 ;span.f1: {current} ;span.f-1: access denied == ;form.fr.g1.br2.bd1.af.b2 =method "post" =action "/~/login" ;input.hidden(name "eauth"); ;input.hidden(name "redirect", value "/~~{pate}"); ;input.p-1.s-1.b1.bd0.br2 =style "width: 175px;" =placeholder "~zod" =autocomplete "off" =spellcheck "false" =pattern (trip '~([a-z]{3}|[a-z]{6}(-[a-z]{6})*)') =title "urbit name like ~sampel-palnet" =name "name" ; == ;button.p-1.f3.s-1.bd0.br2: login == ;div.o4.mt1.frw.g1 ;span: or ;a.underline(href "/~/login?redirect=/~~{pate}"): login as admin == == :: ++ login :: |= =path =/ redir (crip "/~/login?redirect={(spud path)}") [~ ~ ~[['X-Status' '403']] 'not allowed'] :: ++ eauth-login :: |= =path =/ redir (crip "/~/login?redirect={(spud path)}&eauth=") [~ ~ ~[['X-Status' '307'] ['Location' redir]] ''] :: ++ system-part |= pax=path ^- manx =< data %+ fall (~(get of tree) (welp /~/config/parts pax)) %+ fall (~(get of tree) (welp /~/system/parts pax)) %* . *page data ;div.p8.tc: no system file == :: :: ++ part-version :: =/ v=(trel @ @ @) .^((trel @ @ @) %gx /[(scot %p our.bowl)]/docket/[(scot %da now.bowl)]/charges/hawk/version/noun) ;a.frw.g1.ac.jc.o5.p1.mono.s-1(target "_blank") =href "https://hawk.computer/~~/" ;span.f4: version {<p.v>}.{<q.v>}.{<r.v>} of ;span.f2: hawk == :: ++ res :: |= req=(pair @ta inbound-request:eyre) ^- [cards=(list card:agent:gall) tree=file] :: =/ purl :: parsed url with query params :: ^- [pax=path pam=(map @t @t)] =+ %+ rash url.request.q.req ;~ plug ;~(pfix fas (more fas smeg:de-purl:html)) yque:de-purl:html == [-.- (molt +.-)] :: :: =. pax.purl ?: ?& (gth (lent pax.purl) 0) =(~ (rear pax.purl)) ?| =(/['~~'] (scag 1 pax.purl)) =(/apps/hawk (scag 2 pax.purl)) == :: == (snip pax.purl) pax.purl :: =/ headers header-list.request.q.req =/ multibody :: :: YIKES! this is brittle %+ turn ^- (list [@t part:multipart]) %+ fall (de-request:multipart headers body.request.q.req) ~ |= [name=@t =part:multipart] ^- [@t @t] :- name body.part :: =/ body :: parsed body :: :: www-form-encoded is default :: text/html is also supported :: ^- (map @t @t) =/ ct (~(get by (malt headers)) 'content-type') =/ bod q:(fall body.request.q.req [p=0 q='']) ?: =(ct `'text/html') (malt ['/html' bod]~) %- ~(uni by pam.purl) %- malt %+ welp multibody %+ fall %+ rush bod yquy:de-purl:html ~ :: :: =/ meth :: aesthetic-ized request method :: ?+ method.request.q.req 'get' %'GET' 'get' %'POST' 'pos' %'DELETE' 'del' %'PATCH' 'pat' %'PUT' 'put' == :: :: =/ caz (pams-to-card body) :: =/ rus %- mule |. (htmx [[meth pax.purl] caz]) :: =/ ros ^- (unit [effects=(list card:agent:gall) headers=(list [@t @t]) fragment=$@(@t manx)]) ?- -.rus %.y p.rus %.n :^ ~ ~ ~[['HX-Retarget' '#hawk-top'] ['HX-Reswap' 'innerHTML'] ['HX-Reselect' '#error']] ;div#error.wf.hf.grow.fc.ac.jc.g4 ;h1.f-3: application error ;p: report this bug to ~migrev-dolseg immediately! ;+ (part-error p.rus) ;a.b1.p2.br1.bd1(href ""): reload == == :: :_ tree ^- (list card:agent:gall) ?~ ros [%pass /http-redir %agent [our.bowl %docket] %poke %handle-http-request !>(req)]~ :: redirect to docket =/ res u.ros =/ headers (malt headers.res) =/ headers ?: (~(has by headers) 'content-type') headers (~(put by headers) 'content-type' 'text/html') %+ welp effects.res %: http-cards p.req ~(tap by headers) :: ?@ fragment.res fragment.res %- crip %+ welp =/ ct (~(got by headers) 'content-type') ?: =(ct 'text/html') "<!DOCTYPE html>" "<?xml version=\"1.0\" encoding=\"utf-8\"?>" (en-xml:html fragment.res) == :: ++ doc :: |= [title=tape body=manx] ^- manx ;html ;head ;meta(charset "UTF-8"); ;title: {title} ;meta =name "viewport" =content """ width=device-width, initial-scale=1.0, maximum-scale=1.0 """ ; == ;link(rel "stylesheet", href "/apps/hawk/static/web/styles/feather/"); ;script(src "https://code.jquery.com/jquery-3.7.1.js"); ;script(src "/apps/hawk/static/web/scripts/jquery-config/"); ;script(src "https://unpkg.com/htmx.org@2.0.2"); ;script(src "/apps/hawk/static/web/scripts/htmx-config/"); ;script(src "/apps/hawk/static/web/scripts/url-config/"); ;script(src "/apps/hawk/static/web/scripts/network-indicator/"); ;+ ?. =(our.bowl src.bowl) ;/("") ;link(rel "manifest", href "/apps/hawk/static/web/scripts/manifest/"); :: ;script(type "module", src "/apps/hawk/static/web/components/ax-al/"); ;script(type "module", src "/apps/hawk/static/web/components/spine-sidebar/"); ;script(type "module", src "/apps/hawk/static/web/components/spine-tabs/"); ;script(type "module", src "/apps/hawk/static/web/components/spine-input-atom/"); ;script(type "module", src "/apps/hawk/static/web/components/spine-code-editor/"); :: :: ;script(type "module", src "https://cdn.jsdelivr.net/npm/@urbit/nockjs@1.3.0/dist/nockjs.esm.js"); ;* :: add user-generated content to the head c:(system-part /head-extras) == ;body.scroll-none(hx-boost "true") ;+ body == == :: ++ main :: |= [body=manx error=manx] ;div#hawk-main.grow.basis-half.fc.hf.wf.scroll-none :: main :: :: =style "overflow-y: hidden;" ;* :: double render for oob =; m :~ m(a.g [[%real ""] a.g.m]) m(a.g [[%hidden ""] [%hx-swap-oob "innerHTML:#hawk-main > .error-oob"] a.g.m]) == ^- manx ;div.error-oob ;+ error == ;div#hawk-content.grow.scroll-y.scroll-x.relative.hf.wf =style "container-type: inline-size;" ;+ body == == :: ++ htmx :::: |= req=[pax=(pole cord) =card] ^- (unit [effects=(list card:agent:gall) headers=(list [@t @t]) fragment=$@(@t manx)]) :: ?+ road=pax.req ~ ::: :: [%get ~] :: :^ ~ ~ (redir '303' / [~ ~]) '' :: [%get %'' ~] :: :^ ~ ~ (redir '303' / [~ ~]) '' :: :: [%get %'session.js' ~] :: :^ ~ ~ ['content-type' 'text/javascript']~ (rap 3 'window.ship = "' (rsh 3 (scot %p our.bowl)) '";' ~) :: [%get %apps %hawk %system pit=*] :: =/ sys (welp /~/system pit.road) =/ cof (welp /~/config pit.road) =/ pag=page %+ fall (~(get of tree) cof) %+ fall (~(get of tree) sys) *page :^ ~ ~ :~ ['content-type' (crip +:(spud p.code.pag))] == q.q.code.pag [%get %apps %hawk %static pit=*] :: =/ sys (welp /~/system pit.road) =/ cof (welp /~/config pit.road) =/ pag=page %+ fall (~(get of tree) cof) %+ fall (~(get of tree) sys) *page :^ ~ ~ :~ ['content-type' (crip +:(spud p.code.pag))] ['cache-control' 'max-age=3600'] == q.q.code.pag :: :: app [%get %'~~' pit=*] ?. (can-peek pit.road) (login-page pit.road) =/ pag (fall (~(get of tree) pit.road) *page) :: XX remove =/ the-file=file (~(dip of tree) pit.road) =/ the-tree=file tree =/ t ~(. fu tree) =/ f ~(. fu the-file) =/ c ~(. iu card.req) =/ m (m:t pit.road) =/ d (d:f /) =/ error (~(get of err.state) pit.road) =/ title (pub:m /title tas/?~(pit.road '/' (rear pit.road))) :: :: :: short circuit normal rendering with query params :: ?: (~(has of card.req) /data) :: =/ pag (fall (~(get of tree) pit.road) *page) :^ ~ ~ ~ %+ doc title %+ main %+ process-classes pit.road ?. (empty-manx data.pag) data.pag (system-part /empty-page) ?~(error ;/("") (part-error q.u.error)) :: ?: (~(has of card.req) /code) :: =/ pag (fall (~(get of tree) pit.road) *page) :^ ~ ~ ~[['content-type' (crip +:(spud p.code.pag))]] q.q.code.pag :: ?: (~(has of card.req) /mime) :: =/ pag (fall (~(get of tree) pit.road) *page) :^ ~ ~ ~[['content-type' (crip (pib:c /mime))]] data.pag :: :: normal rendering :: =/ pate=tape (welp (spud pit.road) ?~(pit.road "" "/")) :^ ~ ~ (rewrite pit.road card.req) %+ doc title ;div#hawk-top.hf.fc.scroll-none ;style :: ;- %- trip ''' nav#nav-bar-admin [for=tree] { grid-area: tree; } nav#nav-bar-admin [for=search] { grid-area: search; } nav#nav-bar-admin [for=edit] { grid-area: edit; } /* desktop */ @media (min-width: 700px) { nav#nav-bar-admin { display: grid; grid-template: "tree search edit" auto / auto 1fr auto; } #tree-tab { max-width: 400px; } #wall[empty] #tree-tab { max-width: 100%; } .hawk-mobile-footer { display: none; } } /* mobile */ @media (max-width: 700px) { nav#nav-bar-admin { display: grid; grid-template: "search search" auto "tree edit" auto / 1fr auto; } #login { flex-basis: 80%; flex-grow: 1; } #wall.empty #hawk-main { display: none; } #wall.auth[data-tab] #hawk-main { display: none; } .tab { border: none !important; } .tab-btn { width: auto; flex-grow: 1; } /* #hawk-view { flex-basis: 80%; } .tab-btn[for=tree] { flex-basis: 60%; } */ } ''' == ;script :: ;- %- trip ''' function openTab(name) { let btn = $(`button[for=${name}]`); let tab = $(`#${name}`).closest('.tab') let editTab = $(`#form-${name}`); if (editTab.length) { editTab.siblings('.edit-tab').addClass('hidden'); editTab.removeClass('hidden') $(btn).siblings('button.tab-btn').removeClass('toggled'); $(btn).siblings('button.tab-btn').addClass('b2'); // $(btn).removeClass('b2'); $(btn).addClass('toggled'); } else { btn.parent().find('.tab-btn').removeClass('active f4').addClass('f1'); btn.removeClass('f1').addClass('active f4'); } if (name !== 'tree') { // color edit button $('button[for=edit]').removeClass('f1').addClass('active f4'); } tab.removeClass('hidden'); tab.siblings('.tab').addClass('hidden'); $('#wall').attr('data-tab', name); const url = new URL(window.location); url.searchParams.set('view', name); window.history.replaceState({}, '', url); $('body').emit('changeTab'); }; function closeTabs() { $('.tab').addClass('hidden') if ($('#wall.empty').length > 0) { $('#tree').closest('.tab').removeClass('hidden') } $('#wall').attr('data-tab', null); $('nav button').removeClass('active f4').addClass('f1'); const url = new URL(window.location); url.searchParams.delete('view'); window.history.replaceState({}, '', url); }; function toggleTab(name) { let tab = $(`#${name}`).closest('.tab') if (tab.hasClass('hidden')) { openTab(name) } else { closeTabs() } } function unfocusSearch() { $('#hawk-search').addClass('hidden'); $('#hawk-crumbs').removeClass('hidden'); } function focusSearch() { $('#hawk-crumbs').addClass('hidden'); $('#hawk-search').removeClass('hidden'); $('#hawk-search-in').focus().get(0).setSelectionRange(999,999); } function respectUrlParams() { const urlParams = new URLSearchParams(window.location.search); const view = urlParams.get('view'); if (!!view) { openTab(view) } unfocusSearch(); } function respectScrollParam() { const urlParams = new URLSearchParams(window.location.search); const scroll = urlParams.get('scroll'); if (!!scroll) { document.querySelector(`#${scroll}`)?.scrollIntoView(); } } onload = () => { respectUrlParams(); respectScrollParam(); } window.addEventListener('htmx:afterSettle', (e) => { if (e.target == document.body && window.innerWidth > 700) { respectUrlParams(); } respectScrollParam(); }) window.addEventListener('htmx:historyRestore', (e) => { if (e.target == document.body && window.innerWidth > 700) { respectUrlParams(); } respectScrollParam(); }) ''' == ;div#loader.b2.fr.af :: loading bar :: =style "height: 3.3px;" ;div#progress.o7.fr.af ;div.grow(style "background: var(--f-4);"); == == ;+ ?. =(our.bowl src.bowl) :: unauthed :: ;nav#nav-bar-non-admin.frw.af.jb.b1.mono =style "border-bottom: 0.5px solid #77777777;" :: ;a.f-4.s-1.p-1.fc.ac.jc(hx-boost "false") =href "/~/login?redirect=/~~{pate}" ; admin == :: ;form#login.f1.mono.fr.af.je :: login :: =hx-boost "false" =style "filter: brightness(90%);" =method "POST" =action "/~/login" ;* ?: (is-valid-rank:ames src.bowl `@ubC`16) ;= ;input.hidden(name "eauth"); ;input.hidden(name "redirect", value "/~~{pate}"); ;input.p-1.s-1.b0.bd0.br0 =style "width: 135px;" =placeholder "~zod" =autocomplete "off" =spellcheck "false" =pattern (trip '~([a-z]{3}|[a-z]{6}(-[a-z]{6})*)') =title "urbit name like ~sampel-palnet" =name "name" ; == ;button.p-1.f-4.s-1.bd0: login == ;= ;span.p1.s-1.fr.ac: {(scow %p src.bowl)} ;a.f-4.p-1.s-1.o7.fr.ac(href "/~/logout?redirect=/~~{pate}"): logout == == == ;nav#nav-bar-admin.mono.b1 :: authed :: =style "border-bottom: 0.5px solid #77777777;" ;button.f0.wfc.tab-btn.b1.hover.fr.ac.jc.s2 :: tree button :: =style "padding: 0px 16px;" =onclick "toggleTab('tree')" =title "file tree" =for "tree" ; ≡ == ;div#hawk-view.fr.grow :: breadcrumbs & search :: =for "search" ;form#hawk-search.wf.hidden.p0.fr.af :: search :: =method "post" =action "/apps/hawk/go" =hx-on-htmx-after-request "unfocusSearch();" ;input#hawk-search-in.b2.grow.s0.p-1 =type "text" =required "" =name "path" =placeholder "/path/to/file" =title "/some/file/path" =pattern (trip '^(/)?([a-zA-Z0-9_\\-\\~\\.]+(/)?)*$') =spellcheck "false" =autocomplete "off" =onfocusout "unfocusSearch();" =value pate ; == ;div.f4.fr.ac.tr.o7.p-1 ;div.wfc.hfc =style "transform: rotate(-27deg); padding: 0 10px;" ;- "⚲" == == == ;div#hawk-crumbs.frw.af.wf.b1 :: breadcrumbs :: ;a.f2.mono.b1.hover.fr.ac.bold.loader.s0 =style "padding: 4px 12px;" =href "/~~/?view=tree" ;span.loaded: / ;span.loading ; - == == :: ;* =< p %^ spin pit.road 1 |= [=term i=@] :_ +(i) ;a.f2.mono.fr.g1.wfc.hover.b1.ac.p1.loader.s0 =href "/~~/{`tape`(join '/' (scag i pit.road))}/?view=tree" ;div.loaded ;span: {(trip term)} ;span.f4.o3: / == ;span.loading ;- `tape`(reap (lent (trip term)) '=') == == ;button.f4.grow.fr.ac.js.hover.b1.p-1.o7 =onclick "focusSearch();" ;div.wfc.hfc =style "transform: rotate(-27deg); padding: 0 10px;" ;- "⚲" == == == == ;button.wfc.tab-btn.b1.hover.fr.ac.jc.g1.f0 :: edit button :: =style "padding: 0px 16px;" =onclick "toggleTab('edit');" =for "edit" ;+ ?. =(`f/%.y (reb:m /peek/public)) ;/("") ;span.fr.ac.f-2: • ;+ ?. =(`f/%.y (reb:m /poke/public)) ;/("") ;span.fr.ac.f-1: • ;+ ?. =(`f/%.y (reb:m /pulse)) ;/("") ;span.fr.ac.f-3: • ;span.s0: edit == == ;div#wall =class "fr grow scroll-none hf {?:(=(our.bowl src.bowl) "auth" "no-auth")}" ;+ =/ hid ?: (empty-manx data.pag) "" "hidden" ?. =(our.bowl src.bowl) ;/("") ;div#tree-tab :: tree :: =class "b1 tab fc grow wf hidden scroll-none" =style "border-right: 0.5px solid #77777777;" ;div#tree.hidden: tree :: ;* :: double render for oob =; m :~ m(a.g [[%real ""] a.g.m]) m(a.g [[%hidden ""] [%hx-swap-oob "innerHTML:#tree-tab > .oob"] a.g.m]) == ^- manx ;div.fc.grow.oob.scroll-none ;div.fc.g2.sticky.b1.z1 =style "top: 0; border-bottom: 0.5px solid var(--b2);" ;div.frw.ac.jb.p2.g2 ;div.fr.g2 ;form(method "post") =action "/apps/hawk/new-sibling{pate}" ;+ =; m ?. =(pit.road /) m m(a.g [[%disabled ""] a.g.m]) ^- manx ;button.b2.bd1.br1.mono.s-1.p-1.loader.hover ;span.loaded: new sibling ;span.loading: =========== == == ;form(method "post") =action "/apps/hawk/new-sub-file{pate}" ;button.b2.bd1.br1.mono.s-1.p-1.loader.hover ;span.loaded: new sub-file ;span.loading: ============ == == == ;a.bd1.br1.mono.s-1.p-1.b1.hover.loader(href "/~~/~/manual/") ;span.loaded: docs ;span.loading: ==== == == == ;div.p-page.scroll-y.grow ;a.p-1.wf.mono.s0.f2.hover.b1.block.fr.js =href "/~~/?view=tree" ;div.loader.wfc ;span.loaded: / ;span.loading: = == == ;* =/ og-tree the-tree =/ pax=path / |- %+ turn %+ sort ~(tap by dir.the-tree) |= [a=[=term *] b=[=term *]] (aor term.a term.b) |= [=term fi=file] =. pax (snoc pax term) =/ mm (~(m fu og-tree) pax) =; m %= m a.g %- zing ^- (list mart) :~ ?. (is-ancestor pax pit.road) ~ [%open ""]~ :: ?. =(pax pit.road) ~ [%open ""]~ :: ?. =(pax pit.road) ~ [%selected ""]~ :: ?~ fil.fi ~ [%file ""]~ ?~ fil.fi ~ ?. =(`f/%.y (reb:mm /pulse)) ~ [%pulse ""]~ ?~ fil.fi ~ ?. =(`f/%.y (reb:mm /peek/public)) ~ [%peek ""]~ ?~ fil.fi ~ ?. =(`f/%.y (reb:mm /poke/public)) ~ [%poke ""]~ :: a.g.m == == ^- manx ;ax-al =path (spud pax) ;* ^$(the-tree fi, pax pax) == == == ;+ part-version ;div.hawk-mobile-footer.b1(style "height: 22px; flex-shrink: 0;"); == ;+ %+ main %+ process-classes pit.road ?. (empty-manx data.pag) data.pag (system-part /empty-page) ?~(error ;/("") (part-error q.u.error)) :: ;+ ?. =(our.bowl src.bowl) ;/("") :: edit :: ;div.tab.basis-half.grow.fc.hf.hidden.relative.b1.scroll-none =style "border-left: 0.5px solid #77777777;" ;div#edit.frw.je.af :: nav :: ;button.p2.f2.mono.hover.grow.b2.tab-btn.bd1 =onclick "openTab('meta');" =type "button" =for "meta" ; meta == ;button.p2.f2.mono.hover.grow.b2.toggled.tab-btn.bd1 =onclick "openTab('code');" =type "button" =for "code" ; code == ;button.p2.b2.f2.mono.hover.grow.tab-btn.bd1 =onclick "openTab('data');" =type "button" =for "data" ; data == ;button.p2.f2.mono.hover.grow.b2.tab-btn.bd1 =onclick "openTab('auth');" =type "button" =for "auth" ; auth == ;button.p2.f2.mono.hover.grow.b2.tab-btn.bd1 =onclick "openTab('dojo');" =type "button" =for "dojo" ; dojo == == ;div#form-auth.grow.fc.hidden.edit-tab.scroll-none :: auth :: ;div#auth.hidden; ;div.grow.scroll-y ;+ |^ ;div.page.fc.g6.mono ;+ %^ attribute-section "f-2" /peek/public "can others view this file?" ;+ %^ attribute-section "f-1" /poke/public "can others submit forms in this file?" == ++ attribute-section |= [color=tape rib=path desc=tape] ;div.fc.g4.br2.bd1.p4.ac ;div.frw.g2.ac ;div =class "bold s1 {color}" ; {(trip (head rib))} == ;div.f3: {desc} == ;div.frw.g3 ;+ (single-form rib "this page") ;+ (single-form (welp rib //~) "sub-pages") == ;+ (inheritance rib) == ++ inheritance |= [rib=path] =/ inherited=info %- ~(gas of *info) %+ murn tap:m |= [pax=path dim=dime] =/ our (~(get of met:f) pax) ?: =(our `dim) ~ `[pax dim] ?~ their=(~(get of inherited) rib) ;/("") ;div.o5.s-1.tc ;span: an ancestor page has already set the ;span.bold ;+ ;/ (trip (head rib)) == ;span: attribute to ;span.bold ;+ ;/ ?: =(%.y q.u.their) "public" "private" == ;span: . ;span: but you may override it with these forms. == ++ single-form |= [rib=path desc=tape] =/ me ~(. iu met:(f:f /)) =/ val (reb:me rib) ;div.mono.fc.af.wfc ;form.fc.wfc.af(method "post") =action "/apps/hawk/meta-set{(spud pit.road)}" ;input.hidden(name "/sys/view", value "auth"); ;input.hidden(name "/rib", value (spud rib)); ;+ %+ label desc ;spine-input-atom =name "/val" =value ?~(val "" (scow u.val)) =aura "f" =required "" ; == ;button.bd1.loader.hover.b2.p-1 ;span.loaded: save ;span.loading: ==== == == ;+ ?~ val ;/("") ;form.fc.af(method "post") =action "/apps/hawk/meta-del{(spud pit.road)}" ;input.hidden(name "/rib", value (spud rib)); ;input.hidden(name "/sys/view", value "auth"); ;button.bd1.loader.hover.b2.p-1.s-1.o6 ;span.loaded: unset ;span.loading: ==== == == == ++ label |= [label=tape =manx] ;div.wfc.p2.bd1.fc.g1.ac ;span.s0.bold.f4: {label} ;+ manx == -- == == ;div#form-dojo.grow.fc.hidden.edit-tab.scroll-y :: dojo :: ;div#dojo.hidden; ;div.grow.fc.ac.jc.scroll-hidden.scroll-none ;button.p-2.b2.wfc.mono.s1.br1.bd1.hover =onclick """ $(this).parent().replaceWith($(`<iframe src="/apps/webterm" class="grow fc ac jc bd0 m0 p0"><iframe/>`)) """ ; connect to dojo == == == ;div#form-meta.grow.fc.hidden.edit-tab.scroll-none :: meta :: ;div#meta.hidden; ;* :: double render for oob =; m :~ m(a.g [[%hidden ""] [%hx-swap-oob "innerHTML:#form-meta .oob"] a.g.m]) m == ^- manx ;div.p3.fc.g8.grow.scroll-y.oob.mono.s0 :: ;div.fc.g2 :: metadata ;div :: inherited metadata :: ;* %+ murn tap:(m:t pit.road) |= [=path =dime] =/ raw=(set ^path) (silt ~(tah iu met:f)) ?: (~(has in raw) path) ~ :- ~ ;div.s-1.o7.frw.g3 ;span.f4: {(spud path)} ;span.f0: {(print-dime dime)} == == ;div.fc :: node's raw metadata :: ;* %+ turn ~(tap iu met:f) |= [=path =dime] ;form.frw.g3.ac.js =method "post" =hx-boost "true" =action "/apps/hawk/meta-del{pate}" ;input.hidden(name "rib", value (spud path)); ;input.hidden(name "/sys/view", value "meta"); ;span.f4: {(spud path)} ;span.s-1.f0: {(print-dime dime)} ;button.b1.hover.bd1.loader.br2.o8.s-2 =style "padding: 2px 4px;" ;span.loaded: del ;span.loading: === == == == ;form.frw.s-1.af.g1 :: set metadata :: =method "post" =action "/apps/hawk/meta-set{pate}" =hx-boost "true" ;input.hidden(name "/sys/view", value "meta"); ;input.p-1.grow.bd1.br1 =name "/rib" =required "" =spellcheck "off" =autocomplete "off" =placeholder "/meta/path" ; == ;div.frw.af.grow.g1 ;select.p-1.bd1.br1 =oninput "$(this).next().attr('aura', this.value).attr('value', '')" ;* %+ turn ~["t" "tas" "da" "ud" "rs" "p" "q" "f"] |= =tape ;option(value tape) ; {"%"}{tape} == == ;spine-input-atom.p-1.grow.bd1.br1 =name "/val" =aura "t" =placeholder "value" ; == ;button.p2.bd1.br1.b2.s-1.hover.loader ;span.loaded: save ;span.loading: ==== == == == == :: ;div.frw.g2.af :: import/export :: ;form.fc.grow(hx-boost "false") =action "/apps/hawk/export{pate}" =method "post" ;button.p-1.br1.bd1.b2.loader.hover.wf.s0 ;span.loaded: export with sub-files ;span.loading: ===================== == == ;form.fc.grow.af.jf =method "post" =action "/apps/hawk/import{pate}" =hx-encoding "multipart/form-data" =hx-confirm "this action is potentially destructive because it could overwrite this node or its subfiles. continue?" =hx-boost "true" =hx-swap "none" ;label.p-1.br1.bd1.b2.hover.loader.wf.fc.ac.jc.s0.pointer.hf =for "hawk-import" ;span.loaded: import ;span.loading: ====== == ;input.hidden =id "hawk-import" =style "width: 250px;" =name "noun" =oninput "$(this).next().click()" =required "" =autocomplete "off" =type "file" ; == ;button.hidden: import == == :: ;div.fc.g1.break :: deps :: ;h3: dependencies ;* =; links ?~ links :_ ~ ;div.f4: none links %+ turn ~(tap in (fall (~(get of dep.state) pit.road) *(set path))) |= =path ;a.underline(href "/~~{(spud path)}/"): {(spud path)} == :: ;div.fc.g1.break :: backlinks :: ;h3: backlinks ;* =; links ?~ links :_ ~ ;div.f4: none links %+ turn ~(tap in (fall (~(get of sub.state) pit.road) *(set path))) |= =path ;a.underline(href "/~~{(spud path)}/"): {(spud path)} == :: ;div.fc.break.g1 :: sub files :: ;h3: sub-files ;* =; links ?~ links :_ ~ ;div.f4: none links %+ turn wah:f |= =path =/ peta (welp (spud (welp pit.road path)) ?~(path "" "/")) ;a.underline =href (welp "/~~" peta) ; {(spud path)} == == :: ;div(style "padding-bottom: 40vh;"); == == ;form#form-code.grow.fc.edit-tab.scroll-none :: code :: =hx-post "/apps/hawk/code{pate}" =hx-swap "outerHTML" =hx-target "find .loader" =hx-select ".refresher" ;div.fc.mono.s-1.b1 ;select.p1.f3.b1.hover =name "/protocol" =autocomplete "off" =required "" ;* %+ turn ^- (list mite) :~ /text/plain /text/hawk-500 /text/markdown /text/javascript /text/css /application/json == |= =mite =; man ?. =(p:mim:f mite) man man(a.g [[%selected ""] a.g.man]) ^- manx ;option(value (spud mite)): {(spud mite)} == == ;div.grow.fc.af.scroll-none ::;textarea#code.grow.mono.pre ;spine-code-editor#code.grow.mono.pre =name "code" =autocomplete "off" =placeholder "the code of this file." =spellcheck "false" =mite (spud p:mim:f) ;* ~[;/((trip q.q.code.pag))] == == ;button.p2.fr.ac.jc.g3.mono.b2.bd1.hover =id "code-save" =type "submit" ;div.loader ;* :: double render for oob =; m :~ m(a.g [[%real ""] a.g.m]) m(a.g [[%hidden ""] [%hx-swap-oob "innerHTML:#code-save .loading"] a.g.m]) == ^- manx ;div.loading: ==== ;div.loaded: save == == == ;form#form-data.grow.fc.hidden.edit-tab :: data :: =hx-post "/apps/hawk/data{pate}" ;div#data.grow.mono.pre.fc.ac.jc ;button.loader.p-2.br1.bd1.b2.hover.s1 =type "button" =hx-get "/apps/hawk/data{pate}" =hx-target "#data" =hx-select "#data" =hx-swap "outerHTML" ;span.loaded: load printed sail data ;span.loading: ====================== == == == ;div.hawk-mobile-footer.b1(style "height: 22px; flex-shrink: 0;"); == == == :: [%get %apps %hawk %data pit=*] :: ?. =(our.bowl src.bowl) (login pit.road) =/ pag (fall (~(get of tree) pit.road) *page) :^ ~ ~ ~ ;div#data.grow.fc ;textarea.grow.mono.pre =style "padding-top: 8px;" =name "data" =autocomplete "off" =placeholder "hoon that resolves to a $manx" =spellcheck "false" =onkeydown "if (event.key == 'Enter' && event.ctrlKey) this.nextElementSibling.click()" ; {(print-sail data.pag 0)} == ;button.p2.fr.ac.jc.g3.mono.b2.bd1.loader.hover ;span.loaded: overwrite ;span.loading: ========= == == [%get %apps %hawk %login key-path=@ *] :: :^ ~ ~ ~ %+ doc "bootstrapping login" ;form.wf.hf.fc.ac.jc(method "post") =hx-boost "false" =action "/~/login" ;h1: logging in ;input.hidden(name "password", value (trip key-path.road)); ;input.hidden(name "redirect", value "/"); ;script: $('form').submit(); == :: [%get %apps %hawk pit=*] :: ?. =(our.bowl src.bowl) (login pit.road) :^ ~ ~ ~[['X-Status' '307'] ['Location' '/~~/']] '' :: :: form handlers :: [%pos %apps %hawk %export pit=*] :: ?. =(our.bowl src.bowl) (login pit.road) :^ ~ ~ :~ :- 'Content-Disposition' %- crip "attachment; filename=\"{(scow %p our.bowl)}{(scow %da now.bowl)}-{(flatten-path pit.road)}.jam\"" ['content-type' 'application/urbit-noun'] == =/ without-defaults %- %~ lop of (~(lop of tree) /~/system) /~/manual (jam [%6 (~(dip of without-defaults) pit.road)]) :: [%pos %apps %hawk %import pit=*] :: ?. =(our.bowl src.bowl) (login pit.road) =/ whole (cue q:(need (~(get of card.req) /noun))) =/ imported ?: =(-.whole %4) =/ four %* . *state-4:hu tree (file-4:hu +.whole) == tree:(state-4-to-6:hu four) ?: =(-.whole %5) =/ five %* . *state-5:hu tree (file-5:hu +.whole) == tree:(state-5-to-6:hu five) ?: =(-.whole %6) (file-6:hu +.whole) ~|(%incompatible-version !!) :^ ~ [%pass /graft %agent [our.bowl %hawk] %poke graft/!>([pit.road imported])]~ ['HX-Refresh' 'true']~ '' :: [%pos %apps %hawk %become pit=*] :: ?. =(our.bowl src.bowl) (login pit.road) =/ idol (stab q:(need (~(get of card.req) /idol))) :^ ~ [%pass /put %agent [our.bowl %hawk] %poke become/!>([pit.road idol])]~ (redir '303' pit.road (~(gas of *card) [/sys/view tas/%code]~)) '' [%pos %apps %hawk %meta-set pit=*] :: ?. =(our.bowl src.bowl) (login pit.road) =/ c ~(. iu card.req) =/ rib=path (crashless-stab q:(rib:c /rib)) =/ val=dime (rib:c /val) :^ ~ [%pass /put %agent [our.bowl %hawk] %poke meta-set/!>([pit.road rib val])]~ (redir '303' pit.road card.req) '' [%pos %apps %hawk %meta-sot pit=*] :: ?. =(our.bowl src.bowl) (login pit.road) =/ c ~(. iu card.req) =/ rib=path (crashless-stab q:(rib:c /rib)) =/ vals=info (dip:c /val) :^ ~ [%pass /put %agent [our.bowl %hawk] %poke meta-sot/!>([pit.road rib vals])]~ (redir '303' pit.road card.req) '' [%pos %apps %hawk %meta-del pit=*] :: ?. =(our.bowl src.bowl) (login pit.road) =/ c ~(. iu card.req) =/ rib (crashless-stab q:(rib:c /rib)) :^ ~ [%pass /put %agent [our.bowl %hawk] %poke meta-del/!>([pit.road rib])]~ (redir '303' pit.road card.req) '' :: [%pos %apps %hawk %code pit=*] :: ?. =(our.bowl src.bowl) (login pit.road) =/ code q:(need (~(get of card.req) /code)) =/ =mite (stab q:(~(rob iu card.req) /protocol)) :^ ~ [%pass /put %agent [our.bowl %hawk] %poke code/!>([pit.road (en-mime mite code)])]~ ~ ;div.loader.refresher =hx-get "" =hx-trigger "load delay:0.1s" =hx-swap "innerHTML" =hx-target "#hawk-content" =hx-select "#hawk-content > *" ;div.loading: #### ;div.loaded: save == [%pos %apps %hawk %data pit=*] :: ?. =(our.bowl src.bowl) (login pit.road) =/ pate (welp (spud pit.road) ?~(pit.road "" "/")) =/ data q:(need (~(get of card.req) /data)) :^ ~ [%pass /put %agent [our.bowl %hawk] %poke data/!>([pit.road data])]~ ~[['HX-Refresh' 'true']] '' :: [%pos %apps %hawk %go ~] :: ?. =(our.bowl src.bowl) (login /) =/ =path (lax-stab q:(need (~(get of card.req) /path))) =/ pate (welp (spud path) ?~(path "" "/")) :^ ~ ~ (redir '303' path (~(gas of *card) [/sys/view tas/%tree]~)) '' [%pos %apps %hawk %new-sibling pit=*] :: ?. =(our.bowl src.bowl) (login /) =/ name (crip +>:(scow %ux (~(sit fo 0xffff) eny.bowl))) =/ =path (snoc (snip pit.road) name) =/ pate (welp (spud path) ?~(path "" "/")) :^ ~ [%pass /put %agent [our.bowl %hawk] %poke put/!>(path)]~ (redir '303' path (~(gas of *card) [/sys/view tas/%tree]~)) '' [%pos %apps %hawk %new-sub-file pit=*] :: ?. =(our.bowl src.bowl) (login /) =/ name (crip +>:(scow %ux (~(sit fo 0xffff) eny.bowl))) =/ =path (snoc pit.road name) =/ pate (welp (spud path) ?~(path "" "/")) :^ ~ [%pass /put %agent [our.bowl %hawk] %poke put/!>(path)]~ (redir '303' path (~(gas of *card) [/sys/view tas/%tree]~)) '' [%pos %apps %hawk %put ~] :: ?. =(our.bowl src.bowl) (login /) =/ pad q:(need (~(get of card.req) /path)) =/ =path (stab pad) =/ pate (welp (spud path) ?~(path "" "/")) :^ ~ [%pass /put %agent [our.bowl %hawk] %poke put/!>(path)]~ ~[['HX-Location' (crip "/~~{pate}")]] '' [%pos %apps %hawk %lop pit=*] :: ?. =(our.bowl src.bowl) (login pit.road) :^ ~ [%pass /put %agent [our.bowl %hawk] %poke lop/!>(pit.road)]~ (redir '303' pit.road (~(gas of *card) [/sys/view tas/%tree]~)) '' [%pos %apps %hawk %del pit=*] :: ?. =(our.bowl src.bowl) (login pit.road) :^ ~ [%pass /put %agent [our.bowl %hawk] %poke del/!>(pit.road)]~ (redir '303' pit.road (~(gas of *card) [/sys/view tas/%tree]~)) '' :: [%pos %'~~' pit=*] :: ?. (can-poke pit.road) (eauth-login pit.road) :^ ~ [%pass /put %agent [our.bowl %hawk] %poke noun/!>([src.bowl pit.road card.req])]~ =/ next=path %+ fall (mole |.((stab `@t`q:(~(rib iu card.req) /sys/redirect)))) %+ fall ?~ name=(stub q:(~(rib iu card.req) /sys/redirect-via-name)) ~ (stub q:(~(rib iu card.req) u.name)) pit.road (redir '303' next card.req) '' :: [%get *] :: not found :: :^ ~ ~ ['X-Status' '404']~ ;div *page not found* [navigate to root](/) == == -- --