/- 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) == == --