From 8e1e9fe2011e12fef1b7306e0aa1827aa428bba9 Mon Sep 17 00:00:00 2001 From: Edouard Vanbelle Date: Wed, 6 May 2026 17:43:30 +0200 Subject: [PATCH] show item path on Recent + Favorites, add go to parent folder, fix Folder browsing in Favorites --- src/application/dtos/favorites_dto.rs | 5 ++ src/application/dtos/recent_dto.rs | 5 ++ .../pg/favorites_pg_repository.rs | 10 ++- .../pg/recent_items_pg_repository.rs | 10 ++- static/css/base/variables.css | 2 + static/css/components/pathTooltip.css | 25 ++++++ static/css/layout/sidebar.css | 2 +- static/index.html | 1 + static/js/app/ui.js | 21 ++++- static/js/features/files/contextMenus.js | 18 +++++ static/js/features/library/favorites.js | 10 ++- static/js/features/library/recent.js | 8 +- static/js/features/pathTooltip.js | 78 +++++++++++++++++++ static/locales/ar.json | 3 +- static/locales/de.json | 3 +- static/locales/en.json | 3 +- static/locales/es.json | 3 +- static/locales/fa.json | 3 +- static/locales/fr.json | 3 +- static/locales/hi.json | 3 +- static/locales/it.json | 3 +- static/locales/ja.json | 3 +- static/locales/ko.json | 3 +- static/locales/nl.json | 3 +- static/locales/pl.json | 3 +- static/locales/pt.json | 3 +- static/locales/ru.json | 3 +- static/locales/zh.json | 3 +- 28 files changed, 217 insertions(+), 23 deletions(-) create mode 100644 static/css/components/pathTooltip.css create mode 100644 static/js/features/pathTooltip.js diff --git a/src/application/dtos/favorites_dto.rs b/src/application/dtos/favorites_dto.rs index 21a473d5..545cef0a 100644 --- a/src/application/dtos/favorites_dto.rs +++ b/src/application/dtos/favorites_dto.rs @@ -46,6 +46,11 @@ pub struct FavoriteItemDto { #[serde(skip_serializing_if = "Option::is_none")] pub modified_at: Option>, + /// Full human-readable path (e.g. "Documents/Work" for a folder, + /// "Documents/Work/report.pdf" for a file) + #[serde(skip_serializing_if = "Option::is_none")] + pub item_path: Option, + // ── Pre-computed display fields ── /// FontAwesome icon CSS class (e.g. "fas fa-file-image", "fas fa-folder") pub icon_class: String, diff --git a/src/application/dtos/recent_dto.rs b/src/application/dtos/recent_dto.rs index 85dec184..0f03cf83 100644 --- a/src/application/dtos/recent_dto.rs +++ b/src/application/dtos/recent_dto.rs @@ -42,6 +42,11 @@ pub struct RecentItemDto { #[serde(skip_serializing_if = "Option::is_none")] pub parent_id: Option, + /// Full human-readable path (e.g. "Documents/Work" for a folder, + /// "Documents/Work/report.pdf" for a file) + #[serde(skip_serializing_if = "Option::is_none")] + pub item_path: Option, + // ── Pre-computed display fields ── /// FontAwesome icon CSS class (e.g. "fas fa-file-image", "fas fa-folder") pub icon_class: String, diff --git a/src/infrastructure/repositories/pg/favorites_pg_repository.rs b/src/infrastructure/repositories/pg/favorites_pg_repository.rs index 800cb847..b705810e 100644 --- a/src/infrastructure/repositories/pg/favorites_pg_repository.rs +++ b/src/infrastructure/repositories/pg/favorites_pg_repository.rs @@ -33,10 +33,17 @@ impl FavoritesRepositoryPort for FavoritesPgRepository { f.size AS "item_size", f.mime_type AS "item_mime_type", COALESCE(f.folder_id::TEXT, fld.parent_id::TEXT) AS "parent_id", - COALESCE(f.updated_at, fld.updated_at) AS "modified_at" + COALESCE(f.updated_at, fld.updated_at) AS "modified_at", + CASE + WHEN uf.item_type = 'folder' THEN fld.path + WHEN uf.item_type = 'file' THEN COALESCE(pfld.path || '/' || f.name, f.name) + ELSE NULL + END AS "item_path" FROM auth.user_favorites uf LEFT JOIN storage.files f ON uf.item_type = 'file' AND f.id = uf.item_id::UUID + LEFT JOIN storage.folders pfld ON uf.item_type = 'file' + AND pfld.id = f.folder_id LEFT JOIN storage.folders fld ON uf.item_type = 'folder' AND fld.id = uf.item_id::UUID WHERE uf.user_id = $1 @@ -70,6 +77,7 @@ impl FavoritesRepositoryPort for FavoritesPgRepository { item_mime_type: row.try_get("item_mime_type").ok(), parent_id: row.try_get("parent_id").ok(), modified_at: row.try_get("modified_at").ok(), + item_path: row.try_get("item_path").ok(), // Temporary defaults; with_display_fields() computes the real values icon_class: String::new(), icon_special_class: String::new(), diff --git a/src/infrastructure/repositories/pg/recent_items_pg_repository.rs b/src/infrastructure/repositories/pg/recent_items_pg_repository.rs index 759d82fe..867331b7 100644 --- a/src/infrastructure/repositories/pg/recent_items_pg_repository.rs +++ b/src/infrastructure/repositories/pg/recent_items_pg_repository.rs @@ -31,10 +31,17 @@ impl RecentItemsRepositoryPort for RecentItemsPgRepository { COALESCE(f.name, fld.name) AS "item_name", f.size AS "item_size", f.mime_type AS "item_mime_type", - COALESCE(f.folder_id::TEXT, fld.parent_id::TEXT) AS "parent_id" + COALESCE(f.folder_id::TEXT, fld.parent_id::TEXT) AS "parent_id", + CASE + WHEN ur.item_type = 'folder' THEN fld.path + WHEN ur.item_type = 'file' THEN COALESCE(pfld.path || '/' || f.name, f.name) + ELSE NULL + END AS "item_path" FROM auth.user_recent_files ur LEFT JOIN storage.files f ON ur.item_type = 'file' AND f.id = ur.item_id::UUID + LEFT JOIN storage.folders pfld ON ur.item_type = 'file' + AND pfld.id = f.folder_id LEFT JOIN storage.folders fld ON ur.item_type = 'folder' AND fld.id = ur.item_id::UUID WHERE ur.user_id = $1 @@ -68,6 +75,7 @@ impl RecentItemsRepositoryPort for RecentItemsPgRepository { item_size: row.try_get("item_size").ok(), item_mime_type: row.try_get("item_mime_type").ok(), parent_id: row.try_get("parent_id").ok(), + item_path: row.try_get("item_path").ok(), // Temporary defaults; with_display_fields() computes the real values icon_class: String::new(), icon_special_class: String::new(), diff --git a/static/css/base/variables.css b/static/css/base/variables.css index 73f73ffa..3cbd8dcd 100644 --- a/static/css/base/variables.css +++ b/static/css/base/variables.css @@ -1,4 +1,6 @@ :root { + --sidebar-width: 250px; + /* Backgrounds */ --color-bg-page: #f5f7fa; --color-bg-surface: #ffffff; diff --git a/static/css/components/pathTooltip.css b/static/css/components/pathTooltip.css new file mode 100644 index 00000000..575c0ff8 --- /dev/null +++ b/static/css/components/pathTooltip.css @@ -0,0 +1,25 @@ +.path-tooltip { + position: fixed; + bottom: 8px; + left: calc(var(--sidebar-width) + 8px); + z-index: 5; + + max-width: 120ch; + padding: 4px 10px; + border-radius: 3px; + + background-color: var(--color-bg-subtle); + border: 1px solid var(--color-border-faint); + color: var(--color-text-muted); + font-size: 0.75rem; + font-family: monospace; + + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + pointer-events: none; + + /* avoid blinking when pointer change items */ + transition: display 0.2s allow-discrete; +} diff --git a/static/css/layout/sidebar.css b/static/css/layout/sidebar.css index daecb3d2..fd8ad252 100644 --- a/static/css/layout/sidebar.css +++ b/static/css/layout/sidebar.css @@ -1,6 +1,6 @@ /* Sidebar */ .sidebar { - width: 250px; + width: var(--sidebar-width); background: linear-gradient(180deg, var(--color-sidebar-bg-from) 0%, var(--color-sidebar-bg-to) 100%); color: var(--color-sidebar-text-active); display: flex; diff --git a/static/index.html b/static/index.html index 22ddc143..3d5037de 100644 --- a/static/index.html +++ b/static/index.html @@ -17,6 +17,7 @@ + diff --git a/static/js/app/ui.js b/static/js/app/ui.js index 3f4b86b0..f8cb49bc 100644 --- a/static/js/app/ui.js +++ b/static/js/app/ui.js @@ -20,7 +20,7 @@ import { thumbnail } from '../features/thumbnail.js'; import { sharedView } from '../views/shared/sharedView.js'; import { loadFiles } from './filesView.js'; import { updateHistory } from './main.js'; -import { syncViewContainers } from './navigation.js'; +import { switchToFilesSection, syncViewContainers } from './navigation.js'; import { app } from './state.js'; import { uiFileTypes } from './uiFileTypes.js'; import { uiNotifications } from './uiNotifications.js'; @@ -62,6 +62,7 @@ const ui = { `; document.body.appendChild(folderMenu); + i18n.translateElement(folderMenu); } // File context menu @@ -82,6 +83,9 @@ const ui = {
Download
+
Add to favorites @@ -105,6 +109,7 @@ const ui = {
`; document.body.appendChild(fileMenu); + i18n.translateElement(fileMenu); } // Rename dialog — modern @@ -912,6 +917,12 @@ const ui = { const navigateFolder = (card) => { const folderId = card.dataset.folderId; const folderName = card.dataset.folderName; + if (app.currentSection === 'favorites' || app.currentSection === 'recent') { + switchToFilesSection(); + app.currentPath = folderId; + loadFiles(); + return; + } app.breadcrumbPath.push({ id: folderId, name: folderName }); app.currentPath = folderId; this.updateBreadcrumb(); @@ -1012,6 +1023,9 @@ const ui = { if (contextMenus && typeof contextMenus.syncAddToPlaylistOption === 'function') { contextMenus.syncAddToPlaylistOption(); } + if (contextMenus && typeof contextMenus.syncOpenParentFolderOption === 'function') { + contextMenus.syncOpenParentFolderOption(); + } if (menu) { menu.style.left = `${e.pageX}px`; menu.style.top = `${e.pageY}px`; @@ -1295,6 +1309,7 @@ const ui = { el.dataset.folderId = folder.id; el.dataset.folderName = folder.name; el.dataset.parentId = folder.parent_id || ''; + if (folder.path) el.dataset.path = folder.path; const isFav = favorites?.isFavorite(folder.id, 'folder'); const isShared = sharedView.isShared(folder.id, 'folder'); @@ -1345,6 +1360,7 @@ const ui = { el.dataset.fileId = file.id; el.dataset.fileName = file.name; el.dataset.folderId = file.folder_id || ''; + if (file.path) el.dataset.path = file.path; el.setAttribute('draggable', 'true'); el.innerHTML = ` @@ -1557,6 +1573,9 @@ function showContextMenuAtElement(triggerElement, menuId) { if (contextMenus && typeof contextMenus.syncAddToPlaylistOption === 'function') { contextMenus.syncAddToPlaylistOption(); } + if (contextMenus && typeof contextMenus.syncOpenParentFolderOption === 'function') { + contextMenus.syncOpenParentFolderOption(); + } menu.style.left = `${left}px`; menu.style.top = `${top}px`; diff --git a/static/js/features/files/contextMenus.js b/static/js/features/files/contextMenus.js index f322d365..bd47d5db 100644 --- a/static/js/features/files/contextMenus.js +++ b/static/js/features/files/contextMenus.js @@ -5,6 +5,7 @@ import { resolveHomeFolder } from '../../app/authSession.js'; import { loadFiles } from '../../app/filesView.js'; +import { switchToFilesSection } from '../../app/navigation.js'; import { app } from '../../app/state.js'; import { showConfirmDialog, ui } from '../../app/ui.js'; import { getCsrfHeaders } from '../../core/csrf.js'; @@ -64,6 +65,13 @@ const contextMenus = { } }, + syncOpenParentFolderOption() { + const option = document.getElementById('open-parent-folder-option'); + if (!option) return; + const folderId = app?.contextMenuTargetFile?.folder_id; + option.classList.toggle('hidden', !folderId); + }, + syncAddToPlaylistOption() { const option = document.getElementById('add-to-playlist-option'); if (!option) return; @@ -198,6 +206,16 @@ const contextMenus = { ui.closeFileContextMenu(); }); + document.getElementById('open-parent-folder-option').addEventListener('click', () => { + const folderId = app.contextMenuTargetFile?.folder_id; + ui.closeFileContextMenu(); + if (folderId) { + switchToFilesSection(); + app.currentPath = folderId; + loadFiles(); + } + }); + document.getElementById('favorite-file-option').addEventListener('click', async () => { if (app.contextMenuTargetFile) { const file = app.contextMenuTargetFile; diff --git a/static/js/features/library/favorites.js b/static/js/features/library/favorites.js index 7fb19e63..0979ce39 100644 --- a/static/js/features/library/favorites.js +++ b/static/js/features/library/favorites.js @@ -10,6 +10,7 @@ import { ui } from '../../app/ui.js'; import { getCsrfHeaders } from '../../core/csrf.js'; import { i18n } from '../../core/i18n.js'; import { multiSelect } from '../files/multiSelect.js'; +import * as pathTooltip from '../pathTooltip.js'; const favorites = { /** @type {Map} key = "file:" | "folder:" */ @@ -190,7 +191,8 @@ const favorites = { id: item.item_id, name: item.item_name || item.item_id, parent_id: item.parent_id || '', - modified_at: item.modified_at || item.created_at + modified_at: item.modified_at || item.created_at, + path: item.item_path || '' }); } else { files.push({ @@ -203,12 +205,16 @@ const favorites = { category: item.category, size: item.item_size || 0, size_formatted: item.size_formatted, - modified_at: item.modified_at || item.created_at + modified_at: item.modified_at || item.created_at, + path: item.item_path || '' }); } } if (folders.length) ui.renderFolders(folders); if (files.length) ui.renderFiles(files); + + const filesList = document.getElementById('files-list'); + if (filesList) pathTooltip.init(filesList); } catch (error) { console.error('Error displaying favorites:', error); if (ui?.showNotification) { diff --git a/static/js/features/library/recent.js b/static/js/features/library/recent.js index 833b33a7..ad4b833b 100644 --- a/static/js/features/library/recent.js +++ b/static/js/features/library/recent.js @@ -10,6 +10,7 @@ import { ui } from '../../app/ui.js'; import { getCsrfHeaders } from '../../core/csrf.js'; import { i18n } from '../../core/i18n.js'; import { multiSelect } from '../files/multiSelect.js'; +import * as pathTooltip from '../pathTooltip.js'; const recent = { /** Maximum items to request from the server */ @@ -128,7 +129,8 @@ const recent = { id: item.item_id, name: item.item_name || item.item_id, parent_id: item.parent_id || '', - modified_at: item.accessed_at + modified_at: item.accessed_at, + path: item.item_path || '' }); } else { files.push({ @@ -141,12 +143,14 @@ const recent = { category: item.category, size: item.item_size || 0, size_formatted: item.size_formatted, - modified_at: item.accessed_at + modified_at: item.accessed_at, + path: item.item_path || '' }); } } if (folders.length) ui.renderFolders(folders); if (files.length) ui.renderFiles(files); + if (filesList) pathTooltip.init(filesList); } catch (error) { console.error('Error displaying recent files:', error); if (ui?.showNotification) { diff --git a/static/js/features/pathTooltip.js b/static/js/features/pathTooltip.js new file mode 100644 index 00000000..4af11ab6 --- /dev/null +++ b/static/js/features/pathTooltip.js @@ -0,0 +1,78 @@ +/** + * Path tooltip — shows the full path of a hovered file/folder item + * in an overlay at the bottom-left of the content area. + * + * Usage: call init(container) after rendering items, destroy(container) on teardown. + * Only file-item elements with a data-path attribute trigger the tooltip. + */ + +/** @type {HTMLElement|null} */ +let _tooltip = null; + +function _getOrCreateTooltip() { + if (_tooltip) return _tooltip; + _tooltip = document.getElementById('path-tooltip'); + if (!_tooltip) { + _tooltip = document.createElement('div'); + _tooltip.id = 'path-tooltip'; + _tooltip.className = 'path-tooltip hidden'; + document.querySelector('.main-content')?.appendChild(_tooltip); + } + return _tooltip; +} + +/** + * @param {MouseEvent} e + */ +function _onEnter(e) { + const item = /** @type {HTMLElement} */ (e.currentTarget); + const path = item.dataset.path; + if (!path) return; + + const tooltip = _getOrCreateTooltip(); + tooltip.textContent = path; + tooltip.classList.remove('hidden'); +} + +function _onLeave() { + _tooltip?.classList.add('hidden'); +} + +/** @type {WeakMap} */ +const _listeners = new WeakMap(); + +/** + * Attach path tooltip listeners to all file-item elements inside container. + * @param {HTMLElement} container + */ +function init(container) { + const items = container.querySelectorAll('.file-item[data-path]'); + items.forEach((item) => { + const el = /** @type {HTMLElement} */ (item); + const enter = (e) => _onEnter(e); + const leave = () => _onLeave(); + el.addEventListener('mouseenter', enter); + el.addEventListener('mouseleave', leave); + _listeners.set(el, { enter, leave }); + }); +} + +/** + * Remove path tooltip listeners from all file-item elements inside container. + * @param {HTMLElement} container + */ +function destroy(container) { + const items = container.querySelectorAll('.file-item'); + items.forEach((item) => { + const el = /** @type {HTMLElement} */ (item); + const fns = _listeners.get(el); + if (fns) { + el.removeEventListener('mouseenter', fns.enter); + el.removeEventListener('mouseleave', fns.leave); + _listeners.delete(el); + } + }); + _onLeave(); +} + +export { destroy, init }; diff --git a/static/locales/ar.json b/static/locales/ar.json index a70ef652..9b4a9d1e 100644 --- a/static/locales/ar.json +++ b/static/locales/ar.json @@ -116,7 +116,8 @@ "search_btn": "بحث", "close": "إغلاق", "delete_permanently": "حذف نهائياً", - "empty_trash": "تفريغ سلة المهملات" + "empty_trash": "تفريغ سلة المهملات", + "open_parent_folder": "الانتقال إلى المجلد الأصلي" }, "user_menu": { "appearance": "المظهر", diff --git a/static/locales/de.json b/static/locales/de.json index e470e34b..d7574ae5 100644 --- a/static/locales/de.json +++ b/static/locales/de.json @@ -116,7 +116,8 @@ "search_btn": "Suchen", "close": "Schließen", "delete_permanently": "Endgültig löschen", - "empty_trash": "Papierkorb leeren" + "empty_trash": "Papierkorb leeren", + "open_parent_folder": "Zum übergeordneten Ordner" }, "user_menu": { "appearance": "Erscheinungsbild", diff --git a/static/locales/en.json b/static/locales/en.json index c70b250c..d39c2aa3 100644 --- a/static/locales/en.json +++ b/static/locales/en.json @@ -116,7 +116,8 @@ "search_btn": "Search", "close": "Close", "delete_permanently": "Delete permanently", - "empty_trash": "Empty trash" + "empty_trash": "Empty trash", + "open_parent_folder": "Go to parent folder" }, "user_menu": { "appearance": "Appearance", diff --git a/static/locales/es.json b/static/locales/es.json index 0c776c09..26d7bab7 100644 --- a/static/locales/es.json +++ b/static/locales/es.json @@ -235,7 +235,8 @@ "search_btn": "Buscar", "close": "Cerrar", "delete_permanently": "Eliminar permanentemente", - "empty_trash": "Vaciar papelera" + "empty_trash": "Vaciar papelera", + "open_parent_folder": "Ir a la carpeta padre" }, "user_menu": { "appearance": "Apariencia", diff --git a/static/locales/fa.json b/static/locales/fa.json index 023bc2d8..0ce76f48 100644 --- a/static/locales/fa.json +++ b/static/locales/fa.json @@ -116,7 +116,8 @@ "search_btn": "جست‌و‌جو", "close": "بستن", "delete_permanently": "Delete permanently", - "empty_trash": "Empty trash" + "empty_trash": "Empty trash", + "open_parent_folder": "رفتن به پوشه والد" }, "user_menu": { "appearance": "ظاهر", diff --git a/static/locales/fr.json b/static/locales/fr.json index 27ffae81..abfae6ed 100644 --- a/static/locales/fr.json +++ b/static/locales/fr.json @@ -116,7 +116,8 @@ "search_btn": "Rechercher", "close": "Fermer", "delete_permanently": "Supprimer définitivement", - "empty_trash": "Vider la corbeille" + "empty_trash": "Vider la corbeille", + "open_parent_folder": "Aller au dossier parent" }, "user_menu": { "appearance": "Apparence", diff --git a/static/locales/hi.json b/static/locales/hi.json index 36d79903..360e3e33 100644 --- a/static/locales/hi.json +++ b/static/locales/hi.json @@ -116,7 +116,8 @@ "search_btn": "खोजें", "close": "बंद करें", "delete_permanently": "स्थायी रूप से हटाएँ", - "empty_trash": "रद्दी खाली करें" + "empty_trash": "रद्दी खाली करें", + "open_parent_folder": "मूल फ़ोल्डर पर जाएं" }, "user_menu": { "appearance": "दिखावट", diff --git a/static/locales/it.json b/static/locales/it.json index 0e64b1d8..f11ff4d0 100644 --- a/static/locales/it.json +++ b/static/locales/it.json @@ -116,7 +116,8 @@ "search_btn": "Cerca", "close": "Chiudi", "delete_permanently": "Elimina definitivamente", - "empty_trash": "Svuota il cestino" + "empty_trash": "Svuota il cestino", + "open_parent_folder": "Vai alla cartella padre" }, "user_menu": { "appearance": "Aspetto", diff --git a/static/locales/ja.json b/static/locales/ja.json index a6406a34..8744589f 100644 --- a/static/locales/ja.json +++ b/static/locales/ja.json @@ -116,7 +116,8 @@ "search_btn": "検索", "close": "閉じる", "delete_permanently": "完全に削除", - "empty_trash": "ゴミ箱を空にする" + "empty_trash": "ゴミ箱を空にする", + "open_parent_folder": "親フォルダへ移動" }, "user_menu": { "appearance": "外観", diff --git a/static/locales/ko.json b/static/locales/ko.json index c2256ea7..80a3663e 100644 --- a/static/locales/ko.json +++ b/static/locales/ko.json @@ -116,7 +116,8 @@ "search_btn": "검색", "close": "닫기", "delete_permanently": "영구 삭제", - "empty_trash": "휴지통 비우기" + "empty_trash": "휴지통 비우기", + "open_parent_folder": "상위 폴더로 이동" }, "user_menu": { "appearance": "외관", diff --git a/static/locales/nl.json b/static/locales/nl.json index 68f836cc..8e582422 100644 --- a/static/locales/nl.json +++ b/static/locales/nl.json @@ -116,7 +116,8 @@ "search_btn": "Zoeken", "close": "Sluiten", "delete_permanently": "Permanent verwijderen", - "empty_trash": "Prullenbak legen" + "empty_trash": "Prullenbak legen", + "open_parent_folder": "Naar bovenliggende map" }, "user_menu": { "appearance": "Weergave", diff --git a/static/locales/pl.json b/static/locales/pl.json index d5b13014..b746fbdc 100644 --- a/static/locales/pl.json +++ b/static/locales/pl.json @@ -116,7 +116,8 @@ "search_btn": "Szukaj", "close": "Zamknij", "delete_permanently": "Usuń trwale", - "empty_trash": "Opróżnij kosz" + "empty_trash": "Opróżnij kosz", + "open_parent_folder": "Przejdź do folderu nadrzędnego" }, "user_menu": { "appearance": "Wygląd", diff --git a/static/locales/pt.json b/static/locales/pt.json index dc55c3ba..ced7be8e 100644 --- a/static/locales/pt.json +++ b/static/locales/pt.json @@ -116,7 +116,8 @@ "search_btn": "Pesquisar", "close": "Fechar", "delete_permanently": "Excluir permanentemente", - "empty_trash": "Esvaziar lixeira" + "empty_trash": "Esvaziar lixeira", + "open_parent_folder": "Ir para a pasta pai" }, "user_menu": { "appearance": "Aparência", diff --git a/static/locales/ru.json b/static/locales/ru.json index d14b3185..0524a294 100644 --- a/static/locales/ru.json +++ b/static/locales/ru.json @@ -116,7 +116,8 @@ "search_btn": "Найти", "close": "Закрыть", "delete_permanently": "Удалить навсегда", - "empty_trash": "Очистить корзину" + "empty_trash": "Очистить корзину", + "open_parent_folder": "Перейти в родительскую папку" }, "user_menu": { "appearance": "Оформление", diff --git a/static/locales/zh.json b/static/locales/zh.json index 9223f058..c8eb1aae 100644 --- a/static/locales/zh.json +++ b/static/locales/zh.json @@ -116,7 +116,8 @@ "search_btn": "搜索", "close": "关闭", "delete_permanently": "Delete permanently", - "empty_trash": "Empty trash" + "empty_trash": "Empty trash", + "open_parent_folder": "转到父文件夹" }, "user_menu": { "appearance": "外观",