An image or video — added to the workspace library, full quality.
+
+
+
+
diff --git a/frontend/js/agency-portal.js b/frontend/js/agency-portal.js
index ff089bc..8744bae 100644
--- a/frontend/js/agency-portal.js
+++ b/frontend/js/agency-portal.js
@@ -59,6 +59,25 @@
// #73: the placement card reacts to the playlist selector - "where does THIS playlist go?"
sel.onchange = () => loadLayoutForPlaylist(sel.value);
loadLayoutForPlaylist(sel.value); // initial selection
+ loadFolders();
+ }
+
+ // #158 (Hybrid-C): the folders this token may drop into = its bound folder + descendants.
+ // Only offer the picker when there's a real choice (a subfolder exists); with just the bound
+ // folder, uploads default to it server-side and the picker stays hidden. The bound "root" is
+ // the one node whose parent isn't in the returned set, so we can label it "Main folder"
+ // (default, value="") and list the descendants under it — without the portal ever learning
+ // the token's folder id.
+ async function loadFolders() {
+ const row = $('folderRow'), sel = $('folderSelect');
+ let folders;
+ try { folders = await (await agencyFetch('/folders')).json(); } catch (e) { return; }
+ const ids = new Set(folders.map(f => f.id));
+ const descendants = folders.filter(f => f.parent_id && ids.has(f.parent_id)); // exclude the root
+ if (!descendants.length) { row.style.display = 'none'; return; }
+ sel.innerHTML = ''
+ + descendants.map(f => ``).join('');
+ row.style.display = 'block';
}
// Visual placement guide for the SELECTED playlist: draw its layout to scale, highlight the
@@ -115,6 +134,8 @@
try {
const fd = new FormData();
fd.append('file', file);
+ const folderId = $('folderSelect') && $('folderSelect').value;
+ if (folderId) fd.append('folder_id', folderId); // #158: a subfolder of the bound folder; empty = the bound default
const res = await agencyFetch('/content', { method: 'POST', body: fd });
if (!res.ok) { portalMsg('Upload failed. Try again.', 'err'); return; }
const content = await res.json();
diff --git a/frontend/js/api.js b/frontend/js/api.js
index 019e28e..379fc6a 100644
--- a/frontend/js/api.js
+++ b/frontend/js/api.js
@@ -180,6 +180,7 @@ export const api = {
createToken: (data) => request('/tokens', { method: 'POST', body: JSON.stringify(data) }),
revokeToken: (id) => request('/tokens/' + id, { method: 'DELETE' }),
setTokenTargets: (id, target_playlist_ids) => request('/tokens/' + id + '/targets', { method: 'PUT', body: JSON.stringify({ target_playlist_ids }) }), // #73: re-designate agency token playlists
+ setTokenUploadFolder: (id, upload_folder_id) => request('/tokens/' + id + '/upload-folder', { method: 'PUT', body: JSON.stringify({ upload_folder_id }) }), // #158: rebind agency token upload folder (null = root)
// Current user
getMe: () => request('/auth/me'),
diff --git a/frontend/js/i18n/de.js b/frontend/js/i18n/de.js
index bec630d..919d570 100644
--- a/frontend/js/i18n/de.js
+++ b/frontend/js/i18n/de.js
@@ -382,6 +382,13 @@ export default {
'apitoken.auto_publish_label': 'Automatisch veröffentlichen (meine Freigabe überspringen)',
'apitoken.auto_publish_hint': 'Aus (Standard): Hinzufügungen warten als Entwurf auf deine Veröffentlichung. An: sie gehen sofort live – nur für Agenturen, denen du voll vertraust.',
'apitoken.auto_publish_on': 'Auto-Veröffentlichung an',
+ 'apitoken.agency_folder_label': 'Upload-Ordner',
+ 'apitoken.agency_folder_hint': 'Wohin die Uploads dieser Agentur gelangen. Automatisch belassen, um einen nach dem Token benannten Ordner zu erstellen; die Agentur kann dessen Unterordner ansteuern, aber nichts anderes.',
+ 'apitoken.agency_folder_auto': 'Automatisch erstellen (Agency — )',
+ 'apitoken.folder_label': 'Ordner:',
+ 'apitoken.folder_root': 'Bibliotheks-Stammverzeichnis',
+ 'apitoken.edit_folder': 'Ordner',
+ 'apitoken.folder_updated': 'Upload-Ordner aktualisiert',
'apitoken.create': 'Token erstellen',
'apitoken.none': 'Noch keine Tokens.',
'apitoken.col_token': 'Token',
diff --git a/frontend/js/i18n/en.js b/frontend/js/i18n/en.js
index ce97275..a9b1f64 100644
--- a/frontend/js/i18n/en.js
+++ b/frontend/js/i18n/en.js
@@ -524,6 +524,13 @@ export default {
'apitoken.auto_publish_label': 'Auto-publish (skip my approval)',
'apitoken.auto_publish_hint': 'Off (default): additions wait as drafts for you to publish. On: they go live immediately — only for agencies you fully trust.',
'apitoken.auto_publish_on': 'auto-publish on',
+ 'apitoken.agency_folder_label': 'Upload folder',
+ 'apitoken.agency_folder_hint': "Where this agency's uploads land. Leave on auto to create a folder named after the token; the agency can target subfolders of it but nothing else.",
+ 'apitoken.agency_folder_auto': 'Create one automatically (Agency — )',
+ 'apitoken.folder_label': 'Folder:',
+ 'apitoken.folder_root': 'Library root',
+ 'apitoken.edit_folder': 'Folder',
+ 'apitoken.folder_updated': 'Upload folder updated',
'apitoken.create': 'Create token',
'apitoken.none': 'No tokens yet.',
'apitoken.col_token': 'Token',
diff --git a/frontend/js/i18n/es.js b/frontend/js/i18n/es.js
index 777bf39..560ac14 100644
--- a/frontend/js/i18n/es.js
+++ b/frontend/js/i18n/es.js
@@ -381,6 +381,13 @@ export default {
'apitoken.auto_publish_label': 'Publicación automática (omitir mi aprobación)',
'apitoken.auto_publish_hint': 'Desactivado (predeterminado): las adiciones esperan como borradores para que las publiques. Activado: se publican de inmediato, solo para agencias de plena confianza.',
'apitoken.auto_publish_on': 'publicación automática activada',
+ 'apitoken.agency_folder_label': 'Carpeta de subida',
+ 'apitoken.agency_folder_hint': 'Dónde se guardan las subidas de esta agencia. Deja en automático para crear una carpeta con el nombre del token; la agencia puede usar subcarpetas de ella, pero nada más.',
+ 'apitoken.agency_folder_auto': 'Crear una automáticamente (Agency — )',
+ 'apitoken.folder_label': 'Carpeta:',
+ 'apitoken.folder_root': 'Raíz de la biblioteca',
+ 'apitoken.edit_folder': 'Carpeta',
+ 'apitoken.folder_updated': 'Carpeta de subida actualizada',
'apitoken.create': 'Crear token',
'apitoken.none': 'Aún no hay tokens.',
'apitoken.col_token': 'Token',
diff --git a/frontend/js/i18n/fr.js b/frontend/js/i18n/fr.js
index bbb6016..1525925 100644
--- a/frontend/js/i18n/fr.js
+++ b/frontend/js/i18n/fr.js
@@ -382,6 +382,13 @@ export default {
'apitoken.auto_publish_label': 'Publication automatique (ignorer mon approbation)',
'apitoken.auto_publish_hint': 'Désactivé (par défaut) : les ajouts attendent en brouillon votre publication. Activé : ils sont diffusés immédiatement, uniquement pour les agences de pleine confiance.',
'apitoken.auto_publish_on': 'publication automatique activée',
+ 'apitoken.agency_folder_label': 'Dossier de téléversement',
+ 'apitoken.agency_folder_hint': "Où atterrissent les téléversements de cette agence. Laissez sur automatique pour créer un dossier nommé d'après le jeton ; l'agence peut cibler ses sous-dossiers, mais rien d'autre.",
+ 'apitoken.agency_folder_auto': 'En créer un automatiquement (Agency — )',
+ 'apitoken.folder_label': 'Dossier :',
+ 'apitoken.folder_root': 'Racine de la bibliothèque',
+ 'apitoken.edit_folder': 'Dossier',
+ 'apitoken.folder_updated': 'Dossier de téléversement mis à jour',
'apitoken.create': 'Créer un jeton',
'apitoken.none': 'Aucun jeton pour le moment.',
'apitoken.col_token': 'Jeton',
diff --git a/frontend/js/i18n/pt.js b/frontend/js/i18n/pt.js
index db9e87f..3747a32 100644
--- a/frontend/js/i18n/pt.js
+++ b/frontend/js/i18n/pt.js
@@ -382,6 +382,13 @@ export default {
'apitoken.auto_publish_label': 'Publicação automática (ignorar minha aprovação)',
'apitoken.auto_publish_hint': 'Desativado (padrão): as adições aguardam como rascunho para você publicar. Ativado: vão ao ar imediatamente, apenas para agências de total confiança.',
'apitoken.auto_publish_on': 'publicação automática ativada',
+ 'apitoken.agency_folder_label': 'Pasta de upload',
+ 'apitoken.agency_folder_hint': 'Onde os uploads desta agência ficam. Deixe em automático para criar uma pasta com o nome do token; a agência pode usar subpastas dela, mas nada além disso.',
+ 'apitoken.agency_folder_auto': 'Criar uma automaticamente (Agency — )',
+ 'apitoken.folder_label': 'Pasta:',
+ 'apitoken.folder_root': 'Raiz da biblioteca',
+ 'apitoken.edit_folder': 'Pasta',
+ 'apitoken.folder_updated': 'Pasta de upload atualizada',
'apitoken.create': 'Criar token',
'apitoken.none': 'Ainda não há tokens.',
'apitoken.col_token': 'Token',
diff --git a/frontend/js/views/settings.js b/frontend/js/views/settings.js
index 3c8027a..5bff420 100644
--- a/frontend/js/views/settings.js
+++ b/frontend/js/views/settings.js
@@ -88,6 +88,9 @@ export async function render(container) {
${t('apitoken.auto_publish_label')}
${t('apitoken.auto_publish_hint')}
+
+
${t('apitoken.agency_folder_hint')}
+
${t('settings.loading_users')}
@@ -372,14 +375,14 @@ export async function render(container) {