feat(publisher): editor pill shows who you're signed in as
Browse filesUpgrade the editor quick-link from a bare pencil icon to a
proper pill that contextualises the action:
┌──────────────────────────────────────────────────────┐
│ [avatar] Signed in as @tfrere [✎ Edit article] │
└──────────────────────────────────────────────────────┘
The visitor sees who they're authenticated as before clicking,
which makes the "would I be writing to the real file?" question
self-answering. Useful when multiple HF accounts share the same
browser session, or when a collaborator wants to confirm they're
not editing on someone else's behalf.
Implementation:
- Render placeholder content (\"@you\" + empty <img>) so the
layout doesn't shift when /api/auth/status comes back. The
pill is still \`hidden\` by default - no flash for anonymous
visitors.
- Inline script fills the <img>.src (preferring
user.avatarUrl, falling back to /api/users/<name>/avatar)
and the @handle , then drops the \`hidden\` attribute.
- New \`.edit-pill\` styles: rounded pill, page-bg fill, primary
CTA chip on the right, soft shadow, primary-tinted border on
hover. Avatar uses the same 24px round shape as the article's
HfUser cards for visual consistency.
- Still hidden under 1100px (same breakpoint as #theme-toggle).
Snapshot refreshed.
Co-authored-by: Cursor <cursoragent@cursor.com>
|
@@ -198,16 +198,25 @@ ${renderPrimaryColorOverride(meta.primaryHue)}
|
|
| 198 |
</button>
|
| 199 |
|
| 200 |
<!--
|
| 201 |
-
|
| 202 |
/api/auth/status response carries canEdit=true. Hidden by
|
| 203 |
-
default so non-editors never see a flash.
|
| 204 |
-
|
|
|
|
|
|
|
| 205 |
-->
|
| 206 |
-
<a id="edit-link" href="/editor"
|
| 207 |
-
<
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
</
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 211 |
</a>
|
| 212 |
|
| 213 |
<!-- Mobile TOC toggle -->
|
|
@@ -713,25 +722,38 @@ ${renderPrimaryColorOverride(meta.primaryHue)}
|
|
| 713 |
});
|
| 714 |
|
| 715 |
// ----------------------------------------------------------------
|
| 716 |
-
// Editor quick-
|
| 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,
|
| 721 |
-
// top-right
|
| 722 |
-
//
|
| 723 |
//
|
| 724 |
// A network failure (offline preview, exported HTML opened from
|
| 725 |
-
// disk, ...) is silently ignored - the
|
| 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
|
| 733 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 734 |
}
|
|
|
|
| 735 |
})
|
| 736 |
.catch(function() {});
|
| 737 |
})();
|
|
|
|
| 198 |
</button>
|
| 199 |
|
| 200 |
<!--
|
| 201 |
+
Editor quick-access pill, shown only for visitors whose
|
| 202 |
/api/auth/status response carries canEdit=true. Hidden by
|
| 203 |
+
default so non-editors never see a flash. The fields are
|
| 204 |
+
filled by the inline script at the bottom of <body>; we use
|
| 205 |
+
placeholder content so layout doesn't shift when the data
|
| 206 |
+
arrives.
|
| 207 |
-->
|
| 208 |
+
<a id="edit-link" href="/editor" class="edit-pill" hidden>
|
| 209 |
+
<img class="edit-pill__avatar" alt="" width="24" height="24" referrerpolicy="no-referrer">
|
| 210 |
+
<span class="edit-pill__text">
|
| 211 |
+
Signed in as <strong class="edit-pill__name">@you</strong>
|
| 212 |
+
</span>
|
| 213 |
+
<span class="edit-pill__cta">
|
| 214 |
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
| 215 |
+
<path d="M12 20h9"/>
|
| 216 |
+
<path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/>
|
| 217 |
+
</svg>
|
| 218 |
+
Edit article
|
| 219 |
+
</span>
|
| 220 |
</a>
|
| 221 |
|
| 222 |
<!-- Mobile TOC toggle -->
|
|
|
|
| 722 |
});
|
| 723 |
|
| 724 |
// ----------------------------------------------------------------
|
| 725 |
+
// Editor quick-access pill
|
| 726 |
// ----------------------------------------------------------------
|
| 727 |
// The published page lives on the same Space as the editor, so we
|
| 728 |
// can probe /api/auth/status to know if the current visitor has
|
| 729 |
+
// edit rights. When they do, fill in their avatar/handle and
|
| 730 |
+
// surface the pill top-right. Otherwise the pill stays hidden
|
| 731 |
+
// (no flash for anonymous / read-only visitors).
|
| 732 |
//
|
| 733 |
// A network failure (offline preview, exported HTML opened from
|
| 734 |
+
// disk, ...) is silently ignored - the pill just stays hidden.
|
| 735 |
(function() {
|
| 736 |
var link = document.getElementById('edit-link');
|
| 737 |
if (!link) return;
|
| 738 |
fetch('/api/auth/status', { credentials: 'same-origin' })
|
| 739 |
.then(function(r) { return r.ok ? r.json() : null; })
|
| 740 |
.then(function(data) {
|
| 741 |
+
if (!data || !data.canEdit) return;
|
| 742 |
+
var u = data.user || {};
|
| 743 |
+
var handle = u.name || 'editor';
|
| 744 |
+
var avatar = link.querySelector('.edit-pill__avatar');
|
| 745 |
+
var nameEl = link.querySelector('.edit-pill__name');
|
| 746 |
+
if (avatar) {
|
| 747 |
+
var src = u.avatarUrl ||
|
| 748 |
+
('https://huggingface.co/api/users/' +
|
| 749 |
+
encodeURIComponent(handle) + '/avatar');
|
| 750 |
+
avatar.src = src;
|
| 751 |
+
avatar.alt = handle + ' avatar';
|
| 752 |
+
}
|
| 753 |
+
if (nameEl) {
|
| 754 |
+
nameEl.textContent = '@' + handle;
|
| 755 |
}
|
| 756 |
+
link.removeAttribute('hidden');
|
| 757 |
})
|
| 758 |
.catch(function() {});
|
| 759 |
})();
|
|
@@ -126,16 +126,25 @@ exports[`snapshot - full render > matches snapshot for a typical article 1`] = `
|
|
| 126 |
</button>
|
| 127 |
|
| 128 |
<!--
|
| 129 |
-
|
| 130 |
/api/auth/status response carries canEdit=true. Hidden by
|
| 131 |
-
default so non-editors never see a flash.
|
| 132 |
-
|
|
|
|
|
|
|
| 133 |
-->
|
| 134 |
-
<a id="edit-link" href="/editor"
|
| 135 |
-
<
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
</
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
</a>
|
| 140 |
|
| 141 |
<!-- Mobile TOC toggle -->
|
|
@@ -656,25 +665,38 @@ exports[`snapshot - full render > matches snapshot for a typical article 1`] = `
|
|
| 656 |
});
|
| 657 |
|
| 658 |
// ----------------------------------------------------------------
|
| 659 |
-
// Editor quick-
|
| 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,
|
| 664 |
-
// top-right
|
| 665 |
-
//
|
| 666 |
//
|
| 667 |
// A network failure (offline preview, exported HTML opened from
|
| 668 |
-
// disk, ...) is silently ignored - the
|
| 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
|
| 676 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 677 |
}
|
|
|
|
| 678 |
})
|
| 679 |
.catch(function() {});
|
| 680 |
})();
|
|
|
|
| 126 |
</button>
|
| 127 |
|
| 128 |
<!--
|
| 129 |
+
Editor quick-access pill, shown only for visitors whose
|
| 130 |
/api/auth/status response carries canEdit=true. Hidden by
|
| 131 |
+
default so non-editors never see a flash. The fields are
|
| 132 |
+
filled by the inline script at the bottom of <body>; we use
|
| 133 |
+
placeholder content so layout doesn't shift when the data
|
| 134 |
+
arrives.
|
| 135 |
-->
|
| 136 |
+
<a id="edit-link" href="/editor" class="edit-pill" hidden>
|
| 137 |
+
<img class="edit-pill__avatar" alt="" width="24" height="24" referrerpolicy="no-referrer">
|
| 138 |
+
<span class="edit-pill__text">
|
| 139 |
+
Signed in as <strong class="edit-pill__name">@you</strong>
|
| 140 |
+
</span>
|
| 141 |
+
<span class="edit-pill__cta">
|
| 142 |
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
| 143 |
+
<path d="M12 20h9"/>
|
| 144 |
+
<path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/>
|
| 145 |
+
</svg>
|
| 146 |
+
Edit article
|
| 147 |
+
</span>
|
| 148 |
</a>
|
| 149 |
|
| 150 |
<!-- Mobile TOC toggle -->
|
|
|
|
| 665 |
});
|
| 666 |
|
| 667 |
// ----------------------------------------------------------------
|
| 668 |
+
// Editor quick-access pill
|
| 669 |
// ----------------------------------------------------------------
|
| 670 |
// The published page lives on the same Space as the editor, so we
|
| 671 |
// can probe /api/auth/status to know if the current visitor has
|
| 672 |
+
// edit rights. When they do, fill in their avatar/handle and
|
| 673 |
+
// surface the pill top-right. Otherwise the pill stays hidden
|
| 674 |
+
// (no flash for anonymous / read-only visitors).
|
| 675 |
//
|
| 676 |
// A network failure (offline preview, exported HTML opened from
|
| 677 |
+
// disk, ...) is silently ignored - the pill just stays hidden.
|
| 678 |
(function() {
|
| 679 |
var link = document.getElementById('edit-link');
|
| 680 |
if (!link) return;
|
| 681 |
fetch('/api/auth/status', { credentials: 'same-origin' })
|
| 682 |
.then(function(r) { return r.ok ? r.json() : null; })
|
| 683 |
.then(function(data) {
|
| 684 |
+
if (!data || !data.canEdit) return;
|
| 685 |
+
var u = data.user || {};
|
| 686 |
+
var handle = u.name || 'editor';
|
| 687 |
+
var avatar = link.querySelector('.edit-pill__avatar');
|
| 688 |
+
var nameEl = link.querySelector('.edit-pill__name');
|
| 689 |
+
if (avatar) {
|
| 690 |
+
var src = u.avatarUrl ||
|
| 691 |
+
('https://huggingface.co/api/users/' +
|
| 692 |
+
encodeURIComponent(handle) + '/avatar');
|
| 693 |
+
avatar.src = src;
|
| 694 |
+
avatar.alt = handle + ' avatar';
|
| 695 |
+
}
|
| 696 |
+
if (nameEl) {
|
| 697 |
+
nameEl.textContent = '@' + handle;
|
| 698 |
}
|
| 699 |
+
link.removeAttribute('hidden');
|
| 700 |
})
|
| 701 |
.catch(function() {});
|
| 702 |
})();
|
|
@@ -138,44 +138,82 @@
|
|
| 138 |
transform: scale(0.92);
|
| 139 |
}
|
| 140 |
|
| 141 |
-
/* Editor quick-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
|
|
|
|
|
|
| 146 |
position: absolute;
|
| 147 |
top: var(--spacing-4);
|
| 148 |
right: var(--spacing-4);
|
| 149 |
margin: 0;
|
| 150 |
z-index: var(--z-overlay);
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
|
|
|
|
|
|
| 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 |
-
|
| 165 |
}
|
| 166 |
-
#edit-link:hover {
|
| 167 |
-
|
|
|
|
| 168 |
}
|
| 169 |
-
#edit-link:active {
|
| 170 |
-
transform: scale(0.
|
| 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 |
}
|
|
|
|
| 138 |
transform: scale(0.92);
|
| 139 |
}
|
| 140 |
|
| 141 |
+
/* Editor quick-access pill (published page only). The element ships
|
| 142 |
+
with `hidden` and the inline script in the published page reveals
|
| 143 |
+
it only when /api/auth/status returns canEdit=true, after also
|
| 144 |
+
filling in the avatar / handle. We split the rules across the
|
| 145 |
+
element + a class so the JS can target the id (it's stable across
|
| 146 |
+
page exports) while the styles live on the class. */
|
| 147 |
+
#edit-link.edit-pill {
|
| 148 |
position: absolute;
|
| 149 |
top: var(--spacing-4);
|
| 150 |
right: var(--spacing-4);
|
| 151 |
margin: 0;
|
| 152 |
z-index: var(--z-overlay);
|
| 153 |
+
display: inline-flex;
|
| 154 |
+
align-items: center;
|
| 155 |
+
gap: 10px;
|
| 156 |
+
padding: 4px 4px 4px 6px;
|
| 157 |
+
border-radius: 999px;
|
| 158 |
border: 1px solid var(--border-color);
|
| 159 |
background: var(--page-bg);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
color: var(--text-color);
|
| 161 |
text-decoration: none;
|
| 162 |
+
box-shadow: 0 2px 12px rgba(0,0,0,.08);
|
| 163 |
+
font-size: 13px;
|
| 164 |
+
line-height: 1.2;
|
| 165 |
transition: transform 150ms ease, box-shadow 150ms ease,
|
| 166 |
+
border-color 150ms ease;
|
| 167 |
}
|
| 168 |
+
#edit-link.edit-pill:hover {
|
| 169 |
+
border-color: color-mix(in srgb, var(--primary-color) 50%, var(--border-color));
|
| 170 |
+
box-shadow: 0 4px 16px rgba(0,0,0,.12);
|
| 171 |
}
|
| 172 |
+
#edit-link.edit-pill:active {
|
| 173 |
+
transform: scale(0.98);
|
| 174 |
}
|
| 175 |
+
#edit-link.edit-pill[hidden] {
|
| 176 |
display: none;
|
| 177 |
}
|
| 178 |
|
| 179 |
+
.edit-pill__avatar {
|
| 180 |
+
width: 24px;
|
| 181 |
+
height: 24px;
|
| 182 |
+
border-radius: 50%;
|
| 183 |
+
object-fit: cover;
|
| 184 |
+
flex-shrink: 0;
|
| 185 |
+
background: var(--surface-bg);
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
.edit-pill__text {
|
| 189 |
+
color: var(--muted-color);
|
| 190 |
+
white-space: nowrap;
|
| 191 |
+
}
|
| 192 |
+
.edit-pill__name {
|
| 193 |
+
color: var(--text-color);
|
| 194 |
+
font-weight: 600;
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
.edit-pill__cta {
|
| 198 |
+
display: inline-flex;
|
| 199 |
+
align-items: center;
|
| 200 |
+
gap: 5px;
|
| 201 |
+
padding: 4px 10px;
|
| 202 |
+
border-radius: 999px;
|
| 203 |
+
background: var(--primary-color);
|
| 204 |
+
color: #fff;
|
| 205 |
+
font-weight: 600;
|
| 206 |
+
font-size: 12px;
|
| 207 |
+
white-space: nowrap;
|
| 208 |
+
flex-shrink: 0;
|
| 209 |
+
}
|
| 210 |
+
.edit-pill__cta svg {
|
| 211 |
+
stroke: currentColor;
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
@media (max-width: 1100px) {
|
| 215 |
#theme-toggle,
|
| 216 |
+
#edit-link.edit-pill {
|
| 217 |
display: none;
|
| 218 |
}
|
| 219 |
}
|