diff --git a/tools/blogeditor/blog-editor.vsix b/tools/blogeditor/blog-editor.vsix index b48dee7..dd1646e 100644 Binary files a/tools/blogeditor/blog-editor.vsix and b/tools/blogeditor/blog-editor.vsix differ diff --git a/tools/blogeditor/editor.html b/tools/blogeditor/editor.html index 0b2c881..5b948c4 100644 --- a/tools/blogeditor/editor.html +++ b/tools/blogeditor/editor.html @@ -780,7 +780,7 @@ * @returns {Promise} */ async function uploadRemainingBlobs(markdown) { - const blobMatches = [...markdown.matchAll(/!\[([^\]]*)\]\((blob:[^)]+)\)/g)]; + const blobMatches = [...markdown.matchAll(/!\[([^\]]*)\]\((blob:[^\s)]+)/g)]; if (blobMatches.length === 0) return markdown; console.warn(`[Blog Editor] Found ${blobMatches.length} remaining blob URLs - uploading them`); const uploads = blobMatches.map(async m => { @@ -820,7 +820,8 @@ let result = markdown; for (const { blobUrl, relPath } of results) { if (relPath) { - result = result.replace(new RegExp(`!\\[([^\\]]*)\\]\\(${blobUrl.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}\\)`, 'g'), `![$1](${relPath})`); + result = result.replace(new RegExp(`!\\[([^\\]]*)\\]\\(${blobUrl.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(\\s+["'][\\s\\S]*?["'])?\\)`, 'g'), `![$1](${relPath}$2)`); + } } return result; @@ -1574,7 +1575,7 @@ const rawDoc = buildDocument(); console.log('[Blog Editor] savePost called, cleaning image paths...'); - const blobs = [...rawDoc.matchAll(/!\[[^\]]*\]\((blob:[^)]+)\)/g)].map(m => m[1]); + const blobs = [...rawDoc.matchAll(/!\[[^\]]*\]\((blob:[^\s)]+)/g)].map(m => m[1]); const unknown = [...new Set(blobs.filter(b => !blobToRelative.has(b)))]; if (unknown.length > 0) { diff --git a/website/data/blog/media/paste-1778545573604-pe0k3.png b/website/data/blog/media/paste-1778545573604-pe0k3.png new file mode 100644 index 0000000..9f14576 Binary files /dev/null and b/website/data/blog/media/paste-1778545573604-pe0k3.png differ diff --git a/website/scripts/BlogComments.js b/website/scripts/BlogComments.js index 6492ba3..00862cb 100644 --- a/website/scripts/BlogComments.js +++ b/website/scripts/BlogComments.js @@ -230,7 +230,7 @@ export class BlogComments { } /** - * Renders the "Commenting as [name] ✏️" identity bar. + * Renders the "Commenting as [name]" identity bar with editable text box. * * @returns {void} */ @@ -244,50 +244,67 @@ export class BlogComments { label.className = "blog-comments-identity-label"; label.textContent = "Commenting as "; - const nameSpan = document.createElement("strong"); - nameSpan.className = "blog-comments-identity-name"; - nameSpan.textContent = this.#displayName; - - const editBtn = document.createElement("button"); - editBtn.type = "button"; - editBtn.className = "blog-comments-identity-edit-btn"; - editBtn.setAttribute("aria-label", "Edit display name"); + const nameInput = document.createElement("input"); + nameInput.type = "text"; + nameInput.className = "blog-comments-identity-name-input"; + nameInput.value = this.#displayName; + nameInput.maxLength = 64; + nameInput.setAttribute("aria-label", "Display name"); const remaining = this.#nickCooldownRemaining(); if (remaining > 0) { - editBtn.disabled = true; - editBtn.title = `Wait ${remaining}s before changing name again`; - editBtn.textContent = `✏️ ${remaining}s`; - this.#startCooldownTick(editBtn); + nameInput.disabled = true; + nameInput.title = `Wait ${remaining}s before changing name again`; + this.#startCooldownTick(nameInput); } else { - editBtn.title = "Edit display name"; - editBtn.textContent = "✏️"; - editBtn.addEventListener("click", () => this.#startEditName()); + nameInput.title = "Edit your display name"; } - this.#identityEl.append(label, nameSpan, editBtn); + const updateWidth = () => { + const tempSpan = document.createElement("span"); + tempSpan.style.visibility = "hidden"; + tempSpan.style.position = "absolute"; + tempSpan.style.fontSize = "0.85rem"; + tempSpan.style.fontFamily = getComputedStyle(nameInput).fontFamily; + tempSpan.textContent = nameInput.value || nameInput.placeholder || "Guest"; + document.body.appendChild(tempSpan); + const width = Math.min(250, Math.max(60, tempSpan.offsetWidth + 20)); + document.body.removeChild(tempSpan); + nameInput.style.width = `${width}px`; + }; + + nameInput.addEventListener("input", updateWidth); + nameInput.addEventListener("blur", () => { + const newName = nameInput.value.trim(); + if (newName && newName !== this.#displayName) { + this.#saveDisplayName(newName); + } else { + nameInput.value = this.#displayName; + } + updateWidth(); + }); + + this.#identityEl.append(label, nameInput); + updateWidth(); } /** - * Ticks the countdown text on the edit button without rebuilding the DOM. + * Ticks the countdown on the name input without rebuilding the DOM. * - * @param {HTMLButtonElement} editBtn + * @param {HTMLInputElement} nameInput * @returns {void} */ - #startCooldownTick(editBtn) { + #startCooldownTick(nameInput) { if (this.#nickCooldownTimer !== null) clearInterval(this.#nickCooldownTimer); this.#nickCooldownTimer = setInterval(() => { const remaining = this.#nickCooldownRemaining(); if (remaining <= 0) { clearInterval(this.#nickCooldownTimer); this.#nickCooldownTimer = null; - editBtn.disabled = false; - editBtn.title = "Edit display name"; - editBtn.textContent = "✏️"; - editBtn.addEventListener("click", () => this.#startEditName()); + nameInput.disabled = false; + nameInput.title = "Edit your display name"; } else { - editBtn.textContent = `✏️ ${remaining}s`; - editBtn.title = `Wait ${remaining}s before changing name again`; + nameInput.title = `Wait ${remaining}s before changing name again`; } }, 1000); } diff --git a/website/styles/main.css b/website/styles/main.css index bd2c098..3fcc0e3 100644 --- a/website/styles/main.css +++ b/website/styles/main.css @@ -477,7 +477,7 @@ svg.world-image { width: 100%; font-family: inherit; font-size: 10px; - padding: 3px 8px; + padding: 0 4px; background: rgba(137, 180, 250, 0.15); color: rgba(137, 180, 250, 0.9); border: 1px solid rgba(137, 180, 250, 0.3); @@ -778,7 +778,7 @@ body.blog-page { .blog-post-peek-wrapper { position: absolute; top: 0; - width: 160px; + min-width: 60px;`n max-width: 250px; height: 0; pointer-events: none; overflow: visible; @@ -793,7 +793,7 @@ body.blog-page { } .blog-post-peek { - width: 160px; + min-width: 60px;`n max-width: 250px; background: var(--node-bg); border: 1px solid var(--node-border); border-radius: 8px; @@ -981,7 +981,7 @@ body.blog-page { border: 1px solid rgba(137, 180, 250, 0.35); background: rgba(137, 180, 250, 0.12); color: var(--ctp-sky); - padding: 3px 8px; + padding: 0 4px; border-radius: 999px; font-size: 0.78rem; } @@ -1576,9 +1576,9 @@ body.blog-page { background: rgba(24, 24, 37, 0.85); border-radius: 6px; color: var(--ctp-text); - padding: 3px 8px; + padding: 0 4px; font-size: 0.85rem; - width: 160px; + min-width: 60px;`n max-width: 250px; } .blog-comments-identity-input:focus { @@ -1586,6 +1586,30 @@ body.blog-page { border-color: var(--ctp-blue); } +.blog-comments-identity-name-input { + border: 1px solid rgba(205, 214, 244, 0.25); + background: rgba(24, 24, 37, 0.85); + border-radius: 6px; + color: var(--ctp-text); + padding: 3px 8px; + font-size: 0.85rem; + min-width: 60px; + max-width: 250px; + vertical-align: baseline; + transition: border-color 0.15s, background 0.15s, width 0.15s; +} + +.blog-comments-identity-name-input:focus { + outline: none; + border-color: var(--ctp-blue); + background: rgba(69, 71, 90, 0.6); +} + +.blog-comments-identity-name-input:disabled { + opacity: 0.5; + cursor: not-allowed; +} + .blog-comments-identity-save-btn { background: rgba(137, 180, 250, 0.2); border: 1px solid rgba(137, 180, 250, 0.3); @@ -3067,7 +3091,7 @@ body.blog-page { color: rgba(243, 139, 168, 0.9); border: 1px solid rgba(243, 139, 168, 0.3); border-radius: 4px; - padding: 3px 8px; + padding: 0 4px; font-size: 10px; cursor: pointer; transition: all 0.15s; @@ -3124,7 +3148,7 @@ body.blog-page { font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px; - padding: 3px 8px; + padding: 0 4px; border-radius: 3px; }