From aaab11b84ab12cea6f7033dd80590b6a44b7b4d9 Mon Sep 17 00:00:00 2001 From: Ali Schlereth Date: Tue, 18 Nov 2025 20:02:07 -0700 Subject: [PATCH 1/3] Support table of contents keys --- lib/tip_tap/html_renderable.rb | 3 ++- lib/tip_tap/nodes/heading.rb | 7 +++++++ spec/tip_tap/nodes/heading_spec.rb | 23 ++++++++++++++++++----- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/lib/tip_tap/html_renderable.rb b/lib/tip_tap/html_renderable.rb index 963d69c..2be50f2 100644 --- a/lib/tip_tap/html_renderable.rb +++ b/lib/tip_tap/html_renderable.rb @@ -59,7 +59,8 @@ def to_html end def html_attributes - {style: inline_styles, class: html_class_name}.reject { |key, value| value.blank? } + attributes = {style: inline_styles, class: html_class_name}.reject { |key, value| value.blank? } + attributes.merge({"id" => attrs["id"], "data-toc-id" => attrs["data-toc-id"]}) if self.is_a?(TipTap::Nodes::Heading) end def inline_styles diff --git a/lib/tip_tap/nodes/heading.rb b/lib/tip_tap/nodes/heading.rb index 7a82a5c..d120fdd 100644 --- a/lib/tip_tap/nodes/heading.rb +++ b/lib/tip_tap/nodes/heading.rb @@ -8,6 +8,13 @@ class Heading < Node self.type_name = "heading" self.html_tag = proc { "h#{level}" } + def initialize(content = [], **attributes) + super(content, **attributes) + uuid = SecureRandom.uuid + @attrs["id"] = uuid + @attrs["data-toc-id"] = uuid + end + def text(text, marks: []) add_content(Text.new(text, marks: marks)) end diff --git a/spec/tip_tap/nodes/heading_spec.rb b/spec/tip_tap/nodes/heading_spec.rb index e7fd882..7af88c8 100644 --- a/spec/tip_tap/nodes/heading_spec.rb +++ b/spec/tip_tap/nodes/heading_spec.rb @@ -3,13 +3,17 @@ require "tip_tap" RSpec.describe TipTap::Nodes::Heading do + before do + allow(SecureRandom).to receive(:uuid).and_return("auto-uuid-999") + end + describe "to_html" do - it "returns a tag corresponding to the level attribute" do + it "returns a tag corresponding to the level attribute with auto-generated UUID" do node = TipTap::Nodes::Heading.from_json({content: [], attrs: {level: 2}}) html = node.to_html expect(html).to be_a(String) - expect(html).to eq("

") + expect(html).to eq('

') end context "when the textAlign attribute is present" do @@ -18,18 +22,27 @@ html = node.to_html expect(html).to be_a(String) - expect(html).to eq('

') + expect(html).to eq('

') end end end describe "level" do - it "returns a the level attribute" do + it "returns the level attribute" do node = TipTap::Nodes::Heading.from_json({content: [], attrs: {level: 2}}) expect(node.level).to eq(2) end end + describe "table of contents key" do + it "automatically generates and sets UUID in attrs" do + heading = TipTap::Nodes::Heading.new(level: 1) + + expect(heading.attrs["id"]).to eq("auto-uuid-999") + expect(heading.attrs["data-toc-id"]).to eq("auto-uuid-999") + end + end + describe "text" do it "adds a text node to the node" do heading = TipTap::Nodes::Heading.new(level: 1) @@ -44,7 +57,7 @@ node = TipTap::Nodes::Heading.new(level: 1) json = node.to_h - expect(json).to eq({type: "heading", attrs: {level: 1}, content: []}) + expect(json).to eq({type: "heading", attrs: {level: 1, id: "auto-uuid-999", "data-toc-id": "auto-uuid-999"}, content: []}) end end end From 898fb38cc1d303a52b3b8530c82b06b89ff01b1b Mon Sep 17 00:00:00 2001 From: Ali Schlereth Date: Wed, 19 Nov 2025 16:50:00 -0700 Subject: [PATCH 2/3] Mutate rather than return --- lib/tip_tap/html_renderable.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/tip_tap/html_renderable.rb b/lib/tip_tap/html_renderable.rb index 2be50f2..6b02c3b 100644 --- a/lib/tip_tap/html_renderable.rb +++ b/lib/tip_tap/html_renderable.rb @@ -60,7 +60,8 @@ def to_html def html_attributes attributes = {style: inline_styles, class: html_class_name}.reject { |key, value| value.blank? } - attributes.merge({"id" => attrs["id"], "data-toc-id" => attrs["data-toc-id"]}) if self.is_a?(TipTap::Nodes::Heading) + attributes.merge!({"id" => attrs["id"], "data-toc-id" => attrs["data-toc-id"]}) if self.is_a?(TipTap::Nodes::Heading) + attributes end def inline_styles From cb6cbe719179be3c5b64f15b29e0bad4daf3f8c3 Mon Sep 17 00:00:00 2001 From: Ali Schlereth Date: Thu, 20 Nov 2025 16:06:50 -0700 Subject: [PATCH 3/3] Move html_attributes changes to heading class --- lib/tip_tap/html_renderable.rb | 4 +--- lib/tip_tap/nodes/heading.rb | 11 +++++++++++ spec/tip_tap_spec.rb | 6 +++++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/lib/tip_tap/html_renderable.rb b/lib/tip_tap/html_renderable.rb index 6b02c3b..963d69c 100644 --- a/lib/tip_tap/html_renderable.rb +++ b/lib/tip_tap/html_renderable.rb @@ -59,9 +59,7 @@ def to_html end def html_attributes - attributes = {style: inline_styles, class: html_class_name}.reject { |key, value| value.blank? } - attributes.merge!({"id" => attrs["id"], "data-toc-id" => attrs["data-toc-id"]}) if self.is_a?(TipTap::Nodes::Heading) - attributes + {style: inline_styles, class: html_class_name}.reject { |key, value| value.blank? } end def inline_styles diff --git a/lib/tip_tap/nodes/heading.rb b/lib/tip_tap/nodes/heading.rb index d120fdd..04f8612 100644 --- a/lib/tip_tap/nodes/heading.rb +++ b/lib/tip_tap/nodes/heading.rb @@ -22,6 +22,17 @@ def text(text, marks: []) def level attrs["level"] end + + def html_attributes + # doc-toc-id comes from TipTap and Ruby symbols do not support - + # so we use string keys here instead. + { + "style" => inline_styles, + "class" => html_class_name, + "id" => attrs["id"], + "data-toc-id" => attrs["data-toc-id"] + }.reject { |key, value| value.blank? } + end end end end diff --git a/spec/tip_tap_spec.rb b/spec/tip_tap_spec.rb index eda94bd..dafa7a2 100644 --- a/spec/tip_tap_spec.rb +++ b/spec/tip_tap_spec.rb @@ -12,9 +12,13 @@ let(:json) { JSON.parse(File.read("spec/support/files/tiptap-state.json")) } let(:document) { TipTap::Document.from_json(json) } + before do + allow(SecureRandom).to receive(:uuid).and_return("auto-uuid-999") + end + it "parses the json and returns the html" do expect(document.to_html).to eq( - '

Site Summary Overview - May 2nd 2023

This is a site visit summary that is being synthesized by Chad Wilken.

  • Todo 1

  • Todo 2

  • Todo 3

This is a heading 2

This is a heading 3

  • This is a bullet item

  • This is another item

Final paragraph.

' + "

Site Summary Overview - May 2nd 2023

This is a site visit summary that is being synthesized by Chad Wilken.

  • Todo 1

  • Todo 2

  • Todo 3

This is a heading 2

This is a heading 3

  • This is a bullet item

  • This is another item

Final paragraph.

" ) end