Add image-library dedupe and untagged filter, UI text scaling, table paste
Image library:
- "Clean up duplicates" finds byte-identical images by md5, keeps one
file per group (preferring the most-used, then the oldest), merges
the tags/descriptions and captions of the copies, repoints slides in
open decks and in .md presentations on disk, and deletes the copies
after a confirmation that lists every group.
- A header toggle filters to images without tags/description, so it is
easy to see which ones still need attention.
- The delete warning now also lists presentations on disk that still
reference the image (marked "not open"), next to the open decks.
Editor and accessibility (already in tree):
- Interface text scaling up to 200%, keyboard-operable panel divider,
keyboard-first add-slide dialog, and screen-reader improvements.
- Paste a spreadsheet/CSV/markdown selection into a table cell to fill
the whole grid.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:36:44 +02:00
|
|
|
import 'dart:io';
|
|
|
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
|
import 'package:path/path.dart' as p;
|
2026-06-11 22:16:39 +02:00
|
|
|
import '../utils/log.dart';
|
Add image-library dedupe and untagged filter, UI text scaling, table paste
Image library:
- "Clean up duplicates" finds byte-identical images by md5, keeps one
file per group (preferring the most-used, then the oldest), merges
the tags/descriptions and captions of the copies, repoints slides in
open decks and in .md presentations on disk, and deletes the copies
after a confirmation that lists every group.
- A header toggle filters to images without tags/description, so it is
easy to see which ones still need attention.
- The delete warning now also lists presentations on disk that still
reference the image (marked "not open"), next to the open decks.
Editor and accessibility (already in tree):
- Interface text scaling up to 200%, keyboard-operable panel divider,
keyboard-first add-slide dialog, and screen-reader improvements.
- Paste a spreadsheet/CSV/markdown selection into a table cell to fill
the whole grid.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:36:44 +02:00
|
|
|
|
|
|
|
|
/// Vindt en herschrijft afbeeldingsverwijzingen (``) in
|
|
|
|
|
/// Marp-markdownbestanden op schijf. Zo gaan bij het opruimen van duplicaten
|
|
|
|
|
/// ook presentaties mee die nu niet geopend zijn.
|
|
|
|
|
class ImageReferenceService {
|
|
|
|
|
/// Zelfde mappen als FileService.scanPresentations overslaat.
|
|
|
|
|
static const _ignoredDirs = {
|
|
|
|
|
'images',
|
|
|
|
|
'logos',
|
|
|
|
|
'themes',
|
|
|
|
|
'node_modules',
|
|
|
|
|
'build',
|
|
|
|
|
'.git',
|
|
|
|
|
'.dart_tool',
|
|
|
|
|
};
|
|
|
|
|
static const _maxDepth = 4;
|
|
|
|
|
|
|
|
|
|
/// Markdown-afbeelding: ``.
|
|
|
|
|
static final _imageRef = RegExp(r'!\[([^\]]*)\]\(([^)\n]+)\)');
|
|
|
|
|
|
|
|
|
|
/// Zoek recursief alle `.md`-bestanden onder [searchDirs] (begrensd op
|
|
|
|
|
/// diepte, asset- en verborgen mappen worden overgeslagen). Dubbele treffers
|
|
|
|
|
/// via overlappende zoekpaden worden één keer teruggegeven.
|
|
|
|
|
Future<List<String>> findDeckFiles(Iterable<String> searchDirs) async {
|
|
|
|
|
final found = <String>{};
|
|
|
|
|
|
|
|
|
|
Future<void> walk(Directory dir, int depth) async {
|
|
|
|
|
List<FileSystemEntity> entries;
|
|
|
|
|
try {
|
|
|
|
|
entries = await dir.list(followLinks: false).toList();
|
2026-06-11 22:16:39 +02:00
|
|
|
} catch (e) {
|
|
|
|
|
logWarning('ImageReferenceService.findDeckFiles: list directory', e);
|
Add image-library dedupe and untagged filter, UI text scaling, table paste
Image library:
- "Clean up duplicates" finds byte-identical images by md5, keeps one
file per group (preferring the most-used, then the oldest), merges
the tags/descriptions and captions of the copies, repoints slides in
open decks and in .md presentations on disk, and deletes the copies
after a confirmation that lists every group.
- A header toggle filters to images without tags/description, so it is
easy to see which ones still need attention.
- The delete warning now also lists presentations on disk that still
reference the image (marked "not open"), next to the open decks.
Editor and accessibility (already in tree):
- Interface text scaling up to 200%, keyboard-operable panel divider,
keyboard-first add-slide dialog, and screen-reader improvements.
- Paste a spreadsheet/CSV/markdown selection into a table cell to fill
the whole grid.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:36:44 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
for (final entity in entries) {
|
|
|
|
|
if (entity is File) {
|
|
|
|
|
if (entity.path.toLowerCase().endsWith('.md')) {
|
|
|
|
|
found.add(p.normalize(entity.path));
|
|
|
|
|
}
|
|
|
|
|
} else if (entity is Directory && depth < _maxDepth) {
|
|
|
|
|
final name = p.basename(entity.path);
|
|
|
|
|
if (_ignoredDirs.contains(name) || name.startsWith('.')) continue;
|
|
|
|
|
await walk(entity, depth + 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (final dirPath in searchDirs) {
|
|
|
|
|
if (dirPath.isEmpty) continue;
|
|
|
|
|
final root = Directory(dirPath);
|
|
|
|
|
if (!root.existsSync()) continue;
|
|
|
|
|
await walk(root, 0);
|
|
|
|
|
}
|
|
|
|
|
return found.toList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Tel per pad uit [targets] hoe vaak het in [deckFiles] wordt genoemd.
|
|
|
|
|
/// Paden in de markdown worden opgelost relatief aan de map van het
|
|
|
|
|
/// `.md`-bestand. Paden zonder verwijzingen ontbreken in het resultaat.
|
|
|
|
|
Future<Map<String, int>> countReferences(
|
|
|
|
|
Iterable<String> deckFiles,
|
|
|
|
|
Iterable<String> targets,
|
|
|
|
|
) async {
|
|
|
|
|
final wanted = {for (final t in targets) p.normalize(t)};
|
|
|
|
|
final counts = <String, int>{};
|
|
|
|
|
for (final deckFile in deckFiles) {
|
|
|
|
|
String content;
|
|
|
|
|
try {
|
|
|
|
|
content = await File(deckFile).readAsString();
|
2026-06-11 22:16:39 +02:00
|
|
|
} catch (e) {
|
|
|
|
|
logWarning('ImageReferenceService.countReferences: read deck file', e);
|
Add image-library dedupe and untagged filter, UI text scaling, table paste
Image library:
- "Clean up duplicates" finds byte-identical images by md5, keeps one
file per group (preferring the most-used, then the oldest), merges
the tags/descriptions and captions of the copies, repoints slides in
open decks and in .md presentations on disk, and deletes the copies
after a confirmation that lists every group.
- A header toggle filters to images without tags/description, so it is
easy to see which ones still need attention.
- The delete warning now also lists presentations on disk that still
reference the image (marked "not open"), next to the open decks.
Editor and accessibility (already in tree):
- Interface text scaling up to 200%, keyboard-operable panel divider,
keyboard-first add-slide dialog, and screen-reader improvements.
- Paste a spreadsheet/CSV/markdown selection into a table cell to fill
the whole grid.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:36:44 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
final mdDir = p.dirname(deckFile);
|
|
|
|
|
for (final match in _imageRef.allMatches(content)) {
|
|
|
|
|
final resolved = _resolve(match.group(2)!, mdDir);
|
|
|
|
|
if (resolved == null) continue;
|
|
|
|
|
for (final target in wanted) {
|
|
|
|
|
if (p.equals(resolved, target)) {
|
|
|
|
|
counts[target] = (counts[target] ?? 0) + 1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return counts;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Per deckbestand: hoe vaak [target] erin wordt genoemd. Bestanden zonder
|
|
|
|
|
/// treffer ontbreken in het resultaat. Gebruikt voor de waarschuwing bij
|
|
|
|
|
/// verwijderen, zodat ook niet-geopende presentaties zichtbaar zijn.
|
|
|
|
|
Future<Map<String, int>> referencingFiles(
|
|
|
|
|
Iterable<String> deckFiles,
|
|
|
|
|
String target,
|
|
|
|
|
) async {
|
|
|
|
|
final wanted = p.normalize(target);
|
|
|
|
|
final result = <String, int>{};
|
|
|
|
|
for (final deckFile in deckFiles) {
|
|
|
|
|
String content;
|
|
|
|
|
try {
|
|
|
|
|
content = await File(deckFile).readAsString();
|
2026-06-11 22:16:39 +02:00
|
|
|
} catch (e) {
|
|
|
|
|
logWarning('ImageReferenceService.referencingFiles: read deck file', e);
|
Add image-library dedupe and untagged filter, UI text scaling, table paste
Image library:
- "Clean up duplicates" finds byte-identical images by md5, keeps one
file per group (preferring the most-used, then the oldest), merges
the tags/descriptions and captions of the copies, repoints slides in
open decks and in .md presentations on disk, and deletes the copies
after a confirmation that lists every group.
- A header toggle filters to images without tags/description, so it is
easy to see which ones still need attention.
- The delete warning now also lists presentations on disk that still
reference the image (marked "not open"), next to the open decks.
Editor and accessibility (already in tree):
- Interface text scaling up to 200%, keyboard-operable panel divider,
keyboard-first add-slide dialog, and screen-reader improvements.
- Paste a spreadsheet/CSV/markdown selection into a table cell to fill
the whole grid.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:36:44 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
final mdDir = p.dirname(deckFile);
|
|
|
|
|
var count = 0;
|
|
|
|
|
for (final match in _imageRef.allMatches(content)) {
|
|
|
|
|
final resolved = _resolve(match.group(2)!, mdDir);
|
|
|
|
|
if (resolved != null && p.equals(resolved, wanted)) count++;
|
|
|
|
|
}
|
|
|
|
|
if (count > 0) result[deckFile] = count;
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Herschrijf in [deckFile] elke verwijzing naar [fromAbsolute] zodat die
|
|
|
|
|
/// naar [toAbsolute] wijst. Alleen het pad binnen `` verandert; de
|
|
|
|
|
/// rest van het bestand blijft byte-voor-byte gelijk. Geeft true terug
|
|
|
|
|
/// wanneer het bestand daadwerkelijk is gewijzigd.
|
|
|
|
|
Future<bool> replaceReferences(
|
|
|
|
|
String deckFile,
|
|
|
|
|
String fromAbsolute,
|
|
|
|
|
String toAbsolute,
|
|
|
|
|
) async {
|
|
|
|
|
final file = File(deckFile);
|
|
|
|
|
String content;
|
|
|
|
|
try {
|
|
|
|
|
content = await file.readAsString();
|
2026-06-11 22:16:39 +02:00
|
|
|
} catch (e) {
|
|
|
|
|
logWarning('ImageReferenceService.replaceReferences: read deck file', e);
|
Add image-library dedupe and untagged filter, UI text scaling, table paste
Image library:
- "Clean up duplicates" finds byte-identical images by md5, keeps one
file per group (preferring the most-used, then the oldest), merges
the tags/descriptions and captions of the copies, repoints slides in
open decks and in .md presentations on disk, and deletes the copies
after a confirmation that lists every group.
- A header toggle filters to images without tags/description, so it is
easy to see which ones still need attention.
- The delete warning now also lists presentations on disk that still
reference the image (marked "not open"), next to the open decks.
Editor and accessibility (already in tree):
- Interface text scaling up to 200%, keyboard-operable panel divider,
keyboard-first add-slide dialog, and screen-reader improvements.
- Paste a spreadsheet/CSV/markdown selection into a table cell to fill
the whole grid.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:36:44 +02:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
final mdDir = p.dirname(deckFile);
|
|
|
|
|
var changed = false;
|
|
|
|
|
final updated = content.replaceAllMapped(_imageRef, (m) {
|
|
|
|
|
final ref = m.group(2)!;
|
|
|
|
|
final resolved = _resolve(ref, mdDir);
|
|
|
|
|
if (resolved == null || !p.equals(resolved, fromAbsolute)) {
|
|
|
|
|
return m.group(0)!;
|
|
|
|
|
}
|
|
|
|
|
changed = true;
|
|
|
|
|
// Blijf relatief schrijven als de verwijzing dat al was en het nieuwe
|
|
|
|
|
// pad binnen de projectmap ligt; anders absoluut.
|
|
|
|
|
final replacement =
|
|
|
|
|
!p.isAbsolute(ref.trim()) && p.isWithin(mdDir, toAbsolute)
|
|
|
|
|
? p.relative(toAbsolute, from: mdDir)
|
|
|
|
|
: toAbsolute;
|
|
|
|
|
return '';
|
|
|
|
|
});
|
|
|
|
|
if (!changed) return false;
|
|
|
|
|
try {
|
|
|
|
|
await file.writeAsString(updated);
|
2026-06-11 22:16:39 +02:00
|
|
|
} catch (e) {
|
|
|
|
|
logWarning('ImageReferenceService.replaceReferences: write deck file', e);
|
Add image-library dedupe and untagged filter, UI text scaling, table paste
Image library:
- "Clean up duplicates" finds byte-identical images by md5, keeps one
file per group (preferring the most-used, then the oldest), merges
the tags/descriptions and captions of the copies, repoints slides in
open decks and in .md presentations on disk, and deletes the copies
after a confirmation that lists every group.
- A header toggle filters to images without tags/description, so it is
easy to see which ones still need attention.
- The delete warning now also lists presentations on disk that still
reference the image (marked "not open"), next to the open decks.
Editor and accessibility (already in tree):
- Interface text scaling up to 200%, keyboard-operable panel divider,
keyboard-first add-slide dialog, and screen-reader improvements.
- Paste a spreadsheet/CSV/markdown selection into a table cell to fill
the whole grid.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:36:44 +02:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String? _resolve(String ref, String mdDir) {
|
|
|
|
|
final cleaned = ref.trim();
|
|
|
|
|
if (cleaned.isEmpty || cleaned.contains('://')) return null;
|
2026-06-11 22:16:39 +02:00
|
|
|
return p.normalize(
|
|
|
|
|
p.isAbsolute(cleaned) ? cleaned : p.join(mdDir, cleaned),
|
|
|
|
|
);
|
Add image-library dedupe and untagged filter, UI text scaling, table paste
Image library:
- "Clean up duplicates" finds byte-identical images by md5, keeps one
file per group (preferring the most-used, then the oldest), merges
the tags/descriptions and captions of the copies, repoints slides in
open decks and in .md presentations on disk, and deletes the copies
after a confirmation that lists every group.
- A header toggle filters to images without tags/description, so it is
easy to see which ones still need attention.
- The delete warning now also lists presentations on disk that still
reference the image (marked "not open"), next to the open decks.
Editor and accessibility (already in tree):
- Interface text scaling up to 200%, keyboard-operable panel divider,
keyboard-first add-slide dialog, and screen-reader improvements.
- Paste a spreadsheet/CSV/markdown selection into a table cell to fill
the whole grid.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:36:44 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final imageReferenceServiceProvider = Provider<ImageReferenceService>(
|
|
|
|
|
(_) => ImageReferenceService(),
|
|
|
|
|
);
|