From 60d53ea77938678e9e1551e8f194ffd038aeef52 Mon Sep 17 00:00:00 2001 From: Edouard Vanbelle Date: Fri, 22 May 2026 10:15:25 +0200 Subject: [PATCH] feat(ui): add section 'Shared with me' --- static/css/views/sharedWithMe.css | 13 + static/index.html | 7 +- static/js/app/filesView.js | 31 ++- static/js/app/main.js | 22 +- static/js/app/navigation.js | 48 ++++ static/js/app/ui.js | 24 +- static/js/core/icons.js | 8 + static/js/core/types.js | 60 +++++ static/js/model/grants.js | 111 ++++++++ .../js/views/sharedWithMe/sharedWithMeView.js | 248 ++++++++++++++++++ static/locales/ar.json | 20 +- static/locales/de.json | 20 +- static/locales/en.json | 18 +- static/locales/es.json | 18 +- static/locales/fa.json | 20 +- static/locales/fr.json | 20 +- static/locales/hi.json | 18 +- static/locales/it.json | 20 +- static/locales/ja.json | 18 +- static/locales/ko.json | 18 +- static/locales/nl.json | 18 +- static/locales/pl.json | 18 +- static/locales/pt.json | 20 +- static/locales/ru.json | 18 +- static/locales/zh-TW.json | 18 +- static/locales/zh.json | 18 +- 26 files changed, 816 insertions(+), 56 deletions(-) create mode 100644 static/css/views/sharedWithMe.css create mode 100644 static/js/model/grants.js create mode 100644 static/js/views/sharedWithMe/sharedWithMeView.js diff --git a/static/css/views/sharedWithMe.css b/static/css/views/sharedWithMe.css new file mode 100644 index 00000000..d60aad0f --- /dev/null +++ b/static/css/views/sharedWithMe.css @@ -0,0 +1,13 @@ +/** + * Shared-with-me view styles. + * + * The grid/list rendering reuses the standard `.files-container` / `#files-list` + * styles from filesView.css. Only view-specific additions are defined here. + */ + +/* ── "Load more" button row ───────────────────────────────────────────────── */ +.swm-load-more-wrapper { + display: flex; + justify-content: center; + padding: 16px 0 24px; +} diff --git a/static/index.html b/static/index.html index 16692523..e7416e36 100644 --- a/static/index.html +++ b/static/index.html @@ -14,6 +14,7 @@ + @@ -76,9 +77,13 @@ Files + ${_multiSelectButons} ${_toggleButtons} + `, + sharedwithme: ` +
+ ${_toggleButtons} ` }; /** * - * @param {'files' | 'trash' | 'favorites' | 'recent' | 'hidden'} mode + * @param {'files' | 'trash' | 'favorites' | 'recent' | 'sharedwithme' | 'hidden'} mode * @param {boolean} [force=false] * @returns */ @@ -398,6 +405,9 @@ function initApp() { app.viewFile = hashContext.file; } + // get grants (xxx: async methods) + await grants.fetchIncomingGrants(); + await grants.fetchOutgoingGrants(); loadFiles(); } }); @@ -652,6 +662,10 @@ function setupEventListeners() { switchToSharedSection(); break; + case 'nav.sharedwithme': + switchToSharedWithMeSection(); + break; + case 'nav.favorites': // Switch to favorites view switchToFavoritesSection(); @@ -723,6 +737,12 @@ function setupEventListeners() { * @param {string} name */ export function selectFolder(id, name) { + // When entering from a non-files section (e.g. "Shared with me"), + // activate the Files UI (nav active state, breadcrumb, action bar, + // container) without resetting the current path. + if (app.currentSection !== 'files') { + activateFilesUI(); + } app.breadcrumbPath.push({ id, name }); app.currentPath = id; ui.updateBreadcrumb(); diff --git a/static/js/app/navigation.js b/static/js/app/navigation.js index 4cfba60f..df0e2002 100644 --- a/static/js/app/navigation.js +++ b/static/js/app/navigation.js @@ -10,6 +10,7 @@ import { musicView } from '../features/library/music.js'; import { photosView } from '../features/library/photos.js'; import { recent } from '../features/library/recent.js'; import { sharedView } from '../views/shared/sharedView.js'; +import { sharedWithMeView } from '../views/sharedWithMe/sharedWithMeView.js'; import { loadFiles } from './filesView.js'; import { setActionsBarMode } from './main.js'; import { app, appElements } from './state.js'; @@ -122,6 +123,7 @@ function getSectionFromNavItem(navItem) { export const SECTIONS_MAPPER = { files: switchToFilesSection, shared: switchToSharedSection, + sharedwithme: switchToSharedWithMeSection, recent: switchToRecentFilesSection, favorites: switchToFavoritesSection, trash: switchToTrashSection, @@ -157,6 +159,11 @@ function setCurrentSection(section) { sharedView.hide(); } + // Hide "Load more" button when leaving the sharedwithme section + if (section !== 'sharedwithme' && sharedWithMeView) { + sharedWithMeView.hide(); + } + // Hide photosView when switching to any other section if (section !== 'photos' && photosView) { photosView.hide(); @@ -194,6 +201,26 @@ function switchToSharedSection() { if (multiSelect) multiSelect.clear(); } +function switchToSharedWithMeSection() { + if (!setCurrentSection('sharedwithme')) return; + + // Hide breadcrumb (only shown in Files view) + const breadcrumb = document.querySelector('.breadcrumb'); + breadcrumb?.classList.add('hidden'); + + // Show actions-bar with view toggle (no upload / new-folder in this view) + setActionsBarMode('sharedwithme'); + + // Show the standard files container and respect grid/list preference + toggleFileContainer(true); + syncViewContainers(); + + if (multiSelect) multiSelect.clear(); + + // Load and render items into the files container + sharedWithMeView.init(); +} + function switchToFilesSection() { if (!setCurrentSection('files')) return; @@ -368,13 +395,34 @@ function switchToMusicSection() { if (multiSelect) multiSelect.clear(); } +/** + * Activate the Files section UI (nav state, breadcrumb, actions bar, + * files container, grid/list sync) WITHOUT resetting `app.currentPath` + * or `app.breadcrumbPath`. + * + * Used by `selectFolder` when the user clicks a folder from a + * non-files section (e.g. "Shared with me") so the Files view is + * fully set up before the folder content loads. + */ +function activateFilesUI() { + setCurrentSection('files'); + setActionsBarMode('files', true); + const breadcrumb = document.querySelector('.breadcrumb'); + breadcrumb?.classList.remove('hidden'); + toggleFileContainer(true); + syncViewContainers(); + if (multiSelect) multiSelect.clear(); +} + export { + activateFilesUI, switchToFavoritesSection, switchToFilesSection, switchToMusicSection, switchToPhotosSection, switchToRecentFilesSection, switchToSharedSection, + switchToSharedWithMeSection, switchToTrashSection, syncViewContainers }; diff --git a/static/js/app/ui.js b/static/js/app/ui.js index a878f867..9d721e37 100644 --- a/static/js/app/ui.js +++ b/static/js/app/ui.js @@ -17,10 +17,10 @@ import { favorites } from '../features/library/favorites.js'; import { recent } from '../features/library/recent.js'; import { fileSharing } from '../features/sharing/fileSharing.js'; import { thumbnail } from '../features/thumbnail.js'; -import { sharedView } from '../views/shared/sharedView.js'; +import { grants } from '../model/grants.js'; import { loadFiles } from './filesView.js'; import { updateHistory } from './main.js'; -import { switchToFilesSection, syncViewContainers } from './navigation.js'; +import { activateFilesUI, switchToFilesSection, syncViewContainers } from './navigation.js'; import { app } from './state.js'; import { uiFileTypes } from './uiFileTypes.js'; import { uiNotifications } from './uiNotifications.js'; @@ -54,7 +54,7 @@ const ui = { Add to favorites
- Share + Share
@@ -98,7 +98,7 @@ const ui = { Add to favorites
- Share + Share
${escapeHtml(file.name)}
-
+
${typeLabel}
${fileSize}
diff --git a/static/js/core/icons.js b/static/js/core/icons.js index 12d6441d..1d2c5229 100644 --- a/static/js/core/icons.js +++ b/static/js/core/icons.js @@ -246,6 +246,14 @@ const OxiIcons = { 384, 'M223.5 32C100 32 0 132.3 0 256S100 480 223.5 480c60.6 0 115.5-24.2 155.8-63.4c5-4.9 6.3-12.5 3.1-18.7s-10.1-9.7-17-8.5c-9.8 1.7-19.8 2.6-30.1 2.6c-96.9 0-175.5-78.8-175.5-176c0-65.8 36-123.1 89.3-153.3c6.1-3.5 9.2-10.5 7.7-17.3s-7.3-11.9-14.3-12.5c-6.3-.5-12.6-.8-19-.8z' ], + oxiexport: [ + 576, + 'M384.5 24l0 72-64 0c-79.5 0-144 64.5-144 144 0 93.4 82.8 134.8 100.6 142.6 2.2 1 4.6 1.4 7.1 1.4l2.5 0c9.8 0 17.8-8 17.8-17.8 0-8.3-5.9-15.5-12.8-20.3-8.9-6.2-19.2-18.2-19.2-40.5 0-45 36.5-81.5 81.5-81.5l30.5 0 0 72c0 9.7 5.8 18.5 14.8 22.2s19.3 1.7 26.2-5.2l136-136c9.4-9.4 9.4-24.6 0-33.9L425.5 7c-6.9-6.9-17.2-8.9-26.2-5.2S384.5 14.3 384.5 24zm-272 72c-44.2 0-80 35.8-80 80l0 256c0 44.2 35.8 80 80 80l256 0c44.2 0 80-35.8 80-80l0-32c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 32c0 8.8-7.2 16-16 16l-256 0c-8.8 0-16-7.2-16-16l0-256c0-8.8 7.2-16 16-16l16 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-16 0z' + ], + oxiimport: [ + 576, + 'm 360.55,24 v 72 h 64 c 79.5,0 144,64.5 144,144 0,93.4 -82.8,134.8 -100.6,142.6 -2.2,1 -4.6,1.4 -7.1,1.4 h -2.5 c -9.8,0 -17.8,-8 -17.8,-17.8 0,-8.3 5.9,-15.5 12.8,-20.3 8.9,-6.2 19.2,-18.2 19.2,-40.5 0,-45 -36.5,-81.5 -81.5,-81.5 h -30.5 v 72 c 0,9.7 -5.8,18.5 -14.8,22.2 -9,3.7 -19.3,1.7 -26.2,-5.2 l -136,-136 c -9.4,-9.4 -9.4,-24.6 0,-33.9 l 136,-136 c 6.9,-6.9 17.2,-8.9 26.2,-5.2 9,3.7 14.8,12.5 14.8,22.2 z M 112.5,96 c -44.2,0 -80,35.8 -80,80 v 256 c 0,44.2 35.8,80 80,80 h 256 c 44.2,0 80,-35.8 80,-80 v -32 c 0,-17.7 -14.3,-32 -32,-32 -17.7,0 -32,14.3 -32,32 v 32 c 0,8.8 -7.2,16 -16,16 h -256 c -8.8,0 -16,-7.2 -16,-16 V 176 c 0,-8.8 7.2,-16 16,-16 h 16 c 17.7,0 32,-14.3 32,-32 0,-17.7 -14.3,-32 -32,-32 z' + ], pen: [ 512, 'M362.7 19.3L314.3 67.7 444.3 197.7l48.4-48.4c25-25 25-65.5 0-90.5L453.3 19.3c-25-25-65.5-25-90.5 0zm-71 71L58.6 323.5c-10.4 10.4-18 23.3-22.2 37.4L1 481.2C-1.5 489.7 .8 498.8 7 505s15.3 8.5 23.7 6.1l120.3-35.4c14.1-4.2 27-11.8 37.4-22.2L421.7 220.3 291.7 90.3z' diff --git a/static/js/core/types.js b/static/js/core/types.js index a1670b80..030f29a3 100644 --- a/static/js/core/types.js +++ b/static/js/core/types.js @@ -253,3 +253,63 @@ * @property {number|null} width * @property {number|null} height */ + +// ------------------- grants + +/** + * @typedef {'read'|'create'|'share'|'comment'|'delete'|'update'} PermissionTypeEnum + */ + +/** + * @typedef {'folder'|'file'} ResourceTypeEnum + */ + +/** + * @typedef {Object} Resource + * @property {ResourceTypeEnum} type + * @property {String} id + */ + +/** + * @typedef {'user'|'group'|'external'} SubjectTypeEnum + */ + +/** + * @typedef {Object} Subject + * @property {SubjectTypeEnum} type + * @property {String} id + */ + +/** + * @typedef {Object} Grant + * @property {string} id + * @property {number} granted_at + * @property {string} granted_by + * @property {Subject} subject + * @property {PermissionTypeEnum} permission + * @property {Resource} resource + */ + +/** + * Roles: `viewer`, `commenter`, `editor`, `manager`, `admin` + */ + +/** + * One item returned by `GET /api/grants/incoming/resources`. + * Exactly one of `file` / `folder` is populated (indicated by `resource_type`). + * @typedef {Object} SharedWithMeItem + * @property {ResourceTypeEnum} resource_type + * @property {PermissionTypeEnum[]} permissions - All permissions the caller holds on this resource. + * @property {string} granted_at - ISO-8601 timestamp of the earliest grant. + * @property {string} granted_by - UUID of the user who created the grant. + * @property {FileItem|undefined} [file] - Populated when resource_type === 'file'. + * @property {FolderItem|undefined} [folder] - Populated when resource_type === 'folder'. + */ + +/** + * Response for `GET /api/grants/incoming/resources`. + * @typedef {Object} SharedWithMeResponse + * @property {SharedWithMeItem[]} items + * @property {string|undefined} [next_cursor] - Absent when the last page is reached. + */ + diff --git a/static/js/model/grants.js b/static/js/model/grants.js new file mode 100644 index 00000000..e741f1c7 --- /dev/null +++ b/static/js/model/grants.js @@ -0,0 +1,111 @@ +/** + * @import {Grant, ResourceTypeEnum, SharedWithMeResponse} from '../core/types.js' + */ + +const grants = { + /** @type {Record>} */ + outgoingGrants: {}, + + /** @type {Record>} */ + incomingGrants: {}, + + async fetchOutgoingGrants() { + const response = await fetch('/api/grants/outgoing'); + + if (!response.ok) { + console.log(`error ${response.status} while fetching /api/grants/outgoing:`, await response.json()); + return; + } + + /** @type {Grant[]} */ + const outgoingGrants = await response.json(); + + console.log(outgoingGrants); + + // store grants by type, then by id + outgoingGrants.forEach((grant) => { + this.outgoingGrants[grant.resource.type] ??= {}; + this.outgoingGrants[grant.resource.type][grant.resource.id] ??= []; + this.outgoingGrants[grant.resource.type][grant.resource.id].push(grant); + }); + + console.log(`outgoing grants: `, this.outgoingGrants); + }, + + /** + * get grant for a resource + * @param {ResourceTypeEnum} resourceType + * @param {String} id + * @returns {Grant[] | null} + */ + getOutgoingGrantsFor(resourceType, id) { + try { + return this.outgoingGrants[resourceType][id] ?? []; + } catch { + return []; + } + }, + + async fetchIncomingGrants() { + const response = await fetch('/api/grants/incoming'); + + if (!response.ok) { + console.log(`error ${response.status} while fetching /api/grants/incoming:`, await response.json); + return; + } + + /** @type {Grant[]} */ + const incomingGrants = await response.json(); + + // store grants by type, then by id + incomingGrants.forEach((grant) => { + this.incomingGrants[grant.resource.type] ??= {}; + this.incomingGrants[grant.resource.type][grant.resource.id] ??= []; + this.incomingGrants[grant.resource.type][grant.resource.id].push(grant); + }); + + console.log(`incoming grants: `, this.incomingGrants); + }, + + /** + * get grant for a resource + * @param {ResourceTypeEnum} resourceType + * @param {String} id + * @returns {Grant[] | null} + */ + getIncomingGrantsFor(resourceType, id) { + try { + return this.incomingGrants[resourceType][id] ?? []; + } catch { + return []; + } + }, + + /** + * Fetch a cursor-paginated list of resources shared with the current user, + * with full file / folder metadata resolved server-side. + * + * @param {object} [opts] + * @param {ResourceTypeEnum[]} [opts.resourceTypes] - Resource types to include (default: ['file','folder']). + * @param {number} [opts.limit] - Max items per page (1–200, default 50). + * @param {string} [opts.cursor] - Opaque cursor from a previous call; omit for first page. + * @returns {Promise} + */ + async fetchSharedWithMe({ resourceTypes = ['file', 'folder'], limit = 50, cursor } = {}) { + const params = new URLSearchParams({ + limit: String(limit), + resource_types: resourceTypes.join(',') + }); + if (cursor) params.set('cursor', cursor); + + const response = await fetch(`/api/grants/incoming/resources?${params}`); + + if (!response.ok) { + throw new Error(`Failed to fetch shared-with-me items: HTTP ${response.status}`); + } + + return response.json(); + } +}; + +export { grants }; diff --git a/static/js/views/sharedWithMe/sharedWithMeView.js b/static/js/views/sharedWithMe/sharedWithMeView.js new file mode 100644 index 00000000..24ff8171 --- /dev/null +++ b/static/js/views/sharedWithMe/sharedWithMeView.js @@ -0,0 +1,248 @@ +/** + * OxiCloud – "Shared with me" view. + * + * Renders files and folders that other users have explicitly granted the + * current user access to, using the cursor-paginated + * `GET /api/grants/incoming/resources` endpoint. + * + * Reuses the existing `#files-list` container and `ui.renderFolders` / + * `ui.renderFiles` so the grid ↔ list toggle and all card components work + * out of the box. A "Load more" button is injected below the files container + * for cursor-based pagination. + * + * NOTE: the grid/list container will be extracted into a reusable component + * in a future refactor — this view is intentionally kept thin. + */ + +import { ui } from '../../app/ui.js'; +import { i18n } from '../../core/i18n.js'; +import { ownerTooltip } from '../../features/ownerTooltip.js'; +import { multiSelect } from '../../features/files/multiSelect.js'; +import { grants } from '../../model/grants.js'; +import { systemUsers } from '../../model/systemUsers.js'; + +/** @import {SharedWithMeItem, FileItem, FolderItem, ResourceTypeEnum} from '../../core/types.js' */ + +/** ID of the "Load more" wrapper injected below `.files-container`. */ +const LOAD_MORE_ID = 'swm-load-more-wrapper'; + +const sharedWithMeView = { + // ── State ───────────────────────────────────────────────────────────────── + + /** @type {string|null} */ + _nextCursor: null, + + _loading: false, + + // ── Public API ──────────────────────────────────────────────────────────── + + /** + * (Re-)load from page 1 and render into the existing files container. + * Called every time the user switches to this section. + */ + async init() { + this._nextCursor = null; + this._loading = false; + + this._ensureLoadMoreButton(); + + // Start fetching system users in background so tooltips resolve instantly + // by the time the user hovers over an item. + systemUsers.prefetch(); + + // Standard files-view setup: clear list, show container, init multiselect + ui.resetFilesList(); + multiSelect.init(); + ui.updateBreadcrumb(); + + await this._loadPage(); + }, + + /** + * Hide the "Load more" button when leaving this section. + * The files container itself is managed by navigation.js. + */ + hide() { + const w = document.getElementById(LOAD_MORE_ID); + if (w) w.classList.add('hidden'); + + const filesList = document.getElementById('files-list'); + if (filesList) ownerTooltip.destroy(filesList); + }, + + // ── Internal helpers ────────────────────────────────────────────────────── + + /** + * Fetch one page, map items → FileItem / FolderItem, render them, then + * stamp `data-owner-id` and wire the owner tooltip. + * @returns {Promise} + */ + async _loadPage() { + if (this._loading) return; + this._loading = true; + + try { + const data = await grants.fetchSharedWithMe({ + resourceTypes: /** @type {ResourceTypeEnum[]} */ (['file', 'folder']), + limit: 50, + cursor: this._nextCursor ?? undefined + }); + + this._nextCursor = data.next_cursor ?? null; + + if (data.items.length === 0 && !this._nextCursor) { + // First page came back empty + ui.showError(` + +

${i18n.t('sharedwithme_emptyStateTitle', 'Nothing shared with you yet')}

+

${i18n.t('sharedwithme_emptyStateDesc', 'Items shared with you by other users will appear here')}

+ `); + this._setLoadMoreVisible(false); + return; + } + + const { folders, files, ownerMap } = this._mapItems(data.items); + if (folders.length) ui.renderFolders(folders); + if (files.length) ui.renderFiles(files); + + // Stamp data-owner-id on the freshly-rendered cards and attach tooltips. + const filesList = document.getElementById('files-list'); + if (filesList) { + this._stampOwnerIds(filesList, ownerMap); + ownerTooltip.init(filesList); + } + + this._setLoadMoreVisible(!!this._nextCursor); + } catch (err) { + ui.showError(` + +

${i18n.t('errors_loadFailed', 'Failed to load items')}

+ `); + console.error('sharedWithMeView: load error', err); + } finally { + this._loading = false; + } + }, + + /** + * Map `SharedWithMeItem[]` to separate arrays for rendering plus an + * `ownerMap` (itemId → grantedBy userId) used to stamp `data-owner-id` + * after the cards are in the DOM. + * + * The backend already includes all display fields (`icon_class`, + * `icon_special_class`, `category`, `size_formatted`) inside the nested + * `file` / `folder` objects, so no client-side enrichment is needed. + * + * @param {SharedWithMeItem[]} items + * @returns {{ folders: FolderItem[], files: FileItem[], ownerMap: Map }} + */ + _mapItems(items) { + /** @type {FolderItem[]} */ + const folders = []; + + /** @type {FileItem[]} */ + const files = []; + + /** @type {Map} itemId → grantedBy userId */ + const ownerMap = new Map(); + + for (const item of items) { + if (item.resource_type === 'folder' && item.folder) { + const f = item.folder; + folders.push( + /** @type {FolderItem} */ ({ + id: f.id, + name: f.name, + path: f.path ?? '', + parent_id: f.parent_id ?? '', + owner_id: f.owner_id ?? '', + is_root: f.is_root ?? false, + created_at: f.created_at, + modified_at: f.modified_at, + icon_class: f.icon_class, + icon_special_class: f.icon_special_class ?? '', + category: 'folder' + }) + ); + ownerMap.set(f.id, item.granted_by); + } else if (item.resource_type === 'file' && item.file) { + const f = item.file; + files.push( + /** @type {FileItem} */ ({ + id: f.id, + name: f.name, + path: f.path ?? '', + folder_id: f.folder_id ?? '', + owner_id: f.owner_id ?? '', + mime_type: f.mime_type, + size: f.size, + size_formatted: f.size_formatted, + created_at: f.created_at, + modified_at: f.modified_at, + sort_date: f.modified_at, + icon_class: f.icon_class, + icon_special_class: f.icon_special_class ?? '', + category: f.category + }) + ); + ownerMap.set(f.id, item.granted_by); + } + } + + return { folders, files, ownerMap }; + }, + + /** + * Walk `ownerMap` and set `data-owner-id` on matching `.file-item` cards + * inside `container`. Must be called after `renderFolders`/`renderFiles`. + * + * @param {HTMLElement} container + * @param {Map} ownerMap itemId → grantedBy userId + */ + _stampOwnerIds(container, ownerMap) { + for (const [itemId, ownerId] of ownerMap) { + const el = container.querySelector( + `[data-folder-id="${itemId}"], [data-file-id="${itemId}"]` + ); + if (el instanceof HTMLElement) { + el.dataset.ownerId = ownerId; + } + } + }, + + // ── "Load more" button ──────────────────────────────────────────────────── + + /** + * Create the "Load more" wrapper once and attach it below `.files-container`. + * Subsequent calls are no-ops. + */ + _ensureLoadMoreButton() { + if (document.getElementById(LOAD_MORE_ID)) return; + + const filesContainer = document.querySelector('.files-container'); + if (!filesContainer) return; + + const wrapper = document.createElement('div'); + wrapper.id = LOAD_MORE_ID; + wrapper.className = 'swm-load-more-wrapper hidden'; + + const btn = document.createElement('button'); + btn.id = 'swm-load-more'; + btn.className = 'button secondary'; + btn.textContent = i18n.t('sharedwithme_loadMore', 'Load more'); + btn.addEventListener('click', () => this._loadPage()); + + wrapper.appendChild(btn); + filesContainer.after(wrapper); + }, + + /** + * @param {boolean} visible + */ + _setLoadMoreVisible(visible) { + const w = document.getElementById(LOAD_MORE_ID); + if (w) w.classList.toggle('hidden', !visible); + } +}; + +export { sharedWithMeView }; diff --git a/static/locales/ar.json b/static/locales/ar.json index aca16a60..19affe25 100644 --- a/static/locales/ar.json +++ b/static/locales/ar.json @@ -5,12 +5,13 @@ }, "nav": { "files": "الملفات", - "shared": "المشترك", + "shared": "مشاركاتي", "recent": "الأخيرة", "favorites": "المفضلة", "photos": "الصور", "music": "الموسيقى", - "trash": "سلة المهملات" + "trash": "سلة المهملات", + "sharedwithme": "مشتركة معي" }, "photos": { "empty_state": "لا توجد صور بعد", @@ -682,5 +683,18 @@ "files": "ملفات", "complete": "{{count}} / {{total}} تم الرفع" }, - "storage_quota_exceeded": "تجاوز حصة التخزين" + "storage_quota_exceeded": "تجاوز حصة التخزين", + "sharedwithme": { + "pageTitle": "مشترك معي", + "pageDescription": "الملفات والمجلدات التي شاركها معك مستخدمون آخرون", + "emptyStateTitle": "لم يُشارك معك أي شيء بعد", + "emptyStateDesc": "ستظهر هنا العناصر التي يشاركها معك مستخدمون آخرون", + "loadMore": "تحميل المزيد", + "sharedBy": "مشترك من قِبل", + "colName": "الاسم", + "colType": "النوع", + "colSharedBy": "مشترك من قِبل", + "colDate": "تاريخ المشاركة", + "colPermissions": "الصلاحيات" + } } diff --git a/static/locales/de.json b/static/locales/de.json index cefca7db..781199f3 100644 --- a/static/locales/de.json +++ b/static/locales/de.json @@ -5,12 +5,13 @@ }, "nav": { "files": "Dateien", - "shared": "Geteilt", + "shared": "Freigaben", "recent": "Zuletzt verwendet", "favorites": "Favoriten", "photos": "Fotos", "music": "Musik", - "trash": "Papierkorb" + "trash": "Papierkorb", + "sharedwithme": "Mit mir geteilt" }, "photos": { "empty_state": "Noch keine Fotos", @@ -682,5 +683,18 @@ "files": "Dateien", "complete": "{{count}} / {{total}} hochgeladen" }, - "storage_quota_exceeded": "Speicherplatz erschöpft" + "storage_quota_exceeded": "Speicherplatz erschöpft", + "sharedwithme": { + "pageTitle": "Mit mir geteilt", + "pageDescription": "Dateien und Ordner, die andere Benutzer mit Ihnen geteilt haben", + "emptyStateTitle": "Noch nichts mit Ihnen geteilt", + "emptyStateDesc": "Elemente, die andere Benutzer mit Ihnen teilen, erscheinen hier", + "loadMore": "Mehr laden", + "sharedBy": "Geteilt von", + "colName": "Name", + "colType": "Typ", + "colSharedBy": "Geteilt von", + "colDate": "Datum der Freigabe", + "colPermissions": "Berechtigungen" + } } diff --git a/static/locales/en.json b/static/locales/en.json index 46b13b95..302cfaae 100644 --- a/static/locales/en.json +++ b/static/locales/en.json @@ -5,7 +5,8 @@ }, "nav": { "files": "Files", - "shared": "Shared", + "shared": "My shares", + "sharedwithme": "Shared with me", "recent": "Recent", "favorites": "Favorites", "photos": "Photos", @@ -682,5 +683,18 @@ "files": "files", "complete": "{{count}} / {{total}} uploaded" }, - "storage_quota_exceeded": "Storage quota exceeded" + "storage_quota_exceeded": "Storage quota exceeded", + "sharedwithme": { + "pageTitle": "Shared with me", + "pageDescription": "Files and folders others have shared with you", + "emptyStateTitle": "Nothing shared with you yet", + "emptyStateDesc": "Items shared with you by other users will appear here", + "loadMore": "Load more", + "sharedBy": "Shared by", + "colName": "Name", + "colType": "Type", + "colSharedBy": "Shared by", + "colDate": "Date shared", + "colPermissions": "Permissions" + } } diff --git a/static/locales/es.json b/static/locales/es.json index 6c59eea2..e5081c3f 100644 --- a/static/locales/es.json +++ b/static/locales/es.json @@ -10,7 +10,8 @@ "favorites": "Favoritos", "photos": "Fotos", "music": "Música", - "trash": "Papelera" + "trash": "Papelera", + "sharedwithme": "Compartidos conmigo" }, "photos": { "empty_state": "Aún no hay fotos", @@ -682,5 +683,18 @@ "files": "archivos", "complete": "{{count}} / {{total}} subidos" }, - "storage_quota_exceeded": "Cuota de almacenamiento superada" + "storage_quota_exceeded": "Cuota de almacenamiento superada", + "sharedwithme": { + "pageTitle": "Compartido conmigo", + "pageDescription": "Archivos y carpetas que otros usuarios han compartido contigo", + "emptyStateTitle": "Aún no hay nada compartido contigo", + "emptyStateDesc": "Los elementos que otros usuarios compartan contigo aparecerán aquí", + "loadMore": "Cargar más", + "sharedBy": "Compartido por", + "colName": "Nombre", + "colType": "Tipo", + "colSharedBy": "Compartido por", + "colDate": "Fecha de compartición", + "colPermissions": "Permisos" + } } diff --git a/static/locales/fa.json b/static/locales/fa.json index 34ea6b09..226a9c0c 100644 --- a/static/locales/fa.json +++ b/static/locales/fa.json @@ -5,12 +5,13 @@ }, "nav": { "files": "پرونده‌ها", - "shared": "هم‌رسانی شده", + "shared": "هم‌رسانی‌های من", "recent": "اخیر", "favorites": "موردعلاقه‌ها", "photos": "عکس‌ها", "music": "موسیقی", - "trash": "سطل زباله" + "trash": "سطل زباله", + "sharedwithme": "به اشتراک‌گذاشته شده با من" }, "photos": { "empty_state": "هنوز عکسی نیست", @@ -682,5 +683,18 @@ "files": "فایل‌ها", "complete": "{{count}} / {{total}} آپلود شد" }, - "storage_quota_exceeded": "سهمیه فضای ذخیره‌سازی تجاوز کرده است" + "storage_quota_exceeded": "سهمیه فضای ذخیره‌سازی تجاوز کرده است", + "sharedwithme": { + "pageTitle": "به اشتراک‌گذاشته شده با من", + "pageDescription": "فایل‌ها و پوشه‌هایی که کاربران دیگر با شما به اشتراک گذاشته‌اند", + "emptyStateTitle": "هنوز چیزی با شما به اشتراک گذاشته نشده", + "emptyStateDesc": "مواردی که کاربران دیگر با شما به اشتراک می‌گذارند اینجا نمایش داده می‌شوند", + "loadMore": "بارگذاری بیشتر", + "sharedBy": "به اشتراک‌گذاشته توسط", + "colName": "نام", + "colType": "نوع", + "colSharedBy": "به اشتراک‌گذاشته توسط", + "colDate": "تاریخ اشتراک‌گذاری", + "colPermissions": "مجوزها" + } } diff --git a/static/locales/fr.json b/static/locales/fr.json index d06fcf81..40479502 100644 --- a/static/locales/fr.json +++ b/static/locales/fr.json @@ -5,12 +5,13 @@ }, "nav": { "files": "Fichiers", - "shared": "Partagés", + "shared": "Partages", "recent": "Récents", "favorites": "Favoris", "photos": "Photos", "music": "Musique", - "trash": "Corbeille" + "trash": "Corbeille", + "sharedwithme": "Partages avec moi" }, "photos": { "empty_state": "Pas encore de photos", @@ -682,5 +683,18 @@ "files": "fichiers", "complete": "{{count}} / {{total}} téléchargés" }, - "storage_quota_exceeded": "Quota de stockage dépassé" + "storage_quota_exceeded": "Quota de stockage dépassé", + "sharedwithme": { + "pageTitle": "Partagé avec moi", + "pageDescription": "Fichiers et dossiers que d'autres utilisateurs ont partagés avec vous", + "emptyStateTitle": "Rien n'a encore été partagé avec vous", + "emptyStateDesc": "Les éléments partagés avec vous par d'autres utilisateurs apparaîtront ici", + "loadMore": "Charger plus", + "sharedBy": "Partagé par", + "colName": "Nom", + "colType": "Type", + "colSharedBy": "Partagé par", + "colDate": "Date de partage", + "colPermissions": "Permissions" + } } diff --git a/static/locales/hi.json b/static/locales/hi.json index dc85c2e2..97f86c2b 100644 --- a/static/locales/hi.json +++ b/static/locales/hi.json @@ -10,7 +10,8 @@ "favorites": "पसंदीदा", "photos": "फ़ोटो", "music": "संगीत", - "trash": "रद्दी" + "trash": "रद्दी", + "sharedwithme": "मेरे साथ साझा किए गए" }, "photos": { "empty_state": "अभी कोई फ़ोटो नहीं", @@ -682,5 +683,18 @@ "files": "फ़ाइलें", "complete": "{{count}} / {{total}} अपलोड हुए" }, - "storage_quota_exceeded": "स्टोरेज कोटा पार हो गया" + "storage_quota_exceeded": "स्टोरेज कोटा पार हो गया", + "sharedwithme": { + "pageTitle": "मेरे साथ साझा किया", + "pageDescription": "फ़ाइलें और फ़ोल्डर जो अन्य उपयोगकर्ताओं ने आपके साथ साझा किए हैं", + "emptyStateTitle": "अभी तक आपके साथ कुछ भी साझा नहीं किया गया", + "emptyStateDesc": "अन्य उपयोगकर्ताओं द्वारा आपके साथ साझा किए गए आइटम यहाँ दिखाई देंगे", + "loadMore": "और लोड करें", + "sharedBy": "द्वारा साझा किया", + "colName": "नाम", + "colType": "प्रकार", + "colSharedBy": "द्वारा साझा किया", + "colDate": "साझाकरण तिथि", + "colPermissions": "अनुमतियाँ" + } } diff --git a/static/locales/it.json b/static/locales/it.json index 04c96988..7a89ba83 100644 --- a/static/locales/it.json +++ b/static/locales/it.json @@ -5,12 +5,13 @@ }, "nav": { "files": "File", - "shared": "Condivisi", + "shared": "Condivisioni", "recent": "Recenti", "favorites": "Preferiti", "photos": "Foto", "music": "Musica", - "trash": "Cestino" + "trash": "Cestino", + "sharedwithme": "Condivisi con me" }, "photos": { "empty_state": "Nessuna foto ancora", @@ -682,5 +683,18 @@ "files": "file", "complete": "{{count}} / {{total}} caricati" }, - "storage_quota_exceeded": "Quota di archiviazione superata" + "storage_quota_exceeded": "Quota di archiviazione superata", + "sharedwithme": { + "pageTitle": "Condiviso con me", + "pageDescription": "File e cartelle che altri utenti hanno condiviso con te", + "emptyStateTitle": "Niente è ancora condiviso con te", + "emptyStateDesc": "Gli elementi condivisi con te da altri utenti appariranno qui", + "loadMore": "Carica altri", + "sharedBy": "Condiviso da", + "colName": "Nome", + "colType": "Tipo", + "colSharedBy": "Condiviso da", + "colDate": "Data condivisione", + "colPermissions": "Permessi" + } } diff --git a/static/locales/ja.json b/static/locales/ja.json index eb7899b2..544cc8c9 100644 --- a/static/locales/ja.json +++ b/static/locales/ja.json @@ -10,7 +10,8 @@ "favorites": "お気に入り", "photos": "写真", "music": "音楽", - "trash": "ゴミ箱" + "trash": "ゴミ箱", + "sharedwithme": "自分と共有" }, "photos": { "empty_state": "写真はまだありません", @@ -682,5 +683,18 @@ "files": "ファイル", "complete": "{{count}} / {{total}} アップロード済み" }, - "storage_quota_exceeded": "ストレージ容量を超過しました" + "storage_quota_exceeded": "ストレージ容量を超過しました", + "sharedwithme": { + "pageTitle": "自分と共有", + "pageDescription": "他のユーザーがあなたと共有したファイルとフォルダー", + "emptyStateTitle": "まだ何も共有されていません", + "emptyStateDesc": "他のユーザーがあなたと共有したアイテムがここに表示されます", + "loadMore": "さらに読み込む", + "sharedBy": "共有者", + "colName": "名前", + "colType": "タイプ", + "colSharedBy": "共有者", + "colDate": "共有日", + "colPermissions": "権限" + } } diff --git a/static/locales/ko.json b/static/locales/ko.json index 663ab4a1..52b18e08 100644 --- a/static/locales/ko.json +++ b/static/locales/ko.json @@ -10,7 +10,8 @@ "favorites": "즐겨찾기", "photos": "사진", "music": "음악", - "trash": "휴지통" + "trash": "휴지통", + "sharedwithme": "나와 공유됨" }, "photos": { "empty_state": "아직 사진이 없습니다", @@ -682,5 +683,18 @@ "files": "파일", "complete": "{{count}} / {{total}} 업로드됨" }, - "storage_quota_exceeded": "저장 공간 할당량 초과" + "storage_quota_exceeded": "저장 공간 할당량 초과", + "sharedwithme": { + "pageTitle": "나와 공유됨", + "pageDescription": "다른 사용자가 나와 공유한 파일 및 폴더", + "emptyStateTitle": "아직 공유된 항목이 없습니다", + "emptyStateDesc": "다른 사용자가 공유한 항목이 여기에 표시됩니다", + "loadMore": "더 불러오기", + "sharedBy": "공유한 사람", + "colName": "이름", + "colType": "유형", + "colSharedBy": "공유한 사람", + "colDate": "공유 날짜", + "colPermissions": "권한" + } } diff --git a/static/locales/nl.json b/static/locales/nl.json index 582b43bd..37b1afd8 100644 --- a/static/locales/nl.json +++ b/static/locales/nl.json @@ -10,7 +10,8 @@ "favorites": "Favorieten", "photos": "Foto's", "music": "Muziek", - "trash": "Prullenbak" + "trash": "Prullenbak", + "sharedwithme": "Gedeeld met mij" }, "photos": { "empty_state": "Nog geen foto's", @@ -682,5 +683,18 @@ "files": "bestanden", "complete": "{{count}} / {{total}} geüpload" }, - "storage_quota_exceeded": "Opslagquotum overschreden" + "storage_quota_exceeded": "Opslagquotum overschreden", + "sharedwithme": { + "pageTitle": "Gedeeld met mij", + "pageDescription": "Bestanden en mappen die andere gebruikers met u hebben gedeeld", + "emptyStateTitle": "Er is nog niets met u gedeeld", + "emptyStateDesc": "Items die andere gebruikers met u delen, verschijnen hier", + "loadMore": "Meer laden", + "sharedBy": "Gedeeld door", + "colName": "Naam", + "colType": "Type", + "colSharedBy": "Gedeeld door", + "colDate": "Datum gedeeld", + "colPermissions": "Machtigingen" + } } diff --git a/static/locales/pl.json b/static/locales/pl.json index 1000580e..0e11ada7 100644 --- a/static/locales/pl.json +++ b/static/locales/pl.json @@ -10,7 +10,8 @@ "favorites": "Ulubione", "photos": "Zdjęcia", "music": "Muzyka", - "trash": "Kosz" + "trash": "Kosz", + "sharedwithme": "Udostępnione dla mnie" }, "photos": { "empty_state": "Brak zdjęć", @@ -682,5 +683,18 @@ "files": "plików", "complete": "{{count}} / {{total}} przesłano" }, - "storage_quota_exceeded": "Przekroczono limit pamięci masowej" + "storage_quota_exceeded": "Przekroczono limit pamięci masowej", + "sharedwithme": { + "pageTitle": "Udostępnione dla mnie", + "pageDescription": "Pliki i foldery, które inni użytkownicy udostępnili Ci", + "emptyStateTitle": "Nic nie zostało Ci jeszcze udostępnione", + "emptyStateDesc": "Elementy udostępnione Ci przez innych użytkowników pojawią się tutaj", + "loadMore": "Załaduj więcej", + "sharedBy": "Udostępnione przez", + "colName": "Nazwa", + "colType": "Typ", + "colSharedBy": "Udostępnione przez", + "colDate": "Data udostępnienia", + "colPermissions": "Uprawnienia" + } } diff --git a/static/locales/pt.json b/static/locales/pt.json index 3629935b..e81d69c2 100644 --- a/static/locales/pt.json +++ b/static/locales/pt.json @@ -5,12 +5,13 @@ }, "nav": { "files": "Arquivos", - "shared": "Compartilhados", + "shared": "Compartilhamentos", "recent": "Recentes", "favorites": "Favoritos", "photos": "Fotos", "music": "Música", - "trash": "Lixeira" + "trash": "Lixeira", + "sharedwithme": "Compartilhados comigo" }, "photos": { "empty_state": "Nenhuma foto ainda", @@ -682,5 +683,18 @@ "files": "ficheiros", "complete": "{{count}} / {{total}} carregados" }, - "storage_quota_exceeded": "Cota de armazenamento excedida" + "storage_quota_exceeded": "Cota de armazenamento excedida", + "sharedwithme": { + "pageTitle": "Compartilhado comigo", + "pageDescription": "Arquivos e pastas que outros usuários compartilharam com você", + "emptyStateTitle": "Nada compartilhado com você ainda", + "emptyStateDesc": "Itens compartilhados com você por outros usuários aparecerão aqui", + "loadMore": "Carregar mais", + "sharedBy": "Compartilhado por", + "colName": "Nome", + "colType": "Tipo", + "colSharedBy": "Compartilhado por", + "colDate": "Data de compartilhamento", + "colPermissions": "Permissões" + } } diff --git a/static/locales/ru.json b/static/locales/ru.json index 5451bad5..02f496b3 100644 --- a/static/locales/ru.json +++ b/static/locales/ru.json @@ -10,7 +10,8 @@ "favorites": "Избранное", "photos": "Фото", "music": "Музыка", - "trash": "Корзина" + "trash": "Корзина", + "sharedwithme": "Доступно мне" }, "photos": { "empty_state": "Фотографий пока нет", @@ -682,5 +683,18 @@ "files": "файлов", "complete": "{{count}} / {{total}} загружено" }, - "storage_quota_exceeded": "Превышена квота хранилища" + "storage_quota_exceeded": "Превышена квота хранилища", + "sharedwithme": { + "pageTitle": "Доступно мне", + "pageDescription": "Файлы и папки, которые другие пользователи предоставили вам", + "emptyStateTitle": "Вам ещё ничего не предоставлено", + "emptyStateDesc": "Элементы, которые другие пользователи предоставят вам, появятся здесь", + "loadMore": "Загрузить ещё", + "sharedBy": "Предоставлено", + "colName": "Имя", + "colType": "Тип", + "colSharedBy": "Предоставлено", + "colDate": "Дата предоставления", + "colPermissions": "Права" + } } diff --git a/static/locales/zh-TW.json b/static/locales/zh-TW.json index bb12823b..3313bfde 100644 --- a/static/locales/zh-TW.json +++ b/static/locales/zh-TW.json @@ -10,7 +10,8 @@ "favorites": "收藏", "photos": "照片", "music": "音樂", - "trash": "回收站" + "trash": "回收站", + "sharedwithme": "與我共享" }, "photos": { "empty_state": "還沒有照片", @@ -682,5 +683,18 @@ "files": "個檔案", "complete": "已上傳 {{count}} / {{total}}" }, - "storage_quota_exceeded": "儲存配額已超限" + "storage_quota_exceeded": "儲存配額已超限", + "sharedwithme": { + "pageTitle": "與我共享", + "pageDescription": "其他使用者與您共享的檔案和資料夾", + "emptyStateTitle": "目前沒有內容與您共享", + "emptyStateDesc": "其他使用者與您共享的項目將顯示在這裡", + "loadMore": "載入更多", + "sharedBy": "共享者", + "colName": "名稱", + "colType": "類型", + "colSharedBy": "共享者", + "colDate": "共享日期", + "colPermissions": "權限" + } } diff --git a/static/locales/zh.json b/static/locales/zh.json index 70cef715..1efe5b0e 100644 --- a/static/locales/zh.json +++ b/static/locales/zh.json @@ -10,7 +10,8 @@ "favorites": "收藏", "photos": "照片", "music": "音乐", - "trash": "回收站" + "trash": "回收站", + "sharedwithme": "与我共享" }, "photos": { "empty_state": "还没有照片", @@ -682,5 +683,18 @@ "files": "个文件", "complete": "已上传 {{count}} / {{total}}" }, - "storage_quota_exceeded": "存储配额已超限" + "storage_quota_exceeded": "存储配额已超限", + "sharedwithme": { + "pageTitle": "与我共享", + "pageDescription": "其他用户与您共享的文件和文件夹", + "emptyStateTitle": "暂无内容与您共享", + "emptyStateDesc": "其他用户与您共享的项目将显示在此处", + "loadMore": "加载更多", + "sharedBy": "共享者", + "colName": "名称", + "colType": "类型", + "colSharedBy": "共享者", + "colDate": "共享日期", + "colPermissions": "权限" + } }