feat(publisher): editor quick-link for signed-in editors
Browse filesThe published article and the editor share the same Space, but a
visitor who already has edit rights had no way to jump from one
to the other without retyping \`/editor\` in the URL bar. Add a
small pencil icon top-right, mirroring the existing theme toggle
on the top-left, that links to /editor.
Visibility:
- Hidden by default (\`<a id="edit-link" ... hidden>\`) so non-
editors / anonymous visitors never see a flash.
- An inline script probes \`/api/auth/status\` on load and removes
the \`hidden\` attribute when \`canEdit\` is true. Fetch failures
(offline preview, exported HTML opened from disk, etc.) are
silently swallowed - the button just stays hidden.
- Hidden under 1100px (same breakpoint as the theme toggle) so
mobile / tablet layouts aren't cluttered.
Styling: matches the theme toggle (40px circle, page-bg fill,
1px border, soft shadow) so the page stays balanced visually.
\`mix(--primary-color)\` tint on hover hints at the destination.
Snapshot updated to reflect the new markup + script block.
Co-authored-by: Cursor <cursoragent@cursor.com>
|
@@ -197,6 +197,19 @@ ${renderPrimaryColorOverride(meta.primaryHue)}
|
|
| 197 |
</span>
|
| 198 |
</button>
|
| 199 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 200 |
<!-- Mobile TOC toggle -->
|
| 201 |
<button class="toc-mobile-toggle" aria-label="Open table of contents" aria-expanded="false">
|
| 202 |
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
@@ -698,6 +711,30 @@ ${renderPrimaryColorOverride(meta.primaryHue)}
|
|
| 698 |
if (h) { var t = document.querySelector(h); if (t) t.scrollIntoView({block:'start'}); }
|
| 699 |
else window.scrollTo({top:0});
|
| 700 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 701 |
})();
|
| 702 |
</script>
|
| 703 |
</body>
|
|
|
|
| 197 |
</span>
|
| 198 |
</button>
|
| 199 |
|
| 200 |
+
<!--
|
| 201 |
+
Quick link to the editor, shown only for visitors whose
|
| 202 |
+
/api/auth/status response carries canEdit=true. Hidden by
|
| 203 |
+
default so non-editors never see a flash. Same shape as the
|
| 204 |
+
theme toggle, mirrored to the right edge of the viewport.
|
| 205 |
+
-->
|
| 206 |
+
<a id="edit-link" href="/editor" aria-label="Open the editor" title="Open the editor" hidden>
|
| 207 |
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
| 208 |
+
<path d="M12 20h9"/>
|
| 209 |
+
<path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/>
|
| 210 |
+
</svg>
|
| 211 |
+
</a>
|
| 212 |
+
|
| 213 |
<!-- Mobile TOC toggle -->
|
| 214 |
<button class="toc-mobile-toggle" aria-label="Open table of contents" aria-expanded="false">
|
| 215 |
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
|
| 711 |
if (h) { var t = document.querySelector(h); if (t) t.scrollIntoView({block:'start'}); }
|
| 712 |
else window.scrollTo({top:0});
|
| 713 |
});
|
| 714 |
+
|
| 715 |
+
// ----------------------------------------------------------------
|
| 716 |
+
// Editor quick-link
|
| 717 |
+
// ----------------------------------------------------------------
|
| 718 |
+
// The published page lives on the same Space as the editor, so we
|
| 719 |
+
// can probe /api/auth/status to know if the current visitor has
|
| 720 |
+
// edit rights. When they do, surface a small "open editor" pencil
|
| 721 |
+
// top-right; otherwise the button stays \`hidden\` and is removed
|
| 722 |
+
// on the next layout pass (no flicker for read-only visitors).
|
| 723 |
+
//
|
| 724 |
+
// A network failure (offline preview, exported HTML opened from
|
| 725 |
+
// disk, ...) is silently ignored - the button just stays hidden.
|
| 726 |
+
(function() {
|
| 727 |
+
var link = document.getElementById('edit-link');
|
| 728 |
+
if (!link) return;
|
| 729 |
+
fetch('/api/auth/status', { credentials: 'same-origin' })
|
| 730 |
+
.then(function(r) { return r.ok ? r.json() : null; })
|
| 731 |
+
.then(function(data) {
|
| 732 |
+
if (data && data.canEdit) {
|
| 733 |
+
link.removeAttribute('hidden');
|
| 734 |
+
}
|
| 735 |
+
})
|
| 736 |
+
.catch(function() {});
|
| 737 |
+
})();
|
| 738 |
})();
|
| 739 |
</script>
|
| 740 |
</body>
|
|
@@ -125,6 +125,19 @@ exports[`snapshot - full render > matches snapshot for a typical article 1`] = `
|
|
| 125 |
</span>
|
| 126 |
</button>
|
| 127 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
<!-- Mobile TOC toggle -->
|
| 129 |
<button class="toc-mobile-toggle" aria-label="Open table of contents" aria-expanded="false">
|
| 130 |
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
@@ -641,6 +654,30 @@ exports[`snapshot - full render > matches snapshot for a typical article 1`] = `
|
|
| 641 |
if (h) { var t = document.querySelector(h); if (t) t.scrollIntoView({block:'start'}); }
|
| 642 |
else window.scrollTo({top:0});
|
| 643 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 644 |
})();
|
| 645 |
</script>
|
| 646 |
</body>
|
|
|
|
| 125 |
</span>
|
| 126 |
</button>
|
| 127 |
|
| 128 |
+
<!--
|
| 129 |
+
Quick link to the editor, shown only for visitors whose
|
| 130 |
+
/api/auth/status response carries canEdit=true. Hidden by
|
| 131 |
+
default so non-editors never see a flash. Same shape as the
|
| 132 |
+
theme toggle, mirrored to the right edge of the viewport.
|
| 133 |
+
-->
|
| 134 |
+
<a id="edit-link" href="/editor" aria-label="Open the editor" title="Open the editor" hidden>
|
| 135 |
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
| 136 |
+
<path d="M12 20h9"/>
|
| 137 |
+
<path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/>
|
| 138 |
+
</svg>
|
| 139 |
+
</a>
|
| 140 |
+
|
| 141 |
<!-- Mobile TOC toggle -->
|
| 142 |
<button class="toc-mobile-toggle" aria-label="Open table of contents" aria-expanded="false">
|
| 143 |
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
|
| 654 |
if (h) { var t = document.querySelector(h); if (t) t.scrollIntoView({block:'start'}); }
|
| 655 |
else window.scrollTo({top:0});
|
| 656 |
});
|
| 657 |
+
|
| 658 |
+
// ----------------------------------------------------------------
|
| 659 |
+
// Editor quick-link
|
| 660 |
+
// ----------------------------------------------------------------
|
| 661 |
+
// The published page lives on the same Space as the editor, so we
|
| 662 |
+
// can probe /api/auth/status to know if the current visitor has
|
| 663 |
+
// edit rights. When they do, surface a small "open editor" pencil
|
| 664 |
+
// top-right; otherwise the button stays \`hidden\` and is removed
|
| 665 |
+
// on the next layout pass (no flicker for read-only visitors).
|
| 666 |
+
//
|
| 667 |
+
// A network failure (offline preview, exported HTML opened from
|
| 668 |
+
// disk, ...) is silently ignored - the button just stays hidden.
|
| 669 |
+
(function() {
|
| 670 |
+
var link = document.getElementById('edit-link');
|
| 671 |
+
if (!link) return;
|
| 672 |
+
fetch('/api/auth/status', { credentials: 'same-origin' })
|
| 673 |
+
.then(function(r) { return r.ok ? r.json() : null; })
|
| 674 |
+
.then(function(data) {
|
| 675 |
+
if (data && data.canEdit) {
|
| 676 |
+
link.removeAttribute('hidden');
|
| 677 |
+
}
|
| 678 |
+
})
|
| 679 |
+
.catch(function() {});
|
| 680 |
+
})();
|
| 681 |
})();
|
| 682 |
</script>
|
| 683 |
</body>
|
|
@@ -138,8 +138,44 @@
|
|
| 138 |
transform: scale(0.92);
|
| 139 |
}
|
| 140 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
@media (max-width: 1100px) {
|
| 142 |
-
#theme-toggle
|
|
|
|
| 143 |
display: none;
|
| 144 |
}
|
| 145 |
}
|
|
|
|
| 138 |
transform: scale(0.92);
|
| 139 |
}
|
| 140 |
|
| 141 |
+
/* Editor quick-link (published page only). Mirrors the theme toggle
|
| 142 |
+
on the opposite side. The button itself ships with `hidden` and is
|
| 143 |
+
revealed by inline JS in the published page only when
|
| 144 |
+
/api/auth/status returns canEdit=true. */
|
| 145 |
+
#edit-link {
|
| 146 |
+
position: absolute;
|
| 147 |
+
top: var(--spacing-4);
|
| 148 |
+
right: var(--spacing-4);
|
| 149 |
+
margin: 0;
|
| 150 |
+
z-index: var(--z-overlay);
|
| 151 |
+
width: 40px;
|
| 152 |
+
height: 40px;
|
| 153 |
+
border-radius: 50%;
|
| 154 |
+
border: 1px solid var(--border-color);
|
| 155 |
+
background: var(--page-bg);
|
| 156 |
+
box-shadow: 0 2px 12px rgba(0,0,0,.08);
|
| 157 |
+
display: flex;
|
| 158 |
+
align-items: center;
|
| 159 |
+
justify-content: center;
|
| 160 |
+
padding: 0;
|
| 161 |
+
color: var(--text-color);
|
| 162 |
+
text-decoration: none;
|
| 163 |
+
transition: transform 150ms ease, box-shadow 150ms ease,
|
| 164 |
+
background 150ms ease;
|
| 165 |
+
}
|
| 166 |
+
#edit-link:hover {
|
| 167 |
+
background: color-mix(in srgb, var(--primary-color) 12%, var(--page-bg));
|
| 168 |
+
}
|
| 169 |
+
#edit-link:active {
|
| 170 |
+
transform: scale(0.92);
|
| 171 |
+
}
|
| 172 |
+
#edit-link[hidden] {
|
| 173 |
+
display: none;
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
@media (max-width: 1100px) {
|
| 177 |
+
#theme-toggle,
|
| 178 |
+
#edit-link {
|
| 179 |
display: none;
|
| 180 |
}
|
| 181 |
}
|