From e42fafdabebdb3da937f4820d27bc0da3c2382a5 Mon Sep 17 00:00:00 2001 From: David Mzareulyan Date: Sat, 19 Aug 2023 16:11:07 +0300 Subject: [PATCH 1/9] Use short IDs in post URLs --- src/app.jsx | 3 ++- src/redux/reducers.js | 6 +++--- src/utils/canonical-uri.js | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/app.jsx b/src/app.jsx index b3504f199..d721cd63f 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -425,7 +425,8 @@ function isPostPath({ params: { postId, userName } }) { // old groups can have up to 27 characters in username return ( /^[a-z\d-]{3,30}$/i.test(userName) && - /^[a-f\d]{8}-[a-f\d]{4}-4[a-f\d]{3}-[89ab][a-f\d]{3}-[a-f\d]{12}$/i.test(postId) + (/^[a-f\d]{8}-[a-f\d]{4}-4[a-f\d]{3}-[89ab][a-f\d]{3}-[a-f\d]{12}$/i.test(postId) || + /^[a-f\d]{6,10}$/i.test(postId)) // Short post ID ); } diff --git a/src/redux/reducers.js b/src/redux/reducers.js index a909e0c6d..efd2d8453 100644 --- a/src/redux/reducers.js +++ b/src/redux/reducers.js @@ -253,7 +253,7 @@ export function feedViewState(state = initFeed, action) { }; } case response(ActionTypes.GET_SINGLE_POST): { - const { postId } = action.request; + const { id: postId } = action.payload.posts; return { ...initFeed, entries: [postId], @@ -1318,8 +1318,8 @@ export function singlePostId(state = null, action) { if (ActionHelpers.isFeedRequest(action)) { return null; } - if (action.type == request(ActionTypes.GET_SINGLE_POST)) { - return action.payload.postId; + if (action.type == response(ActionTypes.GET_SINGLE_POST)) { + return action.payload.posts.id; } return state; } diff --git a/src/utils/canonical-uri.js b/src/utils/canonical-uri.js index 699785620..1ec5135b0 100644 --- a/src/utils/canonical-uri.js +++ b/src/utils/canonical-uri.js @@ -5,5 +5,5 @@ export function canonicalURI(post) { if (post.recipients.length > 0 && !post.recipients.some((r) => r.type === 'user')) { urlName = post.recipients[0].username; } - return `/${encodeURIComponent(urlName)}/${encodeURIComponent(post.id)}`; + return `/${encodeURIComponent(urlName)}/${encodeURIComponent(post.shortId)}`; } From b2394bafb22f18d20eebbe4a1049a2e2f4bdf259 Mon Sep 17 00:00:00 2001 From: David Mzareulyan Date: Sat, 19 Aug 2023 16:28:45 +0300 Subject: [PATCH 2/9] Use short IDs in comment URLs --- src/components/post/post-comment-preview.jsx | 4 ++-- src/components/post/post-comment.jsx | 4 ++-- src/components/select-utils.js | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/post/post-comment-preview.jsx b/src/components/post/post-comment-preview.jsx index ba33f26af..f1f8f9911 100644 --- a/src/components/post/post-comment-preview.jsx +++ b/src/components/post/post-comment-preview.jsx @@ -134,7 +134,7 @@ export function PostCommentPreview({ {comment && frontPreferences.comments.showTimestamps && ( @@ -172,7 +172,7 @@ export function PostCommentPreview({ )}
- + Go to comment
diff --git a/src/components/post/post-comment.jsx b/src/components/post/post-comment.jsx index 7d46ed22c..d32ad514c 100644 --- a/src/components/post/post-comment.jsx +++ b/src/components/post/post-comment.jsx @@ -228,7 +228,7 @@ class PostComment extends Component { getBackwardIdx={this.backwardIdx} createdAt={this.props.createdAt} updatedAt={this.props.updatedAt} - permalink={`${this.props.entryUrl}#comment-${this.props.id}`} + permalink={`${this.props.entryUrl}#${this.props.shortId}`} likesCount={this.props.likes} setMenuOpener={this.setMoreMenuOpener} onMenuOpened={this.onMoreMenuOpened} @@ -240,7 +240,7 @@ class PostComment extends Component { {(this.props.showTimestamps || this.props.forceAbsTimestamps) && ( { if (!hash) { return ''; } - return hash.replace('#comment-', ''); + return hash.startsWith('#comment-') ? hash.replace('#comment-', '') : hash.replace('#', ''); }; export const joinPostData = (state) => (postId) => { @@ -161,7 +161,7 @@ export const joinPostData = (state) => (postId) => { isEditable: user.id === comment.createdBy, isDeletable: isModeratable || isModeratable, likesList: selectCommentLikes(state, commentId), - highlightedFromUrl: commentId === hashedCommentId, + highlightedFromUrl: commentId === hashedCommentId || comment.shortId === hashedCommentId, }; }) .filter(Boolean); From 797d3624a572464d65830bb459bd1b88ca1ffbf1 Mon Sep 17 00:00:00 2001 From: David Mzareulyan Date: Sat, 19 Aug 2023 16:30:08 +0300 Subject: [PATCH 3/9] Use short IDs in rendered notifications --- src/components/notifications.jsx | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/components/notifications.jsx b/src/components/notifications.jsx index 479f8f64f..30590d6f0 100644 --- a/src/components/notifications.jsx +++ b/src/components/notifications.jsx @@ -30,9 +30,12 @@ const getAuthorName = ({ postAuthor, createdUser, group }) => { return createdUser.username; }; -const generatePostUrl = ({ post_id, ...event }) => `/${getAuthorName(event)}/${post_id}`; -const generateCommentUrl = ({ post_id, comment_id, ...event }) => - `/${getAuthorName(event)}/${post_id}#comment-${comment_id}`; +const generatePostUrl = ({ post_id, shortPostId, ...event }) => + `/${getAuthorName(event)}/${shortPostId ?? post_id}`; +const generateCommentUrl = ({ post_id, comment_id, shortPostId, shortCommentId, ...event }) => + `/${getAuthorName(event)}/${shortPostId ?? post_id}#${ + shortCommentId ? shortCommentId : `comment-${comment_id}` + }`; const postLink = (event, fallback = 'deleted post') => event.post_id ? post : fallback; const directPostLink = (event) => @@ -421,14 +424,22 @@ function Notification({ event }) { : contentSource === 'comment' ? allComments[event.comment_id]?.body : null; - const mainLink = mainEventLink(event); + const eventWithShortIds = useMemo( + () => ({ + ...event, + shortPostId: allPosts[event.post_id]?.shortId, + shortCommentId: allComments[event.comment_id]?.shortId, + }), + [allComments, allPosts, event], + ); + const mainLink = mainEventLink(eventWithShortIds); return (
- {(notificationTemplates[event.event_type] || nop)(event)} + {(notificationTemplates[event.event_type] || nop)(eventWithShortIds)}
{content ? ( From 61449c30b2c9defcae8ce93a42c570130f7af37a Mon Sep 17 00:00:00 2001 From: David Mzareulyan Date: Mon, 21 Aug 2023 12:35:57 +0300 Subject: [PATCH 4/9] Support short links in the text parser --- src/utils/parse-text.js | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/src/utils/parse-text.js b/src/utils/parse-text.js index bdc974a7a..8d7f1a70a 100644 --- a/src/utils/parse-text.js +++ b/src/utils/parse-text.js @@ -28,29 +28,43 @@ const { siteDomains, } = CONFIG; +const shortLinkRe = /\/[a-z\d-]{3,35}\/[\da-f]{6,10}(?:#[\da-f]{4,6})?/gi; +const shortLinkExactRe = /^\/[a-z\d-]{3,35}\/[\da-f]{6,10}(?:#[\da-f]{4,6})?$/i; + export class Link extends TLink { localDomains = []; hostname = null; path = '/'; link; + isShort = false; constructor(link, localDomains) { super(link.offset, link.text); this.link = link; this.localDomains = localDomains; + this.isShort = shortLinkExactRe.test(link.text); - const m = this.link.href.match(/^https?:\/\/([^/]+)(.*)/i); + const m = this.href.match(/^https?:\/\/([^/]+)(.*)/i); if (m) { this.hostname = m[1].toLowerCase(); this.path = m[2] || '/'; + } else if (this.isShort) { + this.path = link.text; } } - get href() { - return this.link.href; + get pretty() { + if (this.isShort) { + return this.path; + } + return super.pretty; } get isLocal() { + // Short links are always local + if (this.isShort) { + return true; + } const p = this.localDomains.indexOf(this.hostname); if (p === -1) { return false; @@ -61,7 +75,7 @@ export class Link extends TLink { } // Other domains in localDomains list are alternative frontends or mirrors. - // Such links should be treated as remote if theay lead to the domain root. + // Such links should be treated as remote if they lead to the domain root. return this.path !== '/'; } @@ -127,6 +141,23 @@ const redditLinks = () => { }); }; +const shortLinks = () => { + const beforeChars = new RegExp(`[${wordAdjacentChars}]`); + const afterChars = new RegExp(`[${wordAdjacentChars.clone().removeChars('/')}]`); + return byRegexp(shortLinkRe, (offset, text, match) => { + const charBefore = match.input.charAt(offset - 1); + const charAfter = match.input.charAt(offset + text.length); + if (charBefore !== '' && !beforeChars.test(charBefore)) { + return null; + } + if (charAfter !== '' && !afterChars.test(charAfter)) { + return null; + } + + return new TLink(offset, text); + }); +}; + export class LineBreak extends Token {} export class ParagraphBreak extends Token {} @@ -151,6 +182,7 @@ const tokenize = withText( mentions(), foreignMentions(), links({ tldRe }), + shortLinks(), redditLinks(), arrows(/\u2191+|\^([1-9]\d*|\^*)/g), tokenizerStartSpoiler, From 4a4f1df3b3463a1fbdbec295215b64d55762f973 Mon Sep 17 00:00:00 2001 From: David Mzareulyan Date: Sat, 19 Aug 2023 21:06:55 +0300 Subject: [PATCH 5/9] Update changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c16e63b3..da34560ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [1.123.0] - Not released +### Added +- Links to posts and comments are now shorter: e.g. `/user/4a39b8` (a post) or + `/groupname/f482e5#ad2b` (a comment). + - All public URLs and links to posts and comments are now in short format. + - Addresses of posts and comments with leading slash (`/user/...`) are now + parses in texts as active links. + - Old-fashion links, with long UIDs, are still fully supported. + ### Changed - Spoiler tag can now contain line feeds. In addition, user text formatting utilizes simpler HTML, with fewer wrappers. Visually, the output for the user From b3362f46b580334ea00ef8f71c8d6079a8094d33 Mon Sep 17 00:00:00 2001 From: David Mzareulyan Date: Sun, 20 Aug 2023 15:11:34 +0300 Subject: [PATCH 6/9] Support short IDs in SSI opengraph calls --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 67ff13494..7f13f1bb6 100644 --- a/index.html +++ b/index.html @@ -5,7 +5,7 @@ - + From 5383325062bb5cb46dcfcfcd8c57edb59ee78b26 Mon Sep 17 00:00:00 2001 From: David Mzareulyan Date: Mon, 21 Aug 2023 12:41:02 +0300 Subject: [PATCH 7/9] Adjust short (and Reddit) link boundaries --- src/utils/parse-text.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/parse-text.js b/src/utils/parse-text.js index 8d7f1a70a..feed4f4f2 100644 --- a/src/utils/parse-text.js +++ b/src/utils/parse-text.js @@ -125,7 +125,7 @@ export class RedditLink extends TLink { } const redditLinks = () => { - const beforeChars = new RegExp(`[${wordAdjacentChars}]`); + const beforeChars = new RegExp(`[${wordAdjacentChars.clone().removeChars('/')}]`); const afterChars = new RegExp(`[${wordAdjacentChars.clone().removeChars('/')}]`); return byRegexp(/\/?r\/[A-Za-z\d]\w{1,20}/g, (offset, text, match) => { const charBefore = match.input.charAt(offset - 1); @@ -142,7 +142,7 @@ const redditLinks = () => { }; const shortLinks = () => { - const beforeChars = new RegExp(`[${wordAdjacentChars}]`); + const beforeChars = new RegExp(`[${wordAdjacentChars.clone().removeChars('/')}]`); const afterChars = new RegExp(`[${wordAdjacentChars.clone().removeChars('/')}]`); return byRegexp(shortLinkRe, (offset, text, match) => { const charBefore = match.input.charAt(offset - 1); From 0960eeed12f725d599d28b038e1c20776afa37c9 Mon Sep 17 00:00:00 2001 From: David Mzareulyan Date: Tue, 22 Aug 2023 23:33:49 +0300 Subject: [PATCH 8/9] Don't try to normalize post URI until the loading is over --- src/components/single-post.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/single-post.jsx b/src/components/single-post.jsx index fdc414109..82e2daa28 100644 --- a/src/components/single-post.jsx +++ b/src/components/single-post.jsx @@ -14,8 +14,8 @@ import { PostContextProvider } from './post/post-context'; class SinglePostHandler extends Component { UNSAFE_componentWillReceiveProps(nextProps) { - const { post, router } = nextProps; - if (!post) { + const { post, router, routeLoadingState } = nextProps; + if (!post || routeLoadingState) { return; } const { pathname, search, hash } = router.location; @@ -84,13 +84,13 @@ class SinglePostHandler extends Component { } function selectState(state) { - const { boxHeader, user } = state; + const { boxHeader, user, routeLoadingState } = state; const post = joinPostData(state)(state.singlePostId); const viewState = state.postsViewState[state.singlePostId]; const errorString = viewState ? viewState.errorString || 'Unknown error' : null; - return { post, user, boxHeader, errorString }; + return { post, user, boxHeader, errorString, routeLoadingState }; } function selectActions(dispatch) { From 4bff3e4702381710830d6bd53cb4868cbdb5b4ec Mon Sep 17 00:00:00 2001 From: David Mzareulyan Date: Wed, 23 Aug 2023 00:20:17 +0300 Subject: [PATCH 9/9] Show links to local short post addresses without domain --- src/utils/parse-text.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/parse-text.js b/src/utils/parse-text.js index feed4f4f2..2f837b211 100644 --- a/src/utils/parse-text.js +++ b/src/utils/parse-text.js @@ -54,7 +54,7 @@ export class Link extends TLink { } get pretty() { - if (this.isShort) { + if (this.isShort || (this.isLocal && shortLinkExactRe.test(this.path))) { return this.path; } return super.pretty;