123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405 |
- const { div, h2, p, section, button, form, a, input, img, label, select, option, br, textarea, h1 } = require("../server/node_modules/hyperaxe");
- const { template, i18n } = require('./main_views');
- const { config } = require('../server/SSB_server.js');
- const userId = config.keys.id;
- const paginateFeedTribesView = (feed, page = 1, itemsPerPage = 5) => {
- const startIndex = (page - 1) * itemsPerPage;
- return feed.slice(startIndex, startIndex + itemsPerPage);
- };
- const renderPaginationTribesView = (page, totalPages, filter) => {
- if (totalPages <= 1) return '';
- return div({ class: 'pagination' },
- page > 1 ? form({ method: 'GET', action: '/tribes' },
- input({ type: 'hidden', name: 'filter', value: filter }),
- input({ type: 'hidden', name: 'page', value: page - 1 }),
- button({ type: 'submit' }, i18n.previousPage)
- ) : null,
- page < totalPages ? form({ method: 'GET', action: '/tribes' },
- input({ type: 'hidden', name: 'filter', value: filter }),
- input({ type: 'hidden', name: 'page', value: page + 1 }),
- button({ type: 'submit' }, i18n.nextPage)
- ) : null
- );
- };
- const renderFeedTribesView = (tribe, page, query, filter) => {
- const feed = Array.isArray(tribe.feed) ? tribe.feed : [];
- const feedFilter = (query.feedFilter || 'TOP').toUpperCase();
- let filteredFeed = feed;
- if (feedFilter === 'MINE') filteredFeed = feed.filter(m => m.author === userId);
- if (feedFilter === 'RECENT') {
- const last24h = Date.now() - 86400000;
- filteredFeed = [...feed]
- .filter(m => {
- const msgDate = typeof m.date === "string" ? Date.parse(m.date) : m.date;
- return msgDate >= last24h;
- })
- .sort((a, b) => {
- const dateA = typeof a.date === "string" ? Date.parse(a.date) : a.date;
- const dateB = typeof b.date === "string" ? Date.parse(b.date) : b.date;
- return dateB - dateA;
- });
- }
- if (feedFilter === 'ALL') filteredFeed = [...feed].sort((a, b) => b.date - a.date);
- if (feedFilter === 'TOP') filteredFeed = [...feed].sort((a, b) => (b.refeeds || 0) - (a.refeeds || 0));
- const totalPages = Math.ceil(filteredFeed.length / 5);
- const paginatedFeed = paginateFeedTribesView(filteredFeed, page);
- return div({ class: 'tribe-feed' },
- div({ class: 'feed-actions', style: 'margin-bottom:8px;' },
- ['TOP', 'MINE', 'ALL', 'RECENT'].map(f =>
- form({ method: 'GET', action: '/tribes' },
- input({ type: 'hidden', name: 'filter', value: filter }),
- input({ type: 'hidden', name: 'feedFilter', value: f }),
- button({ type: 'submit', class: feedFilter === f ? 'active' : '' }, i18n[`tribeFeedFilter${f}`])
- )
- )
- ),
- paginatedFeed.length === 0
- ? p(i18n.tribeFeedEmpty)
- : div({ class: 'feed-list' },
- paginatedFeed.map(m => div({ class: 'feed-item' },
- div({ class: 'feed-row' },
- div({ class: 'refeed-column' },
- h1(`${m.refeeds || 0}`),
- !m.refeeds_inhabitants.includes(userId)
- ? form({ method: 'POST', action: `/tribes/${encodeURIComponent(tribe.id)}/refeed/${encodeURIComponent(m.id)}` }, button({ class: 'refeed-btn' }, i18n.tribeFeedRefeed))
- : p(i18n.alreadyRefeeded)
- ),
- div({ class: 'feed-main' },
- p(`${new Date(m.date).toLocaleString()} — `, a({ class: 'user-link', href: `/author/${encodeURIComponent(m.author)}` }, m.author)),
- p(m.message)
- )
- )
- ))
- ),
- tribe.members.includes(userId)
- ? form({ method: 'POST', action: `/tribes/${encodeURIComponent(tribe.id)}/message` },
- textarea({ name: 'message', rows: 3, cols: 50, maxlength: 280, placeholder: i18n.tribeFeedMessagePlaceholder }),
- button({ type: 'submit' }, i18n.tribeFeedSend)
- )
- : null,
- renderPaginationTribesView(page, totalPages, filter)
- );
- };
- const renderGallery = (sortedTribes) => {
- return div({ class: "gallery", style: 'display:grid; grid-template-columns: repeat(3, 1fr); gap:16px;' },
- sortedTribes.length
- ? sortedTribes.map(t =>
- a({ href: `#tribe-${encodeURIComponent(t.id)}`, class: "gallery-item" },
- img({ src: t.image ? `/blob/${encodeURIComponent(t.image)}` : '/assets/images/default-tribe.png', alt: t.title || "", class: "gallery-image" })
- )
- )
- : p(i18n.noTribes)
- );
- };
- const renderLightbox = (sortedTribes) => {
- return sortedTribes.map(t =>
- div(
- { id: `tribe-${encodeURIComponent(t.id)}`, class: "lightbox" },
- a({ href: "#", class: "lightbox-close" }, "×"),
- img({
- src: t.image ? `/blob/${encodeURIComponent(t.image)}` : '/assets/images/default-tribe.png',
- class: "lightbox-image",
- alt: t.title || ""
- })
- )
- );
- };
- exports.renderInvitePage = (inviteCode) => {
- const pageContent = div({ class: 'invite-page' },
- h2(i18n.tribeInviteCodeText, inviteCode),
- form({ method: "GET", action: `/tribes` },
- button({ type: "submit", class: "filter-btn" }, i18n.walletBack)
- ),
- );
- return template('Invite Page', section(pageContent));
- };
- exports.tribesView = async (tribes, filter, tribeId, query = {}) => {
- const now = Date.now();
- const search = (query.search || '').toLowerCase();
- const filtered = tribes.filter(t => {
- return (
- filter === 'all' ? t.isAnonymous === false :
- filter === 'mine' ? t.author === userId :
- filter === 'membership' ? t.members.includes(userId) :
- filter === 'recent' ? t.isAnonymous === false && ((typeof t.createdAt === 'string' ? Date.parse(t.createdAt) : t.createdAt) >= now - 86400000 ) :
- filter === 'top' ? t.isAnonymous === false :
- filter === 'gallery' ? t.isAnonymous === false :
- filter === 'larp' ? t.isAnonymous === false && t.isLARP === true :
- filter === 'create' ? true :
- filter === 'edit' ? true :
- false
- );
- });
- const searched = filter === 'create' || filter === 'edit' || !search
- ? filtered
- : filtered.filter(t =>
- (t.title && t.title.toLowerCase().includes(search)) ||
- (t.description && t.description.toLowerCase().includes(search))
- );
- const sorted = filter === 'top'
- ? [...searched].sort((a, b) => b.members.length - a.members.length)
- : [...searched].sort((a, b) => b.createdAt - a.createdAt);
- const title =
- filter === 'mine' ? i18n.tribeMineSectionTitle :
- filter === 'create' ? i18n.tribeCreateSectionTitle :
- filter === 'edit' ? i18n.tribeUpdateSectionTitle :
- filter === 'gallery' ? i18n.tribeGallerySectionTitle :
- filter === 'recent' ? i18n.tribeRecentSectionTitle :
- filter === 'top' ? i18n.tribeTopSectionTitle :
- filter === 'larp' ? i18n.tribeLarpSectionTitle :
- i18n.tribeAllSectionTitle;
- const header = div({ class: 'tags-header' }, h2(title), p(i18n.tribeDescription));
- const filters = div({ class: 'filters' },
- form({ method: 'GET', action: '/tribes' },
- input({ type: 'hidden', name: 'filter', value: filter }),
- input({ type: 'text', name: 'search', placeholder: i18n.searchTribesPlaceholder, value: query.search || '' }),
- br(),
- button({ type: 'submit' }, i18n.applyFilters),
- br()
- )
- );
- const modeButtons = div({ class: 'mode-buttons', style: 'display:flex; gap:8px; margin-top:16px;' },
- ['all','mine','membership','larp','recent','top','gallery'].map(f =>
- form({ method: 'GET', action: '/tribes' },
- input({ type: 'hidden', name: 'filter', value: f }),
- button({ type: 'submit', class: filter === f ? 'filter-btn active' : 'filter-btn' },
- i18n[`tribeFilter${f.charAt(0).toUpperCase()+f.slice(1)}`]
- )
- )
- ),
- form({ method: 'GET', action: '/tribes/create' },
- button({ type: 'submit', class: 'create-button' }, i18n.tribeCreateButton)
- )
- );
- const isEdit = filter === 'edit' && tribeId;
- const tribeToEdit = isEdit ? tribes.find(t => t.id === tribeId) : {};
- const createForm = (filter === 'create' || isEdit) ? div({ class: 'create-tribe-form' },
- h2(isEdit ? i18n.updateTribeTitle : i18n.createTribeTitle),
- form({
- method: 'POST',
- action: isEdit ? `/tribes/update/${encodeURIComponent(tribeToEdit.id)}` : '/tribes/create',
- enctype: 'multipart/form-data'
- },
- label({ for: 'title' }, i18n.tribeTitleLabel),
- br,
- input({ type: 'text', name: 'title', id: 'title', required: true, placeholder: i18n.tribeTitlePlaceholder, value: tribeToEdit.title || '' }),
- br(),
- label({ for: 'description' }, i18n.tribeDescriptionLabel),
- br,
- textarea({ name: 'description', id: 'description', required: true, rows: 4, cols: 50, placeholder: i18n.tribeDescriptionPlaceholder }, tribeToEdit.description || ''),
- br(),
- label({ for: 'location' }, i18n.tribeLocationLabel),
- br,
- input({ type: 'text', name: 'location', id: 'location', required: true, placeholder: i18n.tribeLocationPlaceholder, value: tribeToEdit.location || '' }),
- br(),
- label({ for: 'image' }, i18n.tribeImageLabel),
- br,
- input({ type: 'file', name: 'image', id: 'image', accept: 'image/*' }),
- br(), br(),
- label({ for: 'tags' }, i18n.tribeTagsLabel),
- br,
- input({ type: 'text', name: 'tags', id: 'tags', placeholder: i18n.tribeTagsPlaceholder, value: (tribeToEdit.tags || []).join(', ') }),
- br(),
- label({ for: 'isAnonymous' }, i18n.tribeIsAnonymousLabel),
- br,
- select({ name: 'isAnonymous', id: 'isAnonymous' },
- option({ value: 'true', selected: tribeToEdit.isAnonymous === true ? 'selected' : undefined }, i18n.tribePrivate),
- option({ value: 'false', selected: tribeToEdit.isAnonymous === false ? 'selected' : undefined }, i18n.tribePublic)
- ),
- br(), br(),
- label({ for: 'inviteMode' }, i18n.tribeModeLabel),
- br,
- select({ name: 'inviteMode', id: 'inviteMode' },
- option({ value: 'strict', selected: tribeToEdit.inviteMode === 'strict' ? 'selected' : undefined }, i18n.tribeStrict),
- option({ value: 'open', selected: tribeToEdit.inviteMode === 'open' ? 'selected' : undefined }, i18n.tribeOpen)
- ),
- br(), br(),
-
- // label({ for: 'isLARP' }, i18n.tribeIsLARPLabel),
- // br,
- // select({ name: 'isLARP', id: 'isLARP' },
- // option({ value: 'true', selected: tribeToEdit.isLARP === true ? 'selected' : undefined }, i18n.tribeYes),
- // option({ value: 'false', selected: tribeToEdit.isLARP === false ? 'selected' : undefined }, i18n.tribeNo)
- // ),
- // br(), br(),
-
- button({ type: 'submit' }, isEdit ? i18n.tribeUpdateButton : i18n.tribeCreateButton)
- )
- ) : null;
- const tribeCards = sorted.map(t => {
- const imageSrc = t.image
- ? `/blob/${encodeURIComponent(t.image)}`
- : '/assets/images/default-tribe.png';
- const infoCol = div({ class: 'tribe-card', style: 'width:50%' },
- filter === 'mine' ? div({ class: 'tribe-actions' },
- form({ method: 'GET', action: `/tribes/edit/${encodeURIComponent(t.id)}` }, button({ type: 'submit' }, i18n.tribeUpdateButton)),
- form({ method: 'POST', action: `/tribes/delete/${encodeURIComponent(t.id)}` }, button({ type: 'submit' }, i18n.tribeDeleteButton))
- ) : null,
- div({ style: 'display: flex; justify-content: space-between;' },
- form({ method: 'GET', action: `/tribe/${encodeURIComponent(t.id)}` },
- button({ type: 'submit', class: 'filter-btn' }, i18n.tribeviewTribeButton)
- ),
- h2(t.title)
- ),
- p(`${i18n.tribeIsAnonymousLabel}: ${t.isAnonymous ? i18n.tribePrivate : i18n.tribePublic}`),
- p(`${i18n.tribeModeLabel}: ${t.inviteMode.toUpperCase()}`),
- p(`${i18n.tribeLARPLabel}: ${t.isLARP ? i18n.tribeYes : i18n.tribeNo}`),
- img({ src: imageSrc }),
- p(t.description),
- p(`${i18n.tribeLocationLabel}: ${t.location}`),
- h2(`${i18n.tribeMembersCount}: ${t.members.length}`),
- t.tags && t.tags.filter(Boolean).length ? div(t.tags.filter(Boolean).map(tag =>
- a({ href: `/search?query=%23${encodeURIComponent(tag)}`, class: 'tag-link', style: 'margin-right:0.8em;margin-bottom:0.5em;' }, `#${tag}`)
- )) : null,
- p(`${i18n.tribeCreatedAt}: ${new Date(t.createdAt).toLocaleString()}`),
- p(a({ class: 'user-link', href: `/author/${encodeURIComponent(t.author)}` }, t.author)),
- t.members.includes(userId) ? div(
- form({ method: 'POST', action: '/tribes/generate-invite' },
- input({ type: 'hidden', name: 'tribeId', value: t.id }),
- button({ type: 'submit' }, i18n.tribeGenerateInvite)
- ),
- form({ method: 'POST', action: `/tribes/leave/${encodeURIComponent(t.id)}` }, button({ type: 'submit' }, i18n.tribeLeaveButton))
- ) : null
- );
- const feedCol = renderFeedTribesView(t, query.page || 1, query, filter);
- return div({ class: 'tribe-row', style: 'display:flex; gap:24px; margin-bottom:32px;' }, infoCol, feedCol);
- });
- return template(
- title,
- section(header),
- section(filters),
- section(modeButtons),
- section(
- (filter === 'create' || filter === 'edit')
- ? createForm
- : filter === 'gallery'
- ? renderGallery(sorted.filter(t => t.isAnonymous === false))
- : div({ class: 'tribe-grid', style: 'display:grid; grid-template-columns: repeat(3, 1fr); gap:16px;' },
- tribeCards.length > 0 ? tribeCards : p(i18n.noTribes)
- )
- ),
- ...renderLightbox(sorted.filter(t => t.isAnonymous === false))
- );
- };
- const renderFeedTribeView = async (tribe, query = {}, filter) => {
- const feed = Array.isArray(tribe.feed) ? tribe.feed : [];
- const feedFilter = (query.feedFilter || 'RECENT').toUpperCase();
- let filteredFeed = feed;
- if (feedFilter === 'MINE') {
- filteredFeed = feed.filter(m => m.author === userId);
- }
- if (feedFilter === 'RECENT') {
- const last24h = Date.now() - 86400000;
- filteredFeed = feed
- .filter(m => {
- const msgDate = typeof m.date === 'string' ? Date.parse(m.date) : m.date;
- return msgDate >= last24h;
- })
- .sort((a, b) => {
- const dateA = typeof a.date === 'string' ? Date.parse(a.date) : a.date;
- const dateB = typeof b.date === 'string' ? Date.parse(b.date) : b.date;
- return dateB - dateA;
- });
- }
- if (feedFilter === 'ALL') {
- filteredFeed = [...feed].sort((a, b) => b.date - a.date);
- }
- if (feedFilter === 'TOP') {
- filteredFeed = [...feed].sort((a, b) => (b.refeeds || 0) - (a.refeeds || 0));
- }
- return div({ class: 'tribe-feed-full' },
- div({ class: 'feed-actions', style: 'margin-bottom:8px;' },
- ['TOP', 'MINE', 'ALL', 'RECENT'].map(f =>
- form({ method: 'GET', action: `/tribe/${encodeURIComponent(tribe.id)}` },
- input({ type: 'hidden', name: 'filter', value: filter }),
- input({ type: 'hidden', name: 'feedFilter', value: f }),
- button({ type: 'submit', class: feedFilter === f ? 'active' : '' }, i18n[`tribeFeedFilter${f}`])
- )
- )
- ),
- filteredFeed.length === 0
- ? p(i18n.tribeFeedEmpty)
- : div({ class: 'feed-list' },
- filteredFeed.map(m => div({ class: 'feed-item' },
- div({ class: 'feed-row' },
- div({ class: 'refeed-column' },
- h1(`${m.refeeds || 0}`),
- !m.refeeds_inhabitants.includes(userId)
- ? form({ method: 'POST', action: `/tribe/${encodeURIComponent(tribe.id)}/refeed/${encodeURIComponent(m.id)}` }, button({ class: 'refeed-btn' }, i18n.tribeFeedRefeed))
- : p(i18n.alreadyRefeeded)
- ),
- div({ class: 'feed-main' },
- p(`${new Date(m.date).toLocaleString()} — `, a({ class: 'user-link', href: `/author/${encodeURIComponent(m.author)}` }, m.author)),
- p(m.message)
- )
- )
- ))
- )
- );
- };
- exports.tribeView = async (tribe, userId, query) => {
- if (!tribe) {
- return div({ class: 'error' }, 'Tribe not found!');
- }
- const feedFilter = (query.feedFilter || 'TOP').toUpperCase();
- const imageSrc = tribe.image
- ? `/blob/${encodeURIComponent(tribe.image)}`
- : '/assets/images/default-tribe.png';
- const pageTitle = tribe.title;
- const tribeDetails = div({ class: 'tribe-details' },
- h2(tribe.title),
- p(`${i18n.tribeIsAnonymousLabel}: ${tribe.isAnonymous ? i18n.tribePrivate : i18n.tribePublic}`),
- p(`${i18n.tribeModeLabel}: ${tribe.inviteMode.toUpperCase()}`),
- p(`${i18n.tribeLARPLabel}: ${tribe.isLARP ? i18n.tribeYes : i18n.tribeNo}`),
- img({ src: imageSrc, alt: tribe.title }),
- p(tribe.description),
- p(`${i18n.tribeLocationLabel}: ${tribe.location}`),
- h2(`${i18n.tribeMembersCount}: ${tribe.members.length}`),
- tribe.tags && tribe.tags.filter(Boolean).length ? div(tribe.tags.filter(Boolean).map(tag =>
- a({ href: `/search?query=%23${encodeURIComponent(tag)}`, class: 'tag-link', style: 'margin-right:0.8em;margin-bottom:0.5em;' }, `#${tag}`)
- )) : null,
- p(`${i18n.tribeCreatedAt}: ${new Date(tribe.createdAt).toLocaleString()}`),
- p(a({ class: 'user-link', href: `/author/${encodeURIComponent(tribe.author)}` }, tribe.author)),
- div({ class: 'tribe-feed-form' }, tribe.members.includes(config.keys.id)
- ? form({ method: 'POST', action: `/tribe/${encodeURIComponent(tribe.id)}/message` },
- textarea({ name: 'message', rows: 3, cols: 50, maxlength: 280, placeholder: i18n.tribeFeedMessagePlaceholder }),
- br,
- button({ type: 'submit' }, i18n.tribeFeedSend)
- )
- : null
- ),
- div({ class: 'tribe-feed-full' }, await renderFeedTribeView(tribe, query, query.filter)),
- );
- return template(
- pageTitle,
- tribeDetails
- );
- };
|