Community Notes support
This commit is contained in:
parent
f5fadca631
commit
1e9d4bae83
6 changed files with 81 additions and 0 deletions
|
@ -466,6 +466,14 @@ proc parseGraphTweet(js: JsonNode; isLegacy=false): Tweet =
|
||||||
if result.quote.isSome:
|
if result.quote.isSome:
|
||||||
result.quote = some(parseGraphTweet(js{"quoted_status_result", "result"}, isLegacy))
|
result.quote = some(parseGraphTweet(js{"quoted_status_result", "result"}, isLegacy))
|
||||||
|
|
||||||
|
with communityNote, js{"birdwatch_pivot"}:
|
||||||
|
let note = BirdwatchNote(
|
||||||
|
id: communityNote{"note", "rest_id"}.getId,
|
||||||
|
title: communityNote{"title"}.getStr,
|
||||||
|
)
|
||||||
|
note.expandBirdwatchEntities(communityNote{"subtitle"})
|
||||||
|
result.birdwatch = some(note)
|
||||||
|
|
||||||
proc parseGraphThread(js: JsonNode): tuple[thread: Chain; self: bool] =
|
proc parseGraphThread(js: JsonNode): tuple[thread: Chain; self: bool] =
|
||||||
for t in js{"content", "items"}:
|
for t in js{"content", "items"}:
|
||||||
let entryId = t{"entryId"}.getStr
|
let entryId = t{"entryId"}.getStr
|
||||||
|
|
|
@ -319,3 +319,28 @@ proc expandNoteTweetEntities*(tweet: Tweet; js: JsonNode) =
|
||||||
tweet.expandTextEntities(entities, text, textSlice)
|
tweet.expandTextEntities(entities, text, textSlice)
|
||||||
|
|
||||||
tweet.text = tweet.text.multiReplace((unicodeOpen, xmlOpen), (unicodeClose, xmlClose))
|
tweet.text = tweet.text.multiReplace((unicodeOpen, xmlOpen), (unicodeClose, xmlClose))
|
||||||
|
|
||||||
|
proc expandBirdwatchEntities*(note: BirdwatchNote; js: JsonNode) =
|
||||||
|
let
|
||||||
|
entities = ? js{"entities"}
|
||||||
|
text = js{"text"}.getStr
|
||||||
|
runes = text.toRunes
|
||||||
|
|
||||||
|
var replacements = newSeq[ReplaceSlice]()
|
||||||
|
|
||||||
|
for ent in entities:
|
||||||
|
let
|
||||||
|
# twitter devs in their infinite wisdom making these entities of all others 1 indexed
|
||||||
|
slice = (ent{"fromIndex"}.getInt - 1) .. (ent{"toIndex"}.getInt - 1)
|
||||||
|
refType = ent{"ref", "type"}.getStr
|
||||||
|
|
||||||
|
case refType
|
||||||
|
of "TimelineUrl":
|
||||||
|
let
|
||||||
|
url = ent{"ref", "url"}.getStr
|
||||||
|
display = $runes[slice]
|
||||||
|
|
||||||
|
replacements.add ReplaceSlice(kind: rkUrl, slice: slice, url: url, display: display)
|
||||||
|
|
||||||
|
note.text = runes.replacedWith(replacements, 0 .. runes.len)
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
@import 'card';
|
@import 'card';
|
||||||
@import 'poll';
|
@import 'poll';
|
||||||
@import 'quote';
|
@import 'quote';
|
||||||
|
@import 'community_note';
|
||||||
|
|
||||||
.tweet-body {
|
.tweet-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
30
src/sass/tweet/community_note.scss
Normal file
30
src/sass/tweet/community_note.scss
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
@import '_variables';
|
||||||
|
|
||||||
|
.community-note {
|
||||||
|
margin-top: 10px;
|
||||||
|
border: solid 1px var(--dark_grey);
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: var(--bg_elements);
|
||||||
|
overflow: hidden;
|
||||||
|
pointer-events: all;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--grey);
|
||||||
|
}
|
||||||
|
|
||||||
|
.community-note-title {
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: var(--bg_overlays);
|
||||||
|
padding: 6px 8px;
|
||||||
|
margin-top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.community-note-text {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -198,6 +198,11 @@ type
|
||||||
likes*: int
|
likes*: int
|
||||||
quotes*: int
|
quotes*: int
|
||||||
|
|
||||||
|
BirdwatchNote* = ref object
|
||||||
|
id*: int64
|
||||||
|
title*: string
|
||||||
|
text*: string
|
||||||
|
|
||||||
Tweet* = ref object
|
Tweet* = ref object
|
||||||
id*: int64
|
id*: int64
|
||||||
threadId*: int64
|
threadId*: int64
|
||||||
|
@ -223,6 +228,7 @@ type
|
||||||
gif*: Option[Gif]
|
gif*: Option[Gif]
|
||||||
video*: Option[Video]
|
video*: Option[Video]
|
||||||
photos*: seq[string]
|
photos*: seq[string]
|
||||||
|
birdwatch*: Option[BirdwatchNote]
|
||||||
|
|
||||||
Tweets* = seq[Tweet]
|
Tweets* = seq[Tweet]
|
||||||
|
|
||||||
|
|
|
@ -263,6 +263,14 @@ proc renderQuote(quote: Tweet; prefs: Prefs; path: string): VNode =
|
||||||
if quote.photos.len > 0 or quote.video.isSome or quote.gif.isSome:
|
if quote.photos.len > 0 or quote.video.isSome or quote.gif.isSome:
|
||||||
renderQuoteMedia(quote, prefs, path)
|
renderQuoteMedia(quote, prefs, path)
|
||||||
|
|
||||||
|
proc renderCommunityNote(note: BirdwatchNote; prefs: Prefs): VNode =
|
||||||
|
buildHtml(tdiv(class="community-note")):
|
||||||
|
tdiv(class="community-note-title"):
|
||||||
|
text note.title
|
||||||
|
|
||||||
|
tdiv(class="community-note-text", dir="auto"):
|
||||||
|
verbatim replaceUrls(note.text, prefs)
|
||||||
|
|
||||||
proc renderLocation*(tweet: Tweet): string =
|
proc renderLocation*(tweet: Tweet): string =
|
||||||
let (place, url) = tweet.getLocation()
|
let (place, url) = tweet.getLocation()
|
||||||
if place.len == 0: return
|
if place.len == 0: return
|
||||||
|
@ -343,6 +351,9 @@ proc renderTweet*(tweet: Tweet; prefs: Prefs; path: string; class=""; index=0;
|
||||||
if tweet.quote.isSome:
|
if tweet.quote.isSome:
|
||||||
renderQuote(tweet.quote.get(), prefs, path)
|
renderQuote(tweet.quote.get(), prefs, path)
|
||||||
|
|
||||||
|
if mainTweet and tweet.birdwatch.isSome:
|
||||||
|
renderCommunityNote(tweet.birdwatch.get(), prefs)
|
||||||
|
|
||||||
if mainTweet:
|
if mainTweet:
|
||||||
p(class="tweet-published"): text &"{getTime(tweet)}"
|
p(class="tweet-published"): text &"{getTime(tweet)}"
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue