From 2fd50546036ebafee28f1faac111cac62010caa2 Mon Sep 17 00:00:00 2001 From: Brenno de Winter Date: Tue, 9 Jun 2026 13:28:23 +0200 Subject: [PATCH] Improve presentation editing and playback --- lib/l10n/app_localizations.dart | 142 +++ lib/models/settings.dart | 28 +- lib/models/slide.dart | 35 + lib/services/markdown_service.dart | 155 ++- lib/widgets/app_shell.dart | 8 + lib/widgets/dialogs/settings_dialog.dart | 54 +- lib/widgets/editors/bullets_editor.dart | 100 +- lib/widgets/editors/bullets_image_editor.dart | 100 +- lib/widgets/editors/chart_editor.dart | 155 ++- lib/widgets/editors/image_slide_editor.dart | 19 +- lib/widgets/editors/list_style_selector.dart | 41 + lib/widgets/editors/two_bullets_editor.dart | 110 +- lib/widgets/panels/editor_panel.dart | 11 + lib/widgets/presentation/audience_window.dart | 32 +- .../presentation/fullscreen_presenter.dart | 117 ++- lib/widgets/slides/slide_preview.dart | 964 +++++++++++++----- test/bullets_editor_test.dart | 286 ++++++ test/chart_editor_test.dart | 52 +- test/chart_preview_test.dart | 43 +- test/fullscreen_presenter_test.dart | 85 ++ test/image_slide_editor_test.dart | 71 ++ test/markdown_round_trip_test.dart | 67 +- test/settings_provider_test.dart | 12 + test/two_bullets_preview_test.dart | 7 +- 24 files changed, 2341 insertions(+), 353 deletions(-) create mode 100644 lib/widgets/editors/list_style_selector.dart create mode 100644 test/bullets_editor_test.dart create mode 100644 test/image_slide_editor_test.dart diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 80dc866..1cf1f97 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -2327,6 +2327,26 @@ const _dutchSourceStrings = { const _dutchSourceStringAdditions = { 'en': { 'Annuleren': 'Cancel', + 'Checklist': 'Task checklist', + 'Voortgangsgrafiek tonen': 'Show progress chart', + 'Toont afgevinkt en niet afgevinkt als percentages.': + 'Shows checked and unchecked items as percentages.', + 'Afgevinkt': 'Checked', + 'Niet afgevinkt': 'Unchecked', + 'Afgevinkte tekst doorhalen': 'Strike through checked text', + 'Toont een streep door voltooide checklistitems.': + 'Shows completed checklist items with a strike-through.', + 'Na media automatisch doorgaan': 'Advance automatically after media', + 'Opsomming': 'Bullets', + 'Nummering': 'Numbering', + 'Varianten': 'Variants', + 'Grafiekvarianten maken': 'Create chart variants', + 'Slides toevoegen': 'Add slides', + 'Omhoog': 'Move up', + 'Omlaag': 'Move down', + 'Niet toevoegen': 'Do not add', + 'Deze slides gebruiken dezelfde data, kleuren en titel. Kies met de pijlen de volgorde na de huidige slide.': + 'These slides use the same data, colors, and title. Use the arrows to choose their order after the current slide.', 'Afbeelding': 'Image', 'Broncode': 'Source code', 'Bullet': 'Bullet', @@ -2406,6 +2426,27 @@ const _dutchSourceStringAdditions = { }, 'it': { 'Annuleren': 'Annulla', + 'Checklist': 'Lista di controllo', + 'Voortgangsgrafiek tonen': 'Mostra grafico di avanzamento', + 'Toont afgevinkt en niet afgevinkt als percentages.': + 'Mostra gli elementi selezionati e non selezionati in percentuale.', + 'Afgevinkt': 'Selezionati', + 'Niet afgevinkt': 'Non selezionati', + 'Afgevinkte tekst doorhalen': 'Barra il testo selezionato', + 'Toont een streep door voltooide checklistitems.': + 'Mostra gli elementi completati con il testo barrato.', + 'Na media automatisch doorgaan': + 'Avanza automaticamente dopo i contenuti multimediali', + 'Opsomming': 'Elenco puntato', + 'Nummering': 'Numerazione', + 'Varianten': 'Varianti', + 'Grafiekvarianten maken': 'Crea varianti del grafico', + 'Slides toevoegen': 'Aggiungi diapositive', + 'Omhoog': 'Sposta su', + 'Omlaag': 'Sposta giù', + 'Niet toevoegen': 'Non aggiungere', + 'Deze slides gebruiken dezelfde data, kleuren en titel. Kies met de pijlen de volgorde na de huidige slide.': + 'Queste diapositive usano gli stessi dati, colori e titolo. Usa le frecce per scegliere l’ordine dopo la diapositiva corrente.', 'Spider': 'Radar', 'Een spider-diagram heeft minstens drie labels (assen) nodig; elke reeks vormt een vlak.': 'Un grafico radar richiede almeno tre etichette (assi); ogni serie forma una superficie.', @@ -2638,6 +2679,26 @@ const _dutchSourceStringAdditions = { }, 'de': { 'Annuleren': 'Abbrechen', + 'Checklist': 'Checkliste', + 'Voortgangsgrafiek tonen': 'Fortschrittsdiagramm anzeigen', + 'Toont afgevinkt en niet afgevinkt als percentages.': + 'Zeigt erledigte und offene Einträge als Prozentwerte.', + 'Afgevinkt': 'Erledigt', + 'Niet afgevinkt': 'Offen', + 'Afgevinkte tekst doorhalen': 'Erledigten Text durchstreichen', + 'Toont een streep door voltooide checklistitems.': + 'Zeigt erledigte Checklistenpunkte durchgestrichen an.', + 'Na media automatisch doorgaan': 'Nach Medienwiedergabe automatisch weiter', + 'Opsomming': 'Aufzählung', + 'Nummering': 'Nummerierung', + 'Varianten': 'Varianten', + 'Grafiekvarianten maken': 'Diagrammvarianten erstellen', + 'Slides toevoegen': 'Folien hinzufügen', + 'Omhoog': 'Nach oben', + 'Omlaag': 'Nach unten', + 'Niet toevoegen': 'Nicht hinzufügen', + 'Deze slides gebruiken dezelfde data, kleuren en titel. Kies met de pijlen de volgorde na de huidige slide.': + 'Diese Folien verwenden dieselben Daten, Farben und denselben Titel. Lege mit den Pfeilen die Reihenfolge nach der aktuellen Folie fest.', 'Spider': 'Netz', 'Een spider-diagram heeft minstens drie labels (assen) nodig; elke reeks vormt een vlak.': 'Ein Netzdiagramm braucht mindestens drei Beschriftungen (Achsen); jede Reihe bildet eine Fläche.', @@ -2871,6 +2932,26 @@ const _dutchSourceStringAdditions = { }, 'fr': { 'Annuleren': 'Annuler', + 'Checklist': 'Liste de contrôle', + 'Voortgangsgrafiek tonen': 'Afficher le graphique de progression', + 'Toont afgevinkt en niet afgevinkt als percentages.': + 'Affiche les éléments cochés et non cochés en pourcentage.', + 'Afgevinkt': 'Coché', + 'Niet afgevinkt': 'Non coché', + 'Afgevinkte tekst doorhalen': 'Barrer le texte coché', + 'Toont een streep door voltooide checklistitems.': + 'Affiche les éléments terminés avec un texte barré.', + 'Na media automatisch doorgaan': 'Avancer automatiquement après le média', + 'Opsomming': 'Liste à puces', + 'Nummering': 'Numérotation', + 'Varianten': 'Variantes', + 'Grafiekvarianten maken': 'Créer des variantes du graphique', + 'Slides toevoegen': 'Ajouter les diapositives', + 'Omhoog': 'Monter', + 'Omlaag': 'Descendre', + 'Niet toevoegen': 'Ne pas ajouter', + 'Deze slides gebruiken dezelfde data, kleuren en titel. Kies met de pijlen de volgorde na de huidige slide.': + 'Ces diapositives utilisent les mêmes données, couleurs et titre. Utilisez les flèches pour choisir leur ordre après la diapositive actuelle.', 'Spider': 'Radar', 'Een spider-diagram heeft minstens drie labels (assen) nodig; elke reeks vormt een vlak.': 'Un graphique radar nécessite au moins trois étiquettes (axes); chaque série forme une surface.', @@ -3104,6 +3185,27 @@ const _dutchSourceStringAdditions = { }, 'es': { 'Annuleren': 'Cancelar', + 'Checklist': 'Lista de verificación', + 'Voortgangsgrafiek tonen': 'Mostrar gráfico de progreso', + 'Toont afgevinkt en niet afgevinkt als percentages.': + 'Muestra los elementos marcados y sin marcar como porcentajes.', + 'Afgevinkt': 'Marcado', + 'Niet afgevinkt': 'Sin marcar', + 'Afgevinkte tekst doorhalen': 'Tachar el texto marcado', + 'Toont een streep door voltooide checklistitems.': + 'Muestra tachados los elementos completados.', + 'Na media automatisch doorgaan': + 'Avanzar automáticamente tras el contenido multimedia', + 'Opsomming': 'Viñetas', + 'Nummering': 'Numeración', + 'Varianten': 'Variantes', + 'Grafiekvarianten maken': 'Crear variantes del gráfico', + 'Slides toevoegen': 'Añadir diapositivas', + 'Omhoog': 'Subir', + 'Omlaag': 'Bajar', + 'Niet toevoegen': 'No añadir', + 'Deze slides gebruiken dezelfde data, kleuren en titel. Kies met de pijlen de volgorde na de huidige slide.': + 'Estas diapositivas usan los mismos datos, colores y título. Usa las flechas para elegir su orden después de la diapositiva actual.', 'Spider': 'Radar', 'Een spider-diagram heeft minstens drie labels (assen) nodig; elke reeks vormt een vlak.': 'Un gráfico radar necesita al menos tres etiquetas (ejes); cada serie forma una superficie.', @@ -3337,6 +3439,26 @@ const _dutchSourceStringAdditions = { }, 'fy': { 'Annuleren': 'Annulearje', + 'Checklist': 'Kontrôlelist', + 'Voortgangsgrafiek tonen': 'Fuortgongsgrafyk toane', + 'Toont afgevinkt en niet afgevinkt als percentages.': + 'Toant ôffinkte en net ôffinkte items as persintaazjes.', + 'Afgevinkt': 'Ôffinkt', + 'Niet afgevinkt': 'Net ôffinkt', + 'Afgevinkte tekst doorhalen': 'Ôffinkte tekst trochstreekje', + 'Toont een streep door voltooide checklistitems.': + 'Toant foltôge kontrôlelistitems mei in streek der troch.', + 'Na media automatisch doorgaan': 'Nei media automatysk trochgean', + 'Opsomming': 'Puntelist', + 'Nummering': 'Nûmering', + 'Varianten': 'Farianten', + 'Grafiekvarianten maken': 'Grafykfarianten meitsje', + 'Slides toevoegen': 'Dia’s tafoegje', + 'Omhoog': 'Omheech', + 'Omlaag': 'Omleech', + 'Niet toevoegen': 'Net tafoegje', + 'Deze slides gebruiken dezelfde data, kleuren en titel. Kies met de pijlen de volgorde na de huidige slide.': + 'Dizze dia’s brûke deselde gegevens, kleuren en titel. Kies mei de pylken de folchoarder nei de aktive dia.', 'Spider': 'Spider', 'Een spider-diagram heeft minstens drie labels (assen) nodig; elke reeks vormt een vlak.': 'In spiderdiagram hat op syn minst trije labels (assen) nedich; eltse rige foarmet in flak.', @@ -3567,6 +3689,26 @@ const _dutchSourceStringAdditions = { }, 'pap': { 'Annuleren': 'Kanselá', + 'Checklist': 'Lista di kontrol', + 'Voortgangsgrafiek tonen': 'Mustra gráfiko di progreso', + 'Toont afgevinkt en niet afgevinkt als percentages.': + 'Ta mustra elementonan marká i no marká komo porsentahe.', + 'Afgevinkt': 'Marká', + 'Niet afgevinkt': 'No marká', + 'Afgevinkte tekst doorhalen': 'Raya teksto marká', + 'Toont een streep door voltooide checklistitems.': + 'Ta mustra elementonan kompletá ku un raya den e teksto.', + 'Na media automatisch doorgaan': 'Sigui outomátiko despues di multimedia', + 'Opsomming': 'Lista ku punto', + 'Nummering': 'Numerashon', + 'Varianten': 'Variantenan', + 'Grafiekvarianten maken': 'Krea variantenan di gráfiko', + 'Slides toevoegen': 'Agregá diapositivanan', + 'Omhoog': 'Move ariba', + 'Omlaag': 'Move abou', + 'Niet toevoegen': 'No agregá', + 'Deze slides gebruiken dezelfde data, kleuren en titel. Kies met de pijlen de volgorde na de huidige slide.': + 'E diapositivanan aki ta usa e mesun datonan, kolónan i título. Usa e flechanan pa skohe nan órden despues di e diapositiva aktual.', 'Spider': 'Radar', 'Een spider-diagram heeft minstens drie labels (assen) nodig; elke reeks vormt een vlak.': 'Un grafiko radar mester di por lo ménos tres etiketa (ehe); kada serie ta forma un superfisie.', diff --git a/lib/models/settings.dart b/lib/models/settings.dart index a059f70..04e4c27 100644 --- a/lib/models/settings.dart +++ b/lib/models/settings.dart @@ -3,6 +3,9 @@ class ThemeProfile { final String slideBackgroundColor; final String textColor; final String accentColor; + final String checklistCheckedColor; + final String checklistUncheckedColor; + final bool checklistStrikeThrough; final String tableTextColor; final String tableHeaderTextColor; final String titleBackgroundColor; @@ -50,6 +53,9 @@ class ThemeProfile { this.slideBackgroundColor = '#FFFFFF', this.textColor = '#222222', this.accentColor = '#2E7D64', + this.checklistCheckedColor = '#2E7D64', + this.checklistUncheckedColor = '#CBD5E1', + this.checklistStrikeThrough = true, String? tableTextColor, this.tableHeaderTextColor = '#FFFFFF', this.titleBackgroundColor = '#1C2B47', @@ -84,6 +90,9 @@ class ThemeProfile { String? slideBackgroundColor, String? textColor, String? accentColor, + String? checklistCheckedColor, + String? checklistUncheckedColor, + bool? checklistStrikeThrough, String? tableTextColor, String? tableHeaderTextColor, String? titleBackgroundColor, @@ -109,6 +118,12 @@ class ThemeProfile { slideBackgroundColor: slideBackgroundColor ?? this.slideBackgroundColor, textColor: textColor ?? this.textColor, accentColor: accentColor ?? this.accentColor, + checklistCheckedColor: + checklistCheckedColor ?? this.checklistCheckedColor, + checklistUncheckedColor: + checklistUncheckedColor ?? this.checklistUncheckedColor, + checklistStrikeThrough: + checklistStrikeThrough ?? this.checklistStrikeThrough, tableTextColor: tableTextColor ?? this.tableTextColor, tableHeaderTextColor: tableHeaderTextColor ?? this.tableHeaderTextColor, titleBackgroundColor: titleBackgroundColor ?? this.titleBackgroundColor, @@ -138,6 +153,9 @@ class ThemeProfile { 'name': name, 'textColor': textColor, 'accentColor': accentColor, + 'checklistCheckedColor': checklistCheckedColor, + 'checklistUncheckedColor': checklistUncheckedColor, + 'checklistStrikeThrough': checklistStrikeThrough, 'tableTextColor': tableTextColor, 'tableHeaderTextColor': tableHeaderTextColor, 'titleBackgroundColor': titleBackgroundColor, @@ -166,6 +184,13 @@ class ThemeProfile { name: json['name'] as String? ?? 'Standaard', textColor: json['textColor'] as String? ?? '#222222', accentColor: json['accentColor'] as String? ?? '#2E7D64', + checklistCheckedColor: + json['checklistCheckedColor'] as String? ?? + json['accentColor'] as String? ?? + '#2E7D64', + checklistUncheckedColor: + json['checklistUncheckedColor'] as String? ?? '#CBD5E1', + checklistStrikeThrough: json['checklistStrikeThrough'] as bool? ?? true, tableTextColor: json['tableTextColor'] as String? ?? json['textColor'] as String? ?? @@ -177,8 +202,7 @@ class ThemeProfile { titleTextColor: json['titleTextColor'] as String? ?? '#FFFFFF', sectionBackgroundColor: json['sectionBackgroundColor'] as String? ?? '#2E7D64', - codeBackgroundColor: - json['codeBackgroundColor'] as String? ?? '#282C34', + codeBackgroundColor: json['codeBackgroundColor'] as String? ?? '#282C34', codeTextColor: json['codeTextColor'] as String? ?? '#ABB2BF', codeHighlightSyntax: json['codeHighlightSyntax'] as bool? ?? true, codeFontFamily: json['codeFontFamily'] as String? ?? 'monospace', diff --git a/lib/models/slide.dart b/lib/models/slide.dart index 748a710..2a1107a 100644 --- a/lib/models/slide.dart +++ b/lib/models/slide.dart @@ -19,6 +19,30 @@ enum SlideType { chart, } +enum ListStyle { bullets, numbered, checklist } + +int bulletLevel(String value) { + var level = 0; + while (level < value.length && value[level] == '\t') { + level++; + } + return level; +} + +String bulletText(String value) => value.substring(bulletLevel(value)); + +bool checklistItemChecked(String value) => + RegExp(r'^\[[xX]\]\s*').hasMatch(bulletText(value)); + +String checklistItemText(String value) => + bulletText(value).replaceFirst(RegExp(r'^\[[ xX]\]\s*'), ''); + +String checklistBullet({ + required int level, + required String text, + required bool checked, +}) => '${'\t' * level}[${checked ? 'x' : ' '}] $text'; + extension SlideTypeExtension on SlideType { String get label { switch (this) { @@ -90,6 +114,8 @@ class Slide { final String subtitle; final List bullets; final List bullets2; + final ListStyle listStyle; + final bool showChecklistProgress; /// Optional headings above the two bullet columns (twoBullets only). Empty = /// no heading for that column. @@ -128,6 +154,8 @@ class Slide { this.subtitle = '', this.bullets = const [], this.bullets2 = const [], + this.listStyle = ListStyle.bullets, + this.showChecklistProgress = false, this.columnTitle1 = '', this.columnTitle2 = '', this.imagePath = '', @@ -183,6 +211,8 @@ class Slide { subtitle: src.subtitle, bullets: List.from(src.bullets), bullets2: List.from(src.bullets2), + listStyle: src.listStyle, + showChecklistProgress: src.showChecklistProgress, columnTitle1: src.columnTitle1, columnTitle2: src.columnTitle2, imagePath: src.imagePath, @@ -215,6 +245,8 @@ class Slide { String? subtitle, List? bullets, List? bullets2, + ListStyle? listStyle, + bool? showChecklistProgress, String? columnTitle1, String? columnTitle2, String? imagePath, @@ -246,6 +278,9 @@ class Slide { subtitle: subtitle ?? this.subtitle, bullets: bullets ?? this.bullets, bullets2: bullets2 ?? this.bullets2, + listStyle: listStyle ?? this.listStyle, + showChecklistProgress: + showChecklistProgress ?? this.showChecklistProgress, columnTitle1: columnTitle1 ?? this.columnTitle1, columnTitle2: columnTitle2 ?? this.columnTitle2, imagePath: imagePath ?? this.imagePath, diff --git a/lib/services/markdown_service.dart b/lib/services/markdown_service.dart index 2c2b206..f70ae52 100644 --- a/lib/services/markdown_service.dart +++ b/lib/services/markdown_service.dart @@ -216,10 +216,12 @@ class MarkdownService { case SlideType.bullets: if (slide.title.isNotEmpty) buf.writeln('# ${slide.title}'); if (slide.subtitle.isNotEmpty) buf.writeln('## ${slide.subtitle}'); - buf.writeln(); - for (final b in slide.bullets) { - _writeBullet(buf, b); + if (slide.listStyle != ListStyle.bullets) { + buf.writeln(''); } + _writeChecklistProgress(buf, slide); + buf.writeln(); + _writeList(buf, slide.bullets, slide.listStyle); case SlideType.twoBullets: if (slide.title.isNotEmpty) buf.writeln('# ${slide.title}'); @@ -230,6 +232,9 @@ class MarkdownService { slide.bullets2, slide.columnTitle1, slide.columnTitle2, + slide.listStyle, + slide.showChecklistProgress, + themeProfile ?? const ThemeProfile(), ); case SlideType.bulletsImage: @@ -248,10 +253,12 @@ class MarkdownService { ); buf.writeln(); if (slide.title.isNotEmpty) buf.writeln('# ${slide.title}'); - buf.writeln(); - for (final b in slide.bullets) { - _writeBullet(buf, b); + if (slide.listStyle != ListStyle.bullets) { + buf.writeln(''); } + _writeChecklistProgress(buf, slide); + buf.writeln(); + _writeList(buf, slide.bullets, slide.listStyle); buf.writeln(); buf.writeln(''); buf.writeln(); @@ -263,10 +270,12 @@ class MarkdownService { buf.writeln(''); } else { if (slide.title.isNotEmpty) buf.writeln('# ${slide.title}'); - buf.writeln(); - for (final b in slide.bullets) { - _writeBullet(buf, b); + if (slide.listStyle != ListStyle.bullets) { + buf.writeln(''); } + _writeChecklistProgress(buf, slide); + buf.writeln(); + _writeList(buf, slide.bullets, slide.listStyle); } case SlideType.twoImages: @@ -401,14 +410,35 @@ class MarkdownService { return buf.toString(); } - static void _writeBullet(StringBuffer buf, String bullet) { - int level = 0; - while (level < bullet.length && bullet[level] == '\t') { - level++; - } - final text = bullet.substring(level); - if (text.isNotEmpty) { - buf.writeln('${' ' * level}- $text'); + static void _writeList( + StringBuffer buf, + List items, + ListStyle style, + ) { + final counters = []; + for (final item in items) { + int level = 0; + while (level < item.length && item[level] == '\t') { + level++; + } + final text = item.substring(level); + if (text.isEmpty) continue; + while (counters.length <= level) { + counters.add(0); + } + counters[level]++; + if (counters.length > level + 1) { + counters.removeRange(level + 1, counters.length); + } + final marker = switch (style) { + ListStyle.numbered => '${counters[level]}.', + ListStyle.bullets || ListStyle.checklist => '-', + }; + final body = style == ListStyle.checklist + ? '[${checklistItemChecked(item) ? 'x' : ' '}] ' + '${checklistItemText(item)}' + : text; + buf.writeln('${' ' * level}$marker $body'); } } @@ -418,9 +448,18 @@ class MarkdownService { List right, String leftTitle, String rightTitle, + ListStyle listStyle, + bool showChecklistProgress, + ThemeProfile themeProfile, ) { buf.writeln(''); buf.writeln(''); + if (listStyle != ListStyle.bullets) { + buf.writeln(''); + } + if (showChecklistProgress) { + buf.writeln(''); + } if (leftTitle.isNotEmpty) { buf.writeln( '', @@ -434,15 +473,23 @@ class MarkdownService { buf.writeln( '
', ); - _writeBulletColumn(buf, left, leftTitle); - _writeBulletColumn(buf, right, rightTitle); + _writeBulletColumn(buf, left, leftTitle, listStyle, themeProfile); + _writeBulletColumn(buf, right, rightTitle, listStyle, themeProfile); buf.writeln('
'); } + static void _writeChecklistProgress(StringBuffer buf, Slide slide) { + if (slide.showChecklistProgress) { + buf.writeln(''); + } + } + static void _writeBulletColumn( StringBuffer buf, List bullets, String columnTitle, + ListStyle listStyle, + ThemeProfile themeProfile, ) { buf.writeln('
'); if (columnTitle.isNotEmpty) { @@ -450,9 +497,10 @@ class MarkdownService { '

${_escapeHtml(columnTitle)}

', ); } - buf.writeln('
    '); - _writeHtmlBulletItems(buf, bullets); - buf.writeln('
'); + final tag = listStyle == ListStyle.numbered ? 'ol' : 'ul'; + buf.writeln('<$tag style="margin:0; padding-left:1.3em;">'); + _writeHtmlBulletItems(buf, bullets, listStyle, themeProfile); + buf.writeln(''); buf.writeln('
'); } @@ -480,16 +528,41 @@ class MarkdownService { return const []; } - static void _writeHtmlBulletItems(StringBuffer buf, List bullets) { + static void _writeHtmlBulletItems( + StringBuffer buf, + List bullets, + ListStyle listStyle, + ThemeProfile themeProfile, + ) { + final counters = []; for (final b in bullets) { int level = 0; while (level < b.length && b[level] == '\t') { level++; } - final text = b.substring(level).trim(); + final text = listStyle == ListStyle.checklist + ? checklistItemText(b).trim() + : b.substring(level).trim(); if (text.isEmpty) continue; + while (counters.length <= level) { + counters.add(0); + } + counters[level]++; + if (counters.length > level + 1) { + counters.removeRange(level + 1, counters.length); + } final style = level == 0 ? '' : ' style="margin-left:${level * 1.4}em;"'; - buf.writeln('${_escapeHtml(text)}'); + final value = listStyle == ListStyle.numbered + ? ' value="${counters[level]}"' + : ''; + final checkbox = listStyle == ListStyle.checklist + ? '${checklistItemChecked(b) ? '☑' : '☐'} ' + : ''; + final decoration = listStyle == ListStyle.checklist + ? ' style="${level == 0 ? '' : 'margin-left:${level * 1.4}em;'}' + '${checklistItemChecked(b) && themeProfile.checklistStrikeThrough ? 'text-decoration:line-through;opacity:.7;' : ''}"' + : style; + buf.writeln('${_escapeHtml(checkbox + text)}'); } } @@ -676,6 +749,8 @@ class MarkdownService { TlpLevel slideTlp = TlpLevel.none; final bullets = []; var bullets2 = []; + var listStyle = ListStyle.bullets; + var showChecklistProgress = false; var columnTitle1 = ''; var columnTitle2 = ''; // bulletsImage slides store their panel width in `