fix(editor): tables with whole-number sizes, or none, show

The editor casts a table's sizes (its column width, row height, height,
and each cell's width and height) to decimals, and finds a row's default
height with a null check. JSON written elsewhere may hold whole numbers,
40 where the editor writes 40.0, or no sizes at all: such a table showed
Flutter's error box instead. The Writing System paths in an archive
brought over from elsewhere hold whole numbers. Opening content now gives
its tables their sizes as the editor reads them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-11 19:31:39 +02:00
commit 277994d31b
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
2 changed files with 102 additions and 1 deletions

View file

@ -18,10 +18,56 @@ extension RichContentToDocument on RichContent {
// A mutable deep copy: the editor keeps parts of the JSON it is given and
// may change them in place, while RichContent's JSON is unmodifiable.
final json = jsonDecode(jsonEncode(editorJson)) as Map<String, Object?>;
_sizeTables(json['document']);
return Document.fromJson(json);
}
}
/// The sizes a table keeps, with what it takes where it has none.
final _tableSizes = {
TableBlockKeys.colDefaultWidth: TableDefaults.colWidth,
TableBlockKeys.rowDefaultHeight: TableDefaults.rowHeight,
TableBlockKeys.colMinimumWidth: TableDefaults.colMinimumWidth,
};
/// Gives the tables in [node] their sizes as the editor reads them, which
/// casts them to decimals and fails on whole numbers or none (checked with
/// our fork of v6.2): JSON written elsewhere, such as imports and archives,
/// may hold `40` where the editor itself writes `40.0`.
void _sizeTables(Object? node) {
if (node is! Map<String, Object?>) {
return;
}
double? decimal(Object? size) => switch (size) {
final num size => size.toDouble(),
final String size => double.tryParse(size),
_ => null,
};
if (node['data'] case final Map<String, Object?> data) {
switch (node['type']) {
case TableBlockKeys.type:
for (final MapEntry(:key, value: fallback) in _tableSizes.entries) {
data[key] = decimal(data[key]) ?? fallback;
}
if (decimal(data[TableBlockKeys.colsHeight]) case final height?) {
data[TableBlockKeys.colsHeight] = height;
}
case TableCellBlockKeys.type:
for (final key in [
TableCellBlockKeys.width,
TableCellBlockKeys.height,
]) {
if (decimal(data[key]) case final size?) {
data[key] = size;
}
}
}
}
if (node['children'] case final List<Object?> children) {
children.forEach(_sizeTables);
}
}
/// Stores the editor's document as [RichContent].
extension DocumentToRichContent on Document {
/// This document's content, in the current format.

View file

@ -37,6 +37,61 @@ void main() {
await tester.pump(const Duration(seconds: 1));
}
testWidgets('shows a table whose sizes are whole numbers, or missing, as '
'JSON written elsewhere may hold them', (tester) async {
Map<String, Object?> cell(int column, int row, Map<String, Object?> data) =>
{
'type': 'table/cell',
'data': {'colPosition': column, 'rowPosition': row, ...data},
'children': [
{
'type': 'paragraph',
'data': {
'delta': [
{'insert': '$column$row'},
],
},
},
],
};
for (final (table, cells) in [
// As an archive from elsewhere brought it: whole numbers.
(
{
'colsLen': 2,
'rowsLen': 1,
'colsHeight': 82,
'colDefaultWidth': 160,
'rowDefaultHeight': 40,
'colMinimumWidth': 40,
},
{'width': 80, 'height': 40},
),
// Without the editor's sizes.
(<String, Object?>{'colsLen': 2, 'rowsLen': 1}, <String, Object?>{}),
]) {
await show(
tester,
RichContent({
'document': {
'type': 'page',
'children': [
{
'type': 'table',
'data': table,
'children': [cell(0, 0, cells), cell(1, 0, cells)],
},
],
},
}),
);
expect(tester.takeException(), isNull);
expect(find.textContaining('00', findRichText: true), findsOneWidget);
expect(find.textContaining('10', findRichText: true), findsOneWidget);
}
});
testWidgets('shows the content', (tester) async {
await show(tester, paragraph('猫は動物です。'));