Skip to content

Commit f35ef3c

Browse files
authored
Merge pull request #20 from RZEROSTERN/feat/phase-2-viewer-completeness-part-2
Viewer completeness read-only block types
2 parents 06f67b4 + f369f9c commit f35ef3c

22 files changed

Lines changed: 692 additions & 47 deletions

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
## [0.2.1] - 2026-03-25 — Viewer completeness: embed, linkTool, attaches, raw
2+
3+
### New block types (viewer only)
4+
* `embed` — tappable card showing service name, source URL, and optional caption; opens URL via `url_launcher`.
5+
* `linkTool` — link preview card with thumbnail, title, description, and URL; opens link via `url_launcher`.
6+
* `attaches` — file download card with type-specific icon, file size, and `url_launcher` download action.
7+
* `raw` — raw HTML content rendered through `flutter_html` with full `HtmlSanitizer` protection.
8+
9+
### Cross-cutting renderer improvements
10+
* Extracted `HtmlStyleBuilder` shared utility — applies `defaultFont` to `flutter_html` `body` style and all CSS tag overrides, eliminating duplicate helpers across renderers.
11+
* Applied `HtmlSanitizer.sanitize()` to `QuoteRenderer` and `ListRenderer` (HTML content fields).
12+
* `ImageRenderer` caption now respects `styleConfig.defaultFont`.
13+
14+
### Exports
15+
* All four new block entity classes (`EmbedBlock`, `LinkToolBlock`, `AttachesBlock`, `RawBlock`) exported from the public barrel file.
16+
17+
---
18+
119
## [0.2.0] - 2026-03-25 — Phase 2: Viewer & editor completeness
220

321
### New block types (viewer + editor)

lib/editorjs_flutter.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,7 @@ export 'src/domain/entities/blocks/code_block.dart';
3333
export 'src/domain/entities/blocks/checklist_block.dart';
3434
export 'src/domain/entities/blocks/table_block.dart';
3535
export 'src/domain/entities/blocks/warning_block.dart';
36+
export 'src/domain/entities/blocks/embed_block.dart';
37+
export 'src/domain/entities/blocks/link_tool_block.dart';
38+
export 'src/domain/entities/blocks/attaches_block.dart';
39+
export 'src/domain/entities/blocks/raw_block.dart';
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import '../../domain/entities/blocks/attaches_block.dart';
2+
import 'block_mapper.dart';
3+
4+
class AttachesMapper implements BlockMapper<AttachesBlock> {
5+
const AttachesMapper();
6+
7+
@override
8+
String get supportedType => 'attaches';
9+
10+
@override
11+
AttachesBlock fromJson(Map<String, dynamic> data) {
12+
final file = data['file'] as Map<String, dynamic>?;
13+
return AttachesBlock(
14+
url: (file?['url'] as String?) ?? '',
15+
name: file?['name'] as String?,
16+
extension: file?['extension'] as String?,
17+
size: file?['size'] as int?,
18+
title: data['title'] as String?,
19+
);
20+
}
21+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import '../../domain/entities/blocks/embed_block.dart';
2+
import 'block_mapper.dart';
3+
4+
class EmbedMapper implements BlockMapper<EmbedBlock> {
5+
const EmbedMapper();
6+
7+
@override
8+
String get supportedType => 'embed';
9+
10+
@override
11+
EmbedBlock fromJson(Map<String, dynamic> data) => EmbedBlock(
12+
service: (data['service'] as String?) ?? '',
13+
source: (data['source'] as String?) ?? '',
14+
embed: (data['embed'] as String?) ?? '',
15+
width: data['width'] as int?,
16+
height: data['height'] as int?,
17+
caption: data['caption'] as String?,
18+
);
19+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import '../../domain/entities/blocks/link_tool_block.dart';
2+
import 'block_mapper.dart';
3+
4+
class LinkToolMapper implements BlockMapper<LinkToolBlock> {
5+
const LinkToolMapper();
6+
7+
@override
8+
String get supportedType => 'linkTool';
9+
10+
@override
11+
LinkToolBlock fromJson(Map<String, dynamic> data) {
12+
final rawMeta = data['meta'] as Map<String, dynamic>?;
13+
final rawImage = rawMeta?['image'] as Map<String, dynamic>?;
14+
15+
final meta = rawMeta == null
16+
? null
17+
: LinkToolMeta(
18+
title: rawMeta['title'] as String?,
19+
description: rawMeta['description'] as String?,
20+
imageUrl: rawImage?['url'] as String?,
21+
);
22+
23+
return LinkToolBlock(
24+
link: (data['link'] as String?) ?? '',
25+
meta: meta,
26+
);
27+
}
28+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import '../../domain/entities/blocks/raw_block.dart';
2+
import 'block_mapper.dart';
3+
4+
class RawMapper implements BlockMapper<RawBlock> {
5+
const RawMapper();
6+
7+
@override
8+
String get supportedType => 'raw';
9+
10+
@override
11+
RawBlock fromJson(Map<String, dynamic> data) =>
12+
RawBlock(html: (data['html'] as String?) ?? '');
13+
}

lib/src/data/registry/block_type_registry.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import '../../domain/entities/block_entity.dart';
2+
import '../mappers/attaches_mapper.dart';
23
import '../mappers/block_mapper.dart';
34
import '../mappers/checklist_mapper.dart';
45
import '../mappers/code_mapper.dart';
56
import '../mappers/delimiter_mapper.dart';
7+
import '../mappers/embed_mapper.dart';
68
import '../mappers/header_mapper.dart';
79
import '../mappers/image_mapper.dart';
10+
import '../mappers/link_tool_mapper.dart';
811
import '../mappers/list_mapper.dart';
912
import '../mappers/paragraph_mapper.dart';
1013
import '../mappers/quote_mapper.dart';
14+
import '../mappers/raw_mapper.dart';
1115
import '../mappers/table_mapper.dart';
1216
import '../mappers/warning_mapper.dart';
1317

@@ -29,6 +33,10 @@ class BlockTypeRegistry {
2933
register(const ChecklistMapper());
3034
register(const TableMapper());
3135
register(const WarningMapper());
36+
register(const EmbedMapper());
37+
register(const LinkToolMapper());
38+
register(const AttachesMapper());
39+
register(const RawMapper());
3240
}
3341

3442
/// Registers a [BlockMapper]. Overwrites any existing mapper for the same type.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import '../block_entity.dart';
2+
3+
class AttachesBlock extends BlockEntity {
4+
final String url;
5+
final String? name;
6+
final String? extension;
7+
8+
/// File size in bytes. Null if not provided.
9+
final int? size;
10+
11+
final String? title;
12+
13+
const AttachesBlock({
14+
required this.url,
15+
this.name,
16+
this.extension,
17+
this.size,
18+
this.title,
19+
});
20+
21+
@override
22+
String get type => 'attaches';
23+
24+
@override
25+
Map<String, dynamic> toJson() => {
26+
'file': {
27+
'url': url,
28+
if (name != null) 'name': name,
29+
if (extension != null) 'extension': extension,
30+
if (size != null) 'size': size,
31+
},
32+
'title': title ?? '',
33+
};
34+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import '../block_entity.dart';
2+
3+
class EmbedBlock extends BlockEntity {
4+
/// The service name (e.g. 'youtube', 'vimeo', 'codepen').
5+
final String service;
6+
7+
/// Original source URL as entered by the user.
8+
final String source;
9+
10+
/// Embed URL (typically an iframe src). Not used for rendering in Flutter,
11+
/// but preserved for round-trip JSON serialization.
12+
final String embed;
13+
14+
final int? width;
15+
final int? height;
16+
final String? caption;
17+
18+
const EmbedBlock({
19+
required this.service,
20+
required this.source,
21+
required this.embed,
22+
this.width,
23+
this.height,
24+
this.caption,
25+
});
26+
27+
@override
28+
String get type => 'embed';
29+
30+
@override
31+
Map<String, dynamic> toJson() => {
32+
'service': service,
33+
'source': source,
34+
'embed': embed,
35+
if (width != null) 'width': width,
36+
if (height != null) 'height': height,
37+
'caption': caption ?? '',
38+
};
39+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import '../block_entity.dart';
2+
3+
class LinkToolMeta {
4+
final String? title;
5+
final String? description;
6+
final String? imageUrl;
7+
8+
const LinkToolMeta({this.title, this.description, this.imageUrl});
9+
10+
Map<String, dynamic> toJson() => {
11+
if (title != null) 'title': title,
12+
if (description != null) 'description': description,
13+
if (imageUrl != null) 'image': {'url': imageUrl},
14+
};
15+
}
16+
17+
class LinkToolBlock extends BlockEntity {
18+
final String link;
19+
final LinkToolMeta? meta;
20+
21+
const LinkToolBlock({required this.link, this.meta});
22+
23+
@override
24+
String get type => 'linkTool';
25+
26+
@override
27+
Map<String, dynamic> toJson() => {
28+
'link': link,
29+
if (meta != null) 'meta': meta!.toJson(),
30+
};
31+
}

0 commit comments

Comments
 (0)