feat: the preview is a viewer — one page at a time, arrows, keys, swipe, a PDF tab and downloads; the theme leaves the list row
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
This commit is contained in:
parent
b277ce4b10
commit
cba2a312e1
6 changed files with 183 additions and 93 deletions
|
|
@ -436,6 +436,14 @@ shared with them, each row saying `owned` and `shared_by_name`.
|
|||
|
||||
`GET /api/my-resources/:id/preview` renders the resource the way its download
|
||||
is built and turns it into one PNG per page (`src/utils/previewPages.js`:
|
||||
Gotenberg to PDF, `pdftoppm` to pages), keyed on `updated_at` and theme and
|
||||
kept under the OS temp directory; `…/preview/:page` serves a page. Theme
|
||||
samples have the same pair under `theme-sample/:id/preview`.
|
||||
Gotenberg to PDF, `pdftoppm` at 150 dpi), keyed on `updated_at`, theme and
|
||||
resolution and kept under the OS temp directory; `…/preview/:page` serves a
|
||||
page. Theme samples have the same pair under `theme-sample/:id/preview`.
|
||||
|
||||
The viewer (`openPreview` in `public/js/myResources.js`) shows one page at a
|
||||
time filling the screen — arrows, ←/→, PageUp/PageDown, Home/End, a swipe on a
|
||||
phone, "Page n of N" — fetching pages as they are reached and two ahead. A
|
||||
second tab, **PDF**, loads the resource's PDF export into the page so it can
|
||||
be read as a document or printed from the browser; the download buttons for
|
||||
every format sit in the bar. The theme is chosen in the editor, not on the
|
||||
list row.
|
||||
|
|
|
|||
|
|
@ -1303,15 +1303,33 @@ textarea.full-input{resize:vertical;}
|
|||
in a gutter; the background is opaque because content scrolls under it. */
|
||||
/* The document preview: pages as pictures, edge to edge on a phone, a strip
|
||||
down the middle on a desktop; the bar stays put, the pages scroll. */
|
||||
.mr-preview { position:fixed; inset:0; z-index:9000; background:rgba(15,23,42,.85); display:flex; flex-direction:column; }
|
||||
.mr-preview { position:fixed; inset:0; z-index:9000; background:#0b1220; display:flex; flex-direction:column; color:#fff; }
|
||||
body.mr-preview-open { overflow:hidden; }
|
||||
.mr-preview-bar { display:flex; align-items:center; gap:10px; padding:8px 12px; background:var(--g900,#0f172a); color:#fff; flex:0 0 auto; }
|
||||
.mr-preview-title { font-weight:600; font-size:14px; flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
||||
.mr-preview-count { font-size:12px; color:#cbd5e1; }
|
||||
.mr-preview-close { color:#fff; }
|
||||
.mr-preview-pages { flex:1; overflow:auto; -webkit-overflow-scrolling:touch; padding:10px; display:flex; flex-direction:column; align-items:center; gap:10px; }
|
||||
.mr-preview-page { width:100%; max-width:960px; height:auto; background:#fff; box-shadow:0 2px 12px rgba(0,0,0,.35); border-radius:4px; min-height:120px; }
|
||||
.mr-preview-status { color:#e2e8f0; font-size:14px; margin:24px 0; }
|
||||
.mr-preview-bar { display:flex; align-items:center; gap:10px; padding:8px 12px; background:#0f172a; flex:0 0 auto; flex-wrap:wrap; }
|
||||
.mr-preview-title { font-weight:600; font-size:14px; flex:1 1 200px; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
||||
.mr-preview-tabs { display:flex; gap:4px; background:rgba(255,255,255,.08); border-radius:8px; padding:3px; }
|
||||
.mr-preview-tab { background:transparent; border:none; color:#cbd5e1; padding:5px 12px; border-radius:6px; font-size:13px; font-weight:600; cursor:pointer; }
|
||||
.mr-preview-tab.is-on { background:#fff; color:#0f172a; }
|
||||
.mr-preview-downloads { display:flex; gap:4px; }
|
||||
.mr-preview-dl { background:rgba(255,255,255,.12); border:none; color:#fff; padding:5px 10px; border-radius:6px; font-size:12px; font-weight:600; cursor:pointer; }
|
||||
.mr-preview-dl:hover, .mr-preview-close:hover { background:rgba(255,255,255,.22); }
|
||||
.mr-preview-close { background:rgba(255,255,255,.12); border:none; color:#fff; padding:6px 14px; border-radius:8px; font-size:13px; font-weight:600; cursor:pointer; }
|
||||
.mr-preview-stage { flex:1; min-height:0; display:flex; align-items:center; justify-content:center; gap:8px; padding:10px; position:relative; }
|
||||
.mr-preview-stage[data-view="pdf"] .mr-preview-frame, .mr-preview-stage[data-view="pdf"] .mr-preview-nav { display:none; }
|
||||
.mr-preview-frame { flex:1; height:100%; min-width:0; display:flex; align-items:center; justify-content:center; }
|
||||
.mr-preview-page { max-width:100%; max-height:100%; width:auto; height:auto; object-fit:contain; background:#fff; box-shadow:0 8px 40px rgba(0,0,0,.6); border-radius:4px; }
|
||||
.mr-preview-nav { background:rgba(255,255,255,.12); border:none; color:#fff; font-size:34px; line-height:1; padding:10px 14px; border-radius:10px; cursor:pointer; flex:0 0 auto; user-select:none; }
|
||||
.mr-preview-nav:disabled { opacity:.25; cursor:default; }
|
||||
.mr-preview-pdf { flex:1; height:100%; border:none; background:#fff; border-radius:4px; }
|
||||
.mr-preview-foot { flex:0 0 auto; text-align:center; padding:6px 0 10px; }
|
||||
.mr-preview-count { font-size:13px; font-weight:600; color:#fff; background:rgba(255,255,255,.15); padding:4px 14px; border-radius:20px; }
|
||||
.mr-preview-status { color:#e2e8f0; font-size:14px; margin:24px; text-align:center; }
|
||||
@media (max-width:600px) {
|
||||
.mr-preview-nav { position:absolute; top:50%; transform:translateY(-50%); font-size:26px; padding:8px 10px; background:rgba(15,23,42,.55); }
|
||||
.mr-preview-nav[data-step="-1"] { left:6px; } .mr-preview-nav[data-step="1"] { right:6px; }
|
||||
.mr-preview-stage { padding:4px; }
|
||||
.mr-preview-downloads { display:none; }
|
||||
}
|
||||
.admin-save-row {
|
||||
border-top:1px solid var(--g100); display:flex; align-items:center; gap:8px; flex-wrap:wrap;
|
||||
position:sticky; bottom:0; z-index:2;
|
||||
|
|
|
|||
|
|
@ -101,7 +101,6 @@
|
|||
// row would leak listeners and miss anything added later.
|
||||
var list = document.getElementById('mr-list');
|
||||
if (list) list.addEventListener('click', onRowClick);
|
||||
if (list) list.addEventListener('change', onRowChange);
|
||||
|
||||
// Filtering is local: the whole library is already in hand, so searching it
|
||||
// is instant and costs no request.
|
||||
|
|
@ -831,15 +830,17 @@
|
|||
// without overflowing anything.
|
||||
// Look first, download after. The preview is the file's own pages as
|
||||
// pictures, so it shows exactly what the download would.
|
||||
var formats = row.kind === 'article' ? ['docx', 'pdf'] : ['pptx', 'docx', 'pdf'];
|
||||
var look = document.createElement('button');
|
||||
look.className = 'btn-sm btn-ghost';
|
||||
look.type = 'button';
|
||||
look.dataset.preview = String(row.id);
|
||||
look.innerHTML = '<i class="fas fa-eye"></i> Preview';
|
||||
look.dataset.previewTitle = row.title || row.topic || 'Preview';
|
||||
look.dataset.previewFormats = formats.join(',');
|
||||
look.title = 'See every page here, without downloading';
|
||||
wrap.appendChild(look);
|
||||
|
||||
var formats = row.kind === 'article' ? ['docx', 'pdf'] : ['pptx', 'docx', 'pdf'];
|
||||
formats.forEach(function (format) {
|
||||
var btn = document.createElement('button');
|
||||
btn.className = 'btn-sm btn-ghost';
|
||||
|
|
@ -865,22 +866,8 @@
|
|||
share.title = 'Share with people on this site';
|
||||
wrap.appendChild(share);
|
||||
|
||||
// Every presentation takes a theme now — markdown slides too.
|
||||
if (row.kind !== 'article' && themeCatalogue.length > 1) {
|
||||
var theme = document.createElement('select');
|
||||
theme.className = 'btn-sm';
|
||||
theme.dataset.theme = String(row.id);
|
||||
theme.title = 'Theme — changes the colours of the next download';
|
||||
theme.style.cssText = 'font-size:11px;padding:2px 4px;border:1px solid var(--g200);border-radius:6px;background:var(--white,#fff);max-width:130px;';
|
||||
themeCatalogue.forEach(function (t) {
|
||||
var option = document.createElement('option');
|
||||
option.value = t.id;
|
||||
option.textContent = t.name;
|
||||
theme.appendChild(option);
|
||||
});
|
||||
theme.value = row.theme || themeCatalogue[0].id;
|
||||
wrap.appendChild(theme);
|
||||
}
|
||||
// The theme is chosen in the editor when the deck is made or modified;
|
||||
// the list row only looks and downloads.
|
||||
|
||||
// Send the rendered file to the owner's own Nextcloud. Offered only when
|
||||
// there is a Nextcloud to send it to.
|
||||
|
|
@ -911,32 +898,6 @@
|
|||
return wrap;
|
||||
}
|
||||
|
||||
// Delegated like the download and delete buttons, so rows re-rendered by a
|
||||
// refresh do not need rebinding.
|
||||
function onRowChange(event) {
|
||||
var select = event.target.closest && event.target.closest('[data-theme]');
|
||||
if (!select) return;
|
||||
var previous = select.dataset.previous || '';
|
||||
select.disabled = true;
|
||||
fetch('/api/my-resources/' + encodeURIComponent(select.dataset.theme) + '/theme', {
|
||||
method: 'PUT',
|
||||
headers: getAuthHeaders(),
|
||||
body: JSON.stringify({ theme: select.value })
|
||||
})
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (data) {
|
||||
if (!data.success) throw new Error(data.error || 'Could not change the theme');
|
||||
select.dataset.previous = select.value;
|
||||
if (typeof showToast === 'function') showToast('Theme changed — download it again to see it.', 'success');
|
||||
})
|
||||
.catch(function (err) {
|
||||
// Put the control back where it was: a select showing a theme the
|
||||
// resource does not have is worse than no feedback.
|
||||
if (previous) select.value = previous;
|
||||
if (typeof showToast === 'function') showToast(err.message, 'error');
|
||||
})
|
||||
.finally(function () { select.disabled = false; });
|
||||
}
|
||||
|
||||
// Rendered server-side and pushed straight to their storage — the file never
|
||||
// travels through this browser, which is the point: it is a copy in their own
|
||||
|
|
@ -969,7 +930,16 @@
|
|||
var shareBtn = event.target.closest && event.target.closest('[data-share]');
|
||||
if (shareBtn) { openSharePanel(shareBtn.dataset.share, shareBtn.closest('.saved-enc-item')); return; }
|
||||
var preview = event.target.closest && event.target.closest('[data-preview]');
|
||||
if (preview) { openPreview('/api/my-resources/' + encodeURIComponent(preview.dataset.preview) + '/preview', preview.dataset.previewTitle || 'Preview'); return; }
|
||||
if (preview) {
|
||||
var pid = encodeURIComponent(preview.dataset.preview);
|
||||
openPreview('/api/my-resources/' + pid + '/preview', preview.dataset.previewTitle || 'Preview', {
|
||||
pdf: '/api/my-resources/' + pid + '/export?format=pdf',
|
||||
downloads: (preview.dataset.previewFormats || '').split(',').filter(Boolean).map(function (f) {
|
||||
return { label: f.toUpperCase(), url: '/api/my-resources/' + pid + '/export?format=' + f };
|
||||
})
|
||||
});
|
||||
return;
|
||||
}
|
||||
var download = event.target.closest && event.target.closest('[data-download]');
|
||||
if (download) return downloadResource(download.dataset.download, download.dataset.format, download);
|
||||
|
||||
|
|
@ -1083,64 +1053,152 @@
|
|||
// screen. Each page is fetched with the auth header (an <img src> cannot
|
||||
// carry one) and shown as it arrives, so the first page is up before the
|
||||
// last is rendered. Pinch-zoom is the browser's own.
|
||||
function openPreview(base, title) {
|
||||
// One page at a time, the way a deck is shown: arrows, keys, a swipe on a
|
||||
// phone, a counter, and the PDF itself on a second tab for anyone who wants
|
||||
// to read it as a document or print it. Pages are fetched as they are
|
||||
// reached, two ahead, and kept as object URLs until the viewer closes.
|
||||
function openPreview(base, title, opts) {
|
||||
opts = opts || {};
|
||||
var old = document.getElementById('mr-preview');
|
||||
if (old) old.remove();
|
||||
var overlay = document.createElement('div');
|
||||
overlay.id = 'mr-preview';
|
||||
overlay.className = 'mr-preview';
|
||||
overlay.innerHTML = '<div class="mr-preview-bar"><span class="mr-preview-title"></span>' +
|
||||
'<span class="mr-preview-count" role="status"></span>' +
|
||||
'<button type="button" class="btn-sm btn-ghost mr-preview-close" aria-label="Close preview"><i class="fas fa-xmark"></i> Close</button></div>' +
|
||||
'<div class="mr-preview-pages"><p class="mr-preview-status">Rendering pages…</p></div>';
|
||||
overlay.setAttribute('role', 'dialog');
|
||||
overlay.setAttribute('aria-modal', 'true');
|
||||
overlay.innerHTML =
|
||||
'<div class="mr-preview-bar">' +
|
||||
'<span class="mr-preview-title"></span>' +
|
||||
'<div class="mr-preview-tabs" role="tablist">' +
|
||||
'<button type="button" class="mr-preview-tab is-on" data-view="pages" role="tab">Pages</button>' +
|
||||
(opts.pdf ? '<button type="button" class="mr-preview-tab" data-view="pdf" role="tab">PDF</button>' : '') +
|
||||
'</div>' +
|
||||
'<span class="mr-preview-downloads"></span>' +
|
||||
'<button type="button" class="mr-preview-close" aria-label="Close preview">✕ Close</button>' +
|
||||
'</div>' +
|
||||
'<div class="mr-preview-stage" data-view="pages">' +
|
||||
'<button type="button" class="mr-preview-nav" data-step="-1" aria-label="Previous page">‹</button>' +
|
||||
'<div class="mr-preview-frame"><p class="mr-preview-status">Rendering pages…</p></div>' +
|
||||
'<button type="button" class="mr-preview-nav" data-step="1" aria-label="Next page">›</button>' +
|
||||
'</div>' +
|
||||
'<div class="mr-preview-foot"><span class="mr-preview-count" role="status"></span></div>';
|
||||
overlay.querySelector('.mr-preview-title').textContent = title || 'Preview';
|
||||
var downloadsEl = overlay.querySelector('.mr-preview-downloads');
|
||||
(opts.downloads || []).forEach(function (d) {
|
||||
var b = document.createElement('button');
|
||||
b.type = 'button'; b.className = 'mr-preview-dl'; b.textContent = d.label; b.title = 'Download as ' + d.label;
|
||||
b.addEventListener('click', function () { fetchToFile(d.url, (title || 'resource') + '.' + d.label.toLowerCase()); });
|
||||
downloadsEl.appendChild(b);
|
||||
});
|
||||
document.body.appendChild(overlay);
|
||||
document.body.classList.add('mr-preview-open');
|
||||
var urls = [];
|
||||
|
||||
var urls = [], pages = 0, current = 1, blobs = {}, pending = {}, view = 'pages';
|
||||
var frame = overlay.querySelector('.mr-preview-frame');
|
||||
var countEl = overlay.querySelector('.mr-preview-count');
|
||||
var stage = overlay.querySelector('.mr-preview-stage');
|
||||
var pdfFrame = null;
|
||||
|
||||
function close() {
|
||||
overlay.remove();
|
||||
document.body.classList.remove('mr-preview-open');
|
||||
urls.forEach(function (u) { URL.revokeObjectURL(u); });
|
||||
document.removeEventListener('keydown', onKey);
|
||||
}
|
||||
function onKey(e) { if (e.key === 'Escape') close(); }
|
||||
function onKey(e) {
|
||||
if (e.key === 'Escape') close();
|
||||
else if (view === 'pages' && (e.key === 'ArrowRight' || e.key === 'PageDown' || e.key === ' ')) { e.preventDefault(); go(current + 1); }
|
||||
else if (view === 'pages' && (e.key === 'ArrowLeft' || e.key === 'PageUp')) { e.preventDefault(); go(current - 1); }
|
||||
else if (view === 'pages' && e.key === 'Home') go(1);
|
||||
else if (view === 'pages' && e.key === 'End') go(pages);
|
||||
}
|
||||
document.addEventListener('keydown', onKey);
|
||||
overlay.querySelector('.mr-preview-close').addEventListener('click', close);
|
||||
overlay.addEventListener('click', function (e) { if (e.target === overlay) close(); });
|
||||
overlay.querySelectorAll('.mr-preview-nav').forEach(function (b) {
|
||||
b.addEventListener('click', function () { go(current + Number(b.dataset.step)); });
|
||||
});
|
||||
// A swipe on a phone turns the page.
|
||||
var touchX = null;
|
||||
stage.addEventListener('touchstart', function (e) { touchX = e.touches[0].clientX; }, { passive: true });
|
||||
stage.addEventListener('touchend', function (e) {
|
||||
if (touchX === null || view !== 'pages') return;
|
||||
var dx = e.changedTouches[0].clientX - touchX; touchX = null;
|
||||
if (Math.abs(dx) > 50) go(current + (dx < 0 ? 1 : -1));
|
||||
}, { passive: true });
|
||||
|
||||
function fetchPage(n) {
|
||||
if (blobs[n] || pending[n] || n < 1 || n > pages) return Promise.resolve(blobs[n]);
|
||||
pending[n] = fetch(base + '/' + n, { headers: getAuthHeaders() })
|
||||
.then(function (r) { if (!r.ok) throw new Error('page ' + n); return r.blob(); })
|
||||
.then(function (blob) { var u = URL.createObjectURL(blob); urls.push(u); blobs[n] = u; return u; })
|
||||
.finally(function () { delete pending[n]; });
|
||||
return pending[n];
|
||||
}
|
||||
function show(n) {
|
||||
frame.innerHTML = '';
|
||||
var img = document.createElement('img');
|
||||
img.className = 'mr-preview-page';
|
||||
img.alt = 'Page ' + n + ' of ' + pages;
|
||||
img.draggable = false;
|
||||
frame.appendChild(img);
|
||||
fetchPage(n).then(function (u) { if (current === n && u) img.src = u; })
|
||||
.catch(function () { if (current === n) frame.innerHTML = '<p class="mr-preview-status">Page ' + n + ' could not be shown</p>'; });
|
||||
fetchPage(n + 1); fetchPage(n + 2);
|
||||
countEl.textContent = 'Page ' + n + ' of ' + pages;
|
||||
overlay.querySelectorAll('.mr-preview-nav').forEach(function (b) {
|
||||
b.disabled = Number(b.dataset.step) < 0 ? n <= 1 : n >= pages;
|
||||
});
|
||||
}
|
||||
function go(n) {
|
||||
if (!pages) return;
|
||||
n = Math.max(1, Math.min(pages, n));
|
||||
if (n === current && frame.querySelector('img')) return;
|
||||
current = n;
|
||||
show(n);
|
||||
}
|
||||
function switchView(name) {
|
||||
view = name;
|
||||
overlay.querySelectorAll('.mr-preview-tab').forEach(function (t) { t.classList.toggle('is-on', t.dataset.view === name); t.setAttribute('aria-selected', String(t.dataset.view === name)); });
|
||||
stage.dataset.view = name;
|
||||
overlay.querySelector('.mr-preview-foot').hidden = name !== 'pages';
|
||||
if (name === 'pdf') {
|
||||
if (!pdfFrame) {
|
||||
pdfFrame = document.createElement('iframe');
|
||||
pdfFrame.className = 'mr-preview-pdf';
|
||||
pdfFrame.title = (title || 'Preview') + ' as PDF';
|
||||
stage.appendChild(pdfFrame);
|
||||
fetch(opts.pdf, { headers: getAuthHeaders() })
|
||||
.then(function (r) { if (!r.ok) throw new Error('The PDF could not be produced'); return r.blob(); })
|
||||
.then(function (blob) { var u = URL.createObjectURL(new Blob([blob], { type: 'application/pdf' })); urls.push(u); pdfFrame.src = u; })
|
||||
.catch(function (err) { pdfFrame.remove(); pdfFrame = null; frame.innerHTML = '<p class="mr-preview-status">' + err.message + '</p>'; });
|
||||
}
|
||||
}
|
||||
}
|
||||
overlay.querySelectorAll('.mr-preview-tab').forEach(function (t) { t.addEventListener('click', function () { switchView(t.dataset.view); }); });
|
||||
|
||||
var pagesEl = overlay.querySelector('.mr-preview-pages');
|
||||
var countEl = overlay.querySelector('.mr-preview-count');
|
||||
fetch(base, { headers: getAuthHeaders() })
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (d) {
|
||||
if (!d.success) throw new Error(d.error || 'Could not render a preview');
|
||||
pagesEl.innerHTML = '';
|
||||
countEl.textContent = d.pages + (d.pages === 1 ? ' page' : ' pages');
|
||||
var i = 1;
|
||||
function next() {
|
||||
if (i > d.pages || !overlay.isConnected) return;
|
||||
var n = i++;
|
||||
var img = document.createElement('img');
|
||||
img.className = 'mr-preview-page';
|
||||
img.alt = 'Page ' + n;
|
||||
pagesEl.appendChild(img);
|
||||
fetch(base + '/' + n, { headers: getAuthHeaders() })
|
||||
.then(function (r) { if (!r.ok) throw new Error('page ' + n); return r.blob(); })
|
||||
.then(function (blob) { var u = URL.createObjectURL(blob); urls.push(u); img.src = u; })
|
||||
.catch(function () { img.alt = 'Page ' + n + ' could not be shown'; })
|
||||
.finally(next);
|
||||
}
|
||||
next(); next();
|
||||
pages = d.pages;
|
||||
show(1);
|
||||
})
|
||||
.catch(function (err) {
|
||||
pagesEl.innerHTML = '';
|
||||
frame.innerHTML = '';
|
||||
var p = document.createElement('p');
|
||||
p.className = 'mr-preview-status';
|
||||
p.textContent = err.message;
|
||||
pagesEl.appendChild(p);
|
||||
frame.appendChild(p);
|
||||
});
|
||||
}
|
||||
|
||||
function fetchToFile(url, name) {
|
||||
fetch(url, { headers: getAuthHeaders() })
|
||||
.then(function (r) { if (!r.ok) throw new Error('Download failed'); return r.blob(); })
|
||||
.then(function (blob) { saveBlob(blob, name); })
|
||||
.catch(function (err) { status(err.message); });
|
||||
}
|
||||
|
||||
function saveBlob(blob, name) {
|
||||
var url = URL.createObjectURL(blob);
|
||||
var link = document.createElement('a');
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -79,13 +79,14 @@ test('the theme is shown by a deck the renderer built, not a mocked-up swatch',
|
|||
assert.doesNotMatch(route, /THEME_PREVIEW_DIR|theme-preview/, 'the cached-PNG preview is gone');
|
||||
});
|
||||
|
||||
test('the library offers a theme only where there is a deck to re-skin', () => {
|
||||
test('the theme is chosen in the editor, not on the list row', () => {
|
||||
const ui = read('public/js/myResources.js');
|
||||
assert.match(ui, /row\.kind !== 'article' && themeCatalogue\.length > 1/);
|
||||
assert.match(ui, /data-theme|dataset\.theme/);
|
||||
// A failed change puts the control back rather than showing a theme the
|
||||
// resource does not have.
|
||||
assert.match(ui, /if \(previous\) select\.value = previous;/);
|
||||
// The list row looks and downloads; it carries no theme dropdown.
|
||||
assert.doesNotMatch(ui, /theme\.dataset\.theme = String\(row\.id\)/);
|
||||
assert.doesNotMatch(ui, /closest\('\[data-theme\]'\)/);
|
||||
// The editor still offers the catalogue and a sample of each theme.
|
||||
assert.match(ui, /getElementById\('mr-theme'\)/);
|
||||
assert.match(ui, /theme-sample/);
|
||||
});
|
||||
|
||||
test('a modification keeps the deck\'s theme', () => {
|
||||
|
|
|
|||
|
|
@ -43,6 +43,11 @@ test('the page offers Preview beside the downloads and beside the theme sample,
|
|||
// Every presentation takes a theme now, markdown slides included.
|
||||
assert.doesNotMatch(js, /row\.has_deck !== false && themeCatalogue\.length > 1/);
|
||||
const css = read('public/css/styles.css');
|
||||
assert.match(css, /\.mr-preview-pages \{[^}]*overflow:auto/);
|
||||
assert.match(css, /\.mr-preview-page \{[^}]*width:100%/);
|
||||
assert.match(css, /\.mr-preview-page \{[^}]*max-height:100%/);
|
||||
assert.match(css, /\.mr-preview-pdf \{/);
|
||||
const mr = read('public/js/myResources.js');
|
||||
assert.match(mr, /data-view="pdf"/);
|
||||
assert.match(mr, /e\.key === 'ArrowRight'/);
|
||||
assert.match(mr, /touchend/);
|
||||
assert.doesNotMatch(mr, /theme\.dataset\.theme = String\(row\.id\)/, 'the list row no longer carries a theme dropdown');
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue