defmodule NLdoc.Conversion.Reader.Tiptap do @moduledoc """ Reader for convert_elementing Tiptap elements to Spec Resources. """ alias NLdoc.Conversion.Reader.Tiptap.{Attribute, Mark} alias NLdoc.Spec.{ BlockQuotation, DefinitionDetails, DefinitionList, DefinitionTerm, Document, Heading, Image, Link, ListItem, OrderedList, Paragraph, Preformatted, Table, TableCell, TableHeader, TableRow, Text, UnorderedList } # TODO: Support descriptors # This defines functions to be generated in the for-loops below. # # This is a tuple in format: # 1. Element name in Tiptap. # 2. Struct from Spec. # 3. Attributes as keyword list in format {spec_attribute: tiptap_attribute} # where spec_attribute is an atom and tiptap_attribute is a string. @element_mappings [ {"doc", Document, [id: "id", assets: "assets"]}, # TODO: Support attribute caption {"image", Image, [id: "id", source: "assetId", alternative_text: "alt", decorative: "isDecorative"]}, # TODO: Support attributes cite, caption/footer {"blockquote", BlockQuotation, [id: "id"]}, {"codeBlock", Preformatted, [id: "id"]}, {"heading", Heading, [id: "id", level: "level"]}, # TODO: Support attributes style type, reversed {"orderedList", OrderedList, [id: "id", start: "start"]}, # TODO: Support attribute style type {"bulletList", UnorderedList, [id: "id"]}, {"listItem", ListItem, [id: "id"]}, # TODO: Support attribute caption {"table", Table, [id: "id"]}, {"tableRow", TableRow, [id: "id"]}, {"tableCell", TableCell, [id: "id", rowspan: "rowspan", colspan: "colspan"]}, # TODO: Support attributes abbreviation, scope {"tableHeader", TableHeader, [id: "id", rowspan: "rowspan", colspan: "colspan"]}, {"definitionList", DefinitionList, [id: "id"]}, {"definitionTerm", DefinitionTerm, [id: "id"]}, {"definitionDetails", DefinitionDetails, [id: "id"]}, {"paragraph", Paragraph, [id: "id"]}, # Custom cases, will generate &convert_element/1 functions that won't be used. # Used for setting the attributes. {"_text", Text, [id: "id"]}, {"_link", Link, [id: "id", uri: "href", purpose: "purpose"]} ] @type tiptap_attribute_value :: String.t() | number() | boolean() @type tiptap_attribute :: {String.t(), tiptap_attribute_value()} @doc """ Converts Tiptap document to a NLdoc document. """ @spec convert(map()) :: Document.t() def convert(element = %{"type" => "doc"}), do: element |> convert_element() |> hd() @spec convert_element(map()) :: [NLdoc.Spec.object()] defp convert_element(%{"type" => "text", "text" => text, "marks" => marks}) when is_binary(text) and text !== "" and is_list(marks), do: %Text{text: text} |> Mark.apply(marks) |> post_process() for {tiptap_type, struct_module, mappings} <- @element_mappings, not String.starts_with?(tiptap_type, "_") do if Map.has_key?(struct_module.__struct__(), :children) do defp convert_element(element = %{"type" => unquote(tiptap_type)}) do children = element |> Map.get("content", []) |> Enum.flat_map(&convert_element/1) %unquote(struct_module){} |> Map.put(:children, children) |> Attribute.apply(element |> Map.get("attrs", %{}), unquote(mappings)) |> post_process() end else defp convert_element(element = %{"type" => unquote(tiptap_type)}), do: %unquote(struct_module){} |> Attribute.apply(element |> Map.get("attrs", %{}), unquote(mappings)) |> post_process() end end # Unsupported element defp convert_element(_), do: [] @spec post_process(t) :: [t] when t: term() defp post_process(%Image{source: nil}), do: [] defp post_process(element), do: element |> List.wrap() end