1/* âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 2 * LA MÃDIATHÃQUE â LE CATALOGUE DE CRÃA 3 * SEIZIÃME MORCEAU DÃTACHà DE src/app.js 4 * âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 5 * CREA MEDIATHEQUE â le catalogue : ce qu'on a, ce qu'on a fait, ce qu'on a lu 6 * 7 * « J'ai l'impression qu'on l'enferme pour contenir des images et des fichiers. 8 * Dis-moi tout ce qu'on peut en faire, pour la rendre riche, utile à tout 9 * l'écosystème, vraiment puissante et ouverte en vrai. » 10 * 11 * ââ LE RENVERSEMENT ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 12 * 13 * Une bibliothèque municipale ne possède pas 3 % de ce à quoi elle donne 14 * accès. Ce qui la définit n'est pas son stock, c'est son ACCÃS : le 15 * catalogue, les bases auxquelles elle vous abonne, et le prêt entre 16 * bibliothèques quand elle n'a rien. 17 * 18 * La Médiathèque ne possède donc pas. Elle sait Oà SONT les choses. Un fichier 19 * reste sur le disque, un livrable dans son projet, un document partagé chez 20 * son auteur. Ce qu'elle tient, c'est le lien â et c'est ce qui permet de 21 * chercher une seule fois dans seize matières au lieu de quatre fois dans 22 * quatre écrans. 23 * 24 * ââ CE QUI ÃTAIT LÃ, ET POURQUOI C'ÃTAIT UN PLACARD âââââââââââââââââââââââ 25 * 26 * L'écran d'avant montrait DEUX choses : une grille plate de livrables, et un 27 * onglet de sessions. Relevé dans l'état de l'application : VINGT-SIX réserves 28 * de matière existent, sont sauvegardées, voyagent d'un appareil à l'autre â 29 * et vingt-quatre n'avaient aucun écran. Ce que CRÃA a lu pour vous 30 * (`filLus`, `filGardes`), ce qu'elle croit savoir (`memories`, 31 * `knowledgeGraph`), ce qu'elle a ouvert (`browserHistory`) : rien de tout ça 32 * n'était regardable, et rien n'était corrigible. 33 * 34 * ââ DEUX SYSTÃMES, PAS UN âââââââââââââââââââââââââââââââââââââââââââââââââ 35 * 36 * « Même si tu les as liés, c'est deux systèmes différents. Ils se parlent, 37 * ils travaillent ensemble, mais c'est deux systèmes différents. » 38 * 39 * La Médiathèque CATALOGUE. L'explorateur PARCOURT UN DISQUE. Ce sont DEUX 40 * BRANCHES VOISINES, pas l'une dans l'autre â deux entrées de la barre, deux 41 * fenêtres, deux réalités. L'explorateur a vécu une journée dans une SECTION 42 * de ce module, et c'était une faute de fond : enfermer un explorateur dans un 43 * bloc du catalogue lui retire précisément ce qui en fait un autre système. 44 * 45 * Ce module ne dessine donc aucun explorateur, et n'en ouvre plus aucun. Il 46 * lui PARLE : ce qui vient du disque arrive par `verser()`, et ce que le 47 * catalogue sait d'un fichier du disque repart par la colonne « Dans CRÃA ». 48 * 49 * ââ CE QUE CE MODULE NE TIENT PAS, ET C'EST ÃCRIT à L'ÃCRAN âââââââââââââââ 50 * 51 * LE PARTAGE N'EXISTE PAS. Prêter une pièce à quelqu'un bute sur le même mur 52 * que le poste partagé : /api/sync/push range un instantané ENTIER par 53 * personne. à deux sur une même étagère, le dernier à écrire efface l'autre. 54 * Le verbe est nommé et ÃTEINT â il refuse même si on force le clic. 55 * 56 * LES SOURCES NE SONT PAS ENCORE GARDÃES. Mesuré : zéro. CRÃA cite et jette. 57 * La section existe et le dit, parce qu'un trou nommé se répare et qu'un trou 58 * caché s'oublie. 59 * 60 * LE MONDE EXTÃRIEUR EST EN PARTIE BRANCHÃ. Sept catalogues sont déjà atteints 61 * par le serveur (mesuré) ; les autres sont nommés et éteints. 62 * 63 * Emprunts : AUCUN nom lu du tronc autrement que par `window`. 64 * 65 * Expose window.CREA_MEDIATHEQUE. 66 * âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 67;(function () { 68 'use strict' 69 if (window.CREA_MEDIATHEQUE) return 70 71 function S() { return window.STATE || (window.STATE = {}) } 72 function esc(s) { 73 if (typeof window.escHtml === 'function') return window.escHtml(s) 74 return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) { 75 return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c] 76 }) 77 } 78 function redessiner() { if (typeof window.render === 'function') window.render() } 79 function garder() { if (typeof window.save === 'function') window.save() } 80 function dire(t, g, s) { if (typeof window.logNotice === 'function') window.logNotice(t, g || 'info', s || 'â¹ï¸') } 81 function registre() { return window.fichiersRegistre || null } 82 function pieces() { 83 var r = registre() 84 if (!r || typeof r.lister !== 'function') return [] 85 try { return r.lister() || [] } catch (e) { 86 /* Le registre se reconstruit au démarrage ; l'interroger trop tôt jette.
87 * Rendre une étagère vide est juste â elle se remplira au rendu suivant. */ 88 return [] 89 } 90 } 91 /* CE QUE LE CATALOGUE A REÃU DU DISQUE. C'est la trace de l'autre branche 92 * ICI, et elle se relit à chaque rendu : le catalogue ne tient pas la liste 93 * des dossiers accordés â il constate ce qui lui est arrivé. */ 94 function versesDuDisque() { 95 return pieces().filter(function (f) { 96 return !f.supprime && f.origine && f.origine.studio === 'explorateur' 97 }) 98 } 99 function jours(t) { return Math.floor((Date.now() - t) / 86400000) } 100 function ilYA(t) { 101 if (!t) return '' 102 var j = jours(t) 103 if (j <= 0) return "aujourd'hui" 104 if (j === 1) return 'hier' 105 if (j < 31) return 'il y a ' + j + ' j' 106 if (j < 365) return 'il y a ' + Math.round(j / 30) + ' mois' 107 return 'il y a ' + Math.round(j / 365) + ' an' + (j >= 730 ? 's' : '') 108 } 109 function poids(o) { 110 if (!o) return '' 111 if (o < 1024) return o + ' o' 112 if (o < 1048576) return Math.round(o / 1024) + ' Ko' 113 return (o / 1048576).toFixed(1).replace('.', ',') + ' Mo' 114 } 115 116 var IC = { 117 livre: '<path d="M4.5 4.8h4.8a2.5 2.5 0 0 1 2.7 2.5v12a2 2 0 0 0-2-2H4.5Z"/><path d="M19.5 4.8h-4.8a2.5 2.5 0 0 0-2.7 2.5v12a2 2 0 0 1 2-2h5.5Z"/>', 118 ordi: '<rect x="3" y="5" width="18" height="12" rx="2"/><path d="M8 20h8M12 17v3"/>', 119 image: '<rect x="3.5" y="5" width="17" height="14" rx="2"/><circle cx="9" cy="10" r="1.6"/><path d="m5 17 5-4.5 4 3.5 2.5-2 3 3"/>', 120 son: '<path d="M5 9.5h3l4-3.5v12l-4-3.5H5Z"/><path d="M16 9a4 4 0 0 1 0 6"/>', 121 video: '<rect x="3" y="6" width="13" height="12" rx="2"/><path d="m16 11 5-3v8l-5-3Z"/>', 122 doc: '<path d="M5 4.5h9l5 5V19.5H5Z"/><path d="M8.5 13h7M8.5 16h4.5"/>', 123 etincelle: '<path d="M12 3v3M12 18v3M3 12h3M18 12h3"/><rect x="7" y="7" width="10" height="10" rx="3"/>', 124 bulle: '<path d="M4 5.5h16v10H9l-4 3.5Z"/>', 125 horloge: '<circle cx="12" cy="12" r="8.2"/><path d="M12 7.4V12l3 1.8"/>', 126 gabarit: '<rect x="4" y="4" width="7" height="7" rx="1.6"/><rect x="13" y="13" width="7" height="7" rx="1.6"/><path d="M11 7.5h6.5V13"/>', 127 kiosque: '<path d="M4 6h11v13H4Z"/><path d="M15 9h5v10h-5"/><path d="M7 9.5h5M7 13h5"/>', 128 source: '<path d="M9 5.5h10v13H9Z"/><path d="M5 8v11h10"/>', 129 savoir: '<circle cx="12" cy="12" r="3"/><circle cx="12" cy="4.5" r="2"/><circle cx="19" cy="16" r="2"/><circle cx="5" cy="16" r="2"/><path d="M12 6.5v2.5M14.4 13.5l2.8 1.3M9.6 13.5l-2.8 1.3"/>', 130 lecture: '<path d="M5 4.5h9l5 5V19.5H5Z"/><path d="M8.5 13h7"/>', 131 monde: '<circle cx="12" cy="12" r="8.5"/><path d="M3.5 12h17M12 3.5c2.5 2.6 2.5 14.4 0 17M12 3.5c-2.5 2.6-2.5 14.4 0 17"/>', 132 relais: '<path d="M4 12h11"/><path d="m11 8 4 4-4 4"/><circle cx="19" cy="12" r="2.2"/>', 133 gens: '<circle cx="12" cy="6" r="2.4"/><circle cx="5.5" cy="17" r="2.4"/><circle cx="18.5" cy="17" r="2.4"/><path d="M10.4 7.8 7 14.6M13.6 7.8 17 14.6M8 17h8"/>', 134 corbeille: '<path d="M5 7h14M9.5 7V5h5v2M7 7v13h10V7"/>', 135 loupe: '<circle cx="11" cy="11" r="6.5"/><path d="m16 16 4.5 4.5"/>', 136 plus: '<path d="M12 5v14M5 12h14"/>', 137 chevron: '<path d="M6 9l6 6 6-6"/>', 138 dossier: '<path d="M3.5 6.5h6l1.6 2h9.4v9H3.5Z"/>', 139 } 140 function ic(n, t, w) { 141 return '<svg viewBox="0 0 24 24" width="' + (t || 14) + '" height="' + (t || 14) + '" fill="none" stroke="currentColor" ' + 142 'stroke-width="' + (w || 1.7) + '" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' + (IC[n] || IC.livre) + '</svg>' 143 } 144 145 /* ââ LES SECTIONS, EN QUATRE FAMILLES âââââââââââââââââââââââââââââââââââ 146 * Elles se comptent, elles ne s'annoncent pas : la barre d'état écrivait 147 * « 16 sections » quand le rail en dessinait dix-sept. Un nombre écrit à la 148 * main ment dès qu'on ajoute une ligne, et personne ne le relit. 149 * L'ordre n'est pas décoratif : il va de ce qui est le plus à vous vers ce 150 * qui l'est le moins. `porte: true` marque une section qui appartient à un 151 * AUTRE système et que la Médiathèque ne fait qu'ouvrir. */ 152 var FAMILLES = [ 153 { id: 'avoir', nom: 'Ce que vous avez' }, 154 { id: 'crea', nom: 'Ce que CRÃA a fait' }, 155 { id: 'lu', nom: "Ce qu'elle a lu" }, 156 { id: 'dehors', nom: 'Dehors' }, 157 ] 158 /* « Il ne faut pas mettre Fichiers, il faut mettre Explorateur pour faire la 159 * distinction, parce que dans Catalogue il y a déjà une forme de fic
159hiers. » 160 * Le catalogue range des PIÃCES â images, sons, documents. L'autre branche 161 * parcourt un DISQUE. Deux mots pour deux choses, sinon on cherche ses 162 * fichiers dans celui qui n'en a pas. */ 163 var BRANCHES = ['catalogue', 'explorateur'] 164 var SECTIONS = [ 165 { id: 'images', nom: 'Images', ic: 'image', fam: 'avoir', types: ['image'] }, 166 { id: 'sons', nom: 'Sons & voix', ic: 'son', fam: 'avoir', types: ['audio', 'son'] }, 167 { id: 'videos', nom: 'Vidéos', ic: 'video', fam: 'avoir', types: ['video'] }, 168 { id: 'documents', nom: 'Documents', ic: 'doc', fam: 'avoir', types: ['document', 'doc', 'pdf', 'texte'] }, 169 { id: 'creations', nom: 'Créations', ic: 'etincelle', fam: 'crea' }, 170 { id: 'conversations', nom: 'Conversations', ic: 'bulle', fam: 'crea' }, 171 { id: 'fabrication', nom: 'En fabrication', ic: 'horloge', fam: 'crea' }, 172 { id: 'modeles', nom: 'Modèles', ic: 'gabarit', fam: 'crea' }, 173 { id: 'kiosque', nom: 'Kiosque', ic: 'kiosque', fam: 'lu' }, 174 { id: 'sources', nom: 'Sources', ic: 'source', fam: 'lu' }, 175 { id: 'savoir', nom: 'Savoir', ic: 'savoir', fam: 'lu' }, 176 { id: 'lecture', nom: 'Salle de lecture', ic: 'lecture', fam: 'lu' }, 177 { id: 'monde', nom: 'Le monde', ic: 'monde', fam: 'dehors' }, 178 { id: 'emprunts', nom: 'Emprunts', ic: 'relais', fam: 'dehors' }, 179 { id: 'partage', nom: 'Partagé', ic: 'gens', fam: 'dehors' }, 180 { id: 'desherbage', nom: 'Désherbage', ic: 'corbeille', fam: 'dehors' }, 181 ] 182 183 function etat() { 184 var st = S() 185 if (!st.mediatheque || typeof st.mediatheque !== 'object') st.mediatheque = {} 186 var m = st.mediatheque 187 /* DEUX BRANCHES, PUIS LES SECTIONS DE LA PREMIÃRE. 188 * 189 * « Je t'ai dit un catalogue, et à côté du catalogue une branche Fichiers 190 * qui a sa propre vue. » Ce sont les deux entrées du fil, en haut : 191 * Catalogue et Fichiers. Les seize sections sont le rail DU CATALOGUE â 192 * Fichiers n'en est pas une, et n'en a pas. 193 * 194 * L'explorateur a vécu deux fausses vies avant celle-ci : dix-huitième 195 * section du rail, puis branche de la barre latérale, hors de la 196 * Médiathèque. Ni l'un ni l'autre. */ 197 if (!m.section || (BRANCHES.indexOf(m.section) < 0 && !SECTIONS.some(function (x) { return x.id === m.section }))) m.section = 'catalogue' 198 if (typeof m.recherche !== 'string') m.recherche = '' 199 if (!m.tri) m.tri = 'recent' 200 return m 201 } 202 function section() { return etat().section } 203 204 /* UNE SEULE FAÃON D'OUVRIR UNE SECTION. Le rail, un fonds qui renvoie vers 205 * ses créations, un raccourci venu d'un autre espace : tous passent ici. 206 * Deux chemins, et l'un des deux oublierait de vider la recherche â on 207 * chercherait encore dans une section qu'on ne regarde plus. */ 208 function ouvrirSection(id, recherche) { 209 var m = etat() 210 m.section = id 211 /* La recherche se vide â SAUF quand l'appelant mène à une trouvaille 212 * précise. Sans cette exception, la recherche de la page ouvrirait la 213 * bonne section et laisserait rechercher une SECONDE fois ce qu'on vient 214 * de trouver. C'est toujours un seul chemin : l'exception passe par ici. */ 215 m.recherche = typeof recherche === 'string' ? recherche : '' 216 garder() 217 redessiner() 218 } 219 220 /* MENER à UN FICHIER PRÃCIS, depuis ailleurs. 221 * 222 * LE DÃGÃT SANS ELLE : la recherche de la page trouve désormais dans le 223 * CONTENU des fichiers du registre â c'est ce que CRÃA sait faire depuis 224 * `fichier_chercher` et que la personne ne pouvait pas. Trouver sans pouvoir 225 * ouvrir n'est pas trouver : il fallait une porte qui pose la vue sur LE 226 * fichier, pas sur la médiathèque en général. 227 * 228 * Elle rend `false` quand l'entrée n'est pas là â le registre est cloisonné 229 * par projet, et un identifiant d'un autre projet ne se force pas d'ici. 230 * L'appelant sait alors qu'il n'a rien ouvert, au lieu de croire que si. */ 231 function montrer(fichierId) { 232 var id = String(fichierId || '') 233 if (!id) return false 234 var f = pieces().find(function (x) { return x && x.id === id && !x.supprime }) 235 if (!f) return false 236 ouvrirSection(sectionDuType(f.type), String(f.nom || '')) 237 return true 238 } 239 240 /* ââ LA MATIÃRE, RELUE ET JAMAIS RECOPIÃE âââââââââââââââââââââââââââââââ 241 * Chaque compteur du rail interroge sa source à l'instant du rendu. Rien 242 * n'est mis en cache : un chiffre gardé devient faux dès qu'on produit 243 * ailleurs, et un rail qui ment sur ses comptes est pire qu'un rail sans 244 * compte du tout. */ 245 function parType(ids) { 246 return pieces().filter(function (f) { 247 return !f.supprime && ids.indexOf(String(f.type || '').toLowerCase()) >= 0 248 }) 249 } 250 /* LES SESSIONS DES STUDIOS SONT DES SESSIONS AUSSI. Cette vue ne listait que
251 * les fils de chat : depuis qu'un fichier peut venir d'un studio, son 252 * origine n'apparaissait NULLE PART, et « N pièces » tombait à zéro pour 253 * tout ce qu'un studio avait fabriqué. Le tronc le dit déjà de son côté â 254 * « un seul système de sessions dans toute l'application, pas un par 255 * endroit » ; la Médiathèque le lit enfin comme tel. */ 256 function conversations() { 257 var st = S() 258 var out = (st.conversations || []) 259 .filter(function (c) { return c && !c.archived && !c.ephemeral }) 260 .map(function (c) { 261 return { id: c.id, title: c.title || 'Session', messages: c.messages || [], 262 updatedAt: c.updatedAt, createdAt: c.createdAt, studio: '' } 263 }) 264 var parStudio = st.studioSessions || {} 265 for (var mode in parStudio) { 266 if (!Object.prototype.hasOwnProperty.call(parStudio, mode)) continue 267 var liste = parStudio[mode] 268 if (!Array.isArray(liste)) continue 269 for (var i = 0; i < liste.length; i++) { 270 var s = liste[i] 271 if (!s || !s.id || s.archived) continue 272 out.push({ id: s.id, title: s.title || 'Session', messages: s.chat || [], 273 updatedAt: s.updatedAt, createdAt: s.createdAt, studio: mode }) 274 } 275 } 276 return out.sort(function (a, b) { return (b.updatedAt || b.createdAt || 0) - (a.updatedAt || a.createdAt || 0) }) 277 } 278 /* Le nom d'un studio se demande à SA table, jamais à une liste écrite ici : 279 * un studio ajouté demain doit se nommer sans qu'on touche à ce fichier. */ 280 function nomStudio(id) { 281 try { 282 var reg = (typeof STUDIOS_CREA !== 'undefined' ? STUDIOS_CREA : []) 283 var s = reg.find(function (x) { return x.id === id }) 284 if (s) return s.n 285 } catch (e) { /* table illisible : le nom brut du mode vaut mieux que rien */ } 286 return id ? id.charAt(0).toUpperCase() + id.slice(1) : '' 287 } 288 function gardes() { return S().filGardes || [] } 289 function lus() { return S().filLus || [] } 290 function memoires() { 291 var st = S() 292 var m = Array.isArray(st.memories) ? st.memories : [] 293 var g = st.knowledgeGraph && Array.isArray(st.knowledgeGraph.nodes) ? st.knowledgeGraph.nodes : [] 294 return { memoires: m, noeuds: g } 295 } 296 function enFabrication() { 297 return (S().bgTasks || []).filter(function (t) { return t.status === 'running' || t.status === 'queued' }) 298 } 299 function modeles() { return (window.FOLDER_TEMPLATES || []).slice() } 300 function realisations() { 301 var r = registre() 302 if (!r || typeof r.realisations !== 'function') return [] 303 try { return r.realisations() || [] } catch (e) { 304 /* Même raison que `pieces()` : trop tôt au démarrage. */ 305 return [] 306 } 307 } 308 function corbeille() { 309 var r = registre() 310 if (!r || typeof r.supprimes !== 'function') return [] 311 try { return r.supprimes() || [] } catch (e) { 312 /* Même raison que `pieces()`. */ 313 return [] 314 } 315 } 316 /* LE DÃSHERBAGE SE MESURE, IL NE SE DEVINE PAS. Deux pièces sont un doublon 317 * quand elles portent le même nom ET le même poids : le nom seul accuse deux 318 * « rapport.pdf » qui n'ont rien à voir, et le poids seul rapproche deux 319 * images de 240 Ko sans rapport. */ 320 function doublons() { 321 var vus = {}, sortie = [] 322 pieces().forEach(function (f) { 323 if (f.supprime) return 324 var cle = String(f.nom || '').toLowerCase() + '|' + (f.octets || 0) 325 if (vus[cle]) { vus[cle].push(f); return } 326 vus[cle] = [f] 327 }) 328 Object.keys(vus).forEach(function (k) { if (vus[k].length > 1) sortie.push(vus[k]) }) 329 return sortie 330 } 331 function jamaisRouvertes() { 332 return pieces().filter(function (f) { 333 return !f.supprime && f.majAt && jours(f.majAt) > 180 334 }) 335 } 336 337 function compte(id) { 338 var s = SECTIONS.find(function (x) { return x.id === id }) 339 if (s && s.types) return parType(s.types).length 340 switch (id) { 341 case 'creations': return pieces().filter(function (f) { return !f.supprime }).length 342 case 'conversations': return conversations().length 343 case 'fabrication': return enFabrication().length 344 case 'modeles': return modeles().length 345 case 'kiosque': return gardes().length + lus().length 346 case 'sources': return 0 347 case 'savoir': return memoires().memoires.length + memoires().noeuds.length 348 case 'lecture': return 0 349 case 'monde': return CATALOGUES.filter(function (c) { return c.branche }).length 350 case 'emprunts': return 0 351 case 'partage': return 0 352 case 'desherbage': return doublons().length + jamaisRouvertes().length 353 } 354 return 0 355 } 356 357 /* ââ LES CATALOGUES DU DEHORS âââââââââââââââââââââââââââââââââââââââââââ 358 * `branche` a été RELEVà dans le serveur, pas décrété : le nombre est le 359 * nombre d'occurrences trouvées le jour du relevé. Un catalogue non branché 360 * est nommé et éteint â il n'est pas caché, parce qu'un manque nommé se 361 * répare et qu'un manque caché s'oublie. */
362 var CATALOGUES = [ 363 { nom: 'Wikipédia', branche: true, dit: 'Encyclopédie libre â déjà atteinte par le serveur.' }, 364 { nom: 'YouTube', branche: true, dit: 'Vidéos â déjà atteintes.' }, 365 { nom: 'Flux RSS', branche: true, dit: "N'importe quel flux, n'importe quel podcast." }, 366 { nom: 'Unsplash · Pexels', branche: true, dit: 'Images libres de droits.' }, 367 { nom: 'NewsAPI · GDELT', branche: true, dit: 'La presse du jour, deux sources croisées.' }, 368 { nom: 'OpenStreetMap', branche: true, dit: 'Lieux et adresses â la porte de Monde Réel.' }, 369 { nom: 'Gallica · BnF', branche: false, dit: 'Des millions de documents du domaine public français.' }, 370 { nom: 'Internet Archive', branche: false, dit: "Livres, films, et les pages telles qu'elles étaient." }, 371 { nom: 'Légifrance', branche: false, dit: 'Le droit français, à jour et faisant foi.' }, 372 { nom: 'data.gouv.fr', branche: false, dit: 'Les données publiques, dont les guides associatifs.' }, 373 { nom: 'OpenLibrary · ISBN', branche: false, dit: "N'importe quel livre par son code-barres." }, 374 { nom: 'Ma bibliothèque municipale', branche: false, dit: 'Chercher, voir si câest disponible, réserver pour de vrai.' }, 375 { nom: 'France Travail', branche: false, dit: 'Offres et alternance â pour ne pas enfermer les gens dans CRÃA.' }, 376 ] 377 378 /* âââ LA RECHERCHE QUI TRAVERSE âââââââââââââââââââââââââââââââââââââââââ 379 * C'est le verbe qui sépare un catalogue d'un placard : chercher UNE fois 380 * dans les seize matières. Avant, il fallait chercher quatre fois dans 381 * quatre écrans, et l'on ne trouvait que ce qu'on savait déjà où chercher. */ 382 function chercher(q) { 383 var t = String(q || '').trim().toLowerCase() 384 if (!t) return [] 385 var out = [] 386 function pousse(o) { if (out.length < 60) out.push(o) } 387 pieces().forEach(function (f) { 388 if (f.supprime) return 389 var dans = String(f.nom || '').toLowerCase() + ' ' + String(f.note || '').toLowerCase() + ' ' + String(f.chemin || '').toLowerCase() 390 if (dans.indexOf(t) < 0) return 391 pousse({ nom: f.nom, ou: f.chemin || 'Créations', quand: f.majAt, section: sectionDuType(f.type), id: f.id }) 392 }) 393 conversations().forEach(function (c) { 394 if (String(c.title || '').toLowerCase().indexOf(t) < 0) return 395 pousse({ nom: c.title || 'Session', ou: 'Conversations', quand: c.updatedAt || c.createdAt, section: 'conversations', convId: c.id }) 396 }) 397 gardes().concat(lus()).forEach(function (a) { 398 var titre = String((a && (a.title || a.titre)) || '') 399 if (titre.toLowerCase().indexOf(t) < 0) return 400 pousse({ nom: titre, ou: 'Kiosque', quand: a.quand || a.at || 0, section: 'kiosque' }) 401 }) 402 memoires().memoires.forEach(function (m) { 403 var txt = String((m && (m.text || m.contenu || m)) || '') 404 if (txt.toLowerCase().indexOf(t) < 0) return 405 pousse({ nom: txt.slice(0, 70), ou: 'Savoir', quand: m.createdAt || 0, section: 'savoir' }) 406 }) 407 return out 408 } 409 function sectionDuType(type) { 410 var t = String(type || '').toLowerCase() 411 var s = SECTIONS.find(function (x) { return x.types && x.types.indexOf(t) >= 0 }) 412 return s ? s.id : 'creations' 413 } 414 415 /* âââ LE RENDU ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 416 417 function railSections() { 418 var cour = section() 419 return FAMILLES.map(function (fam) { 420 var siennes = SECTIONS.filter(function (s) { return s.fam === fam.id }) 421 return `<p class="mq-fam">${esc(fam.nom)}</p>` + siennes.map(function (s) { 422 var n = compte(s.id) 423 return `<button class="mq-item${cour === s.id ? ' on f-' + fam.id : ''}" data-mq="section" data-mq-val="${esc(s.id)}"> 424 <span class="i-${fam.id}">${ic(s.ic, 13)}</span>${esc(s.nom)}${s.porte ? '<span class="mq-porte" title="Un autre système">â</span>' : ''}${n ? `<span class="n">${n}</span>` : ''} 425 </button>` 426 }).join('') 427 }).join('') 428 } 429 430 function enteteFin() { 431 var m = etat() 432 return `<div class="mq-entete-fin">
433 <span class="mq-compte">${pieces().filter(function (f) { return !f.supprime }).length} notices · ${SECTIONS.length} sections</span> 434 <button class="mq-primaire mq-mini" data-mq="acquerir">${ic('plus', 12)} Faire entrer</button> 435 </div>` 436 } 437 438 /* ââ LE CATALOGUE â la page d'entrée ââââââââââââââââââââââââââââââââââ */ 439 function vueCatalogue() { 440 var m = etat() 441 var res = chercher(m.recherche) 442 var fonds = (S().folders || []).slice(0, 6) 443 return `<div class="mq-page"> 444 <div class="mq-grande-recherche"> 445 ${ic('loupe', 18, 1.9)} 446 <input id="mq-q" class="mq-champ-grand" placeholder="Chercher dans tout â et dehors" value="${esc(m.recherche)}" /> 447 ${m.recherche ? `<button class="mq-verbe" data-mq="vider">Effacer</button>` : `<span class="mq-touche">âK</span>`} 448 </div> 449 450 ${m.recherche ? `<section class="mq-bloc"> 451 <p class="mq-etiq">Trouvé <span class="mq-n">${res.length}</span></p> 452 ${res.length ? res.map(function (r) { 453 return `<button class="mq-ligne" data-mq="aller-section" data-mq-val="${esc(r.section)}"> 454 <span class="mq-t">${esc(r.nom)}</span> 455 <span class="mq-m">${esc(r.ou)}${r.quand ? ' · ' + esc(ilYA(r.quand)) : ''}</span> 456 </button>` 457 }).join('') : vide('loupe', 'Rien sous ce mot', `Rien sous ce mot, ni dans les seize sections. Les catalogues du dehors ne sont pas encore interrogés par ce champ â c'est écrit plutôt que promis.`)} 458 </section>` : ''} 459 460 <section class="mq-bloc"> 461 <p class="mq-etiq">Le fonds <span class="mq-apres">ce qui fait l'identité de la collection</span></p> 462 ${fonds.length ? `<div class="mq-bande">${fonds.map(function (f) { 463 var n = pieces().filter(function (x) { return !x.supprime && x.projectId === f.id }).length 464 return `<button class="mq-bande-c" data-mq="fonds" data-mq-val="${esc(f.id)}"> 465 <span class="mq-vig" style="background:${esc(f.color || 'var(--accent)')}">${ic('dossier', 18)}</span> 466 <span class="mq-bande-n">${esc(f.name || 'Projet')}</span> 467 <span class="mq-bande-s">${n} pièce${n > 1 ? 's' : ''}</span> 468 </button>` 469 }).join('')}</div>` 470 : vide('livre', 'Aucun fonds encore', `Aucun projet encore. Un fonds, c'est ce qui donne une identité à une collection : le vôtre naîtra du premier projet que vous créerez.`)} 471 </section> 472 473 <section class="mq-bloc"> 474 <p class="mq-etiq">Ce que CRÃA vous tend <span class="mq-apres">le conseil â c'est son meilleur rôle</span></p> 475 ${conseils().length ? conseils().map(function (c) { 476 return `<div class="mq-ligne-plate"> 477 <span class="mq-t">${esc(c.quoi)}</span> 478 <span class="mq-source">${esc(c.ou)}</span> 479 <button class="mq-verbe" data-mq="aller-section" data-mq-val="${esc(c.section)}">${esc(c.verbe)}</button> 480 </div>` 481 }).join('') : vide('etincelle', 'Rien à vous tendre', `Rien à vous tendre pour l'instant. Le conseil se nourrit de ce que vous gardez, produisez et laissez de côté â il arrivera tout seul.`)} 482 </section> 483 </div>` 484 } 485 486 /* CE QUE CRÃA VOUS TEND â relu, jamais recopié. Chaque conseil naît d'un 487 * FAIT mesurable à l'instant du rendu, et disparaît quand le fait disparaît. 488 * Un conseil qu'on ne peut pas faire taire en agissant est une publicité. */ 489 function conseils() { 490 var out = [] 491 var d = doublons() 492 if (d.length) out.push({ quoi: d.length + ' pièce' + (d.length > 1 ? 's' : '') + ' en double', ou: 'désherbage', section: 'desherbage', verbe: 'Passer en revue' }) 493 var vieux = jamaisRouvertes() 494 if (vieux.length) out.push({ quoi: vieux.length + ' pièce' + (vieux.length > 1 ? 's' : '') + ' jamais rouverte' + (vieux.length > 1 ? 's' : '') + ' depuis six mois', ou: 'désherbage', section: 'desherbage', verbe: 'Regarder' }) 495 var g = gardes() 496 if (g.length) out.push({ quoi: g.length + ' article gardé' + (g.length > 1 ? 's' : '') + ', jamais rouvert', ou: 'kiosque', section: 'kiosque', verbe: 'Relire' }) 497 var sons = parType(['audio', 'son']) 498 if (sons.length) out.push({ quoi: sons.length + ' enregistrement' + (sons.length > 1 ? 's' : '') + ' sans transcription', ou: 'sons', section: 'sons', verbe: 'Ouvrir' }) 499 /* LE SEUL CONSEIL QUI SORT DU CATALOGUE, ET IL MÃNE à LA BRANCHE VOISINE. 500 * Il ne « joint » plus rien lui-même : joindre un dossier est un acte de 501 * DISQUE, et le catalogue n'en tient pas la liste. Il constate qu'il n'a 502 * rien reçu du disque, et montre la porte. */ 503 if (!versesDuDisque().length) out.push({ quoi: "Rien de votre ordinateur n'est encore versé ici", ou: 'Explorateur', section: 'explorateur', verbe: "Ouvrir l'Explorateur" }) 504 return out.slice(0, 5) 505 } 506 507 /* ââ UN VIDE SE MONTRE, IL NE SE RACONTE PAS ââââââââââââââââââââââââââââ 508 * 509 * « Dans la maquette c'est imagé, c'est structuré. Là , tu ne m'as fait que 510 * des écritures â tous les fichiers sont des écrans d'écriture bizarre. » 511 * 512 * La carte a été réparée ; restait ce qu'on voit quand il n'y a PAS de 513 * carte. Chaque section vide posait deux paragraphes l'un sous l'autre â 514 * la note qui annonce le rayon, puis le texte qui dit qu'il est vide â et 515 * les deux racontaient la même chose dans deux formulations différentes. 516 * Sur seize sections d'un catalogue neuf, c'est seize murs de prose là où 517 * il n'y a précisément rien à lire. 518 * 519 * Un vide de bibliothèque se reconnaît à l'Åil : une marque, une phrase,
520 * et la porte à pousser s'il y en a une. Le RESTE â la raison, la promesse, 521 * ce qui viendra s'y ranger â est la note de la section, qui n'a plus 522 * besoin d'être dite deux fois. C'est la règle 3 portée à l'écran : la 523 * note se juge sur son terrain, et son terrain est le rayon vide. 524 */ 525 function vide(icone, titre, dit, verbe) { 526 return `<div class="mq-vide"> 527 <span class="mq-vide-marque">${ic(icone || 'livre', 26, 1.4)}</span> 528 <p class="mq-vide-titre">${titre}</p> 529 ${dit ? `<p class="mq-vide-dit">${dit}</p>` : ''} 530 ${verbe || ''} 531 </div>` 532 } 533 534 /* ââ UNE ÃTAGÃRE â le gabarit commun aux sections de matière âââââââââââ 535 * La note ne s'empile plus AU-DESSUS du rayon comme un bloc : elle passe 536 * sous le titre, alignée à gauche, à la largeur de lecture â c'est là 537 * qu'une explication se lit. La figure, elle, ne dit QUE ce qui manque. */ 538 function etagere(titre, liste, dit, verbes, icone) { 539 return `<div class="mq-page"> 540 <div class="mq-barre"> 541 <div class="mq-titre-bloc"> 542 <p class="mq-page-titre">${esc(titre)}</p> 543 ${dit ? `<p class="mq-dit">${dit}</p>` : ''} 544 </div> 545 ${verbes || ''} 546 </div> 547 ${liste.length ? `<div class="mq-grille">${liste.map(cartePiece).join('')}</div>` 548 : vide(icone, 'Cette étagère est encore vide', 'Elle existe quand même : une section quâon ne voit pas est une section qui nâexiste pas.')} 549 </div>` 550 } 551 /* ââ UNE PIÃCE SE RECONNAÃT à SON IMAGE, PAS à SON NOM DE FICHIER âââââââ 552 * 553 * « Dans la maquette c'est imagé, c'est structuré. Là , tu ne m'as fait que 554 * des écritures â tous les fichiers sont des écrans d'écriture bizarre. » 555 * 556 * Il a raison, et c'est la MÃME carte dans les seize sections : la réparer 557 * ici les répare toutes. Elle portait deux lignes de texte et rien d'autre. 558 * 559 * Une affiche se reconnaît à son visuel ; un son à sa forme d'onde ; un 560 * document à sa première page. Quand l'image existe VRAIMENT, on la montre. 561 * Quand elle n'existe pas, on ne fabrique pas un faux aperçu : on pose 562 * l'icône du type sur un fond teinté de sa famille â ce qui se reconnaît 563 * quand même d'un coup d'Åil, sans mentir sur ce qu'on a. */ 564 var TEINTES = { 565 image: 'img', photo: 'img', png: 'img', jpg: 'img', jpeg: 'img', svg: 'img', gif: 'img', webp: 'img', 566 audio: 'son', son: 'son', voix: 'son', mp3: 'son', wav: 'son', 567 video: 'vid', mp4: 'vid', mov: 'vid', 568 code: 'code', html: 'code', js: 'code', css: 'code', json: 'code', 569 } 570 function apercu(f) { 571 var t = String(f.type || '').toLowerCase() 572 var fam = TEINTES[t] || 'doc' 573 /* L'IMAGE N'EST MONTRÃE QUE SI ELLE EXISTE VRAIMENT. `url` peut être une 574 * adresse ou une donnée embarquée ; les deux s'affichent. Un `contenu` 575 * textuel n'est PAS une image, même pour un fichier dont le type dit 576 * « image » â c'est le cas d'un SVG écrit à la main, et le navigateur 577 * afficherait un carré cassé. */ 578 var src = fam === 'img' && typeof f.url === 'string' && /^(https?:|data:|blob:)/.test(f.url) ? f.url : '' 579 if (src) { 580 return `<span class="mq-apercu mq-apercu-img"><img src="${esc(src)}" alt="" loading="lazy"></span>` 581 } 582 return `<span class="mq-apercu t-${fam}">${ic({ img: 'image', son: 'son', vid: 'video', code: 'source' }[fam] || 'doc', 22, 1.4)}</span>` 583 } 584 function cartePiece(f) { 585 var studio = f.origine && f.origine.studio 586 return `<article class="mq-carte" data-mq="piece" data-mq-val="${esc(f.id)}"> 587 ${apercu(f)} 588 <span class="mq-carte-t" title="${esc(f.nom || 'Sans nom')}">${esc(f.nom || 'Sans nom')}</span> 589 <span class="mq-carte-lignes"> 590 ${studio ? `<span class="mq-puce">${esc(studio)}</span>` : ''} 591 <span class="mq-carte-m">${esc([poids(f.octets), ilYA(f.majAt)].filter(Boolean).join(' · '))}</span> 592 </span> 593 <div class="mq-verbes"> 594 <button class="mq-verbe" data-mq="verser" data-mq-val="${esc(f.id)}">Verser dansâ¦</button> 595 ${f.convId ? `<button class="mq-verbe" data-mq="sa-session" data-mq-val="${esc(f.convId)}">Sa session</button>` : ''} 596 </div> 597 </article>` 598 } 599 600 /* ââ LES SECTIONS ââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 601 function vueSection(id) { 602 var s = SECTIONS.find(function (x) { return x.id === id }) 603 if (!s) return vueCatalogue() 604 605 if (s.types) { 606 var dits = { 607 images: "Une photothèque, pas un dossier d'images : elle trie par ce que la photo montre, pas par son nom de fichier. La description est produite par CRÃA quand vous le lui demandez â elle n'est jamais inventée.", 608 sons: "Enregistrements, voix générées, ambiances. Une transcription rend deux heures d'assemblée cherchables en un mot ; elle n'est pas encore branchée, et c'est dit.", 609 videos: 'Les vôtres, celles produites ici, celles regardées.', 610 documents: "Ce qui compte n'est pas le nom du fichier mais ce qu'il y a dedans. L'indexation du contenu est le prochain lot.", 611 } 612 return etagere(s.nom, parType(s.types), dits[id] || '', '', s.ic) 613 } 614 615 if (id === 'creations') return vueCreations() 616 if (id === 'conversations') return vueConversations() 617 if (id === 'fabrication') return vueFabrication() 618 if (id === 'modeles') return vueModeles() 619 if (id === 'kiosque') return vueKiosque() 620 if (id === 'sources') return vueSources() 621 if (id === 'savoir') return vueSavoir() 622 if (id === 'lecture') return vueLecture() 623 if (id === 'monde') return vueMonde() 624 if (id === 'emprunts') return vueEmprunts() 625 if (id === 'partage') return vuePartage() 626 if (id === 'desherbage') return vueDesherbage() 627 return vueCatalogue() 628 } 629 630 /* FICHIERS â UNE PORTE, PAS UNE SECTION. C'est l'autre système. La 631 * Médiathèque n'en dessine pas l'écran : elle l'ouvre, et dit ce qui passe 632 * la frontière dans chaque sens. */ 633 function vueCreations() { 634 var reals = realisations() 635 var seules = pieces().filter(function (f) { return !f.supprime && !f.realisation }) 636 return `<div class="mq-page"> 637 <div class="mq-barre"><div class="mq-titre-bloc"><p class="mq-page-titre">Créations</p><p class="mq-dit">Ce que les studios ont produit. Une <b>réalisation</b>, c'est plusieurs fichiers sortis d'un seul geste â une affiche, son format carré, son tract et son texte pour les réseaux. Les compter séparément ferait perdre ce qui les relie.</p></div></div> 638 <section class="mq-bloc"> 639 <p class="mq-etiq">Réalisations <span class="mq-n">${reals.length}</span></p> 640 ${reals.length ? reals.slice(0, 12).map(function (r) { 641 var fs = registre() && registre().fichiersDe ? (registre().fichiersDe(r.id) || []) : [] 642 return `<div class="mq-ligne-plate">
643 <span class="mq-t">${esc(r.nom || 'Réalisation')}</span> 644 <span class="mq-m">${fs.length} fichier${fs.length > 1 ? 's' : ''} · ${esc(ilYA(r.majAt || r.creeAt))}</span> 645 </div>` 646 }).join('') : vide('etincelle', 'Aucune réalisation', `Aucune réalisation encore. Elles naissent toutes seules dès qu'un studio produit plusieurs pièces d'un coup.`)} 647 </section> 648 <section class="mq-bloc"> 649 <p class="mq-etiq">Pièces seules <span class="mq-n">${seules.length}</span></p> 650 ${seules.length ? `<div class="mq-grille">${seules.slice(0, 24).map(cartePiece).join('')}</div>` 651 : vide('image', 'Aucune pièce seule', `Rien encore. Chaque image, document ou site produit dans un studio atterrit ici sans qu'on ait à le ranger.`)} 652 </section> 653 </div>` 654 } 655 656 function vueConversations() { 657 var cs = conversations() 658 return `<div class="mq-page"> 659 <div class="mq-barre"><div class="mq-titre-bloc"><p class="mq-page-titre">Conversations</p><p class="mq-dit">Les sessions ne sont pas un onglet à part : ce sont une <b>matière</b> du catalogue, au même titre qu'une image. On remonte d'un livrable à l'échange qui l'a fait naître, et l'inverse.</p></div></div> 660 ${cs.length ? `<div class="mq-grille">${cs.slice(0, 30).map(function (c) { 661 var produits = pieces().filter(function (f) { return !f.supprime && f.convId === c.id }).length 662 return `<article class="mq-carte" data-mq="ouvrir-session" data-mq-val="${esc(c.id)}"> 663 <span class="mq-carte-t">${esc(c.title || 'Session')}</span> 664 <span class="mq-carte-m">${c.studio ? esc(nomStudio(c.studio)) + ' · ' : ''}${(c.messages || []).length} message${(c.messages || []).length > 1 ? 's' : ''}${produits ? ' · ' + produits + ' pièce' + (produits > 1 ? 's' : '') : ''} · ${esc(ilYA(c.updatedAt || c.createdAt))}</span> 665 </article>` 666 }).join('')}</div>` : vide('bulle', 'Aucune conversation', `Aucune session. La première conversation que vous ouvrirez apparaîtra ici avec ce qu'elle aura produit.`)} 667 </div>` 668 } 669 670 function vueFabrication() { 671 var t = enFabrication() 672 return `<div class="mq-page"> 673 <div class="mq-barre"><div class="mq-titre-bloc"><p class="mq-page-titre">En fabrication</p><p class="mq-dit">Une médiathèque a une vie, pas seulement un inventaire. Ce qui se produit pendant que vous lisez cette page est ici.</p></div></div> 674 ${t.length ? t.map(function (x) { 675 return `<div class="mq-ligne-plate"> 676 <span class="mq-t">${esc(String(x.label || x.prompt || 'Travail en cours').slice(0, 80))}</span> 677 <span class="mq-m">${(x.steps || []).length ? 'étape ' + (x.steps || []).length : x.status === 'queued' ? 'en file' : 'en cours'}</span> 678 </div>` 679 }).join('') : vide('horloge', 'Rien en fabrication', `Rien en cours de fabrication. Les travaux de fond lancés depuis les studios apparaîtront ici pendant qu'ils tournent.`)} 680 </div>` 681 } 682 683 function vueModeles() { 684 var ms = modeles() 685 return `<div class="mq-page"> 686 <div class="mq-barre"><div class="mq-titre-bloc"><p class="mq-page-titre">Modèles</p><p class="mq-dit">Ce qui sert à refaire : gabarits de projet, chartes, points de départ. C'est ici que viendront se brancher les modèles de document et de vidéo.</p></div></div> 687 ${ms.length ? `<div class="mq-grille">${ms.map(function (t) { 688 return `<article class="mq-carte" data-mq="modele" data-mq-val="${esc(t.key)}"> 689 <span class="mq-carte-t">${esc(t.name)}</span> 690 <span class="mq-carte-m">${esc((t.studios || []).join(' · '))}</span> 691 </article>` 692 }).join('')}</div>` : vide('gabarit', 'Aucun modèle', `Aucun modèle.`)} 693 </div>` 694 } 695 696 function vueKiosque() { 697 var g = gardes(), l = lus() 698 return `<div class="mq-page"> 699 <div class="mq-barre"><div class="mq-titre-bloc"><p class="mq-page-titre">Kiosque</p><p class="mq-dit">Ce que CRÃA a lu pour vous. C'est la presse du jour â et surtout ce que vous en avez gardé, qui n'était visible nulle part jusqu'ici.</p></div></div> 700 <section class="mq-bloc"> 701 <p class="mq-etiq">Gardé <span class="mq-n">${g.length}</span></p> 702 ${g.length ? g.slice(0, 20).map(ligneArticle).join('') 703 : vide('kiosque', 'Rien de gardé', `Rien de gardé. Ce que vous mettez de côté dans le fil d'actualités atterrit ici et n'en repart plus.`)} 704 </section> 705 <section class="mq-bloc"> 706 <p class="mq-etiq">Lu <span class="mq-n">${l.length}</span></p> 707 ${l.length ? l.slice(0, 20).map(ligneArticle).join('') 708 : vide('kiosque', 'Rien encore lu', `Rien encore lu. Le fil d'actualités retient déjà ce que vous ouvrez â cette étagère se remplira toute seule.`)} 709 </section> 710 </div>` 711 } 712 function ligneArticle(a) { 713 var titre = String((a && (a.title || a.titre || a)) || 'Article') 714 return `<div class="mq-ligne-plate">
715 <span class="mq-t">${esc(titre.slice(0, 110))}</span> 716 ${a && (a.source || a.quand) ? `<span class="mq-m">${esc([a.source, a.quand ? ilYA(a.quand) : ''].filter(Boolean).join(' · '))}</span>` : ''} 717 </div>` 718 } 719 720 /* SOURCES â LE TROU LE PLUS GRAND, ET IL EST NOMMÃ. 721 * Mesuré dans le dépôt : zéro. CRÃA cite quatre pages dans une réponse et 722 * n'en garde aucune. Une source disparue du web rend son affirmation 723 * invérifiable pour toujours ; une source gardée à la date de lecture reste 724 * vérifiable. C'est ce que fait une bibliothèque, et c'est ce qu'aucun 725 * assistant ne fait. La section existe VIDE pour que le trou se voie. */ 726 function vueSources() { 727 return `<div class="mq-page"> 728 <div class="mq-barre"><div class="mq-titre-bloc"><p class="mq-page-titre">Sources</p><p class="mq-dit">D'où vient ce que CRÃA a dit. Elle cite des pages dans ses réponses et n'en conserve aucune â relevé dans le code : <b>zéro</b>. Trois mois plus tard, la page citée a bougé ou disparu, et la réponse ne s'appuie plus sur rien.</p></div></div> 729 <section class="mq-bloc"> 730 <p class="mq-etiq">Rien n'est encore gardé</p> 731 ${vide('source', 'Aucune source conservée', `Cette étagère se remplira le jour où une citation sera gardée avec sa date.`)} 732 </section> 733 </div>` 734 } 735 736 /* SAVOIR â CE QU'ELLE CROIT SAVOIR DE VOUS, ET QUE VOUS POUVEZ CORRIGER. 737 * Soixante-cinq usages dans le code, aucun écran : on ne pouvait ni voir ni 738 * réparer ce que CRÃA retenait. Montrer sans permettre de corriger serait 739 * pire que de cacher. */ 740 function vueSavoir() { 741 var m = memoires() 742 return `<div class="mq-page"> 743 <div class="mq-barre"><div class="mq-titre-bloc"><p class="mq-page-titre">Savoir</p><p class="mq-dit">Ce que CRÃA a retenu de vous. Le montrer n'a de sens que si l'on peut le corriger : chaque ligne se retire.</p></div></div> 744 <section class="mq-bloc"> 745 <p class="mq-etiq">Mémoires <span class="mq-n">${m.memoires.length}</span></p> 746 ${m.memoires.length ? m.memoires.slice(0, 25).map(function (x, i) { 747 var txt = String((x && (x.text || x.contenu)) || x || '') 748 return `<div class="mq-ligne-plate"> 749 <span class="mq-t">${esc(txt.slice(0, 140))}</span> 750 <button class="mq-verbe" data-mq="memoire-retirer" data-mq-val="${i}">Retirer</button> 751 </div>` 752 }).join('') : vide('savoir', 'Rien retenu pour lâinstant', `CRÃA n'a rien retenu de vous pour l'instant. Ce qu'elle retiendra apparaîtra ici, et pourra en être retiré.`)} 753 </section> 754 <section class="mq-bloc"> 755 <p class="mq-etiq">Graphe de connaissances <span class="mq-n">${m.noeuds.length}</span></p> 756 ${m.noeuds.length ? `<div class="mq-jetons">${m.noeuds.slice(0, 40).map(function (n) { 757 return `<span class="mq-jeton">${esc(String((n && (n.label || n.id)) || n).slice(0, 40))}</span>` 758 }).join('')}</div>` : vide('savoir', 'Aucun nÅud', `Aucun nÅud. Le graphe se construit à mesure des échanges.`)} 759 </section> 760 </div>` 761 } 762 763 function vueLecture() { 764 return `<div class="mq-page"> 765 <div class="mq-barre"><div class="mq-titre-bloc"><p class="mq-page-titre">Salle de lecture</p><p class="mq-dit">Une médiathèque a des <b>lieux</b>, pas seulement des rayons. La salle de lecture est un endroit où l'on lit vraiment : une colonne, pas de barre d'outils, et la marge pour ce qu'on note.</p></div></div> 766 <section class="mq-bloc"> 767 <p class="mq-etiq">Aucun ouvrage en salle</p> 768 ${vide('lecture', 'La salle nâest pas encore ouverte', `Le lecteur reste à construire : c'est un lieu, pas une liste, et il se fait à part.`)} 769 </section> 770 </div>` 771 } 772 773 function vueMonde() { 774 var branches = CATALOGUES.filter(function (c) { return c.branche }) 775 var restants = CATALOGUES.filter(function (c) { return !c.branche }) 776 return `<div class="mq-page"> 777 <div class="mq-barre"><div class="mq-titre-bloc"><p class="mq-page-titre">Le monde</p><p class="mq-dit">Ce qui définit une bibliothèque n'est pas son stock : c'est son accès. CRÃA atteint déjà six catalogues extérieurs tous les jours â et ne gardait rien de ce qu'elle en rapportait.</p></div></div> 778 <section class="mq-bloc"> 779 <p class="mq-etiq">Branchés <span class="mq-n">${branches.length}</span></p> 780 ${branches.map(function (c) { 781 return `<div class="mq-ligne-plate"><span class="mq-source mq-source-vif">branché</span><span class="mq-t">${esc(c.nom)}</span><span class="mq-m">${esc(c.dit)}</span></div>` 782 }).join('')} 783 </section> 784 <section class="mq-bloc"> 785 <p class="mq-etiq">à brancher <span class="mq-n">${restants.length}</span></p> 786 ${restants.map(function (c) { 787 return `<div class="mq-ligne-plate mq-eteint"><span class="mq-source">
787à venir</span><span class="mq-t">${esc(c.nom)}</span><span class="mq-m">${esc(c.dit)}</span></div>` 788 }).join('')} 789 </section> 790 </div>` 791 } 792 793 function vueEmprunts() { 794 return `<div class="mq-page"> 795 <div class="mq-barre"><div class="mq-titre-bloc"><p class="mq-page-titre">Emprunts</p><p class="mq-dit">Ce qu'on a <b>pour un temps</b> : une licence, un accès, un livre réservé. Et le verbe le plus fort d'une bibliothèque â le <b>prêt entre bibliothèques</b> : demander à quelqu'un d'autre ce qu'on n'a pas.</p></div></div> 796 <section class="mq-bloc"> 797 <p class="mq-etiq">Rien en cours</p> 798 ${vide('relais', 'Aucun emprunt', `Le prêt bute sur le même mur que le poste partagé : la sauvegarde range un instantané entier par personne.`)} 799 </section> 800 </div>` 801 } 802 803 function vuePartage() { 804 return `<div class="mq-page"> 805 <div class="mq-barre"><div class="mq-titre-bloc"><p class="mq-page-titre">Partagé</p><p class="mq-dit">Ce qui vient des autres : les pièces jointes d'une mission, les ressources d'un cercle, ce qu'un membre d'un poste a déposé. L'auteur reste écrit dessus.</p></div></div> 806 <section class="mq-bloc"> 807 <p class="mq-etiq">Rien de partagé</p> 808 ${vide('gens', 'Rien reçu', `Elle se remplira le jour où deux personnes pourront écrire dans le même espace sans s'effacer.`)} 809 </section> 810 </div>` 811 } 812 813 function vueDesherbage() { 814 var d = doublons(), v = jamaisRouvertes(), c = corbeille() 815 return `<div class="mq-page"> 816 <div class="mq-barre"><div class="mq-titre-bloc"><p class="mq-page-titre">Désherbage</p><p class="mq-dit">Une bibliothèque qui ne désherbe pas meurt étouffée. Ici, ce qui existe en double et ce qui n'a pas été rouvert depuis six mois â plus la corbeille, qui rend toujours.</p></div></div> 817 <section class="mq-bloc"> 818 <p class="mq-etiq">En double <span class="mq-n">${d.length}</span></p> 819 ${d.length ? d.slice(0, 15).map(function (grp) { 820 return `<div class="mq-ligne-plate"> 821 <span class="mq-t">${esc(grp[0].nom)}</span> 822 <span class="mq-m">${grp.length} exemplaires · ${esc(poids(grp[0].octets))} chacun</span> 823 </div>` 824 }).join('') : vide('loupe', 'Aucun doublon', `Comptent comme doublons deux pièces de même nom <b>et</b> de même poids.`)} 825 </section> 826 <section class="mq-bloc"> 827 <p class="mq-etiq">Jamais rouvert depuis six mois <span class="mq-n">${v.length}</span></p> 828 ${v.length ? v.slice(0, 15).map(function (f) { 829 return `<div class="mq-ligne-plate"><span class="mq-t">${esc(f.nom)}</span><span class="mq-m">${esc(ilYA(f.majAt))} · ${esc(poids(f.octets))}</span></div>` 830 }).join('') : vide('horloge', 'Rien dâoublié', `Rien d'oublié. Ce qui dort depuis six mois viendra ici â pour être regardé, pas pour être jeté d'office.`)} 831 </section> 832 <section class="mq-bloc"> 833 <p class="mq-etiq">Corbeille <span class="mq-n">${c.length}</span></p> 834 ${c.length ? c.slice(0, 15).map(function (f) { 835 return `<div class="mq-ligne-plate"> 836 <span class="mq-t">${esc(f.nom)}</span> 837 <button class="mq-verbe" data-mq="restaurer" data-mq-val="${esc(f.id)}">Rendre</button> 838 </div>` 839 }).join('') : vide('corbeille', 'La corbeille est vide', `La corbeille est vide. Ce qui y tombe en revient : rien de ce que la Médiathèque supprime ne part sans retour.`)} 840 </section> 841 </div>` 842 } 843 844 /* âââ LA COQUILLE âââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 845 function rendre() { 846 var cour = section() 847 var s = SECTIONS.find(function (x) { return x.id === cour }) 848 var entete = typeof window._espaceMiniHeader === 'function' 849 ? window._espaceMiniHeader('Médiathèque', '', enteteFin()) 850 : `<div class="mq-entete-repli">Médiathèque</div>` 851 return `<main class="mq-espace flex flex-1 flex-col overflow-hidden min-w-0" style="background:var(--bg-primary)"> 852 ${entete} 853 <nav class="mq-fil"> 854 <button class="mq-fil-seg${cour === 'catalogue' ? ' on' : ''}" data-mq="section" data-mq-val="catalogue">${ic('livre', 13)} Catalogue</button> 855 <button class="mq-fil-seg${cour === 'explorateur' ? ' on' : ''}" data-mq="section" data-mq-val="explorateur">${ic('ordi', 13)} Explorateur</button>
856 ${s ? `<span class="mq-fil-sep">âº</span><span class="mq-fil-seg on">${esc(s.nom)}</span>` : ''} 857 </nav> 858 ${cour === 'explorateur' ? (window.CREA_DISQUE ? window.CREA_DISQUE.rendre() : `<div class="mq-defilement">${vide('ordi', 'LâExplorateur nâest pas chargé ici', 'Cette surface sert la Médiathèque sans le module de disque. Le catalogue, lui, fonctionne : câest la branche voisine.')}</div>`) : `<div class="mq-defilement"> 859 <div class="mq-colonnes"> 860 <aside class="mq-rail">${railSections()}</aside> 861 <div class="mq-centre">${cour === 'catalogue' ? vueCatalogue() : vueSection(cour)}</div> 862 </div> 863 </div>`} 864 ${cour === 'explorateur' ? '' : `<div class="mq-etat"> 865 <span>${pieces().filter(function (f) { return !f.supprime }).length} notices</span> 866 <span class="droite">${versesDuDisque().length} venue(s) du disque · ${CATALOGUES.filter(function (c) { return c.branche }).length} catalogues branchés</span> 867 </div>`} 868 </main>` 869 } 870 871 /* âââ LES GESTES ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 872 function cible(e) { 873 var n = e.target 874 while (n && n !== document) { 875 if (n.getAttribute && n.getAttribute('data-mq')) return n 876 n = n.parentNode 877 } 878 return null 879 } 880 881 async function agir(geste, val) { 882 var m = etat() 883 switch (geste) { 884 case 'section': 885 case 'aller-section': 886 ouvrirSection(val); return 887 case 'vider': m.recherche = ''; garder(); redessiner(); return 888 889 890 /* JOINDRE UN DOSSIER N'EST PLUS D'ICI, ET C'EST LE FOND DU SUJET. 891 * Un dossier accordé est un fait de DISQUE : il appartient à l'autre 892 * branche, qui en tient la seule liste. Le catalogue en gardait une 893 * copie â deux comptes qui divergeaient au premier retrait, et un bouton 894 * « Joindre » de chaque côté. */ 895 case 'verser': { 896 var f = registre() && registre().lire ? registre().lire(val) : null 897 if (!f) return 898 dire('« ' + esc(f.nom) + ' » â le versement vers un projet ou une session arrive au prochain lot.', 'info', 'ð¥') 899 return 900 } 901 case 'sa-session': 902 case 'ouvrir-session': 903 /* UN LIVRABLE SAIT D'Oà IL VIENT, ET C'EST TOUT L'INTÃRÃT. Remonter au 904 * fil qui l'a produit est le lien que la Médiathèque tient et qu'aucun 905 * dossier ne tiendra jamais. */ 906 /* DEUX SORTES DE SESSIONS, UNE SEULE PORTE. `openChatTab` supposait un 907 * fil de chat ; depuis qu'un fichier peut venir d'une session de 908 * STUDIO, cet identifiant désigne deux choses. Le résolveur 909 * (src/crea-chaines.js) sait laquelle et ouvre du bon côté. */ 910 if (typeof window.ouvrirSessionParIdentifiant === 'function') { 911 if (window.ouvrirSessionParIdentifiant(val)) return 912 dire('Cette session n\'existe plus â le fichier, lui, est toujours là .', 'info', 'ð§') 913 return 914 } 915 if (typeof window.openChatTab === 'function') { window.openChatTab(val); return } 916 if (typeof window.openModeTab === 'function') window.openModeTab('chat') 917 return 918 case 'fonds': 919 ouvrirSection('creations'); return 920 case 'modele': 921 if (typeof window.createFolderFromTemplate === 'function') { window.createFolderFromTemplate(val); return } 922 return 923 case 'memoire-retirer': { 924 var st = S() 925 if (!Array.isArray(st.memories)) return 926 st.memories.splice(Number(val), 1) 927 dire('Retiré de ce que CRÃA retient.', 'success', 'ð§ ') 928 garder(); redessiner(); return 929 } 930 case 'restaurer': 931 if (registre() && typeof registre().restaurer === 'function') registre().restaurer(val) 932 garder(); redessiner(); return 933 case 'acquerir': 934 ouvrirSection('monde'); return 935 } 936 } 937 938 function brancher() { 939 document.addEventListener('click', function (e) { 940 var el = cible(e) 941 if (!el) return 942 if (S().workMode !== 'library') return 943 e.preventDefault() 944 agir(el.getAttribute('data-mq'), el.getAttribute('data-mq-val')) 945 }) 946 /* LE CHAMP DE RECHERCHE NE PASSE PAS PAR LE RENDU GÃNÃRAL. Redessiner 947 * l'espace à chaque frappe ferait perdre le curseur une fois par lettre â 948 * c'est le défaut qu'on attribue au clavier. On écrit dans l'état, et on 949 * ne redessine que le résultat. */ 950 document.addEventListener('input', function (e) { 951 if (!e.target || e.target.id !== 'mq-q') return 952 etat().recherche = e.target.value 953 redessiner() 954 var c = document.getElementById('mq-q') 955 if (c) { c.focus(); c.setSelectionRange(c.value.length, c.value.length) } 956 }) 957 } 958 if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', brancher) 959 else brancher() 960 961 window.CREA_MEDIATHEQUE = { 962 rendre: rendre, 963 sections: SECTIONS, 964 familles: FAMILLES,
965 /* LA PORTE QUE L'AUTRE SYSTÃME POUSSE. L'explorateur verse un fichier du 966 * disque dans le catalogue, puis mène ici â c'est son seul droit sur cet 967 * état, et il ne connaît pas la forme du reste. Elle a été retirée une 968 * journée, exportée pour un appelant qui n'était pas encore écrit : le 969 * cliquet des champs morts avait raison de la voir. */ 970 ouvrirSection: ouvrirSection, 971 montrer: montrer, 972 chercher: chercher, 973 compte: compte, 974 } 975})()
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.