From 910bef07a40097d5629ecc5a61fbcb7a9862d6b6 Mon Sep 17 00:00:00 2001 From: Damien Harper Date: Sun, 26 Jun 2022 16:34:23 +0200 Subject: [PATCH] HTML export --- composer.json | 2 +- phpstan.neon | 2 +- src/Exporter/ExporterInterface.php | 10 + .../Html/Block/BlockquoteExporter.php | 17 + .../Html/Block/BulletListExporter.php | 17 + src/Exporter/Html/Block/CodeBlockExporter.php | 17 + src/Exporter/Html/Block/DocumentExporter.php | 17 + src/Exporter/Html/Block/ExpandExporter.php | 17 + src/Exporter/Html/Block/HeadingExporter.php | 20 ++ .../Html/Block/MediaGroupExporter.php | 17 + .../Html/Block/MediaSingleExporter.php | 17 + .../Html/Block/OrderedListExporter.php | 17 + src/Exporter/Html/Block/PanelExporter.php | 54 +++ src/Exporter/Html/Block/ParagraphExporter.php | 17 + src/Exporter/Html/Block/RuleExporter.php | 17 + src/Exporter/Html/Block/TableExporter.php | 18 + src/Exporter/Html/Child/ListItemExporter.php | 17 + src/Exporter/Html/Child/MediaExporter.php | 19 + src/Exporter/Html/Child/TableCellExporter.php | 33 ++ .../Html/Child/TableHeaderExporter.php | 17 + src/Exporter/Html/Child/TableRowExporter.php | 17 + src/Exporter/Html/HtmlExporter.php | 164 +++++++++ src/Exporter/Html/Inline/DateExporter.php | 28 ++ src/Exporter/Html/Inline/EmojiExporter.php | 22 ++ .../Html/Inline/HardbreakExporter.php | 18 + .../Html/Inline/InlineCardExporter.php | 25 ++ src/Exporter/Html/Inline/MentionExporter.php | 24 ++ src/Exporter/Html/Inline/StatusExporter.php | 24 ++ src/Exporter/Html/Inline/TextExporter.php | 25 ++ src/Exporter/Html/Mark/CodeExporter.php | 25 ++ src/Exporter/Html/Mark/EmExporter.php | 25 ++ src/Exporter/Html/Mark/LinkExporter.php | 27 ++ src/Exporter/Html/Mark/StrikeExporter.php | 25 ++ src/Exporter/Html/Mark/StrongExporter.php | 25 ++ src/Exporter/Html/Mark/SubsupExporter.php | 25 ++ src/Exporter/Html/Mark/TextColorExporter.php | 27 ++ src/Exporter/Html/Mark/UnderlineExporter.php | 25 ++ src/Node/Block/CodeBlock.php | 8 +- src/Node/Block/Expand.php | 5 + src/Node/Block/Heading.php | 5 + src/Node/Block/MediaSingle.php | 10 + src/Node/Block/Panel.php | 5 + src/Node/Block/Rule.php | 12 +- src/Node/Block/Table.php | 15 + src/Node/BlockNode.php | 5 + src/Node/Child/Media.php | 30 ++ src/Node/Child/TableCell.php | 20 ++ src/Node/Inline/Date.php | 5 + src/Node/Inline/Emoji.php | 15 + src/Node/Inline/InlineCard.php | 10 + src/Node/Inline/Mention.php | 25 ++ src/Node/Inline/Status.php | 20 ++ src/Node/Inline/Text.php | 19 + src/Node/InlineNode.php | 16 - src/Node/Mark/Link.php | 30 ++ src/Node/Mark/Subsup.php | 5 + src/Node/Mark/TextColor.php | 5 + .../Html/Block/BlockquoteExporterTest.php | 49 +++ .../Html/Block/DocumentExporterTest.php | 334 ++++++++++++++++++ 59 files changed, 1539 insertions(+), 22 deletions(-) create mode 100644 src/Exporter/ExporterInterface.php create mode 100644 src/Exporter/Html/Block/BlockquoteExporter.php create mode 100644 src/Exporter/Html/Block/BulletListExporter.php create mode 100644 src/Exporter/Html/Block/CodeBlockExporter.php create mode 100644 src/Exporter/Html/Block/DocumentExporter.php create mode 100644 src/Exporter/Html/Block/ExpandExporter.php create mode 100644 src/Exporter/Html/Block/HeadingExporter.php create mode 100644 src/Exporter/Html/Block/MediaGroupExporter.php create mode 100644 src/Exporter/Html/Block/MediaSingleExporter.php create mode 100644 src/Exporter/Html/Block/OrderedListExporter.php create mode 100644 src/Exporter/Html/Block/PanelExporter.php create mode 100644 src/Exporter/Html/Block/ParagraphExporter.php create mode 100644 src/Exporter/Html/Block/RuleExporter.php create mode 100644 src/Exporter/Html/Block/TableExporter.php create mode 100644 src/Exporter/Html/Child/ListItemExporter.php create mode 100644 src/Exporter/Html/Child/MediaExporter.php create mode 100644 src/Exporter/Html/Child/TableCellExporter.php create mode 100644 src/Exporter/Html/Child/TableHeaderExporter.php create mode 100644 src/Exporter/Html/Child/TableRowExporter.php create mode 100644 src/Exporter/Html/HtmlExporter.php create mode 100644 src/Exporter/Html/Inline/DateExporter.php create mode 100644 src/Exporter/Html/Inline/EmojiExporter.php create mode 100644 src/Exporter/Html/Inline/HardbreakExporter.php create mode 100644 src/Exporter/Html/Inline/InlineCardExporter.php create mode 100644 src/Exporter/Html/Inline/MentionExporter.php create mode 100644 src/Exporter/Html/Inline/StatusExporter.php create mode 100644 src/Exporter/Html/Inline/TextExporter.php create mode 100644 src/Exporter/Html/Mark/CodeExporter.php create mode 100644 src/Exporter/Html/Mark/EmExporter.php create mode 100644 src/Exporter/Html/Mark/LinkExporter.php create mode 100644 src/Exporter/Html/Mark/StrikeExporter.php create mode 100644 src/Exporter/Html/Mark/StrongExporter.php create mode 100644 src/Exporter/Html/Mark/SubsupExporter.php create mode 100644 src/Exporter/Html/Mark/TextColorExporter.php create mode 100644 src/Exporter/Html/Mark/UnderlineExporter.php create mode 100644 tests/Exporter/Html/Block/BlockquoteExporterTest.php create mode 100644 tests/Exporter/Html/Block/DocumentExporterTest.php diff --git a/composer.json b/composer.json index ccb628d..cef1d43 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ }, "require-dev": { "phpunit/phpunit": "^9.0", - "symfony/var-dumper": "^4.0|^5.0|^6.0" + "symfony/var-dumper": "^5.0|^6.0" }, "scripts": { "test": "php -d pcov.enabled=1 ./vendor/bin/phpunit --colors=always", diff --git a/phpstan.neon b/phpstan.neon index 54472b5..222bb84 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -7,4 +7,4 @@ parameters: ignoreErrors: # false positives - '~Class DH\\Adf\\.* constructor invoked with \d parameters?, .* required\.~' - - '~Parameter \#1 \$.* of class DH\\Adf\\Node\\.* constructor expects .*, DH\\Adf\\Node\\BlockNode\|null given\.~' + - '~Parameter \#1 \$.* of class DH\\Adf\\.* constructor expects .*, DH\\Adf\\Node\\(Block|Inline)Node(\|null)? given\.~' diff --git a/src/Exporter/ExporterInterface.php b/src/Exporter/ExporterInterface.php new file mode 100644 index 0000000..e7244d5 --- /dev/null +++ b/src/Exporter/ExporterInterface.php @@ -0,0 +1,10 @@ +tags = ['
', '
']; + } +} diff --git a/src/Exporter/Html/Block/BulletListExporter.php b/src/Exporter/Html/Block/BulletListExporter.php new file mode 100644 index 0000000..ea06d04 --- /dev/null +++ b/src/Exporter/Html/Block/BulletListExporter.php @@ -0,0 +1,17 @@ +tags = ['']; + } +} diff --git a/src/Exporter/Html/Block/CodeBlockExporter.php b/src/Exporter/Html/Block/CodeBlockExporter.php new file mode 100644 index 0000000..3b36fff --- /dev/null +++ b/src/Exporter/Html/Block/CodeBlockExporter.php @@ -0,0 +1,17 @@ +tags = ['
', '
']; + } +} diff --git a/src/Exporter/Html/Block/DocumentExporter.php b/src/Exporter/Html/Block/DocumentExporter.php new file mode 100644 index 0000000..199d077 --- /dev/null +++ b/src/Exporter/Html/Block/DocumentExporter.php @@ -0,0 +1,17 @@ +tags = ['
', '
']; + } +} diff --git a/src/Exporter/Html/Block/ExpandExporter.php b/src/Exporter/Html/Block/ExpandExporter.php new file mode 100644 index 0000000..aac00f9 --- /dev/null +++ b/src/Exporter/Html/Block/ExpandExporter.php @@ -0,0 +1,17 @@ +tags = ['
'.$node->getTitle().'
', '
']; + } +} diff --git a/src/Exporter/Html/Block/HeadingExporter.php b/src/Exporter/Html/Block/HeadingExporter.php new file mode 100644 index 0000000..f3033b5 --- /dev/null +++ b/src/Exporter/Html/Block/HeadingExporter.php @@ -0,0 +1,20 @@ +tags = [ + 'getLevel().'>', + 'getLevel().'>', + ]; + } +} diff --git a/src/Exporter/Html/Block/MediaGroupExporter.php b/src/Exporter/Html/Block/MediaGroupExporter.php new file mode 100644 index 0000000..019aea9 --- /dev/null +++ b/src/Exporter/Html/Block/MediaGroupExporter.php @@ -0,0 +1,17 @@ +tags = ['
', '
']; + } +} diff --git a/src/Exporter/Html/Block/MediaSingleExporter.php b/src/Exporter/Html/Block/MediaSingleExporter.php new file mode 100644 index 0000000..75b54a4 --- /dev/null +++ b/src/Exporter/Html/Block/MediaSingleExporter.php @@ -0,0 +1,17 @@ +tags = ['
', '
']; + } +} diff --git a/src/Exporter/Html/Block/OrderedListExporter.php b/src/Exporter/Html/Block/OrderedListExporter.php new file mode 100644 index 0000000..2133cd1 --- /dev/null +++ b/src/Exporter/Html/Block/OrderedListExporter.php @@ -0,0 +1,17 @@ +tags = ['
    ', '
']; + } +} diff --git a/src/Exporter/Html/Block/PanelExporter.php b/src/Exporter/Html/Block/PanelExporter.php new file mode 100644 index 0000000..76a54c9 --- /dev/null +++ b/src/Exporter/Html/Block/PanelExporter.php @@ -0,0 +1,54 @@ +node instanceof Panel); + + switch ($this->node->getPanelType()) { + case Panel::ERROR: + $icon = ''; + + break; + + case Panel::WARNING: + $icon = ''; + + break; + + case Panel::SUCCESS: + $icon = ''; + + break; + + case Panel::NOTE: + $icon = ''; + + break; + + case Panel::INFO: + default: + $icon = ''; + + break; + } + + $outputs = []; + foreach ($this->node->getContent() as $child) { + $class = self::NODE_MAPPING[$child::class]; + $exporter = new $class($child); + $outputs[] = $exporter->export(); + } + $output = implode('', $outputs); + + return '
'.$icon.'
'.$output.'
'; + } +} diff --git a/src/Exporter/Html/Block/ParagraphExporter.php b/src/Exporter/Html/Block/ParagraphExporter.php new file mode 100644 index 0000000..c52c049 --- /dev/null +++ b/src/Exporter/Html/Block/ParagraphExporter.php @@ -0,0 +1,17 @@ +tags = ['

', '

']; + } +} diff --git a/src/Exporter/Html/Block/RuleExporter.php b/src/Exporter/Html/Block/RuleExporter.php new file mode 100644 index 0000000..97154f1 --- /dev/null +++ b/src/Exporter/Html/Block/RuleExporter.php @@ -0,0 +1,17 @@ +tags = ['
']; + } +} diff --git a/src/Exporter/Html/Block/TableExporter.php b/src/Exporter/Html/Block/TableExporter.php new file mode 100644 index 0000000..6ad8d2f --- /dev/null +++ b/src/Exporter/Html/Block/TableExporter.php @@ -0,0 +1,18 @@ +tags = [sprintf('', $node->getLayout()), '
']; + } +} diff --git a/src/Exporter/Html/Child/ListItemExporter.php b/src/Exporter/Html/Child/ListItemExporter.php new file mode 100644 index 0000000..84d9e37 --- /dev/null +++ b/src/Exporter/Html/Child/ListItemExporter.php @@ -0,0 +1,17 @@ +tags = ['
  • ', '
  • ']; + } +} diff --git a/src/Exporter/Html/Child/MediaExporter.php b/src/Exporter/Html/Child/MediaExporter.php new file mode 100644 index 0000000..fc67e02 --- /dev/null +++ b/src/Exporter/Html/Child/MediaExporter.php @@ -0,0 +1,19 @@ +tags = ['

    Atlassian Media API is not publicly available at the moment.

    ', '
    ']; +// $this->tags = ['
    ', '
    ']; + } +} diff --git a/src/Exporter/Html/Child/TableCellExporter.php b/src/Exporter/Html/Child/TableCellExporter.php new file mode 100644 index 0000000..78496f3 --- /dev/null +++ b/src/Exporter/Html/Child/TableCellExporter.php @@ -0,0 +1,33 @@ +getBackground()) { + $attributes[] = sprintf('style="background-color: %s"', $node->getBackground()); + } + if (null !== $node->getColspan()) { + $attributes[] = sprintf('colspan="%s"', $node->getColspan()); + } + if (null !== $node->getRowspan()) { + $attributes[] = sprintf('rowspan="%s"', $node->getRowspan()); + } + // TODO: support colwidth + + $this->tags = [ + sprintf('', implode(' ', $attributes)), + '', + ]; + } +} diff --git a/src/Exporter/Html/Child/TableHeaderExporter.php b/src/Exporter/Html/Child/TableHeaderExporter.php new file mode 100644 index 0000000..e3a3de0 --- /dev/null +++ b/src/Exporter/Html/Child/TableHeaderExporter.php @@ -0,0 +1,17 @@ +tags = ['', '']; + } +} diff --git a/src/Exporter/Html/Child/TableRowExporter.php b/src/Exporter/Html/Child/TableRowExporter.php new file mode 100644 index 0000000..c40c7f8 --- /dev/null +++ b/src/Exporter/Html/Child/TableRowExporter.php @@ -0,0 +1,17 @@ +tags = ['', '']; + } +} diff --git a/src/Exporter/Html/HtmlExporter.php b/src/Exporter/Html/HtmlExporter.php new file mode 100644 index 0000000..8eddd9f --- /dev/null +++ b/src/Exporter/Html/HtmlExporter.php @@ -0,0 +1,164 @@ + DocumentExporter::class, + Blockquote::class => BlockquoteExporter::class, + BulletList::class => BulletListExporter::class, + CodeBlock::class => CodeBlockExporter::class, + Heading::class => HeadingExporter::class, + MediaGroup::class => MediaGroupExporter::class, + MediaSingle::class => MediaSingleExporter::class, + OrderedList::class => OrderedListExporter::class, + Panel::class => PanelExporter::class, + Paragraph::class => ParagraphExporter::class, + Rule::class => RuleExporter::class, + Table::class => TableExporter::class, + Expand::class => ExpandExporter::class, + + // child nodes + ListItem::class => ListItemExporter::class, + TableCell::class => TableCellExporter::class, + TableHeader::class => TableHeaderExporter::class, + TableRow::class => TableRowExporter::class, + Media::class => MediaExporter::class, + InlineCard::class => InlineCardExporter::class, + + // inline nodes + Emoji::class => EmojiExporter::class, + Hardbreak::class => HardbreakExporter::class, + Mention::class => MentionExporter::class, + Text::class => TextExporter::class, + Status::class => StatusExporter::class, + Date::class => DateExporter::class, + + // mark nodes + Em::class => EmExporter::class, + Strong::class => StrongExporter::class, + Code::class => CodeExporter::class, + Strike::class => StrikeExporter::class, + Subsup::class => SubsupExporter::class, + Underline::class => UnderlineExporter::class, + Link::class => LinkExporter::class, + TextColor::class => TextColorExporter::class, + ]; + + protected Node $node; + protected array $tags = []; + + public function __construct(Node $node) + { + $this->node = $node; + } + + public function export(): string + { + $outputs = []; + + if ($this->node instanceof BlockNode) { + // $node has children (content) => iterate over them + foreach ($this->node->getContent() as $child) { + $class = self::NODE_MAPPING[$child::class]; + $exporter = new $class($child); + $outputs[] = $exporter->export(); + } + } elseif ($this->node instanceof InlineNode) { + // $node doesn't have children but can have marks + $class = self::NODE_MAPPING[$this->node::class]; + $exporter = new $class($this->node); + $outputs[] = $exporter->export(); + } + + $output = implode('', $outputs); + + if (0 === \count($this->tags)) { + // no wrapping tags + return $output; + } + + if (1 === \count($this->tags)) { + // no closing tag + return $this->tags[0].$output; + } + + // opening and closing tags + return $this->tags[0].$output.$this->tags[1]; + } +} diff --git a/src/Exporter/Html/Inline/DateExporter.php b/src/Exporter/Html/Inline/DateExporter.php new file mode 100644 index 0000000..a95bbf0 --- /dev/null +++ b/src/Exporter/Html/Inline/DateExporter.php @@ -0,0 +1,28 @@ +tags = ['', '']; + } + + public function export(): string + { + \assert($this->node instanceof Date); + + $timestamp = (int) $this->node->getTimestamp(); + $date = (new DateTimeImmutable())->setTimestamp($timestamp / 1000); + + return $this->tags[0].$date->format('Y-m-d').$this->tags[1]; + } +} diff --git a/src/Exporter/Html/Inline/EmojiExporter.php b/src/Exporter/Html/Inline/EmojiExporter.php new file mode 100644 index 0000000..111d3a0 --- /dev/null +++ b/src/Exporter/Html/Inline/EmojiExporter.php @@ -0,0 +1,22 @@ +node instanceof Emoji); + + return sprintf( + ':%s:', + $this->node->getId(), + $this->node->getShortName() + ); + } +} diff --git a/src/Exporter/Html/Inline/HardbreakExporter.php b/src/Exporter/Html/Inline/HardbreakExporter.php new file mode 100644 index 0000000..0821f1a --- /dev/null +++ b/src/Exporter/Html/Inline/HardbreakExporter.php @@ -0,0 +1,18 @@ +node instanceof Hardbreak); + + return '
    '; + } +} diff --git a/src/Exporter/Html/Inline/InlineCardExporter.php b/src/Exporter/Html/Inline/InlineCardExporter.php new file mode 100644 index 0000000..fa35e41 --- /dev/null +++ b/src/Exporter/Html/Inline/InlineCardExporter.php @@ -0,0 +1,25 @@ +tags = ['
    ', '
    ']; + } + + public function export(): string + { + \assert($this->node instanceof InlineCard); + + return $this->tags[0].$this->node->getUrl().$this->tags[1]; + } +} diff --git a/src/Exporter/Html/Inline/MentionExporter.php b/src/Exporter/Html/Inline/MentionExporter.php new file mode 100644 index 0000000..ceeb34f --- /dev/null +++ b/src/Exporter/Html/Inline/MentionExporter.php @@ -0,0 +1,24 @@ +tags = ['', '']; + } + + public function export(): string + { + \assert($this->node instanceof Mention); + + return $this->tags[0].$this->node->getText().$this->tags[1]; + } +} diff --git a/src/Exporter/Html/Inline/StatusExporter.php b/src/Exporter/Html/Inline/StatusExporter.php new file mode 100644 index 0000000..bf701d4 --- /dev/null +++ b/src/Exporter/Html/Inline/StatusExporter.php @@ -0,0 +1,24 @@ +tags = [sprintf('', $node->getColor()), '']; + } + + public function export(): string + { + \assert($this->node instanceof Status); + + return $this->tags[0].$this->node->getText().$this->tags[1]; + } +} diff --git a/src/Exporter/Html/Inline/TextExporter.php b/src/Exporter/Html/Inline/TextExporter.php new file mode 100644 index 0000000..70e8701 --- /dev/null +++ b/src/Exporter/Html/Inline/TextExporter.php @@ -0,0 +1,25 @@ +node instanceof Text); + $output = $this->node->getText(); + + foreach ($this->node->getMarks() as $mark) { + $class = self::NODE_MAPPING[$mark::class]; + $exporter = new $class($mark, $output); + $output = $exporter->export(); + } + + return $output; + } +} diff --git a/src/Exporter/Html/Mark/CodeExporter.php b/src/Exporter/Html/Mark/CodeExporter.php new file mode 100644 index 0000000..4f8f184 --- /dev/null +++ b/src/Exporter/Html/Mark/CodeExporter.php @@ -0,0 +1,25 @@ +text = $text; + $this->tags = ['
    ', '
    ']; + } + + public function export(): string + { + return $this->tags[0].$this->text.$this->tags[1]; + } +} diff --git a/src/Exporter/Html/Mark/EmExporter.php b/src/Exporter/Html/Mark/EmExporter.php new file mode 100644 index 0000000..fefca3c --- /dev/null +++ b/src/Exporter/Html/Mark/EmExporter.php @@ -0,0 +1,25 @@ +text = $text; + $this->tags = ['', '']; + } + + public function export(): string + { + return $this->tags[0].$this->text.$this->tags[1]; + } +} diff --git a/src/Exporter/Html/Mark/LinkExporter.php b/src/Exporter/Html/Mark/LinkExporter.php new file mode 100644 index 0000000..ab43930 --- /dev/null +++ b/src/Exporter/Html/Mark/LinkExporter.php @@ -0,0 +1,27 @@ +text = $text; + $this->tags = ['', '']; + } + + public function export(): string + { + \assert($this->node instanceof Link); + + return sprintf($this->tags[0], $this->node->getHref(), $this->node->getTitle()).$this->text.$this->tags[1]; + } +} diff --git a/src/Exporter/Html/Mark/StrikeExporter.php b/src/Exporter/Html/Mark/StrikeExporter.php new file mode 100644 index 0000000..1b6078a --- /dev/null +++ b/src/Exporter/Html/Mark/StrikeExporter.php @@ -0,0 +1,25 @@ +text = $text; + $this->tags = ['', '']; + } + + public function export(): string + { + return $this->tags[0].$this->text.$this->tags[1]; + } +} diff --git a/src/Exporter/Html/Mark/StrongExporter.php b/src/Exporter/Html/Mark/StrongExporter.php new file mode 100644 index 0000000..782ad19 --- /dev/null +++ b/src/Exporter/Html/Mark/StrongExporter.php @@ -0,0 +1,25 @@ +text = $text; + $this->tags = ['', '']; + } + + public function export(): string + { + return $this->tags[0].$this->text.$this->tags[1]; + } +} diff --git a/src/Exporter/Html/Mark/SubsupExporter.php b/src/Exporter/Html/Mark/SubsupExporter.php new file mode 100644 index 0000000..c25699b --- /dev/null +++ b/src/Exporter/Html/Mark/SubsupExporter.php @@ -0,0 +1,25 @@ +text = $text; + $this->tags = 'sub' === $node->getType() ? ['', ''] : ['', '']; + } + + public function export(): string + { + return $this->tags[0].$this->text.$this->tags[1]; + } +} diff --git a/src/Exporter/Html/Mark/TextColorExporter.php b/src/Exporter/Html/Mark/TextColorExporter.php new file mode 100644 index 0000000..8d2e2f5 --- /dev/null +++ b/src/Exporter/Html/Mark/TextColorExporter.php @@ -0,0 +1,27 @@ +text = $text; + $this->tags = ['', '']; + } + + public function export(): string + { + \assert($this->node instanceof TextColor); + + return sprintf($this->tags[0], $this->node->getColor()).$this->text.$this->tags[1]; + } +} diff --git a/src/Exporter/Html/Mark/UnderlineExporter.php b/src/Exporter/Html/Mark/UnderlineExporter.php new file mode 100644 index 0000000..719db0d --- /dev/null +++ b/src/Exporter/Html/Mark/UnderlineExporter.php @@ -0,0 +1,25 @@ +text = $text; + $this->tags = ['', '']; + } + + public function export(): string + { + return $this->tags[0].$this->text.$this->tags[1]; + } +} diff --git a/src/Node/Block/CodeBlock.php b/src/Node/Block/CodeBlock.php index 85a7361..a4bc5fc 100644 --- a/src/Node/Block/CodeBlock.php +++ b/src/Node/Block/CodeBlock.php @@ -32,9 +32,8 @@ public function __construct(?string $language = null, ?BlockNode $parent = null) public static function load(array $data, ?BlockNode $parent = null): self { self::checkNodeData(static::class, $data, ['attrs']); - self::checkRequiredKeys(['language'], $data['attrs']); - $node = new self($data['attrs']['language'], $parent); + $node = new self($data['attrs']['language'] ?? null, $parent); // set content if defined if (\array_key_exists('content', $data)) { @@ -49,6 +48,11 @@ public static function load(array $data, ?BlockNode $parent = null): self return $node; } + public function getLanguage(): ?string + { + return $this->language; + } + protected function attrs(): array { $attrs = parent::attrs(); diff --git a/src/Node/Block/Expand.php b/src/Node/Block/Expand.php index 2d8446f..532fd60 100644 --- a/src/Node/Block/Expand.php +++ b/src/Node/Block/Expand.php @@ -76,6 +76,11 @@ public static function load(array $data, ?BlockNode $parent = null): self return $node; } + public function getTitle(): ?string + { + return $this->title; + } + protected function attrs(): array { $attrs = parent::attrs(); diff --git a/src/Node/Block/Heading.php b/src/Node/Block/Heading.php index bd9f785..aacb770 100644 --- a/src/Node/Block/Heading.php +++ b/src/Node/Block/Heading.php @@ -51,6 +51,11 @@ public static function load(array $data, ?BlockNode $parent = null): self return $node; } + public function getLevel(): int + { + return $this->level; + } + protected function attrs(): array { $attrs = parent::attrs(); diff --git a/src/Node/Block/MediaSingle.php b/src/Node/Block/MediaSingle.php index 8bfcfb4..d14802e 100644 --- a/src/Node/Block/MediaSingle.php +++ b/src/Node/Block/MediaSingle.php @@ -72,6 +72,16 @@ public static function load(array $data, ?BlockNode $parent = null): self return $node; } + public function getLayout(): string + { + return $this->layout; + } + + public function getWidth(): ?int + { + return $this->width; + } + protected function attrs(): array { $attrs = parent::attrs(); diff --git a/src/Node/Block/Panel.php b/src/Node/Block/Panel.php index 0024397..ac6f6d2 100644 --- a/src/Node/Block/Panel.php +++ b/src/Node/Block/Panel.php @@ -67,6 +67,11 @@ public static function load(array $data, ?BlockNode $parent = null): self return $node; } + public function getPanelType(): string + { + return $this->panelType; + } + protected function attrs(): array { $attrs = parent::attrs(); diff --git a/src/Node/Block/Rule.php b/src/Node/Block/Rule.php index e460d65..8ce29c7 100644 --- a/src/Node/Block/Rule.php +++ b/src/Node/Block/Rule.php @@ -4,13 +4,21 @@ namespace DH\Adf\Node\Block; -use DH\Adf\Node\InlineNode; +use DH\Adf\Node\BlockNode; use JsonSerializable; /** * @see https://developer.atlassian.com/cloud/jira/platform/apis/document/nodes/rule */ -class Rule extends InlineNode implements JsonSerializable +class Rule extends BlockNode implements JsonSerializable { protected string $type = 'rule'; + + public function jsonSerialize(): array + { + $result = parent::jsonSerialize(); + unset($result['content']); + + return $result; + } } diff --git a/src/Node/Block/Table.php b/src/Node/Block/Table.php index acf0b94..21e3351 100644 --- a/src/Node/Block/Table.php +++ b/src/Node/Block/Table.php @@ -65,6 +65,21 @@ public static function load(array $data, ?BlockNode $parent = null): self return $node; } + public function getLayout(): string + { + return $this->layout; + } + + public function isNumberColumnEnabled(): bool + { + return $this->isNumberColumnEnabled; + } + + public function getLocalId(): ?string + { + return $this->localId; + } + protected function attrs(): array { $attrs = parent::attrs(); diff --git a/src/Node/BlockNode.php b/src/Node/BlockNode.php index e2136d9..9dac9bc 100644 --- a/src/Node/BlockNode.php +++ b/src/Node/BlockNode.php @@ -47,6 +47,11 @@ public static function load(array $data, ?self $parent = null): self return $node; } + public function getContent(): array + { + return $this->content; + } + protected function append(Node $node): void { foreach ($this->allowedContentTypes as $type) { diff --git a/src/Node/Child/Media.php b/src/Node/Child/Media.php index d8a4b4b..b3fbd3b 100644 --- a/src/Node/Child/Media.php +++ b/src/Node/Child/Media.php @@ -55,6 +55,36 @@ public static function load(array $data, ?BlockNode $parent = null): self ); } + public function getId(): string + { + return $this->id; + } + + public function getMediaType(): string + { + return $this->mediaType; + } + + public function getCollection(): string + { + return $this->collection; + } + + public function getOccurrenceKey(): ?string + { + return $this->occurrenceKey; + } + + public function getWidth(): ?int + { + return $this->width; + } + + public function getHeight(): ?int + { + return $this->height; + } + protected function attrs(): array { $attrs = parent::attrs(); diff --git a/src/Node/Child/TableCell.php b/src/Node/Child/TableCell.php index d59b91c..b46fc20 100644 --- a/src/Node/Child/TableCell.php +++ b/src/Node/Child/TableCell.php @@ -93,6 +93,26 @@ public static function load(array $data, ?BlockNode $parent = null): self return $node; } + public function getBackground(): ?string + { + return $this->background; + } + + public function getColspan(): ?int + { + return $this->colspan; + } + + public function getRowspan(): ?int + { + return $this->rowspan; + } + + public function getColwidth(): ?array + { + return $this->colwidth; + } + protected function attrs(): array { $attrs = parent::attrs(); diff --git a/src/Node/Inline/Date.php b/src/Node/Inline/Date.php index cb7f830..a6257b4 100644 --- a/src/Node/Inline/Date.php +++ b/src/Node/Inline/Date.php @@ -26,6 +26,11 @@ public static function load(array $data): self return new self($data['attrs']['timestamp']); } + public function getTimestamp(): string + { + return $this->timestamp; + } + protected function attrs(): array { return [ diff --git a/src/Node/Inline/Emoji.php b/src/Node/Inline/Emoji.php index fff9d74..d68ed84 100644 --- a/src/Node/Inline/Emoji.php +++ b/src/Node/Inline/Emoji.php @@ -31,6 +31,21 @@ public static function load(array $data): self return new self(trim($data['attrs']['shortName'], ' \t\n\r\0\x0B:'), $data['attrs']['id'] ?? null, $data['attrs']['text'] ?? null); } + public function getShortName(): string + { + return $this->shortName; + } + + public function getId(): ?string + { + return $this->id; + } + + public function getText(): ?string + { + return $this->text; + } + protected function attrs(): array { $attrs = parent::attrs(); diff --git a/src/Node/Inline/InlineCard.php b/src/Node/Inline/InlineCard.php index 3cba032..bf71a7a 100644 --- a/src/Node/Inline/InlineCard.php +++ b/src/Node/Inline/InlineCard.php @@ -35,6 +35,16 @@ public static function load(array $data, ?BlockNode $parent = null): self return new self($data['attrs']['url'] ?? null, $data['attrs']['data'] ?? null); } + public function getData(): ?string + { + return $this->data; + } + + public function getUrl(): ?string + { + return $this->url; + } + protected function attrs(): array { $attrs = parent::attrs(); diff --git a/src/Node/Inline/Mention.php b/src/Node/Inline/Mention.php index 8196744..e88a1f0 100644 --- a/src/Node/Inline/Mention.php +++ b/src/Node/Inline/Mention.php @@ -68,6 +68,31 @@ public static function load(array $data, ?BlockNode $parent = null): self ); } + public function getType(): string + { + return $this->type; + } + + public function getId(): string + { + return $this->id; + } + + public function getText(): string + { + return $this->text; + } + + public function getAccessLevel(): ?string + { + return $this->accessLevel; + } + + public function getUserType(): ?string + { + return $this->userType; + } + protected function attrs(): array { $attrs = parent::attrs(); diff --git a/src/Node/Inline/Status.php b/src/Node/Inline/Status.php index f44dcfd..7478599 100644 --- a/src/Node/Inline/Status.php +++ b/src/Node/Inline/Status.php @@ -57,6 +57,26 @@ public static function load(array $data): self ); } + public function getText(): string + { + return $this->text; + } + + public function getColor(): string + { + return $this->color; + } + + public function getLocalId(): ?string + { + return $this->localId; + } + + public function getStyle(): ?string + { + return $this->style; + } + protected function attrs(): array { $attrs = parent::attrs(); diff --git a/src/Node/Inline/Text.php b/src/Node/Inline/Text.php index f0820b3..9b08575 100644 --- a/src/Node/Inline/Text.php +++ b/src/Node/Inline/Text.php @@ -15,6 +15,7 @@ class Text extends InlineNode { protected string $type = 'text'; private string $text; + private array $marks = []; public function __construct(string $text, MarkNode ...$marks) { @@ -28,11 +29,19 @@ public function __construct(string $text, MarkNode ...$marks) public function jsonSerialize(): array { $result = parent::jsonSerialize(); + if ($this->marks) { + $result['marks'] = $this->marks; + } $result['text'] = $this->text; return $result; } + public function getMarks(): array + { + return $this->marks; + } + public static function load(array $data): self { self::checkNodeData(static::class, $data, ['text']); @@ -49,4 +58,14 @@ public static function load(array $data): self return new self($data['text'], ...$args); } + + public function getText(): string + { + return $this->text; + } + + protected function addMark(MarkNode $mark): void + { + $this->marks[] = $mark; + } } diff --git a/src/Node/InlineNode.php b/src/Node/InlineNode.php index 464bfdb..1108c8b 100644 --- a/src/Node/InlineNode.php +++ b/src/Node/InlineNode.php @@ -6,20 +6,4 @@ abstract class InlineNode extends Node { - private array $marks = []; - - public function jsonSerialize(): array - { - $result = parent::jsonSerialize(); - if ($this->marks) { - $result['marks'] = $this->marks; - } - - return $result; - } - - protected function addMark(MarkNode $mark): void - { - $this->marks[] = $mark; - } } diff --git a/src/Node/Mark/Link.php b/src/Node/Mark/Link.php index 099cf67..3de69a2 100644 --- a/src/Node/Mark/Link.php +++ b/src/Node/Mark/Link.php @@ -42,6 +42,36 @@ public static function load(array $data, ?BlockNode $parent = null): self ); } + public function getType(): string + { + return $this->type; + } + + public function getHref(): string + { + return $this->href; + } + + public function getTitle(): ?string + { + return $this->title; + } + + public function getId(): ?string + { + return $this->id; + } + + public function getCollection(): ?string + { + return $this->collection; + } + + public function getOccurrenceKey(): ?string + { + return $this->occurrenceKey; + } + protected function attrs(): array { $attrs = [ diff --git a/src/Node/Mark/Subsup.php b/src/Node/Mark/Subsup.php index 7d4512e..6767664 100644 --- a/src/Node/Mark/Subsup.php +++ b/src/Node/Mark/Subsup.php @@ -27,6 +27,11 @@ public static function load(array $data): self return new self($data['attrs']['type'] ?? 'sup'); } + public function getType(): string + { + return $this->subType; + } + protected function attrs(): array { return [ diff --git a/src/Node/Mark/TextColor.php b/src/Node/Mark/TextColor.php index 044ae9f..7f9c9c4 100644 --- a/src/Node/Mark/TextColor.php +++ b/src/Node/Mark/TextColor.php @@ -27,6 +27,11 @@ public static function load(array $data): self return new self($data['attrs']['color'] ?? 'black'); } + public function getColor(): string + { + return $this->color; + } + protected function attrs(): array { return [ diff --git a/tests/Exporter/Html/Block/BlockquoteExporterTest.php b/tests/Exporter/Html/Block/BlockquoteExporterTest.php new file mode 100644 index 0000000..281a2e3 --- /dev/null +++ b/tests/Exporter/Html/Block/BlockquoteExporterTest.php @@ -0,0 +1,49 @@ +', $exporter->export()); + } + + public function testBlockquoteWithText(): void + { + $node = (new Blockquote()) + ->paragraph() + ->text('This is a text inside a paragraph wrapped into a blockquote.') + ->end() + ; + $exporter = new BlockquoteExporter($node); + + self::assertSame('

    This is a text inside a paragraph wrapped into a blockquote.

    ', $exporter->export()); + } + + public function testBlockquoteWithEmoji(): void + { + $node = (new Blockquote()) + ->paragraph() + ->emoji('poop', '1f4a9', '\\ud83d\\udca9') + ->end() + ; + $exporter = new BlockquoteExporter($node); + + self::assertSame('

    :poop:

    ', $exporter->export()); + } +} diff --git a/tests/Exporter/Html/Block/DocumentExporterTest.php b/tests/Exporter/Html/Block/DocumentExporterTest.php new file mode 100644 index 0000000..2b69715 --- /dev/null +++ b/tests/Exporter/Html/Block/DocumentExporterTest.php @@ -0,0 +1,334 @@ +', $exporter->export()); + } + + public function testDocumentWithEmptyParagraph(): void + { + $document = (new Document()) + ->paragraph() + ->end() + ; + $exporter = new DocumentExporter($document); + + self::assertSame('

    ', $exporter->export()); + } + + public function testDocumentWithTextInParagraph(): void + { + $document = (new Document()) + ->paragraph() + ->color('Luke, ', 'red') + ->text('may ') + ->em('the ') + ->strong('force ') + ->text('be ') + ->underline('with ') + ->strike('you! ') + ->sub('Obi-Wan') + ->sup('Kenobi') + ->Link('Star Wars @ Wikipedia', 'https://wikipedia.org/wiki/Star_Wars') + ->end() + ; + $exporter = new DocumentExporter($document); + + self::assertSame('

    Luke, may the force be with you! Obi-WanKenobiStar Wars @ Wikipedia

    ', $exporter->export()); + } + + public function testDocumentWithMentionInParagraph(): void + { + $document = (new Document()) + ->paragraph() + ->mention('ABCDE-ABCDE-ABCDE-ABCDE', '@DarkVador', Mention::ACCESS_LEVEL_APPLICATION) + ->end() + ; + $exporter = new DocumentExporter($document); + + self::assertSame('

    @DarkVador

    ', $exporter->export()); + } + + public function testDocumentWithMultipleTextNodes(): void + { + $document = (new Document()) + ->paragraph() + ->text('Hello world') + ->text('How are you') + ->end() + ; + $exporter = new DocumentExporter($document); + + self::assertSame('

    Hello worldHow are you

    ', $exporter->export()); + } + + public function testDocumentWithBlockquote(): void + { + $document = (new Document()) + ->blockquote() + ->paragraph() + ->text('quoted text') + ->end() + ->end() + ; + $exporter = new DocumentExporter($document); + + self::assertSame('

    quoted text

    ', $exporter->export()); + } + + public function testDocumentWithHeading(): void + { + $document = (new Document()) + ->heading(3) + ->text('heading text') + ->end() + ; + $exporter = new DocumentExporter($document); + + self::assertSame('

    heading text

    ', $exporter->export()); + } + + public function testDocumentWithCodeblock(): void + { + $document = (new Document()) + ->codeblock('php') + ->text('var_dump($foo);') + ->end() + ; + $exporter = new DocumentExporter($document); + + self::assertSame('
    var_dump($foo);
    ', $exporter->export()); + } + + public function testDocumentWithBulletList(): void + { + $document = (new Document()) + ->bulletlist() + ->item() + ->paragraph() + ->text('item 1') + ->end() + ->end() + ->item() + ->paragraph() + ->text('item 2') + ->end() + ->end() + ->end() + ; + $exporter = new DocumentExporter($document); + + self::assertSame('
    • item 1

    • item 2

    ', $exporter->export()); + } + + public function testDocumentWithOrderedList(): void + { + $document = (new Document()) + ->orderedlist() + ->item() + ->paragraph() + ->text('item 1') + ->end() + ->end() + ->item() + ->paragraph() + ->text('item 2') + ->end() + ->end() + ->end() + ; + $exporter = new DocumentExporter($document); + + self::assertSame('
    1. item 1

    2. item 2

    ', $exporter->export()); + } + + public function testDocumentWithPanel(): void + { + $document = (new Document()) + ->panel(Panel::INFO) + ->paragraph() + ->text('panel text') + ->end() + ->end() + ; + $exporter = new DocumentExporter($document); + + self::assertSame('

    panel text

    ', $exporter->export()); + } + + public function testDocumentWithRule(): void + { + $document = (new Document()) + ->rule() + ->end() + ; + $exporter = new DocumentExporter($document); + + self::assertSame('

    ', $exporter->export()); + } + + public function testDocumentWithMediaSingle(): void + { + $document = (new Document()) + ->mediaSingle(MediaSingle::LAYOUT_WIDE) + ->media('6e7c7f2c-dd7a-499c-bceb-6f32bfbf30b5', Media::TYPE_FILE, 'my project files', 100, 200) + ->end() + ->end() + ; + $exporter = new DocumentExporter($document); + + self::assertSame('

    Atlassian Media API is not publicly available at the moment.

    ', $exporter->export()); + } + + public function testDocumentWithMediaGroup(): void + { + $document = (new Document()) + ->mediaGroup() + ->media('6e7c7f2c-dd7a-499c-bceb-6f32bfbf30b5', Media::TYPE_FILE, 'my project files', 100, 200) + ->end() + ->media('7a7c7f2c-dd7a-499c-bceb-6f32bfbf30c7', Media::TYPE_FILE, 'my project files', 100, 200) + ->end() + ->end() + ; + $exporter = new DocumentExporter($document); + + self::assertSame('

    Atlassian Media API is not publicly available at the moment.

    Atlassian Media API is not publicly available at the moment.

    ', $exporter->export()); + } + + public function testDocumentWithTable(): void + { + $document = (new Document()) + ->table(Table::LAYOUT_DEFAULT) + ->row() + ->header() + ->paragraph() + ->text('header 1') + ->end() + ->end() + ->header() + ->paragraph() + ->text('header 2') + ->end() + ->end() + ->end() + ->row() + ->cell() + ->paragraph() + ->text('cell 1') + ->end() + ->end() + ->cell() + ->paragraph() + ->text('cell 2') + ->end() + ->end() + ->end() + ->end() + ; + $exporter = new DocumentExporter($document); + + self::assertSame('

    header 1

    header 2

    cell 1

    cell 2

    ', $exporter->export()); + } + + public function testLoad(): void + { + $json = <<<'TXT' +{"version":1,"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"type":"text","text":"En-t\u00eate #1"}]},{"type":"paragraph","content":[{"type":"text","text":"lorem","marks":[{"type":"strong"},{"type":"textColor","attrs":{"color":"#008da6"}}]},{"type":"text","text":" "},{"type":"text","text":"ipsum","marks":[{"type":"textColor","attrs":{"color":"#6554c0"}}]},{"type":"text","text":" "},{"type":"text","text":"dolor","marks":[{"type":"strong"},{"type":"textColor","attrs":{"color":"#4c9aff"}}]},{"type":"text","text":" "},{"type":"text","text":"sit","marks":[{"type":"textColor","attrs":{"color":"#00b8d9"}}]},{"type":"text","text":" "},{"type":"text","text":"amet","marks":[{"type":"strong"},{"type":"textColor","attrs":{"color":"#ffc400"}}]}]},{"type":"paragraph","content":[{"type":"text","text":"lorem","marks":[{"type":"strong"}]},{"type":"text","text":" "},{"type":"text","text":"ipsum","marks":[{"type":"em"}]},{"type":"text","text":" "},{"type":"text","text":"dolor","marks":[{"type":"underline"}]},{"type":"text","text":" "},{"type":"text","text":"sit","marks":[{"type":"strike"}]},{"type":"text","text":" "},{"type":"text","text":"amet","marks":[{"type":"code"}]}]},{"type":"paragraph","content":[{"type":"text","text":"lorem","marks":[{"type":"strong"},{"type":"subsup","attrs":{"type":"sub"}}]},{"type":"text","text":" "},{"type":"text","text":"ipsum","marks":[{"type":"em"},{"type":"subsup","attrs":{"type":"sup"}}]}]},{"type":"bulletList","content":[{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"item 1"}]}]},{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"item 2"}]},{"type":"bulletList","content":[{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"item 2.1"}]}]},{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"item 2.2"}]}]}]}]},{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"item 3"}]}]}]},{"type":"paragraph","content":[{"type":"text","text":"This is a link!","marks":[{"type":"link","attrs":{"href":"https://www.google.fr","title":"Google"}}]}]},{"type":"heading","attrs":{"level":2},"content":[{"type":"text","text":"En-t\u00eate #2"}]},{"type":"paragraph","content":[{"type":"text","text":"lorem ipsum"}]},{"type":"orderedList","content":[{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"item 1"}]}]},{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"item 2"}]}]},{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"item 3"}]},{"type":"orderedList","content":[{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"item 3.1"}]}]},{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"item 3.2"}]}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"type":"text","text":"En-t\u00eate #3"}]},{"type":"paragraph","content":[{"type":"text","text":"lorem ipsum"}]},{"type":"paragraph","content":[{"type":"inlineCard","attrs":{"url":"https:\/\/github.com"}},{"type":"text","text":" "}]},{"type":"mediaSingle","attrs":{"layout":"align-start"},"content":[{"type":"media","attrs":{"id":"d7174b86-2fb7-4126-991d-0e2fc9f0dac7","type":"file","collection":"","width":483,"height":130}}]},{"type":"paragraph","content":[{"type":"text","text":"Yo "},{"type":"mention","attrs":{"id":"60196ed3cd564b0068440c33","text":"@Damien Harper","accessLevel":""}},{"type":"text","text":", tu peux check ce ticket please "},{"type":"emoji","attrs":{"shortName":":poop:","id":"1f4a9","text":"\ud83d\udca9"}},{"type":"text","text":" "}]},{"type":"table","attrs":{"isNumberColumnEnabled":false,"layout":"default","localId":"e77ccf12-2262-42ea-a9f2-979c14ff1efc"},"content":[{"type":"tableRow","content":[{"type":"tableHeader","content":[{"type":"paragraph","content":[{"type":"text","text":"Col 1","marks":[{"type":"strong"}]}]}]},{"type":"tableHeader","content":[{"type":"paragraph","content":[{"type":"text","text":"Col 2","marks":[{"type":"strong"}]}]}]},{"type":"tableHeader","content":[{"type":"paragraph","content":[{"type":"text","text":"Col 3","marks":[{"type":"strong"}]}]}]}]},{"type":"tableRow","content":[{"type":"tableCell","content":[{"type":"paragraph","content":[{"type":"text","text":"lorem","marks":[{"type":"strong"}]}]}]},{"type":"tableCell","content":[{"type":"paragraph","content":[{"type":"text","text":"ipsum","marks":[{"type":"em"}]}]}]},{"type":"tableCell","content":[{"type":"bulletList","content":[{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"item 1"}]}]},{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"item 2"}]}]}]}]}]},{"type":"tableRow","content":[{"type":"tableCell","content":[{"type":"paragraph","content":[{"type":"text","text":"dolor"}]}]},{"type":"tableCell","content":[{"type":"paragraph","content":[{"type":"text","text":"sit"}]}]},{"type":"tableCell","content":[{"type":"paragraph","content":[{"type":"text","text":"amet","marks":[{"type":"strong"},{"type":"textColor","attrs":{"color":"#00b8d9"}},{"type":"underline"}]}]}]}]}]},{"type":"rule"},{"type":"codeBlock","attrs":{"language":"php"},"content":[{"type":"text","text":"version;\n\n return $result;\n }\n}\n"}]},{"type":"panel","attrs":{"panelType":"info"},"content":[{"type":"paragraph","content":[{"type":"text","text":"Info news"}]},{"type":"orderedList","content":[{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"item "},{"type":"text","text":"1","marks":[{"type":"strong"}]}]}]},{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"item "},{"type":"text","text":"2","marks":[{"type":"strong"}]}]}]}]}]},{"type":"panel","attrs":{"panelType":"warning"},"content":[{"type":"paragraph","content":[{"type":"text","text":"warning!"}]}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"type":"text","text":"lorem ipsum ","marks":[{"type":"em"}]}]},{"type":"paragraph","content":[{"type":"text","text":"dolor sit amet","marks":[{"type":"em"}]}]}]},{"type":"heading","attrs":{"level":1},"content":[{"type":"text","text":"En-t\u00eate #1 bis"}]},{"type":"paragraph","content":[{"type":"status","attrs":{"text":"In progress","color":"blue","localId":"05c0d929-8e76-4469-94f8-0762cc0216bf","style":""}},{"type":"text","text":" "},{"type":"status","attrs":{"text":"TODO","color":"neutral","localId":"9163f1e7-d30a-451b-90d3-eb43b505b38c","style":""}},{"type":"text","text":" "},{"type":"status","attrs":{"text":"Done","color":"green","localId":"bf2a0f38-c488-49ab-8faa-e749c6e37841","style":""}},{"type":"text","text":" "}]},{"type":"paragraph","content":[{"type":"date","attrs":{"timestamp":"1655424000000"}},{"type":"text","text":" "}]},{"type":"paragraph","content":[]},{"type":"expand","attrs":{"title":"Section masquable"},"content":[{"type":"paragraph","content":[{"type":"text","text":"Texte qui peut se cacher "},{"type":"emoji","attrs":{"shortName":":slight_smile:","id":"1f642","text":"\ud83d\ude42"}},{"type":"text","text":" "}]}]}]} +TXT; + $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR); + $document = Document::load($data); + + \assert($document instanceof Document); + $exporter = new DocumentExporter($document); + + $expected = <<<'TXT' +

    En-tête #1

    lorem ipsum dolor sit amet

    lorem ipsum dolor sit

    amet

    lorem ipsum

    • item 1

    • item 2

      • item 2.1

      • item 2.2

    • item 3

    This is a link!

    En-tête #2

    lorem ipsum

    1. item 1

    2. item 2

    3. item 3

      1. item 3.1

      2. item 3.2

    En-tête #3

    lorem ipsum

    https://github.com

    Atlassian Media API is not publicly available at the moment.

    Yo @Damien Harper, tu peux check ce ticket please :poop:

    Col 1

    Col 2

    Col 3

    lorem

    ipsum

    • item 1

    • item 2

    dolor

    sit

    amet


    version;
    +
    +        return $result;
    +    }
    +}
    +

    Info news

    1. item 1

    2. item 2

    warning!

    lorem ipsum

    dolor sit amet

    En-tête #1 bis

    In progress TODO Done

    2022-06-17

    Section masquable

    Texte qui peut se cacher :slight_smile:

    +TXT; + + self::assertSame($expected, $exporter->export()); + } +}