diff options
author | polwex <polwex@sortug.com> | 2025-06-27 22:53:52 +0700 |
---|---|---|
committer | polwex <polwex@sortug.com> | 2025-06-27 22:53:52 +0700 |
commit | 328ebe85135912678bdacd3381126ffd66ef2761 (patch) | |
tree | 365962bf45302f2a440f766a4f3c9e0a962dbe47 /desk/web/board |
init
Diffstat (limited to 'desk/web/board')
-rw-r--r-- | desk/web/board/index.hoon | 456 | ||||
-rw-r--r-- | desk/web/board/mobile-index.hoon | 128 | ||||
-rw-r--r-- | desk/web/board/new.hoon | 299 | ||||
-rw-r--r-- | desk/web/board/router.hoon | 281 | ||||
-rw-r--r-- | desk/web/board/thread.hoon | 807 |
5 files changed, 1971 insertions, 0 deletions
diff --git a/desk/web/board/index.hoon b/desk/web/board/index.hoon new file mode 100644 index 0000000..f70eb03 --- /dev/null +++ b/desk/web/board/index.hoon @@ -0,0 +1,456 @@ +/- boke, tp=trill-post, cnt=contact +/+ sr=sortug, plib=trill-utils, sigil=sigil-sigil, lib=boke, kaji, const=constants, fetch-lib=fetch +/= thread-preview /web/components/thread-preview +|_ [s=state:boke =bowl:gall is-mobile=?] ++* fetch ~(. fetch-lib [s bowl]) +++ show-css ^~ %- trip +''' +.thread-preview{ + display: flex; + gap: 0.5rem; + align-items: center; + border-bottom: 1px solid var(--text-color); + margin: auto; + padding: 0.4rem; + color: var(--text-color); + + & .thread-author{ + width: 10%; + & img, & svg { + width: 40px; + } + } + + & .thread-name{ + width: 60%; + + & h2{ + margin: 0; + color: var(--text-color); + } + + & .thread-tags{ + display: flex; + gap: 0.3rem; + & .tag{ + padding: 0.2rem; + background-color: var(--huang); + opacity: 0.9; + font-size: 0.7rem; + cursor: pointer; + border-radius: 0.7rem; + } + } + } + + & .reply-count{ + width: 10%; + } + & .dates{ + width: 20%; + text-align: right; + padding-right: 0.5rem; + & .last-reply-author{ + display: flex; + gap: 1ch; + margin-left: auto; + width: fit-content; + } + } + + &. reply-count{ + font-size: 0.9rem; + } +} +@media (max-width: 768px){ + .thread-preview{ + & .meta{ + justify-content: space-between; + } + & img, & svg{ + display: none!important; + } + } +} +''' +++ css ^~ %- trip +''' +body > main{ + width: unset!important; +} +.tab:hover{ + font-weight: 700; +} +.tab.active{ + font-weight: 700; +} +#board-index{ + width: 16%; + padding: 0 0.5rem; + border-right: 1px solid grey; + + & header{ + display: flex; + height: 60px; + + & #select{ + color: var(--hong); + } + & img{ + width: 30px; + } + } +} +#board-list{ + padding: 0.5rem; + height: calc(100% - 4rem); + & .entry.active{ + border: 1px solid black; /* TODO */ + } + & .entry{ + cursor: pointer; + display: flex; + align-items: center; + margin-bottom: 0.5rem; + & img{ + width: 30px; + margin-right: 10px; + } + & h3{ + font-size: 30px; + margin: 0 10px 0 0; + } + & h4{ + margin: 0; + } + } + & .entry:hover{ + background-color: rgb(100, 100, 100, 0.3); + } +} +.tabs{ + align-items: center; + justify-content: space-evenly; + width: 100%; +} +#board-proper{ + width: 84%; /* TODO third column?*/ + + & .title{ + padding: 1rem 2rem; + height: 60px; + border-bottom: 1px solid var(--text-color); + display: flex; + justify-content: space-between; + + & h2{ + text-transform: uppercase; + width: 30%; + margin: 0; + } + } + & #thread-list{ + height: calc(100% - 60px); + overflow-y: scroll; + } + #sorting{ + display: flex; + justify-content: space-evenly; + gap: 1rem; + align-items: center; + border-bottom: 1px solid var(--text-color); + + & .sort-cat{ + display: flex; + align-items: center; + cursor: pointer; + } + } +} + +@media (max-width: 768px){ + #board-main{ + display: block; + & #board-index{ + width: 100%; + } + & #board-proper{ + display: none; + } + } +} +''' +++ mobile-css ^~ %- trip +''' +@media (max-width: 800px){ + #board-proper{ + & .title{ + padding: 0.5rem; + border-bottom: 1px solid var(--text-color); + & input{ + width: 30%; + line-height: 2rem; + } + } + & #thread-list{ + padding: 1rem; + + & .thread-preview{ + color: var(--text-color); + & h2{ + margin-bottom: 0.2rem; + } + & .thread-inner{ + border-bottom: 1px solid black; + margin-bottom: 0.5rem; + } + & .meta{ + opacity: 0.7; + display: flex; + align-items: center; + justify-content: space-between; + } + } + } + } +} +.by-author{ + display: flex; + gap: 1ch; +} + +#tag-search{ + margin-bottom: 1rem; +} +''' +++ big-button-css ^~ %- trip +''' +.big-button{ + display: flex; + background-color: var(--hong); + margin: auto; + position: fixed; + bottom: 50px; + left: 50%; + transform: translateX(-50%); + & svg{ + width: 20px; + } +} +''' +++ big-button +;a.button.big-button/"/board/n" + ;style:"{big-button-css}" + ;svg + =viewBox "0 0 20 20" + =enable-background "new 0 0 20 20" + ;path + =fill "#FFFFFF" + =d "M17.561,2.439c-1.442-1.443-2.525-1.227-2.525-1.227L8.984,7.264L2.21,14.037L1.2,18.799l4.763-1.01l6.774-6.771l6.052-6.052C18.788,4.966,19.005,3.883,17.561,2.439z M5.68,17.217l-1.624,0.35c-0.156-0.293-0.345-0.586-0.69-0.932c-0.346-0.346-0.639-0.533-0.932-0.691l0.35-1.623l0.47-0.469c0,0,0.883,0.018,1.881,1.016c0.997,0.996,1.016,1.881,1.016,1.881L5.68,17.217z" + ; + == + ; + == + ;span:"New Thread" +== + +++ recent-post +|= [* =post:tp] +:: =/ snippet (abbreviate-post:plib contents.post 100) +=/ tags=marl %^ foldi:sr ~(tap in tags.post) `marl`~ |= [i=@ud tag=@t acc=marl] +?: (gth i 3) acc +:_ acc +=/ tt (trip tag) +;a@"/boards/tag/{tt}":"{tt}" + +;div.recent-post + ;div.author:"{(scow %p author.post)}" + ;div.tags + ;* tags + == +== +++ main +|= post-list=manx +^- manx +;div#board-main.flex.fh +;style: {css} +;style:"{show-css}" + ;div#board-index.fh + ;header + ;div.tabs :: Boards/tags/latest + ;div.tab.active + =kaji "scry" + =path "/board/f/boards" + =swap "swap" + =targ "#board-list" + ; Boards + == + ;div.tab + =kaji "scry" + =path "/board/f/tags" + =swap "swap" + =targ "#board-list" + ; Tags + == + == + == + ;div#board-list.fsy + ;* category-list + == + == + ;+ post-list +== +++ category-list ^- marl + :- ;div.entry + =kaji "scry" + =swap "swap" + =path "/board/f/page/l/l/0" + =targ "#board-proper" + :: ;img@"https://s3.spandrell.ch/assets/board/boards/{name}.svg"; + ;h3: 全 + ;h4: All + == + ;* %+ turn ~(tap by categories:const) |= [p=@tas *] + =/ name (trip p) + ;div.entry + =kaji "scry" + =swap "swap" + =path "/board/f/page/b/{name}/0" + =targ "#board-proper" + ;img@"https://s3.spandrell.ch/assets/board/boards/{name}.svg"; + ;h4: {name} + == +++ make-tag-list + |= l=(list [@t @ud]) ^- marl + ;= ;+ tag-search-input + ;+ ?~ l + ;span:"No tags found" + ;div#board-list-inner.tags + ;* %+ turn l tag-preview + == + == +++ tag-search-input +;div#tag-search + ;input(type "text", name "query", placeholder "Search tag") + =kaji "search" + =bounce "1000" + =path "/board/f/tag-search" + =swap "swap" + =targ "#board-list-inner" + ; + == +== +++ tag-preview + |= [t=@t c=@ud] ^- manx + =/ name (trip t) + =/ count (scow:parsing:sr %ud c) + ?. is-mobile + ;div.entry + =kaji "scry" + =swap "swap" + =path "/board/f/page/t/{name}/0" + =targ "#board-proper" + ;h4:"{name} ({count})" + == + ;a.entry/"/board/t/{name}" + ;h4:"{name} ({count})" + == + +++ cursor +|= [cursor=(unit @da) label=tape =rq search=(unit @t)] ^- manx +?~ cursor ;div; +=/ type + ?- -.rq + %latest "l/l" + %board "b/{(trip name.rq)}" + %tag "t/{(trip name.rq)}" + == +=/ path ?~ search +"/board/f/page/{type}/{(scow:parsing:sr %uw u.cursor)}" +"/board/f/search/{type}/{(scow:parsing:sr %uw u.cursor)}?query={(trip u.search)}" + +;div.cursor(kaji "iscroll", path path, cont "#thread-list", where "bottom") + ;button:"{label} posts" +== ++$ rq $%([%latest ~] [%board name=@t] [%tag name=@t]) +++ post-index +|= [=tpage:tp =rq contacts=whoms:cnt search=(unit @t) is-mobile=?] ^- manx +~& > cursors=[newer.tpage older.tpage] +=/ title ?- -.rq + %latest "Latest posts" + %board (trip +.rq) + %tag "Tag: {(trip +.rq)}" == +:: =/ cursor-string ?~ older.page "0" (scow:parsing:sr %uw u.older.page) +=/ cursor-string "" +~& page-req=+.page +:: =/ order "Post date" :: || "Reply date" +=/ flop-path "/board/f/flop/" +=/ search-path ?- -.rq + %latest "/board/f/search/l/l/{cursor-string}" + %board "/board/f/search/b/{(trip +.rq)}/{cursor-string}" + %tag "/board/f/search/t/{(trip +.rq)}/{cursor-string}" == +=/ sort-path ?- -.rq + %latest "/board/f/page/h/l/l/0" + %board "/board/f/page/b/{(trip +.rq)}/0" + %tag "/board/f/page/t/{(trip +.rq)}/0" == + +;div#board-proper.fh + ;style: {mobile-css} + ;div.title.f1 + :: ;button + :: =kaji "scry" + :: path flop-path + :: ; Flop + :: == + ;h2:"{title}" + ;input(type "text") + =placeholder "Search threads" + =kaji "search" + =name "query" + =bounce "1000" + =path search-path + =swap "swap" + =targ "#thread-list" + ; + == + == + :: ;div#sorting + :: =kaji "scry" + :: =swap "swap" + :: =targ "thread-list" + + :: ;div.sort-cat + :: ;span:"Title" + :: ;span(path "{sort-path}?sort=title&dir=asc"):"⇑" + :: ;span(path "{sort-path}?sort=title&dir=desc"):"⇓" + :: == + :: ;div.sort-cat + :: ;span:"Author" + :: == + :: ;div.sort-cat + :: ;span:"Posted" + :: == + :: ;div.sort-cat + :: ;span:"Last Reply" + :: == + :: ;div.sort-cat + :: ;span:"Reply Count" + :: == + :: == + ;div#thread-list + ;* %+ turn p.tpage |= t=thread:tp + =/ preview ~(. thread-preview [t contacts bowl]) + ?: is-mobile mobile:preview wide:preview + ;+ (cursor older.tpage "Older" rq search) + == + ;+ big-button +== +++ previews +|= [=tpage:tp =rq contacts=whoms:cnt search=(unit @t) is-mobile=?] ^- manx +;div + ;* %+ turn p.tpage |= t=thread:tp + =/ preview ~(. thread-preview [t contacts bowl]) + ?: is-mobile mobile:preview wide:preview + ;+ (cursor older.tpage "Older" rq search) +== +-- diff --git a/desk/web/board/mobile-index.hoon b/desk/web/board/mobile-index.hoon new file mode 100644 index 0000000..3c57173 --- /dev/null +++ b/desk/web/board/mobile-index.hoon @@ -0,0 +1,128 @@ +/- boke, tp=trill-post, cnt=contact +/+ sr=sortug, plib=trill-utils, sigil=sigil-sigil, lib=boke, kaji, const=constants +/= board /web/board/index +|_ [s=state:boke =bowl:gall] +++ css ^~ %- trip +''' +#board-index{ + margin-top: 1rem; + .tabs{ + width: 50%; + margin: auto; + + & .tab.active{ + font-weight: 700; + } + } + & #board-list{ + input{ + display: block; + margin: auto; + } + } + & #board-list-inner { + display: flex; + flex-wrap: wrap; + justify-content: center; + + & .board{ + display: block; + cursor: pointer; + padding: 1rem 0; + border: 1px solid var(--text-color); + margin: 0.3rem; + width: 20%; + text-align: center; + & .hanzi{ + font-size: 50px; + margin: 0; + color: var(--text-color); + } + & h4{ + word-break: keep-all; + text-align: center; + } + + & img{ + width: 50px; + display: block; + margin: 0 auto 0.5rem auto; + } + } + & .entry{ + display: flex; + flex: 0 0 33%; + align-items: center; + padding: 0.5rem 1rem; + + & h4{ + margin-right: 0.2rem; + } + } + } + +} +#board{ + display: block; +} + #board-index{ + .board{ + width: unset; + } + & h4{ + font-size: 1rem; + } + } + #tag-search{ + margin-bottom: 1rem; + } +''' +++ $ +:: =/ latest (bot:gorrm:tp feed.s 10) +;div#board-index + ;style: {css} + ;h2.tc:"Forum" + ;div.tabs + :: ;div.tab + :: =path "/board/latest" + :: ; Latest + :: == + ;div.tab.active + =kaji "scry" + =swap "swap" + =targ "#board-list-inner" + =path "/board/ff/boards" + ; Categories + == + ;div.tab + =kaji "scry" + =swap "swap" + =targ "#board-list" + =path "/board/ff/tags" + ; Tags + == + == + ;div#board-list + ;div#board-list-inner + ;* category-list + == + == + ;+ big-button:board +== +++ category-list ^- marl + :- + ;a.board/"/board/all" + ;div + ;h3.hanzi: 全 + ;h4: All + == + == + ;* %+ turn ~(tap by categories:const) |= [p=@tas *] + =/ name (trip p) + ;a.board/"/board/b/{name}" + ;div + ;img@"https://s3.spandrell.ch/assets/board/boards/{name}.svg"; + ;h4: {name} + == + == +-- diff --git a/desk/web/board/new.hoon b/desk/web/board/new.hoon new file mode 100644 index 0000000..ed71a91 --- /dev/null +++ b/desk/web/board/new.hoon @@ -0,0 +1,299 @@ +/- *boke, cnt=contact, tp=trill-post +/+ ui=trill-ui, kaji, const=constants, wall +/= user /web/components/user +|_ [tt=tags-table =bowl:gall edit=(unit [thread:tp post:tp])] +++ css ^~ %- trip +''' +#error-div{ + color: red; +} +#new-thread{ + padding: 10px; + margin: auto; + border: 1px solid var(--background-color); + border-radius: 1%; + + & fieldset{ + border: none; + padding: 0; + margin-top: 1rem; + + & label{ + display: block; + } + & input{ + width: 100%; + line-height: 1.5rem; + } + } + + & .board-choice{ + display: flex; + align-items: center; + + & .left{ + width: 20%; + & select{ + background-color: var(--background-color); + height: 2rem; + } + } + & .right{ + margin-left: 2rem; + flex-grow: 1; + } + } + & .author{ + img{ + width:30px; + height:30px; + } + & .name{ + margin-left: 1rem; + } + } + & #bordered{ + border: 1px solid black; + width: 100%; + height: 50vh; + overflow-y: scroll; + & textarea{ + resize: none; + width: 100%; + height: 90%; + border: none; + outline: none; + } + } + & #chips{ + position: relative; + width: 100%; + border: 1px solid rgb(107, 114, 128); + padding: 0 0.5rem; + + & .chip{ + display: inline-block; + font-size: 13px; + font-weight: 500; + color: rgba(0, 0, 0, 0.6); + line-height: 26px; + padding: 0 12px; + border-radius: 16px; + background-color: #e4e4e4; + cursor: pointer; + margin: 0 2px; + } + & .selected-chip{ + background-color: rgb(100, 100, 100); + } + & .chip:hover{ + background-color: rgb(100, 100, 100); + } + + & input{ + border: 0; + display: inline-block; + font-size: 16px; + height: 2rem; + line-height: 32px; + outline: 0; + margin: 0; + padding: 0 !important; + } + } +} +@media (min-width: 800px){ + #new-thread{ + width: 70%; + } + +} +@media (max-width: 800px){ + +} +.buttons{ + display: flex; + gap: 0.5rem; + justify-content: end; + align-items: center; + +} +#poll-button{ + border: none; + background: url('https://s3.spandrell.ch/assets/board/ui/poll.svg'); + background-size: cover; + width: 30px; + height: 30px; +} +''' +++ script +=/ json-tags=json %- pairs:enjs:format + %+ turn ~(tap by tt) |= [tag=@t p=(list *)] + :+ tag %n (scot %ud (lent p)) +=/ json-string %- trip %- en:json:html json-tags +^~ %+ weld +""" +const jsonString = '{json-string}'; +const allTags = JSON.parse(jsonString); + +""" +%- trip +''' +console.log(allTags, "tags") +const tagInput = document.getElementById("tag-input"); +const tagStore = document.getElementById("tag-store"); +const chipContainer = document.getElementById("chips"); +tagInput.addEventListener("keypress", handleKey); +tagInput.addEventListener("keyup", handleBackspace); + +const chipList = new Set(); + +function init(){ + setTimeout(() => { + console.log(tagStore.value, "tagstore") + const initTags = tagStore.value.split(","); + for (let tag of initTags){ + if (tag){ + chipList.add(tag); + loadChip(tag); + } + } + }, 500) +} +init(); + +function chipString(){ + const string = [...chipList].reduce((acc, i) => `${acc},${i}`); + // const string = JSON.stringify(Array.from(chipList)); + tagStore.value = string; +} +function addChip(itag){ + const tag = itag.toLowerCase(); + if (!chipList.has(tag) && tag.length > 0 && tag.length < 32) { + chipList.add(tag); + this.loadChip(tag) + tagInput.value = ""; + chipString(); + } +} +function removeChip(e){ + chipList.delete(e.target.innerText); + chipContainer.removeChild(e.target); + chipString(); +} +function loadChip(tag) { + const c = document.createElement('div'); + c.classList.add("chip"); + c.id = tag; + c.innerText = tag; + c.addEventListener("click", removeChip) + chipContainer.insertBefore(c, tagInput); +} +function handleBackspace(e){ + console.log(e.keyCode, "keyup") + console.log(e.target.selectionStart, "keyup") + if (!(e.keyCode === 8 && e.target.selectionStart == 0)) return + const chips = chipContainer.querySelectorAll('.chip'); + if (chips.length === 0) return + const to_delete = chipContainer.querySelector('.selected-chip'); + + if (to_delete) removeChip({target: to_delete}); + else { + const last_chip = chips[chips.length - 1]; + last_chip.classList.add("selected-chip"); + } +} +function handleKey(e) { + // autocomplete? + console.log(e.keyCode, "keycode") + console.log(e) + if (e.keyIdentifier == 'U+000A' || e.keyIdentifier == 'Enter' || e.keyCode == 13) e.preventDefault(); + if (e.keyCode === 13) addChip(e.target.value); +} + + +''' +++ $ ^- manx +=/ [stitle=tape stags=tape stext=tape] ?~ edit ["" "" ""] +:+ (trip title.-.u.edit) + (tags-to-tape:ui tags.-.u.edit) + (content-to-md:ui contents.+.u.edit) +=/ wal ~(. wall src.bowl) +=/ cats boards:wal +=/ whoms (get-contacts:cnt bowl) +=/ usr (user src.bowl whoms 30) +=/ subscription (subscription-type:wal now.bowl) +;div#new-thread.fsy + ;style:"{css}" + ;+ ?~ edit + ;h1.tc:"New Thread" + ;h1.tc:"Edit Thread" + ;form + =kaji "poke" + =action "add-thread" + ;fieldset + ;label:"Title" + ;input(type "text", name "title", value stitle); + == + ;fieldset.board-choice + ;label.left + ;div:"Board" + ;select + =name "board" + ;* %+ turn %- sort :_ aor ~(tap in cats) |= nam=@t + =/ name (trip nam) + ?: .=(nam 'public') + ;option(value name, selected ""):"{name}" + ;option(value name):"{name}" + == + == + ;label.right + ;div:"Tags" + ;div#chips + ;input(type "text", placeholder "press enter to input individual tags, click on a tag to delete", enterkeyhint "enter", id "tag-input"); + ;input(type "hidden", id "tag-store", name "tags", value stags); + == + == + == + ;fieldset + ;label:"Text" + ;div#bordered + ;textarea(name "text") + ; {stext} + == + == + == + ;div.f1 + ;div.author.flex + ;div:"Posting as" + ;div.flex.ml1 + ;+ avatar.usr + ;+ name.usr + == + == + ;input(type "hidden", name "error-div", value "#error-div"); + ;span#error-div; + ;div.buttons + ;* ?~ subscription ~ + ;= ;button#poll-button(type "button") + =kaji "scry" + =swap "add" + =cont "#bordered" + =path "/board/f/poll/new" + ; + == + ;input#upload-img@"https://s3.spandrell.ch/assets/board/octicons/image-24.svg"(type "image"); + == + ;* ?~ edit ;+ ;input(type "submit", value "Submit"); + ;= ;input(type "submit", value "Edit"); + ;input(type "hidden", name "editing") + =value (enc:kaji pid.-.u.edit) + ; + == + == + == + == + == + ;script:"{script}" +== +-- diff --git a/desk/web/board/router.hoon b/desk/web/board/router.hoon new file mode 100644 index 0000000..48c72a9 --- /dev/null +++ b/desk/web/board/router.hoon @@ -0,0 +1,281 @@ +/- boke, cnt=contact, tp=trill-post +/+ kaji, fetch-lib=fetch, plib=trill-utils, const=constants, sr=sortug, lib=boke, ui=trill-ui +/= index /web/index +/= board /web/board/index +/= mobile /web/board/mobile-index +/= thread-page /web/board/thread +/= new-thread /web/board/new +/= nag /web/nag +/= poll /web/components/poll + +|_ [rl=req-line:kaji s=state:boke =bowl:gall] + +* fetch ~(. fetch-lib [s bowl]) + b ~(. board [s bowl mob.rl]) + :: + ++ eyre-bail (error-response:kaji 404) + ++ manx-bail (error-page:kaji 404) +:: +++ $ ^- eyre-res:kaji + =/ p ?. mob.rl pat.rl [%m pat.rl] + + ?: ?=([%m ~] p) [%page (index ~[(mobile s bowl)] bowl)] + ?: ?=([%m %ff rest=*] p) [%html (mobile-fragment rest.p)] + =. p ?: ?=([%m rest=*] p) ((pole knot) rest.p) p + ?: ?=([%f rest=*] p) [%html (fragment rest.p)] + :- %page + %- index :_ bowl :_ ~ + ?+ p manx-bail + :: boards + ~ (main par.rl) + :: these two mostly for mobile + [%t tag=@t ~] (tag-index tag.p) + [%b cat=@t ~] (board-index cat.p) + [%all ~] all-posts + :: threads + [%n ~] (new-thread tags.s bowl ~) + [%edit uid=@t ~] (edit-thread uid.p) + [%blog rest=*] (blog-as-thread rest.p) + [%p %esc uid=@t ~] (board-thread-no-path uid.p) + [tag=@t title=@t rest=*] (board-thread tag.p title.p rest.p) + :: [%p rest=*] (board-thread rest.p) + == +++ fragment + |= p=(pole knot) + ?+ p ~& no-fragment=p manx-bail + :: fetching categories + [%boards ~] (w category-list:b) + [%tags ~] (w (make-tag-list:b (scag 21 (tags-by-size:lib tags.s src.bowl)))) + [%tag-search ~] (w tag-search) + :: fetching posts + [%page type=@t name=@t cursor=@t ~] (post-page type.p name.p cursor.p *_par.rl) + [%search type=@t name=@t cursor=@t ~] (post-page type.p name.p cursor.p par.rl) + :: + :: fetching replies of thread + [%replies thread=@t cursor=@t ~] (thread-replies thread.p cursor.p) + [%snip uid=@t ~] (post-snippet uid.p) + [%edit uid=@t ~] (edit-box uid.p) + [%poll %new ~] new-poll + == +++ mobile-fragment + |= p=(pole knot) + ?+ p manx-bail + [%boards ~] (w category-list:mobile) + [%tags ~] (w (make-tag-list:b (scag 33 (tags-by-size:lib tags.s src.bowl)))) + [%tag-search ~] + =/ uq (~(get by par.rl) 'query') + %- w + ;* ?~ uq :~(;span:"No query") + %+ turn (tag-search:fetch u.uq) tag-preview:b + [%snip uid=@t ~] (post-snippet uid.p) + [%edit uid=@t ~] (edit-box uid.p) + == + + + +++ w wrap-marl:kaji +:: Boards +++ main +|= params=(map @t @t) + =/ post-list + =/ tag (~(get by params) 't') + ?^ tag (post-page 't' u.tag '0' ~) + =/ brd (~(get by params) 'b') + ?^ brd (post-page 'b' u.brd '0' ~) + (post-page '' '' '0' ~) + + (main.b post-list) + +++ tag-search ^- marl + =/ uq (~(get by par.rl) 'query') + ;* ?~ uq :~(;span:"No query") + %+ turn (tag-search:fetch u.uq) tag-preview:b + + ++$ rq $%([%latest ~] [%board name=@t] [%tag name=@t]) + +++ tag-index +|= tag=@t + %- post-page + :^ 't' tag '0' ~ +++ board-index +|= cat=@t + %- post-page + :^ 'b' cat '0' ~ +++ all-posts + %- post-page + :^ 'l' '' '0' ~ + +++ post-page + |= [stype=@t name=@t scursor=@t params=(map @t @t)] + =/ contacts (get-contacts:cnt bowl) + =/ older=(unit @) ?: .=('0' scursor) ~ (slaw:parsing:sr %uw scursor) + =/ =rq + ?: .=(stype 't') [%tag name] + ?: .=(stype 'b') [%board name] + [%latest ~] + + =/ query (~(get by params) 'query') + =/ sort-by (~(get by params) 'sort') + =/ page-req [~ older board-page-size:const] + =/ filter ?~ query + %- some |= =thread:tp + !(~(has in tags.thread) 'blog') + %- some |= =thread:tp + ?: (~(has in tags.thread) 'blog') .n + (cfind:sr u.query title.thread .n) + =/ =tpage:tp + ?- -.rq + %latest (active-thread-page-all:fetch page-req filter) + %tag (active-thread-page-by-tags:fetch ~[name] page-req filter) + %board (active-thread-page-by-board:fetch name page-req filter) + == + ?: .=('0' scursor) + (post-index:b tpage rq contacts query mob.rl) + (previews:b tpage rq contacts query mob.rl) +:: Threads + +++ edit-thread |= uid=@t + =/ op (thread-by-hash:fetch uid) + ?~ op manx-bail + (new-thread tags.s bowl op) +++ board-thread-no-path + |= uid=@t + =/ post (post:fetch uid) + ?~ post manx-bail + (serve-thread u.post) + +++ blog-as-thread + |= p=path + =/ op (by-path:fetch p) + ?~ op ~& board-blog-path-not-found=p manx-bail + =/ is-op .=(thread.u.op [author.u.op id.u.op]) + ?. is-op manx-bail (serve-thread u.op) + +++ serve-thread +|= =post:tp + =/ ted (get:torm:tp threads.s thread.post) + ?~ ted ~& board-ted-not-found=rl manx-bail + :: =/ fn (node-to-full:plib post feed.s) + =/ contacts (get-contacts:cnt bowl) + :: =/ post-pid=pid:tp [author.u.post id.u.post] + :: ?~ parent.post + =/ =cpage:tp (thread-children:fetch u.ted) + =. p.cpage [post p.cpage] + ~& >> cpage=+.cpage + (thread-page u.ted cpage ~ ~ contacts s bowl mob.rl) +++ thread-replies +|= [ts=@t cs=@t] + =/ contacts (get-contacts:cnt bowl) + =/ thread (dec:kaji ts pid:tp) + ?~ thread manx-bail + =/ cursor (dec:kaji cs @ud) + ?~ cursor manx-bail + =/ ted (get:torm:tp threads.s u.thread) + ?~ ted manx-bail + ?~ replies.u.ted manx-bail + =/ =cpage:tp (older-children:fetch u.ted +(u.cursor)) + =< inline + %= thread-page + thread u.ted + children cpage + focus (some i.replies.u.ted) + search ~ + contacts contacts + state s + bowl bowl + is-mobile mob.rl + == + +++ board-thread + |= [tag=@t title=@t rest=(pole knot)] + =/ post (by-path:fetch [tag title ~]) + ?~ post ~& board-path-not-found=rl cant-show + (serve-thread u.post) + + ++ cant-show html:nag + + :: =/ children (flatten-fn-2:plib fn) + :: =/ req [~ ~ thread-page-size:const] + :: =/ children (get-children-page:plib fn req) + :: ~& children=+.children + + :: =/ par (get:gorm:tp feed.s u.parent.post) + :: ?~ par ~& par-not-found=rl + :: =/ children (bulk-post-by-pid:fetch replies.u.ted) + :: =/ =spage:tp [children ~ ~ (lent children)] + :: (thread-page title.u.ted contents.u.post spage ~ ~ contacts bowl mob.rl) + :: =/ siblings (get-siblings-page-2:plib u.par) + :: (thread-page title.u.ted contents.u.post siblings `post-pid ~ contacts bowl mob.rl) + +++ edit-box + |= uid=@t + =/ up (post:fetch uid) + ?~ up not-found + =/ tag-string (tags-to-tape:ui tags.u.up) + =/ post-text (content-to-md:ui contents.u.up) + =/ action ?~ parent.u.up "add-post" "add-reply" + ;form.content.edit-box + =kaji "poke" + =action action + ;input(type "hidden", name "editing", value (trip uid)); + ;input(type "hidden", name "error-div", value ".error-div"); + ;input(type "hidden", name "title", value (trip title.u.up)); + ;* ?~ parent.u.up ~ =/ p-string (enc:kaji [thread.u.up u.parent.u.up]) + ;+ ;input(type "hidden", name "parents", value p-string); + ;textarea + =name "text" + ; {post-text} + == + :: ;label + :: ; Tags + :: ;input(type "text", name "tags", value tag-string); + :: == + ;div.buttons + ;input + =type "submit" + =value "Save" + ; + == + ;button + =kaji "poke" + =action "del-reply" + =name "pid" + =payload (trip uid) + ; Delete + == + ;div.error-div; + == + == +++ post-snippet + |= uid=@t + =/ up (post:fetch uid) + ?~ up not-found + =/ contacts (get-contacts:cnt bowl) + =/ t thread-page(bowl bowl, state s, contacts contacts) + (snippet:t u.up) + + +++ not-found +;div + ; Post not found +== + +++ new-poll (form:poll bowl) + + +:: ++ serve-tags +:: |= tags=path +:: =/ postl (search-filter-full:plib feed.s [~ ~ board-page-size:const] (tag-search:plib tags)) +:: =/ tag-string %^ foldi:sr tags "" |= [i=@ud t=@t acc=tape] +:: ?: .=(i (dec (lent tags))) +:: "{acc}{(trip t)}" +:: "{acc}{(trip t)}, " +:: =/ page +:: ;div#tag-search +:: ;header +:: ;h2.tc: Tag Search: {tag-string} +:: == +:: ;+ (html:post-list postl) +:: == +:: (index ~[title page] bowl) +-- diff --git a/desk/web/board/thread.hoon b/desk/web/board/thread.hoon new file mode 100644 index 0000000..98811f8 --- /dev/null +++ b/desk/web/board/thread.hoon @@ -0,0 +1,807 @@ +/- boke, tp=trill-post, cons=contact +/+ sr=sortug, plib=trill-utils, ui=trill-ui, sigil=sigil-sigil, lib=boke, kaji, constants, wall +/= post-text /web/components/post-text +/= user /web/components/user +|_ [=thread:tp children=cpage:tp focus=(unit pid:tp) search=(unit @t) contacts=whoms:cons =state:boke =bowl:gall is-mobile=?] ++* wal ~(. wall src.bowl) + pt ~(. post-text [state bowl]) +++ css ^~ %- trip +''' +body>main{ + width: 100%!important; +} +@media (max-width: 800px){ + body>main{ + padding: 0!important; + } +} +#board-thread{ + max-width: 1280px; + margin: auto; + +/* base styles */ + & header{ + margin-top: 1rem; + & img{ + width: 20px; + margin-left: 1rem; + } + } + + & p{ + font-size: 1.1rem; + } + +} + +.reply{ + display: grid; + border: 1px solid silver; + + & .author{ + + & .avatar{ + max-height: 100px; + max-width: 64px; + display: block; + margin: auto; + } + & .name{ + font-weight: 500; + color: var(--hong); + text-align: center; + word-break: break-word; + } + } + + & .metadata { + & p{ + margin: -10px 0 0 0; + font-size: 14px; + } + & .timestamps span{ + margin-right: 0.3rem; + } + & .buttons{ + display: flex; + & img{ + height: 20px; + width: 20px; + margin: 5px 5px; + cursor: pointer; + } + } + + } + + & .content{ + text-align: left: + word-wrap: break-word; + + & img{ + max-width: 100%; + } + + & p{ + padding: 0.5rem; + font-weight: 400; + overflow: hidden; + word-break: break-word; + white-space: normal; + margin: 1rem 0; + } + & blockquote{ + margin: 1rem 2rem 1rem 1rem; + padding: 0.4rem; + border: 1px solid grey; + border-radius: 2%; + } + & .youtube-frame{ + display: block; + margin: auto; + width: 70%; + min-height: 30vw; + } + } + & footer{ + width: 100%; + padding: 0 0.5rem; + .tags{ + padding: 0; + font-size: 0.9rem; + flex-wrap: wrap; + + a{ + margin-left: 0.1rem; + } + } + } + +} + + + + /* desktop */ + + @media (min-width: 1000px){ + #posts{ + display: flex; + } + #replies{ + width: 83.33%; + } + } + @media(min-width: 800px){ + + #sidebar{ + position: sticky; + top: 4rem; + right: 0; + align-self: flex-start; + + & button, & .button{ + display: block; + margin: 1rem auto; + width: 100%; + } + } + + .reply{ + padding: 1rem; + padding-left: 0.4rem; + margin: 1rem; + grid-template-columns: 10% 90%; + grid-template-rows: 40px auto; + + & .author{ + grid-column-start: 1; + grid-column-end: 2; + grid-row-start: 1; + grid-row-end: 3; + } + + & .metadata{ + display: flex; + justify-content: space-between; + border-bottom: 1px solid grey; /* TODO darkmode */ + grid-column-start: 2; + grid-column-end: 3; + grid-row-start: 1; + grid-row-end: 2; + } + + & .content{ + grid-column-start: 2; + grid-column-end: 3; + grid-row-start: 2; + grid-row-end: 3; + + & img{ + margin: 0 auto; + max-height: 300px; + } + } + } + } + /* mobile */ + @media(max-width: 800px){ + & header{ + padding: 0 1rem; + & h1{ + font-size: 1.8rem; + } + } + #replies{ + margin-bottom: 3rem; + } + + #posts{ + display: block; + width: 100%; + + & > footer{ + background-color: black; + display:flex; + justify-content: space-evenly; + width: 100vw; + position: fixed; + bottom: 0; + height: 3rem; + + & button{ + height: 3rem; + } + } + & .replies{ + margin-bottom: 3rem; + } + } + + .reply{ + padding-top: 10px; + grid-template-columns: 100px auto; + grid-template-rows: 80px auto; + + & .author{ + grid-column-start; 1; + grid-column-end: 2; + grid-row-start: 1; + grid-row-end: 2; + + & .avatar{ + height: 60px; + width: 60px; + display: block; + margin: 0 auto; + } + & .name{ + font-size: 0.9rem; + } + } + & .metadata{ + grid-column-start: 2; + grid-column-end: 3; + grid-row-start: 1; + grid-row-end: 2; + padding: 0 0.2rem; + border-bottom: 1px solid grey; + + /* & .date{ + display: flex; + justify-content: space-between; + align-items: center; + }*/ + .updated-date{ + display: none; + } + + & .buttons{ + width: 100%; + justify-content: space-between; + margin-top: 0.5rem; + } + } + & .content{ + grid-column-start: 1; + grid-column-end: 3; + grid-row-start: 2; + grid-row-end: 3; + + & img{ + max-height: 300px; + } + + } + } + } + + +} + +#page-nav{ + .button{ + width: 30px; + height: 30px; + } +} +.icon-button{ + padding: 0.2rem; + & img{ + width: 20px; + } +} +.replying{ + margin: 1rem; + padding: 0.5rem; + width: fit-content; + cursor: pointer; + background-color: rgb(100, 100, 100, 0.3); + border-radius: 0.5rem; + & span{ + margin: 0 0.4rem; + } +} +.edit-box{ + display: block; + & textarea{ + display: block; + width: 100%; + height: 200px; + outline: none; + resize: none; + } + & label{ + margin-right: 1rem; + } +} +.reply-box{ + & .bordered{ + border: 1px solid black; + & blockquote{ + margin:0; + padding: 0.3rem; + border-bottom: 1px solid grey; + & .content{ + color: green; + white-space: pre-wrap; + } + & .content::before{ + content: "> "; + } + } + & .quoting-author{ + display: flex; + gap: 1ch; + } + & textarea{ + resize: none; + display: block; + border: none; + outline: none; + width: 100%; + } + } + & .reply-buttons{ + margin-top: 0.8rem; + display: flex; + gap: 0.5rem; + justify-content: end; + align-items: center; + + & input[type=image]{ + width: 30px; + } + + } +} +/* poll */ +/* base */ + +.poll{ + border: 1px solid black; + margin: 1rem auto; + & .bottom-row{ + height: 1rem; + } + & .top-row{ + position: relative; + height: 2rem; + & h6{ + position: absolute; + left: 1%; + font-size: 1.1rem; + } + & h3{ + position: absolute; + left: 50%; + transform: translateX(-50%); + font-size: 1.4rem; + max-width: 65%; + } + } + & .poll-type{ + font-size: 0.7rem; + float: left; + } + & .expiry{ + font-size: 0.8rem; + float: right; + } + & .poll-option{ + position: relative; + border: 2px solid grey; + width: 80%; + height: 2rem; + margin: 0.3rem auto 0.3rem auto; + & .inner{ + padding: 0 0.5rem; + z-index: 20; + } + & .poll-option-color{ + z-index: 19; + position: absolute; + top: 0; + left: 0; + background-color: var(--huang); + opacity: 0.6; + height: 100%; + } + } + & .poll-option-name{ + max-width: 75%; + } + + & .poll-option.open:hover{ + border:2px solid green; + cursor: pointer; + } + & .poll-option.not-voted{ + cursor: pointer; + } + & .poll-option.voted{ + border: 2px solid blue; + } +} +@media (min-width: 800px){ + .poll{ + width: 50%; + padding: 1rem; + } + +} +@media (max-width: 800px){ + .poll{ + width: 80%; + padding: 0.3rem; + } + +} +#poll-button{ + border: none; + background: url('https://s3.spandrell.ch/assets/board/ui/poll.svg'); + background-size: cover; + width: 40px; + height: 40px; +} +.copied{ + border: 2px solid green; + border-radius: 50%; +} +''' +++ thread-js ^~ %- trip +''' +function threadJS(){ + const buttons = document.querySelectorAll(".buttons"); + buttons.forEach(el => { + const parent = el.closest(".reply"); + if (!parent) return + const cb = el.querySelector(".copy-button") + + cb.addEventListener("click", () => { + const bp = `https://spandrell.ch/board` + const path = document.getElementById("board-thread").getAttribute("path"); + navigator.clipboard.writeText(bp + path).then(res => { + console.log(res, "write to clipboard") + cb.classList.add("copied"); + setTimeout(() => cb.classList.remove("copied"), 1000) + }).catch(er => { + console.log(er, "couldn't write to clipboard") + }) + }) + + const db = el.querySelector(".down-button") + db.addEventListener("click", () => { + parent.querySelector("footer").scrollIntoView(); + }) + }); + const up = document.querySelector("#go-up") + up.addEventListener("click", () => { + document.querySelector("#top").scrollIntoView(); + }) + const down = document.querySelector("#go-down") + down.addEventListener("click", () => { + document.querySelector("#bottom").scrollIntoView(); + }) +} + +function scanTweets(){ + const nodes = document.querySelectorAll(".parsed-twatter"); + for (let n of nodes){ + const status = n.getAttribute("status"); + twttr.widgets.createTweet(status, n.parentElement, { + conversation: true + }) + } +} +window.addEventListener('load', () => { + scanTweets(); + threadJS(); +}) +function composerJS(){ + const upl = document.getElementById("upload-img"); + upl.addEventListener("click", (e) => { + e.preventDefault(); + e.stopPropagation(); + }) + +} +''' +++ $ ^- manx +:: =/ index (mul (dec page) board-page-size:constants) +:: =/ is-op .=(thread.p.fn [author.p.fn id.p.fn]) +=/ is-op .y +=/ author=@p ship.pid.thread +=/ path-string (trip (spat path.thread)) +;div#board-thread.fsy + =path path-string + ;div#top; + ;style:"{css}" + ;header.fxc + :: ;+ ?: is-op + ;h1.tc:"{(trip title.thread)}" + :: ;h3.tc:"From {(trip title.p.fn)}" + ;* ?. ?|((is-admin:lib src.bowl) .=(author src.bowl)) ~ + :~(edit-button delete-button) + == + ;div#posts + ;+ inline + ;+ ?. is-mobile sidebar mobile-footer + == + ;script:"{thread-js}" +== +++ inline + ;div#replies + :: ;* (cursor newer.children "Newer" search) + ;* %+ mapi:sr p.children thread-child + ;* (cursor bot.children "Older" search) + == +++ cursor +|= [cursor=@ud label=tape search=(unit @t)] ^- marl + =/ len (lent replies.thread) + ?: (gte +(cursor) len) ~ + =/ thread-path (enc:kaji pid.thread) + =/ cursor-path (enc:kaji cursor) + =/ path ?~ search + "/board/f/replies/{thread-path}/{cursor-path}" + "/board/f/replies/{thread-path}/{cursor-path}?query={(trip u.search)}" + + ;+ ;div.cursor(kaji "iscroll", path path, cont "#replies", where "bottom") + ;button:"{label} posts" + == + +++ edit-button +=/ pid-string (enc:kaji pid.thread) +;a/"/board/edit/{pid-string}" + ;img@"https://s3.spandrell.ch/assets/board/ui/edit.svg"; +== +++ delete-button +=/ pid-string (enc:kaji pid.thread) +;a + =kaji "poke" + =action "del-thread" + =name "pid" + =payload pid-string + ;img@"https://s3.spandrell.ch/assets/board/ui/delete.svg"; +== + +++ thread-child +|= [post-index=@ud =post:tp] ^- manx +:: =/ base-index (mul (dec page) board-page-size:constants) +:: =/ pind (add base-index +(post-index)) +=/ post-index (add post-index top.children) +=/ usr ?: is-mobile + (user author.post contacts 60) + (user author.post contacts 64) +=/ pid-string (enc:kaji [author.post id.post]) +=/ jammed-parents %- enc:kaji [op=pid.thread parent=[author.post id.post]] +;div.reply(id pid-string) + ;div.author + ;+ avatar.usr + ;+ name.usr + == + ;div.metadata + ;+ (date post post-index) + ;* ?: .=(post-index (dec (lent p.children))) ;+ ;div#bottom; ~ + ;+ (buttons post pid-string) + == + ;div.content + ;* (quote post) + ;* (content:pt contents.post) + ;* ?: .=(post-index 0) ;+ (footer post) ~ + == + ;+ (reply-box post name.usr jammed-parents) +== +++ quote +|= p=post:tp ^- marl +?~ parent.p ~ +?: .=(u.parent.p thread.p) ~ +=/ usr (user ship.u.parent.p contacts 0) +;+ ;div.replying.flex + =kaji "modal" + =path "/board/f/snip/{(enc:kaji u.parent.p)}" + ;span:"Replying to:" + ;+ name.usr + == + + +++ date +|= [=post:tp post-index=@ud] +=/ date-string (datetime-to-tape:string:sr id.post "-") +=/ posted ;span.posted-date:"Posted: {date-string}" +=/ last-edit=time key:head:(pop:corm:tp contents.post) +=/ =marl ?: .=(last-edit id.post) posted^~ +;= ;+ posted + ;span.updated-date:"Edited: {(datetime-to-tape:string:sr last-edit "-")}" +== +;div.date + ;p.timestamps + ;* marl + == + ;p.meta + ;span:"Post #{(scow:parsing:sr %ud +(post-index))}/{<+((lent replies.thread))>}" + ;span.engagement:"" + == +== +++ buttons +|= [=post:tp id=tape] +:: =/ votes %+ roll ~(tap by reacts.engagement.post) |= [[s=@p [r=@t *]] acc=@] +:: ?: ?|(=(r "thumbsup") =(r "👍")) +;div.buttons + ;* ?. ?|((is-admin:lib src.bowl) .=(author.post src.bowl)) ~ + ;+ ;img@"https://s3.spandrell.ch/assets/board/ui/edit.svg" + =kaji "scry" + =path "/board/f/edit/{id}" + =swap "swap" + =where "outer" + =targ "[id=\"{id}\"] .content" + ; + == + ;img.copy-button@"https://s3.spandrell.ch/assets/board/ui/copy.svg"; + ;img + =src "https://s3.spandrell.ch/assets/board/ui/upvote.svg" + ; + == + ;img@"https://s3.spandrell.ch/assets/board/ui/downvote.svg" + ; + == + ;img.down-button@"https://s3.spandrell.ch/assets/board/ui/down.svg"; + ;img@"https://s3.spandrell.ch/assets/board/ui/reply.svg" + =kaji "toggle" + =targ ".reply-box" + =modal "1" + ; + == + ;img.collapse-button + =src "https://s3.spandrell.ch/assets/board/ui/collapse.svg" + =kaji "toggle" + =targ ".content/.avatar/.uncollapse-button/.collapse-button" + ; + == + ;img.uncollapse-button + =src "https://s3.spandrell.ch/assets/board/ui/uncollapse.svg" + =kaji "toggle" + =targ ".content/.avatar/.uncollapse-button/.collapse-button" + =hidden "" + ; + == +== +++ reply-box +|= [parent=post:tp author=manx jammed-parents=tape] ^- manx +=/ parent-text=tape (content-to-md:ui contents.parent) +=/ snip ?: (gth (lent parent-text) 150) + "{(scag 150 parent-text)}..." + parent-text +=/ error-div "err{jammed-parents}" +=/ subscription (subscription-type:wal now.bowl) +:: %- serve-modal:kaji :_ ~ +;div.reply-box + =hidden "" + ;form + =kaji "poke" + =action "add-reply" + ;div.bordered + ;blockquote + ;div.quoting-author + ;span:"Replying to" + ;+ author + == + ;div.content:"{snip}" + == + ;textarea + =name "text" + =rows "10" + =placeholder "Your reply" + ; + == + == + ;input(type "hidden", name "parents", value jammed-parents); + ;input(type "hidden", name "title", value ""); + ;input(type "hidden", name "error-div", value "#{error-div}"); + ;span.error-div(id error-div); + ;div.reply-buttons + ;* ?~ subscription ~ + ;= ;button#poll-button(type "button") + =kaji "scry" + =swap "add" + =cont ".bordered" + =path "/board/f/poll/new" + ; + == + ;input#upload-img@"https://s3.spandrell.ch/assets/board/octicons/image-24.svg"(type "image"); + == + ;input(type "submit", value "Send"); + == + :: == + == +== +++ footer +|= =post:tp +=/ count ~(wyt in tags.post) +=/ tags ?. (gth count 0) ;span; +=/ tag-links +%+ mapi:sr ~(tap in tags.post) + |= [i=@ t=@t] ^- manx =/ tt (trip t) =/ path "/board?t={tt}" + =/ show ?: .=(i (dec count)) tt "{tt}, " + ;a/"{path}":"{show}" +;div.tags.flex + ; Tags: + ;* tag-links +== +;footer + ;+ tags +== + + ++ mobile-footer + =/ blog-path (trip (spat (blog-path:lib thread))) + ;footer + ;a/"{blog-path}" + ;button:"Blog view" + == + ;button#go-up:"Up" + ;button#go-down:"Down" + ;button + =kaji "toggle" + =targ ".reply-box" + =modal "1" + ; Reply + == + == + + ++ sidebar + :: =/ from +((mul (dec page) board-page-size:constants)) + :: =/ to + :: =/ added (add from board-page-size:constants) + :: ?: (gth added (lent children)) (lent children) added + :: =/ froms (number:string:sr from) + :: =/ tos (number:string:sr to) + =/ post-count (number:string:sr (lent p.children)) + =/ blog-path (trip (spat (blog-path:lib thread))) +:: =/ max-page +((div (lent children) board-page-size:constants)) + :: =/ page-count %- number:string:sr max-page + :: =/ prev-page %- number:string:sr ?: .=(page 1) 1 (dec page) + :: =/ curr-page (number:string:sr page) + :: =/ next-page %- number:string:sr ?: .=(page max-page) page +(page) + ;div#sidebar + :: ;div#search-box + :: ;form.flex + :: ;input(name "thread-search", placeholder "Search on Thread"); + :: ;button.icon-button + :: ;img@"https://s3.spandrell.ch/assets/board/ui/search.svg"(type "image"); + :: == + :: == + :: == + ;a/"{blog-path}" + ;button:"Blog view" + == + ;button#go-up:"Go To Top" + ;button#go-down:"Go To Bottom" + ;button + =kaji "toggle" + =targ ".reply-box" + =modal "1" + ; Reply + == + == +++ snippet +|= =post:tp ^- manx +=/ date-string (datetime-to-tape:string:sr id.post "-") +=/ usr (user author.post contacts 64) +;div.snippet.reply + ;div.author + ;+ avatar.usr + ;+ name.usr + == + ;div.metadata + ;div.date + ;p.timestamps + ;span.posted-date:"Posted: {date-string}" + == + == + == + ;div.content + ;* (quote post) + ;* (content:pt contents.post) + :: ;+ (footer post) + == +== +-- |