mirror of
https://git.sr.ht/~cadence/bibliogram
synced 2024-11-22 16:17:29 +00:00
Clickable usernames and hashtags
Should work well enough. Report edge cases.
This commit is contained in:
parent
1fcdfce868
commit
0ea95d1943
@ -21,9 +21,9 @@ Join the Bibliogram discussion room on Matrix: [#bibliogram:matrix.org](https://
|
|||||||
- [x] Galleries of videos
|
- [x] Galleries of videos
|
||||||
- [x] Optimised for mobile
|
- [x] Optimised for mobile
|
||||||
- [x] Instance list
|
- [x] Instance list
|
||||||
|
- [x] Clickable usernames and hashtags
|
||||||
|
- [x] Proper error checking
|
||||||
- [ ] Image disk cache
|
- [ ] Image disk cache
|
||||||
- [ ] Clickable usernames and hashtags
|
|
||||||
- [ ] Proper error checking
|
|
||||||
- [ ] Favicon
|
- [ ] Favicon
|
||||||
- [ ] Settings (e.g. data saving)
|
- [ ] Settings (e.g. data saving)
|
||||||
- [ ] List view
|
- [ ] List view
|
||||||
@ -33,7 +33,7 @@ Join the Bibliogram discussion room on Matrix: [#bibliogram:matrix.org](https://
|
|||||||
- [ ] Public API
|
- [ ] Public API
|
||||||
- [ ] Explore hashtags
|
- [ ] Explore hashtags
|
||||||
- [ ] Explore locations
|
- [ ] Explore locations
|
||||||
- [ ] _more..._
|
- [ ] _more...?_
|
||||||
|
|
||||||
These features may not be able to be implemented for technical reasons:
|
These features may not be able to be implemented for technical reasons:
|
||||||
|
|
||||||
|
@ -42,7 +42,8 @@ let constants = {
|
|||||||
shortcode_query_hash: "2b0673e0dc4580674a88d426fe00ea90",
|
shortcode_query_hash: "2b0673e0dc4580674a88d426fe00ea90",
|
||||||
timeline_fetch_first: 12,
|
timeline_fetch_first: 12,
|
||||||
username_regex: "[\\w.]+",
|
username_regex: "[\\w.]+",
|
||||||
shortcode_regex: "[\\w-]+"
|
shortcode_regex: "[\\w-]+",
|
||||||
|
hashtag_regex: "[\\w]+"
|
||||||
},
|
},
|
||||||
|
|
||||||
resources: {
|
resources: {
|
||||||
|
@ -2,6 +2,7 @@ const constants = require("../constants")
|
|||||||
const {proxyImage, proxyExtendedOwner} = require("../utils/proxyurl")
|
const {proxyImage, proxyExtendedOwner} = require("../utils/proxyurl")
|
||||||
const {compile} = require("pug")
|
const {compile} = require("pug")
|
||||||
const collectors = require("../collectors")
|
const collectors = require("../collectors")
|
||||||
|
const {structure} = require("../utils/structuretext")
|
||||||
const TimelineBaseMethods = require("./TimelineBaseMethods")
|
const TimelineBaseMethods = require("./TimelineBaseMethods")
|
||||||
const TimelineChild = require("./TimelineChild")
|
const TimelineChild = require("./TimelineChild")
|
||||||
require("../testimports")(collectors, TimelineChild, TimelineBaseMethods)
|
require("../testimports")(collectors, TimelineChild, TimelineBaseMethods)
|
||||||
@ -87,6 +88,12 @@ class TimelineEntry extends TimelineBaseMethods {
|
|||||||
else return edge.node.text.replace(/\u2063/g, "") // I don't know why U+2063 INVISIBLE SEPARATOR is in here, but it is, and it causes rendering issues with certain fonts, so let's just remove it.
|
else return edge.node.text.replace(/\u2063/g, "") // I don't know why U+2063 INVISIBLE SEPARATOR is in here, but it is, and it causes rendering issues with certain fonts, so let's just remove it.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getStructuredCaption() {
|
||||||
|
const caption = this.getCaption()
|
||||||
|
if (!caption) return null // no caption
|
||||||
|
else return structure(caption)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to get the first meaningful line or sentence from the caption.
|
* Try to get the first meaningful line or sentence from the caption.
|
||||||
*/
|
*/
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
const constants = require("../constants")
|
const constants = require("../constants")
|
||||||
const {proxyImage} = require("../utils/proxyurl")
|
const {proxyImage} = require("../utils/proxyurl")
|
||||||
|
const {structure} = require("../utils/structuretext")
|
||||||
const Timeline = require("./Timeline")
|
const Timeline = require("./Timeline")
|
||||||
require("../testimports")(constants, Timeline)
|
require("../testimports")(constants, Timeline)
|
||||||
|
|
||||||
@ -17,6 +18,11 @@ class User {
|
|||||||
this.proxyProfilePicture = proxyImage(this.data.profile_pic_url)
|
this.proxyProfilePicture = proxyImage(this.data.profile_pic_url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getStructuredBio() {
|
||||||
|
if (!this.data.biography) return null
|
||||||
|
return structure(this.data.biography)
|
||||||
|
}
|
||||||
|
|
||||||
getTtl(scale = 1) {
|
getTtl(scale = 1) {
|
||||||
const expiresAt = this.cachedAt + constants.caching.resource_cache_time
|
const expiresAt = this.cachedAt + constants.caching.resource_cache_time
|
||||||
const ttl = expiresAt - Date.now()
|
const ttl = expiresAt - Date.now()
|
||||||
|
53
src/lib/utils/structuretext.js
Normal file
53
src/lib/utils/structuretext.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
const constants = require("../constants")
|
||||||
|
const {Parser} = require("./parser/parser")
|
||||||
|
|
||||||
|
function tryMatch(text, against, callback) {
|
||||||
|
let matched = text.match(against)
|
||||||
|
if (matched) callback(matched)
|
||||||
|
}
|
||||||
|
|
||||||
|
function textToParts(text) {
|
||||||
|
return [{type: "text", text: text}]
|
||||||
|
}
|
||||||
|
|
||||||
|
function replacePart(parts, index, match, replacements) {
|
||||||
|
const toReplace = parts.splice(index, 1)[0]
|
||||||
|
const before = toReplace.text.slice(0, match.index)
|
||||||
|
const after = toReplace.text.slice(match.index + match[0].length)
|
||||||
|
parts.splice(index, 0, ...textToParts(before), ...replacements, ...textToParts(after))
|
||||||
|
}
|
||||||
|
|
||||||
|
function partsUsername(parts) {
|
||||||
|
for (let i = 0; i < parts.length; i++) {
|
||||||
|
if (parts[i].type === "text") {
|
||||||
|
tryMatch(parts[i].text, `@(${constants.external.username_regex})`, match => {
|
||||||
|
replacePart(parts, i, match, [
|
||||||
|
{type: "user", text: match[0], user: match[1]}
|
||||||
|
])
|
||||||
|
i += 1 // skip parts: user
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function partsHashtag(parts) {
|
||||||
|
for (let i = 0; i < parts.length; i++) {
|
||||||
|
if (parts[i].type === "text") {
|
||||||
|
tryMatch(parts[i].text, `#(${constants.external.hashtag_regex})`, match => {
|
||||||
|
replacePart(parts, i, match, [
|
||||||
|
{type: "hashtag", text: match[0], hashtag: match[1]}
|
||||||
|
])
|
||||||
|
i += 1 // skip parts: hashtag
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function structure(text) {
|
||||||
|
const parts = textToParts(text)
|
||||||
|
partsUsername(parts)
|
||||||
|
partsHashtag(parts)
|
||||||
|
return parts
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.structure = structure
|
11
src/site/pug/includes/display_structured.pug
Normal file
11
src/site/pug/includes/display_structured.pug
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
mixin display_structured(parts)
|
||||||
|
each part in parts
|
||||||
|
if part.type === "text"
|
||||||
|
= part.text
|
||||||
|
else if part.type === "user"
|
||||||
|
a(href="/u/"+part.user).link-to-user= part.text
|
||||||
|
else if part.type === "hashtag"
|
||||||
|
//- todo: add link to explore page, when explore page exists.
|
||||||
|
a.link-to-hashtag= part.text
|
||||||
|
else
|
||||||
|
| [UNKNOWN PART TYPE #{part.type}, TEXT:] [#{part.text}]
|
@ -1,3 +1,5 @@
|
|||||||
|
include includes/display_structured
|
||||||
|
|
||||||
- const numberFormat = new Intl.NumberFormat().format
|
- const numberFormat = new Intl.NumberFormat().format
|
||||||
|
|
||||||
doctype html
|
doctype html
|
||||||
@ -20,7 +22,8 @@ html
|
|||||||
img(src=post.ownerPfpCacheP width=150 height=150 alt="").pfp
|
img(src=post.ownerPfpCacheP width=150 height=150 alt="").pfp
|
||||||
a.name(href=`/u/${post.getBasicOwner().username}`)= `${post.data.owner.full_name} (@${post.getBasicOwner().username})`
|
a.name(href=`/u/${post.getBasicOwner().username}`)= `${post.data.owner.full_name} (@${post.getBasicOwner().username})`
|
||||||
if post.getCaption()
|
if post.getCaption()
|
||||||
p.description= post.getCaption()
|
p.structured-text.description
|
||||||
|
+display_structured(post.getStructuredCaption())
|
||||||
section.images-gallery
|
section.images-gallery
|
||||||
for entry in post.children
|
for entry in post.children
|
||||||
if entry.isVideo()
|
if entry.isVideo()
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
include includes/timeline_page.pug
|
include includes/timeline_page.pug
|
||||||
include includes/next_page_button.pug
|
include includes/next_page_button.pug
|
||||||
|
include includes/display_structured
|
||||||
|
|
||||||
- const numberFormat = new Intl.NumberFormat().format
|
- const numberFormat = new Intl.NumberFormat().format
|
||||||
|
|
||||||
@ -30,7 +31,8 @@ html
|
|||||||
else
|
else
|
||||||
h1.full-name= `@${user.data.username}`
|
h1.full-name= `@${user.data.username}`
|
||||||
if !user.fromReel
|
if !user.fromReel
|
||||||
p.bio= user.data.biography
|
p.structured-text.bio
|
||||||
|
+display_structured(user.getStructuredBio())
|
||||||
if user.data.external_url
|
if user.data.external_url
|
||||||
p.website
|
p.website
|
||||||
a(href=user.data.external_url)= user.data.external_url
|
a(href=user.data.external_url)= user.data.external_url
|
||||||
|
@ -3,6 +3,7 @@ $layout-b-min: 821px
|
|||||||
$layout-c-max: 680px;
|
$layout-c-max: 680px;
|
||||||
$layout-home-a-max: 520px
|
$layout-home-a-max: 520px
|
||||||
$layout-home-b-min: 521px
|
$layout-home-b-min: 521px
|
||||||
|
$main-theme-link-color: #085cae
|
||||||
|
|
||||||
body
|
body
|
||||||
margin: 0
|
margin: 0
|
||||||
@ -89,7 +90,10 @@ body
|
|||||||
flex-wrap: wrap
|
flex-wrap: wrap
|
||||||
justify-content: center
|
justify-content: center
|
||||||
|
|
||||||
a
|
a, a:visited
|
||||||
|
color: $main-theme-link-color
|
||||||
|
|
||||||
|
> *
|
||||||
margin: 5px
|
margin: 5px
|
||||||
|
|
||||||
> *:last-child
|
> *:last-child
|
||||||
@ -426,3 +430,14 @@ body
|
|||||||
|
|
||||||
.link-list
|
.link-list
|
||||||
color: $link-color
|
color: $link-color
|
||||||
|
|
||||||
|
.structured-text
|
||||||
|
a, a:visited
|
||||||
|
color: $main-theme-link-color
|
||||||
|
text-decoration: none
|
||||||
|
|
||||||
|
a:link, a:link:visited
|
||||||
|
text-decoration: underline
|
||||||
|
|
||||||
|
.link-to-hashtag
|
||||||
|
color: #127722
|
||||||
|
Loading…
Reference in New Issue
Block a user