diff --git a/Cargo.lock b/Cargo.lock index 01d9c17..725b8e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2581,7 +2581,7 @@ dependencies = [ [[package]] name = "rustpad" -version = "0.2.79" +version = "0.2.80" dependencies = [ "argon2", "aws-config", diff --git a/Cargo.toml b/Cargo.toml index bfa54df..25afdc2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustpad" -version = "0.2.79" +version = "0.2.80" edition = "2024" rust-version = "1.94" description = "Collaborative Markdown notepad built with Axum, WebSockets and SQLite, PostgreSQL and MySQL" diff --git a/static/css/styles.css b/static/css/styles.css index d4a4bca..96d16bb 100644 --- a/static/css/styles.css +++ b/static/css/styles.css @@ -2854,7 +2854,6 @@ dialog::backdrop { flex: 0 0 auto; } -.files-head-actions .primary-button, .files-head-actions .action-button { width: auto; } @@ -2916,28 +2915,9 @@ dialog::backdrop { } .file-row-main { - display: grid; - grid-template-columns: 44px minmax(0, 1fr); - gap: 12px; - align-items: center; min-width: 0; } -.file-type-badge { - display: grid; - place-items: center; - width: 44px; - height: 44px; - border: 1px solid var(--border-strong); - border-radius: 10px; - background: var(--surface-2); - color: var(--accent-text-strong); - font-size: .64rem; - font-weight: 800; - letter-spacing: .045em; - text-transform: uppercase; -} - .file-copy { min-width: 0; } @@ -2980,9 +2960,7 @@ dialog::backdrop { } .file-actions button, -.file-secondary-actions button { - min-height: 34px; - height: 34px; +.file-secondary-actions__buttons button { white-space: nowrap; } @@ -2997,11 +2975,9 @@ dialog::backdrop { .file-secondary-actions { grid-column: 1 / -1; - display: flex; - flex-wrap: wrap; - gap: 7px; - align-items: center; - padding-top: 11px; + display: grid; + gap: 8px; + padding-top: 9px; border-top: 1px solid var(--border); } @@ -3009,32 +2985,58 @@ dialog::backdrop { display: none; } +.file-secondary-actions__buttons { + display: flex; + flex-wrap: wrap; + gap: 7px; + align-items: center; +} + .file-code { - grid-column: 1 / -1; display: grid; grid-template-columns: minmax(0, 1fr) auto; - gap: 8px; - align-items: stretch; - padding-top: 2px; + gap: 5px; + align-items: center; + min-height: 30px; + padding: 3px 4px 3px 9px; + border: 1px solid var(--border); + border-radius: 8px; + background: color-mix(in srgb, var(--surface-2) 74%, transparent); } .file-code[hidden] { display: none; } -.file-code textarea { +.file-code-value { width: 100%; - min-height: 72px; - max-height: 150px; - resize: vertical; + height: 24px; + min-width: 0; box-sizing: border-box; - border-radius: 8px; - font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + margin: 0; + padding: 0; + border: 0; + outline: 0; + background: transparent; + color: var(--text-secondary); + font: 11px/1.2 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + text-overflow: ellipsis; } -.file-code button { - align-self: stretch; - min-width: 74px; +.file-code-value:focus { + color: var(--text); +} + +.file-code-copy { + min-width: 56px; + min-height: 28px; + padding: 4px 9px; + border-radius: 6px; +} + +.file-secondary-actions [data-show-file-code][aria-pressed="true"] { + border-color: var(--border-strong); + background: var(--surface-2); } .file-delete-notice { @@ -3105,12 +3107,6 @@ dialog::backdrop { animation: file-skeleton-pulse 1.25s ease-in-out infinite; } -.file-skeleton--badge { - width: 44px; - height: 44px; - border-radius: 10px; -} - .file-skeleton--name { width: min(260px, 80%); height: 12px; @@ -3210,9 +3206,6 @@ dialog::backdrop { padding-top: 10px; } - .file-code { - grid-template-columns: 1fr; - } } .video-insert-dialog { @@ -6017,16 +6010,6 @@ dialog::backdrop { background: var(--danger-hover); } -.action-button.compact-button { - min-height: 36px; - padding: 7px 11px; -} - -.file-actions button, -.file-code button { - height: auto; -} - .footer-access { color: var(--muted, var(--muted-fallback)); font-weight: 600; diff --git a/static/js/note-files.js b/static/js/note-files.js index 237355c..7d0fb07 100644 --- a/static/js/note-files.js +++ b/static/js/note-files.js @@ -153,14 +153,6 @@ export function bindNoteFiles({ editor, endpoints, getAccessToken, getUploadMaxS }); } - function fileTypeBadge(file) { - const filename = String(file?.filename || ""); - const extension = filename.includes(".") ? filename.split(".").pop().replace(/[^a-z0-9]/gi, "").slice(0, 4) : ""; - if (extension) return extension.toUpperCase(); - const major = String(file?.mime_type || "file").split("/", 1)[0]; - return ({ image: "IMG", video: "VID", audio: "AUD", text: "TXT", application: "FILE" })[major] || "FILE"; - } - function fileActionButtons(file) { const attributes = `data-url="${escapeHtml(file.url)}" data-name="${escapeHtml(file.filename)}" data-mime="${escapeHtml(file.mime_type)}"`; const primaryInsert = isVideo(file.mime_type) @@ -170,12 +162,12 @@ export function bindNoteFiles({ editor, endpoints, getAccessToken, getUploadMaxS ? `` : ""; const codeButtons = isVideo(file.mime_type) - ? ` - - ` - : ` - - `; + ? ` + + ` + : ` + + `; const deleteButton = canDelete() ? `` : ""; @@ -200,7 +192,7 @@ export function bindNoteFiles({ editor, endpoints, getAccessToken, getUploadMaxS } function fileSearchText(file) { - return `${file.filename || ""} ${file.mime_type || ""} ${fileTypeBadge(file)}`.toLocaleLowerCase(); + return `${file.filename || ""} ${file.mime_type || ""}`.toLocaleLowerCase(); } function fileRow(file) { @@ -211,7 +203,6 @@ export function bindNoteFiles({ editor, endpoints, getAccessToken, getUploadMaxS return `
-
${escapeHtml(file.filename)}
@@ -223,8 +214,13 @@ export function bindNoteFiles({ editor, endpoints, getAccessToken, getUploadMaxS
${actions.primary}
- - +
`; } @@ -258,7 +254,7 @@ export function bindNoteFiles({ editor, endpoints, getAccessToken, getUploadMaxS if (refreshing && filesLoaded) return; list.innerHTML = Array.from({ length: 3 }, () => ` `).join(""); } @@ -431,12 +427,18 @@ export function bindNoteFiles({ editor, endpoints, getAccessToken, getUploadMaxS const toggleButton = event.target.closest("[data-toggle-file-actions]"); if (toggleButton) { - const panel = toggleButton.closest(".file-row")?.querySelector(".file-secondary-actions"); + const row = toggleButton.closest(".file-row"); + const panel = row?.querySelector(".file-secondary-actions"); if (!panel) return; const expanded = panel.hidden; panel.hidden = !expanded; toggleButton.setAttribute("aria-expanded", String(expanded)); toggleButton.textContent = expanded ? t("files.less", {}, "Less") : t("common.more", {}, "More"); + if (!expanded) { + const codePanel = row.querySelector(".file-code"); + if (codePanel) codePanel.hidden = true; + row.querySelectorAll("[data-show-file-code][aria-pressed="true"]").forEach(button => button.setAttribute("aria-pressed", "false")); + } return; } @@ -452,8 +454,9 @@ export function bindNoteFiles({ editor, endpoints, getAccessToken, getUploadMaxS const showButton = event.target.closest("[data-show-file-code]"); if (showButton) { - const panel = showButton.closest(".file-row").querySelector(".file-code"); - const output = panel.querySelector("textarea"); + const row = showButton.closest(".file-row"); + const panel = row.querySelector(".file-code"); + const output = panel.querySelector(".file-code-value"); const safeUrl = safeAttachmentUrl(showButton.dataset.url); const absolute = new URL(safeUrl, location.origin).href; let text = showButton.dataset.showFileCode === "link" && isVideo(showButton.dataset.mime) @@ -470,6 +473,7 @@ export function bindNoteFiles({ editor, endpoints, getAccessToken, getUploadMaxS } output.value = text; panel.hidden = false; + row.querySelectorAll("[data-show-file-code]").forEach(button => button.setAttribute("aria-pressed", String(button === showButton))); output.focus(); output.select(); return; @@ -478,7 +482,7 @@ export function bindNoteFiles({ editor, endpoints, getAccessToken, getUploadMaxS const copyButton = event.target.closest("[data-copy-generated]"); if (copyButton) { try { - await copyText(copyButton.closest(".file-code").querySelector("textarea").value); + await copyText(copyButton.closest(".file-code").querySelector(".file-code-value").value); notify("success", "Generated file code copied to the clipboard.", { title: "Copied" }); } catch (error) { notify("danger", error.message, { title: "Could not copy file code" });