Add per-slide TLP classification with sharing-level filtering
Each slide can now carry its own Traffic Light Protocol level. When the presentation is shared at a given TLP level, slides classified stricter than that level are withheld, so the same deck can be shown safely to audiences with different clearances. - Slide.tlp field with markdown round-trip via a <!-- tlp: <key> --> marker (also on code slides). - Editor: a per-slide "TLP van deze slide" dropdown. - Central rule slideVisibleAtTlp() compares levels on the TLP severity order (none < CLEAR < GREEN < AMBER < AMBER+STRICT < RED). - Filtering lives in _slidesForPresentationOrExport, the single source of slides for presenting (single-window and dual-screen) and for every export (PDF, PPTX, HTML), so all paths honour it. - Translations for the new strings in all supported languages, plus tests for the round-trip and the visibility rule. flutter analyze is clean and all tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
ffcda70966
commit
d1862935ab
8 changed files with 164 additions and 2 deletions
|
|
@ -1174,6 +1174,7 @@ const _dutchSourceStrings = {
|
||||||
'Vrije Markdown': 'Markdown libero',
|
'Vrije Markdown': 'Markdown libero',
|
||||||
'Broncode': 'Codice sorgente',
|
'Broncode': 'Codice sorgente',
|
||||||
'Programmeertaal': 'Linguaggio di programmazione',
|
'Programmeertaal': 'Linguaggio di programmazione',
|
||||||
|
'TLP van deze slide': 'TLP di questa slide',
|
||||||
'Plak of typ hier je broncode...':
|
'Plak of typ hier je broncode...':
|
||||||
'Incolla o digita qui il tuo codice sorgente...',
|
'Incolla o digita qui il tuo codice sorgente...',
|
||||||
'Overgeslagen': 'Saltata',
|
'Overgeslagen': 'Saltata',
|
||||||
|
|
@ -1345,6 +1346,7 @@ const _dutchSourceStrings = {
|
||||||
'Vrije Markdown': 'Freies Markdown',
|
'Vrije Markdown': 'Freies Markdown',
|
||||||
'Broncode': 'Quellcode',
|
'Broncode': 'Quellcode',
|
||||||
'Programmeertaal': 'Programmiersprache',
|
'Programmeertaal': 'Programmiersprache',
|
||||||
|
'TLP van deze slide': 'TLP dieser Folie',
|
||||||
'Plak of typ hier je broncode...':
|
'Plak of typ hier je broncode...':
|
||||||
'Quellcode hier einfügen oder eingeben...',
|
'Quellcode hier einfügen oder eingeben...',
|
||||||
'Overgeslagen': 'Übersprungen',
|
'Overgeslagen': 'Übersprungen',
|
||||||
|
|
@ -1517,6 +1519,7 @@ const _dutchSourceStrings = {
|
||||||
'Vrije Markdown': 'Markdown libre',
|
'Vrije Markdown': 'Markdown libre',
|
||||||
'Broncode': 'Code source',
|
'Broncode': 'Code source',
|
||||||
'Programmeertaal': 'Langage de programmation',
|
'Programmeertaal': 'Langage de programmation',
|
||||||
|
'TLP van deze slide': 'TLP de cette diapositive',
|
||||||
'Plak of typ hier je broncode...':
|
'Plak of typ hier je broncode...':
|
||||||
'Collez ou tapez votre code source ici...',
|
'Collez ou tapez votre code source ici...',
|
||||||
'Overgeslagen': 'Ignorée',
|
'Overgeslagen': 'Ignorée',
|
||||||
|
|
@ -1688,6 +1691,7 @@ const _dutchSourceStrings = {
|
||||||
'Vrije Markdown': 'Markdown libre',
|
'Vrije Markdown': 'Markdown libre',
|
||||||
'Broncode': 'Código fuente',
|
'Broncode': 'Código fuente',
|
||||||
'Programmeertaal': 'Lenguaje de programación',
|
'Programmeertaal': 'Lenguaje de programación',
|
||||||
|
'TLP van deze slide': 'TLP de esta diapositiva',
|
||||||
'Plak of typ hier je broncode...':
|
'Plak of typ hier je broncode...':
|
||||||
'Pega o escribe aquí tu código fuente...',
|
'Pega o escribe aquí tu código fuente...',
|
||||||
'Overgeslagen': 'Omitida',
|
'Overgeslagen': 'Omitida',
|
||||||
|
|
@ -1860,6 +1864,7 @@ const _dutchSourceStrings = {
|
||||||
'Vrije Markdown': 'Frije Markdown',
|
'Vrije Markdown': 'Frije Markdown',
|
||||||
'Broncode': 'Boarnekoade',
|
'Broncode': 'Boarnekoade',
|
||||||
'Programmeertaal': 'Programmeartaal',
|
'Programmeertaal': 'Programmeartaal',
|
||||||
|
'TLP van deze slide': 'TLP fan dizze slide',
|
||||||
'Plak of typ hier je broncode...': 'Plak of typ hjir dyn boarnekoade...',
|
'Plak of typ hier je broncode...': 'Plak of typ hjir dyn boarnekoade...',
|
||||||
'Overgeslagen': 'Oerslein',
|
'Overgeslagen': 'Oerslein',
|
||||||
'Kopiëren': 'Kopiearje',
|
'Kopiëren': 'Kopiearje',
|
||||||
|
|
@ -2032,6 +2037,7 @@ const _dutchSourceStrings = {
|
||||||
'Vrije Markdown': 'Markdown liber',
|
'Vrije Markdown': 'Markdown liber',
|
||||||
'Broncode': 'Código fuente',
|
'Broncode': 'Código fuente',
|
||||||
'Programmeertaal': 'Lenguahe di programashon',
|
'Programmeertaal': 'Lenguahe di programashon',
|
||||||
|
'TLP van deze slide': 'TLP di e slide aki',
|
||||||
'Plak of typ hier je broncode...': 'Pega òf tek bo código fuente akinan...',
|
'Plak of typ hier je broncode...': 'Pega òf tek bo código fuente akinan...',
|
||||||
'Overgeslagen': 'Saltá',
|
'Overgeslagen': 'Saltá',
|
||||||
'Kopiëren': 'Kopia',
|
'Kopiëren': 'Kopia',
|
||||||
|
|
@ -2193,6 +2199,7 @@ const _dutchSourceStringAdditions = {
|
||||||
'Bullet': 'Bullet',
|
'Bullet': 'Bullet',
|
||||||
'Plak of typ hier je broncode...': 'Paste or type your source code here...',
|
'Plak of typ hier je broncode...': 'Paste or type your source code here...',
|
||||||
'Programmeertaal': 'Programming language',
|
'Programmeertaal': 'Programming language',
|
||||||
|
'TLP van deze slide': 'TLP of this slide',
|
||||||
'Platte tekst': 'Plain text',
|
'Platte tekst': 'Plain text',
|
||||||
'Titel (optioneel)': 'Title (optional)',
|
'Titel (optioneel)': 'Title (optional)',
|
||||||
'HTML opent in elke browser zonder internet en rendert codeblokken, wiskunde en mermaid-diagrammen.':
|
'HTML opent in elke browser zonder internet en rendert codeblokken, wiskunde en mermaid-diagrammen.':
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,17 @@ import 'slide.dart';
|
||||||
import 'settings.dart';
|
import 'settings.dart';
|
||||||
|
|
||||||
/// Traffic Light Protocol-classificatie (FIRST TLP 2.0) van een presentatie.
|
/// Traffic Light Protocol-classificatie (FIRST TLP 2.0) van een presentatie.
|
||||||
|
///
|
||||||
|
/// De volgorde loopt van minst naar meest beperkend; [TlpLevel.index] is dus
|
||||||
|
/// bruikbaar om niveaus te vergelijken.
|
||||||
enum TlpLevel { none, clear, green, amber, amberStrict, red }
|
enum TlpLevel { none, clear, green, amber, amberStrict, red }
|
||||||
|
|
||||||
|
/// Of [slide] getoond mag worden wanneer de presentatie op [presentationTlp]
|
||||||
|
/// wordt gedeeld. Een slide wordt achtergehouden zodra zijn eigen TLP-niveau
|
||||||
|
/// strenger (hoger) is dan het voor de presentatie gekozen niveau.
|
||||||
|
bool slideVisibleAtTlp(Slide slide, TlpLevel presentationTlp) =>
|
||||||
|
slide.tlp.index <= presentationTlp.index;
|
||||||
|
|
||||||
extension TlpLevelX on TlpLevel {
|
extension TlpLevelX on TlpLevel {
|
||||||
/// De officiële markering die op de slides verschijnt ('' bij [none]).
|
/// De officiële markering die op de slides verschijnt ('' bij [none]).
|
||||||
String get label {
|
String get label {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
import 'deck.dart';
|
||||||
|
|
||||||
const _uuid = Uuid();
|
const _uuid = Uuid();
|
||||||
|
|
||||||
|
|
@ -104,6 +105,10 @@ class Slide {
|
||||||
final bool showLogo; // show the profile logo on this slide (default true)
|
final bool showLogo; // show the profile logo on this slide (default true)
|
||||||
final bool showFooter; // show the profile footer on this slide (default true)
|
final bool showFooter; // show the profile footer on this slide (default true)
|
||||||
final bool skipped; // skip this slide when presenting and exporting
|
final bool skipped; // skip this slide when presenting and exporting
|
||||||
|
/// Per-slide Traffic Light Protocol classification. The slide is withheld
|
||||||
|
/// when the presentation is shared at a lower (less restrictive) level than
|
||||||
|
/// this. [TlpLevel.none] = no per-slide restriction (always shown).
|
||||||
|
final TlpLevel tlp;
|
||||||
final List<List<String>> tableRows; // first row is the header
|
final List<List<String>> tableRows; // first row is the header
|
||||||
|
|
||||||
const Slide({
|
const Slide({
|
||||||
|
|
@ -132,6 +137,7 @@ class Slide {
|
||||||
this.showLogo = true,
|
this.showLogo = true,
|
||||||
this.showFooter = true,
|
this.showFooter = true,
|
||||||
this.skipped = false,
|
this.skipped = false,
|
||||||
|
this.tlp = TlpLevel.none,
|
||||||
this.tableRows = const [],
|
this.tableRows = const [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -184,6 +190,7 @@ class Slide {
|
||||||
showLogo: src.showLogo,
|
showLogo: src.showLogo,
|
||||||
showFooter: src.showFooter,
|
showFooter: src.showFooter,
|
||||||
skipped: src.skipped,
|
skipped: src.skipped,
|
||||||
|
tlp: src.tlp,
|
||||||
tableRows: src.tableRows.map((r) => List<String>.from(r)).toList(),
|
tableRows: src.tableRows.map((r) => List<String>.from(r)).toList(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -213,6 +220,7 @@ class Slide {
|
||||||
bool? showLogo,
|
bool? showLogo,
|
||||||
bool? showFooter,
|
bool? showFooter,
|
||||||
bool? skipped,
|
bool? skipped,
|
||||||
|
TlpLevel? tlp,
|
||||||
List<List<String>>? tableRows,
|
List<List<String>>? tableRows,
|
||||||
}) {
|
}) {
|
||||||
return Slide(
|
return Slide(
|
||||||
|
|
@ -241,6 +249,7 @@ class Slide {
|
||||||
showLogo: showLogo ?? this.showLogo,
|
showLogo: showLogo ?? this.showLogo,
|
||||||
showFooter: showFooter ?? this.showFooter,
|
showFooter: showFooter ?? this.showFooter,
|
||||||
skipped: skipped ?? this.skipped,
|
skipped: skipped ?? this.skipped,
|
||||||
|
tlp: tlp ?? this.tlp,
|
||||||
tableRows: tableRows ?? this.tableRows,
|
tableRows: tableRows ?? this.tableRows,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -354,6 +354,13 @@ class MarkdownService {
|
||||||
buf.writeln('<!-- skip -->');
|
buf.writeln('<!-- skip -->');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Per-slide TLP classification (used to withhold the slide when sharing at
|
||||||
|
// a lower level). Persisted so it survives save/load round-trips.
|
||||||
|
if (slide.tlp != TlpLevel.none) {
|
||||||
|
buf.writeln();
|
||||||
|
buf.writeln('<!-- tlp: ${slide.tlp.key} -->');
|
||||||
|
}
|
||||||
|
|
||||||
if (slide.notes.isNotEmpty) {
|
if (slide.notes.isNotEmpty) {
|
||||||
buf.writeln();
|
buf.writeln();
|
||||||
buf.writeln('<!--');
|
buf.writeln('<!--');
|
||||||
|
|
@ -597,6 +604,7 @@ class MarkdownService {
|
||||||
final notesBuffer = StringBuffer();
|
final notesBuffer = StringBuffer();
|
||||||
double advanceDuration = 0;
|
double advanceDuration = 0;
|
||||||
bool skipped = false;
|
bool skipped = false;
|
||||||
|
TlpLevel slideTlp = TlpLevel.none;
|
||||||
final bullets = <String>[];
|
final bullets = <String>[];
|
||||||
var bullets2 = <String>[];
|
var bullets2 = <String>[];
|
||||||
// bulletsImage slides store their panel width in `<!-- _style:
|
// bulletsImage slides store their panel width in `<!-- _style:
|
||||||
|
|
@ -610,6 +618,8 @@ class MarkdownService {
|
||||||
advanceDuration = double.tryParse(content.substring(8).trim()) ?? 0;
|
advanceDuration = double.tryParse(content.substring(8).trim()) ?? 0;
|
||||||
} else if (content == 'skip') {
|
} else if (content == 'skip') {
|
||||||
skipped = true;
|
skipped = true;
|
||||||
|
} else if (content.startsWith('tlp:')) {
|
||||||
|
slideTlp = TlpLevelX.fromKey(content.substring(4));
|
||||||
} else if (content.startsWith('_style:')) {
|
} else if (content.startsWith('_style:')) {
|
||||||
final w = RegExp(r'--image-width:\s*(\d+)%').firstMatch(content);
|
final w = RegExp(r'--image-width:\s*(\d+)%').firstMatch(content);
|
||||||
if (w != null) styleImageWidth = int.tryParse(w.group(1)!) ?? 0;
|
if (w != null) styleImageWidth = int.tryParse(w.group(1)!) ?? 0;
|
||||||
|
|
@ -636,6 +646,7 @@ class MarkdownService {
|
||||||
notes: notes,
|
notes: notes,
|
||||||
advanceDuration: advanceDuration,
|
advanceDuration: advanceDuration,
|
||||||
skipped: skipped,
|
skipped: skipped,
|
||||||
|
tlp: slideTlp,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -820,6 +831,7 @@ class MarkdownService {
|
||||||
showLogo: showLogo,
|
showLogo: showLogo,
|
||||||
showFooter: showFooter,
|
showFooter: showFooter,
|
||||||
skipped: skipped,
|
skipped: skipped,
|
||||||
|
tlp: slideTlp,
|
||||||
tableRows: type == SlideType.table ? tableRows : const [],
|
tableRows: type == SlideType.table ? tableRows : const [],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -832,6 +844,7 @@ class MarkdownService {
|
||||||
required String notes,
|
required String notes,
|
||||||
required double advanceDuration,
|
required double advanceDuration,
|
||||||
required bool skipped,
|
required bool skipped,
|
||||||
|
TlpLevel tlp = TlpLevel.none,
|
||||||
}) {
|
}) {
|
||||||
final lines = remaining.split('\n');
|
final lines = remaining.split('\n');
|
||||||
String title = '';
|
String title = '';
|
||||||
|
|
@ -892,6 +905,7 @@ class MarkdownService {
|
||||||
showLogo: !classTokens.contains('no-logo'),
|
showLogo: !classTokens.contains('no-logo'),
|
||||||
showFooter: !classTokens.contains('no-footer'),
|
showFooter: !classTokens.contains('no-footer'),
|
||||||
skipped: skipped,
|
skipped: skipped,
|
||||||
|
tlp: tlp,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,11 @@ List<String> _imageUsages(WidgetRef ref, String absolutePath) {
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Slide> _slidesForPresentationOrExport(Deck deck) {
|
List<Slide> _slidesForPresentationOrExport(Deck deck) {
|
||||||
final slides = deck.slides.where((s) => !s.skipped).toList();
|
// Drop skipped slides and slides whose TLP classification is stricter than
|
||||||
|
// the level chosen for this presentation/export.
|
||||||
|
final slides = deck.slides
|
||||||
|
.where((s) => !s.skipped && slideVisibleAtTlp(s, deck.tlp))
|
||||||
|
.toList();
|
||||||
final closingMarkdown = deck.themeProfile.closingSlideMarkdown.trim();
|
final closingMarkdown = deck.themeProfile.closingSlideMarkdown.trim();
|
||||||
if (deck.themeProfile.closingSlideEnabled && closingMarkdown.isNotEmpty) {
|
if (deck.themeProfile.closingSlideEnabled && closingMarkdown.isNotEmpty) {
|
||||||
slides.add(
|
slides.add(
|
||||||
|
|
@ -947,7 +951,9 @@ class _MainLayoutState extends ConsumerState<_MainLayout> {
|
||||||
// zichtbare slide vertalen.
|
// zichtbare slide vertalen.
|
||||||
final visible = <int>[
|
final visible = <int>[
|
||||||
for (var i = 0; i < deck.slides.length; i++)
|
for (var i = 0; i < deck.slides.length; i++)
|
||||||
if (!deck.slides[i].skipped) i,
|
if (!deck.slides[i].skipped &&
|
||||||
|
slideVisibleAtTlp(deck.slides[i], deck.tlp))
|
||||||
|
i,
|
||||||
];
|
];
|
||||||
final slides = _slidesForPresentationOrExport(deck);
|
final slides = _slidesForPresentationOrExport(deck);
|
||||||
if (slides.isEmpty) {
|
if (slides.isEmpty) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import '../../models/deck.dart';
|
||||||
import '../../models/settings.dart';
|
import '../../models/settings.dart';
|
||||||
import '../../models/slide.dart';
|
import '../../models/slide.dart';
|
||||||
import '../../services/image_service.dart';
|
import '../../services/image_service.dart';
|
||||||
|
|
@ -126,6 +127,8 @@ class EditorPanel extends ConsumerWidget {
|
||||||
const Divider(height: 1),
|
const Divider(height: 1),
|
||||||
_SlideTimingControl(slide: slide, onUpdate: update),
|
_SlideTimingControl(slide: slide, onUpdate: update),
|
||||||
const Divider(height: 1),
|
const Divider(height: 1),
|
||||||
|
_SlideTlpControl(slide: slide, onUpdate: update),
|
||||||
|
const Divider(height: 1),
|
||||||
_NotesField(slide: slide, onUpdate: update),
|
_NotesField(slide: slide, onUpdate: update),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -174,6 +177,7 @@ class EditorPanel extends ConsumerWidget {
|
||||||
imageSize: slide.imageSize,
|
imageSize: slide.imageSize,
|
||||||
showLogo: slide.showLogo,
|
showLogo: slide.showLogo,
|
||||||
showFooter: slide.showFooter,
|
showFooter: slide.showFooter,
|
||||||
|
tlp: slide.tlp,
|
||||||
tableRows: newType == SlideType.table
|
tableRows: newType == SlideType.table
|
||||||
? (slide.tableRows.isNotEmpty
|
? (slide.tableRows.isNotEmpty
|
||||||
? slide.tableRows
|
? slide.tableRows
|
||||||
|
|
@ -660,6 +664,57 @@ class _SlideFooterControl extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Per-slide TLP-classificatie ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
class _SlideTlpControl extends StatelessWidget {
|
||||||
|
final Slide slide;
|
||||||
|
final ValueChanged<Slide> onUpdate;
|
||||||
|
const _SlideTlpControl({required this.slide, required this.onUpdate});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final l10n = context.l10n;
|
||||||
|
return Container(
|
||||||
|
color: const Color(0xFFF8FAFC),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 7),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.shield_outlined, size: 14, color: Color(0xFF64748B)),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
l10n.d('TLP van deze slide'),
|
||||||
|
style: const TextStyle(fontSize: 12, color: Color(0xFF475569)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DropdownButtonHideUnderline(
|
||||||
|
child: DropdownButton<TlpLevel>(
|
||||||
|
value: slide.tlp,
|
||||||
|
isDense: true,
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
style: const TextStyle(fontSize: 12, color: Color(0xFF0F172A)),
|
||||||
|
items: [
|
||||||
|
for (final level in TlpLevel.values)
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: level,
|
||||||
|
child: Text(
|
||||||
|
level == TlpLevel.none
|
||||||
|
? l10n.d('Geen')
|
||||||
|
: level.menuLabel,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onChanged: (v) {
|
||||||
|
if (v != null) onUpdate(slide.copyWith(tlp: v));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Speakernotes veld ─────────────────────────────────────────────────────────
|
// ── Speakernotes veld ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
class _NotesField extends StatefulWidget {
|
class _NotesField extends StatefulWidget {
|
||||||
|
|
|
||||||
|
|
@ -307,6 +307,32 @@ void main() {
|
||||||
expect(normal.skipped, isFalse);
|
expect(normal.skipped, isFalse);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('keeps the per-slide TLP classification', () {
|
||||||
|
final out = _roundTrip(
|
||||||
|
Slide.create(
|
||||||
|
SlideType.bullets,
|
||||||
|
).copyWith(title: 'Gevoelig', bullets: ['Geheim'], tlp: TlpLevel.amber),
|
||||||
|
);
|
||||||
|
expect(out.tlp, TlpLevel.amber);
|
||||||
|
|
||||||
|
final none = _roundTrip(
|
||||||
|
Slide.create(SlideType.bullets).copyWith(bullets: ['Open']),
|
||||||
|
);
|
||||||
|
expect(none.tlp, TlpLevel.none);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('keeps the per-slide TLP on a code slide', () {
|
||||||
|
final out = _roundTrip(
|
||||||
|
Slide.create(SlideType.code).copyWith(
|
||||||
|
customMarkdown: 'secret_key = 42',
|
||||||
|
codeLanguage: 'python',
|
||||||
|
tlp: TlpLevel.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(out.type, SlideType.code);
|
||||||
|
expect(out.tlp, TlpLevel.red);
|
||||||
|
});
|
||||||
|
|
||||||
test('keeps general presentation metadata in the front matter', () {
|
test('keeps general presentation metadata in the front matter', () {
|
||||||
final service = MarkdownService();
|
final service = MarkdownService();
|
||||||
final markdown = service.generateDeck(
|
final markdown = service.generateDeck(
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,42 @@ void main() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('slideVisibleAtTlp', () {
|
||||||
|
Slide slideAt(TlpLevel level) =>
|
||||||
|
Slide.create(SlideType.bullets).copyWith(tlp: level);
|
||||||
|
|
||||||
|
test('an unclassified slide is always visible', () {
|
||||||
|
for (final level in TlpLevel.values) {
|
||||||
|
expect(slideVisibleAtTlp(slideAt(TlpLevel.none), level), isTrue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a slide stricter than the presentation is withheld', () {
|
||||||
|
// Presentation at GREEN: CLEAR/GREEN shown, AMBER/RED withheld.
|
||||||
|
expect(slideVisibleAtTlp(slideAt(TlpLevel.clear), TlpLevel.green), isTrue);
|
||||||
|
expect(slideVisibleAtTlp(slideAt(TlpLevel.green), TlpLevel.green), isTrue);
|
||||||
|
expect(
|
||||||
|
slideVisibleAtTlp(slideAt(TlpLevel.amber), TlpLevel.green),
|
||||||
|
isFalse,
|
||||||
|
);
|
||||||
|
expect(slideVisibleAtTlp(slideAt(TlpLevel.red), TlpLevel.green), isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a RED presentation shows every slide', () {
|
||||||
|
for (final level in TlpLevel.values) {
|
||||||
|
expect(slideVisibleAtTlp(slideAt(level), TlpLevel.red), isTrue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('an unset presentation only shows unclassified slides', () {
|
||||||
|
expect(slideVisibleAtTlp(slideAt(TlpLevel.none), TlpLevel.none), isTrue);
|
||||||
|
expect(
|
||||||
|
slideVisibleAtTlp(slideAt(TlpLevel.clear), TlpLevel.none),
|
||||||
|
isFalse,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
group('TLP marking on slides', () {
|
group('TLP marking on slides', () {
|
||||||
Widget host(TlpLevel tlp) => MaterialApp(
|
Widget host(TlpLevel tlp) => MaterialApp(
|
||||||
home: Scaffold(
|
home: Scaffold(
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue