UX/UI redesign for the mobile WebView

- Compact top bar (logo, OASIS, user avatar with default fallback).
- Fixed bottom navigation (Search, Inbox, Write, Activity + Peers/Invites) with active state.
- Removed duplicated header nav buttons on mobile.
- Personal/Community submenus: single-open accordion.
- Activity filters grouped into quick modes + collapsible thematic groups.
- Private messages rendered as chat bubbles on mobile.
- Text fixes, full-width stacked tables, touch feedback; Activity group labels in 11 locales.
Mobile-scoped (mobile.css + views); desktop unaffected.
This commit is contained in:
s1to 2026-07-09 09:56:30 +02:00
parent e563b86ede
commit ea5f5cbcc2
16 changed files with 847 additions and 35 deletions

View file

@ -972,6 +972,12 @@ const getOasisVersion = async (feedId) => {
if (OASIS_VERSION && mine !== OASIS_VERSION) {
ssb.publish({ type: 'oasisVersion', version: OASIS_VERSION, updatedAt: new Date().toISOString() }, () => {});
}
try {
const img = await about.image(ssb.id);
global.__OASIS_SELF_AVATAR__ = (typeof img === 'string' && img[0] === '&')
? getAvatarUrl(img)
: '/assets/images/default-avatar.png';
} catch (_) {}
} catch (_) {}
})();
async function renderBlobMarkdown(text, mentions = {}, myFeedId, myUsername) {
@ -2802,6 +2808,7 @@ router
inhabitantsModel.getLastActivityTimestampByUserId(myFeedId).catch(() => null)
]);
const larpHouse = larpHouseKey ? { key: larpHouseKey, ...larpModel.getHouse(larpHouseKey) } : null;
global.__OASIS_SELF_AVATAR__ = getAvatarUrl(image);
const stats = await inhabitantsModel.getInhabitantStats(myFeedId, myFeedId).catch(() => ({}));
const userActions = (allActions || []).filter(a => a && a.author === myFeedId && a.type !== 'tombstone' && a.type !== 'post');
const normTs = t => { const n = Number(t || 0); return !isFinite(n) || n <= 0 ? 0 : n < 1e12 ? n * 1000 : n; };

View file

@ -914,3 +914,620 @@ a.create-button {
.peers-technical-block .block-info-table td[data-label=""]::before { content: none !important; }
.peers-list .block-info-table td .peer-key,
.peers-technical-block .block-info-table td .peer-key { text-align: right !important; }
.oasis-bottombar {
display: flex !important;
position: fixed !important;
left: 0 !important; right: 0 !important; bottom: 0 !important;
z-index: 1000 !important;
justify-content: space-around !important;
align-items: center !important;
gap: 4px !important;
background: #1F1F1F !important;
border-top: 1px solid #333 !important;
padding: 8px 6px calc(8px + env(safe-area-inset-bottom, 0px)) !important;
}
.oasis-bottombar .bb-item {
position: relative !important;
display: flex !important;
flex-direction: column !important;
align-items: center !important;
gap: 3px !important;
flex: 1 1 0 !important;
min-width: 0 !important;
padding: 2px 0 !important;
text-decoration: none !important;
}
.oasis-bottombar .bb-ico { font-size: 18px !important; line-height: 1 !important; color: #FFA500 !important; }
.oasis-bottombar .bb-lbl { font-size: 9px !important; color: #ccc !important; white-space: nowrap !important; }
.oasis-bottombar .bb-badge {
position: absolute !important;
top: -4px !important;
left: calc(50% + 6px) !important;
min-width: 15px !important; height: 15px !important;
padding: 0 3px !important;
border-radius: 8px !important;
background: #FFA500 !important;
color: #121212 !important;
font-size: 9px !important; font-weight: bold !important;
display: flex !important; align-items: center !important; justify-content: center !important;
}
body { padding-bottom: 64px !important; }
.oasis-mobile-topbar {
display: flex !important;
align-items: center !important;
justify-content: space-between !important;
width: 100% !important;
padding: 8px 12px !important;
background: transparent !important;
box-shadow: none !important; border: 0 !important;
}
.oasis-mobile-topbar .omt-logo img,
.oasis-mobile-topbar .omt-avatar img {
width: 32px !important; height: 32px !important;
border-radius: 50% !important;
border: 2px solid #FFA500 !important;
object-fit: cover !important; display: block !important;
}
.oasis-mobile-topbar .omt-title {
flex: 1 1 auto !important;
text-align: center !important;
font-size: 26px !important; font-weight: bold !important;
letter-spacing: 10px !important; text-indent: 10px !important;
color: #FFD700 !important; text-decoration: none !important;
white-space: nowrap !important;
background: transparent !important; border: 0 !important; box-shadow: none !important; padding: 0 !important;
}
.header .top-bar-left > a.logo-icon { display: none !important; }
.oasis-mobile-menus { display: flex !important; gap: 8px !important; padding: 0 10px 6px !important; }
.oasis-mobile-menus .oasis-menu-ico { display: none !important; }
.oasis-mobile-menus .oasis-menu-btn {
flex: 1 1 0 !important;
display: flex !important; align-items: center !important; justify-content: center !important;
padding: 12px !important;
border: 1.5px solid #333 !important; border-radius: 10px !important;
background: #121212 !important; color: #ccc !important;
font-weight: bold !important; letter-spacing: 1px !important; cursor: pointer !important;
}
#nav-left-toggle:checked ~ .header .oasis-menu-btn[for="nav-left-toggle"] {
background: #FFA500 !important; color: #121212 !important; border-color: #FFA500 !important;
}
#nav-right-toggle:checked ~ .header .oasis-menu-btn[for="nav-right-toggle"] {
background: #FFD700 !important; color: #121212 !important; border-color: #FFD700 !important;
}
:root {
--space-1: 4px; --space-2: 8px; --space-3: 16px; --space-4: 24px; --space-5: 32px;
--fs-caption: 12px; --fs-body: 14px; --fs-body-lg: 16px; --fs-title: 20px;
--lh-body: 1.5; --measure: 66ch;
--c-muted: #8c8c8c; --radius-md: 12px;
}
.oasis-bottombar .bb-fab .bb-ico {
width: 46px !important; height: 46px !important;
margin-top: -22px !important;
border-radius: 50% !important;
background: #FFA500 !important; color: #121212 !important;
display: flex !important; align-items: center !important; justify-content: center !important;
font-size: 22px !important;
box-shadow: 0 2px 8px rgba(0,0,0,.4) !important;
}
.oasis-bottombar .bb-fab .bb-lbl { color: #FFA500 !important; font-weight: bold !important; }
.main-column, .main-column p, .main-column li, .main-column a,
.main-column span, .main-column td, .main-column h1, .main-column h2, .main-column h3 {
overflow-wrap: anywhere !important;
word-break: break-word !important;
}
.main-column { padding: var(--space-3) !important; }
.main-column p { line-height: var(--lh-body) !important; }
.main-column > * + * { margin-top: var(--space-3) !important; }
.main-column img, .main-column video, .main-column table, .main-column pre, .main-column iframe {
max-width: 100% !important; height: auto !important;
}
.main-column pre { white-space: pre-wrap !important; }
.oasis-nav-group {
list-style: none !important;
background: #1c1c1c !important;
border: 1px solid #333 !important;
border-radius: var(--radius-md) !important;
overflow: hidden !important;
margin-bottom: var(--space-2) !important;
}
.oasis-nav-header {
display: flex !important; align-items: center !important; gap: var(--space-2) !important;
min-height: 48px !important; padding: 0 var(--space-3) !important;
cursor: pointer !important;
color: #FFD700 !important; font-weight: bold !important;
text-transform: uppercase !important; letter-spacing: .04em !important;
font-size: var(--fs-caption) !important;
}
.oasis-nav-header .nav-text { flex: 1 1 auto !important; }
.oasis-nav-header .oasis-nav-arrow { color: var(--c-muted) !important; transition: transform 150ms ease !important; }
.oasis-nav-toggle:checked + .oasis-nav-header .oasis-nav-arrow { transform: rotate(180deg) !important; }
.oasis-nav-header:active { background: #262626 !important; }
.oasis-nav-list { list-style: none !important; padding: 0 !important; margin: 0 !important; }
.oasis-nav-list li a {
display: flex !important; align-items: center !important; gap: var(--space-3) !important;
min-height: 48px !important; padding: 0 var(--space-3) !important;
color: #e8e8e8 !important; text-decoration: none !important;
border-top: 1px solid #262626 !important;
}
.oasis-nav-list li a .emoji { flex: 0 0 auto !important; width: 22px !important; text-align: center !important; color: #FFA500 !important; }
.oasis-nav-list li a .nav-text { font-size: var(--fs-body) !important; }
.oasis-nav-list li a:active { background: #262626 !important; }
.oasis-nav-list li a.current { color: #FFA500 !important; font-weight: bold !important; }
.main-column h1 { font-size: var(--fs-title) !important; font-weight: 700 !important; line-height: 1.25 !important; margin: 0 0 var(--space-2) !important; }
.main-column h2 { font-size: 18px !important; font-weight: 700 !important; line-height: 1.3 !important; margin: 0 0 var(--space-2) !important; }
.main-column h3 { font-size: var(--fs-body-lg) !important; font-weight: 600 !important; margin: 0 0 var(--space-1) !important; }
.main-column p { font-size: var(--fs-body-lg) !important; }
.main-column .created-at, .main-column .small, .main-column .time,
.main-column .meta, .main-column .author-meta, .main-column .subtitle {
color: var(--c-muted) !important; font-size: var(--fs-caption) !important;
}
.main-column article,
.main-column [class*="-card"] {
background: #1c1c1c !important;
border: 1px solid #333 !important;
border-radius: var(--radius-md) !important;
padding: var(--space-3) !important;
}
.main-column article:active,
.main-column [class*="-card"]:active { background: #202020 !important; }
.btn, button, input[type="submit"], input[type="button"], .submit-button,
.filter-btn, .create-button, .edit-btn, .delete-btn, .join-btn, .leave-btn, .buy-btn {
min-height: 48px !important;
}
button:active, input[type="submit"]:active, .submit-button:active,
.filter-btn:active, .create-button:active, .edit-btn:active,
.delete-btn:active, .join-btn:active, .leave-btn:active, .buy-btn:active {
filter: brightness(.85) !important;
}
.main-column a:active { opacity: .6 !important; }
* { -webkit-tap-highlight-color: transparent !important; }
a, button, label, .tap, .oasis-menu-btn, .bb-item, .oasis-nav-header, .oasis-nav-list li a {
touch-action: manipulation !important;
}
body { overscroll-behavior: none !important; }
.header .top-bar-left nav,
.header .top-bar-right nav { display: none !important; }
.oasis-mobile-topbar { padding-top: calc(env(safe-area-inset-top, 0px) + 10px) !important; }
.oasis-bottombar .bb-pair {
display: flex !important; align-items: center !important;
flex: 0 0 auto !important;
gap: 2px !important; padding: 3px !important;
background: #262626 !important;
border: 1px solid #3a3a3a !important;
border-radius: 999px !important;
}
.oasis-bottombar .bb-pair .bb-item { flex: 0 0 auto !important; padding: 2px 8px !important; }
.oasis-bottombar .bb-pair .bb-lbl { font-size: 8px !important; }
.oasis-bottombar a.bb-item.current .bb-ico {
background: #FFA500 !important; color: #121212 !important;
border-radius: 999px !important; padding: 4px 12px !important;
}
.oasis-bottombar a.bb-item.current .bb-lbl { color: #FFA500 !important; font-weight: bold !important; }
.oasis-bottombar a.bb-item.current .bb-badge { background: #121212 !important; color: #FFA500 !important; }
.main-column, .main-column p, .main-column li, .main-column a,
.main-column span, .main-column td, .main-column th, .main-column label,
.main-column h1, .main-column h2, .main-column h3, .main-column h4,
.main-column strong, .main-column em, .main-column small,
.created-at, .post-meta, .feed-post-meta,
.feed-row .small, .feed-row .time, .feed-row .created-at,
.profile-sensors-box > *,
.card-field .card-value, .card-field .card-label {
overflow-wrap: break-word !important;
word-break: normal !important;
-webkit-hyphens: none !important;
hyphens: none !important;
}
.main-column pre, .main-column code, kbd, samp,
.peer-key, .user-link.peer-key, .oasis-id-copy, .search-oasis-id,
.pm-sent-key-value, .kpi__value,
.block-info-table td .peer-key, .block-info-table td .user-link,
.pm-info-table td .user-link,
.main-column a[href*="ed25519"], .main-column a[href^="ssb:"],
.main-column a[href^="/author/"] {
overflow-wrap: anywhere !important;
word-break: break-all !important;
}
button, input[type="submit"], input[type="button"],
.filter-btn, a.filter-btn, .create-button, .edit-btn, .delete-btn,
.join-btn, .leave-btn, .buy-btn, .submit-button, .btn,
.conn-actions button, .peers-conn-actions button,
.mode-buttons button, .mode-buttons .filter-btn,
.filter-group button, .filter-group .filter-btn,
.main-column button, .main-column input[type="submit"] {
white-space: normal !important;
word-break: normal !important;
overflow-wrap: break-word !important;
-webkit-hyphens: none !important;
hyphens: none !important;
}
.conn-actions, .peers-conn-actions {
display: flex !important;
flex-direction: row !important;
flex-wrap: wrap !important;
justify-content: center !important;
align-items: stretch !important;
gap: 8px !important;
width: 100% !important;
max-width: 100% !important;
overflow-x: hidden !important;
}
.conn-actions > form, .peers-conn-actions > form {
flex: 0 1 auto !important;
width: auto !important;
min-width: 0 !important;
margin: 0 !important;
display: flex !important;
}
.pm-info-table, .pm-info-table tbody,
.block-info-table, .block-info-table tbody {
display: block !important;
width: 100% !important;
max-width: 100% !important;
border: 0 !important;
background: transparent !important;
box-shadow: none !important;
table-layout: auto !important;
}
.pm-info-table thead, .block-info-table thead { display: none !important; }
.pm-info-table tr, .block-info-table tr {
display: block !important;
width: 100% !important;
box-sizing: border-box !important;
border: 0 !important;
border-bottom: 1px solid rgba(255,165,0,0.18) !important;
border-radius: 0 !important;
margin: 0 0 8px 0 !important;
padding: 6px 0 !important;
background: transparent !important;
}
.pm-info-table td, .block-info-table td {
display: block !important;
width: 100% !important;
max-width: 100% !important;
box-sizing: border-box !important;
border: 0 !important;
background: transparent !important;
padding: 1px 0 !important;
text-align: left !important;
overflow-wrap: break-word !important;
word-break: normal !important;
}
.pm-info-table td.card-label, .block-info-table td.card-label {
color: var(--c-muted, #8c8c8c) !important;
font-size: 11px !important;
font-weight: 700 !important;
text-transform: uppercase !important;
letter-spacing: .03em !important;
margin-bottom: 2px !important;
padding-bottom: 0 !important;
}
.pm-info-table td.card-value, .block-info-table td.card-value {
font-size: 15px !important;
line-height: 1.5 !important;
margin-bottom: 6px !important;
}
.block-info-table tr:first-child:has(> td.card-label) { display: none !important; }
.block-info-table td[data-label]:not([data-label=""])::before {
content: attr(data-label);
display: block !important;
color: var(--c-muted, #8c8c8c) !important;
font-size: 10px !important;
font-weight: 700 !important;
text-transform: uppercase !important;
letter-spacing: .03em !important;
margin-bottom: 1px !important;
white-space: nowrap !important;
word-break: normal !important;
}
.block-info-table td[data-label=""]::before { content: none !important; }
.peers-list .block-info-table td,
.peers-technical-block .block-info-table td,
.gov-overview tbody td {
word-break: normal !important;
overflow-wrap: break-word !important;
}
.pm-info-table td .user-link, .pm-info-table td .peer-key, .pm-info-table td a[href],
.block-info-table td .peer-key, .block-info-table td .user-link, .block-info-table td a[href] {
display: inline-block !important;
max-width: 100% !important;
overflow-wrap: anywhere !important;
word-break: break-all !important;
}
.pm-info-table td form, .block-info-table td form,
.pm-info-table td button, .block-info-table td button {
width: 100% !important;
margin: 0 !important;
}
.main-content { width: 100% !important; max-width: 100% !important; }
.main-column {
width: 100% !important; max-width: 100% !important;
padding: 16px !important; box-sizing: border-box !important;
}
.main-column .card-field {
flex-direction: column !important;
align-items: stretch !important;
gap: 2px !important;
}
.main-column .card-field .card-value {
width: 100% !important; max-width: 100% !important; font-size: 15px !important;
}
.main-column .card-field .card-label {
color: var(--c-muted, #8c8c8c) !important; font-size: 11px !important;
}
.main-column img, .main-column video, .main-column iframe, .main-column canvas {
max-width: 100% !important; height: auto !important;
}
.main-column hr {
width: 100% !important; border: 0 !important;
border-top: 1px solid #333 !important; margin: 16px 0 !important;
}
.activity-filter-acc { width: 100% !important; margin-bottom: 8px !important; }
.activity-quick-modes {
display: flex !important; flex-wrap: nowrap !important; gap: 6px !important; margin-bottom: 8px !important;
}
.activity-quick-modes form { flex: 1 1 0 !important; min-width: 0 !important; margin: 0 !important; display: flex !important; }
.activity-quick-modes .filter-btn {
flex: 1 1 0 !important; width: 100% !important; min-height: 44px !important;
white-space: nowrap !important; word-break: keep-all !important; overflow-wrap: normal !important;
padding-left: 6px !important; padding-right: 6px !important; font-size: 14px !important;
}
.activity-groups { list-style: none !important; padding: 0 !important; margin: 0 !important; }
.activity-groups .oasis-nav-header .emoji { display: none !important; }
.activity-groups .oasis-nav-header .nav-text { flex: 1 1 auto !important; }
.activity-groups .act-count {
flex: 0 0 auto !important; min-width: 20px !important; text-align: center !important;
font-size: 11px !important; font-weight: 700 !important; color: #121212 !important;
background: #FFA500 !important; border-radius: 999px !important; padding: 1px 7px !important;
margin: 0 8px 0 0 !important;
}
.activity-groups .oasis-nav-list li { list-style: none !important; margin: 0 !important; padding: 0 !important; }
.activity-groups .oasis-nav-list form { margin: 0 !important; display: block !important; width: 100% !important; }
.activity-groups .oasis-nav-list .filter-btn {
display: block !important; width: 100% !important; text-align: left !important;
min-height: 44px !important; margin: 2px 0 !important; box-sizing: border-box !important;
}
.activity-groups .oasis-nav-list .filter-btn.active {
outline: 2px solid #FFA500 !important; outline-offset: -2px !important;
}
.message-list { display: flex !important; flex-direction: column !important; gap: 4px !important; width: 100% !important; }
.message-list .pm-thread { display: flex !important; flex-direction: column !important; width: 100% !important; align-self: stretch !important; }
.pm-thread-replies { display: flex !important; flex-direction: column !important; gap: 4px !important; }
.pm-card.pm-bubble {
max-width: 82% !important; width: fit-content !important; min-width: 0 !important;
box-sizing: border-box !important; padding: 8px 12px !important; margin: 2px 0 !important;
border-radius: 16px !important; border: 1px solid #333 !important;
background: #1e1e1e !important; box-shadow: none !important; cursor: default !important;
}
.pm-card.pm-bubble.pm-recv {
align-self: flex-start !important; margin-left: 0 !important; margin-right: auto !important;
border-bottom-left-radius: 4px !important; background: #1e1e1e !important; border-color: #343434 !important;
}
.pm-card.pm-bubble.pm-sent {
align-self: flex-end !important; margin-left: auto !important; margin-right: 0 !important;
border-bottom-right-radius: 4px !important; background: #2a2413 !important; border-color: #5c4c1f !important;
}
.pm-bubble .pm-bubble-sender { font-size: 11px !important; font-weight: 700 !important; color: #FFA500 !important; margin-bottom: 3px !important; }
.pm-bubble .pm-bubble-sender a { color: #FFA500 !important; }
.pm-bubble .message-text { font-size: 15px !important; line-height: 1.42 !important; color: #ececec !important; margin: 0 !important; }
.pm-bubble .message-text a { color: #FFD700 !important; }
.pm-bubble .pm-bubble-meta {
display: flex !important; flex-wrap: wrap !important; align-items: center !important;
gap: 6px !important; margin-top: 4px !important; font-size: 10px !important; color: var(--c-muted, #8c8c8c) !important;
}
.pm-card.pm-sent .pm-bubble-meta { justify-content: flex-end !important; }
.pm-card.pm-recv .pm-bubble-meta { justify-content: flex-start !important; }
.pm-bubble .pm-bubble-subject { font-style: italic !important; opacity: .75 !important; max-width: 50% !important; overflow: hidden !important; text-overflow: ellipsis !important; white-space: nowrap !important; }
.pm-bubble .pm-bubble-lock { opacity: .85 !important; }
.pm-bubble .pm-bubble-eco { font-size: 9px !important; opacity: .9 !important; }
.pm-bubble .pm-bubble-eco * { font-size: 9px !important; }
.pm-bubble .pm-actions { margin-top: 6px !important; display: flex !important; gap: 6px !important; }
.pm-card.pm-sent .pm-actions { justify-content: flex-end !important; }
.pm-bubble .pm-actions .pm-btn { font-size: 10px !important; padding: 3px 8px !important; min-height: 28px !important; }
.pm-bubble .pm-actions form { margin: 0 !important; }
.pm-bubble .pm-info-table { display: none !important; }
.pm-thread .pm-thread-details { width: 100% !important; }
.pm-thread .pm-thread-toggle { font-size: 12px !important; color: var(--c-muted, #8c8c8c) !important; padding: 4px 0 !important; cursor: pointer !important; }
.main-column h1, .main-column h2, .main-column h3, .main-column h4,
.tags-header h2, .tags-header h3, .title-with-chip h2, .pm-title, h2.pm-title,
.cn-name, h1.cn-name, .cn-section, h2.cn-section, .profile-side-name, h2.profile-side-name {
text-wrap: balance !important;
overflow-wrap: break-word !important;
word-break: keep-all !important;
-webkit-hyphens: none !important; hyphens: none !important;
text-align: left !important;
}
.main-column h1, .tags-header h2, .cn-name {
font-size: var(--fs-title, 20px) !important; font-weight: 700 !important;
line-height: 1.22 !important; letter-spacing: -0.01em !important;
margin: 0 0 var(--space-2, 8px) !important;
}
.main-column h2, .pm-title, .title-with-chip h2, .cn-section {
font-size: 18px !important; font-weight: 700 !important;
line-height: 1.28 !important; margin: var(--space-1, 4px) 0 var(--space-2, 8px) !important;
}
.main-column h3 {
font-size: var(--fs-body-lg, 16px) !important; font-weight: 600 !important;
line-height: 1.35 !important; margin: var(--space-1, 4px) 0 !important;
}
.main-column h1, .main-column h2, .main-column h3,
.tags-header h2, .pm-title, .cn-name, .cn-section {
max-width: 40ch !important; width: auto !important; min-width: 0 !important;
}
.main-column h1 code, .main-column h2 code, .main-column h3 code,
.tags-header h2 code, .pm-title code,
.main-column h1 a[href*="ed25519"], .main-column h2 a[href*="ed25519"],
.main-column h1 a[href^="ssb:"], .main-column h2 a[href^="ssb:"] {
word-break: break-all !important; overflow-wrap: anywhere !important;
}
.main-column .tags-header {
display: flex !important; flex-direction: column !important; align-items: flex-start !important;
gap: var(--space-1, 4px) !important; width: 100% !important; max-width: 100% !important;
margin: 0 0 var(--space-2, 8px) !important;
}
.main-column .tags-header h2 { margin: 0 !important; }
.main-column .title-with-chip {
display: flex !important; flex-wrap: wrap !important; align-items: center !important;
gap: var(--space-2, 8px) !important; width: 100% !important; max-width: 100% !important;
}
.main-column .title-with-chip h2 { flex: 1 1 auto !important; min-width: 0 !important; margin: 0 !important; max-width: none !important; }
.main-column .title-with-chip > :not(h2) { flex: 0 0 auto !important; }
.main-column p, .main-column li,
.tags-header p, .tags-header .prefs-help, .indexing-note, .indexing-percent,
.error-page-message, .welcome-text, .message-text, .pm-bubble .message-text {
text-wrap: pretty !important;
line-height: 1.55 !important;
max-width: min(66ch, 100%) !important;
overflow-wrap: anywhere !important;
word-break: normal !important;
min-width: 0 !important;
-webkit-hyphens: none !important; hyphens: none !important;
}
.main-column p + p { margin-top: var(--space-3, 16px) !important; }
.tags-header p, .tags-header .prefs-help, .indexing-note { line-height: 1.5 !important; margin: 4px 0 0 0 !important; }
.pm-bubble .message-text { line-height: 1.45 !important; max-width: 100% !important; }
[style*="auto-fill"],
[style*="minmax(240px"], [style*="minmax(280px"], [style*="minmax(300px"],
[style*="minmax( 240px"], [style*="minmax( 280px"], [style*="minmax( 300px"],
.trending-container, .cn-hub-grid, .cn-products {
display: grid !important; grid-template-columns: 1fr !important; gap: 12px !important;
width: 100% !important; max-width: 100% !important;
}
.trending-container > *, .games-grid > *, .market-grid > *, .tribe-grid > *,
.feed-container, .feed-card, .trending-card,
.main-column article, .main-column [class*="-card"], .main-column [class*="-item"],
.main-column [class*="-entry"], .main-column .card {
width: 100% !important; max-width: 100% !important; min-width: 0 !important;
box-sizing: border-box !important; margin-left: 0 !important; margin-right: 0 !important;
}
.feed-card .feed-text, .trending-card .feed-text,
.main-column article p, .main-column [class*="-card"] p { max-width: var(--measure, 66ch) !important; }
.main-column form, .comment-form, .comment-form-wrapper, .market-form,
.new-message-form, .project-control-form, .filter-box {
display: block !important; width: 100% !important; max-width: 100% !important;
min-width: 0 !important; box-sizing: border-box !important;
}
.main-column input:not([type="checkbox"]):not([type="radio"]):not(.oasis-nav-toggle):not(.oasis-nav-side-toggle),
.main-column textarea, .main-column select {
width: 100% !important; max-width: 100% !important; min-width: 0 !important; box-sizing: border-box !important;
}
.main-column form > button:only-of-type, .main-column form > .create-button,
.comment-form button[type="submit"], .market-form button[type="submit"],
.new-message-form button[type="submit"] {
display: block !important; width: 100% !important; max-width: 100% !important;
box-sizing: border-box !important; margin-top: 8px !important;
}
[style*="width:33%"], [style*="width: 33%"], [style*="width:25%"], [style*="width: 25%"],
[style*="width:60%"], [style*="width: 60%"], [style*="width:40%"], [style*="width: 40%"] {
width: 100% !important; max-width: 100% !important;
}
.main-column [style*="max-width: 800px"], .main-column [style*="max-width:800px"],
.ai-response, .ai-train-bar {
max-width: 100% !important; width: 100% !important; margin-left: 0 !important; margin-right: 0 !important; box-sizing: border-box !important;
}
.message-list, .pm-thread, .pm-thread-replies { width: 100% !important; max-width: 100% !important; box-sizing: border-box !important; }
.main-column p .user-link, .main-column p a[href^="/author/"],
.main-column li .user-link, .main-column span .user-link,
.main-column .message-text .user-link, .main-column .message-text a[href^="/author/"],
.pm-bubble .message-text .user-link, .pm-bubble .message-text a[href^="/author/"],
.pm-quote a[href^="/author/"] {
display: inline !important; white-space: normal !important;
overflow-wrap: anywhere !important; word-break: keep-all !important;
-webkit-hyphens: none !important; hyphens: none !important;
}
.peer-key, .user-link.peer-key, .block-info-table td .peer-key, .pm-info-table td .peer-key,
.pm-info-table td .user-link, .block-info-table td .user-link,
.search-oasis-id, .pm-sent-key-value, .kpi__value {
display: inline-block !important; max-width: 100% !important;
overflow-wrap: anywhere !important; word-break: break-all !important;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace !important;
font-variant-numeric: tabular-nums !important; line-height: 1.45 !important;
}
.oasis-id-copy {
display: block !important; width: 100% !important; white-space: nowrap !important;
overflow: hidden !important; text-overflow: ellipsis !important;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace !important;
}
.invites-list .user-link.peer-key, .peers-list .block-info-table td .peer-key {
display: block !important; max-width: 100% !important; white-space: nowrap !important;
overflow: hidden !important; text-overflow: ellipsis !important; word-break: normal !important;
}
.main-column .message-text a[href^="http"], .main-column p a[href^="http"],
.pm-bubble .message-text a[href^="http"], .main-column .message-text a.oasis-path-link,
.main-column .message-text a.job-link, .main-column .message-text a.project-link,
.main-column .message-text a.market-link {
overflow-wrap: anywhere !important; word-break: keep-all !important;
-webkit-hyphens: none !important; hyphens: none !important;
}
.main-column code:not(pre code), .pm-bubble .message-text code {
display: inline !important; max-width: 100% !important;
overflow-wrap: anywhere !important; word-break: break-word !important; white-space: pre-wrap !important;
}
.main-column .tags-header { margin-bottom: 6px !important; gap: 2px !important; }
.main-column .tags-header h2, .tags-header h2 {
font-size: 17px !important; line-height: 1.2 !important; letter-spacing: 0 !important;
}
.main-column .tags-header p { font-size: 13px !important; line-height: 1.35 !important; margin: 0 !important; }
.main-column { padding: 10px 12px 16px 12px !important; }
.activity-filter-acc { margin-bottom: 4px !important; }

View file

@ -1,6 +1,12 @@
const { a, em, strong } = require('../../../server/node_modules/hyperaxe');
module.exports = {
ar: {
activityGroupCommunity: "المجتمع والحوكمة",
activityGroupOrg: "التنظيم",
activityGroupComm: "التواصل",
activityGroupEconomy: "الاقتصاد",
activityGroupMedia: "المحتوى والوسائط",
activityGroupOther: "أخرى",
languageName: "العربية",
shopOrderStatusPaid: "تم استلام الدفع",
shopOrderStatusReceived: "تم الاستلام",

View file

@ -1,6 +1,12 @@
const { a, em, strong } = require('../../../server/node_modules/hyperaxe');
module.exports = {
de: {
activityGroupCommunity: "Gemeinschaft & Governance",
activityGroupOrg: "Organisation",
activityGroupComm: "Kommunikation",
activityGroupEconomy: "Wirtschaft",
activityGroupMedia: "Inhalte & Medien",
activityGroupOther: "Sonstiges",
languageName: "Deutsch",
shopOrderStatusPaid: "Zahlung erhalten",
shopOrderStatusReceived: "Erhalten",

View file

@ -1,6 +1,12 @@
const { a, em, strong } = require('../../../server/node_modules/hyperaxe');
module.exports = {
en: {
activityGroupCommunity: "Community & Governance",
activityGroupOrg: "Organization",
activityGroupComm: "Communication",
activityGroupEconomy: "Economy",
activityGroupMedia: "Content & Media",
activityGroupOther: "Others",
languageName: "English",
shopOrderStatusPaid: "Payment received",
shopOrderStatusReceived: "Received",

View file

@ -1,6 +1,12 @@
const { a, em, strong } = require('../../../server/node_modules/hyperaxe');
module.exports = {
es: {
activityGroupCommunity: "Comunidad y Gobernanza",
activityGroupOrg: "Organización",
activityGroupComm: "Comunicación",
activityGroupEconomy: "Economía",
activityGroupMedia: "Contenido y Media",
activityGroupOther: "Otros",
languageName: "Castellano",
shopOrderStatusPaid: "Pago recibido",
shopOrderStatusReceived: "Recibido",

View file

@ -1,6 +1,12 @@
const { a, em, strong } = require('../../../server/node_modules/hyperaxe');
module.exports = {
eu: {
activityGroupCommunity: "Komunitatea eta gobernantza",
activityGroupOrg: "Antolakuntza",
activityGroupComm: "Komunikazioa",
activityGroupEconomy: "Ekonomia",
activityGroupMedia: "Edukia eta multimedia",
activityGroupOther: "Bestelakoak",
languageName: "Basque",
shopOrderStatusPaid: "Ordainketa jasota",
shopOrderStatusReceived: "Jasota",

View file

@ -1,6 +1,12 @@
const { a, em, strong } = require('../../../server/node_modules/hyperaxe');
module.exports = {
fr: {
activityGroupCommunity: "Communauté et gouvernance",
activityGroupOrg: "Organisation",
activityGroupComm: "Communication",
activityGroupEconomy: "Économie",
activityGroupMedia: "Contenu et médias",
activityGroupOther: "Autres",
languageName: "Français",
shopOrderStatusPaid: "Paiement reçu",
shopOrderStatusReceived: "Reçu",

View file

@ -1,6 +1,12 @@
const { a, em, strong } = require('../../../server/node_modules/hyperaxe');
module.exports = {
hi: {
activityGroupCommunity: "समुदाय और शासन",
activityGroupOrg: "संगठन",
activityGroupComm: "संचार",
activityGroupEconomy: "अर्थव्यवस्था",
activityGroupMedia: "सामग्री और मीडिया",
activityGroupOther: "अन्य",
languageName: "हिन्दी",
shopOrderStatusPaid: "भुगतान प्राप्त",
shopOrderStatusReceived: "प्राप्त",

View file

@ -1,6 +1,12 @@
const { a, em, strong } = require('../../../server/node_modules/hyperaxe');
module.exports = {
it: {
activityGroupCommunity: "Comunità e governance",
activityGroupOrg: "Organizzazione",
activityGroupComm: "Comunicazione",
activityGroupEconomy: "Economia",
activityGroupMedia: "Contenuti e media",
activityGroupOther: "Altri",
languageName: "Italiano",
shopOrderStatusPaid: "Pagamento ricevuto",
shopOrderStatusReceived: "Ricevuto",

View file

@ -1,6 +1,12 @@
const { a, em, strong } = require('../../../server/node_modules/hyperaxe');
module.exports = {
pt: {
activityGroupCommunity: "Comunidade e governança",
activityGroupOrg: "Organização",
activityGroupComm: "Comunicação",
activityGroupEconomy: "Economia",
activityGroupMedia: "Conteúdo e média",
activityGroupOther: "Outros",
languageName: "Português",
shopOrderStatusPaid: "Pagamento recebido",
shopOrderStatusReceived: "Recebido",

View file

@ -1,6 +1,12 @@
const { a, em, strong } = require('../../../server/node_modules/hyperaxe');
module.exports = {
ru: {
activityGroupCommunity: "Сообщество и управление",
activityGroupOrg: "Организация",
activityGroupComm: "Коммуникация",
activityGroupEconomy: "Экономика",
activityGroupMedia: "Контент и медиа",
activityGroupOther: "Другое",
languageName: "Русский",
shopOrderStatusPaid: "Оплата получена",
shopOrderStatusReceived: "Получено",

View file

@ -1,6 +1,12 @@
const { a, em, strong } = require('../../../server/node_modules/hyperaxe');
module.exports = {
zh: {
activityGroupCommunity: "社区与治理",
activityGroupOrg: "组织",
activityGroupComm: "沟通",
activityGroupEconomy: "经济",
activityGroupMedia: "内容与媒体",
activityGroupOther: "其他",
languageName: "中文",
shopOrderStatusPaid: "已收款",
shopOrderStatusReceived: "已收到",

View file

@ -0,0 +1,36 @@
(function () {
function mark() {
var p = (location.pathname || "/").replace(/\/+$/, "") || "/";
var links = document.querySelectorAll(".oasis-bottombar a.bb-item");
var best = null, bestLen = -1;
links.forEach(function (a) {
var h = (a.getAttribute("href") || "").replace(/\/+$/, "") || "/";
if (h === "/") return;
if (p === h || p.indexOf(h + "/") === 0) {
if (h.length > bestLen) { best = a; bestLen = h.length; }
}
});
if (best) best.classList.add("current");
}
function onToggle(e) {
var t = e.target;
if (!t || !t.classList || !t.classList.contains("oasis-nav-toggle")) return;
if (!t.checked) return;
var group = t.closest(".oasis-nav-group");
var scope = group && group.parentElement;
if (!scope) return;
var others = scope.querySelectorAll(
":scope > .oasis-nav-group > .oasis-nav-toggle"
);
others.forEach(function (o) { if (o !== t) o.checked = false; });
}
function init() {
mark();
document.addEventListener("change", onToggle);
}
if (document.readyState !== "loading") init();
else document.addEventListener("DOMContentLoaded", init);
})();

View file

@ -1,4 +1,4 @@
const { div, h2, p, section, button, form, a, input, img, textarea, br, span, video: videoHyperaxe, audio: audioHyperaxe, table, tr, td, th, details, summary } = require("../server/node_modules/hyperaxe");
const { div, h2, p, section, button, form, a, input, img, textarea, br, span, video: videoHyperaxe, audio: audioHyperaxe, table, tr, td, th, details, summary, ul, li, label } = require("../server/node_modules/hyperaxe");
const { template, i18n, userLink, userLinkLabel, renderSpreadButton, renderContentActions } = require('./main_views');
const opinionCategories = require('../backend/opinion_categories');
@ -1841,25 +1841,43 @@ exports.activityView = (actions, filter, userId, q = '', extras = {}) => {
h2(i18n.activityList),
p(desc)
),
div({ class: 'activity-filter-grid' },
...[
activityTypes.slice(0, 3),
activityTypes.slice(3, 8),
activityTypes.slice(8, 13),
activityTypes.slice(13, 19),
activityTypes.slice(19, 26),
activityTypes.slice(26)
].map(col =>
div({ class: 'activity-filter-col' },
col.map(({ type, label }) =>
form({ method: 'GET', action: '/activity' },
input({ type: 'hidden', name: 'filter', value: type }),
button({ type: 'submit', class: filter === type ? 'filter-btn active' : 'filter-btn' }, label)
)
)
(() => {
const labelOf = {};
activityTypes.forEach(t => { labelOf[t.type] = t.label; });
const mkBtn = (type) => form({ method: 'GET', action: '/activity' },
input({ type: 'hidden', name: 'filter', value: type }),
button({ type: 'submit', class: filter === type ? 'filter-btn active' : 'filter-btn' }, labelOf[type] || type)
);
const QUICK = ['recent', 'all', 'mine'];
const GROUPS = [
{ id: 'act-comunidad', emoji: '👥', title: i18n.activityGroupCommunity || 'Community & Governance', types: ['inhabitants', 'tribe', 'larp', 'parliament', 'courts', 'votes', 'report'] },
{ id: 'act-organizacion', emoji: '🗓', title: i18n.activityGroupOrg || 'Organization', types: ['event', 'calendar', 'task', 'project', 'job'] },
{ id: 'act-comunicacion', emoji: '💬', title: i18n.activityGroupComm || 'Communication', types: ['post', 'feed', 'chat', 'pad', 'forum'] },
{ id: 'act-economia', emoji: '💰', title: i18n.activityGroupEconomy || 'Economy', types: ['banking', 'market', 'shop', 'transfer'] },
{ id: 'act-media', emoji: '🎬', title: i18n.activityGroupMedia || 'Content & Media', types: ['audio', 'bookmark', 'document', 'image', 'torrent', 'video'] },
{ id: 'act-otros', emoji: '⋯', title: i18n.activityGroupOther || 'Others', types: ['map', 'curriculum'] },
];
return div({ class: 'activity-filter-acc' },
div({ class: 'activity-quick-modes' }, QUICK.map(mkBtn)),
ul({ class: 'oasis-nav-groups activity-groups' },
GROUPS.map(g => {
const openByFilter = g.types.includes(filter);
return li({ class: 'oasis-nav-group' },
input({ type: 'checkbox', id: `oasis-nav-group-${g.id}`, class: 'oasis-nav-toggle', ...(openByFilter ? { checked: true } : {}) }),
label({ for: `oasis-nav-group-${g.id}`, class: 'oasis-nav-header' },
span({ class: 'emoji' }, g.emoji),
span({ class: 'nav-text' }, g.title),
span({ class: 'act-count' }, String(g.types.length)),
span({ class: 'oasis-nav-arrow' }, '▾')
),
ul({ class: 'oasis-nav-list' },
g.types.map(t => li(mkBtn(t)))
)
);
})
)
)
),
);
})(),
sub
? div({ class: 'activity-sub-filter' },
sub.filters.map(f => a({ href: `${sub.url}?filter=${encodeURIComponent(f)}`, class: 'filter-btn' }, String(f).toUpperCase()))

View file

@ -23,7 +23,7 @@ const getUserId = async () => {
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, table, td, textarea, title, tr, ul, strong, video: videoHyperaxe, audio: audioHyperaxe } = require("../server/node_modules/hyperaxe");
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, table, td, textarea, title, tr, ul, strong, script, video: videoHyperaxe, audio: audioHyperaxe } = require("../server/node_modules/hyperaxe");
const lodash = require("../server/node_modules/lodash");
const markdown = require("./markdown");
@ -1073,6 +1073,34 @@ const renderTasksLink = () => {
: "";
};
const renderMobileTopbar = () => div(
{ class: "oasis-mobile-topbar" },
a({ class: "omt-logo", href: "/" }, img({ src: "/assets/images/snh-oasis.jpg", alt: "Oasis" })),
a({ class: "omt-title", href: "/" }, "OASIS"),
a({ class: "omt-avatar", href: "/profile" }, img({ src: (typeof global !== "undefined" && global.__OASIS_SELF_AVATAR__) || "/assets/images/default-avatar.png", alt: i18n.profile || "Profile" }))
);
const renderBottomBar = () => {
const inboxN = sharedState.getInboxCount();
const bbLink = (href, ico, label, badge) => a(
{ class: "bb-item", href },
span({ class: "bb-ico" }, ico),
(badge && badge > 0) ? span({ class: "bb-badge" }, String(badge)) : "",
span({ class: "bb-lbl" }, label)
);
return nav(
{ class: "oasis-bottombar", style: "display:none", "aria-label": "Quick actions" },
bbLink("/search", "ꔅ", i18n.searchTitle || "Buscar"),
bbLink("/inbox", "☂", i18n.inbox || "Inbox", inboxN),
bbLink("/publish", "❂", i18n.publish || "Write"),
bbLink("/activity", "ꔙ", i18n.activityTitle || "Activity"),
div({ class: "bb-pair" },
bbLink("/peers", "⧖", i18n.peers || "Peers"),
bbLink("/invites", "ꔮ", i18n.invites || "Invites")
)
);
};
const template = (titlePrefix, ...elements) => {
const currentConfig = getConfig();
const theme = currentConfig.themes.current || "Dark-SNH";
@ -1095,7 +1123,8 @@ const template = (titlePrefix, ...elements) => {
name: "viewport",
content: toAttributes({
width: "device-width",
"initial-scale": 1
"initial-scale": 1,
"viewport-fit": "cover"
})
})
),
@ -1104,6 +1133,7 @@ const template = (titlePrefix, ...elements) => {
input({ type: "checkbox", id: "nav-right-toggle", class: "oasis-nav-side-toggle" }),
div(
{ class: uxMode === "ainav" ? "header ainav-only" : "header" },
renderMobileTopbar(),
div(
{ class: "top-bar-left" },
a(
@ -1387,6 +1417,8 @@ const template = (titlePrefix, ...elements) => {
)
)
),
renderBottomBar(),
script({ src: "/js/oasis-mobile-nav.js", defer: "defer" }),
renderFooter()
)
);
@ -2991,6 +3023,16 @@ exports.privateView = async (messagesInput, filter, decrypted = null) => {
try { return Buffer.byteLength(JSON.stringify(m && m.value), 'utf8'); } catch (_) { return 0; }
}
function bubbleMeta({ sentAt, subjectRaw, crypter, msgSize, msgKey }) {
const ecoChip = msgSize ? renderEcoTax(msgSize, msgKey) : null
return div({ class: 'pm-bubble-meta' },
subjectRaw ? span({ class: 'pm-bubble-subject' }, subjectRaw) : null,
span({ class: 'pm-bubble-time' }, moment(sentAt).format('DD/MM HH:mm')),
span({ class: 'pm-bubble-lock', title: crypter ? '2xE2E' : 'E2E' }, crypter ? '🔒🔒' : '🔒'),
ecoChip ? span({ class: 'pm-bubble-eco' }, ecoChip) : null
)
}
function actions({ key, replyId, subjectRaw, text }) {
const stop = { onclick: 'event.stopPropagation()' }
const subjectReply = /^(\s*RE:\s*)/i.test(subjectRaw || '') ? (subjectRaw || '') : `RE: ${subjectRaw || ''}`
@ -3278,6 +3320,7 @@ exports.privateView = async (messagesInput, filter, decrypted = null) => {
const toLinks = Array.isArray(content.to) ? content.to.map(addr => linkAuthor(addr)) : []
const level = threadLevel(subjectRaw)
const msgSize = msgSizeBytes(msg)
const isMob = process.env.OASIS_MOBILE === '1'
if (subjectU === 'JOB_SUBSCRIBED' || subjectU === 'JOB_UNSUBSCRIBED') {
return JobCard({ type: subjectU, sentAt, from: fromResolved, toLinks, text, key: msg.key, msgSize })
@ -3295,23 +3338,48 @@ exports.privateView = async (messagesInput, filter, decrypted = null) => {
if (content.crypter === true) {
const dec = (decrypted && decrypted.key === msg.key) ? decrypted : null
const shownText = dec && typeof dec.text === 'string' ? dec.text : ''
const cbody = dec && typeof dec.text === 'string'
? div({ class: 'message-text', innerHTML: sanitizeHtml(clickableLinks(dec.text)) })
: dec && dec.error
? div({ class: 'pm-form-error-msg' }, p('✗ ' + i18n.pmCrypterBadKey))
: null
const cform = dec && typeof dec.text === 'string'
? null
: form({ method: 'POST', action: '/inbox/decrypt', class: 'pm-crypter-decrypt-form' },
input({ type: 'hidden', name: 'id', value: msg.key }),
input({ type: 'hidden', name: 'returnFilter', value: filter || 'all' }),
input({ type: 'text', name: 'key', placeholder: 'd66d7d32f4d30f34812aee3d01347154', minlength: 32, maxlength: 32, size: 34, required: true, class: 'pm-crypter-key-input' }),
button({ type: 'submit', class: 'pm-btn pm-crypter-decrypt-btn' }, (i18n.pmCrypterDecryptButton || 'Decrypt').toUpperCase())
)
const cacts = actions({ key: msg.key, replyId: fromResolved, subjectRaw, text: shownText })
if (isMob) {
const mine = isSent(msg)
return div(
{ class: `pm-card normal-pm pm-crypter-card pm-bubble ${mine ? 'pm-sent' : 'pm-recv'} thread-level-${level}` },
mine ? null : div({ class: 'pm-bubble-sender' }, linkAuthor(fromResolved)),
cbody,
cform,
bubbleMeta({ sentAt, subjectRaw, crypter: true, msgSize, msgKey: msg.key }),
cacts
)
}
return div(
{ class: 'pm-card normal-pm pm-crypter-card' },
headerLine({ sentAt, from: fromResolved, toLinks, subject: subjectRaw, msgKey: msg.key, msgSize, crypter: true }),
dec && typeof dec.text === 'string'
? div({ class: 'message-text', innerHTML: sanitizeHtml(clickableLinks(dec.text)) })
: dec && dec.error
? div({ class: 'pm-form-error-msg' }, p('✗ ' + i18n.pmCrypterBadKey))
: null,
dec && typeof dec.text === 'string'
? null
: form({ method: 'POST', action: '/inbox/decrypt', class: 'pm-crypter-decrypt-form' },
input({ type: 'hidden', name: 'id', value: msg.key }),
input({ type: 'hidden', name: 'returnFilter', value: filter || 'all' }),
input({ type: 'text', name: 'key', placeholder: 'd66d7d32f4d30f34812aee3d01347154', minlength: 32, maxlength: 32, size: 34, required: true, class: 'pm-crypter-key-input' }),
button({ type: 'submit', class: 'pm-btn pm-crypter-decrypt-btn' }, (i18n.pmCrypterDecryptButton || 'Decrypt').toUpperCase())
),
actions({ key: msg.key, replyId: fromResolved, subjectRaw, text: shownText })
cbody,
cform,
cacts
)
}
if (isMob) {
const mine = isSent(msg)
return div(
{ class: `pm-card normal-pm pm-bubble ${mine ? 'pm-sent' : 'pm-recv'} thread-level-${level}` },
mine ? null : div({ class: 'pm-bubble-sender' }, linkAuthor(fromResolved)),
div({ class: 'message-text', innerHTML: sanitizeHtml(clickableLinks(text)) }),
bubbleMeta({ sentAt, subjectRaw, crypter: false, msgSize, msgKey: msg.key }),
actions({ key: msg.key, replyId: fromResolved, subjectRaw, text })
)
}