feat(cc-redirect): redirect URL hash and add redundant redirect method
This CL adds logic to redirect the URL hash to the Community Console,
so actions that are embedded in the URL like |#action=reply| will be
passed to the Community Console.
It also adds another method of redirecting to the Community Console
which will coexist with the old method.
Fixed: twpowertools:164
Change-Id: Ib3f770d7cbeec8f26cdd249e66f7f46ae94bb1c8
diff --git a/src/common/TWBasicUtils.js b/src/common/TWBasicUtils.js
new file mode 100644
index 0000000..b97fba1
--- /dev/null
+++ b/src/common/TWBasicUtils.js
@@ -0,0 +1,31 @@
+import rescape from '@stdlib/utils-escape-regexp-string';
+
+import {correctArrayKeys} from './protojs';
+
+export function parseView(viewVar) {
+ const escapedViewVar = rescape(viewVar);
+ const viewRegex = new RegExp(`var ${escapedViewVar} ?= ?'([^']+)';`);
+
+ const scripts = document.querySelectorAll('script');
+ let viewData = null;
+ for (let i = 0; i < scripts.length; ++i) {
+ const matches = scripts[i].textContent.match(viewRegex);
+ if (matches?.[1]) {
+ let rawJsonStringContents =
+ matches[1]
+ .replace(
+ /\\x([0-9a-f]{2})/ig,
+ (_, pair) => {
+ return String.fromCharCode(parseInt(pair, 16));
+ })
+ .replace(/\\'/g, `'`)
+ .replace(/"/g, `\\"`);
+ let rawJsonString = `"${rawJsonStringContents}"`;
+ let rawJson = JSON.parse(rawJsonString);
+ viewData = JSON.parse(rawJson);
+ break;
+ }
+ }
+ if (!viewData) throw new Error(`Could not find ${viewVar} view data.`);
+ return correctArrayKeys(viewData);
+}
diff --git a/src/contentScripts/publicProfile.js b/src/contentScripts/publicProfile.js
index 6c3da8d..9c36ae1 100644
--- a/src/contentScripts/publicProfile.js
+++ b/src/contentScripts/publicProfile.js
@@ -1,10 +1,10 @@
import {getOptions} from '../common/optionsUtils.js';
-import {correctArrayKeys} from '../common/protojs.js';
+import {parseView} from '../common/TWBasicUtils.js';
import PerForumStatsSection from './communityConsole/utils/PerForumStatsSection.js';
import {injectPreviousPostsLinksUnifiedProfile} from './utilsCommon/unifiedProfiles.js';
-const profileViewRegex = /var view ?= ?'([^']+)';/;
+const kProfileViewVar = 'view';
getOptions(['history', 'perforumstats']).then(options => {
if (options?.history)
@@ -20,27 +20,8 @@
if (!chart) throw new Error('Couldn\'t find existing chart.');
// Extract profile JSON information
- const scripts = document.querySelectorAll('script');
- let profileView = null;
- for (let i = 0; i < scripts.length; ++i) {
- const matches = scripts[i].textContent.match(profileViewRegex);
- if (matches?.[1]) {
- let rawJsonStringContents =
- matches[1]
- .replace(
- /\\x([0-9a-f]{2})/ig,
- (_, pair) => {
- return String.fromCharCode(parseInt(pair, 16));
- })
- .replace(/\\'/g, `'`)
- .replace(/"/g, `\\"`);
- let rawJsonString = `"${rawJsonStringContents}"`;
- let rawJson = JSON.parse(rawJsonString);
- profileView = JSON.parse(rawJson);
- break;
- }
- }
- const profileViewC = {'1': correctArrayKeys(profileView)};
+ const profileView = parseView(kProfileViewVar);
+ const profileViewC = {'1': profileView};
if (!profileView) throw new Error('Could not find user view data.');
new PerForumStatsSection(
chart?.parentNode, profileViewC,
diff --git a/src/contentScripts/publicThread.js b/src/contentScripts/publicThread.js
index dd2088a..4d98bee 100644
--- a/src/contentScripts/publicThread.js
+++ b/src/contentScripts/publicThread.js
@@ -1,7 +1,6 @@
import {injectStylesheet} from '../common/contentScriptsUtils.js';
import {getOptions} from '../common/optionsUtils.js';
-
-var CCThreadWithoutMessage = /forum\/[0-9]*\/thread\/[0-9]*$/;
+import {redirectIfApplicable} from '../redirect/index.js';
const kLoadMoreButtons = [
{
@@ -23,15 +22,27 @@
];
const kMsgidDelay = 3500;
+function main() {
+ const ok = redirectIfApplicable();
+ if (ok) return;
+
+ getOptions(null).then(options => {
+ setUpInfiniteScrollWithPotentialDelay(options);
+
+ if (options.imagemaxheight)
+ injectStylesheet(chrome.runtime.getURL('css/image_max_height.css'));
+ });
+}
+
var intersectionObserver;
-function intersectionCallback(entries, observer) {
+function intersectionCallback(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.click();
}
});
-};
+}
var intersectionOptions = {
threshold: 1.0,
@@ -65,22 +76,4 @@
}
}
-getOptions(null).then(options => {
- var redirectLink = document.querySelector('.community-console');
- if (options.redirect && redirectLink !== null) {
- var redirectUrl = redirectLink.href;
-
- var searchParams = new URLSearchParams(location.search);
- if (searchParams.has('msgid') && searchParams.get('msgid') !== '' &&
- CCThreadWithoutMessage.test(redirectUrl))
- redirectUrl +=
- '/message/' + encodeURIComponent(searchParams.get('msgid'));
-
- window.location = redirectUrl;
- } else {
- setUpInfiniteScrollWithPotentialDelay(options);
-
- if (options.imagemaxheight)
- injectStylesheet(chrome.runtime.getURL('css/image_max_height.css'));
- }
-});
+main();
diff --git a/src/contentScripts/publicThreadStart.js b/src/contentScripts/publicThreadStart.js
index d1c4e3c..9581aec 100644
--- a/src/contentScripts/publicThreadStart.js
+++ b/src/contentScripts/publicThreadStart.js
@@ -1,9 +1,12 @@
import {injectStylesheet} from '../common/contentScriptsUtils.js';
-import {isOptionEnabled} from '../common/optionsUtils.js';
+import {getOptions} from '../common/optionsUtils.js';
+import {setUpRedirectIfEnabled} from '../redirect/setup.js';
-isOptionEnabled('uispacing').then(isUiSpacingEnabled => {
- if (!isUiSpacingEnabled) return;
+getOptions(['uispacing', 'redirect']).then(options => {
+ if (options.uispacing) {
+ injectStylesheet(chrome.runtime.getURL('css/ui_spacing/shared.css'));
+ injectStylesheet(chrome.runtime.getURL('css/ui_spacing/twbasic.css'));
+ }
- injectStylesheet(chrome.runtime.getURL('css/ui_spacing/shared.css'));
- injectStylesheet(chrome.runtime.getURL('css/ui_spacing/twbasic.css'));
+ setUpRedirectIfEnabled(options);
});
diff --git a/src/redirect/index.js b/src/redirect/index.js
new file mode 100644
index 0000000..551d4d6
--- /dev/null
+++ b/src/redirect/index.js
@@ -0,0 +1,66 @@
+import {parseView} from '../common/TWBasicUtils.js';
+import ThreadModel from '../models/Thread.js';
+
+var CCThreadWithoutMessage = /forum\/[0-9]*\/thread\/[0-9]*$/;
+
+/**
+ * @returns {boolean} Whether the redirection was successful.
+ */
+export function redirectIfApplicable() {
+ if (window.TWPTShouldRedirect) {
+ let ok = redirectToCommunityConsoleV2();
+ if (ok) return true;
+
+ return redirectToCommunityConsoleV1();
+ }
+}
+
+function redirectToCommunityConsoleV2() {
+ try {
+ const threadView = parseView('thread_view');
+ if (!threadView) throw new Error('Could not find thread data.');
+
+ const thread = new ThreadModel(threadView);
+ const forumId = thread.getForumId();
+ const threadId = thread.getId();
+
+ if (!forumId || !threadId)
+ throw new Error('Forum id and thread id not present in thread data.');
+
+ const searchParams = new URLSearchParams(location.search);
+ const msgId = searchParams.get('msgid');
+
+ const msgSuffix = msgId ? `/message/${msgId}` : '';
+ const hash = window.TWPTRedirectHash ?? '';
+ const url =
+ `/s/community/forum/${forumId}/thread/${threadId}${msgSuffix}${hash}`;
+ window.location = url;
+ return true;
+ } catch (err) {
+ console.error('Error redirecting to the Community Console (v2): ', err);
+ return false;
+ }
+}
+
+function redirectToCommunityConsoleV1() {
+ try {
+ const redirectLink = document.querySelector('.community-console');
+ if (redirectLink === null) throw new Error('Could not find redirect link.');
+
+ let redirectUrl = redirectLink.href;
+
+ const searchParams = new URLSearchParams(location.search);
+ if (searchParams.has('msgid') && searchParams.get('msgid') !== '' &&
+ CCThreadWithoutMessage.test(redirectUrl)) {
+ redirectUrl +=
+ '/message/' + encodeURIComponent(searchParams.get('msgid'));
+ }
+ redirectUrl += window.TWPTRedirectHash ?? '';
+
+ window.location = redirectUrl;
+ return true;
+ } catch (err) {
+ console.error('Error redirecting to the Community Console (v1): ', err);
+ return false;
+ }
+}
diff --git a/src/redirect/setup.js b/src/redirect/setup.js
new file mode 100644
index 0000000..632bc69
--- /dev/null
+++ b/src/redirect/setup.js
@@ -0,0 +1,16 @@
+import {getOptions} from '../common/optionsUtils.js';
+
+export async function setUpRedirectIfEnabled(options = null) {
+ if (options === null) options = await getOptions(['redirect']);
+
+ if (options.redirect) {
+ setUpRedirect();
+ }
+}
+
+function setUpRedirect() {
+ window.TWPTRedirectHash = window.location.hash;
+ // We're preloading the redirect option so we can redirect faster in
+ // publicThread.js without having to retrieve the options again.
+ window.TWPTShouldRedirect = true;
+}