| j / k | Scroll down / up |
| gg | Scroll to top |
| G | Scroll to bottom |
| ⌘K / / | Open search |
| ? | Show this help |
| Esc | Close search / help |
| n / N | Next / previous section |
| h | Go to landing |
| d | Go to docs |
| : | Command mode — type section name |
Press ? or Esc to close
Server content is authored in TipTap JSON. The app renders it read-only with TipTapRenderer — there is no editor input widget, only a renderer.
Content arrives as a list of TipTapNode objects, each with a type and optional marks (bold, italic, link, etc.).
class TipTapNode {
final String type; // paragraph, heading, image, codeBlock, ...
final Map<String, dynamic>? attrs;
final List<TipTapMark>? marks;
final List<TipTapNode>? content;
} TipTapRenderer walks the node tree and switches on type. Images use CachedNetworkImage with a custom cache manager; mentions and links are tappable.
Widget _renderNode(TipTapNode node) {
switch (node.type) {
case 'paragraph':
return _renderParagraph(node);
case 'heading':
return _renderHeading(node);
case 'image':
return AppImage(src: node.attrs!['src'] as String);
case 'codeBlock':
return _CodeBlock(node);
case 'bulletList':
case 'orderedList':
return _renderList(node);
default:
return const SizedBox.shrink();
}
} TipTapRenderer(nodes: article.content)