Skip to content

Commit

Permalink
Merge pull request #41 from devaryakjha/feat/tables
Browse files Browse the repository at this point in the history
feat: adds basic tables support
  • Loading branch information
devaryakjha authored Dec 26, 2024
2 parents 0d8986f + 5e2f943 commit e393d9a
Show file tree
Hide file tree
Showing 42 changed files with 1,222 additions and 365 deletions.
17 changes: 8 additions & 9 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,10 @@
- [x] Paragraphs (`<p>`)
- [x] Headings (`<h1>` to `<h6>`)
- [x] Divisions (`<div>`)
- [ ] Articles (`<article>`)
- [ ] Sections (`<section>`)
- [ ] Aside (`<aside>`)
- [x] Articles (`<article>`)
- [x] Sections (`<section>`)

- [ ] Text Elements
- [x] Text Elements

- [x] Basic text nodes
- [x] Emphasis (`<em>`, `<i>`)
Expand All @@ -30,21 +29,21 @@
- [x] Subscript/Superscript (`<sub>`, `<sup>`)
- [x] Quotes (`<blockquote>`, `<q>`)

- [ ] Lists
- [x] Lists

- [ ] Unordered lists (`<ul>`, `<li>`)
- [ ] Ordered lists (`<ol>`, `<li>`)
- [x] Unordered lists (`<ul>`, `<li>`)
- [x] Ordered lists (`<ol>`, `<li>`)
- [ ] Description lists (`<dl>`, `<dt>`, `<dd>`)

- [ ] Tables

- [ ] Basic table structure
- [x] Basic table structure
- [ ] Table headers
- [ ] Colspan/Rowspan support
- [ ] Responsive table layout

- [ ] Media
- [ ] Images with `NetworkImage`
- [x] Images with `NetworkImage`
- [ ] Responsive image sizing
- [ ] Image loading states
- [ ] Lazy loading implementation
Expand Down
70 changes: 70 additions & 0 deletions examples/tagflow/lib/screens/table_example.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import 'package:tagflow_example/widgets/example_page.dart';

const _html = r'''
<h3>Basic Table</h3>
<table>
<tr>
<th>Header 1</th>
<th>Header 2</th>
<th>Header 3</th>
</tr>
<tr>
<td>Row 1, Cell 1</td>
<td>Row 1, Cell 2</td>
<td>Row 1, Cell 3</td>
</tr>
<tr>
<td>Row 2, Cell 1</td>
<td>Row 2, Cell 2</td>
<td>Row 2, Cell 3</td>
</tr>
</table>
<h3>Table with Colspan and Rowspan</h3>
<table>
<tr>
<th colspan="2">Merged Header</th>
<th>Header 3</th>
</tr>
<tr>
<td rowspan="2">Spans 2 Rows</td>
<td>Row 1, Cell 2</td>
<td>Row 1, Cell 3</td>
</tr>
<tr>
<td>Row 2, Cell 2</td>
<td>Row 2, Cell 3</td>
</tr>
</table>
<h3>Styled Table</h3>
<table style="border: 1px solid #ddd; width: 100%;">
<tr style="background-color: #f5f5f5;">
<th style="padding: 8px; text-align: left;">Product</th>
<th style="padding: 8px; text-align: right;">Price</th>
<th style="padding: 8px; text-align: center;">Stock</th>
</tr>
<tr>
<td style="padding: 8px;">Widget A</td>
<td style="padding: 8px; text-align: right;">$10.00</td>
<td style="padding: 8px; text-align: center;">In Stock</td>
</tr>
<tr style="background-color: #f9f9f9; color: #333;">
<td style="padding: 8px;">Widget B</td>
<td style="padding: 8px; text-align: right;">$15.00</td>
<td style="padding: 8px; text-align: center;">Low Stock</td>
</tr>
<tr>
<td style="padding: 8px;">Widget C</td>
<td style="padding: 8px; text-align: right;">$20.00</td>
<td style="padding: 8px; text-align: center;">Out of Stock</td>
</tr>
</table>
''';

final class TableExample extends ExamplePage {
const TableExample({super.key, super.title = 'Table'});

@override
String get html => _html;
}
15 changes: 8 additions & 7 deletions examples/tagflow/lib/utils/examples.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:tagflow_example/screens/article_example.dart';
import 'package:tagflow_example/screens/code_example.dart';
import 'package:tagflow_example/screens/image_example.dart';
import 'package:tagflow_example/screens/table_example.dart';
import 'package:tagflow_example/screens/typography_example.dart';
import 'package:tagflow_example/widgets/example_page.dart';

Expand Down Expand Up @@ -49,13 +50,13 @@ final allExamples = <Example>[
icon: Icons.article,
),
// table example
// Example(
// title: 'Table',
// description: 'A demonstration of HTML table rendering',
// path: '/table',
// builder: (context) => const TableExample(),
// icon: Icons.table_chart,
// ),
Example(
title: 'Table',
description: 'A demonstration of HTML table rendering',
path: '/table',
builder: (context) => const TableExample(),
icon: Icons.table_chart,
),
// // list example
// Example(
// title: 'Lists',
Expand Down
8 changes: 6 additions & 2 deletions melos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,13 @@ scripts:
coverage:
run: melos exec -c 1 --fail-fast -- "flutter test --coverage"
description: Run tests with coverage and generate coverage report

packageFilters:
dirExists: test

combine-coverage:
run: |
mkdir -p coverage
lcov --add-tracefile packages/tagflow/coverage/lcov.info -o coverage/lcov.info
description: Combine coverage reports from all packages
description: Combine coverage reports from all packages
packageFilters:
dirExists: coverage
40 changes: 26 additions & 14 deletions packages/tagflow/lib/src/converter/converter.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// lib/src/converter/converter.dart
// ignore_for_file: lines_longer_than_80_chars

import 'dart:developer';

import 'package:flutter/widgets.dart';
import 'package:tagflow/tagflow.dart';

/// Base interface for element converters
abstract class ElementConverter {
abstract class ElementConverter<T extends TagflowNode> {
/// Create a new element converter
const ElementConverter();

Expand All @@ -19,7 +20,7 @@ abstract class ElementConverter {
Set<String> get supportedTags;

/// Whether this converter can handle the given element
bool canHandle(TagflowElement element) {
bool canHandle(TagflowNode element) {
for (final selector in supportedTags) {
if (_matchesSelector(element, selector)) {
return true;
Expand All @@ -29,7 +30,7 @@ abstract class ElementConverter {
}

/// Check if element matches a selector
bool _matchesSelector(TagflowElement element, String selector) {
bool _matchesSelector(TagflowNode element, String selector) {
// Handle negation
if (selector.startsWith('!')) {
log('Negation selector: $selector');
Expand All @@ -41,7 +42,7 @@ abstract class ElementConverter {
}

/// Match positive selectors (without negation)
bool _matchPositiveSelector(TagflowElement element, String selector) {
bool _matchPositiveSelector(TagflowNode element, String selector) {
// Simple tag match
if (!selector.contains(' ')) {
return selector == element.tag;
Expand Down Expand Up @@ -85,18 +86,28 @@ abstract class ElementConverter {

/// Convert the element to a widget
Widget convert(
TagflowElement element,
T element,
BuildContext context,
TagflowConverter converter,
);

/// Get the computed style for an element
TagflowStyle resolveStyle(TagflowElement element, BuildContext context) {
TagflowStyle resolveStyle(TagflowNode element, BuildContext context) {
return TagflowThemeProvider.of(context).resolveStyle(element);
}

@override
String toString() => '$runtimeType(supportedTags: $supportedTags)';

/// Lookup the first parent of the given tag
TagflowNode? lookupParent(TagflowNode element, String tag) {
var parent = element.parent;
while (parent != null) {
if (parent.tag == tag) return parent;
parent = parent.parent;
}
return null;
}
}

/// Main converter that orchestrates the conversion process
Expand Down Expand Up @@ -133,23 +144,23 @@ class TagflowConverter {
const ContainerConverter(),
const ListConverter(),
const ListItemConverter(),
const TableConverter(),
const TableCellConverter(),
]);
}

/// Convert a TagflowElement to a Widget
Widget convert(TagflowElement element, BuildContext context) {
Widget convert(TagflowNode element, BuildContext context) {
// Try custom converters first
for (final converter in _customConverters) {
if (converter.canHandle(element)) {
log('Using custom converter: $converter');
return converter.convert(element, context, this);
}
}

// Then try built-in converters
for (final converter in _builtInConverters) {
if (converter.canHandle(element)) {
log('Using built-in converter: $converter');
return converter.convert(element, context, this);
}
}
Expand All @@ -160,7 +171,7 @@ class TagflowConverter {

/// Convert a list of elements to widgets
List<Widget> convertChildren(
List<TagflowElement> elements,
List<TagflowNode> elements,
BuildContext context,
) {
return elements.map((e) => convert(e, context)).toList();
Expand All @@ -175,19 +186,20 @@ class DefaultConverter extends ElementConverter {
Set<String> get supportedTags => const {};

@override
bool canHandle(TagflowElement element) => true;
bool canHandle(TagflowNode element) => true;

@override
Widget convert(
TagflowElement element,
TagflowNode element,
BuildContext context,
TagflowConverter converter,
) {
if (element.isTextNode) {
final style = resolveStyle(element, context);

return Text(
element.textContent ?? '',
style: style.textStyle,
style: style.textStyleWithColor,
textAlign: style.textAlign,
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:tagflow/tagflow.dart';

/// A converter for the `blockquote` tag.
final class BlockquoteConverter extends ElementConverter {
final class BlockquoteConverter extends ElementConverter<TagflowElement> {
/// Create a new blockquote converter
const BlockquoteConverter();

Expand Down Expand Up @@ -32,7 +32,7 @@ final class BlockquoteConverter extends ElementConverter {
}
}

final class BlockquoteFooterConverter extends ElementConverter {
final class BlockquoteFooterConverter extends ElementConverter<TagflowElement> {
const BlockquoteFooterConverter();

@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ final class CodeConverter extends TextConverter {
const CodeConverter();

@override
bool shouldForceWidgetSpan(TagflowElement element) {
bool shouldForceWidgetSpan(TagflowNode element) {
return super.shouldForceWidgetSpan(element) ||
['code', 'pre'].contains(element.tag);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'package:flutter/widgets.dart';
import 'package:tagflow/tagflow.dart';

/// Converts container elements (div, section, article, etc.)
class ContainerConverter extends ElementConverter {
class ContainerConverter extends ElementConverter<TagflowElement> {
const ContainerConverter();

@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export 'container_converter.dart';
export 'hr_converter.dart';
export 'img_converter.dart';
export 'list_converter.dart';
export 'table_converter.dart';
export 'text_converter.dart';
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:tagflow/tagflow.dart';

/// Converter for horizontal rules
final class HrConverter extends ElementConverter {
final class HrConverter extends ElementConverter<TagflowElement> {
/// Create a new horizontal rule converter
const HrConverter();

Expand All @@ -22,7 +22,7 @@ final class HrConverter extends ElementConverter {
width: double.infinity,
border: Border(
bottom: BorderSide(
color: style.textStyle?.color ?? Colors.grey,
color: style.textStyleWithColor?.color ?? Colors.grey,
width: element.height ?? 1,
),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ import 'package:flutter/widgets.dart';
import 'package:tagflow/tagflow.dart';

/// Converter for img elements
final class ImgConverter extends ElementConverter {
final class ImgConverter extends ElementConverter<TagflowImgElement> {
/// Create a new img converter
const ImgConverter();

@override
Set<String> get supportedTags => {'img'};

String? _createSelectionText(TagflowElement element, BuildContext context) {
String? _createSelectionText(
TagflowImgElement element,
BuildContext context,
) {
final options = TagflowOptions.maybeOf(context);
final behavior = options?.selectable.imageSelectionBehavior;
final altText = element.alt;
Expand Down Expand Up @@ -44,7 +47,7 @@ final class ImgConverter extends ElementConverter {

@override
Widget convert(
TagflowElement element,
TagflowImgElement element,
BuildContext context,
TagflowConverter converter,
) {
Expand Down
Loading

0 comments on commit e393d9a

Please sign in to comment.