123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704 |
- "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 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, head, header, hr, html, img, input, label, li, link, main, meta, nav, option, p, pre, section, select, span, summary, textarea, title, tr, ul } = 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 }) =>
- li(
- a(
- { href, class: current ? "current" : "" },
- 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 renderPopularLink = () => {
- const popularMod = getConfig().modules.popularMod === 'on';
- if (popularMod) {
- return [
- navLink({ href: "/public/popular/day", emoji: "⌘", text: i18n.popular, class: "popular-link enabled" }),
- hr,
- ];
- }
- return '';
- };
- 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';
- if (bookmarksMod) {
- return [
- hr(),
- navLink({ href: "/bookmarks", emoji: "ꔪ", text: i18n.bookmarksLabel, class: "bookmark-link enabled" }),
- ];
- }
- return '';
- };
- 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
- ? [
- hr,
- navLink({ href: "/public/latest/extended", emoji: "∞", text: i18n.multiverse, class: "multiverse-link enabled" })
- ]
- : '';
- };
- const renderMarketLink = () => {
- const marketMod = getConfig().modules.marketMod === 'on';
- return marketMod
- ? [
- hr(),
- navLink({ href: "/market", emoji: "ꕻ", text: i18n.marketTitle }),
- ]
- : '';
- };
- const renderTribesLink = () => {
- const tribesMod = getConfig().modules.tribesMod === 'on';
- return tribesMod
- ? [
- navLink({ href: "/tribes", emoji: "ꖥ", text: i18n.tribesTitle, class: "tribes-link enabled" }),
- hr(),
- ]
- : '';
- };
- const renderGovernanceLink = () => {
- const governanceMod = getConfig().modules.governanceMod === 'on';
- return governanceMod
- ? [
- navLink({ href: "/votes", emoji: "ꔰ", text: i18n.governanceTitle, class: "votes-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
- ? [
- hr(),
- 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 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: "/profile", emoji: "⚉", text: i18n.profile }),
- navLink({ href: "/cv", emoji: "ꕛ", text: i18n.cvTitle }),
- renderLegacyLink(),
- renderWalletLink(),
- navLink({ href: "/peers", emoji: "⧖", text: i18n.peers }),
- renderInvitesLink(),
- navLink({ href: "/modules", emoji: "ꗣ", text: i18n.modules }),
- navLink({ href: "/settings", emoji: "⚙", text: i18n.settings })
- )
- )
- ),
- div(
- { class: "top-bar-right" },
- nav(
- ul(
- renderCipherLink(),
- navLink({ href: "/pm", emoji: "ꕕ", text: i18n.privateMessage }),
- navLink({ href: "/publish", emoji: "❂", text: i18n.publish }),
- renderTagsLink(),
- renderAILink(),
- navLink({ href: "/search", emoji: "ꔅ", text: i18n.search })
- )
- ),
- )
- ),
- div(
- { class: "main-content" },
- div(
- { class: "sidebar-left" },
- nav(
- ul(
- navLink({ href: "/mentions", emoji: "✺", text: i18n.mentions }),
- navLink({ href: "/inbox", emoji: "☂", text: i18n.inbox }),
- renderAgendaLink(),
- navLink({ href: "/stats", emoji: "ꕷ", text: i18n.statistics }),
- hr,
- renderLatestLink(),
- renderThreadsLink(),
- renderTopicsLink(),
- renderSummariesLink(),
- renderPopularLink(),
- navLink({ href: "/inhabitants", emoji: "ꖘ", text: i18n.inhabitantsLabel }),
- renderTribesLink(),
- renderGovernanceLink(),
- renderEventsLink(),
- renderTasksLink(),
- renderReportsLink(),
- renderMultiverseLink()
- )
- )
- ),
- main({ id: "content", class: "main-column" }, elements),
- div(
- { class: "sidebar-right" },
- nav(
- ul(
- navLink({ href: "/activity", emoji: "ꔙ", text: i18n.activityTitle }),
- renderTrendingLink(),
- renderOpinionsLink(),
- renderFeedLink(),
- renderPixeliaLink(),
- renderMarketLink(),
- renderTransfersLink(),
- 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");
- 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 { name } = msg.value?.meta?.author || { name: "Anonymous" };
- const markdownContent = msg.value?.content?.text;
- const emptyContent = "<p>undefined</p>\n";
- const articleElement =
- markdownContent === emptyContent
- ? article(
- { class: "content" },
- pre({
- innerHTML: highlightJs.highlight(
- JSON.stringify(msg, null, 2),
- { language: "json", ignoreIllegals: true }
- ).value,
- })
- )
- : article({ class: "content", innerHTML: markdownContent });
- 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;
- if (!ts_received || !ts_received.iso8601 || !moment(ts_received.iso8601, moment.ISO_8601, true).isValid()) {
- return null;
- }
- const validTimestamp = moment(ts_received.iso8601, 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((name) => name.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 postOptions = {
- post: null,
- comment: i18n.commentDescription({ parentUrl: url.parent }),
- subtopic: i18n.subtopicDescription({ parentUrl: url.parent }),
- mystery: i18n.mysteryDescription,
- };
- 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), ` ${i18n.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",
- },
- description
- )
- ),
- br,
- button(
- {
- type: "submit",
- },
- i18n.submit
- )
- )
- )
- );
- exports.authorView = ({
- avatarUrl,
- description,
- feedId,
- messages,
- firstPost,
- lastPost,
- name,
- relationship,
- }) => {
- 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 messages = [];
- messages.push(
- following
- ? i18n.relationshipFollowing
- : i18n.relationshipNone
- );
- messages.push(
- followsMe
- ? i18n.relationshipTheyFollow
- : i18n.relationshipNotFollowing
- );
- return messages.join(". ") + ".";
- })();
- const prefix = section(
- { class: "message" },
- div(
- { class: "profile" },
- div({ class: "avatar-container" },
- img({ class: "avatar", src: avatarUrl }),
- h1({ class: "name" }, name),
- ),
- pre({
- class: "md-mention",
- innerHTML: markdownMention,
- })
- ),
- 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)
- )
- )
- );
- const linkUrl = relationship.me
- ? "/profile/"
- : `/author/${encodeURIComponent(feedId)}/`;
- 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(messages[0].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
- ) => {
- let markdownMention;
- const authorName = parentMessage?.value?.meta?.author?.name || "Anonymous";
-
- const messageElements = await Promise.all(
- messages.reverse().map(async (message) => {
- const isRootMessage = message.key === parentMessage.key;
- const messageAuthorName = message.value?.meta?.author?.name || "Anonymous";
- const authorFeedId = myFeedId;
-
- if (authorFeedId !== myFeedId) {
- if (message.key === parentMessage.key) {
- const x = `[@${messageAuthorName}](${authorFeedId})\n\n`;
- markdownMention = x;
- }
- }
- const timestamp = message?.value?.meta?.timestamp?.received;
- const validTimestamp = moment(timestamp, moment.ISO_8601, true);
- const timeAgo = validTimestamp.isValid()
- ? validTimestamp.fromNow()
- : "Invalid time";
- const messageId = message.key.endsWith('.sha256') ? message.key.slice(0, -7) : message.key;
- const result = await post({ msg: { ...message, key: messageId } });
- return result;
- })
- );
- const action = `/comment/preview/${encodeURIComponent(messages[0].key)}`;
- const method = "post";
- const isPrivate = parentMessage?.value?.meta?.private;
- const publicOrPrivate = isPrivate ? i18n.commentPrivate : i18n.commentPublic;
- const maybeSubtopicText = isPrivate ? [null] : i18n.commentWarning;
- 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 : isPrivate ? null : 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) : ""
- );
- };
- 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).length > 0;
- });
- 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 (input, filter) => {
- const messages = Array.isArray(input) ? input : input.messages;
- const userId = await getUserId();
- const counts = {
- inbox: messages.filter(m => m.value.content.to?.includes(userId)).length,
- sent: messages.filter(m => m.value.content.from === userId).length
- };
- const filtered =
- filter === 'sent' ? messages.filter(m => m.value.content.from === userId) :
- filter === 'inbox' ? messages.filter(m => m.value.content.to?.includes(userId)) :
- messages;
- 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),
- button({
- type: 'submit',
- name: 'filter',
- value: 'sent',
- class: filter === 'sent' ? 'filter-btn active' : 'filter-btn'
- }, i18n.privateSent),
- button({
- type: 'submit',
- name: 'filter',
- value: 'create',
- class: 'create-button',
- formaction: '/pm',
- formmethod: 'GET'
- }, i18n.pmCreateButton)
- ])
- ),
- div({ class: 'message-list' },
- filtered.length
- ? filtered.map(msg => {
- const content = msg?.value?.content;
- const author = msg?.value?.author;
- if (!content || !author) {
- return div({ class: 'malformed-message' }, 'Invalid message');
- }
- const subject = content.subject || '(no subject)';
- const text = content.text || '';
- const sentAt = new Date(content.sentAt || msg.timestamp).toLocaleString();
- const from = content.from;
- const toLinks = (content.to || []).map(addr =>
- a({ class: 'user-link', href: `/author/${encodeURIComponent(addr)}` }, addr)
- );
- return div({ class: 'message-item' },
- p(subject),
- div({ class: 'message-text' }, text),
- p({ class: 'card-footer' },
- span({ class: 'date-link' }, `${sentAt} ${i18n.performed} `),
- a({ href: `/author/${encodeURIComponent(from)}`, class: 'user-link' }, `${from}`)
- ),
- form({ method: 'POST', action: `/inbox/delete/${encodeURIComponent(msg.key)}`, class: 'delete-message-form' },
- button({ type: 'submit', class: 'delete-btn' }, i18n.privateDelete)
- )
- );
- })
- : 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 rootSnippet = postSnippet(
- lodash.get(rootMessage, "value.content.text", i18n.mysteryDescription)
- );
- return template([`@${rootAuthorName}`],
- div(
- thread(messages)
- )
- );
- };
- 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 }), // Pass the formatted text
- 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.formattedText, 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) : ""
- );
- };
|