v0.2.79
This commit is contained in:
Generated
+1
-1
@@ -2581,7 +2581,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustpad"
|
name = "rustpad"
|
||||||
version = "0.2.78"
|
version = "0.2.79"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"argon2",
|
"argon2",
|
||||||
"aws-config",
|
"aws-config",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rustpad"
|
name = "rustpad"
|
||||||
version = "0.2.78"
|
version = "0.2.79"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version = "1.94"
|
rust-version = "1.94"
|
||||||
description = "Collaborative Markdown notepad built with Axum, WebSockets and SQLite, PostgreSQL and MySQL"
|
description = "Collaborative Markdown notepad built with Axum, WebSockets and SQLite, PostgreSQL and MySQL"
|
||||||
|
|||||||
+15
-1
@@ -360,6 +360,8 @@
|
|||||||
"editor.emoji": "😀 Emoji",
|
"editor.emoji": "😀 Emoji",
|
||||||
"editor.emojiCategories": "Emoji categories",
|
"editor.emojiCategories": "Emoji categories",
|
||||||
"editor.emojiEmpty": "No emoji found.",
|
"editor.emojiEmpty": "No emoji found.",
|
||||||
|
"editor.emojiLoading": "Loading emoji…",
|
||||||
|
"editor.emojiLoadFailed": "Could not load emoji. Close and reopen the picker to retry.",
|
||||||
"editor.emojiLabel": "Emoji",
|
"editor.emojiLabel": "Emoji",
|
||||||
"editor.emojiSearch": "Search emoji",
|
"editor.emojiSearch": "Search emoji",
|
||||||
"editor.emojiSearchPlaceholder": "Search emoji…",
|
"editor.emojiSearchPlaceholder": "Search emoji…",
|
||||||
@@ -600,6 +602,12 @@
|
|||||||
"files.download": "Download {label}",
|
"files.download": "Download {label}",
|
||||||
"files.downloadCode": "Download code",
|
"files.downloadCode": "Download code",
|
||||||
"files.empty": "No files uploaded.",
|
"files.empty": "No files uploaded.",
|
||||||
|
"files.filter": "Filter files",
|
||||||
|
"files.filterPlaceholder": "Filter files…",
|
||||||
|
"files.loading": "Loading files…",
|
||||||
|
"files.refreshing": "Refreshing…",
|
||||||
|
"files.noMatches": "No files match this filter.",
|
||||||
|
"files.retry": "Try again",
|
||||||
"files.generatedCode": "Generated file code",
|
"files.generatedCode": "Generated file code",
|
||||||
"files.imageAlignment": "Image alignment",
|
"files.imageAlignment": "Image alignment",
|
||||||
"files.imageHeight": "Image height",
|
"files.imageHeight": "Image height",
|
||||||
@@ -957,6 +965,12 @@
|
|||||||
"error.document.publishedNotFound": "Published page not found · RustPad",
|
"error.document.publishedNotFound": "Published page not found · RustPad",
|
||||||
"error.document.pageNotFound": "Page not found · RustPad",
|
"error.document.pageNotFound": "Page not found · RustPad",
|
||||||
"error.document.methodNotAllowed": "Method not allowed · RustPad",
|
"error.document.methodNotAllowed": "Method not allowed · RustPad",
|
||||||
"error.document.serverError": "Server error · RustPad"
|
"error.document.serverError": "Server error · RustPad",
|
||||||
|
"files.emptyHelp": "Upload a file to keep it with this note and insert it into the document.",
|
||||||
|
"files.filteredSummary": "{shown} of {count} files · {size}",
|
||||||
|
"files.less": "Less",
|
||||||
|
"files.noMatchesHelp": "Try a different file name or type.",
|
||||||
|
"files.link": "Link",
|
||||||
|
"files.markdown": "Markdown"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-1
@@ -360,6 +360,8 @@
|
|||||||
"editor.emoji": "😀 Emoji",
|
"editor.emoji": "😀 Emoji",
|
||||||
"editor.emojiCategories": "Kategorie emoji",
|
"editor.emojiCategories": "Kategorie emoji",
|
||||||
"editor.emojiEmpty": "Nie znaleziono emoji.",
|
"editor.emojiEmpty": "Nie znaleziono emoji.",
|
||||||
|
"editor.emojiLoading": "Ładowanie emoji…",
|
||||||
|
"editor.emojiLoadFailed": "Nie udało się wczytać emoji. Zamknij i otwórz ponownie wybór emoji, aby spróbować jeszcze raz.",
|
||||||
"editor.emojiLabel": "Emoji",
|
"editor.emojiLabel": "Emoji",
|
||||||
"editor.emojiSearch": "Szukaj emoji",
|
"editor.emojiSearch": "Szukaj emoji",
|
||||||
"editor.emojiSearchPlaceholder": "Szukaj emoji…",
|
"editor.emojiSearchPlaceholder": "Szukaj emoji…",
|
||||||
@@ -600,6 +602,12 @@
|
|||||||
"files.download": "Pobierz {label}",
|
"files.download": "Pobierz {label}",
|
||||||
"files.downloadCode": "Kod pobierania",
|
"files.downloadCode": "Kod pobierania",
|
||||||
"files.empty": "Nie przesłano żadnych plików.",
|
"files.empty": "Nie przesłano żadnych plików.",
|
||||||
|
"files.filter": "Filtruj pliki",
|
||||||
|
"files.filterPlaceholder": "Filtruj pliki…",
|
||||||
|
"files.loading": "Ładowanie plików…",
|
||||||
|
"files.refreshing": "Odświeżanie…",
|
||||||
|
"files.noMatches": "Żaden plik nie pasuje do filtra.",
|
||||||
|
"files.retry": "Spróbuj ponownie",
|
||||||
"files.generatedCode": "Wygenerowany kod pliku",
|
"files.generatedCode": "Wygenerowany kod pliku",
|
||||||
"files.imageAlignment": "Wyrównanie obrazu",
|
"files.imageAlignment": "Wyrównanie obrazu",
|
||||||
"files.imageHeight": "Wysokość obrazu",
|
"files.imageHeight": "Wysokość obrazu",
|
||||||
@@ -957,6 +965,12 @@
|
|||||||
"error.document.publishedNotFound": "Nie znaleziono opublikowanej strony · RustPad",
|
"error.document.publishedNotFound": "Nie znaleziono opublikowanej strony · RustPad",
|
||||||
"error.document.pageNotFound": "Nie znaleziono strony · RustPad",
|
"error.document.pageNotFound": "Nie znaleziono strony · RustPad",
|
||||||
"error.document.methodNotAllowed": "Metoda niedozwolona · RustPad",
|
"error.document.methodNotAllowed": "Metoda niedozwolona · RustPad",
|
||||||
"error.document.serverError": "Błąd serwera · RustPad"
|
"error.document.serverError": "Błąd serwera · RustPad",
|
||||||
|
"files.emptyHelp": "Prześlij plik, aby zachować go przy tej notatce i wstawiać do dokumentu.",
|
||||||
|
"files.filteredSummary": "{shown} z {count} plików · {size}",
|
||||||
|
"files.less": "Mniej",
|
||||||
|
"files.noMatchesHelp": "Spróbuj innej nazwy pliku lub typu.",
|
||||||
|
"files.link": "Link",
|
||||||
|
"files.markdown": "Markdown"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+320
-82
@@ -2810,20 +2810,16 @@ dialog::backdrop {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.files-dialog {
|
.files-dialog {
|
||||||
|
width: min(940px, calc(100% - 28px));
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.files-panel {
|
.files-panel {
|
||||||
grid-template-rows: auto minmax(0, 1fr);
|
grid-template-rows: auto auto minmax(0, 1fr);
|
||||||
|
min-height: 0;
|
||||||
max-height: calc(100vh - 28px);
|
height: min(650px, calc(100dvh - 28px));
|
||||||
}
|
max-height: calc(100dvh - 28px);
|
||||||
|
gap: 14px;
|
||||||
.files-list {
|
|
||||||
|
|
||||||
overflow-y: auto;
|
|
||||||
overscroll-behavior: contain;
|
|
||||||
scrollbar-gutter: stable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.files-head {
|
.files-head {
|
||||||
@@ -2838,10 +2834,17 @@ dialog::backdrop {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.files-head h2 {
|
||||||
|
font-size: 1.08rem;
|
||||||
|
letter-spacing: -.01em;
|
||||||
|
}
|
||||||
|
|
||||||
.files-head p {
|
.files-head p {
|
||||||
|
max-width: 620px;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
font-size: .8rem;
|
font-size: .8rem;
|
||||||
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.files-head-actions {
|
.files-head-actions {
|
||||||
@@ -2856,110 +2859,163 @@ dialog::backdrop {
|
|||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.files-toolbar {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) minmax(220px, 320px);
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 11px;
|
||||||
|
background: color-mix(in srgb, var(--surface-2) 72%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.files-dialog-summary {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 0;
|
||||||
|
gap: 8px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: .78rem;
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
|
||||||
|
.files-search {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 38px;
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.files-list {
|
.files-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 9px;
|
align-content: start;
|
||||||
overflow: auto;
|
gap: 10px;
|
||||||
min-height: 80px;
|
min-height: 100px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
padding: 1px 3px 2px 1px;
|
||||||
|
scrollbar-gutter: stable;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-row {
|
.file-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 1fr) auto;
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
gap: 12px;
|
gap: 12px 16px;
|
||||||
padding: 12px;
|
align-items: center;
|
||||||
|
padding: 13px 14px;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 9px;
|
border-radius: 12px;
|
||||||
background: var(--control-bg);
|
background: var(--control-bg);
|
||||||
|
transition: border-color .14s ease, background .14s ease, transform .14s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-row:not(.file-row--skeleton):hover {
|
||||||
|
border-color: var(--border-strong);
|
||||||
|
background: color-mix(in srgb, var(--surface-2) 78%, var(--control-bg));
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-name {
|
.file-name {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
color: var(--text);
|
||||||
|
font-size: .86rem;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.35;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
font-weight: 650;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-meta {
|
.file-meta {
|
||||||
margin-top: 4px;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px 8px;
|
||||||
|
margin-top: 5px;
|
||||||
color: var(--muted-2);
|
color: var(--muted-2);
|
||||||
font-size: .72rem;
|
font-size: .7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-meta > span:not(.file-flag) + span:not(.file-flag)::before {
|
||||||
|
content: "·";
|
||||||
|
margin-right: 8px;
|
||||||
|
color: var(--border-strong);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-flag {
|
||||||
|
margin-left: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-actions {
|
.file-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-actions button {
|
|
||||||
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 6px;
|
|
||||||
background: var(--surface-2);
|
|
||||||
color: var(--text);
|
|
||||||
padding: 0 8px;
|
|
||||||
font-size: .72rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 620px) {
|
|
||||||
.files-head {
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.files-head-actions {
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.files-head-actions .action-button {
|
|
||||||
padding-inline: 9px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-row {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-actions {
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.files-panel {
|
|
||||||
min-height: 0;
|
|
||||||
height: min(590px, calc(100vh - 28px));
|
|
||||||
}
|
|
||||||
|
|
||||||
.files-list {
|
|
||||||
align-content: start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-row {
|
|
||||||
align-items: start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-row-main {
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-actions {
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
align-self: start;
|
justify-content: flex-end;
|
||||||
flex-wrap: nowrap;
|
gap: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-actions button {
|
.file-actions button,
|
||||||
|
.file-secondary-actions button {
|
||||||
min-height: 34px;
|
min-height: 34px;
|
||||||
height: 34px;
|
height: 34px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-primary-action {
|
||||||
|
min-width: 102px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-more-button[aria-expanded="true"] {
|
||||||
|
border-color: var(--border-strong);
|
||||||
|
background: var(--surface-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-secondary-actions {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 7px;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 11px;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-secondary-actions[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.file-code {
|
.file-code {
|
||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 1fr) auto;
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
|
padding-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-code[hidden] {
|
.file-code[hidden] {
|
||||||
@@ -2968,10 +3024,11 @@ dialog::backdrop {
|
|||||||
|
|
||||||
.file-code textarea {
|
.file-code textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 76px;
|
min-height: 72px;
|
||||||
max-height: 150px;
|
max-height: 150px;
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
border-radius: 8px;
|
||||||
font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2984,19 +3041,175 @@ dialog::backdrop {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
border: 1px solid var(--border-strong);
|
border: 1px solid var(--border-strong);
|
||||||
border-radius: 9px;
|
border-radius: 10px;
|
||||||
background: var(--surface-2);
|
background: var(--surface-2);
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
font-size: .8rem;
|
font-size: .78rem;
|
||||||
line-height: 1.45;
|
line-height: 1.45;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 760px) {
|
.files-state {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
align-content: center;
|
||||||
|
min-height: 250px;
|
||||||
|
padding: 28px;
|
||||||
|
color: var(--muted);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.files-state strong {
|
||||||
|
margin-top: 10px;
|
||||||
|
color: var(--text);
|
||||||
|
font-size: .88rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.files-state span {
|
||||||
|
max-width: 420px;
|
||||||
|
margin-top: 5px;
|
||||||
|
font-size: .76rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.files-state .action-button {
|
||||||
|
margin-top: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.files-state-mark {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
width: 46px;
|
||||||
|
height: 46px;
|
||||||
|
border: 1px solid var(--border-strong);
|
||||||
|
border-radius: 13px;
|
||||||
|
background: var(--surface-2);
|
||||||
|
color: var(--accent-text-strong);
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.files-state--error .files-state-mark {
|
||||||
|
border-color: color-mix(in srgb, var(--danger) 45%, var(--border));
|
||||||
|
color: var(--danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-row--skeleton {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-skeleton {
|
||||||
|
display: block;
|
||||||
|
border-radius: 7px;
|
||||||
|
background: linear-gradient(90deg, var(--surface-2), var(--surface-3), var(--surface-2));
|
||||||
|
background-size: 220% 100%;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-skeleton--meta {
|
||||||
|
width: min(360px, 95%);
|
||||||
|
height: 9px;
|
||||||
|
margin-top: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-skeleton--action {
|
||||||
|
width: 132px;
|
||||||
|
height: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes file-skeleton-pulse {
|
||||||
|
from { background-position: 100% 0; }
|
||||||
|
to { background-position: -100% 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-spinner {
|
||||||
|
display: inline-block;
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 2px solid var(--border-strong);
|
||||||
|
border-top-color: var(--accent);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: ui-spinner-rotate .7s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-spinner--small {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border-width: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ui-spinner-rotate {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.file-row,
|
||||||
|
.file-skeleton,
|
||||||
|
.ui-spinner {
|
||||||
|
transition: none;
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
.files-panel {
|
||||||
|
height: min(720px, calc(100dvh - 18px));
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.files-head {
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.files-head p {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.files-head-actions {
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.files-head-actions .action-button {
|
||||||
|
padding-inline: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.files-toolbar {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.files-search {
|
||||||
|
min-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-row {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
.file-actions {
|
.file-actions {
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-primary-action {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-secondary-actions {
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.file-code {
|
.file-code {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
@@ -5532,6 +5745,31 @@ dialog::backdrop {
|
|||||||
box-shadow: 0 14px 40px var(--shadow-45);
|
box-shadow: 0 14px 40px var(--shadow-45);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.emoji-loading {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
align-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
min-height: 210px;
|
||||||
|
padding: 22px;
|
||||||
|
color: var(--muted);
|
||||||
|
text-align: center;
|
||||||
|
font-size: .78rem;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-loading[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-loading.is-error {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-picker-content[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.emoji-search {
|
.emoji-search {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 36px;
|
min-height: 36px;
|
||||||
|
|||||||
+17
-6
@@ -94,11 +94,17 @@
|
|||||||
<details id="emoji-picker" class="emoji-picker">
|
<details id="emoji-picker" class="emoji-picker">
|
||||||
<summary title="Insert emoji" aria-label="Insert emoji">😀 Emoji</summary>
|
<summary title="Insert emoji" aria-label="Insert emoji">😀 Emoji</summary>
|
||||||
<div class="emoji-picker-panel">
|
<div class="emoji-picker-panel">
|
||||||
<input id="emoji-search" class="emoji-search" type="search" placeholder="Search emoji…"
|
<div id="emoji-loading" class="emoji-loading" role="status" aria-live="polite">
|
||||||
autocomplete="off" aria-label="Search emoji">
|
<span class="ui-spinner" aria-hidden="true"></span>
|
||||||
<div id="emoji-categories" class="emoji-categories" aria-label="Emoji categories"></div>
|
<span id="emoji-loading-text" data-i18n="editor.emojiLoading">Loading emoji…</span>
|
||||||
<div id="emoji-grid" class="emoji-grid" role="listbox" aria-label="Emoji"></div>
|
</div>
|
||||||
<p id="emoji-empty" class="emoji-empty" hidden>No emoji found.</p>
|
<div id="emoji-content" class="emoji-picker-content" hidden>
|
||||||
|
<input id="emoji-search" class="emoji-search" type="search" placeholder="Search emoji…"
|
||||||
|
autocomplete="off" aria-label="Search emoji">
|
||||||
|
<div id="emoji-categories" class="emoji-categories" aria-label="Emoji categories"></div>
|
||||||
|
<div id="emoji-grid" class="emoji-grid" role="listbox" aria-label="Emoji"></div>
|
||||||
|
<p id="emoji-empty" class="emoji-empty" hidden>No emoji found.</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
<details class="markdown-more">
|
<details class="markdown-more">
|
||||||
@@ -331,7 +337,12 @@
|
|||||||
<button id="close-files" class="icon-button" type="button" aria-label="Close files">×</button>
|
<button id="close-files" class="icon-button" type="button" aria-label="Close files">×</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="files-list" class="files-list"></div>
|
<div class="files-toolbar">
|
||||||
|
<div id="files-dialog-summary" class="files-dialog-summary" aria-live="polite">Loading files…</div>
|
||||||
|
<input id="files-search" class="files-search" type="search" placeholder="Filter files…"
|
||||||
|
autocomplete="off" aria-label="Filter files">
|
||||||
|
</div>
|
||||||
|
<div id="files-list" class="files-list" aria-live="polite"></div>
|
||||||
</div>
|
</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
<dialog id="identity-dialog">
|
<dialog id="identity-dialog">
|
||||||
|
|||||||
@@ -2102,13 +2102,44 @@ export function startNoteEditor(adapter) {
|
|||||||
grid: document.querySelector("#emoji-grid"),
|
grid: document.querySelector("#emoji-grid"),
|
||||||
empty: document.querySelector("#emoji-empty"),
|
empty: document.querySelector("#emoji-empty"),
|
||||||
};
|
};
|
||||||
|
const emojiLoading = document.querySelector("#emoji-loading");
|
||||||
|
const emojiLoadingText = document.querySelector("#emoji-loading-text");
|
||||||
|
const emojiContent = document.querySelector("#emoji-content");
|
||||||
let emojiPickerLoad = null;
|
let emojiPickerLoad = null;
|
||||||
|
let emojiPickerBound = false;
|
||||||
|
|
||||||
|
const setEmojiPickerState = state => {
|
||||||
|
const failed = state === "error";
|
||||||
|
const ready = state === "ready";
|
||||||
|
emojiPickerArgs.details?.setAttribute("aria-busy", String(state === "loading"));
|
||||||
|
if (emojiLoading) {
|
||||||
|
emojiLoading.hidden = ready;
|
||||||
|
emojiLoading.classList.toggle("is-error", failed);
|
||||||
|
const spinner = emojiLoading.querySelector(".ui-spinner");
|
||||||
|
if (spinner) spinner.hidden = failed;
|
||||||
|
}
|
||||||
|
if (emojiLoadingText) {
|
||||||
|
emojiLoadingText.textContent = failed
|
||||||
|
? t("editor.emojiLoadFailed", {}, "Could not load emoji. Close and reopen the picker to retry.")
|
||||||
|
: t("editor.emojiLoading", {}, "Loading emoji…");
|
||||||
|
}
|
||||||
|
if (emojiContent) emojiContent.hidden = !ready;
|
||||||
|
};
|
||||||
|
|
||||||
const loadEmojiPicker = async () => {
|
const loadEmojiPicker = async () => {
|
||||||
|
if (emojiPickerBound) {
|
||||||
|
setEmojiPickerState("ready");
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (emojiPickerLoad) return emojiPickerLoad;
|
if (emojiPickerLoad) return emojiPickerLoad;
|
||||||
|
setEmojiPickerState("loading");
|
||||||
emojiPickerLoad = import("@rustpad/emoji-picker").then(({ bindEmojiPicker }) => {
|
emojiPickerLoad = import("@rustpad/emoji-picker").then(({ bindEmojiPicker }) => {
|
||||||
bindEmojiPicker(emojiPickerArgs);
|
bindEmojiPicker(emojiPickerArgs);
|
||||||
|
emojiPickerBound = true;
|
||||||
|
setEmojiPickerState("ready");
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
emojiPickerLoad = null;
|
emojiPickerLoad = null;
|
||||||
|
setEmojiPickerState("error");
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
return emojiPickerLoad;
|
return emojiPickerLoad;
|
||||||
|
|||||||
+144
-34
@@ -7,7 +7,7 @@
|
|||||||
* See LICENSE file in repository root for details.
|
* See LICENSE file in repository root for details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { formatDateTime, formatNumber, tp } from "@rustpad/i18n";
|
import { formatDateTime, formatNumber, t, tp } from "@rustpad/i18n";
|
||||||
|
|
||||||
import { api, uploadWithProgress } from "@rustpad/api";
|
import { api, uploadWithProgress } from "@rustpad/api";
|
||||||
import { copyText } from "@rustpad/clipboard";
|
import { copyText } from "@rustpad/clipboard";
|
||||||
@@ -153,48 +153,140 @@ 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) {
|
function fileActionButtons(file) {
|
||||||
const attributes = `data-url="${escapeHtml(file.url)}" data-name="${escapeHtml(file.filename)}" data-mime="${escapeHtml(file.mime_type)}"`;
|
const attributes = `data-url="${escapeHtml(file.url)}" data-name="${escapeHtml(file.filename)}" data-mime="${escapeHtml(file.mime_type)}"`;
|
||||||
const insertButtons = isVideo(file.mime_type)
|
const primaryInsert = isVideo(file.mime_type)
|
||||||
? `<button class="action-button action-button--primary compact-button" data-add-file-to-note data-insert-mode="player" ${attributes}>Add player</button>
|
? `<button class="action-button action-button--primary compact-button file-primary-action" data-add-file-to-note data-insert-mode="player" ${attributes}>${escapeHtml(t("files.addPlayer", {}, "Add player"))}</button>`
|
||||||
<button class="action-button action-button--secondary compact-button" data-add-file-to-note data-insert-mode="download" ${attributes}>Add download</button>`
|
: `<button class="action-button action-button--primary compact-button file-primary-action" data-add-file-to-note data-insert-mode="auto" ${attributes}>${escapeHtml(t("files.addToNote", {}, "Add to note"))}</button>`;
|
||||||
: `<button class="action-button action-button--primary compact-button" data-add-file-to-note data-insert-mode="auto" ${attributes}>Add to note</button>`;
|
const secondaryInsert = isVideo(file.mime_type)
|
||||||
const codeButtons = isVideo(file.mime_type)
|
? `<button class="action-button action-button--secondary compact-button" data-add-file-to-note data-insert-mode="download" ${attributes}>${escapeHtml(t("files.addDownload", {}, "Add download"))}</button>`
|
||||||
? `<button class="action-button action-button--secondary compact-button" data-show-file-code="link" ${attributes}>Link</button>
|
|
||||||
<button class="action-button action-button--secondary compact-button" data-show-file-code="player" ${attributes}>Player code</button>
|
|
||||||
<button class="action-button action-button--secondary compact-button" data-show-file-code="download" ${attributes}>Download code</button>`
|
|
||||||
: `<button class="action-button action-button--secondary compact-button" data-show-file-code="link" ${attributes}>Link</button>
|
|
||||||
<button class="action-button action-button--primary compact-button" data-show-file-code="alias" ${attributes}>Alias</button>
|
|
||||||
<button class="action-button action-button--primary compact-button" data-show-file-code="markdown" ${attributes}>Markdown</button>`;
|
|
||||||
const deleteButton = canDelete()
|
|
||||||
? `<button class="action-button action-button--danger compact-button" data-delete-file="${file.id}" data-file-name="${escapeHtml(file.filename)}">Delete</button>`
|
|
||||||
: "";
|
: "";
|
||||||
return `${codeButtons}${insertButtons}${deleteButton}`;
|
const codeButtons = isVideo(file.mime_type)
|
||||||
|
? `<button class="action-button action-button--secondary compact-button" data-show-file-code="link" ${attributes}>${escapeHtml(t("files.link", {}, "Link"))}</button>
|
||||||
|
<button class="action-button action-button--secondary compact-button" data-show-file-code="player" ${attributes}>${escapeHtml(t("files.playerCode", {}, "Player code"))}</button>
|
||||||
|
<button class="action-button action-button--secondary compact-button" data-show-file-code="download" ${attributes}>${escapeHtml(t("files.downloadCode", {}, "Download code"))}</button>`
|
||||||
|
: `<button class="action-button action-button--secondary compact-button" data-show-file-code="link" ${attributes}>${escapeHtml(t("files.link", {}, "Link"))}</button>
|
||||||
|
<button class="action-button action-button--secondary compact-button" data-show-file-code="alias" ${attributes}>${escapeHtml(t("files.alias", {}, "Alias"))}</button>
|
||||||
|
<button class="action-button action-button--secondary compact-button" data-show-file-code="markdown" ${attributes}>${escapeHtml(t("files.markdown", {}, "Markdown"))}</button>`;
|
||||||
|
const deleteButton = canDelete()
|
||||||
|
? `<button class="action-button action-button--danger compact-button" data-delete-file="${file.id}" data-file-name="${escapeHtml(file.filename)}">${escapeHtml(t("files.delete", {}, "Delete file"))}</button>`
|
||||||
|
: "";
|
||||||
|
return {
|
||||||
|
primary: `${primaryInsert}<button class="action-button action-button--secondary compact-button file-more-button" type="button" data-toggle-file-actions aria-expanded="false">${escapeHtml(t("common.more", {}, "More"))}</button>`,
|
||||||
|
secondary: `${secondaryInsert}${codeButtons}${deleteButton}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const summary = document.querySelector("#files-dialog-summary");
|
||||||
|
const search = document.querySelector("#files-search");
|
||||||
|
let currentFiles = [];
|
||||||
|
let filesLoaded = false;
|
||||||
|
let loadSequence = 0;
|
||||||
|
|
||||||
|
function summaryText(files, shown = files.length) {
|
||||||
|
const totalSize = files.reduce((sum, file) => sum + (Number(file.size_bytes) || 0), 0);
|
||||||
|
if (shown !== files.length) {
|
||||||
|
return t("files.filteredSummary", { shown, count: files.length, size: formatBytes(totalSize) }, `${shown} of ${files.length} files · ${formatBytes(totalSize)}`);
|
||||||
|
}
|
||||||
|
return tp("files.summary", files.length, { size: formatBytes(totalSize) }, `${files.length} ${files.length === 1 ? "file" : "files"} · ${formatBytes(totalSize)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fileSearchText(file) {
|
||||||
|
return `${file.filename || ""} ${file.mime_type || ""} ${fileTypeBadge(file)}`.toLocaleLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function fileRow(file) {
|
||||||
|
const created = file.created_at ? formatDate(file.created_at) : "";
|
||||||
|
const status = file.is_attached ? t("files.inNote", {}, "in note") : t("files.removedContent", {}, "removed from content");
|
||||||
|
const generatedCodeLabel = t("files.generatedCode", {}, "Generated file code");
|
||||||
|
const actions = fileActionButtons(file);
|
||||||
|
return `
|
||||||
|
<article class="file-row" data-file-row="${file.id}" data-file-search="${escapeHtml(fileSearchText(file))}">
|
||||||
|
<div class="file-row-main">
|
||||||
|
<div class="file-type-badge" aria-hidden="true">${escapeHtml(fileTypeBadge(file))}</div>
|
||||||
|
<div class="file-copy">
|
||||||
|
<div class="file-name" title="${escapeHtml(file.filename)}">${escapeHtml(file.filename)}</div>
|
||||||
|
<div class="file-meta">
|
||||||
|
<span>${formatBytes(file.size_bytes)}</span>
|
||||||
|
<span>${escapeHtml(file.mime_type || "file")}</span>
|
||||||
|
${created ? `<span>${escapeHtml(created)}</span>` : ""}
|
||||||
|
<span class="file-flag${file.is_attached ? "" : " detached"}">${escapeHtml(status)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="file-actions">${actions.primary}</div>
|
||||||
|
<div class="file-secondary-actions" hidden>${actions.secondary}</div>
|
||||||
|
<div class="file-code" hidden><textarea readonly aria-label="${escapeHtml(generatedCodeLabel)}"></textarea><button class="action-button action-button--primary compact-button" data-copy-generated>${escapeHtml(t("share.copy", {}, "Copy"))}</button></div>
|
||||||
|
</article>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderFiles() {
|
||||||
|
if (!filesLoaded) return;
|
||||||
|
if (search) search.disabled = false;
|
||||||
|
const query = String(search?.value || "").trim().toLocaleLowerCase();
|
||||||
|
const restrictionMessage = getDeleteRestrictionMessage();
|
||||||
|
const restrictionNotice = restrictionMessage
|
||||||
|
? `<p class="file-delete-notice">${escapeHtml(restrictionMessage)}</p>`
|
||||||
|
: "";
|
||||||
|
const visible = query ? currentFiles.filter(file => fileSearchText(file).includes(query)) : currentFiles;
|
||||||
|
if (summary) summary.textContent = summaryText(currentFiles, visible.length);
|
||||||
|
|
||||||
|
if (!currentFiles.length) {
|
||||||
|
list.innerHTML = `${restrictionNotice}<div class="files-state files-state--empty"><div class="files-state-mark" aria-hidden="true">+</div><strong>${escapeHtml(t("files.empty", {}, "No files uploaded."))}</strong><span>${escapeHtml(t("files.emptyHelp", {}, "Upload a file to keep it with this note and insert it into the document."))}</span></div>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!visible.length) {
|
||||||
|
list.innerHTML = `${restrictionNotice}<div class="files-state files-state--empty"><div class="files-state-mark files-state-mark--search" aria-hidden="true">⌕</div><strong>${escapeHtml(t("files.noMatches", {}, "No files match this filter."))}</strong><span>${escapeHtml(t("files.noMatchesHelp", {}, "Try a different file name or type."))}</span></div>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
list.innerHTML = restrictionNotice + visible.map(fileRow).join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderLoading({ refreshing = false } = {}) {
|
||||||
|
if (search && !filesLoaded) search.disabled = true;
|
||||||
|
if (summary) {
|
||||||
|
summary.innerHTML = `<span class="ui-spinner ui-spinner--small" aria-hidden="true"></span><span>${escapeHtml(t(refreshing ? "files.refreshing" : "files.loading", {}, refreshing ? "Refreshing…" : "Loading files…"))}</span>`;
|
||||||
|
}
|
||||||
|
if (refreshing && filesLoaded) return;
|
||||||
|
list.innerHTML = Array.from({ length: 3 }, () => `
|
||||||
|
<div class="file-row file-row--skeleton" aria-hidden="true">
|
||||||
|
<div class="file-row-main"><span class="file-skeleton file-skeleton--badge"></span><div class="file-copy"><span class="file-skeleton file-skeleton--name"></span><span class="file-skeleton file-skeleton--meta"></span></div></div>
|
||||||
|
<span class="file-skeleton file-skeleton--action"></span>
|
||||||
|
</div>`).join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderLoadError(error) {
|
||||||
|
if (search && !filesLoaded) search.disabled = true;
|
||||||
|
if (summary) summary.textContent = t("files.loadFailed", {}, "Could not load files");
|
||||||
|
if (filesLoaded) return;
|
||||||
|
list.innerHTML = `<div class="files-state files-state--error"><div class="files-state-mark" aria-hidden="true">!</div><strong>${escapeHtml(t("files.loadFailed", {}, "Could not load files"))}</strong><span>${escapeHtml(error?.message || "")}</span><button class="action-button action-button--secondary compact-button" type="button" data-files-retry>${escapeHtml(t("files.retry", {}, "Try again"))}</button></div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadFiles({ open = false } = {}) {
|
async function loadFiles({ open = false } = {}) {
|
||||||
|
if (open && !dialog.open) dialog.showModal();
|
||||||
|
const sequence = ++loadSequence;
|
||||||
|
renderLoading({ refreshing: filesLoaded });
|
||||||
try {
|
try {
|
||||||
const files = await api(endpoints.list, { method: "PUT", body: JSON.stringify({ access_token: getAccessToken() || null }) });
|
const files = await api(endpoints.list, { method: "PUT", body: JSON.stringify({ access_token: getAccessToken() || null }) });
|
||||||
const totalSize = files.reduce((sum, file) => sum + (Number(file.size_bytes) || 0), 0);
|
if (sequence !== loadSequence) return;
|
||||||
footer.textContent = tp("files.summary", files.length, { size: formatBytes(totalSize) }, `${files.length} ${files.length === 1 ? "file" : "files"} · ${formatBytes(totalSize)}`);
|
currentFiles = Array.isArray(files) ? files : [];
|
||||||
const restrictionMessage = getDeleteRestrictionMessage();
|
filesLoaded = true;
|
||||||
const restrictionNotice = restrictionMessage
|
const totalSize = currentFiles.reduce((sum, file) => sum + (Number(file.size_bytes) || 0), 0);
|
||||||
? `<p class="file-delete-notice">${escapeHtml(restrictionMessage)}</p>`
|
footer.textContent = tp("files.summary", currentFiles.length, { size: formatBytes(totalSize) }, `${currentFiles.length} ${currentFiles.length === 1 ? "file" : "files"} · ${formatBytes(totalSize)}`);
|
||||||
: "";
|
renderFiles();
|
||||||
const fileRows = files.length ? files.map(file => `
|
onFilesChanged(currentFiles);
|
||||||
<div class="file-row" data-file-row="${file.id}">
|
|
||||||
<div class="file-row-main">
|
|
||||||
<div class="file-name">${escapeHtml(file.filename)}</div>
|
|
||||||
<div class="file-meta">${formatBytes(file.size_bytes)} · ${escapeHtml(file.mime_type)}${file.created_at ? ` · ${formatDate(file.created_at)}` : ""} · <span class="file-flag${file.is_attached ? "" : " detached"}">${file.is_attached ? "in note" : "removed from content"}</span></div>
|
|
||||||
</div>
|
|
||||||
<div class="file-actions">${fileActionButtons(file)}</div>
|
|
||||||
<div class="file-code" hidden><textarea readonly aria-label="Generated file code"></textarea><button class="action-button action-button--primary compact-button" data-copy-generated>Copy</button></div>
|
|
||||||
</div>`).join("") : '<p class="dialog-copy">No files uploaded.</p>';
|
|
||||||
list.innerHTML = restrictionNotice + fileRows;
|
|
||||||
onFilesChanged(files);
|
|
||||||
if (open && !dialog.open) dialog.showModal();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (open) notify("danger", error.message, { title: "Could not load files" });
|
if (sequence !== loadSequence) return;
|
||||||
|
renderLoadError(error);
|
||||||
|
if (open) notify("danger", error.message, { title: t("files.loadFailed", {}, "Could not load files") });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,7 +421,25 @@ export function bindNoteFiles({ editor, endpoints, getAccessToken, getUploadMaxS
|
|||||||
document.querySelector("#files-button").addEventListener("click", () => loadFiles({ open: true }));
|
document.querySelector("#files-button").addEventListener("click", () => loadFiles({ open: true }));
|
||||||
footer.addEventListener("click", () => loadFiles({ open: true }));
|
footer.addEventListener("click", () => loadFiles({ open: true }));
|
||||||
document.querySelector("#close-files").addEventListener("click", () => dialog.close());
|
document.querySelector("#close-files").addEventListener("click", () => dialog.close());
|
||||||
|
search?.addEventListener("input", renderFiles);
|
||||||
list.addEventListener("click", async event => {
|
list.addEventListener("click", async event => {
|
||||||
|
const retryButton = event.target.closest("[data-files-retry]");
|
||||||
|
if (retryButton) {
|
||||||
|
void loadFiles();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleButton = event.target.closest("[data-toggle-file-actions]");
|
||||||
|
if (toggleButton) {
|
||||||
|
const panel = toggleButton.closest(".file-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");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const addButton = event.target.closest("[data-add-file-to-note]");
|
const addButton = event.target.closest("[data-add-file-to-note]");
|
||||||
if (addButton) {
|
if (addButton) {
|
||||||
const mode = addButton.dataset.insertMode;
|
const mode = addButton.dataset.insertMode;
|
||||||
|
|||||||
Reference in New Issue
Block a user