Add unique ID to headings

The `name` field on the `paragraph` type contains a unique ID for the
paragraph. It's not guaranteed to be there, on images for example like
in the `fd8d091ab8ef` post, but it's there for everything else I can
find.

This enables deep linking. There's no way to get to the deep link other
than opening up the web console. I wanted to link every heading, but
you can actually have links in part of a heading so that's not tenable.
Maybe a "permalink" link next to every heading?
This commit is contained in:
Edward Loveall 2023-03-25 11:16:00 -04:00
parent 761e4ef170
commit cef1bc256d
No known key found for this signature in database
11 changed files with 67 additions and 17 deletions

View file

@ -8,6 +8,7 @@ describe EmbeddedConverter do
store = GistStore.new store = GistStore.new
paragraph = PostResponse::Paragraph.from_json <<-JSON paragraph = PostResponse::Paragraph.from_json <<-JSON
{ {
"name": "ab12",
"text": "", "text": "",
"type": "IFRAME", "type": "IFRAME",
"href": null, "href": null,
@ -44,6 +45,7 @@ describe EmbeddedConverter do
store = GistStore.new store = GistStore.new
paragraph = PostResponse::Paragraph.from_json <<-JSON paragraph = PostResponse::Paragraph.from_json <<-JSON
{ {
"name": "ab12",
"text": "", "text": "",
"type": "IFRAME", "type": "IFRAME",
"href": null, "href": null,
@ -73,6 +75,7 @@ describe EmbeddedConverter do
store = GistStore.new store = GistStore.new
paragraph = PostResponse::Paragraph.from_json <<-JSON paragraph = PostResponse::Paragraph.from_json <<-JSON
{ {
"name": "ab12",
"text": "", "text": "",
"type": "IFRAME", "type": "IFRAME",
"href": null, "href": null,

View file

@ -12,6 +12,7 @@ describe GistScanner do
) )
paragraphs = [ paragraphs = [
PostResponse::Paragraph.new( PostResponse::Paragraph.new(
name: "ab12",
text: "Check out this gist:", text: "Check out this gist:",
type: PostResponse::ParagraphType::P, type: PostResponse::ParagraphType::P,
markups: [] of PostResponse::Markup, markups: [] of PostResponse::Markup,
@ -20,6 +21,7 @@ describe GistScanner do
metadata: nil metadata: nil
), ),
PostResponse::Paragraph.new( PostResponse::Paragraph.new(
name: "ab13",
text: "", text: "",
type: PostResponse::ParagraphType::IFRAME, type: PostResponse::ParagraphType::IFRAME,
markups: [] of PostResponse::Markup, markups: [] of PostResponse::Markup,
@ -45,6 +47,7 @@ describe GistScanner do
) )
paragraphs = [ paragraphs = [
PostResponse::Paragraph.new( PostResponse::Paragraph.new(
name: "ab12",
text: "", text: "",
type: PostResponse::ParagraphType::IFRAME, type: PostResponse::ParagraphType::IFRAME,
markups: [] of PostResponse::Markup, markups: [] of PostResponse::Markup,
@ -78,6 +81,7 @@ describe GistScanner do
) )
paragraphs = [ paragraphs = [
PostResponse::Paragraph.new( PostResponse::Paragraph.new(
name: "ab12",
text: "", text: "",
type: PostResponse::ParagraphType::IFRAME, type: PostResponse::ParagraphType::IFRAME,
markups: [] of PostResponse::Markup, markups: [] of PostResponse::Markup,
@ -86,6 +90,7 @@ describe GistScanner do
metadata: nil metadata: nil
), ),
PostResponse::Paragraph.new( PostResponse::Paragraph.new(
name: "ab13",
text: "", text: "",
type: PostResponse::ParagraphType::IFRAME, type: PostResponse::ParagraphType::IFRAME,
markups: [] of PostResponse::Markup, markups: [] of PostResponse::Markup,

View file

@ -8,6 +8,7 @@ describe PageConverter do
paragraph_json = <<-JSON paragraph_json = <<-JSON
[ [
{ {
"name": "ab12",
"text": "#{title}", "text": "#{title}",
"type": "H3", "type": "H3",
"markups": [], "markups": [],
@ -28,6 +29,7 @@ describe PageConverter do
it "sets the author" do it "sets the author" do
post_json = <<-JSON post_json = <<-JSON
{ {
"name": "ab12",
"title": "This is a story", "title": "This is a story",
"createdAt": 0, "createdAt": 0,
"creator": { "creator": {
@ -52,6 +54,7 @@ describe PageConverter do
it "sets the publish date/time" do it "sets the publish date/time" do
post_json = <<-JSON post_json = <<-JSON
{ {
"name": "ab12",
"title": "This is a story", "title": "This is a story",
"createdAt": 1000, "createdAt": 1000,
"creator": { "creator": {
@ -77,6 +80,7 @@ describe PageConverter do
paragraph_json = <<-JSON paragraph_json = <<-JSON
[ [
{ {
"name": "ab12",
"text": "#{title}", "text": "#{title}",
"type": "H3", "type": "H3",
"markups": [], "markups": [],
@ -85,6 +89,7 @@ describe PageConverter do
"metadata": null "metadata": null
}, },
{ {
"name": "ab12",
"text": "Content", "text": "Content",
"type": "P", "type": "P",
"markups": [], "markups": [],
@ -117,6 +122,7 @@ def default_post_json(
) )
<<-JSON <<-JSON
{ {
"name": "ab12",
"title": "#{title}", "title": "#{title}",
"createdAt": 1628974309758, "createdAt": 1628974309758,
"creator": { "creator": {

View file

@ -8,6 +8,7 @@ describe ParagraphConverter do
paragraphs = Array(PostResponse::Paragraph).from_json <<-JSON paragraphs = Array(PostResponse::Paragraph).from_json <<-JSON
[ [
{ {
"name": "ab12",
"text": "Title", "text": "Title",
"type": "H3", "type": "H3",
"markups": [], "markups": [],
@ -17,7 +18,7 @@ describe ParagraphConverter do
} }
] ]
JSON JSON
expected = [Heading3.new(children: [Text.new(content: "Title")] of Child)] expected = [Heading3.new(children: [Text.new(content: "Title")] of Child, identifier: "ab12")]
result = ParagraphConverter.new.convert(paragraphs, gist_store) result = ParagraphConverter.new.convert(paragraphs, gist_store)
@ -29,6 +30,7 @@ describe ParagraphConverter do
paragraphs = Array(PostResponse::Paragraph).from_json <<-JSON paragraphs = Array(PostResponse::Paragraph).from_json <<-JSON
[ [
{ {
"name": "ab12",
"text": "inline code", "text": "inline code",
"type": "P", "type": "P",
"markups": [ "markups": [
@ -66,6 +68,7 @@ describe ParagraphConverter do
paragraphs = Array(PostResponse::Paragraph).from_json <<-JSON paragraphs = Array(PostResponse::Paragraph).from_json <<-JSON
[ [
{ {
"name": "ab12",
"text": "One", "text": "One",
"type": "ULI", "type": "ULI",
"markups": [], "markups": [],
@ -74,6 +77,7 @@ describe ParagraphConverter do
"metadata": null "metadata": null
}, },
{ {
"name": "ab13",
"text": "Two", "text": "Two",
"type": "ULI", "type": "ULI",
"markups": [], "markups": [],
@ -82,6 +86,7 @@ describe ParagraphConverter do
"metadata": null "metadata": null
}, },
{ {
"name": "ab14",
"text": "Not a list item", "text": "Not a list item",
"type": "P", "type": "P",
"markups": [], "markups": [],
@ -109,6 +114,7 @@ describe ParagraphConverter do
paragraphs = Array(PostResponse::Paragraph).from_json <<-JSON paragraphs = Array(PostResponse::Paragraph).from_json <<-JSON
[ [
{ {
"name": "ab12",
"text": "One", "text": "One",
"type": "OLI", "type": "OLI",
"markups": [], "markups": [],
@ -117,6 +123,7 @@ describe ParagraphConverter do
"metadata": null "metadata": null
}, },
{ {
"name": "ab13",
"text": "Two", "text": "Two",
"type": "OLI", "type": "OLI",
"markups": [], "markups": [],
@ -125,6 +132,7 @@ describe ParagraphConverter do
"metadata": null "metadata": null
}, },
{ {
"name": "ab14",
"text": "Not a list item", "text": "Not a list item",
"type": "P", "type": "P",
"markups": [], "markups": [],
@ -151,6 +159,7 @@ describe ParagraphConverter do
gist_store = GistStore.new gist_store = GistStore.new
paragraph = PostResponse::Paragraph.from_json <<-JSON paragraph = PostResponse::Paragraph.from_json <<-JSON
{ {
"name": "ab12",
"text": "Image by someuser", "text": "Image by someuser",
"type": "IMG", "type": "IMG",
"markups": [ "markups": [
@ -197,6 +206,7 @@ describe ParagraphConverter do
paragraphs = Array(PostResponse::Paragraph).from_json <<-JSON paragraphs = Array(PostResponse::Paragraph).from_json <<-JSON
[ [
{ {
"name": "ab12",
"text": "text", "text": "text",
"type": "H2", "type": "H2",
"markups": [], "markups": [],
@ -205,6 +215,7 @@ describe ParagraphConverter do
"metadata": null "metadata": null
}, },
{ {
"name": "ab13",
"text": "text", "text": "text",
"type": "H3", "type": "H3",
"markups": [], "markups": [],
@ -213,6 +224,7 @@ describe ParagraphConverter do
"metadata": null "metadata": null
}, },
{ {
"name": "ab14",
"text": "text", "text": "text",
"type": "H4", "type": "H4",
"markups": [], "markups": [],
@ -221,6 +233,7 @@ describe ParagraphConverter do
"metadata": null "metadata": null
}, },
{ {
"name": "ab15",
"text": "text", "text": "text",
"type": "P", "type": "P",
"markups": [], "markups": [],
@ -229,6 +242,7 @@ describe ParagraphConverter do
"metadata": null "metadata": null
}, },
{ {
"name": "ab16",
"text": "text", "text": "text",
"type": "PRE", "type": "PRE",
"markups": [], "markups": [],
@ -237,6 +251,7 @@ describe ParagraphConverter do
"metadata": null "metadata": null
}, },
{ {
"name": "ab17",
"text": "text", "text": "text",
"type": "BQ", "type": "BQ",
"markups": [], "markups": [],
@ -245,6 +260,7 @@ describe ParagraphConverter do
"metadata": null "metadata": null
}, },
{ {
"name": "ab18",
"text": "text", "text": "text",
"type": "PQ", "type": "PQ",
"markups": [], "markups": [],
@ -253,6 +269,7 @@ describe ParagraphConverter do
"metadata": null "metadata": null
}, },
{ {
"name": "ab19",
"text": "text", "text": "text",
"type": "ULI", "type": "ULI",
"markups": [], "markups": [],
@ -261,6 +278,7 @@ describe ParagraphConverter do
"metadata": null "metadata": null
}, },
{ {
"name": "ab20",
"text": "text", "text": "text",
"type": "OLI", "type": "OLI",
"markups": [], "markups": [],
@ -269,6 +287,7 @@ describe ParagraphConverter do
"metadata": null "metadata": null
}, },
{ {
"name": "ab21",
"text": "text", "text": "text",
"type": "IMG", "type": "IMG",
"markups": [], "markups": [],
@ -281,6 +300,7 @@ describe ParagraphConverter do
} }
}, },
{ {
"name": "ab22",
"text": "", "text": "",
"type": "IFRAME", "type": "IFRAME",
"markups": [], "markups": [],
@ -296,6 +316,7 @@ describe ParagraphConverter do
"metadata": null "metadata": null
}, },
{ {
"name": "ab23",
"text": "Mixtape", "text": "Mixtape",
"type": "MIXTAPE_EMBED", "type": "MIXTAPE_EMBED",
"href": null, "href": null,
@ -317,9 +338,9 @@ describe ParagraphConverter do
] ]
JSON JSON
expected = [ expected = [
Heading1.new([Text.new("text")] of Child), Heading1.new([Text.new("text")] of Child, identifier: "ab12"),
Heading2.new([Text.new("text")] of Child), Heading2.new([Text.new("text")] of Child, identifier: "ab13"),
Heading3.new([Text.new("text")] of Child), Heading3.new([Text.new("text")] of Child, identifier: "ab14"),
Paragraph.new([Text.new("text")] of Child), Paragraph.new([Text.new("text")] of Child),
Preformatted.new([Text.new("text")] of Child), Preformatted.new([Text.new("text")] of Child),
BlockQuote.new([Text.new("text")] of Child), # BQ BlockQuote.new([Text.new("text")] of Child), # BQ

View file

@ -184,13 +184,13 @@ describe PageContent do
nodes: [ nodes: [
Heading1.new(children: [ Heading1.new(children: [
Text.new(content: "Title!"), Text.new(content: "Title!"),
] of Child), ] of Child, identifier: "ab12"),
] of Child ] of Child
) )
html = PageContent.new(page: page).render_to_string html = PageContent.new(page: page).render_to_string
html.should eq %(<h1>Title!</h1>) html.should eq %(<h1 id="ab12">Title!</h1>)
end end
it "renders an H3" do it "renders an H3" do
@ -201,13 +201,13 @@ describe PageContent do
nodes: [ nodes: [
Heading2.new(children: [ Heading2.new(children: [
Text.new(content: "Title!"), Text.new(content: "Title!"),
] of Child), ] of Child, identifier: "ab12"),
] of Child ] of Child
) )
html = PageContent.new(page: page).render_to_string html = PageContent.new(page: page).render_to_string
html.should eq %(<h2>Title!</h2>) html.should eq %(<h2 id="ab12">Title!</h2>)
end end
it "renders an H4" do it "renders an H4" do
@ -218,13 +218,13 @@ describe PageContent do
nodes: [ nodes: [
Heading3.new(children: [ Heading3.new(children: [
Text.new(content: "In Conclusion..."), Text.new(content: "In Conclusion..."),
] of Child), ] of Child, identifier: "ab12"),
] of Child ] of Child
) )
html = PageContent.new(page: page).render_to_string html = PageContent.new(page: page).render_to_string
html.should eq %(<h3>In Conclusion...</h3>) html.should eq %(<h3 id="ab12">In Conclusion...</h3>)
end end
it "renders an image" do it "renders an image" do

View file

@ -16,15 +16,15 @@ class ParagraphConverter
when PostResponse::ParagraphType::H2 when PostResponse::ParagraphType::H2
paragraph = paragraphs.shift paragraph = paragraphs.shift
children = MarkupConverter.convert(paragraph.text, paragraph.markups) children = MarkupConverter.convert(paragraph.text, paragraph.markups)
node = Heading1.new(children: children) node = Heading1.new(children: children, identifier: paragraph.name || "")
when PostResponse::ParagraphType::H3 when PostResponse::ParagraphType::H3
paragraph = paragraphs.shift paragraph = paragraphs.shift
children = MarkupConverter.convert(paragraph.text, paragraph.markups) children = MarkupConverter.convert(paragraph.text, paragraph.markups)
node = Heading2.new(children: children) node = Heading2.new(children: children, identifier: paragraph.name || "")
when PostResponse::ParagraphType::H4 when PostResponse::ParagraphType::H4
paragraph = paragraphs.shift paragraph = paragraphs.shift
children = MarkupConverter.convert(paragraph.text, paragraph.markups) children = MarkupConverter.convert(paragraph.text, paragraph.markups)
node = Heading3.new(children: children) node = Heading3.new(children: children, identifier: paragraph.name || "")
when PostResponse::ParagraphType::IFRAME when PostResponse::ParagraphType::IFRAME
paragraph = paragraphs.shift paragraph = paragraphs.shift
node = EmbeddedConverter.convert(paragraph, gist_store) node = EmbeddedConverter.convert(paragraph, gist_store)

View file

@ -27,6 +27,7 @@ class MediumClient
content { content {
bodyModel { bodyModel {
paragraphs { paragraphs {
name
text text
type type
href href

View file

@ -93,15 +93,15 @@ class PageContent < BaseComponent
end end
def render_child(node : Heading1) def render_child(node : Heading1)
h1 { render_children(node.children) } h1(id: node.identifier) { render_children(node.children) }
end end
def render_child(node : Heading2) def render_child(node : Heading2)
h2 { render_children(node.children) } h2(id: node.identifier) { render_children(node.children) }
end end
def render_child(node : Heading3) def render_child(node : Heading3)
h3 { render_children(node.children) } h3(id: node.identifier) { render_children(node.children) }
end end
def render_child(child : Image) def render_child(child : Image)

View file

@ -41,12 +41,24 @@ module Nodes
end end
class Heading1 < Container class Heading1 < Container
getter identifier : String
def initialize(@children : Children, @identifier : String)
end
end end
class Heading2 < Container class Heading2 < Container
getter identifier : String
def initialize(@children : Children, @identifier : String)
end
end end
class Heading3 < Container class Heading3 < Container
getter identifier : String
def initialize(@children : Children, @identifier : String)
end
end end
class ListItem < Container class ListItem < Container

View file

@ -32,6 +32,7 @@ class PostResponse
end end
class Paragraph < Base class Paragraph < Base
property name : String?
property text : String? property text : String?
property type : ParagraphType property type : ParagraphType
property markups : Array(Markup) property markups : Array(Markup)
@ -40,6 +41,7 @@ class PostResponse
property metadata : Metadata? property metadata : Metadata?
def initialize( def initialize(
@name : String,
@text : String?, @text : String?,
@type : ParagraphType, @type : ParagraphType,
@markups : Array(Markup), @markups : Array(Markup),

View file

@ -1,3 +1,3 @@
module Scribe module Scribe
VERSION = "2022-11-06" VERSION = "2023-03-25"
end end