diff --git a/Cargo.toml b/Cargo.toml index 0510d9f..cf6634b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustpad" -version = "0.2.53" +version = "0.2.54" 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 ff06c39..b340cec 100644 --- a/static/css/styles.css +++ b/static/css/styles.css @@ -2062,13 +2062,21 @@ dialog::backdrop { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; + padding: 12px; + border: 1px solid var(--border); + border-radius: 12px; + background: var(--surface-panel); + box-shadow: inset 0 1px 0 var(--wash-soft); } .image-editor-controls label { display: grid; - gap: 6px; - color: var(--muted); - font-size: .78rem; + align-content: start; + gap: 7px; + min-width: 0; + color: var(--text-label); + font-size: .76rem; + font-weight: 700; } .image-editor-controls select, @@ -2076,6 +2084,126 @@ dialog::backdrop { width: 100%; } +.image-editor-controls select { + min-height: 40px; + padding: 0 36px 0 12px; + border: 1px solid var(--border-strong); + border-radius: 9px; + background-color: var(--control-bg); + background-image: linear-gradient(45deg, transparent 50%, var(--select-arrow) 50%), + linear-gradient(135deg, var(--select-arrow) 50%, transparent 50%); + background-position: calc(100% - 16px) 17px, calc(100% - 11px) 17px; + background-repeat: no-repeat; + background-size: 5px 5px, 5px 5px; + box-shadow: inset 0 1px 0 var(--wash-soft); + color: var(--text); + font-size: .84rem; + font-weight: 600; + appearance: none; + -webkit-appearance: none; + cursor: pointer; + transition: border-color .15s ease, background-color .15s ease, box-shadow .15s ease; +} + +.image-editor-controls select:hover { + border-color: var(--select-border); + background-color: var(--surface-hover); +} + +.image-editor-controls select:focus, +.image-editor-controls select:focus-visible { + border-color: var(--focus); + outline: none; + box-shadow: 0 0 0 3px var(--focus-ring), inset 0 1px 0 var(--wash-soft); +} + +.image-editor-controls input[type="range"] { + height: 40px; + min-height: 40px; + margin: 0; + padding: 0 2px; + border: 0; + outline: none; + background: transparent; + box-shadow: none; + appearance: none; + -webkit-appearance: none; + cursor: pointer; +} + +.image-editor-controls input[type="range"]::-webkit-slider-runnable-track { + height: 5px; + border: 1px solid var(--border-strong); + border-radius: 999px; + background: var(--surface-strong-2); +} + +.image-editor-controls input[type="range"]::-webkit-slider-thumb { + width: 17px; + height: 17px; + margin-top: -7px; + border: 2px solid var(--surface-panel); + border-radius: 50%; + background: var(--accent); + box-shadow: 0 0 0 1px var(--accent-border), 0 2px 5px var(--shadow-28); + appearance: none; + -webkit-appearance: none; + transition: transform .12s ease, background-color .12s ease; +} + +.image-editor-controls input[type="range"]:hover::-webkit-slider-thumb { + background: var(--accent-hover); + transform: scale(1.08); +} + +.image-editor-controls input[type="range"]:focus-visible::-webkit-slider-thumb { + box-shadow: 0 0 0 1px var(--accent-border), 0 0 0 4px var(--focus-ring); +} + +.image-editor-controls input[type="range"]::-moz-range-track { + height: 5px; + border: 1px solid var(--border-strong); + border-radius: 999px; + background: var(--surface-strong-2); +} + +.image-editor-controls input[type="range"]::-moz-range-progress { + height: 5px; + border-radius: 999px; + background: var(--accent); +} + +.image-editor-controls input[type="range"]::-moz-range-thumb { + width: 13px; + height: 13px; + border: 2px solid var(--surface-panel); + border-radius: 50%; + background: var(--accent); + box-shadow: 0 0 0 1px var(--accent-border), 0 2px 5px var(--shadow-28); + transition: transform .12s ease, background-color .12s ease; +} + +.image-editor-controls input[type="range"]:hover::-moz-range-thumb { + background: var(--accent-hover); + transform: scale(1.08); +} + +.image-editor-controls input[type="range"]:focus-visible::-moz-range-thumb { + box-shadow: 0 0 0 1px var(--accent-border), 0 0 0 4px var(--focus-ring); +} + +.image-editor-controls input[type="range"]:disabled { + cursor: not-allowed; + opacity: .45; +} + +.image-editor-controls input[type="range"]:disabled::-webkit-slider-thumb, +.image-editor-controls input[type="range"]:disabled::-moz-range-thumb { + background: var(--muted-2); + box-shadow: 0 0 0 1px var(--border-strong); + transform: none; +} + .image-editor-actions { display: flex; justify-content: flex-end; diff --git a/static/js/image-upload.js b/static/js/image-upload.js index aecd53b..ddd9b70 100644 --- a/static/js/image-upload.js +++ b/static/js/image-upload.js @@ -28,7 +28,7 @@ export async function prepareImageFile(file) {
- +
`; diff --git a/static/js/note-files.js b/static/js/note-files.js index fa615bf..45c0259 100644 --- a/static/js/note-files.js +++ b/static/js/note-files.js @@ -203,12 +203,25 @@ export function bindNoteFiles({ editor, endpoints, getAccessToken, getUploadMaxS return { start: start + text.length, end: start + text.length }; } + function moveCursorToNextLine(position, inputType = "insertText") { + const cursor = Math.max(0, Math.min(position, editor.value.length)); + if (editor.value[cursor] === "\n") { + editor.setSelectionRange(cursor + 1, cursor + 1); + return { start: cursor + 1, end: cursor + 1 }; + } + return insertText("\n", { start: cursor, end: cursor }, inputType); + } + + function insertAttachmentText(text, range = null, inputType = "insertText") { + const inserted = insertText(text, range, inputType); + return moveCursorToNextLine(inserted.end, inputType); + } + function insertVideoPlayer(text, range = null, inputType = "insertText") { const start = Math.max(0, Math.min(range?.start ?? editor.selectionStart, editor.value.length)); const end = Math.max(start, Math.min(range?.end ?? editor.selectionEnd, editor.value.length)); const prefix = start > 0 && editor.value[start - 1] !== "\n" ? "\n" : ""; - const suffix = end < editor.value.length && editor.value[end] !== "\n" ? "\n" : ""; - return insertText(`${prefix}${text}${suffix}`, { start, end }, inputType); + return insertAttachmentText(`${prefix}${text}`, { start, end }, inputType); } async function uploadFile(file, onUploaded, insertMode = "auto") { @@ -272,7 +285,7 @@ export function bindNoteFiles({ editor, endpoints, getAccessToken, getUploadMaxS const insertMode = isLikelyVideoFile(file) ? await chooseVideoInsertMode(file.name) : "auto"; if (!insertMode) return; const range = { start: editor.selectionStart, end: editor.selectionEnd }; - await uploadFile(file, text => insertMode === "player" ? insertVideoPlayer(text, range) : insertText(text, range), insertMode); + await uploadFile(file, text => insertMode === "player" ? insertVideoPlayer(text, range) : insertAttachmentText(text, range), insertMode); }); document.querySelector("#editor-workspace")?.addEventListener("paste", async event => { @@ -307,7 +320,7 @@ export function bindNoteFiles({ editor, endpoints, getAccessToken, getUploadMaxS const insertMode = isLikelyVideoFile(file) ? await chooseVideoInsertMode(file.name) : "auto"; if (insertMode) await uploadFile(file, insertPastedAlias, insertMode); } - editor.setSelectionRange(state.cursor, state.cursor); + if (state.inserted) moveCursorToNextLine(state.cursor, "insertFromPaste"); }); document.querySelector("#files-button").addEventListener("click", () => loadFiles({ open: true })); @@ -319,7 +332,7 @@ export function bindNoteFiles({ editor, endpoints, getAccessToken, getUploadMaxS const mode = addButton.dataset.insertMode; const text = aliasCode(addButton.dataset.name, addButton.dataset.name, addButton.dataset.mime, mode); if (mode === "player") insertVideoPlayer(text); - else insertText(text); + else insertAttachmentText(text); toast("Added to note"); return; }