feat: dictionaries tab — list, entry count, import, delete

This commit is contained in:
Axelle Abbadie 2026-05-13 11:21:11 +02:00
parent 446c867e17
commit 631803f8f4
3 changed files with 187 additions and 16 deletions

91
main.js
View file

@ -32598,7 +32598,7 @@ var PseudonymizationView = class extends import_obsidian7.ItemView {
else if (tab === "mappings")
await this.renderMappingsTab(pane);
else if (tab === "dictionaries")
this.renderDictionariesTab(pane);
await this.renderDictionariesTab(pane);
else if (tab === "ner")
await this.renderNerTab(pane);
else
@ -32893,15 +32893,90 @@ var PseudonymizationView = class extends import_obsidian7.ItemView {
}
}
// ---- Onglet Dictionnaires --------------------------------------
renderDictionariesTab(el) {
el.createEl("p", {
text: "La gestion avanc\xE9e des dictionnaires (d\xE9tection NER, listes de lieux et d'institutions) sera disponible en Phase 9.",
cls: "pseudobs-view-hint"
async renderDictionariesTab(el) {
const toolbar = el.createDiv("pseudobs-view-toolbar");
const importBtn = toolbar.createEl("button", {
text: "Importer un dictionnaire (.dict.json)",
cls: "pseudobs-view-add-btn"
});
el.createEl("p", {
text: "Utilisez l'outil Coulmont via le menu contextuel (clic droit sur un pr\xE9nom dans la transcription).",
cls: "pseudobs-view-hint"
const statusEl = toolbar.createSpan({ cls: "pseudobs-view-dict-status" });
importBtn.addEventListener("click", () => {
const input = activeDocument.createElement("input");
input.type = "file";
input.accept = ".json";
input.multiple = true;
input.classList.add("pseudobs-hidden-input");
activeDocument.body.appendChild(input);
input.addEventListener("change", () => {
void (async () => {
const files2 = Array.from(input.files ?? []);
input.remove();
if (files2.length === 0)
return;
await this.plugin.ensureFolder(this.plugin.settings.dictionariesFolder);
let ok = 0;
for (const f of files2) {
try {
const text = await f.text();
const parsed = JSON.parse(text);
if (!parsed.entries || !Array.isArray(parsed.entries))
throw new Error("Format invalide");
const dest = `${this.plugin.settings.dictionariesFolder}/${f.name}`;
const existing = this.app.vault.getAbstractFileByPath(dest);
if (existing instanceof import_obsidian7.TFile) {
await this.app.vault.modify(existing, text);
} else {
await this.app.vault.create(dest, text);
}
ok++;
} catch {
new import_obsidian7.Notice(`Format invalide : ${f.name}`);
}
}
if (ok > 0)
new import_obsidian7.Notice(`\u2713 ${ok} dictionnaire${ok > 1 ? "s" : ""} import\xE9${ok > 1 ? "s" : ""}`);
await this.renderTab("dictionaries");
})();
});
input.click();
});
const folder = this.app.vault.getAbstractFileByPath(this.plugin.settings.dictionariesFolder);
const files = [];
if (folder && "children" in folder) {
for (const child of folder.children) {
if (child instanceof import_obsidian7.TFile && child.name.endsWith(".json"))
files.push(child);
}
}
if (files.length === 0) {
el.createEl("p", { text: "Aucun dictionnaire import\xE9.", cls: "pseudobs-view-hint" });
return;
}
el.createEl("p", {
text: `${files.length} dictionnaire${files.length > 1 ? "s" : ""} charg\xE9${files.length > 1 ? "s" : ""} :`,
cls: "pseudobs-view-count"
});
const list = el.createEl("ul", { cls: "pseudobs-dict-list" });
for (const f of files) {
let entryCount = "?";
try {
const raw = await this.app.vault.read(f);
const parsed = JSON.parse(raw);
entryCount = String(parsed.entries?.length ?? 0);
} catch {
}
const li = list.createEl("li", { cls: "pseudobs-dict-item" });
li.createSpan({ text: f.basename, cls: "pseudobs-dict-name" });
li.createSpan({ text: `${entryCount} entr\xE9es`, cls: "pseudobs-dict-count" });
const removeBtn = li.createEl("button", { text: "\u2715", cls: "pseudobs-dict-remove" });
removeBtn.title = "Supprimer ce dictionnaire";
removeBtn.addEventListener("click", () => {
void (async () => {
await this.app.fileManager.trashFile(f);
await this.renderTab("dictionaries");
})();
});
}
}
// ---- Onglet Exports --------------------------------------------
renderExportsTab(el) {

View file

@ -1,4 +1,5 @@
import { ItemView, Notice, Setting, TFile, WorkspaceLeaf } from 'obsidian';
import type { DictionaryFile } from '../types';
import type PseudObsPlugin from '../main';
import type { MappingRule, Occurrence } from '../types';
import { scanOccurrences } from '../scanner/OccurrenceScanner';
@ -124,7 +125,7 @@ export class PseudonymizationView extends ItemView {
pane.empty();
if (tab === 'occurrences') await this.renderOccurrencesTab(pane);
else if (tab === 'mappings') await this.renderMappingsTab(pane);
else if (tab === 'dictionaries') this.renderDictionariesTab(pane);
else if (tab === 'dictionaries') await this.renderDictionariesTab(pane);
else if (tab === 'ner') await this.renderNerTab(pane);
else this.renderExportsTab(pane);
}
@ -457,15 +458,91 @@ export class PseudonymizationView extends ItemView {
// ---- Onglet Dictionnaires --------------------------------------
private renderDictionariesTab(el: HTMLElement): void {
el.createEl('p', {
text: 'La gestion avancée des dictionnaires (détection NER, listes de lieux et d\'institutions) sera disponible en Phase 9.',
cls: 'pseudobs-view-hint',
private async renderDictionariesTab(el: HTMLElement): Promise<void> {
const toolbar = el.createDiv('pseudobs-view-toolbar');
const importBtn = toolbar.createEl('button', {
text: 'Importer un dictionnaire (.dict.json)',
cls: 'pseudobs-view-add-btn',
});
el.createEl('p', {
text: 'Utilisez l\'outil Coulmont via le menu contextuel (clic droit sur un prénom dans la transcription).',
cls: 'pseudobs-view-hint',
const statusEl = toolbar.createSpan({ cls: 'pseudobs-view-dict-status' });
importBtn.addEventListener('click', () => {
const input = activeDocument.createElement('input');
input.type = 'file';
input.accept = '.json';
input.multiple = true;
input.classList.add('pseudobs-hidden-input');
activeDocument.body.appendChild(input);
input.addEventListener('change', () => { void (async () => {
const files = Array.from(input.files ?? []);
input.remove();
if (files.length === 0) return;
await this.plugin.ensureFolder(this.plugin.settings.dictionariesFolder);
let ok = 0;
for (const f of files) {
try {
const text = await f.text();
const parsed = JSON.parse(text) as DictionaryFile;
if (!parsed.entries || !Array.isArray(parsed.entries)) throw new Error('Format invalide');
const dest = `${this.plugin.settings.dictionariesFolder}/${f.name}`;
const existing = this.app.vault.getAbstractFileByPath(dest);
if (existing instanceof TFile) {
await this.app.vault.modify(existing, text);
} else {
await this.app.vault.create(dest, text);
}
ok++;
} catch {
new Notice(`Format invalide : ${f.name}`);
}
}
if (ok > 0) new Notice(`${ok} dictionnaire${ok > 1 ? 's' : ''} importé${ok > 1 ? 's' : ''}`);
await this.renderTab('dictionaries');
})(); });
input.click();
});
// Liste des dictionnaires présents dans le vault
const folder = this.app.vault.getAbstractFileByPath(this.plugin.settings.dictionariesFolder);
const files: TFile[] = [];
if (folder && 'children' in folder) {
for (const child of (folder as { children: unknown[] }).children) {
if (child instanceof TFile && child.name.endsWith('.json')) files.push(child);
}
}
if (files.length === 0) {
el.createEl('p', { text: 'Aucun dictionnaire importé.', cls: 'pseudobs-view-hint' });
return;
}
el.createEl('p', {
text: `${files.length} dictionnaire${files.length > 1 ? 's' : ''} chargé${files.length > 1 ? 's' : ''} :`,
cls: 'pseudobs-view-count',
});
const list = el.createEl('ul', { cls: 'pseudobs-dict-list' });
for (const f of files) {
let entryCount = '?';
try {
const raw = await this.app.vault.read(f);
const parsed = JSON.parse(raw) as DictionaryFile;
entryCount = String(parsed.entries?.length ?? 0);
} catch { /* fichier malformé */ }
const li = list.createEl('li', { cls: 'pseudobs-dict-item' });
li.createSpan({ text: f.basename, cls: 'pseudobs-dict-name' });
li.createSpan({ text: `${entryCount} entrées`, cls: 'pseudobs-dict-count' });
const removeBtn = li.createEl('button', { text: '✕', cls: 'pseudobs-dict-remove' });
removeBtn.title = 'Supprimer ce dictionnaire';
removeBtn.addEventListener('click', () => { void (async () => {
await this.app.fileManager.trashFile(f);
await this.renderTab('dictionaries');
})(); });
}
void statusEl;
}
// ---- Onglet Exports --------------------------------------------

View file

@ -580,3 +580,22 @@
.pseudobs-view-add-btn::before { content: '+ '; }
.pseudobs-btn-saved::before { content: '✓ '; color: var(--color-green); }
.pseudobs-ner-reset-btn { margin-left: 8px; }
/* Onglet Dictionnaires — panneau latéral */
.pseudobs-dict-list { list-style: none; padding: 0; margin: 6px 0; }
.pseudobs-dict-item {
display: flex;
align-items: center;
gap: 8px;
padding: 5px 6px;
border-radius: 4px;
font-size: 0.85em;
}
.pseudobs-dict-item:hover { background: var(--background-secondary); }
.pseudobs-dict-name { flex: 1; font-family: var(--font-monospace); }
.pseudobs-dict-count { color: var(--text-muted); font-size: 0.8em; white-space: nowrap; }
.pseudobs-dict-remove {
background: none; border: none; cursor: pointer;
color: var(--text-faint); padding: 0 4px;
}
.pseudobs-dict-remove:hover { color: var(--color-red); }