Add a Verse admin tab that opens versebyversewithnate.us/admin
build-container / build (push) Successful in 2m36s
build-container / build (push) Successful in 2m36s
The site sends X-Frame-Options: SAMEORIGIN, so it cannot be embedded; the tab and a Home tile open it in its own browser tab. Address is VERSE_URL. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6ec98d2527
commit
bd58fe73cc
4 files changed
+18
-5
No files matched your search
@@ -9,3 +9,4 @@ COOKIE_SECRET=
|
||||
AUDIOBOOKS_URL=https://audiobooks.necloud.us
|
||||
# First-time passkey enrolment. If unset, a random code is printed in the server log while no passkey exists.
|
||||
HUB_SETUP_CODE=
|
||||
VERSE_URL=https://versebyversewithnate.us/admin
|
||||
@@ -15,6 +15,7 @@ services:
|
||||
SEAFILE_URL: ${SEAFILE_URL:-https://seafile.necloud.us}
|
||||
ONLYOFFICE_URL: ${ONLYOFFICE_URL:-https://onlyoffice.necloud.us}
|
||||
AUDIOBOOKS_URL: ${AUDIOBOOKS_URL:-https://audiobooks.necloud.us}
|
||||
VERSE_URL: ${VERSE_URL:-https://versebyversewithnate.us/admin}
|
||||
ONLYOFFICE_JWT_SECRET: ${ONLYOFFICE_JWT_SECRET:-} # same value as the OnlyOffice server's JWT secret
|
||||
PUBLIC_URL: ${PUBLIC_URL:-} # e.g. https://hub.necloud.us (OnlyOffice posts saved edits here)
|
||||
HUB_SETUP_CODE: ${HUB_SETUP_CODE:-} # first passkey; if empty, a code is printed in the log
|
||||
|
||||
+14
-4
@@ -38,6 +38,7 @@ const ICONS = {
|
||||
more: '<circle cx="5" cy="12" r="1.6" fill="currentColor"/><circle cx="12" cy="12" r="1.6" fill="currentColor"/><circle cx="19" cy="12" r="1.6" fill="currentColor"/>',
|
||||
cal: '<rect x="3" y="5" width="18" height="16" rx="2"/><path d="M3 10h18M8 3v4M16 3v4"/>',
|
||||
search: '<circle cx="11" cy="11" r="7"/><path d="M20 20l-4-4"/>',
|
||||
book: '<path d="M4 5a2 2 0 012-2h13v16H6a2 2 0 00-2 2z"/><path d="M19 19v2H6a2 2 0 01-2-2"/><path d="M9 7h6"/>',
|
||||
trash: '<path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13"/>',
|
||||
};
|
||||
const icon = (n, cls = '') => `<svg class="${cls}" viewBox="0 0 24 24">${ICONS[n]}</svg>`;
|
||||
@@ -61,7 +62,7 @@ function mailUrl(m, kind) {
|
||||
return base; // iCloud has no sign-in hint
|
||||
}
|
||||
|
||||
let cfg = { audiobooks: 'https://audiobooks.necloud.us', seafile: 'https://seafile.necloud.us', onlyoffice: 'https://onlyoffice.necloud.us' };
|
||||
let cfg = { verse: 'https://versebyversewithnate.us/admin', audiobooks: 'https://audiobooks.necloud.us', seafile: 'https://seafile.necloud.us', onlyoffice: 'https://onlyoffice.necloud.us' };
|
||||
|
||||
// ---- Seafile client (via same-origin proxy)
|
||||
const sf = {
|
||||
@@ -95,8 +96,8 @@ const fmtSize = b => b < 1024 ? b + ' B' : b < 1048576 ? (b / 1024).toFixed(0) +
|
||||
const fmtDate = s => new Date(s * 1000).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
|
||||
|
||||
// ---- Navigation
|
||||
const TABS = [['home', 'Home'], ['files', 'Files'], ['music', 'Music'], ['mail', 'Mail'], ['calendar', 'Calendar'], ['search', 'Search'], ['office', 'Office'], ['audio', 'Audio'], ['settings', 'Settings']];
|
||||
const TAB_ICON = { home: 'home', mail: 'mail', files: 'files', office: 'doc', audio: 'audio', music: 'music', settings: 'cog', calendar: 'cal', search: 'search' };
|
||||
const TABS = [['home', 'Home'], ['files', 'Files'], ['music', 'Music'], ['mail', 'Mail'], ['calendar', 'Calendar'], ['search', 'Search'], ['office', 'Office'], ['audio', 'Audio'], ['verse', 'Verse admin'], ['settings', 'Settings']];
|
||||
const TAB_ICON = { home: 'home', mail: 'mail', files: 'files', office: 'doc', audio: 'audio', verse: 'book', music: 'music', settings: 'cog', calendar: 'cal', search: 'search' };
|
||||
const PRIMARY = ['home', 'files', 'music', 'mail']; // phone tab bar; the rest sit behind "More"
|
||||
const phone = matchMedia('(max-width: 767px)');
|
||||
let activeTab = 'home';
|
||||
@@ -120,7 +121,7 @@ function openMore() {
|
||||
el.querySelectorAll('a.row').forEach(a => a.addEventListener('click', closeSheet));
|
||||
}
|
||||
|
||||
const routes = { home: viewHome, mail: viewMail, files: viewFiles, office: viewOffice, audio: viewAudio, music: viewMusic, search: viewSearch, calendar: viewCalendar, settings: viewSettings, edit: viewEdit };
|
||||
const routes = { home: viewHome, mail: viewMail, files: viewFiles, office: viewOffice, audio: viewAudio, verse: viewVerse, music: viewMusic, search: viewSearch, calendar: viewCalendar, settings: viewSettings, edit: viewEdit };
|
||||
let editor;
|
||||
function route() {
|
||||
const [, id = 'home', ...rest] = location.hash.split('/');
|
||||
@@ -157,6 +158,7 @@ function viewHome() {
|
||||
${tile('#/calendar', '#d9453a', 'cal', 'Calendar', 'Agenda')}
|
||||
${tile('#/office', '#e0742b', 'doc', 'Office', 'OnlyOffice')}
|
||||
${tile('#/audio', '#8a5cf0', 'audio', 'Audio', 'Audiobooks')}
|
||||
${tile(cfg.verse, '#0f9d9a', 'book', 'Verse admin', 'Opens in a new tab', true)}
|
||||
${tile('#/mail', '#4f7cff', 'mail', 'Mail', '4 accounts')}
|
||||
</div>
|
||||
<h2>Mail</h2><div class="grid">
|
||||
@@ -507,6 +509,14 @@ async function newOfficeDoc(x, label) {
|
||||
// ---- Audiobooks: embedded site. Sign-in details saved in Settings (this browser only) can be copied with one tap,
|
||||
// because a page can't type into another site's login form.
|
||||
const abCreds = () => store.get('ab_creds', { u: '', p: '' });
|
||||
// Verse by Verse admin: the site refuses to be framed (X-Frame-Options), so this is a launcher.
|
||||
function viewVerse() {
|
||||
const url = cfg.verse, host = new URL(url).host;
|
||||
$('#view').innerHTML = `<div class="wrap"><h1>Verse admin</h1><p class="sub">${esc(host + new URL(url).pathname)}</p>
|
||||
<div class="card" style="max-width:none"><p style="margin-top:0">This site doesn't allow being shown inside another app, so it opens in its own tab.</p>
|
||||
<div class="bar"><a class="btn" href="${esc(url)}" target="_blank" rel="noopener">${icon('book')}Open admin</a></div></div></div>`;
|
||||
}
|
||||
|
||||
function viewAudio() {
|
||||
const url = cfg.audiobooks, c = abCreds();
|
||||
$('#view').innerHTML = `<div class="wrap"><h1>Audio</h1>
|
||||
|
||||
@@ -11,6 +11,7 @@ const ONLYOFFICE_URL = (process.env.ONLYOFFICE_URL || 'https://onlyoffice.neclou
|
||||
const AUDIOBOOKS_URL = (process.env.AUDIOBOOKS_URL || 'https://audiobooks.necloud.us').replace(/\/$/, '');
|
||||
const auth = require('./auth');
|
||||
const cal = require('./cal');
|
||||
const VERSE_URL = process.env.VERSE_URL || 'https://versebyversewithnate.us/admin';
|
||||
const PUBLIC = path.join(__dirname, 'public');
|
||||
|
||||
const TYPES = {
|
||||
@@ -282,7 +283,7 @@ http.createServer((req, res) => {
|
||||
}
|
||||
if (req.url === '/api/config') {
|
||||
res.writeHead(200, { 'content-type': 'application/json' });
|
||||
return res.end(JSON.stringify({ seafile: SEAFILE_URL, onlyoffice: ONLYOFFICE_URL, audiobooks: AUDIOBOOKS_URL, editor: true, jwt: !!JWT_SECRET }));
|
||||
return res.end(JSON.stringify({ seafile: SEAFILE_URL, onlyoffice: ONLYOFFICE_URL, audiobooks: AUDIOBOOKS_URL, verse: VERSE_URL, editor: true, jwt: !!JWT_SECRET }));
|
||||
}
|
||||
if (req.method === 'POST' && req.url === '/api/oo/config') return ooConfig(req, res);
|
||||
if (req.method === 'POST' && req.url.startsWith('/api/oo/callback')) return ooCallback(req, res);
|
||||
|
||||
Reference in new issue
Block a user