| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525 |
- "use strict";
- const path = require("path");
- const fs = require("fs");
- const envPaths = require("../server/node_modules/env-paths");
- const debug = require("../server/node_modules/debug")("oasis");
- const highlightJs = require("../server/node_modules/highlight.js");
- const prettyMs = require("../server/node_modules/pretty-ms");
- const moment = require('../server/node_modules/moment');
- const { renderUrl } = require('../backend/renderUrl');
- const ssbClientGUI = require("../client/gui");
- const config = require("../server/ssb_config");
- const cooler = ssbClientGUI({ offline: config.offline });
- let ssb, userId;
- const getUserId = async () => {
- if (!ssb) ssb = await cooler.open();
- if (!userId) userId = ssb.id;
- return userId;
- };
- const { a, article, br, body, button, details, div, em, footer, form, h1, h2, h3, head, header, hr, html, img, input, label, li, link, main, meta, nav, option, p, pre, section, select, span, summary, textarea, title, tr, ul, strong, video: videoHyperaxe, audio: audioHyperaxe } = require("../server/node_modules/hyperaxe");
- const lodash = require("../server/node_modules/lodash");
- const markdown = require("./markdown");
- // set language
- const i18nBase = require("../client/assets/translations/i18n");
- let selectedLanguage = "en";
- let i18n = {};
- Object.assign(i18n, i18nBase[selectedLanguage]);
- exports.setLanguage = (language) => {
- selectedLanguage = language;
- const newLang = i18nBase[selectedLanguage] || i18nBase['en'];
- Object.keys(i18n).forEach(k => delete i18n[k]);
- Object.assign(i18n, newLang);
- };
- exports.i18n = i18n;
- exports.selectedLanguage = selectedLanguage;
- // markdown
- const markdownUrl = "https://commonmark.org/help/";
- const doctypeString = "<!DOCTYPE html>";
- const THREAD_PREVIEW_LENGTH = 3;
- const toAttributes = (obj) =>
- Object.entries(obj)
- .map(([key, val]) => `${key}=${val}`)
- .join(", ");
-
- const nbsp = "\xa0";
- const { getConfig } = require('../configs/config-manager.js');
- // menu INIT
- const navLink = ({ href, emoji, text, current, class: extraClass }) =>
- li(
- a(
- {
- href,
- class: [current ? "current" : "", extraClass]
- .filter(Boolean)
- .join(" ")
- },
- span({ class: "emoji" }, emoji),
- nbsp,
- text
- )
- );
- const customCSS = (filename) => {
- const customStyleFile = path.join(
- envPaths("oasis", { suffix: "" }).config,
- filename
- );
- try {
- if (fs.existsSync(customStyleFile)) {
- return link({ rel: "stylesheet", href: filename });
- }
- } catch (error) {
- return "";
- }
- };
- const navGroup = ({ id, emoji, title, defaultOpen = false }, ...items) =>
- li(
- { class: "oasis-nav-group" },
- input({
- type: "checkbox",
- id: `oasis-nav-group-${id}`,
- class: "oasis-nav-toggle",
- ...(defaultOpen ? { checked: true } : {})
- }),
- label(
- { for: `oasis-nav-group-${id}`, class: "oasis-nav-header" },
- span({ class: "emoji" }, emoji),
- nbsp,
- title,
- span({ class: "oasis-nav-arrow" }, "▾")
- ),
- ul({ class: "oasis-nav-list" }, ...items)
- );
- const renderPopularLink = () => {
- const popularMod = getConfig().modules.popularMod === "on";
- return popularMod
- ? navLink({
- href: "/public/popular/day",
- emoji: "⌘",
- text: i18n.popular,
- class: "popular-link enabled"
- })
- : "";
- };
- const renderTopicsLink = () => {
- const topicsMod = getConfig().modules.topicsMod === "on";
- return topicsMod
- ? navLink({
- href: "/public/latest/topics",
- emoji: "ϟ",
- text: i18n.topics,
- class: "topics-link enabled"
- })
- : "";
- };
- const renderSummariesLink = () => {
- const summariesMod = getConfig().modules.summariesMod === "on";
- if (summariesMod) {
- return [
- navLink({
- href: "/public/latest/summaries",
- emoji: "※",
- text: i18n.summaries,
- class: "summaries-link enabled"
- })
- ];
- }
- return "";
- };
- const renderLatestLink = () => {
- const latestMod = getConfig().modules.latestMod === "on";
- return latestMod
- ? navLink({
- href: "/public/latest",
- emoji: "☄",
- text: i18n.latest,
- class: "latest-link enabled"
- })
- : "";
- };
- const renderThreadsLink = () => {
- const threadsMod = getConfig().modules.threadsMod === "on";
- if (threadsMod) {
- return [
- navLink({
- href: "/public/latest/threads",
- emoji: "♺",
- text: i18n.threads,
- class: "threads-link enabled"
- })
- ];
- }
- return "";
- };
- const renderInvitesLink = () => {
- const invitesMod = getConfig().modules.invitesMod === "on";
- return invitesMod
- ? navLink({
- href: "/invites",
- emoji: "ꔹ",
- text: i18n.invites,
- class: "invites-link enabled"
- })
- : "";
- };
- const renderWalletLink = () => {
- const walletMod = getConfig().modules.walletMod === "on";
- if (walletMod) {
- return [
- navLink({
- href: "/wallet",
- emoji: "❄",
- text: i18n.wallet,
- class: "wallet-link enabled"
- })
- ];
- }
- return "";
- };
- const renderLegacyLink = () => {
- const legacyMod = getConfig().modules.legacyMod === "on";
- if (legacyMod) {
- return [
- navLink({
- href: "/legacy",
- emoji: "ꖤ",
- text: i18n.legacy,
- class: "legacy-link enabled"
- })
- ];
- }
- return "";
- };
- const renderCipherLink = () => {
- const cipherMod = getConfig().modules.cipherMod === "on";
- if (cipherMod) {
- return [
- navLink({
- href: "/cipher",
- emoji: "ꗄ",
- text: i18n.cipher,
- class: "cipher-link enabled"
- })
- ];
- }
- return "";
- };
- const renderBookmarksLink = () => {
- const bookmarksMod = getConfig().modules.bookmarksMod === "on";
- return bookmarksMod
- ? navLink({
- href: "/bookmarks",
- emoji: "ꔪ",
- text: i18n.bookmarksLabel,
- class: "bookmark-link enabled"
- })
- : "";
- };
- const renderImagesLink = () => {
- const imagesMod = getConfig().modules.imagesMod === "on";
- if (imagesMod) {
- return [
- navLink({
- href: "/images",
- emoji: "ꕥ",
- text: i18n.imagesLabel,
- class: "images-link enabled"
- })
- ];
- }
- return "";
- };
- const renderVideosLink = () => {
- const videosMod = getConfig().modules.videosMod === "on";
- if (videosMod) {
- return [
- navLink({
- href: "/videos",
- emoji: "ꗟ",
- text: i18n.videosLabel,
- class: "videos-link enabled"
- })
- ];
- }
- return "";
- };
- const renderAudiosLink = () => {
- const audiosMod = getConfig().modules.audiosMod === "on";
- if (audiosMod) {
- return [
- navLink({
- href: "/audios",
- emoji: "ꔿ",
- text: i18n.audiosLabel,
- class: "audios-link enabled"
- })
- ];
- }
- return "";
- };
- const renderDocsLink = () => {
- const docsMod = getConfig().modules.docsMod === "on";
- if (docsMod) {
- return [
- navLink({
- href: "/documents",
- emoji: "ꕨ",
- text: i18n.docsLabel,
- class: "docs-link enabled"
- })
- ];
- }
- return "";
- };
- const renderTagsLink = () => {
- const tagsMod = getConfig().modules.tagsMod === "on";
- return tagsMod
- ? [
- navLink({
- href: "/tags",
- emoji: "ꖶ",
- text: i18n.tagsLabel,
- class: "tags-link enabled"
- })
- ]
- : "";
- };
- const renderMultiverseLink = () => {
- const multiverseMod = getConfig().modules.multiverseMod === "on";
- return multiverseMod
- ? navLink({
- href: "/public/latest/extended",
- emoji: "∞",
- text: i18n.multiverse,
- class: "multiverse-link enabled"
- })
- : "";
- };
- const renderMarketLink = () => {
- const marketMod = getConfig().modules.marketMod === "on";
- return marketMod
- ? [
- navLink({
- href: "/market",
- emoji: "ꕻ",
- text: i18n.marketTitle
- })
- ]
- : "";
- };
- const renderJobsLink = () => {
- const jobsMod = getConfig().modules.jobsMod === "on";
- return jobsMod
- ? [
- navLink({
- href: "/jobs",
- emoji: "ꗒ",
- text: i18n.jobsTitle
- })
- ]
- : "";
- };
- const renderProjectsLink = () => {
- const projectsMod = getConfig().modules.projectsMod === "on";
- return projectsMod
- ? [
- navLink({
- href: "/projects",
- emoji: "ꕧ",
- text: i18n.projectsTitle
- })
- ]
- : "";
- };
- const renderBankingLink = () => {
- const bankingMod = getConfig().modules.bankingMod === "on";
- return bankingMod
- ? navLink({
- href: "/banking",
- emoji: "ꗴ",
- text: i18n.bankingTitle
- })
- : "";
- };
- const renderTribesLink = () => {
- const tribesMod = getConfig().modules.tribesMod === "on";
- return tribesMod
- ? [
- navLink({
- href: "/tribes",
- emoji: "ꖥ",
- text: i18n.tribesTitle,
- class: "tribes-link enabled"
- })
- ]
- : "";
- };
- const renderParliamentLink = () => {
- const parliamentMod = getConfig().modules.parliamentMod === "on";
- return parliamentMod
- ? [
- navLink({
- href: "/parliament",
- emoji: "ꗞ",
- text: i18n.parliamentTitle,
- class: "parliament-link enabled"
- })
- ]
- : "";
- };
- const renderCourtsLink = () => {
- const courtsMod = getConfig().modules.courtsMod === "on";
- return courtsMod
- ? navLink({
- href: "/courts",
- emoji: "ꖻ",
- text: i18n.courtsTitle,
- class: "courts-link enabled"
- })
- : "";
- };
- const renderVotationsLink = () => {
- const votesMod = getConfig().modules.votesMod === "on";
- return votesMod
- ? [
- navLink({
- href: "/votes",
- emoji: "ꔰ",
- text: i18n.votationsTitle,
- class: "votations-link enabled"
- })
- ]
- : "";
- };
- const renderTrendingLink = () => {
- const trendingMod = getConfig().modules.trendingMod === "on";
- return trendingMod
- ? [
- navLink({
- href: "/trending",
- emoji: "ꗝ",
- text: i18n.trendingLabel,
- class: "trending-link enabled"
- })
- ]
- : "";
- };
- const renderReportsLink = () => {
- const reportsMod = getConfig().modules.reportsMod === "on";
- return reportsMod
- ? [
- navLink({
- href: "/reports",
- emoji: "ꕥ",
- text: i18n.reportsTitle,
- class: "reports-link enabled"
- })
- ]
- : "";
- };
- const renderOpinionsLink = () => {
- const opinionsMod = getConfig().modules.opinionsMod === "on";
- return opinionsMod
- ? [
- navLink({
- href: "/opinions",
- emoji: "ꔍ",
- text: i18n.opinionsTitle,
- class: "opinions-link enabled"
- })
- ]
- : "";
- };
- const renderTransfersLink = () => {
- const transfersMod = getConfig().modules.transfersMod === "on";
- return transfersMod
- ? [
- navLink({
- href: "/transfers",
- emoji: "ꘉ",
- text: i18n.transfersTitle,
- class: "transfers-link enabled"
- })
- ]
- : "";
- };
- const renderFeedLink = () => {
- const feedMod = getConfig().modules.feedMod === "on";
- return feedMod
- ? navLink({
- href: "/feed",
- emoji: "ꕿ",
- text: i18n.feedTitle,
- class: "feed-link enabled"
- })
- : "";
- };
- const renderPixeliaLink = () => {
- const pixeliaMod = getConfig().modules.pixeliaMod === "on";
- return pixeliaMod
- ? [
- navLink({
- href: "/pixelia",
- emoji: "ꔘ",
- text: i18n.pixeliaTitle,
- class: "pixelia-link enabled"
- })
- ]
- : "";
- };
- const renderForumLink = () => {
- const forumMod = getConfig().modules.forumMod === "on";
- return forumMod
- ? [
- navLink({
- href: "/forum",
- emoji: "ꕒ",
- text: i18n.forumTitle,
- class: "forum-link enabled"
- })
- ]
- : "";
- };
- const renderAgendaLink = () => {
- const agendaMod = getConfig().modules.agendaMod === "on";
- return agendaMod
- ? [
- navLink({
- href: "/agenda",
- emoji: "ꗤ",
- text: i18n.agendaTitle,
- class: "agenda-link enabled"
- })
- ]
- : "";
- };
- const renderAILink = () => {
- const aiMod = getConfig().modules.aiMod === "on";
- return aiMod
- ? [
- navLink({
- href: "/ai",
- emoji: "ꘜ",
- text: i18n.ai,
- class: "ai-link enabled"
- })
- ]
- : "";
- };
- const renderEventsLink = () => {
- const eventsMod = getConfig().modules.eventsMod === "on";
- return eventsMod
- ? [
- navLink({
- href: "/events",
- emoji: "ꕆ",
- text: i18n.eventsLabel,
- class: "events-link enabled"
- })
- ]
- : "";
- };
- const renderTasksLink = () => {
- const tasksMod = getConfig().modules.tasksMod === "on";
- return tasksMod
- ? [
- navLink({
- href: "/tasks",
- emoji: "ꖧ",
- text: i18n.tasksTitle,
- class: "tasks-link enabled"
- })
- ]
- : "";
- };
- const template = (titlePrefix, ...elements) => {
- const currentConfig = getConfig();
- const theme = currentConfig.themes.current || "Dark-SNH";
- const themeLink = link({
- rel: "stylesheet",
- href: `/assets/themes/${theme}.css`
- });
- const nodes = html(
- { lang: "en" },
- head(
- title(titlePrefix, " | Oasis"),
- link({ rel: "stylesheet", href: "/assets/styles/style.css" }),
- themeLink,
- link({ rel: "icon", href: "/assets/images/favicon.svg" }),
- meta({ charset: "utf-8" }),
- meta({ name: "description", content: i18n.oasisDescription }),
- meta({
- name: "viewport",
- content: toAttributes({
- width: "device-width",
- "initial-scale": 1
- })
- })
- ),
- body(
- div(
- { class: "header" },
- div(
- { class: "top-bar-left" },
- a(
- { class: "logo-icon", href: "/" },
- img({
- class: "logo-icon",
- src: "/assets/images/snh-oasis.jpg",
- alt: "Oasis Logo"
- })
- ),
- nav(
- ul(
- navLink({
- href: "/inbox",
- emoji: "☂",
- text: i18n.inbox
- }),
- navLink({
- href: "/pm",
- emoji: "ꕕ",
- text: i18n.privateMessage
- }),
- navLink({ href: "/publish", emoji: "❂", text: i18n.publish })
- )
- )
- ),
- div(
- { class: "top-bar-right" },
- nav(
- ul(
- renderTagsLink(),
- navLink({ href: "/search", emoji: "ꔅ", text: i18n.searchTitle })
- )
- )
- )
- ),
- div(
- { class: "main-content" },
- div(
- { class: "sidebar-left" },
- nav(
- ul(
- navGroup(
- {
- id: "personal",
- emoji: "⚉",
- title: i18n.menuPersonal
- },
- navLink({
- href: "/profile",
- emoji: "⚉",
- text: i18n.profile
- }),
- navLink({
- href: "/cv",
- emoji: "ꕛ",
- text: i18n.cvTitle
- }),
- renderAgendaLink(),
- renderWalletLink(),
- navLink({
- href: "/modules",
- emoji: "ꗣ",
- text: i18n.modules
- }),
- navLink({
- href: "/settings",
- emoji: "⚙",
- text: i18n.settings
- })
- ),
- navGroup(
- {
- id: "content",
- emoji: "✦",
- title: i18n.menuContent
- },
- navLink({
- href: "/mentions",
- emoji: "✺",
- text: i18n.mentions
- }),
- renderLatestLink(),
- renderThreadsLink(),
- renderTopicsLink(),
- renderSummariesLink(),
- renderPopularLink(),
- renderMultiverseLink()
- ),
- navGroup(
- {
- id: "governance",
- emoji: "⚖",
- title: i18n.menuGovernance
- },
- navLink({
- href: "/inhabitants",
- emoji: "ꖘ",
- text: i18n.inhabitantsLabel
- }),
- renderTribesLink(),
- renderParliamentLink(),
- renderCourtsLink()
- ),
- navGroup(
- {
- id: "office",
- emoji: "⌂",
- title: i18n.menuOffice
- },
- renderVotationsLink(),
- renderEventsLink(),
- renderTasksLink(),
- renderReportsLink()
- ),
- navGroup(
- {
- id: "tools",
- emoji: "⚒",
- title: i18n.menuTools
- },
- renderAILink(),
- navLink({
- href: "/stats",
- emoji: "ꕷ",
- text: i18n.statistics
- }),
- navLink({
- href: "/blockexplorer",
- emoji: "ꖸ",
- text: i18n.blockchain
- }),
- renderCipherLink(),
- renderLegacyLink()
- )
- )
- )
- ),
- main({ id: "content", class: "main-column" }, elements),
- div(
- { class: "sidebar-right" },
- nav(
- ul(
- navGroup(
- {
- id: "network",
- emoji: "☍",
- title: i18n.menuNetwork
- },
- navLink({
- href: "/activity",
- emoji: "ꔙ",
- text: i18n.activityTitle
- }),
- renderTrendingLink(),
- renderOpinionsLink(),
- renderForumLink(),
- renderInvitesLink(),
- navLink({
- href: "/peers",
- emoji: "⧖",
- text: i18n.peers
- })
- ),
- navGroup(
- {
- id: "creative",
- emoji: "✎",
- title: i18n.menuCreative
- },
- renderFeedLink(),
- renderPixeliaLink()
- ),
- navGroup(
- {
- id: "economy",
- emoji: "¤",
- title: i18n.menuEconomy
- },
- renderBankingLink(),
- renderMarketLink(),
- renderProjectsLink(),
- renderJobsLink(),
- renderTransfersLink()
- ),
- navGroup(
- {
- id: "media",
- emoji: "▤",
- title: i18n.menuMedia
- },
- renderBookmarksLink(),
- renderImagesLink(),
- renderVideosLink(),
- renderAudiosLink(),
- renderDocsLink()
- )
- )
- )
- )
- )
- )
- );
- return doctypeString + nodes.outerHTML;
- };
- // menu END
- exports.template = template;
- const thread = (messages) => {
- let lookingForTarget = true;
- let shallowest = Infinity;
- for (let i = messages.length - 1; i >= 0; i--) {
- const msg = messages[i];
- const depth = lodash.get(msg, "value.meta.thread.depth", 0);
- if (lookingForTarget) {
- const isThreadTarget = Boolean(
- lodash.get(msg, "value.meta.thread.target", false)
- );
- if (isThreadTarget) {
- lookingForTarget = false;
- }
- } else {
- if (depth < shallowest) {
- lodash.set(msg, "value.meta.thread.ancestorOfTarget", true);
- shallowest = depth;
- }
- }
- }
- const msgList = [];
- for (let i = 0; i < messages.length; i++) {
- const j = i + 1;
- const currentMsg = messages[i];
- const nextMsg = messages[j];
- const depth = (msg) => {
- if (msg === undefined) return 0;
- return lodash.get(msg, "value.meta.thread.depth", 0);
- };
- msgList.push(post({ msg: currentMsg }));
- if (depth(currentMsg) < depth(nextMsg)) {
- const isAncestor = Boolean(
- lodash.get(currentMsg, "value.meta.thread.ancestorOfTarget", false)
- );
- const isBlocked = Boolean(nextMsg.value.meta.blocking);
- const nextAuthor = lodash.get(nextMsg, "value.meta.author.name") || (typeof nextMsg?.value?.author === "string" ? (nextMsg.value.author.startsWith("@") ? nextMsg.value.author.slice(1) : nextMsg.value.author) : "Anonymous");
- const nextSnippet = postSnippet(
- lodash.has(nextMsg, "value.content.contentWarning")
- ? lodash.get(nextMsg, "value.content.contentWarning")
- : lodash.get(nextMsg, "value.content.text")
- );
- msgList.push(
- details(
- isAncestor ? { open: true } : {},
- summary(
- isBlocked
- ? i18n.relationshipBlockingPost
- : `${nextAuthor}: ${nextSnippet}`
- )
- )
- );
- } else if (depth(currentMsg) > depth(nextMsg)) {
- const diffDepth = depth(currentMsg) - depth(nextMsg);
- }
- }
- return div({ class: "thread-container" }, ...msgList);
- };
- const postSnippet = (text) => {
- const max = 40;
- text = text.trim().split("\n", 3).join("\n");
- text = text.replace(/_|`|\*|#|^\[@.*?]|\[|]|\(\S*?\)/g, "").trim();
- text = text.replace(/:$/, "");
- text = text.trim().split("\n", 1)[0].trim();
- if (text.length > max) {
- text = text.substring(0, max - 1) + "…";
- }
- return text;
- };
- const continueThreadComponent = (thread, isComment) => {
- const encoded = {
- next: encodeURIComponent(thread[THREAD_PREVIEW_LENGTH + 1].key),
- parent: encodeURIComponent(thread[0].key),
- };
- const left = thread.length - (THREAD_PREVIEW_LENGTH + 1);
- let continueLink;
- if (isComment == false) {
- continueLink = `/thread/${encoded.parent}#${encoded.next}`;
- return a(
- { href: continueLink },
- i18n.continueReading, ` ${left} `, i18n.moreComments+`${left === 1 ? "" : "s"}`
- );
- } else {
- continueLink = `/thread/${encoded.parent}`;
- return a({ href: continueLink }, i18n.readThread);
- }
- };
- const postAside = ({ key, value }) => {
- const thread = value.meta.thread;
- if (thread == null) return null;
- const isComment = value.meta.postType === "comment";
- let postsToShow;
- if (isComment) {
- const commentPosition = thread.findIndex((msg) => msg.key === key);
- postsToShow = thread.slice(
- commentPosition + 1,
- Math.min(commentPosition + (THREAD_PREVIEW_LENGTH + 1), thread.length)
- );
- } else {
- postsToShow = thread.slice(
- 1,
- Math.min(thread.length, THREAD_PREVIEW_LENGTH + 1)
- );
- }
- const fragments = postsToShow.map((p) => post({ msg: p }));
- if (thread.length > THREAD_PREVIEW_LENGTH + 1) {
- fragments.push(section(continueThreadComponent(thread, isComment)));
- }
- return fragments;
- };
- const post = ({ msg, aside = false, preview = false }) => {
- const encoded = {
- key: encodeURIComponent(msg.key),
- author: encodeURIComponent(msg.value?.author),
- parent: encodeURIComponent(msg.value?.content?.root),
- };
- const url = {
- author: `/author/${encoded.author}`,
- likeForm: `/like/${encoded.key}`,
- link: `/thread/${encoded.key}#${encoded.key}`,
- parent: `/thread/${encoded.parent}#${encoded.parent}`,
- avatar: msg.value?.meta?.author?.avatar?.url || '/assets/images/default-avatar.png',
- json: `/json/${encoded.key}`,
- subtopic: `/subtopic/${encoded.key}`,
- comment: `/comment/${encoded.key}`,
- };
- const isPrivate = Boolean(msg.value?.meta?.private);
- const isBlocked = Boolean(msg.value?.meta?.blocking);
- const isRoot = msg.value?.content?.root == null;
- const isFork = msg.value?.meta?.postType === "subtopic";
- const hasContentWarning = typeof msg.value?.content?.contentWarning === "string";
- const isThreadTarget = Boolean(lodash.get(msg, "value.meta.thread.target", false));
- const authorIdForName = msg.value?.author;
- const name =
- msg.value?.meta?.author?.name ||
- (typeof authorIdForName === "string"
- ? (authorIdForName.startsWith("@") ? authorIdForName.slice(1) : authorIdForName)
- : "Anonymous");
- const content = msg.value?.content || {};
- const contentType = String(content.type || "");
- const THREAD_ENTITY_TYPES = new Set([
- 'bookmark',
- 'image',
- 'audio',
- 'video',
- 'document',
- 'votes',
- 'event',
- 'task',
- 'report',
- 'market',
- 'project',
- 'job'
- ]);
- const safeUpper = (s) => String(s || '').toUpperCase();
- const safeStr = (v) => (v == null ? '' : String(v));
- const isMsgId = (s) => typeof s === 'string' && (s.startsWith('%') || s.startsWith('&') || s.startsWith('@'));
- const fmtDate = (v) => {
- if (!v) return '';
- const m = moment(v, moment.ISO_8601, true);
- if (m.isValid()) return m.format('YYYY-MM-DD HH:mm:ss');
- const n = typeof v === 'number' ? v : Date.parse(v);
- if (!Number.isFinite(n)) return '';
- return moment(n).format('YYYY-MM-DD HH:mm:ss');
- };
- const renderField = (labelText, valueNode) => {
- if (valueNode == null || valueNode === '') return null;
- return div(
- { class: 'card-field' },
- span({ class: 'card-label' }, labelText),
- span({ class: 'card-value' }, valueNode)
- );
- };
- const entityTitle = (c) => {
- const t = String(c.type || '').toLowerCase();
- if (t === 'votes') return safeStr(c.question || c.title);
- if (t === 'bookmark') return safeStr(c.title || c.name || c.url);
- if (t === 'market') return safeStr(c.title);
- if (t === 'project') return safeStr(c.title);
- if (t === 'job') return safeStr(c.title);
- if (t === 'report') return safeStr(c.title);
- if (t === 'task') return safeStr(c.title);
- if (t === 'event') return safeStr(c.title);
- if (t === 'document') return safeStr(c.title || c.name || c.url);
- if (t === 'image' || t === 'audio' || t === 'video') return safeStr(c.title || c.name || c.url);
- return safeStr(c.title || c.name || c.question || c.url);
- };
- const renderEntityRoot = (c) => {
- const t = String(c.type || '').toLowerCase();
- const header = `[${safeUpper(t)}]`;
- const titleText = entityTitle(c) || '(sin título)';
- const nodes = [];
- nodes.push(
- div(
- { class: 'card-field', style: 'margin-bottom:10px;' },
- span({ class: 'card-label', style: 'font-weight:800;' }, header),
- span({ class: 'card-value', style: 'margin-left:10px; font-weight:800;' }, titleText)
- )
- );
- if (t === 'votes') {
- const status = safeStr(c.status);
- const deadline = fmtDate(c.deadline);
- const totalVotes = (typeof c.totalVotes !== 'undefined') ? safeStr(c.totalVotes) : '';
- const tags = Array.isArray(c.tags) ? c.tags.filter(Boolean) : [];
- const f1 = renderField((i18n.status || 'Status') + ':', status ? safeUpper(status) : '');
- const f2 = renderField((i18n.deadline || 'Deadline') + ':', deadline);
- const f3 = renderField((i18n.voteTotalVotes || 'Total votes') + ':', totalVotes);
- if (f1) nodes.push(f1);
- if (f2) nodes.push(f2);
- if (f3) nodes.push(f3);
- if (tags.length) {
- nodes.push(
- div(
- { class: 'card-tags', style: 'margin-top:10px;' },
- ...tags.map(tag =>
- a(
- { href: `/search?query=%23${encodeURIComponent(tag)}`, class: 'tag-link' },
- `#${tag}`
- )
- )
- )
- );
- }
- } else if (t === 'report') {
- const status = safeStr(c.status);
- const severity = safeStr(c.severity);
- const r1 = renderField((i18n.status || 'Status') + ':', status ? safeUpper(status) : '');
- const r2 = renderField((i18n.severity || 'Severity') + ':', severity ? safeUpper(severity) : '');
- if (r1) nodes.push(r1);
- if (r2) nodes.push(r2);
- } else if (t === 'task') {
- const status = safeStr(c.status);
- const priority = safeStr(c.priority);
- const startTime = fmtDate(c.startTime);
- const endTime = fmtDate(c.endTime);
- const r1 = renderField((i18n.status || 'Status') + ':', status ? safeUpper(status) : '');
- const r2 = renderField((i18n.priority || 'Priority') + ':', priority ? safeUpper(priority) : '');
- const r3 = renderField((i18n.taskStartTimeLabel || 'Start') + ':', startTime);
- const r4 = renderField((i18n.taskEndTimeLabel || 'End') + ':', endTime);
- if (r1) nodes.push(r1);
- if (r2) nodes.push(r2);
- if (r3) nodes.push(r3);
- if (r4) nodes.push(r4);
- } else if (t === 'event') {
- const dateStr = fmtDate(c.date);
- const location = safeStr(c.location);
- const price = (typeof c.price !== 'undefined') ? safeStr(c.price) : '';
- const r1 = renderField((i18n.date || 'Date') + ':', dateStr);
- const r2 = renderField((i18n.location || 'Location') + ':', location);
- const r3 = renderField((i18n.price || 'Price') + ':', price ? `${price} ECO` : '');
- if (r1) nodes.push(r1);
- if (r2) nodes.push(r2);
- if (r3) nodes.push(r3);
- } else if (t === 'bookmark') {
- const u = safeStr(c.url);
- if (u) {
- nodes.push(
- renderField((i18n.url || 'URL') + ':', a({ href: u, target: '_blank', rel: 'noopener noreferrer' }, u))
- );
- }
- } else if (t === 'image') {
- const u = safeStr(c.url);
- if (u && isMsgId(u)) {
- nodes.push(
- div({ class: 'card-field', style: 'margin-top:10px;' },
- img({ src: `/blob/${encodeURIComponent(u)}`, class: 'feed-image img-content' })
- )
- );
- }
- } else if (t === 'audio') {
- const u = safeStr(c.url);
- if (u && isMsgId(u)) {
- nodes.push(
- div({ class: 'card-field', style: 'margin-top:10px;' },
- audioHyperaxe({ controls: true, src: `/blob/${encodeURIComponent(u)}` })
- )
- );
- }
- } else if (t === 'video') {
- const u = safeStr(c.url);
- if (u && isMsgId(u)) {
- nodes.push(
- div({ class: 'card-field', style: 'margin-top:10px;' },
- videoHyperaxe({ controls: true, src: `/blob/${encodeURIComponent(u)}` })
- )
- );
- }
- } else if (t === 'document') {
- const u = safeStr(c.url);
- if (u && isMsgId(u)) {
- const safeId = String(msg.key || u).replace(/[^a-zA-Z0-9_-]/g, '');
- nodes.push(
- div({ class: 'card-field', style: 'margin-top:10px;' },
- div({
- id: `pdf-container-${safeId}`,
- class: 'pdf-viewer-container',
- 'data-pdf-url': `/blob/${encodeURIComponent(u)}`
- })
- )
- );
- }
- } else if (t === 'market') {
- const status = safeStr(c.status);
- const price = (typeof c.price !== 'undefined') ? safeStr(c.price) : '';
- const r1 = renderField((i18n.status || 'Status') + ':', status ? safeUpper(status) : '');
- const r2 = renderField((i18n.price || 'Price') + ':', price ? `${price} ECO` : '');
- if (r1) nodes.push(r1);
- if (r2) nodes.push(r2);
- } else if (t === 'project') {
- const status = safeStr(c.status);
- const r1 = renderField((i18n.status || 'Status') + ':', status ? safeUpper(status) : '');
- if (r1) nodes.push(r1);
- } else if (t === 'job') {
- const status = safeStr(c.status);
- const location = safeStr(c.location);
- const salary = (typeof c.salary !== 'undefined') ? safeStr(c.salary) : '';
- const r1 = renderField((i18n.status || 'Status') + ':', status ? safeUpper(status) : '');
- const r2 = renderField((i18n.jobLocation || 'Location') + ':', location ? safeUpper(location) : '');
- const r3 = renderField((i18n.jobSalary || 'Salary') + ':', salary ? `${salary} ECO` : '');
- if (r1) nodes.push(r1);
- if (r2) nodes.push(r2);
- if (r3) nodes.push(r3);
- }
- return article({ class: 'content' }, ...nodes.filter(Boolean));
- };
- const rawText = content.text || "";
- const emptyContent = "<p>undefined</p>\n";
- const isProbablyHtml =
- typeof rawText === "string" &&
- /<\/?[a-z][\s\S]*>/i.test(rawText.trim());
- let articleElement;
- if (contentType !== 'post' && contentType !== 'blog' && THREAD_ENTITY_TYPES.has(contentType)) {
- articleElement = renderEntityRoot(content);
- } else if (rawText === emptyContent) {
- articleElement = article(
- { class: "content" },
- div(
- { class: "card-field", style: "margin-bottom:10px;" },
- span({ class: "card-label" }, (i18n.invalidPost || 'Invalid content') + ':'),
- span({ class: "card-value" }, (i18n.invalidPostHint || 'This message has invalid/empty text.'))
- ),
- details(
- summary(i18n.viewJson || 'View JSON'),
- pre({
- innerHTML: highlightJs.highlight(
- JSON.stringify(msg, null, 2),
- { language: "json", ignoreIllegals: true }
- ).value,
- })
- )
- );
- } else if (isProbablyHtml) {
- let html = rawText;
- if (!/<a\b[^>]*>/i.test(html)) {
- html = html.replace(
- /(https?:\/\/[^\s<]+)/g,
- (u) => `<a href="${u}" target="_blank" rel="noopener noreferrer">${u}</a>`
- );
- }
- articleElement = article({ class: "content", innerHTML: html });
- } else {
- articleElement = article(
- { class: "content" },
- p({ class: "post-text" }, ...renderUrl(rawText))
- );
- }
- if (preview) {
- return section(
- { id: msg.key, class: "post-preview" },
- hasContentWarning
- ? details(summary(msg.value?.content?.contentWarning), articleElement)
- : articleElement
- );
- }
- const ts_received = msg.value?.meta?.timestamp?.received;
- const iso =
- (ts_received && ts_received.iso8601) ||
- (typeof msg.value?.timestamp === 'number' ? new Date(msg.value.timestamp).toISOString() : null) ||
- (content.createdAt ? new Date(content.createdAt).toISOString() : null);
- if (!iso || !moment(iso, moment.ISO_8601, true).isValid()) {
- return null;
- }
- const validTimestamp = moment(iso, moment.ISO_8601);
- const timeAgo = validTimestamp.fromNow();
- const timeAbsolute = validTimestamp.toISOString().split(".")[0].replace("T", " ");
- const likeButton = msg.value?.meta?.voted
- ? { value: 0, class: "liked" }
- : { value: 1, class: null };
- const likeCount = msg.value?.meta?.votes?.length || 0;
- const maxLikedNameLength = 16;
- const maxLikedNames = 16;
- const likedByNames = msg.value?.meta?.votes
- .slice(0, maxLikedNames)
- .map((person) => person.name)
- .map((n) => n.slice(0, maxLikedNameLength))
- .join(", ");
- const additionalLikesMessage =
- likeCount > maxLikedNames ? `+${likeCount - maxLikedNames} more` : ``;
- const likedByMessage =
- likeCount > 0 ? `${likedByNames} ${additionalLikesMessage}` : null;
- const messageClasses = ["post"];
- const recps = [];
- const addRecps = (recpsInfo) => {
- recpsInfo.forEach((recp) => {
- recps.push(
- a(
- { href: `/author/${encodeURIComponent(recp.feedId)}` },
- img({ class: "avatar", src: recp.avatarUrl, alt: "" })
- )
- );
- });
- };
- if (isPrivate) {
- messageClasses.push("private");
- addRecps(msg.value?.meta?.recpsInfo || []);
- }
- if (isThreadTarget) {
- messageClasses.push("thread-target");
- }
- if (isBlocked) {
- messageClasses.push("blocked");
- return section(
- {
- id: msg.key,
- class: messageClasses.join(" "),
- },
- i18n.relationshipBlockingPost
- );
- }
- const articleContent = article(
- { class: "content" },
- hasContentWarning ? div({ class: "post-subject" }, msg.value?.content?.contentWarning) : null,
- articleElement
- );
- const fragment = section(
- {
- id: msg.key,
- class: messageClasses.join(" "),
- },
- header(
- div(
- { class: "header-content" },
- a(
- { href: url.author },
- img({ class: "avatar-profile", src: url.avatar, alt: "" })
- ),
- span(
- { class: "created-at" },
- `${i18n.createdBy} `,
- a({ href: url.author }, "@", name),
- ` | ${timeAbsolute} | ${i18n.sendTime} `,
- a({ href: url.link }, timeAgo)
- ),
- isPrivate ? "🔒" : null,
- isPrivate ? recps : null
- )
- ),
- articleContent,
- footer(
- div(
- form(
- { action: url.likeForm, method: "post" },
- button(
- {
- name: "voteValue",
- type: "submit",
- value: likeButton.value,
- class: likeButton.class,
- title: likedByMessage,
- },
- `☉ ${likeCount}`
- )
- ),
- a({ href: url.comment }, i18n.comment),
- isPrivate || isRoot || isFork
- ? null
- : a({ href: url.subtopic }, nbsp, i18n.subtopic)
- ),
- br()
- )
- );
- const threadSeparator = [br()];
- if (aside) {
- return [fragment, postAside(msg), isRoot ? threadSeparator : null];
- } else {
- return fragment;
- }
- };
- exports.editProfileView = ({ name, description }) =>
- template(
- i18n.editProfile,
- section(
- h1(i18n.editProfile),
- p(i18n.editProfileDescription),
- form(
- {
- action: "/profile/edit",
- method: "POST",
- enctype: "multipart/form-data",
- },
- label(
- i18n.profileImage,
- br,
- input({ type: "file", name: "image", accept: "image/*" })
- ),
- br,br,
- label(i18n.profileName,
- br,
- input({ name: "name", value: name })),
- br,br,
- label(
- i18n.profileDescription,
- br,
- textarea(
- {
- autofocus: true,
- name: "description",
- rows: "6",
- },
- description
- )
- ),
- br,
- button(
- {
- type: "submit",
- },
- i18n.submit
- )
- )
- )
- );
- exports.authorView = ({
- avatarUrl,
- description,
- feedId,
- messages,
- firstPost,
- lastPost,
- name,
- relationship,
- ecoAddress,
- karmaScore = 0,
- lastActivityBucket
- }) => {
- const linkUrl = `/author/${encodeURIComponent(feedId)}`;
- const mention = `[@${name}](${feedId})`;
- const markdownMention = highlightJs.highlight(mention, { language: "markdown", ignoreIllegals: true }).value;
- const contactForms = [];
- const addForm = ({ action }) =>
- contactForms.push(
- form(
- { action: `/${action}/${encodeURIComponent(feedId)}`, method: "post" },
- button({ type: "submit" }, i18n[action])
- )
- );
- if (relationship.me === false) {
- if (relationship.following) addForm({ action: "unfollow" });
- else if (relationship.blocking) addForm({ action: "unblock" });
- else { addForm({ action: "follow" }); addForm({ action: "block" }) }
- }
- const relationshipMessage = (() => {
- if (relationship.me) return i18n.relationshipYou;
- const following = relationship.following === true;
- const followsMe = relationship.followsMe === true;
- if (following && followsMe) return i18n.relationshipMutuals;
- const messagesArr = [];
- messagesArr.push(following ? i18n.relationshipFollowing : i18n.relationshipNone);
- messagesArr.push(followsMe ? i18n.relationshipTheyFollow : i18n.relationshipNotFollowing);
- return messagesArr.join(". ") + ".";
- })();
- const bucket = lastActivityBucket || 'red';
- const dotClass = bucket === "green" ? "green" : bucket === "orange" ? "orange" : "red";
- const prefix = section(
- { class: "message" },
- div(
- { class: "profile" },
- div({ class: "avatar-container" },
- img({ class: "inhabitant-photo-details", src: avatarUrl }),
- h1({ class: "name" }, name),
- ),
- pre({ class: "md-mention", innerHTML: markdownMention }),
- p(a({ class: "user-link", href: `/author/${encodeURIComponent(feedId)}` }, feedId)),
- div({ class: "profile-metrics" },
- p(`${i18n.bankingUserEngagementScore}: `, strong(karmaScore !== undefined ? karmaScore : 0)),
- div({ class: "inhabitant-last-activity" },
- span({ class: "label" }, `${i18n.inhabitantActivityLevel}:`),
- span({ class: `activity-dot ${dotClass}` }, "")
- ),
- ecoAddress
- ? div({ class: "eco-wallet" }, p(`${i18n.bankWalletConnected}: `, strong(ecoAddress)))
- : div({ class: "eco-wallet" }, p(i18n.ecoWalletNotConfigured || "ECOin Wallet not configured"))
- )
- ),
- description !== "" ? article({ innerHTML: markdown(description) }) : null,
- footer(
- div(
- { class: "profile" },
- ...contactForms.map(form => span({ style: "font-weight: bold;" }, form)),
- relationship.me
- ? span({ class: "status you" }, i18n.relationshipYou)
- : div({ class: "relationship-status" },
- relationship.blocking && relationship.blockedBy
- ? span({ class: "status blocked" }, i18n.relationshipMutualBlock)
- : [
- relationship.blocking ? span({ class: "status blocked" }, i18n.relationshipBlocking) : null,
- relationship.blockedBy ? span({ class: "status blocked-by" }, i18n.relationshipBlockedBy) : null,
- relationship.following && relationship.followsMe
- ? span({ class: "status mutual" }, i18n.relationshipMutuals)
- : [
- span({ class: "status supporting" }, relationship.following ? i18n.relationshipFollowing : i18n.relationshipNone),
- span({ class: "status supported-by" }, relationship.followsMe ? i18n.relationshipTheyFollow : i18n.relationshipNotFollowing)
- ]
- ]
- ),
- relationship.me ? a({ href: `/profile/edit`, class: "btn" }, nbsp, i18n.editProfile) : null,
- a({ href: `/likes/${encodeURIComponent(feedId)}`, class: "btn" }, i18n.viewLikes),
- !relationship.me ? a({ href: `/pm?recipients=${encodeURIComponent(feedId)}`, class: "btn" }, i18n.pmCreateButton) : null
- )
- )
- );
- let items = messages.map((msg) => post({ msg }));
- if (items.length === 0) {
- if (lastPost === undefined) {
- items.push(section(div(span(i18n.feedEmpty))));
- } else {
- items.push(
- section(
- div(
- span(i18n.feedRangeEmpty),
- a({ href: `${linkUrl}` }, i18n.seeFullFeed)
- )
- )
- );
- }
- } else {
- const highestSeqNum = messages[0].value.sequence;
- const lowestSeqNum = messages[messages.length - 1].value.sequence;
- const newerPostsLink = a(
- {
- href:
- lastPost !== undefined && highestSeqNum < lastPost.value.sequence
- ? `${linkUrl}?gt=${highestSeqNum}`
- : "#",
- class:
- lastPost !== undefined && highestSeqNum < lastPost.value.sequence
- ? "btn"
- : "btn disabled",
- "aria-disabled":
- lastPost === undefined || highestSeqNum >= lastPost.value.sequence
- },
- i18n.newerPosts
- );
- const olderPostsLink = a(
- {
- href:
- lowestSeqNum > firstPost.value.sequence
- ? `${linkUrl}?lt=${lowestSeqNum}`
- : "#",
- class:
- lowestSeqNum > firstPost.value.sequence
- ? "btn"
- : "btn disabled",
- "aria-disabled": !(lowestSeqNum > firstPost.value.sequence)
- },
- i18n.olderPosts
- );
- const pagination = section(
- { class: "message" },
- footer(div(newerPostsLink, olderPostsLink), br())
- );
- items.unshift(pagination);
- items.push(pagination);
- }
- return template(i18n.profile, prefix, items);
- };
- exports.previewCommentView = async ({
- previewData,
- messages,
- myFeedId,
- parentMessage,
- contentWarning,
- }) => {
- if (!parentMessage || !parentMessage.value) {
- throw new Error("Missing parentMessage or value");
- }
- const publishAction = `/comment/${encodeURIComponent(parentMessage.key)}`;
- const preview = generatePreview({
- previewData,
- contentWarning,
- action: publishAction,
- });
- return exports.commentView(
- { messages, myFeedId, parentMessage },
- preview,
- previewData.text,
- contentWarning
- );
- };
- exports.commentView = async (
- { messages, myFeedId, parentMessage },
- preview,
- text,
- contentWarning
- ) => {
- if (!parentMessage || !parentMessage.value) {
- throw new Error("Missing parentMessage or value");
- }
- const parentKey = parentMessage.key;
- const threadRoot = parentMessage.value?.content?.root || parentKey;
- const messagesInput = Array.isArray(messages) ? messages : [];
- const merged = [parentMessage, ...messagesInput];
- const filtered = merged.filter((m) => {
- if (!m || !m.value) return false;
- return m.key === threadRoot || m.value?.content?.root === threadRoot;
- });
- const seen = new Set();
- const threadMessages = [];
- for (const m of filtered) {
- if (m && m.key && !seen.has(m.key)) {
- seen.add(m.key);
- threadMessages.push(m);
- }
- }
- const tsNum = (m) => {
- const n1 = Number(m?.value?.timestamp);
- if (Number.isFinite(n1) && n1 > 0) return n1;
- const iso = m?.value?.meta?.timestamp?.received?.iso8601;
- const raw = m?.value?.meta?.timestamp?.received;
- const n2 = iso ? Date.parse(iso) : (raw ? Date.parse(raw) : NaN);
- if (Number.isFinite(n2) && n2 > 0) return n2;
- const createdAt = m?.value?.content?.createdAt;
- const n3 = createdAt ? Date.parse(createdAt) : NaN;
- if (Number.isFinite(n3) && n3 > 0) return n3;
- return 0;
- };
- threadMessages.sort((a, b) => tsNum(a) - tsNum(b));
- const authorName = parentMessage.value?.meta?.author?.name || parentMessage.value?.author || "Anonymous";
- let markdownMention = "";
- const parentAuthorFeedId = parentMessage.value?.author;
- const parentAuthorName =
- parentMessage.value?.meta?.author?.name ||
- (typeof parentAuthorFeedId === "string"
- ? (parentAuthorFeedId.startsWith("@") ? parentAuthorFeedId.slice(1) : parentAuthorFeedId)
- : "Anonymous");
- if (parentAuthorFeedId && parentAuthorFeedId !== myFeedId) {
- markdownMention = `[@${parentAuthorName}](${parentAuthorFeedId})\n\n`;
- }
- const messageElements = threadMessages.map((m) => post({ msg: m }));
- const action = `/comment/preview/${encodeURIComponent(parentKey)}`;
- const method = "post";
- const isPrivate = Boolean(parentMessage.value?.meta?.private);
- return template(
- i18n.commentTitle({ authorName }),
- div({ class: "thread-container" }, ...messageElements),
- form(
- { action, method, enctype: "multipart/form-data" },
- i18n.blogSubject,
- br,
- label(
- i18n.contentWarningLabel,
- input({
- name: "contentWarning",
- type: "text",
- class: "contentWarning",
- value: contentWarning ? contentWarning : "",
- placeholder: i18n.contentWarningPlaceholder
- })
- ),
- br,
- label({ for: "text" }, i18n.blogMessage),
- br,
- textarea(
- {
- autofocus: true,
- required: true,
- name: "text",
- rows: "6",
- cols: "50",
- placeholder: i18n.publishWarningPlaceholder
- },
- text ? text : null
- ),
- br,
- label(
- { for: "blob" },
- i18n.blogImage || "Upload Image (jpeg, jpg, png, gif) (max-size: 500px x 400px)"
- ),
- input({ type: "file", id: "blob", name: "blob" }),
- br,
- br,
- button({ type: "submit" }, i18n.blogPublish)
- ),
- preview ? div({ class: "comment-preview" }, preview) : ""
- );
- };
- const renderMessage = (msg) => {
- const content = lodash.get(msg, "value.content", {});
- const author = msg.value.author || "Anonymous";
- const createdAt = new Date(msg.value.timestamp).toLocaleString();
- const mentionsText = content.text || '';
- return div({ class: "mention-item" }, [
- div({ class: "mention-content", innerHTML: mentionsText || '[No content]' }),
- p(a({ class: 'user-link', href: `/author/${encodeURIComponent(author)}` }, author)),
- p(`${i18n.createdAtLabel || i18n.mentionsCreatedAt}: ${createdAt}`)
- ]);
- };
- exports.mentionsView = ({ messages, myFeedId }) => {
- const title = i18n.mentions;
- const description = i18n.mentionsDescription;
- if (!Array.isArray(messages) || messages.length === 0) {
- return template(
- title,
- section(
- div({ class: "tags-header" },
- h2(title),
- p(description)
- )
- ),
- section(
- div({ class: "mentions-list" },
- p({ class: "empty" }, i18n.noMentions)
- )
- )
- );
- }
- const filteredMessages = messages.filter(msg => {
- const mentions = lodash.get(msg, "value.content.mentions", {});
- return Object.keys(mentions).some(key => mentions[key].link === myFeedId);
- });
- if (filteredMessages.length === 0) {
- return template(
- title,
- section(
- div({ class: "tags-header" },
- h2(title),
- p(description)
- )
- ),
- section(
- div({ class: "mentions-list" },
- p({ class: "empty" }, i18n.noMentions)
- )
- )
- );
- }
- return template(
- title,
- section(
- div({ class: "tags-header" },
- h2(title),
- p(description)
- )
- ),
- section(
- div({ class: "mentions-list" },
- filteredMessages.map(renderMessage)
- )
- )
- );
- };
- exports.privateView = async (messagesInput, filter) => {
- const messagesRaw = Array.isArray(messagesInput) ? messagesInput : messagesInput.messages
- const messages = (messagesRaw || []).filter(m => m && m.key && m.value && m.value.content && m.value.content.type === 'post' && m.value.content.private === true)
- const userId = await getUserId()
- const isSent = m => (m?.value?.author === userId) || (m?.value?.content?.from === userId)
- const isToUser = m => Array.isArray(m?.value?.content?.to) && m.value.content.to.includes(userId)
- const linkAuthor = (id) =>
- a({ class: 'user-link', href: `/author/${encodeURIComponent(id)}` }, id)
- const hrefFor = {
- job: (id) => `/jobs/${encodeURIComponent(id)}`,
- project: (id) => `/projects/${encodeURIComponent(id)}`,
- market: (id) => `/market/${encodeURIComponent(id)}`
- }
- const clickableCardProps = (href, extraClass = '') => {
- const props = { class: `pm-card ${extraClass}` }
- if (href) {
- props.onclick = `window.location='${href}'`
- props.tabindex = 0
- props.onkeypress = `if(event.key==='Enter') window.location='${href}'`
- }
- return props
- }
- const chip = (txt) => span({ class: 'chip' }, txt)
- function headerLine({ sentAt, from, toLinks, textLen }) {
- return div({ class: 'pm-header' },
- span({ class: 'date-link' }, `${moment(sentAt).format('YYYY/MM/DD HH:mm:ss')} ${i18n.performed}`),
- span({ class: 'pm-from' }, ' ', i18n.pmFromLabel, ' ', linkAuthor(from)),
- span({ class: 'pm-to' }, ' ', '→', ' ', i18n.pmToLabel, ' ', toLinks)
- )
- }
- function actions({ key, replyId, subjectRaw, text }) {
- const stop = { onclick: 'event.stopPropagation()' }
- const subjectReply = /^(\s*RE:\s*)/i.test(subjectRaw || '') ? (subjectRaw || '') : `RE: ${subjectRaw || ''}`
- return div({ class: 'pm-actions' },
- form({ method: 'GET', action: '/pm', class: 'pm-action-form', ...stop },
- input({ type: 'hidden', name: 'recipients', value: replyId }),
- input({ type: 'hidden', name: 'subject', value: subjectReply }),
- input({ type: 'hidden', name: 'quote', value: text || '' }),
- button({ type: 'submit', class: 'pm-btn reply-btn' }, i18n.pmReply.toUpperCase())
- ),
- form({ method: 'POST', action: `/inbox/delete/${encodeURIComponent(key)}`, class: 'pm-action-form', ...stop },
- button({ type: 'submit', class: 'pm-btn delete-btn' }, i18n.privateDelete.toUpperCase())
- )
- )
- }
- function canonicalSubject(s) {
- return (s || '').replace(/^\s*(RE:\s*)+/i, '').trim()
- }
- function participantsKey(m) {
- const c = m?.value?.content || {}
- const set = new Set([m?.value?.author, ...(Array.isArray(c.to) ? c.to : [])])
- return Array.from(set).sort().join('|')
- }
- function threadId(m) {
- return canonicalSubject(m?.value?.content?.subject || '') + '||' + participantsKey(m)
- }
- function threadLevel(s) {
- const m = (s || '').match(/RE:/gi)
- return m ? Math.min(m.length, 8) : 0
- }
- function quoted(str) {
- const m = str.match(/"([^"]+)"/)
- return m ? m[1] : ''
- }
- function pickLink(str, kind) {
- if (kind === 'job') {
- const m = str.match(/\/jobs\/([%A-Za-z0-9/+._=-]+\.sha256)/)
- return m ? m[1] : ''
- }
- if (kind === 'project') {
- const m = str.match(/\/projects\/([%A-Za-z0-9/+._=-]+\.sha256)/)
- return m ? m[1] : ''
- }
- if (kind === 'market') {
- const m = str.match(/\/market\/([%A-Za-z0-9/+._=-]+\.sha256)/)
- return m ? m[1] : ''
- }
- return ''
- }
- function clickableLinks(str) {
- return str
- .replace(/(@[a-zA-Z0-9/+._=-]+\.ed25519)/g, (match, id) => `<a class="user-link" href="/author/${encodeURIComponent(id)}">${match}</a>`)
- .replace(/\/jobs\/([%A-Za-z0-9/+._=-]+\.sha256)/g, (match, id) => `<a class="job-link" href="${hrefFor.job(id)}">${match}</a>`)
- .replace(/\/projects\/([%A-Za-z0-9/+._=-]+\.sha256)/g, (match, id) => `<a class="project-link" href="${hrefFor.project(id)}">${match}</a>`)
- .replace(/\/market\/([%A-Za-z0-9/+._=-]+\.sha256)/g, (match, id) => `<a class="market-link" href="${hrefFor.market(id)}">${match}</a>`)
- }
- const threads = {}
- for (const m of messages) {
- const tid = threadId(m)
- if (!threads[tid]) threads[tid] = []
- threads[tid].push(m)
- }
- const inboxSet = new Set()
- for (const arr of Object.values(threads)) {
- const hasInbound = arr.some(isToUser)
- if (hasInbound) for (const m of arr) inboxSet.add(m)
- }
- const data =
- filter === 'sent' ? messages.filter(isSent) :
- filter === 'inbox' ? Array.from(inboxSet) :
- messages
- const inboxCount = Array.from(inboxSet).length
- const sentCount = messages.filter(isSent).length
- const sorted = [...data].sort((a, b) => {
- const ta = threadId(a)
- const tb = threadId(b)
- if (ta < tb) return -1
- if (ta > tb) return 1
- const sa = new Date(a?.value?.content?.sentAt || a.timestamp || 0).getTime()
- const sb = new Date(b?.value?.content?.sentAt || b.timestamp || 0).getTime()
- return sa - sb
- })
- function JobCard({ type, sentAt, from, toLinks, text, key }) {
- const isSub = type === 'JOB_SUBSCRIBED'
- const icon = isSub ? '🟡' : '🟠'
- const titleH = isSub ? (i18n.inboxJobSubscribedTitle || 'New subscription to your job offer') : (i18n.inboxJobUnsubscribedTitle || 'Unsubscription from your job offer')
- const jobTitle = quoted(text) || 'job'
- const jobId = pickLink(text, 'job')
- const href = jobId ? hrefFor.job(jobId) : null
- return div(
- clickableCardProps(href, `job-notification thread-level-0`),
- headerLine({ sentAt, from, toLinks, textLen: text.length }),
- h2({ class: 'pm-title' }, `${icon} ${i18n.pmBotJobs} · ${titleH}`),
- p(
- i18n.pmInhabitantWithId, ' ',
- linkAuthor(from), ' ',
- isSub ? i18n.pmHasSubscribedToYourJobOffer : (i18n.pmHasUnsubscribedFromYourJobOffer || 'has unsubscribed from your job offer'),
- ' ',
- href ? a({ class: 'job-link', href }, `"${jobTitle}"`) : `"${jobTitle}"`
- ),
- actions({ key, replyId: from, subjectRaw: jobTitle, text })
- )
- }
- function ProjectFollowCard({ type, sentAt, from, toLinks, text, key }) {
- const isFollow = type === 'PROJECT_FOLLOWED'
- const icon = isFollow ? '🔔' : '🔕'
- const titleH = isFollow
- ? (i18n.inboxProjectFollowedTitle || 'New follower of your project')
- : (i18n.inboxProjectUnfollowedTitle || 'Unfollowed your project')
- const projectTitle = quoted(text) || 'project'
- const projectId = pickLink(text, 'project')
- const href = projectId ? hrefFor.project(projectId) : null
- return div(
- clickableCardProps(href, `project-${isFollow ? 'follow' : 'unfollow'}-notification thread-level-0`),
- headerLine({ sentAt, from, toLinks, textLen: text.length }),
- h2({ class: 'pm-title' }, `${icon} ${i18n.pmBotProjects} · ${titleH}`),
- p(
- i18n.pmInhabitantWithId, ' ',
- a({ class: 'user-link', href: `/author/${encodeURIComponent(from)}` }, from),
- ' ',
- isFollow ? (i18n.pmHasFollowedYourProject || 'has followed your project') : (i18n.pmHasUnfollowedYourProject || 'has unfollowed your project'),
- ' ',
- href ? a({ class: 'project-link', href }, `"${projectTitle}"`) : `"${projectTitle}"`
- ),
- actions({ key, replyId: from, subjectRaw: projectTitle, text })
- )
- }
- function MarketSoldCard({ sentAt, from, toLinks, subject, text, key }) {
- const itemTitle = quoted(subject) || quoted(text) || 'item'
- const buyerId = (text.match(/OASIS ID:\s*([\w=/+.-]+)/) || [])[1] || from
- const price = (text.match(/for:\s*\$([\d.]+)/) || [])[1] || ''
- const marketId = pickLink(text, 'market')
- const href = marketId ? hrefFor.market(marketId) : null
- return div(
- clickableCardProps(href, 'market-sold-notification thread-level-0'),
- headerLine({ sentAt, from, toLinks, textLen: text.length }),
- h2({ class: 'pm-title' }, `💰 ${i18n.pmBotMarket} · ${i18n.inboxMarketItemSoldTitle}`),
- p(
- i18n.pmYourItem, ' ',
- href ? a({ class: 'market-link', href }, `"${itemTitle}"`) : `"${itemTitle}"`,
- ' ',
- i18n.pmHasBeenSoldTo, ' ',
- linkAuthor(buyerId),
- price ? ` ${i18n.pmFor} $${price}.` : '.'
- ),
- actions({ key, replyId: buyerId, subjectRaw: itemTitle, text })
- )
- }
- function ProjectPledgeCard({ sentAt, from, toLinks, content, text, key }) {
- const amount = content.meta?.amount ?? (text.match(/pledged\s+([\d.]+)/)?.[1] || '0')
- const projectTitle = content.meta?.projectTitle ?? (text.match(/project\s+"([^"]+)"/)?.[1] || 'project')
- const projectId = content.meta?.projectId ?? pickLink(text, 'project')
- const href = projectId ? hrefFor.project(projectId) : null
- return div(
- clickableCardProps(href, 'project-pledge-notification thread-level-0'),
- headerLine({ sentAt, from, toLinks, textLen: text.length }),
- h2({ class: 'pm-title' }, `💚 ${i18n.pmBotProjects} · ${i18n.inboxProjectPledgedTitle}`),
- p(
- i18n.pmInhabitantWithId, ' ',
- linkAuthor(from), ' ',
- i18n.pmHasPledged, ' ',
- chip(`${amount} ECO`), ' ',
- i18n.pmToYourProject, ' ',
- href ? a({ class: 'project-link', href }, `"${projectTitle}"`) : `"${projectTitle}"`
- ),
- actions({ key, replyId: from, subjectRaw: projectTitle, text })
- )
- }
- return template(
- i18n.private,
- section(
- div({ class: 'tags-header' },
- h2(i18n.private),
- p(i18n.privateDescription)
- ),
- div({ class: 'filters' },
- form({ method: 'GET', action: '/inbox' }, [
- button({
- type: 'submit',
- name: 'filter',
- value: 'inbox',
- class: filter === 'inbox' ? 'filter-btn active' : 'filter-btn'
- }, `${i18n.privateInbox} (${inboxCount})`),
- button({
- type: 'submit',
- name: 'filter',
- value: 'sent',
- class: filter === 'sent' ? 'filter-btn active' : 'filter-btn'
- }, `${i18n.privateSent} (${sentCount})`),
- button({
- type: 'submit',
- name: 'filter',
- value: 'create',
- class: 'create-button',
- formaction: '/pm',
- formmethod: 'GET'
- }, i18n.pmCreateButton)
- ])
- ),
- div({ class: 'message-list' },
- sorted.length
- ? sorted.map(msg => {
- const content = msg.value.content
- const author = msg.value.author
- const subjectRaw = content.subject || ''
- const subjectU = subjectRaw.toUpperCase()
- const text = content.text || ''
- const sentAt = new Date(content.sentAt || msg.timestamp)
- const fromResolved = content.from || author
- const toLinks = Array.isArray(content.to) ? content.to.map(addr => linkAuthor(addr)) : []
- const level = threadLevel(subjectRaw)
- if (subjectU === 'JOB_SUBSCRIBED' || subjectU === 'JOB_UNSUBSCRIBED') {
- return JobCard({ type: subjectU, sentAt, from: fromResolved, toLinks, text, key: msg.key })
- }
- if (subjectU === 'PROJECT_FOLLOWED' || subjectU === 'PROJECT_UNFOLLOWED') {
- return ProjectFollowCard({ type: subjectU, sentAt, from: fromResolved, toLinks, text, key: msg.key })
- }
- if (subjectU === 'MARKET_SOLD') {
- return MarketSoldCard({ sentAt, from: fromResolved, toLinks, subject: subjectRaw, text, key: msg.key })
- }
- if (subjectU === 'PROJECT_PLEDGE' || content.meta?.type === 'project-pledge') {
- return ProjectPledgeCard({ sentAt, from: fromResolved, toLinks, content, text, key: msg.key })
- }
- return div(
- { class: `pm-card normal-pm thread-level-${level}` },
- headerLine({ sentAt, from: fromResolved, toLinks, textLen: text.length }),
- h2(subjectRaw || i18n.pmNoSubject),
- p({ class: 'message-text' }, ...renderUrl(clickableLinks(text))),
- actions({ key: msg.key, replyId: fromResolved, subjectRaw, text })
- )
- })
- : p({ class: 'empty' }, i18n.noPrivateMessages)
- )
- )
- )
- }
- exports.publishCustomView = async () => {
- const action = "/publish/custom";
- const method = "post";
- return template(
- i18n.publishCustom,
- section(
- div({ class: "tags-header" },
- h2(i18n.publishCustom),
- p(i18n.publishCustomDescription)
- ),
- form(
- { action, method },
- textarea(
- {
- autofocus: true,
- required: true,
- name: "text",
- rows: 10,
- style: "width: 100%;"
- },
- "{\n",
- ' "type": "feed",\n',
- ' "hello": "world"\n',
- "}"
- ),
- br,
- br,
- button({ type: "submit" }, i18n.submit)
- )
- ),
- section(
- div({ class: "tags-header" },
- p(i18n.publishBasicInfo({ href: "/publish" }))
- )
- )
- );
- };
- exports.threadView = ({ messages }) => {
- const rootMessage = messages[0];
- const rootAuthorName = rootMessage.value.meta.author.name;
- const needsPdfViewer = Array.isArray(messages) && messages.some((m) => {
- const t = String(m?.value?.content?.type || "").toLowerCase();
- return t === "document";
- });
- const tpl = template(
- [`@${rootAuthorName}`],
- div(thread(messages))
- );
- return `${tpl}${
- needsPdfViewer
- ? `<script type="module" src="/js/pdf.min.mjs"></script>
- <script src="/js/pdf-viewer.js"></script>`
- : ""
- }`;
- };
- exports.publishView = (preview, text, contentWarning) => {
- return template(
- i18n.publish,
- section(
- div({ class: "tags-header" },
- h2(i18n.publishBlog),
- p(i18n.publishLabel({ markdownUrl, linkTarget: "_blank" }))
- )
- ),
- section(
- div({ class: "publish-form" },
- form(
- {
- action: "/publish/preview",
- method: "post",
- enctype: "multipart/form-data",
- },
- [
- label({ for: "contentWarning" }, i18n.blogSubject),
- br(),
- input({
- name: "contentWarning",
- id: "contentWarning",
- type: "text",
- class: "contentWarning",
- value: contentWarning || "",
- placeholder: i18n.contentWarningPlaceholder
- }),
- br(),
- label({ for: "text" }, i18n.blogMessage),
- br(),
- textarea(
- {
- required: true,
- name: "text",
- id: "text",
- rows: "6",
- cols: "50",
- placeholder: i18n.publishWarningPlaceholder,
- class: "publish-textarea"
- },
- text || ""
- ),
- br(),
- label({ for: "blob" }, i18n.blogImage || "Upload Image (jpeg, jpg, png, gif) (max-size: 500px x 400px)"),
- br(),
- input({ type: "file", id: "blob", name: "blob" }),
- br(), br(),
- button({ type: "submit" }, i18n.blogPublish)
- ]
- )
- )
- ),
- preview || "",
- section(
- div({ class: "tags-header" },
- p(i18n.publishCustomInfo({ href: "/publish/custom" }))
- )
- )
- );
- };
- const generatePreview = ({ previewData, contentWarning, action }) => {
- const { authorMeta, formattedText, mentions } = previewData;
- const renderedText = formattedText;
- const msg = {
- key: "%non-existent.preview",
- value: {
- author: authorMeta.id,
- content: {
- type: "post",
- text: renderedText,
- mentions: mentions,
- },
- timestamp: Date.now(),
- meta: {
- isPrivate: false,
- votes: [],
- author: {
- name: authorMeta.name,
- avatar: {
- url: `http://localhost:3000/blob/${encodeURIComponent(authorMeta.image)}`,
- },
- },
- },
- },
- };
- if (contentWarning) {
- msg.value.content.contentWarning = contentWarning;
- }
- if (msg.value.meta.author.avatar.url === 'http://localhost:3000/blob/%260000000000000000000000000000000000000000000%3D.sha256') {
- msg.value.meta.author.avatar.url = '/assets/images/default-avatar.png';
- }
- const ts = new Date(msg.value.timestamp);
- lodash.set(msg, "value.meta.timestamp.received.iso8601", ts.toISOString());
- const ago = Date.now() - Number(ts);
- const prettyAgo = prettyMs(ago, { compact: true });
- lodash.set(msg, "value.meta.timestamp.received.since", prettyAgo);
- return div(
- section(
- { class: "post-preview" },
- div(
- { class: "preview-content" },
- h2(i18n.messagePreview),
- post({ msg, preview: true })
- ),
- ),
- section(
- { class: "mention-suggestions" },
- Object.keys(mentions).map((name) => {
- const matches = mentions[name];
- return div(
- h2(i18n.mentionsMatching),
- { class: "mention-card" },
- a(
- {
- href: `/author/@${encodeURIComponent(matches[0].feed)}`,
- },
- img({ src: msg.value.meta.author.avatar.url, class: "avatar-profile" })
- ),
- br,
- div(
- { class: "mention-name" },
- span({ class: "label" }, `${i18n.mentionsName}: `),
- a(
- {
- href: `/author/@${encodeURIComponent(matches[0].feed)}`,
- },
- `@${authorMeta.name}`
- )
- ),
- div(
- { class: "mention-relationship" },
- span({ class: "label" }, `${i18n.mentionsRelationship}:`),
- span({ class: "relationship" }, matches[0].rel.followsMe ? i18n.relationshipMutuals : i18n.relationshipNotMutuals),
- { class: "mention-relationship-details" },
- span({ class: "emoji" }, matches[0].rel.followsMe ? "☍" : "⚼"),
- span({ class: "mentions-listing" },
- a({ class: 'user-link', href: `/author/@${encodeURIComponent(matches[0].feed)}` }, `@${matches[0].feed}`)
- )
- )
- );
- })
- ),
- section(
- form(
- { action, method: "post" },
- [
- input({ type: "hidden", name: "text", value: renderedText }),
- input({ type: "hidden", name: "contentWarning", value: contentWarning || "" }),
- input({ type: "hidden", name: "mentions", value: JSON.stringify(mentions) }),
- button({ type: "submit" }, i18n.publish)
- ]
- )
- )
- );
- };
- exports.previewView = ({ previewData, contentWarning }) => {
- const publishAction = "/publish";
- const preview = generatePreview({
- previewData,
- contentWarning,
- action: publishAction,
- });
- return exports.publishView(preview, previewData.text || "", contentWarning);
- };
- const viewInfoBox = ({ viewTitle = null, viewDescription = null }) => {
- if (!viewTitle && !viewDescription) {
- return null;
- }
- return section(
- { class: "viewInfo" },
- viewTitle ? h1(viewTitle) : null,
- viewDescription ? em(viewDescription) : null
- );
- };
- exports.likesView = async ({ messages, feed, name }) => {
- const authorLink = a(
- { href: `/author/${encodeURIComponent(feed)}` },
- "@" + name
- );
- return template(
- ["@", name],
- viewInfoBox({
- viewTitle: span(authorLink),
- viewDescription: span(i18n.spreadedDescription)
- }),
- messages.map((msg) => post({ msg }))
- );
- };
- const messageListView = ({
- messages,
- viewTitle = null,
- viewDescription = null,
- viewElements = null,
- aside = null,
- }) => {
- const hasHeader = !!viewElements;
- const titleBlock = hasHeader
- ? viewElements
- : div({ class: "tags-header" },
- h2(viewTitle),
- p(viewDescription)
- );
- return template(
- viewTitle,
- section(titleBlock),
- messages.map((msg) => post({ msg, aside }))
- );
- };
- exports.popularView = ({ messages, prefix }) => {
- const header = div({ class: "tags-header" },
- h2(i18n.popular),
- p(i18n.popularDescription)
- );
- return messageListView({
- messages,
- viewTitle: i18n.popular,
- viewElements: [header, prefix]
- });
- };
- exports.extendedView = ({ messages }) => {
- const header = div({ class: "tags-header" },
- h2(i18n.extended),
- p(i18n.extendedDescription)
- );
- return messageListView({
- messages,
- viewTitle: i18n.extended,
- viewElements: header
- });
- };
- exports.latestView = ({ messages }) => {
- const header = div({ class: "tags-header" },
- h2(i18n.latest),
- p(i18n.latestDescription)
- );
- return messageListView({
- messages,
- viewTitle: i18n.latest,
- viewElements: header
- });
- };
- exports.topicsView = ({ messages, prefix }) => {
- const header = div({ class: "tags-header" },
- h2(i18n.topics),
- p(i18n.topicsDescription)
- );
- return messageListView({
- messages,
- viewTitle: i18n.topics,
- viewElements: [header, prefix]
- });
- };
- exports.summaryView = ({ messages }) => {
- const header = div({ class: "tags-header" },
- h2(i18n.summaries),
- p(i18n.summariesDescription)
- );
- return messageListView({
- messages,
- viewTitle: i18n.summaries,
- viewElements: header,
- aside: true
- });
- };
- exports.spreadedView = ({ messages }) => {
- const header = div({ class: "tags-header" },
- h2(i18n.spreaded),
- p(i18n.spreadedDescription)
- );
- return spreadedListView({
- messages,
- viewTitle: i18n.spreaded,
- viewElements: header
- });
- };
- exports.threadsView = ({ messages }) => {
- const header = div({ class: "tags-header" },
- h2(i18n.threads),
- p(i18n.threadsDescription)
- );
- return messageListView({
- messages,
- viewTitle: i18n.threads,
- viewElements: header,
- aside: true
- });
- };
- exports.previewSubtopicView = async ({
- previewData,
- messages,
- myFeedId,
- contentWarning,
- }) => {
- const publishAction = `/subtopic/${encodeURIComponent(messages[0].key)}`;
- const preview = generatePreview({
- previewData,
- contentWarning,
- action: publishAction,
- });
- return exports.subtopicView(
- { messages, myFeedId },
- preview,
- previewData.text,
- contentWarning
- );
- };
- exports.subtopicView = async (
- { messages, myFeedId },
- preview,
- text,
- contentWarning
- ) => {
- const subtopicForm = `/subtopic/preview/${encodeURIComponent(
- messages[messages.length - 1].key
- )}`;
- let markdownMention;
- const messageElements = await Promise.all(
- messages.reverse().map((message) => {
- debug("%O", message);
- const authorName = message.value.meta.author.name;
- const authorFeedId = message.value.author;
- if (authorFeedId !== myFeedId) {
- if (message.key === messages[0].key) {
- const x = `[@${authorName}](${authorFeedId})\n\n`;
- markdownMention = x;
- }
- }
- return post({ msg: message });
- })
- );
- const authorName = messages[messages.length - 1].value.meta.author.name;
- return template(
- i18n.subtopicTitle({ authorName }),
- div({ class: "thread-container" }, messageElements),
- form(
- { action: subtopicForm, method: "post", enctype: "multipart/form-data" },
- i18n.blogSubject,
- br,
- label(
- i18n.contentWarningLabel,
- input({
- name: "contentWarning",
- type: "text",
- class: "contentWarning",
- value: contentWarning ? contentWarning : "",
- placeholder: i18n.contentWarningPlaceholder,
- })
- ),
- br,
- label({ for: "text" }, i18n.blogMessage),
- br,
- textarea(
- {
- autofocus: true,
- required: true,
- name: "text",
- rows: "6",
- cols: "50",
- placeholder: i18n.publishWarningPlaceholder,
- },
- text ? text : markdownMention
- ),
- br,
- label(
- { for: "blob" },
- i18n.blogImage || "Upload Image (jpeg, jpg, png, gif) (max-size: 500px x 400px)"
- ),
- input({ type: "file", id: "blob", name: "blob" }),
- br,
- br,
- button({ type: "submit" }, i18n.blogPublish)
- ),
- preview ? div({ class: "comment-preview" }, preview) : ""
- );
- };
|