Skip to content

Commit b8a8421

Browse files
committed
✨ added table creation. fixes #22, #1
1 parent 0ec6f3a commit b8a8421

File tree

5 files changed

+1163
-197
lines changed

5 files changed

+1163
-197
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ Please star⭐ the repo if you like what you see😊.
8282
- [x] Ability to open .md files directly from the explorer
8383
- [x] Convenient way to style text (bold,italics,headings and etc)
8484
- [x] Convenient way to add links
85+
- [x] Convenient way to add tables
8586
- [x] Ability to preview JPEG, PNG, GIF, WebP, BMP, and WBMP image formats.
8687
- [x] Easily open links from the preview
8788
- [x] Light and Dark Theme Modes available
@@ -94,6 +95,7 @@ Please star⭐ the repo if you like what you see😊.
9495
- [x] LaTex support
9596
- [x] Horizontal Swipe to Switch between Preview and Editing View in Single View Mode
9697
- [x] Default Folder for Opening and Saving .md files
98+
- [x] Added Print/Save as Pdf Option
9799

98100
## 📸 Screenshots
99101

lib/l10n/app_en.arb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,5 +138,21 @@
138138
"print": "Print",
139139
"@print": {
140140
"description": "Menu Item for Print"
141+
},
142+
"table": "Table",
143+
"@table": {
144+
"description": "Tooltip for the Table Icon Button"
145+
},
146+
"rows": "Rows",
147+
"@rows": {
148+
"description": "Label for the Rows Text Field in the Table Dialog"
149+
},
150+
"columns": "Columns",
151+
"@columns": {
152+
"description": "Label for the Columns Text Field in the Table Dialog"
153+
},
154+
"insert": "Insert",
155+
"@insert": {
156+
"description": "Text Action for the Insert Table Dialog"
141157
}
142158
}

lib/widgets/MarkdownTextInput/format_markdown.dart

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ class FormatMarkdown {
1616
int titleSize = 1,
1717
String? link,
1818
String selectedText = '',
19+
int tableRows = 3,
20+
int tableCols = 3,
1921
}) {
2022
late String changedData;
2123
late int replaceCursorIndex;
@@ -80,6 +82,44 @@ class FormatMarkdown {
8082
'![${data.substring(lesserIndex, greaterIndex)}](${data.substring(lesserIndex, greaterIndex)})';
8183
replaceCursorIndex = 3;
8284
break;
85+
case MarkdownType.table:
86+
final rows = tableRows.clamp(1, 50);
87+
final cols = tableCols.clamp(1, 20);
88+
89+
String cell(int r, int c) {
90+
// If text was selected, try to split it into cells (basic support) else placeholders
91+
if (selectedText.trim().isNotEmpty) {
92+
// naive split by lines and pipes for quick paste handling
93+
final byLines = selectedText.trim().split('\n');
94+
if (r < byLines.length) {
95+
final parts = byLines[r]
96+
.split('|')
97+
.where((e) => e.trim().isNotEmpty)
98+
.toList();
99+
if (c < parts.length) return parts[c].trim();
100+
}
101+
}
102+
return (r == 0) ? 'Header ${c + 1}' : 'Cell ${r + 1}:${c + 1}';
103+
}
104+
105+
final buffer = StringBuffer();
106+
// First row
107+
buffer.writeln(
108+
'| ${List.generate(cols, (c) => cell(0, c)).join(' | ')} |',
109+
);
110+
// Separator (align left by default). If no header, still required by Markdown.
111+
buffer.writeln('| ${List.generate(cols, (_) => '---').join(' | ')} |');
112+
113+
for (int r = 1; r < rows; r++) {
114+
buffer.writeln(
115+
'| ${List.generate(cols, (c) => cell(r, c)).join(' | ')} |',
116+
);
117+
}
118+
119+
changedData = buffer.toString();
120+
// place cursor in first body cell (roughly after "| ")
121+
replaceCursorIndex = 2; // when no selection,put caret inside first cell
122+
break;
83123
}
84124

85125
final cursorIndex = changedData.length;
@@ -145,7 +185,10 @@ enum MarkdownType {
145185
separator,
146186

147187
/// For ![Alt text](link)
148-
image;
188+
image,
189+
190+
/// For creating tables
191+
table;
149192

150193
String title(BuildContext context) {
151194
switch (this) {
@@ -169,6 +212,8 @@ enum MarkdownType {
169212
return AppLocalizations.of(context)!.horizontalRule;
170213
case MarkdownType.image:
171214
return AppLocalizations.of(context)!.image;
215+
case MarkdownType.table:
216+
return AppLocalizations.of(context)!.table;
172217
}
173218
}
174219

@@ -195,6 +240,8 @@ enum MarkdownType {
195240
return 'separator_button';
196241
case MarkdownType.image:
197242
return 'image_button';
243+
case MarkdownType.table:
244+
return 'table_button';
198245
}
199246
}
200247

@@ -221,6 +268,8 @@ enum MarkdownType {
221268
return Icons.minimize_rounded;
222269
case MarkdownType.image:
223270
return Icons.image_rounded;
271+
case MarkdownType.table:
272+
return Icons.table_chart_rounded;
224273
}
225274
}
226275
}

lib/widgets/MarkdownTextInput/markdown_text_input.dart

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ class MarkdownTextInput extends StatefulWidget {
5555
MarkdownType.blockquote,
5656
MarkdownType.separator,
5757
MarkdownType.image,
58+
MarkdownType.table,
5859
],
5960
this.textStyle,
6061
this.controller,
@@ -78,6 +79,8 @@ class _MarkdownTextInputState extends State<MarkdownTextInput> {
7879
int titleSize = 1,
7980
String? link,
8081
String? selectedText,
82+
int tableRows = 3,
83+
int tableCols = 3,
8184
}) {
8285
final basePosition = _textSelection.baseOffset;
8386
final noTextSelected =
@@ -95,6 +98,8 @@ class _MarkdownTextInputState extends State<MarkdownTextInput> {
9598
link: link,
9699
selectedText:
97100
selectedText ?? _controller.text.substring(fromIndex, toIndex),
101+
tableRows: tableRows,
102+
tableCols: tableCols,
98103
);
99104

100105
_controller.value = _controller.value.copyWith(
@@ -323,6 +328,30 @@ class _MarkdownTextInputState extends State<MarkdownTextInput> {
323328
);
324329
},
325330
);
331+
case MarkdownType.table:
332+
return Tooltip(
333+
message: type.title(context),
334+
child: InkWell(
335+
key: const Key('table_button'),
336+
onTap: () async {
337+
final res = await showDialog<_TableConfig?>(
338+
context: context,
339+
builder: (ctx) => _TableDialog(),
340+
);
341+
if (res != null) {
342+
onTap(
343+
MarkdownType.table,
344+
tableRows: res.rows,
345+
tableCols: res.cols,
346+
);
347+
}
348+
},
349+
child: const Padding(
350+
padding: EdgeInsets.all(10),
351+
child: Icon(Icons.table_chart_rounded),
352+
),
353+
),
354+
);
326355
default:
327356
return _basicInkwell(type);
328357
}
@@ -398,3 +427,89 @@ class _MarkdownTextInputState extends State<MarkdownTextInput> {
398427
);
399428
}
400429
}
430+
431+
class _TableConfig {
432+
final int rows;
433+
final int cols;
434+
const _TableConfig({required this.rows, required this.cols});
435+
}
436+
437+
class _TableDialog extends StatefulWidget {
438+
@override
439+
State<_TableDialog> createState() => _TableDialogState();
440+
}
441+
442+
class _TableDialogState extends State<_TableDialog> {
443+
int _rows = 3;
444+
int _cols = 3;
445+
446+
Widget _numberField({
447+
required String label,
448+
required int value,
449+
required ValueChanged<int> onChanged,
450+
}) {
451+
return TextFormField(
452+
initialValue: value.toString(),
453+
keyboardType: TextInputType.number,
454+
decoration: InputDecoration(
455+
labelText: label,
456+
border: const OutlineInputBorder(),
457+
isDense: true,
458+
),
459+
onChanged: (s) => onChanged(int.tryParse(s) ?? value),
460+
);
461+
}
462+
463+
@override
464+
Widget build(BuildContext context) {
465+
final loc = AppLocalizations.of(context);
466+
return AlertDialog(
467+
title: Row(
468+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
469+
children: [
470+
Text(loc?.table ?? 'Table'),
471+
IconButton(
472+
icon: const Icon(Icons.close),
473+
onPressed: () => Navigator.pop(context),
474+
),
475+
],
476+
),
477+
content: Row(
478+
children: [
479+
Expanded(
480+
child: _numberField(
481+
label: loc?.rows ?? 'Rows',
482+
value: _rows,
483+
onChanged: (v) => setState(() => _rows = v),
484+
),
485+
),
486+
const SizedBox(width: 12),
487+
Expanded(
488+
child: _numberField(
489+
label: loc?.columns ?? 'Columns',
490+
value: _cols,
491+
onChanged: (v) => setState(() => _cols = v),
492+
),
493+
),
494+
],
495+
),
496+
actions: [
497+
TextButton(
498+
onPressed: () => Navigator.pop(context),
499+
child: Text(loc?.cancel ?? 'Cancel'),
500+
),
501+
ElevatedButton(
502+
onPressed: () {
503+
final sanitizedRows = _rows.clamp(1, 50);
504+
final sanitizedCols = _cols.clamp(1, 20);
505+
Navigator.pop(
506+
context,
507+
_TableConfig(rows: sanitizedRows, cols: sanitizedCols),
508+
);
509+
},
510+
child: Text(loc?.insert ?? 'Insert'),
511+
),
512+
],
513+
);
514+
}
515+
}

0 commit comments

Comments
 (0)