const { div, h2, p, section, button, form, img, a, textarea, input, br, span, strong } = require("../server/node_modules/hyperaxe"); const { template, i18n, userLink} = require('./main_views'); const { renderUrl } = require('../backend/renderUrl'); const { getConfig } = require('../configs/config-manager'); const DEFAULT_HASH_ENC = "%260000000000000000000000000000000000000000000%3D.sha256"; const DEFAULT_HASH_PATH_RE = /\/image\/\d+\/%260000000000000000000000000000000000000000000%3D\.sha256$/; function isDefaultImageId(v){ if (!v) return true; if (typeof v === 'string') { if (v === DEFAULT_HASH_ENC) return true; if (DEFAULT_HASH_PATH_RE.test(v)) return true; } return false; } function toImageUrl(imgId, size=256){ if (!imgId || isDefaultImageId(imgId)) return '/assets/images/default-avatar.png'; if (typeof imgId === 'string' && imgId.startsWith('/image/')) { return imgId.replace('/image/256/','/image/'+size+'/').replace('/image/512/','/image/'+size+'/'); } return `/image/${size}/${encodeURIComponent(imgId)}`; } function extractAboutImageId(about){ if (!about || typeof about !== 'object') return null; const aimg = about.image; if (!aimg) return null; if (typeof aimg === 'string') return aimg; return aimg.link || aimg.url || null; } function resolvePhoto(photoField, size = 256) { if (!photoField) return '/assets/images/default-avatar.png'; if (typeof photoField === 'string') { if (photoField.startsWith('/assets/')) return photoField; if (photoField.startsWith('/blob/')) return photoField; if (photoField.startsWith('/image/')) { if (isDefaultImageId(photoField)) return '/assets/images/default-avatar.png'; return photoField.replace('/image/256/','/image/'+size+'/').replace('/image/512/','/image/'+size+'/'); } } return toImageUrl(photoField, size); } const generateFilterButtons = (filters, currentFilter) => filters.map(mode => form({ method: 'GET', action: '/inhabitants' }, input({ type: 'hidden', name: 'filter', value: mode }), button({ type: 'submit', class: currentFilter === mode ? 'filter-btn active' : 'filter-btn' }, i18n[mode + 'Button'] || i18n[mode + 'SectionTitle'] || mode) ) ); function lastActivityBadge(user, isMe) { const bucket = user && user.lastActivityBucket; const dotClass = bucket === 'green' ? 'green' : bucket === 'orange' ? 'orange' : bucket === 'red' ? 'red' : null; if (!dotClass) return []; const items = [ span({ class: 'inhabitant-last-activity' }, `${i18n.inhabitantActivityLevel}: `, span({ class: `activity-dot ${dotClass}` }, '●')) ]; const currentTheme = getConfig().themes.current; const src = isMe ? (currentTheme === 'OasisKIT' ? 'KIT' : (currentTheme === 'OasisMobile' || process.env.OASIS_MOBILE === '1') ? 'MOBILE' : 'DESKTOP') : (user && user.deviceSource) || null; if (src) { const upper = String(src).toUpperCase(); const deviceClass = upper === 'KIT' ? 'device-kit' : upper === 'MOBILE' ? 'device-mobile' : 'device-desktop'; items.push(span({ class: 'inhabitant-last-activity' }, `${i18n.deviceLabel || 'Device'}: `, span({ class: deviceClass }, src))); } return [div({ class: 'inhabitant-activity-group' }, ...items)]; } const lightboxId = (id) => 'inhabitant_' + String(id || 'unknown').replace(/[^a-zA-Z0-9_-]/g, '_'); const renderInhabitantCard = (user, filter, currentUserId) => { const isMe = user.id === currentUserId; return div({ class: 'inhabitant-card' }, div({ class: 'inhabitant-left' }, a( { href: `/author/${encodeURIComponent(user.id)}` }, img({ class: 'inhabitant-photo-details', src: resolvePhoto(user.photo, 256), alt: user.name || 'Anonymous' }) ), br(), ...lastActivityBadge(user, isMe), div({ class: 'inhabitant-karma-ubi' }, span({ class: 'karma-line' }, `${i18n.bankingUserEngagementScore}: `, strong(String(typeof user.karmaScore === 'number' ? user.karmaScore : 0))) ) ), div({ class: 'inhabitant-details' }, h2(user.name || 'Anonymous'), user.description ? p(...renderUrl(user.description)) : null, filter === 'MATCHSKILLS' && user.commonSkills?.length ? div({ class: 'matchskills' }, p(`${i18n.commonSkills}: ${user.commonSkills.join(', ')}`), p(`${i18n.matchScore}: ${Math.round(user.matchScore * 100)}%`) ) : null, filter === 'SUGGESTED' && user.mutualCount ? p(`${i18n.mutualFollowers}: ${user.mutualCount}`) : null, filter === 'blocked' && user.isBlocked ? p(i18n.blockedLabel) : null, p(userLink(user.id)), user.ecoAddress ? div({ class: "eco-wallet" }, p(`${i18n.bankWalletConnected}: `, strong(user.ecoAddress)) ) : div({ class: "eco-wallet" }, p(i18n.ecoWalletNotConfigured || "ECOin Wallet not configured") ), div( { class: 'cv-actions' }, !isMe ? form( { method: 'GET', action: `/inhabitant/${encodeURIComponent(user.id)}` }, button({ type: 'submit', class: 'btn' }, i18n.inhabitantviewDetails) ) : p(i18n.relationshipYou), !isMe ? form( { method: 'GET', action: '/pm' }, input({ type: 'hidden', name: 'recipients', value: user.id }), button({ type: 'submit', class: 'btn' }, i18n.pmCreateButton) ) : null ) ) ); }; const renderGalleryInhabitants = inhabitants => div( { class: "gallery" }, inhabitants.length ? inhabitants.map(u => a({ href: `#${lightboxId(u.id)}`, class: "gallery-item" }, img({ src: resolvePhoto(u.photo, 256), alt: u.name || "Anonymous", class: "gallery-image" }) ) ) : p(i18n.noInhabitantsFound) ); const renderLightbox = inhabitants => inhabitants.map(u => div( { id: lightboxId(u.id), class: "lightbox" }, a({ href: "#", class: "lightbox-close" }, "×"), img({ src: resolvePhoto(u.photo, 256), class: "lightbox-image", alt: u.name || "Anonymous" }) ) ); function stripAndCollectImgs(text) { if (!text || typeof text !== 'string') return { clean: '', imgs: [] }; const imgs = []; let clean = text; const rawImgRe = /]*src="([^"]+)"[^>]*>/gi; clean = clean.replace(rawImgRe, (_, src) => { imgs.push(src); return ''; }); const encImgRe = /<img[^&]*src="([^&]*)"[^&]*>/gi; clean = clean.replace(encImgRe, (_, src) => { imgs.push(src.replace(/&/g, '&')); return ''; }); return { clean, imgs }; } function msgIdOf(m) { return m && (m.key || m.value?.key || m.value?.content?.root || m.value?.content?.branch || null); } exports.inhabitantsView = (inhabitants, filter, query, currentUserId) => { const title = filter === 'contacts' ? i18n.yourContacts : filter === 'CVs' ? i18n.allCVs : filter === 'MATCHSKILLS' ? i18n.matchSkills : filter === 'SUGGESTED' ? i18n.suggestedSectionTitle : filter === 'blocked' ? i18n.blockedSectionTitle : filter === 'GALLERY' ? i18n.gallerySectionTitle : filter === 'TOP KARMA' ? i18n.topkarmaSectionTitle : filter === 'TOP ACTIVITY' ? i18n.topactivitySectionTitle : i18n.allInhabitants; const showCVFilters = filter === 'CVs' || filter === 'MATCHSKILLS'; const filters = ['all', 'TOP ACTIVITY', 'TOP KARMA', 'contacts', 'SUGGESTED', 'blocked', 'CVs', 'MATCHSKILLS', 'GALLERY']; return template( title, section( div({ class: 'tags-header' }, h2(title), p(i18n.discoverPeople) ), div({ class: 'filters' }, form({ method: 'GET', action: '/inhabitants' }, input({ type: 'hidden', name: 'filter', value: filter }), input({ type: 'text', name: 'search', placeholder: i18n.searchInhabitantsPlaceholder, value: (query && query.search) || '' }), showCVFilters ? [ input({ type: 'text', name: 'location', placeholder: i18n.filterLocation, value: (query && query.location) || '' }), input({ type: 'text', name: 'language', placeholder: i18n.filterLanguage, value: (query && query.language) || '' }), input({ type: 'text', name: 'skills', placeholder: i18n.filterSkills, value: (query && query.skills) || '' }) ] : null, br(), button({ type: 'submit' }, i18n.applyFilters) ) ), div({ class: 'inhabitant-action' }, ...generateFilterButtons(filters, filter) ), filter === 'GALLERY' ? renderGalleryInhabitants(inhabitants) : div({ class: 'inhabitants-list' }, inhabitants.length ? inhabitants.map(user => renderInhabitantCard(user, filter, currentUserId)) : p({ class: 'no-results' }, i18n.noInhabitantsFound) ), ...renderLightbox(inhabitants) ) ); }; exports.inhabitantsProfileView = (payload, currentUserId) => { const safe = payload && typeof payload === 'object' ? payload : {}; const about = (safe.about && typeof safe.about === 'object') ? safe.about : {}; const cv = (safe.cv && typeof safe.cv === 'object') ? safe.cv : {}; const feed = Array.isArray(safe.feed) ? safe.feed : []; const viewedId = typeof safe.viewedId === 'string' ? safe.viewedId : ''; const id = (cv && cv.author) || (about && about.about) || viewedId || ''; const baseName = ((cv && cv.name) || (about && about.name) || '').trim(); const name = baseName || (i18n.unnamed || 'Anonymous'); const description = (cv && cv.description) || (about && about.description) || ''; const listPhoto = (typeof safe.photo === 'string' && safe.photo.trim()) ? safe.photo : null; const rawCandidate = listPhoto || extractAboutImageId(about) || (cv && cv.photo) || null; const image = ( typeof rawCandidate === 'string' && rawCandidate.startsWith('/image/') && !DEFAULT_HASH_PATH_RE.test(rawCandidate) && rawCandidate.indexOf(DEFAULT_HASH_ENC) === -1 ) ? rawCandidate.replace('/image/512/','/image/256/').replace('/image/1024/','/image/256/') : resolvePhoto(rawCandidate, 256); const location = (cv && cv.location) || ''; const languages = typeof (cv && cv.languages) === 'string' ? (cv.languages || '').split(',').map(x => x.trim()).filter(Boolean) : Array.isArray(cv && cv.languages) ? cv.languages : []; const skills = [ ...((cv && cv.personalSkills) || []), ...((cv && cv.oasisSkills) || []), ...((cv && cv.educationalSkills) || []), ...((cv && cv.professionalSkills) || []) ]; const status = (cv && cv.status) || ''; const preferences = (cv && cv.preferences) || ''; const createdAt = (cv && cv.createdAt) ? new Date(cv.createdAt).toLocaleString() : ''; const isMe = id && id === currentUserId; const title = i18n.inhabitantProfileTitle || i18n.inhabitantviewDetails; const karmaScore = typeof safe.karmaScore === 'number' ? safe.karmaScore : 0; const providedBucket = typeof safe.lastActivityBucket === 'string' ? safe.lastActivityBucket : null; const dotClass = providedBucket === 'green' ? 'green' : providedBucket === 'orange' ? 'orange' : 'red'; const detailNodes = [ description ? p(...renderUrl(description)) : null, location ? p(`${i18n.locationLabel}: ${location}`) : null, languages.length ? p(`${i18n.languagesLabel}: ${languages.join(', ').toUpperCase()}`) : null, skills.length ? p(`${i18n.skillsLabel}: ${skills.join(', ')}`) : null, status ? p(`${i18n.statusLabel || 'Status'}: ${status}`) : null, preferences ? p(`${i18n.preferencesLabel || 'Preferences'}: ${preferences}`) : null, createdAt ? p(`${i18n.createdAtLabel || 'Created at'}: ${createdAt}`) : null ].filter(Boolean); return template( name, section( div({ class: 'tags-header' }, h2(title), p(i18n.discoverPeople) ), div({ class: 'mode-buttons' }, ...generateFilterButtons(['all', 'TOP ACTIVITY', 'TOP KARMA', 'contacts', 'SUGGESTED', 'blocked', 'CVs', 'MATCHSKILLS', 'GALLERY'], 'all') ), div({ class: 'inhabitant-card' }, div({ class: 'inhabitant-left' }, img({ class: 'inhabitant-photo-details', src: image, alt: name || 'Anonymous' }), h2(name || 'Anonymous'), ...lastActivityBadge({ lastActivityBucket: dotClass, deviceSource: safe.deviceSource }, isMe), div({ class: 'inhabitant-karma-ubi' }, span({ class: 'karma-line' }, `${i18n.bankingUserEngagementScore}: `, strong(String(karmaScore))) ), (!isMe && (id || viewedId)) ? form( { method: 'GET', action: '/pm' }, input({ type: 'hidden', name: 'recipients', value: id || viewedId }), button({ type: 'submit', class: 'btn' }, i18n.pmCreateButton) ) : null ), detailNodes.length ? div({ class: 'inhabitant-details' }, ...detailNodes) : null ), feed.length ? section({ class: 'profile-feed' }, h2(i18n.latestInteractions), ...feed.map(m => { const raw = (m.value?.content?.text || '').replace(//g, ''); const parts = stripAndCollectImgs(raw); const tid = msgIdOf(m); const visitBtn = tid ? form({ method: 'GET', action: `/thread/${encodeURIComponent(tid)}#${encodeURIComponent(tid)}` }, button({ type:'submit', class:'filter-btn' }, i18n.visitContent) ) : null; return div({ class: 'post' }, visitBtn, parts.clean && parts.clean.trim() ? p(...renderUrl(parts.clean)) : null, ...(parts.imgs || []).map(src => img({ src, class: 'post-image', alt: 'image' })) ); }) ) : null ) ); }; exports.lastActivityBadge = lastActivityBadge;