mirror of https://github.com/zedeus/nitter
notes: implement lists, headers and twemoji
This commit is contained in:
parent
63bb30ead7
commit
7127054a12
|
@ -432,13 +432,14 @@ proc parseGraphArticle*(js: JsonNode): Article =
|
||||||
|
|
||||||
for p in content{"blocks"}:
|
for p in content{"blocks"}:
|
||||||
var paragraph = ArticleParagraph(
|
var paragraph = ArticleParagraph(
|
||||||
text: p{"text"}.getStr
|
text: p{"text"}.getStr,
|
||||||
|
baseType: parseEnum[ArticleType](p{"type"}.getStr)
|
||||||
)
|
)
|
||||||
for sr in p{"inlineStyleRanges"}:
|
for sr in p{"inlineStyleRanges"}:
|
||||||
paragraph.inlineStyleRanges.add ArticleStyleRange(
|
paragraph.inlineStyleRanges.add ArticleStyleRange(
|
||||||
offset: sr{"offset"}.getInt,
|
offset: sr{"offset"}.getInt,
|
||||||
length: sr{"length"}.getInt,
|
length: sr{"length"}.getInt,
|
||||||
style: sr{"style"}.getStr
|
style: parseEnum[ArticleStyle](sr{"style"}.getStr)
|
||||||
)
|
)
|
||||||
for er in p{"entityRanges"}:
|
for er in p{"entityRanges"}:
|
||||||
paragraph.entityRanges.add ArticleEntityRange(
|
paragraph.entityRanges.add ArticleEntityRange(
|
||||||
|
@ -461,6 +462,8 @@ proc parseGraphArticle*(js: JsonNode): Article =
|
||||||
entity.mediaIds.add jMedia{"mediaId"}.getStr
|
entity.mediaIds.add jMedia{"mediaId"}.getStr
|
||||||
of ArticleEntityType.tweet:
|
of ArticleEntityType.tweet:
|
||||||
entity.tweetId = jEntity{"data", "tweetId"}.getStr
|
entity.tweetId = jEntity{"data", "tweetId"}.getStr
|
||||||
|
of ArticleEntityType.twemoji:
|
||||||
|
entity.twemoji = jEntity{"data", "url"}.getStr
|
||||||
else: discard
|
else: discard
|
||||||
|
|
||||||
result.entities.add entity
|
result.entities.add entity
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
background-color: var(--bg_panel);
|
background-color: var(--bg_panel);
|
||||||
|
font-family: sans-serif;
|
||||||
|
|
||||||
article {
|
article {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
@ -18,9 +19,12 @@
|
||||||
margin: 30px 0;
|
margin: 30px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p,
|
||||||
|
li {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-family: sans-serif;
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
margin: 30px 0;
|
margin: 30px 0;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
|
@ -37,6 +41,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
line-height: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
iframe {
|
iframe {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 400px;
|
height: 400px;
|
||||||
|
|
|
@ -127,13 +127,30 @@ type
|
||||||
|
|
||||||
ArticleParagraph* = object
|
ArticleParagraph* = object
|
||||||
text*: string
|
text*: string
|
||||||
|
baseType*: ArticleType
|
||||||
inlineStyleRanges*: seq[ArticleStyleRange]
|
inlineStyleRanges*: seq[ArticleStyleRange]
|
||||||
entityRanges*: seq[ArticleEntityRange]
|
entityRanges*: seq[ArticleEntityRange]
|
||||||
|
|
||||||
|
ArticleType* {.pure.} = enum
|
||||||
|
headerOne = "header-one"
|
||||||
|
headerTwo = "header-two"
|
||||||
|
headerThree = "header-three"
|
||||||
|
orderedListItem = "ordered-list-item"
|
||||||
|
unorderedListItem = "unordered-list-item"
|
||||||
|
unstyled = "unstyled"
|
||||||
|
atomic = "atomic"
|
||||||
|
unknown
|
||||||
|
|
||||||
ArticleStyleRange* = object
|
ArticleStyleRange* = object
|
||||||
offset*: int
|
offset*: int
|
||||||
length*: int
|
length*: int
|
||||||
style*: string
|
style*: ArticleStyle
|
||||||
|
|
||||||
|
ArticleStyle* {.pure.} = enum
|
||||||
|
bold = "BOLD"
|
||||||
|
italic = "ITALIC"
|
||||||
|
strikethrough = "STRIKETHROUGH"
|
||||||
|
unknown
|
||||||
|
|
||||||
ArticleEntityRange* = object
|
ArticleEntityRange* = object
|
||||||
offset*: int
|
offset*: int
|
||||||
|
@ -145,11 +162,13 @@ type
|
||||||
url*: string
|
url*: string
|
||||||
mediaIds*: seq[string]
|
mediaIds*: seq[string]
|
||||||
tweetId*: string
|
tweetId*: string
|
||||||
|
twemoji*: string
|
||||||
|
|
||||||
ArticleEntityType* {.pure.} = enum
|
ArticleEntityType* {.pure.} = enum
|
||||||
link = "LINK"
|
link = "LINK"
|
||||||
media = "MEDIA"
|
media = "MEDIA"
|
||||||
tweet = "TWEET"
|
tweet = "TWEET"
|
||||||
|
twemoji = "TWEMOJI"
|
||||||
unknown
|
unknown
|
||||||
|
|
||||||
Poll* = object
|
Poll* = object
|
||||||
|
|
|
@ -21,11 +21,31 @@ proc renderMiniAvatar(user: User; prefs: Prefs): VNode =
|
||||||
|
|
||||||
proc renderNoteParagraph(articleParagraph: ArticleParagraph; article: Article): VNode =
|
proc renderNoteParagraph(articleParagraph: ArticleParagraph; article: Article): VNode =
|
||||||
let text = articleParagraph.text
|
let text = articleParagraph.text
|
||||||
result = p.newVNode()
|
|
||||||
|
|
||||||
if articleParagraph.inlineStyleRanges.len > 0:
|
case articleParagraph.baseType
|
||||||
# Assume the style applies for the entire paragraph
|
of ArticleType.headerOne:
|
||||||
result.setAttr("style", "font-style:" & articleParagraph.inlineStyleRanges[0].style.toLowerAscii)
|
result = h1.newVNode()
|
||||||
|
of ArticleType.headerTwo:
|
||||||
|
result = h2.newVNode()
|
||||||
|
of ArticleType.headerThree:
|
||||||
|
result = h3.newVNode()
|
||||||
|
of ArticleType.orderedListItem:
|
||||||
|
result = li.newVNode()
|
||||||
|
of ArticleType.unorderedListItem:
|
||||||
|
result = li.newVNode()
|
||||||
|
else:
|
||||||
|
result = p.newVNode()
|
||||||
|
|
||||||
|
# Assume the style applies for the entire paragraph
|
||||||
|
for styleRange in articleParagraph.inlineStyleRanges:
|
||||||
|
case styleRange.style
|
||||||
|
of ArticleStyle.bold:
|
||||||
|
result.setAttr("style", "font-weight:bold")
|
||||||
|
of ArticleStyle.italic:
|
||||||
|
result.setAttr("style", "font-style:italic")
|
||||||
|
of ArticleStyle.strikethrough:
|
||||||
|
result.setAttr("style", "text-decoration:line-through")
|
||||||
|
else: discard
|
||||||
|
|
||||||
var last = 0
|
var last = 0
|
||||||
for er in articleParagraph.entityRanges:
|
for er in articleParagraph.entityRanges:
|
||||||
|
@ -45,6 +65,10 @@ proc renderNoteParagraph(articleParagraph: ArticleParagraph; article: Article):
|
||||||
let image = buildHtml(span(class="image")):
|
let image = buildHtml(span(class="image")):
|
||||||
img(src=url, alt="")
|
img(src=url, alt="")
|
||||||
result.add image
|
result.add image
|
||||||
|
of ArticleEntityType.twemoji:
|
||||||
|
let url = entity.twemoji
|
||||||
|
let emoji = buildHtml(img(src=url, alt=""))
|
||||||
|
result.add emoji
|
||||||
of ArticleEntityType.tweet:
|
of ArticleEntityType.tweet:
|
||||||
let url = fmt"/i/status/{entity.tweetId}/embed"
|
let url = fmt"/i/status/{entity.tweetId}/embed"
|
||||||
let iframe = buildHtml(iframe(src=url, loading="lazy", frameborder="0", style={maxWidth: "100%"}))
|
let iframe = buildHtml(iframe(src=url, loading="lazy", frameborder="0", style={maxWidth: "100%"}))
|
||||||
|
@ -61,18 +85,47 @@ proc renderNote*(article: Article; prefs: Prefs): VNode =
|
||||||
let cover = getSmallPic(article.coverImage)
|
let cover = getSmallPic(article.coverImage)
|
||||||
let author = article.user
|
let author = article.user
|
||||||
|
|
||||||
buildHtml(tdiv(class="note")):
|
# build header
|
||||||
img(class="cover", src=(cover), alt="")
|
let main = buildHtml(article):
|
||||||
|
h1: text article.title
|
||||||
article:
|
|
||||||
h1: text article.title
|
tdiv(class="author"):
|
||||||
|
renderMiniAvatar(author, prefs)
|
||||||
tdiv(class="author"):
|
linkUser(author, class="fullname")
|
||||||
renderMiniAvatar(author, prefs)
|
linkUser(author, class="username")
|
||||||
linkUser(author, class="fullname")
|
text " · "
|
||||||
linkUser(author, class="username")
|
text article.time.getShortTime
|
||||||
text " · "
|
|
||||||
text article.time.getShortTime
|
|
||||||
|
|
||||||
for paragraph in article.paragraphs:
|
# add paragraphs
|
||||||
renderNoteParagraph(paragraph, article)
|
var listType = ArticleType.unknown
|
||||||
|
var list: VNode = nil
|
||||||
|
|
||||||
|
for paragraph in article.paragraphs:
|
||||||
|
let node = renderNoteParagraph(paragraph, article)
|
||||||
|
|
||||||
|
let currentType = paragraph.baseType
|
||||||
|
if currentType in [ArticleType.orderedListItem, ArticleType.unorderedListItem]:
|
||||||
|
if currentType != listType:
|
||||||
|
# flush last list
|
||||||
|
if list != nil:
|
||||||
|
main.add list
|
||||||
|
list = nil
|
||||||
|
|
||||||
|
case currentType:
|
||||||
|
of ArticleType.orderedListItem:
|
||||||
|
list = ol.newVNode()
|
||||||
|
of ArticleType.unorderedListItem:
|
||||||
|
list = ul.newVNode()
|
||||||
|
else: discard
|
||||||
|
listType = currentType
|
||||||
|
list.add node
|
||||||
|
else:
|
||||||
|
if list != nil:
|
||||||
|
main.add list
|
||||||
|
list = nil
|
||||||
|
main.add node
|
||||||
|
|
||||||
|
buildHtml(tdiv(class="note")):
|
||||||
|
img(class="cover", src=(cover), alt="")
|
||||||
|
|
||||||
|
main
|
||||||
|
|
Loading…
Reference in New Issue