From 81184cc45673da58b6480a6c09cec53003f16082 Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Sun, 23 Jun 2024 11:54:05 -0700 Subject: [PATCH 01/50] Documentation: Document `inject` util's purpose (#1505) --- src/utils/inject.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/utils/inject.js b/src/utils/inject.js index 5f214ee6d..6bf9ec994 100644 --- a/src/utils/inject.js +++ b/src/utils/inject.js @@ -1,4 +1,8 @@ /** + * Runs a script in the page's "main" execution environment and returns its result. + * This permits access to variables exposed by the Tumblr web platform that are normally inaccessible + * in the content script sandbox. + * See the src/main_world directory and [../main_world/index.js](../main_world/index.js). * @param {string} path - Absolute path of script to inject (will be fed to `runtime.getURL()`) * @param {Array} [args] - Array of arguments to pass to the script * @param {Element} [target] - Target element; will be accessible as the `this` value in the injected function. From fade994a8ae3742aa2736adf085c236aa7fc75ca Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Sun, 23 Jun 2024 12:27:08 -0700 Subject: [PATCH 02/50] Parse `data-timeline-id` on Tumblr's new timeline component (#1482) --- src/features/collapsed_queue.js | 14 +++--- src/features/mutual_checker.js | 3 +- .../no_recommended/hide_recommended_posts.js | 3 +- src/features/seen_posts.js | 5 ++- src/features/show_originals.js | 15 +++---- src/features/tag_tracking_plus.js | 3 +- src/features/tweaks/caught_up_line.js | 3 +- src/features/tweaks/hide_liked_posts.js | 3 +- src/features/tweaks/hide_my_posts.js | 3 +- src/utils/interface.js | 20 ++++++--- src/utils/timeline_id.js | 43 +++++++++++++++++++ 11 files changed, 86 insertions(+), 29 deletions(-) create mode 100644 src/utils/timeline_id.js diff --git a/src/features/collapsed_queue.js b/src/features/collapsed_queue.js index dbeaf1667..1d6561a57 100644 --- a/src/features/collapsed_queue.js +++ b/src/features/collapsed_queue.js @@ -2,16 +2,17 @@ import { filterPostElements } from '../utils/interface.js'; import { getPreferences } from '../utils/preferences.js'; import { onNewPosts } from '../utils/mutations.js'; import { keyToCss } from '../utils/css_map.js'; +import { anyQueueTimelineFilter, anyDraftsTimelineFilter } from '../utils/timeline_id.js'; const excludeClass = 'xkit-collapsed-queue-done'; const wrapperClass = 'xkit-collapsed-queue-wrapper'; const containerClass = 'xkit-collapsed-queue-container'; const footerSelector = keyToCss('footerWrapper'); -let timelineRegex; +let timeline; const processPosts = async function (postElements) { - filterPostElements(postElements, { excludeClass, timeline: timelineRegex }).forEach(async postElement => { + filterPostElements(postElements, { excludeClass, timeline }).forEach(async postElement => { const headerElement = postElement.querySelector('header'); const footerElement = postElement.querySelector(footerSelector); @@ -30,11 +31,10 @@ export const main = async function () { const { runInQueue, runInDrafts } = await getPreferences('collapsed_queue'); if (![runInQueue, runInDrafts].some(Boolean)) return; - const regexGroup = [ - ...runInQueue ? ['queue'] : [], - ...runInDrafts ? ['draft'] : [] - ].join('|'); - timelineRegex = new RegExp(`/v2/blog/[^/]+/posts/(${regexGroup})`); + timeline = [ + runInQueue && anyQueueTimelineFilter, + runInDrafts && anyDraftsTimelineFilter + ].filter(Boolean); onNewPosts.addListener(processPosts); }; diff --git a/src/features/mutual_checker.js b/src/features/mutual_checker.js index ef5577bfd..cb6614c62 100644 --- a/src/features/mutual_checker.js +++ b/src/features/mutual_checker.js @@ -7,6 +7,7 @@ import { onNewPosts, onNewNotifications, pageModifications } from '../utils/muta import { dom } from '../utils/dom.js'; import { getPreferences } from '../utils/preferences.js'; import { translate } from '../utils/language_data.js'; +import { followingTimelineSelector } from '../utils/timeline_id.js'; const mutualIconClass = 'xkit-mutual-icon'; const hiddenAttribute = 'data-mutual-checker-hidden'; @@ -34,7 +35,7 @@ const styleElement = buildStyle(` margin-right: 0.5ch; } - [data-timeline="/v2/timeline/dashboard"] [${hiddenAttribute}] article { + ${followingTimelineSelector} [${hiddenAttribute}] article { display: none; } diff --git a/src/features/no_recommended/hide_recommended_posts.js b/src/features/no_recommended/hide_recommended_posts.js index cbc3a6ad0..4db701d31 100644 --- a/src/features/no_recommended/hide_recommended_posts.js +++ b/src/features/no_recommended/hide_recommended_posts.js @@ -1,11 +1,12 @@ import { buildStyle, getTimelineItemWrapper, filterPostElements, postSelector } from '../../utils/interface.js'; import { onNewPosts } from '../../utils/mutations.js'; import { timelineObject } from '../../utils/react_props.js'; +import { followingTimelineFilter } from '../../utils/timeline_id.js'; const excludeClass = 'xkit-no-recommended-posts-done'; const hiddenAttribute = 'data-no-recommended-posts-hidden'; const unHiddenAttribute = 'data-no-recommended-posts-many'; -const timeline = /\/v2\/timeline\/dashboard/; +const timeline = followingTimelineFilter; const includeFiltered = true; const styleElement = buildStyle(` diff --git a/src/features/seen_posts.js b/src/features/seen_posts.js index 7de74b98e..8b16ecedc 100644 --- a/src/features/seen_posts.js +++ b/src/features/seen_posts.js @@ -2,9 +2,10 @@ import { filterPostElements, getTimelineItemWrapper, postSelector } from '../uti import { getPreferences } from '../utils/preferences.js'; import { onNewPosts } from '../utils/mutations.js'; import { keyToCss } from '../utils/css_map.js'; +import { followingTimelineFilter, followingTimelineSelector } from '../utils/timeline_id.js'; const excludeAttribute = 'data-seen-posts-done'; -const timeline = '/v2/timeline/dashboard'; +const timeline = followingTimelineFilter; const includeFiltered = true; const dimAttribute = 'data-seen-posts-seen'; @@ -45,7 +46,7 @@ const markAsSeen = (element) => { }; const lengthenTimelines = () => - [...document.querySelectorAll(`[data-timeline="${timeline}"]`)].forEach(timelineElement => { + [...document.querySelectorAll(followingTimelineSelector)].forEach(timelineElement => { if (!timelineElement.querySelector(keyToCss('manualPaginatorButtons'))) { timelineElement.classList.add(lengthenedClass); } diff --git a/src/features/show_originals.js b/src/features/show_originals.js index 383e7c9e0..39f29eefb 100644 --- a/src/features/show_originals.js +++ b/src/features/show_originals.js @@ -5,12 +5,12 @@ import { onNewPosts } from '../utils/mutations.js'; import { keyToCss } from '../utils/css_map.js'; import { translate } from '../utils/language_data.js'; import { userBlogs } from '../utils/user.js'; +import { followingTimelineFilter, anyBlogTimelineFilter, blogTimelineFilter, blogSubsTimelineFilter, timelineSelector } from '../utils/timeline_id.js'; const hiddenAttribute = 'data-show-originals-hidden'; const lengthenedClass = 'xkit-show-originals-lengthened'; const controlsClass = 'xkit-show-originals-controls'; -const blogTimelineRegex = /^\/v2\/blog\/[a-z0-9-]{1,32}\/posts$/; const channelSelector = `${keyToCss('bar')} ~ *`; const storageKey = 'show_originals.savedModes'; @@ -65,21 +65,20 @@ const addControls = async (timelineElement, location) => { }; const getLocation = timelineElement => { - const { timeline, which } = timelineElement.dataset; - - const isBlog = blogTimelineRegex.test(timeline) && !timelineElement.matches(channelSelector); + const isBlog = + anyBlogTimelineFilter(timelineElement) && !timelineElement.matches(channelSelector); const on = { - dashboard: timeline === '/v2/timeline/dashboard', - disabled: isBlog && disabledBlogs.some(name => timeline === `/v2/blog/${name}/posts`), + dashboard: followingTimelineFilter(timelineElement), + disabled: isBlog && disabledBlogs.some(name => blogTimelineFilter(name)(timelineElement)), peepr: isBlog, - blogSubscriptions: timeline.includes('blog_subscriptions') || which === 'blog_subscriptions' + blogSubscriptions: blogSubsTimelineFilter(timelineElement) }; return Object.keys(on).find(location => on[location]); }; const processTimelines = async () => { - [...document.querySelectorAll('[data-timeline]')].forEach(async timelineElement => { + [...document.querySelectorAll(timelineSelector)].forEach(async timelineElement => { const location = getLocation(timelineElement); const currentControls = [...timelineElement.children] diff --git a/src/features/tag_tracking_plus.js b/src/features/tag_tracking_plus.js index c79732e36..1469d67f2 100644 --- a/src/features/tag_tracking_plus.js +++ b/src/features/tag_tracking_plus.js @@ -6,6 +6,7 @@ import { onNewPosts, pageModifications } from '../utils/mutations.js'; import { dom } from '../utils/dom.js'; import { addSidebarItem, removeSidebarItem } from '../utils/sidebar.js'; import { getPreferences } from '../utils/preferences.js'; +import { tagTimelineFilter } from '../utils/timeline_id.js'; const storageKey = 'tag_tracking_plus.trackedTagTimestamps'; let timestamps; @@ -116,7 +117,7 @@ const processPosts = async function (postElements) { const currentTag = decodeURIComponent(encodedCurrentTag); if (!trackedTags.includes(currentTag)) return; - const timeline = new RegExp(`/v2/hubs/${encodedCurrentTag}/timeline`); + const timeline = tagTimelineFilter(currentTag); let updated = false; diff --git a/src/features/tweaks/caught_up_line.js b/src/features/tweaks/caught_up_line.js index a6e982b3f..b0ab92ee7 100644 --- a/src/features/tweaks/caught_up_line.js +++ b/src/features/tweaks/caught_up_line.js @@ -1,6 +1,7 @@ import { keyToCss } from '../../utils/css_map.js'; import { buildStyle, getTimelineItemWrapper } from '../../utils/interface.js'; import { pageModifications } from '../../utils/mutations.js'; +import { followingTimelineSelector } from '../../utils/timeline_id.js'; const hiddenAttribute = 'data-tweaks-caught-up-line-title'; const borderAttribute = 'data-tweaks-caught-up-line-border'; @@ -23,7 +24,7 @@ const styleElement = buildStyle(` `); const listTimelineObjectSelector = keyToCss('listTimelineObject'); -const tagChicletCarouselLinkSelector = `[data-timeline="/v2/timeline/dashboard"] ${listTimelineObjectSelector} ${keyToCss('tagChicletLink')}`; +const tagChicletCarouselLinkSelector = `${followingTimelineSelector} ${listTimelineObjectSelector} ${keyToCss('tagChicletLink')}`; const createCaughtUpLine = tagChicletCarouselItems => tagChicletCarouselItems .map(getTimelineItemWrapper) diff --git a/src/features/tweaks/hide_liked_posts.js b/src/features/tweaks/hide_liked_posts.js index e452fa76b..65f43d23f 100644 --- a/src/features/tweaks/hide_liked_posts.js +++ b/src/features/tweaks/hide_liked_posts.js @@ -1,8 +1,9 @@ import { onNewPosts } from '../../utils/mutations.js'; import { buildStyle, getTimelineItemWrapper, filterPostElements } from '../../utils/interface.js'; import { isMyPost, timelineObject } from '../../utils/react_props.js'; +import { followingTimelineFilter } from '../../utils/timeline_id.js'; -const timeline = /\/v2\/timeline\/dashboard/; +const timeline = followingTimelineFilter; const hiddenAttribute = 'data-tweaks-hide-liked-posts-hidden'; const styleElement = buildStyle(`[${hiddenAttribute}] article { display: none; }`); diff --git a/src/features/tweaks/hide_my_posts.js b/src/features/tweaks/hide_my_posts.js index c572fd5ac..6b10b4923 100644 --- a/src/features/tweaks/hide_my_posts.js +++ b/src/features/tweaks/hide_my_posts.js @@ -1,9 +1,10 @@ import { onNewPosts } from '../../utils/mutations.js'; import { buildStyle, getTimelineItemWrapper, filterPostElements } from '../../utils/interface.js'; import { isMyPost } from '../../utils/react_props.js'; +import { followingTimelineFilter } from '../../utils/timeline_id.js'; const excludeClass = 'xkit-tweaks-hide-my-posts-done'; -const timeline = /\/v2\/timeline\/dashboard/; +const timeline = followingTimelineFilter; const hiddenAttribute = 'data-tweaks-hide-my-posts-hidden'; const styleElement = buildStyle(`[${hiddenAttribute}] article { display: none; }`); diff --git a/src/utils/interface.js b/src/utils/interface.js index 757653e90..3eb252ca5 100644 --- a/src/utils/interface.js +++ b/src/utils/interface.js @@ -1,5 +1,6 @@ import { keyToCss } from './css_map.js'; import { dom } from './dom.js'; +import { timelineSelector } from './timeline_id.js'; export const postSelector = '[tabindex="-1"][data-id]'; export const blogViewSelector = '[style*="--blog-title-color"] *'; @@ -19,7 +20,9 @@ const targetWrapperSelector = keyToCss( * @returns {Element | null} The timeline item wrapper */ export const getTimelineItemWrapper = element => - element.closest(cellSelector) || element.closest(listTimelineObjectSelector); + (element.closest('[data-timeline-id]') && element.closest(listTimelineObjectSelector)?.parentElement) || + element.closest(cellSelector) || + element.closest(listTimelineObjectSelector); /** * @param {Element} element Element within a popover wrapper @@ -35,7 +38,7 @@ export const getPopoverWrapper = element => { /** * @typedef {object} PostFilterOptions * @property {string} [excludeClass] - Classname to exclude and add - * @property {RegExp|string} [timeline] - Filter results to matching [data-timeline] children + * @property {Function|Function[]} [timeline] - Filter results to matching timeline element children * @property {boolean} [noBlogView] - Whether to exclude posts in the blog view modal * @property {boolean} [includeFiltered] - Whether to include filtered posts */ @@ -51,10 +54,15 @@ export const filterPostElements = function (postElements, { excludeClass, timeli .map(element => element.closest(postSelector)) .filter(Boolean); - if (timeline instanceof RegExp) { - postElements = postElements.filter(postElement => timeline.test(postElement.closest('[data-timeline]')?.dataset.timeline)); - } else if (timeline) { - postElements = postElements.filter(postElement => timeline === postElement.closest('[data-timeline]')?.dataset.timeline); + if (timeline) { + const timelineFilters = [timeline].flat().filter(Boolean); + postElements = postElements.filter(postElement => { + const timelineElement = postElement.closest(timelineSelector); + return ( + timelineElement && + timelineFilters.some(timelineFilter => timelineFilter(timelineElement)) + ); + }); } if (noBlogView) { diff --git a/src/utils/timeline_id.js b/src/utils/timeline_id.js new file mode 100644 index 000000000..368b1d466 --- /dev/null +++ b/src/utils/timeline_id.js @@ -0,0 +1,43 @@ +const createSelector = (...components) => `:is(${components.filter(Boolean).join(', ')})`; + +export const timelineSelector = ':is([data-timeline], [data-timeline-id])'; + +const anyBlog = '[a-z0-9-]{1,32}'; + +export const followingTimelineFilter = ({ dataset: { timeline, timelineId } }) => + timeline === '/v2/timeline/dashboard' || + timelineId === '/dashboard/following'; + +export const followingTimelineSelector = createSelector( + `[data-timeline="${'/v2/timeline/dashboard'}"]`, + `[data-timeline-id="${'/dashboard/following'}"]` +); + +// includes "channel" user blog view page +export const anyBlogTimelineFilter = ({ dataset: { timeline, timelineId } }) => + timeline?.match(`/v2/blog/${anyBlog}/posts`) || + timelineId?.match(`peepr-posts-${anyBlog}-undefined-undefined-undefined-undefined-undefined-undefined`) || + timelineId?.match(`blog-view-${anyBlog}`); + +// includes "channel" user blog view page +export const blogTimelineFilter = blog => + ({ dataset: { timeline, timelineId } }) => + timeline === `/v2/blog/${blog}/posts` || + timelineId === `peepr-posts-${blog}-undefined-undefined-undefined-undefined-undefined-undefined` || + timelineId === `blog-view-${blog}`; + +export const blogSubsTimelineFilter = ({ dataset: { timeline, which, timelineId } }) => + timeline === '/v2/timeline?which=blog_subscriptions' || + which === 'blog_subscriptions' || + timelineId === '/dashboard/blog_subs'; + +export const anyDraftsTimelineFilter = ({ dataset: { timeline, timelineId } }) => + timeline?.match(`/v2/blog/${anyBlog}/posts/draft`); + +export const anyQueueTimelineFilter = ({ dataset: { timeline, timelineId } }) => + timeline?.match(`/v2/blog/${anyBlog}/posts/queue`); + +export const tagTimelineFilter = tag => + ({ dataset: { timeline, timelineId } }) => + timeline === `/v2/hubs/${encodeURIComponent(tag)}/timeline` || + timelineId?.startsWith(`hubsTimeline-${tag}-recent-`); From daccae86816b62208b787fb066f1b89bbccb7e2a Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Sun, 23 Jun 2024 12:29:09 -0700 Subject: [PATCH 03/50] Warn about unloaded/updated extension errors in Chromium (#1486) --- src/utils/modals.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/utils/modals.js b/src/utils/modals.js index 1a2d077a8..3449ee9db 100644 --- a/src/utils/modals.js +++ b/src/utils/modals.js @@ -47,7 +47,12 @@ export const showErrorModal = exception => { showModal({ title: 'Something went wrong.', message: [ - [exception.body?.errors?.[0]?.detail, exception.errors?.[0]?.detail, exception.message] + [ + exception.body?.errors?.[0]?.detail, + exception.errors?.[0]?.detail, + exception.message, + browser.runtime?.id === undefined && 'Please refresh this browser tab!' + ] .filter(Boolean) .join('\n\n') ], From 52f231acb4c136b282693907455932e5d204f475 Mon Sep 17 00:00:00 2001 From: April Sylph <28949509+AprilSylph@users.noreply.github.com> Date: Sun, 23 Jun 2024 20:53:28 +0100 Subject: [PATCH 04/50] tooling: drop `WEB_EXT_ID` from Publish workflow resolves: UsageError: Cannot set custom ID *** because manifest.json declares ID *** --- .github/workflows/publish.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8a7f121a4..3761ed140 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -29,7 +29,6 @@ jobs: env: WEB_EXT_API_KEY: ${{ secrets.WEB_EXT_API_KEY }} WEB_EXT_API_SECRET: ${{ secrets.WEB_EXT_API_SECRET }} - WEB_EXT_ID: ${{ secrets.WEB_EXT_ID }} chrome: name: Chrome runs-on: ubuntu-latest From cdeb99bb8907a33ef67fcb216d4dfbab5cd9ad5f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 10:37:20 +0100 Subject: [PATCH 05/50] Update dev dependencies (#1532) --- package-lock.json | 103 ++++++++++++++++++++++++++++++++++++++-------- package.json | 6 +-- 2 files changed, 89 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2306cf44b..be52e5e54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,14 +14,14 @@ "webextension-polyfill": "^0.12.0" }, "devDependencies": { - "chrome-webstore-upload-cli": "^3.1.0", + "chrome-webstore-upload-cli": "^3.3.0", "eslint": "^8.57.0", "eslint-config-semistandard": "^17.0.0", "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.29.1", - "eslint-plugin-jsdoc": "^48.2.7", + "eslint-plugin-jsdoc": "^48.5.0", "eslint-plugin-n": "^15.7.0", - "eslint-plugin-promise": "^6.2.0", + "eslint-plugin-promise": "^6.4.0", "web-ext": "^7.12.0" } }, @@ -496,6 +496,19 @@ "node": ">=14" } }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/@pnpm/config.env-replace": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", @@ -1656,10 +1669,11 @@ } }, "node_modules/chrome-webstore-upload": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/chrome-webstore-upload/-/chrome-webstore-upload-3.0.2.tgz", - "integrity": "sha512-HqD4zdtvCROH5L6LVXCIFZUhbtBkSVqfxiZM0VVpWO01Dm6cjwCrnb0WY8upHboobfeJgzBGBQNuRMG9sPV9JQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/chrome-webstore-upload/-/chrome-webstore-upload-3.1.0.tgz", + "integrity": "sha512-FpJFVewoeazHEz86vi6KaWq8+YYuX5RgmH6oVGDjeLS75n62qzjlwx1VQP4U2UeOPMNuUA3PYQQI6qkoSWVN8A==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -1668,12 +1682,13 @@ } }, "node_modules/chrome-webstore-upload-cli": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/chrome-webstore-upload-cli/-/chrome-webstore-upload-cli-3.1.0.tgz", - "integrity": "sha512-dctdp+1rPbYF63s0gZWZboReQcQHc+2yy3Chx6C1u7hMnMgDYCJ2JjkyXnDeILmV6Naf6XUsCaqsJgsdZ2HADw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/chrome-webstore-upload-cli/-/chrome-webstore-upload-cli-3.3.0.tgz", + "integrity": "sha512-8KMq/hqqgy4PZ/PD8ElDphJPSn5szTxcc8frkQQ7yJgFAoV0Z5cpTeTY0wh6QcU08AqNiVM/lp62iFw0G6yJ8w==", "dev": true, + "license": "MIT", "dependencies": { - "chrome-webstore-upload": "^3.0.0", + "chrome-webstore-upload": "^3.1.0", "junk": "^4.0.1", "meow": "^12.1.1", "ora": "^7.0.1", @@ -2448,6 +2463,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "dev": true, + "license": "MIT" + }, "node_modules/es-set-tostringtag": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", @@ -2775,9 +2797,9 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "48.2.7", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.2.7.tgz", - "integrity": "sha512-fYj3roTnkFL9OFFTB129rico8lerC5G8Vp2ZW9SjO9RNWG0exVvI+i/Y8Bpm1ufjR0uvT38xtoab/U0Hp8Ybog==", + "version": "48.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.5.0.tgz", + "integrity": "sha512-ukXPNpGby3KjCveCizIS8t1EbuJEHYEu/tBg8GCbn/YbHcXwphyvYCdvRZ/oMRfTscGSSzfsWoZ+ZkAP0/6YMQ==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -2787,8 +2809,10 @@ "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", "esquery": "^1.5.0", + "parse-imports": "^2.1.0", "semver": "^7.6.2", - "spdx-expression-parse": "^4.0.0" + "spdx-expression-parse": "^4.0.0", + "synckit": "^0.9.0" }, "engines": { "node": ">=18" @@ -2872,9 +2896,9 @@ } }, "node_modules/eslint-plugin-promise": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.2.0.tgz", - "integrity": "sha512-QmAqwizauvnKOlifxyDj2ObfULpHQawlg/zQdgEixur9vl0CvZGv/LCJV2rtj3210QCoeGBzVMfMXqGAOr/4fA==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.4.0.tgz", + "integrity": "sha512-/KWWRaD3fGkVCZsdR0RU53PSthFmoHVhZl+y9+6DqeDLSikLdlUVpVEAmI6iCRR5QyOjBYBqHZV/bdv4DJ4Gtw==", "dev": true, "license": "ISC", "engines": { @@ -5609,6 +5633,20 @@ "node": ">=6" } }, + "node_modules/parse-imports": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.1.0.tgz", + "integrity": "sha512-JQWgmK2o4w8leUkZeZPatWdAny6vXGU/3siIUvMF6J2rDCud9aTt8h/px9oZJ6U3EcfhngBJ635uPFI0q0VAeA==", + "dev": true, + "license": "Apache 2.0", + "dependencies": { + "es-module-lexer": "^1.5.3", + "slashes": "^3.0.12" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/parse-json": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-6.0.2.tgz", @@ -6569,6 +6607,13 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "node_modules/slashes": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/slashes/-/slashes-3.0.12.tgz", + "integrity": "sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==", + "dev": true, + "license": "ISC" + }, "node_modules/sonic-boom": { "version": "3.8.1", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz", @@ -6949,6 +6994,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/synckit": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.0.tgz", + "integrity": "sha512-7RnqIMq572L8PeEzKeBINYEJDDxpcH8JEgLwUqBd3TkofhFRbkq4QLR0u+36avGAhCRbk2nnmjcW9SE531hPDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -7047,6 +7109,13 @@ "strip-bom": "^3.0.0" } }, + "node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true, + "license": "0BSD" + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", diff --git a/package.json b/package.json index fa79e9b87..4421eac45 100644 --- a/package.json +++ b/package.json @@ -8,14 +8,14 @@ "build": "web-ext build" }, "devDependencies": { - "chrome-webstore-upload-cli": "^3.1.0", + "chrome-webstore-upload-cli": "^3.3.0", "eslint": "^8.57.0", "eslint-config-semistandard": "^17.0.0", "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.29.1", - "eslint-plugin-jsdoc": "^48.2.7", + "eslint-plugin-jsdoc": "^48.5.0", "eslint-plugin-n": "^15.7.0", - "eslint-plugin-promise": "^6.2.0", + "eslint-plugin-promise": "^6.4.0", "web-ext": "^7.12.0" }, "dependencies": { From 0756439c4a8dc8761b67091d583e580dbbf801e3 Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Sun, 7 Jul 2024 05:08:09 -0700 Subject: [PATCH 06/50] Refactor: Use Array.prototype.at (#1535) --- src/features/quick_reblog.js | 2 +- src/features/trim_reblogs.js | 2 +- src/utils/sidebar.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/features/quick_reblog.js b/src/features/quick_reblog.js index e52fc9082..3d8077914 100644 --- a/src/features/quick_reblog.js +++ b/src/features/quick_reblog.js @@ -75,7 +75,7 @@ ${postSelector} footer button[aria-label="${translate('Reblog')}"]:not([role]) const renderBlogAvatar = async () => { const { value: selectedUuid } = blogSelector; const { avatar } = userBlogs.find(({ uuid }) => uuid === selectedUuid); - const { url } = avatar[avatar.length - 1]; + const { url } = avatar.at(-1); blogAvatar.style.backgroundImage = `url(${url})`; }; blogSelector.addEventListener('change', renderBlogAvatar); diff --git a/src/features/trim_reblogs.js b/src/features/trim_reblogs.js index b1cbb2936..8d6509c05 100644 --- a/src/features/trim_reblogs.js +++ b/src/features/trim_reblogs.js @@ -73,7 +73,7 @@ const onButtonClicked = async function ({ currentTarget: controlButton }) { const createPreviewItem = ({ blog, brokenBlog, content, disableCheckbox = false }) => { const { avatar, name } = blog ?? brokenBlog ?? blogPlaceholder; - const { url: src } = avatar[avatar.length - 1]; + const { url: src } = avatar.at(-1); const textContent = content.map(({ text }) => text).find(Boolean) ?? '\u22EF'; const checkbox = dom('input', { type: 'checkbox' }); diff --git a/src/utils/sidebar.js b/src/utils/sidebar.js index 290c874e6..6ada9a1e7 100644 --- a/src/utils/sidebar.js +++ b/src/utils/sidebar.js @@ -116,7 +116,7 @@ const addSidebarToPage = (siblingCandidates) => { const addSidebarToDrawer = (navItems) => { updateSidebarItemVisibility(); - const lastNavItem = navItems[navItems.length - 1]; + const lastNavItem = navItems.at(-1); lastNavItem?.after(sidebarItems); }; From 8c574abb57c7bede249aaa5b4531618f6f453b16 Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Sat, 13 Jul 2024 11:57:17 -0700 Subject: [PATCH 07/50] Main world injection: Prevent race condition (#1520) --- src/content_scripts/main.js | 18 ++++++++++++------ src/main_world/index.js | 2 ++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/content_scripts/main.js b/src/content_scripts/main.js index d6c8ee1f1..50a30f844 100644 --- a/src/content_scripts/main.js +++ b/src/content_scripts/main.js @@ -76,24 +76,30 @@ return installedScripts; }; - const initMainWorld = () => { + const initMainWorld = () => new Promise(resolve => { + document.documentElement.addEventListener('xkitinjectionready', resolve, { once: true }); + const { nonce } = [...document.scripts].find(script => script.getAttributeNames().includes('nonce')); const script = document.createElement('script'); script.type = 'module'; script.nonce = nonce; script.src = browser.runtime.getURL('/main_world/index.js'); document.documentElement.append(script); - }; + }); const init = async function () { $('style.xkit').remove(); - initMainWorld(); - browser.storage.onChanged.addListener(onStorageChanged); - const installedScripts = await getInstalledScripts(); - const { enabledScripts = [] } = await browser.storage.local.get('enabledScripts'); + const [ + installedScripts, + { enabledScripts = [] } + ] = await Promise.all([ + getInstalledScripts(), + browser.storage.local.get('enabledScripts'), + initMainWorld() + ]); /** * fixes WebKit (Chromium, Safari) simultaneous import failure of files with unresolved top level await diff --git a/src/main_world/index.js b/src/main_world/index.js index 3d52111a8..2d015a182 100644 --- a/src/main_world/index.js +++ b/src/main_world/index.js @@ -28,3 +28,5 @@ document.documentElement.addEventListener('xkitinjectionrequest', async event => ); } }); + +document.documentElement.dispatchEvent(new CustomEvent('xkitinjectionready')); From 8cb0277fe0afe676bb32e8e4a39ff0c1897fc2bc Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Sat, 13 Jul 2024 11:58:50 -0700 Subject: [PATCH 08/50] Timeline ID util: Enforce exact matches (#1524) --- src/utils/timeline_id.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/utils/timeline_id.js b/src/utils/timeline_id.js index 368b1d466..d7419c44e 100644 --- a/src/utils/timeline_id.js +++ b/src/utils/timeline_id.js @@ -2,6 +2,7 @@ const createSelector = (...components) => `:is(${components.filter(Boolean).join export const timelineSelector = ':is([data-timeline], [data-timeline-id])'; +const exactly = string => `^${string}$`; const anyBlog = '[a-z0-9-]{1,32}'; export const followingTimelineFilter = ({ dataset: { timeline, timelineId } }) => @@ -15,9 +16,9 @@ export const followingTimelineSelector = createSelector( // includes "channel" user blog view page export const anyBlogTimelineFilter = ({ dataset: { timeline, timelineId } }) => - timeline?.match(`/v2/blog/${anyBlog}/posts`) || - timelineId?.match(`peepr-posts-${anyBlog}-undefined-undefined-undefined-undefined-undefined-undefined`) || - timelineId?.match(`blog-view-${anyBlog}`); + timeline?.match(exactly(`/v2/blog/${anyBlog}/posts`)) || + timelineId?.match(exactly(`peepr-posts-${anyBlog}-undefined-undefined-undefined-undefined-undefined-undefined`)) || + timelineId?.match(exactly(`blog-view-${anyBlog}`)); // includes "channel" user blog view page export const blogTimelineFilter = blog => @@ -32,10 +33,10 @@ export const blogSubsTimelineFilter = ({ dataset: { timeline, which, timelineId timelineId === '/dashboard/blog_subs'; export const anyDraftsTimelineFilter = ({ dataset: { timeline, timelineId } }) => - timeline?.match(`/v2/blog/${anyBlog}/posts/draft`); + timeline?.match(exactly(`/v2/blog/${anyBlog}/posts/draft`)); export const anyQueueTimelineFilter = ({ dataset: { timeline, timelineId } }) => - timeline?.match(`/v2/blog/${anyBlog}/posts/queue`); + timeline?.match(exactly(`/v2/blog/${anyBlog}/posts/queue`)); export const tagTimelineFilter = tag => ({ dataset: { timeline, timelineId } }) => From 3d4db5428a0d6a615bf12abf99e35c31a349ec04 Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Sat, 13 Jul 2024 12:04:58 -0700 Subject: [PATCH 09/50] Quick Reblog: Don't clear popup state on error (#1484) --- src/features/quick_reblog.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/features/quick_reblog.js b/src/features/quick_reblog.js index 3d8077914..d48ae26f7 100644 --- a/src/features/quick_reblog.js +++ b/src/features/quick_reblog.js @@ -175,7 +175,6 @@ const reblogPost = async function ({ currentTarget }) { currentTarget.blur(); actionButtons.disabled = true; - lastPostID = null; const postElement = currentTarget.closest(postSelector); const postID = postElement.dataset.id; @@ -204,8 +203,12 @@ const reblogPost = async function ({ currentTarget }) { const { meta, response } = await apiFetch(requestPath, { method: 'POST', body: requestBody }); if (meta.status === 201) { makeButtonReblogged({ buttonDiv: currentReblogButton, state }); - if (lastPostID === null) { + + if (lastPostID === postID) { popupElement.remove(); + lastPostID = null; + } else { + // popup was moved to another post during apiFetch } notify(response.displayText); From 4631a329ac0644e61116ac5e8b75dd46ed9418f7 Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Sat, 13 Jul 2024 12:07:56 -0700 Subject: [PATCH 10/50] Timeline ID util: Process current Tumblr Patio IDs (#1525) --- src/utils/timeline_id.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/utils/timeline_id.js b/src/utils/timeline_id.js index d7419c44e..a70764bc9 100644 --- a/src/utils/timeline_id.js +++ b/src/utils/timeline_id.js @@ -4,28 +4,33 @@ export const timelineSelector = ':is([data-timeline], [data-timeline-id])'; const exactly = string => `^${string}$`; const anyBlog = '[a-z0-9-]{1,32}'; +const uuidV4 = '[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[a-f0-9]{4}-[a-f0-9]{12}'; export const followingTimelineFilter = ({ dataset: { timeline, timelineId } }) => timeline === '/v2/timeline/dashboard' || - timelineId === '/dashboard/following'; + timelineId === '/dashboard/following' || + timelineId?.startsWith('following-'); export const followingTimelineSelector = createSelector( `[data-timeline="${'/v2/timeline/dashboard'}"]`, - `[data-timeline-id="${'/dashboard/following'}"]` + `[data-timeline-id="${'/dashboard/following'}"]`, + `[data-timeline-id^="${'following-'}"]` ); // includes "channel" user blog view page export const anyBlogTimelineFilter = ({ dataset: { timeline, timelineId } }) => timeline?.match(exactly(`/v2/blog/${anyBlog}/posts`)) || timelineId?.match(exactly(`peepr-posts-${anyBlog}-undefined-undefined-undefined-undefined-undefined-undefined`)) || - timelineId?.match(exactly(`blog-view-${anyBlog}`)); + timelineId?.match(exactly(`blog-view-${anyBlog}`)) || + timelineId?.match(exactly(`blog-${uuidV4}-${anyBlog}`)); // includes "channel" user blog view page export const blogTimelineFilter = blog => ({ dataset: { timeline, timelineId } }) => timeline === `/v2/blog/${blog}/posts` || timelineId === `peepr-posts-${blog}-undefined-undefined-undefined-undefined-undefined-undefined` || - timelineId === `blog-view-${blog}`; + timelineId === `blog-view-${blog}` || + timelineId?.match(exactly(`blog-${uuidV4}-${blog}`)); export const blogSubsTimelineFilter = ({ dataset: { timeline, which, timelineId } }) => timeline === '/v2/timeline?which=blog_subscriptions' || @@ -33,12 +38,15 @@ export const blogSubsTimelineFilter = ({ dataset: { timeline, which, timelineId timelineId === '/dashboard/blog_subs'; export const anyDraftsTimelineFilter = ({ dataset: { timeline, timelineId } }) => - timeline?.match(exactly(`/v2/blog/${anyBlog}/posts/draft`)); + timeline?.match(exactly(`/v2/blog/${anyBlog}/posts/draft`)) || + timelineId?.match(exactly(`drafts-${uuidV4}-${anyBlog}`)); export const anyQueueTimelineFilter = ({ dataset: { timeline, timelineId } }) => - timeline?.match(exactly(`/v2/blog/${anyBlog}/posts/queue`)); + timeline?.match(exactly(`/v2/blog/${anyBlog}/posts/queue`)) || + timelineId?.match(exactly(`queue-${uuidV4}-${anyBlog}`)); export const tagTimelineFilter = tag => ({ dataset: { timeline, timelineId } }) => timeline === `/v2/hubs/${encodeURIComponent(tag)}/timeline` || - timelineId?.startsWith(`hubsTimeline-${tag}-recent-`); + timelineId?.startsWith(`hubsTimeline-${tag}-recent-`) || + timelineId?.match(exactly(`tag-${uuidV4}-${tag}-recent`)); From f0d7867f3a4dac1f3bb489ed3f4b2577f93f23ed Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Sat, 13 Jul 2024 12:10:59 -0700 Subject: [PATCH 11/50] Tag Tracking+: Remove search dropdown mode (#1537) Co-authored-by: April Sylph <28949509+AprilSylph@users.noreply.github.com> --- src/features/tag_tracking_plus.css | 5 --- src/features/tag_tracking_plus.js | 66 ++++------------------------- src/features/tag_tracking_plus.json | 14 +----- 3 files changed, 11 insertions(+), 74 deletions(-) diff --git a/src/features/tag_tracking_plus.css b/src/features/tag_tracking_plus.css index ebe36fa30..c27fee4af 100644 --- a/src/features/tag_tracking_plus.css +++ b/src/features/tag_tracking_plus.css @@ -27,8 +27,3 @@ #tag-tracking-plus[data-only-show-new="true"] li:not([data-new="true"]) { display: none; } - -[data-tag-tracking-plus-show-sidebar="false"] #tag-tracking-plus, -[data-tag-tracking-plus-show-search="false"] .xkit-tag-tracking-plus-search-count { - display: none; -} diff --git a/src/features/tag_tracking_plus.js b/src/features/tag_tracking_plus.js index 1469d67f2..2e838c2ac 100644 --- a/src/features/tag_tracking_plus.js +++ b/src/features/tag_tracking_plus.js @@ -1,9 +1,7 @@ import { apiFetch, onClickNavigate } from '../utils/tumblr_helpers.js'; import { filterPostElements } from '../utils/interface.js'; import { timelineObject } from '../utils/react_props.js'; -import { keyToCss } from '../utils/css_map.js'; -import { onNewPosts, pageModifications } from '../utils/mutations.js'; -import { dom } from '../utils/dom.js'; +import { onNewPosts } from '../utils/mutations.js'; import { addSidebarItem, removeSidebarItem } from '../utils/sidebar.js'; import { getPreferences } from '../utils/preferences.js'; import { tagTimelineFilter } from '../utils/timeline_id.js'; @@ -11,14 +9,9 @@ import { tagTimelineFilter } from '../utils/timeline_id.js'; const storageKey = 'tag_tracking_plus.trackedTagTimestamps'; let timestamps; -const searchCountClass = 'xkit-tag-tracking-plus-search-count'; - const excludeClass = 'xkit-tag-tracking-plus-done'; const includeFiltered = true; -const tagLinkSelector = `${keyToCss('searchResult')} h3 ~ a${keyToCss('typeaheadRow')}[href^="/tagged/"]`; -const tagTextSelector = keyToCss('tagText'); - let trackedTags; const unreadCounts = new Map(); @@ -65,19 +58,12 @@ const refreshCount = async function (tag) { console.error(exception); } - [document, ...(!sidebarItem || document.contains(sidebarItem) ? [] : [sidebarItem])] - .flatMap(node => - [...node.querySelectorAll('[data-count-for]')].filter( - ({ dataset: { countFor } }) => countFor === `#${tag}` - ) - ) - .filter((value, index, array) => array.indexOf(value) === index) - .forEach(unreadCountElement => { - unreadCountElement.textContent = unreadCountString; - if (unreadCountElement.closest('li')) { - unreadCountElement.closest('li').dataset.new = unreadCountString !== '0'; - } - }); + const unreadCountElement = sidebarItem.querySelector(`[data-count-for="#${tag}"]`); + + unreadCountElement.textContent = unreadCountString; + if (unreadCountElement.closest('li')) { + unreadCountElement.closest('li').dataset.new = unreadCountString !== '0'; + } unreadCounts.set(tag, unreadCountString); updateSidebarStatus(); @@ -141,36 +127,12 @@ const processPosts = async function (postElements) { } }; -const processTagLinks = function (tagLinkElements) { - tagLinkElements.forEach(tagLinkElement => { - if (tagLinkElement.querySelector('[data-count-for]') !== null) return; - - const tagTextElement = tagLinkElement.querySelector(tagTextSelector); - const tag = tagTextElement.textContent; - const unreadCountElement = dom( - 'span', - { - class: searchCountClass, - 'data-count-for': `#${tag}`, - style: 'margin-left: auto; margin-right: 1ch; opacity: 0.65;' - }, - null, - [unreadCounts.get(tag) ?? '\u22EF'] - ); - - tagTextElement.after(unreadCountElement); - }); -}; - export const onStorageChanged = async (changes, areaName) => { if (Object.keys(changes).includes(storageKey)) { timestamps = changes[storageKey].newValue; } if (Object.keys(changes).some(key => key.startsWith('tag_tracking_plus.preferences'))) { - const { showUnread, onlyShowNew } = await getPreferences('tag_tracking_plus'); - - document.body.dataset.tagTrackingPlusShowSearch = showUnread === 'both' || showUnread === 'search'; - document.body.dataset.tagTrackingPlusShowSidebar = showUnread === 'both' || showUnread === 'sidebar'; + const { onlyShowNew } = await getPreferences('tag_tracking_plus'); sidebarItem.dataset.onlyShowNew = onlyShowNew; } }; @@ -183,11 +145,7 @@ export const main = async function () { ({ [storageKey]: timestamps = {} } = await browser.storage.local.get(storageKey)); - const { showUnread, onlyShowNew } = await getPreferences('tag_tracking_plus'); - document.body.dataset.tagTrackingPlusShowSearch = showUnread === 'both' || showUnread === 'search'; - document.body.dataset.tagTrackingPlusShowSidebar = showUnread === 'both' || showUnread === 'sidebar'; - - pageModifications.register(tagLinkSelector, processTagLinks); + const { onlyShowNew } = await getPreferences('tag_tracking_plus'); sidebarItem = addSidebarItem({ id: 'tag-tracking-plus', @@ -209,16 +167,10 @@ export const main = async function () { export const clean = async function () { stopRefreshInterval(); onNewPosts.removeListener(processPosts); - pageModifications.unregister(processTagLinks); removeSidebarItem('tag-tracking-plus'); - $(`.${searchCountClass}`).remove(); - - document.body.removeAttribute('data-tag-tracking-plus-show-sidebar'); - document.body.removeAttribute('data-tag-tracking-plus-show-search'); unreadCounts.clear(); - sidebarItem = undefined; }; export const stylesheet = true; diff --git a/src/features/tag_tracking_plus.json b/src/features/tag_tracking_plus.json index 490f21b26..0f28e38f2 100644 --- a/src/features/tag_tracking_plus.json +++ b/src/features/tag_tracking_plus.json @@ -1,6 +1,6 @@ { "title": "Tag Tracking+", - "description": "Unread counts on followed tags", + "description": "Unread counts for followed tags", "icon": { "class_name": "ri-hashtag", "color": "white", @@ -8,19 +8,9 @@ }, "help": "https://github.com/AprilSylph/XKit-Rewritten/wiki/Features#tag-tracking", "preferences": { - "showUnread": { - "type": "select", - "label": "Show unread counts", - "options": [ - { "value": "both", "label": "Both in search & sidebar" }, - { "value": "search", "label": "Only in the search dropdown" }, - { "value": "sidebar", "label": "Only in the sidebar" } - ], - "default": "both" - }, "onlyShowNew": { "type": "checkbox", - "label": "Only show tags with new posts in the sidebar", + "label": "Only show tags with new posts", "default": false } } From dbf6b20ecaf74ffc56a63c98efde069f892aec65 Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Sat, 13 Jul 2024 12:11:08 -0700 Subject: [PATCH 12/50] Timeline id util: Process drafts timeline (#1530) --- src/utils/timeline_id.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/timeline_id.js b/src/utils/timeline_id.js index a70764bc9..f91755d3e 100644 --- a/src/utils/timeline_id.js +++ b/src/utils/timeline_id.js @@ -39,6 +39,7 @@ export const blogSubsTimelineFilter = ({ dataset: { timeline, which, timelineId export const anyDraftsTimelineFilter = ({ dataset: { timeline, timelineId } }) => timeline?.match(exactly(`/v2/blog/${anyBlog}/posts/draft`)) || + timelineId?.match(exactly(`drafts-${anyBlog}`)) || timelineId?.match(exactly(`drafts-${uuidV4}-${anyBlog}`)); export const anyQueueTimelineFilter = ({ dataset: { timeline, timelineId } }) => From ba2fce796644f5fce4c420a0ad2146f881162a3e Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Sat, 13 Jul 2024 12:17:04 -0700 Subject: [PATCH 13/50] Tweaks: Fix "restore attribution" breaking Communities (#1495) --- src/features/tweaks/restore_attribution_links.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/tweaks/restore_attribution_links.js b/src/features/tweaks/restore_attribution_links.js index 8c33b2e6c..51a733b1a 100644 --- a/src/features/tweaks/restore_attribution_links.js +++ b/src/features/tweaks/restore_attribution_links.js @@ -29,7 +29,7 @@ const processPosts = async function (postElements) { } = await timelineObject(postElement); const postAttributionLink = postElement.querySelector(postAttributionLinkSelector); - if (postAttributionLink !== null) { + if (postAttributionLink && postAttributionLink.textContent === blogName) { postAttributionLink.href = postUrl; postAttributionLink.dataset.blogName = blogName; postAttributionLink.dataset.postId = id; @@ -37,7 +37,7 @@ const processPosts = async function (postElements) { } const reblogAttributionLink = postElement.querySelector(reblogAttributionLinkSelector); - if (reblogAttributionLink !== null && rebloggedFromUrl !== undefined) { + if (reblogAttributionLink && reblogAttributionLink.textContent === rebloggedFromName) { reblogAttributionLink.href = rebloggedFromUrl; reblogAttributionLink.dataset.blogName = rebloggedFromName; reblogAttributionLink.dataset.postId = rebloggedFromId; From d03d7fe3442aec2aa013de5e9d154ad7e049ec4d Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Sat, 13 Jul 2024 12:24:32 -0700 Subject: [PATCH 14/50] PostBlock: Block posts instantly (#1516) --- src/features/postblock.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/features/postblock.js b/src/features/postblock.js index 5123b640d..802107487 100644 --- a/src/features/postblock.js +++ b/src/features/postblock.js @@ -10,9 +10,9 @@ const meatballButtonLabel = 'Block this post'; const hiddenAttribute = 'data-postblock-hidden'; const storageKey = 'postblock.blockedPostRootIDs'; -const processPosts = async function (postElements) { - const { [storageKey]: blockedPostRootIDs = [] } = await browser.storage.local.get(storageKey); +let blockedPostRootIDs = []; +const processPosts = postElements => filterPostElements(postElements, { includeFiltered: true }).forEach(async postElement => { const postID = postElement.dataset.id; const { rebloggedRootId } = await timelineObject(postElement); @@ -25,9 +25,8 @@ const processPosts = async function (postElements) { getTimelineItemWrapper(postElement).removeAttribute(hiddenAttribute); } }); -}; -const onButtonClicked = async function ({ currentTarget }) { +const onButtonClicked = ({ currentTarget }) => { const { id, rebloggedRootId } = currentTarget.__timelineObjectData; const rootID = rebloggedRootId || id; @@ -52,11 +51,14 @@ const blockPost = async rootID => { export const onStorageChanged = async function (changes, areaName) { if (Object.keys(changes).includes(storageKey)) { + ({ newValue: blockedPostRootIDs = [] } = changes[storageKey]); pageModifications.trigger(processPosts); } }; export const main = async function () { + ({ [storageKey]: blockedPostRootIDs = [] } = await browser.storage.local.get(storageKey)); + registerMeatballItem({ id: meatballButtonId, label: meatballButtonLabel, onclick: onButtonClicked }); onNewPosts.addListener(processPosts); From 76411a6fb2fde3bbd267cbe639a3e624ac4a60e6 Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Sat, 13 Jul 2024 12:29:59 -0700 Subject: [PATCH 15/50] Inject util: Stop processing disconnected elements (#1518) --- src/main_world/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main_world/index.js b/src/main_world/index.js index 2d015a182..63f12c1a2 100644 --- a/src/main_world/index.js +++ b/src/main_world/index.js @@ -8,6 +8,8 @@ document.documentElement.addEventListener('xkitinjectionrequest', async event => moduleCache[path] ??= await import(path); const func = moduleCache[path].default; + if (target.isConnected === false) return; + const result = await func.apply(target, args); target.dispatchEvent( new CustomEvent('xkitinjectionresponse', { detail: JSON.stringify({ id, result }) }) From 2d1c98933329f4868b23988c98a2ce7cbf8f7c66 Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Sat, 13 Jul 2024 13:01:33 -0700 Subject: [PATCH 16/50] Implement Scroll to Bottom on Timeline V2 (#1493) Co-authored-by: April Sylph <28949509+AprilSylph@users.noreply.github.com> --- src/features/scroll_to_bottom.js | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/features/scroll_to_bottom.js b/src/features/scroll_to_bottom.js index e6713c6ff..640abc3e0 100644 --- a/src/features/scroll_to_bottom.js +++ b/src/features/scroll_to_bottom.js @@ -8,8 +8,8 @@ $(`[id="${scrollToBottomButtonId}"]`).remove(); const activeClass = 'xkit-scroll-to-bottom-active'; const loaderSelector = ` -${keyToCss('timeline', 'blogRows')} > :last-child, -${keyToCss('notifications')} + ${keyToCss('loader')} +${keyToCss('timeline', 'blogRows')} > :is(${keyToCss('scrollContainer')}, .sortableContainer) + div, +${keyToCss('notifications')} + div `; const knightRiderLoaderSelector = `:is(${loaderSelector}) > ${keyToCss('knightRiderLoader')}`; @@ -22,13 +22,23 @@ const styleElement = buildStyle(` } `); +let timeoutID; + +const onLoadersAdded = loaders => { + if (active) { + clearTimeout(timeoutID); + } +}; + const scrollToBottom = () => { + clearTimeout(timeoutID); window.scrollTo({ top: document.documentElement.scrollHeight }); - const loaders = [...document.querySelectorAll(knightRiderLoaderSelector)]; - if (loaders.length === 0) { - stopScrolling(); - } + timeoutID = setTimeout(() => { + if (!document.querySelector(knightRiderLoaderSelector)) { + stopScrolling(); + } + }, 500); }; const observer = new ResizeObserver(scrollToBottom); @@ -40,6 +50,7 @@ const startScrolling = () => { }; const stopScrolling = () => { + clearTimeout(timeoutID); observer.disconnect(); active = false; scrollToBottomButton?.classList.remove(activeClass); @@ -79,12 +90,14 @@ const addButtonToPage = async function ([scrollToTopButton]) { export const main = async function () { pageModifications.register(`button[aria-label="${translate('Scroll to top')}"]`, addButtonToPage); + pageModifications.register(knightRiderLoaderSelector, onLoadersAdded); document.documentElement.append(styleElement); }; export const clean = async function () { pageModifications.unregister(addButtonToPage); pageModifications.unregister(checkForButtonRemoved); + pageModifications.unregister(onLoadersAdded); stopScrolling(); scrollToBottomButton?.remove(); styleElement.remove(); From 526466be5b63dd7783204f13df1cca1c12a62374 Mon Sep 17 00:00:00 2001 From: April Sylph <28949509+AprilSylph@users.noreply.github.com> Date: Sun, 14 Jul 2024 19:52:57 +0100 Subject: [PATCH 17/50] bump version to 1.0.1 --- src/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/manifest.json b/src/manifest.json index 8d85a15cf..aa6b68274 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "XKit Rewritten", - "version": "1.0.0", + "version": "1.0.1", "short_name": "XKit", "author": "April Sylph", From 9383a0000181cf4c7e3db1e63899358cd893c4a2 Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Thu, 18 Jul 2024 05:21:26 -0700 Subject: [PATCH 18/50] Fix extension restarts in Firefox (#1551) --- src/content_scripts/main.js | 1 - src/main_world/index.js | 60 ++++++++++++++++++++----------------- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/src/content_scripts/main.js b/src/content_scripts/main.js index 50a30f844..ce9b29633 100644 --- a/src/content_scripts/main.js +++ b/src/content_scripts/main.js @@ -81,7 +81,6 @@ const { nonce } = [...document.scripts].find(script => script.getAttributeNames().includes('nonce')); const script = document.createElement('script'); - script.type = 'module'; script.nonce = nonce; script.src = browser.runtime.getURL('/main_world/index.js'); document.documentElement.append(script); diff --git a/src/main_world/index.js b/src/main_world/index.js index 63f12c1a2..5d7bc1d12 100644 --- a/src/main_world/index.js +++ b/src/main_world/index.js @@ -1,34 +1,38 @@ -const moduleCache = {}; +'use strict'; -document.documentElement.addEventListener('xkitinjectionrequest', async event => { - const { detail, target } = event; - const { id, path, args } = JSON.parse(detail); +{ + const moduleCache = {}; - try { - moduleCache[path] ??= await import(path); - const func = moduleCache[path].default; + document.documentElement.addEventListener('xkitinjectionrequest', async event => { + const { detail, target } = event; + const { id, path, args } = JSON.parse(detail); - if (target.isConnected === false) return; + try { + moduleCache[path] ??= await import(path); + const func = moduleCache[path].default; - const result = await func.apply(target, args); - target.dispatchEvent( - new CustomEvent('xkitinjectionresponse', { detail: JSON.stringify({ id, result }) }) - ); - } catch (exception) { - target.dispatchEvent( - new CustomEvent('xkitinjectionresponse', { - detail: JSON.stringify({ - id, - exception: { - message: exception.message, - name: exception.name, - stack: exception.stack, - ...exception - } + if (target.isConnected === false) return; + + const result = await func.apply(target, args); + target.dispatchEvent( + new CustomEvent('xkitinjectionresponse', { detail: JSON.stringify({ id, result }) }) + ); + } catch (exception) { + target.dispatchEvent( + new CustomEvent('xkitinjectionresponse', { + detail: JSON.stringify({ + id, + exception: { + message: exception.message, + name: exception.name, + stack: exception.stack, + ...exception + } + }) }) - }) - ); - } -}); + ); + } + }); -document.documentElement.dispatchEvent(new CustomEvent('xkitinjectionready')); + document.documentElement.dispatchEvent(new CustomEvent('xkitinjectionready')); +} From ac7e4b3bce841b3b6b6e18e60c4457c4109be2f4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 1 Aug 2024 14:00:20 +0100 Subject: [PATCH 19/50] Update dev dependencies (#1557) --- package-lock.json | 404 +++++++++------------------------------------- package.json | 6 +- 2 files changed, 79 insertions(+), 331 deletions(-) diff --git a/package-lock.json b/package-lock.json index be52e5e54..572c3e5ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,14 +14,14 @@ "webextension-polyfill": "^0.12.0" }, "devDependencies": { - "chrome-webstore-upload-cli": "^3.3.0", + "chrome-webstore-upload-cli": "^3.3.1", "eslint": "^8.57.0", "eslint-config-semistandard": "^17.0.0", "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.29.1", - "eslint-plugin-jsdoc": "^48.5.0", + "eslint-plugin-jsdoc": "^48.10.2", "eslint-plugin-n": "^15.7.0", - "eslint-plugin-promise": "^6.4.0", + "eslint-plugin-promise": "^6.6.0", "web-ext": "^7.12.0" } }, @@ -264,17 +264,14 @@ } }, "node_modules/@es-joy/jsdoccomment": { - "version": "0.43.1", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.43.1.tgz", - "integrity": "sha512-I238eDtOolvCuvtxrnqtlBaw0BwdQuYqK7eA6XIonicMdOOOb75mqdIzkGDUbS04+1Di007rgm9snFRNeVrOog==", + "version": "0.46.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.46.0.tgz", + "integrity": "sha512-C3Axuq1xd/9VqFZpW4YAzOx5O9q/LP46uIQy/iNDpHG3fmPa6TBtvfglMCs3RBiBxAIi0Go97r8+jvTt55XMyQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/eslint": "^8.56.5", - "@types/estree": "^1.0.5", - "@typescript-eslint/types": "^7.2.0", "comment-parser": "1.4.1", - "esquery": "^1.5.0", + "esquery": "^1.6.0", "jsdoc-type-pratt-parser": "~4.0.0" }, "engines": { @@ -568,34 +565,12 @@ "node": ">=14.16" } }, - "node_modules/@types/eslint": { - "version": "8.56.10", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", - "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", - "dev": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true - }, "node_modules/@types/http-cache-semantics": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", "dev": true }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true - }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -627,20 +602,6 @@ "@types/node": "*" } }, - "node_modules/@typescript-eslint/types": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.11.0.tgz", - "integrity": "sha512-MPEsDRZTyCiXkD4vd3zywDCifi7tatc4K37KqTprCvaXptP7Xlpdw0NR2hRJTetG5TxbWDB79Ys4kLmHliEo/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", @@ -661,10 +622,11 @@ } }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -1288,17 +1250,6 @@ "tweetnacl": "^0.14.3" } }, - "node_modules/bl": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", - "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", - "dev": true, - "dependencies": { - "buffer": "^6.0.3", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -1682,16 +1633,15 @@ } }, "node_modules/chrome-webstore-upload-cli": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/chrome-webstore-upload-cli/-/chrome-webstore-upload-cli-3.3.0.tgz", - "integrity": "sha512-8KMq/hqqgy4PZ/PD8ElDphJPSn5szTxcc8frkQQ7yJgFAoV0Z5cpTeTY0wh6QcU08AqNiVM/lp62iFw0G6yJ8w==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/chrome-webstore-upload-cli/-/chrome-webstore-upload-cli-3.3.1.tgz", + "integrity": "sha512-9QHK6/yQAPttUK68Vzrj93nQUWKo0UMYJwG2KTkcUQke31QuzsYB1XGqmNd+XrQj7rxDTyGZa1QJznIgIXkmRw==", "dev": true, "license": "MIT", "dependencies": { "chrome-webstore-upload": "^3.1.0", "junk": "^4.0.1", "meow": "^12.1.1", - "ora": "^7.0.1", "recursive-readdir": "^2.2.3", "yazl": "^2.5.1" }, @@ -1732,33 +1682,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cli-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", - "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", - "dev": true, - "dependencies": { - "restore-cursor": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -2095,10 +2018,11 @@ "dev": true }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "dev": true, + "license": "MIT", "dependencies": { "ms": "2.1.2" }, @@ -2374,12 +2298,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/emoji-regex": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", - "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", - "dev": true - }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -2797,22 +2715,23 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "48.5.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.5.0.tgz", - "integrity": "sha512-ukXPNpGby3KjCveCizIS8t1EbuJEHYEu/tBg8GCbn/YbHcXwphyvYCdvRZ/oMRfTscGSSzfsWoZ+ZkAP0/6YMQ==", + "version": "48.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.10.2.tgz", + "integrity": "sha512-xTkf/MmEeVrTbezc6kDqCJmK9RcseIKo8X4oyoDCMvV4LY8dqrQi8kmfRrv9n0gNBkCclevaOh2Lkmu6Fs8SLg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "@es-joy/jsdoccomment": "~0.43.1", + "@es-joy/jsdoccomment": "~0.46.0", "are-docs-informative": "^0.0.2", "comment-parser": "1.4.1", - "debug": "^4.3.4", + "debug": "^4.3.5", "escape-string-regexp": "^4.0.0", - "esquery": "^1.5.0", - "parse-imports": "^2.1.0", - "semver": "^7.6.2", + "espree": "^10.1.0", + "esquery": "^1.6.0", + "parse-imports": "^2.1.1", + "semver": "^7.6.3", "spdx-expression-parse": "^4.0.0", - "synckit": "^0.9.0" + "synckit": "^0.9.1" }, "engines": { "node": ">=18" @@ -2821,10 +2740,41 @@ "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" } }, + "node_modules/eslint-plugin-jsdoc/node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/espree": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", + "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.12.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/eslint-plugin-jsdoc/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, "license": "ISC", "bin": { @@ -2896,9 +2846,9 @@ } }, "node_modules/eslint-plugin-promise": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.4.0.tgz", - "integrity": "sha512-/KWWRaD3fGkVCZsdR0RU53PSthFmoHVhZl+y9+6DqeDLSikLdlUVpVEAmI6iCRR5QyOjBYBqHZV/bdv4DJ4Gtw==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.6.0.tgz", + "integrity": "sha512-57Zzfw8G6+Gq7axm2Pdo3gW/Rx3h9Yywgn61uE/3elTCOePEHVrn2i5CdfBwA1BLK0Q0WqctICIUSqXZW/VprQ==", "dev": true, "license": "ISC", "engines": { @@ -3006,10 +2956,11 @@ } }, "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" }, @@ -4170,18 +4121,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-interactive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-mergeable-object": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-mergeable-object/-/is-mergeable-object-1.1.1.tgz", @@ -4345,18 +4284,6 @@ "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", "dev": true }, - "node_modules/is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", @@ -4814,34 +4741,6 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "node_modules/log-symbols": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", - "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==", - "dev": true, - "dependencies": { - "chalk": "^5.0.0", - "is-unicode-supported": "^1.1.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/lowercase-keys": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", @@ -5425,68 +5324,6 @@ "node": ">= 0.8.0" } }, - "node_modules/ora": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-7.0.1.tgz", - "integrity": "sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==", - "dev": true, - "dependencies": { - "chalk": "^5.3.0", - "cli-cursor": "^4.0.0", - "cli-spinners": "^2.9.0", - "is-interactive": "^2.0.0", - "is-unicode-supported": "^1.3.0", - "log-symbols": "^5.1.0", - "stdin-discarder": "^0.1.0", - "string-width": "^6.1.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ora/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ora/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/os-locale": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-5.0.0.tgz", @@ -5634,11 +5471,11 @@ } }, "node_modules/parse-imports": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.1.0.tgz", - "integrity": "sha512-JQWgmK2o4w8leUkZeZPatWdAny6vXGU/3siIUvMF6J2rDCud9aTt8h/px9oZJ6U3EcfhngBJ635uPFI0q0VAeA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.1.1.tgz", + "integrity": "sha512-TDT4HqzUiTMO1wJRwg/t/hYk8Wdp3iF/ToMIlAoVQfL1Xs/sTxq1dKWSMjMbQmIarfWKymOyly40+zmPHXMqCA==", "dev": true, - "license": "Apache 2.0", + "license": "Apache-2.0", "dependencies": { "es-module-lexer": "^1.5.3", "slashes": "^3.0.12" @@ -5988,20 +5825,6 @@ "node": ">=0.10.0" } }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/real-require": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", @@ -6292,22 +6115,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/restore-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", - "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", - "dev": true, - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -6728,21 +6535,6 @@ "node": ">=0.10.0" } }, - "node_modules/stdin-discarder": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz", - "integrity": "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==", - "dev": true, - "dependencies": { - "bl": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/stream-to-array": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/stream-to-array/-/stream-to-array-2.3.0.tgz", @@ -6775,23 +6567,6 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/string-width": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-6.1.0.tgz", - "integrity": "sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==", - "dev": true, - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^10.2.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/string-width-cjs": { "name": "string-width", "version": "4.2.3", @@ -6815,33 +6590,6 @@ "dev": true, "license": "MIT" }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/string.prototype.trim": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", @@ -6995,9 +6743,9 @@ } }, "node_modules/synckit": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.0.tgz", - "integrity": "sha512-7RnqIMq572L8PeEzKeBINYEJDDxpcH8JEgLwUqBd3TkofhFRbkq4QLR0u+36avGAhCRbk2nnmjcW9SE531hPDg==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz", + "integrity": "sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 4421eac45..3d9c9cfe3 100644 --- a/package.json +++ b/package.json @@ -8,14 +8,14 @@ "build": "web-ext build" }, "devDependencies": { - "chrome-webstore-upload-cli": "^3.3.0", + "chrome-webstore-upload-cli": "^3.3.1", "eslint": "^8.57.0", "eslint-config-semistandard": "^17.0.0", "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.29.1", - "eslint-plugin-jsdoc": "^48.5.0", + "eslint-plugin-jsdoc": "^48.10.2", "eslint-plugin-n": "^15.7.0", - "eslint-plugin-promise": "^6.4.0", + "eslint-plugin-promise": "^6.6.0", "web-ext": "^7.12.0" }, "dependencies": { From b20d7f156905924658d3973b092880a933cf207c Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Sun, 18 Aug 2024 13:31:59 -0700 Subject: [PATCH 20/50] Cleanfeed, Quick Reblog: Include submitting blogs/group blog authors (#1440) --- src/features/cleanfeed.js | 8 +++++--- src/features/quick_reblog.js | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/features/cleanfeed.js b/src/features/cleanfeed.js index d6b9f1763..0479ffaa9 100644 --- a/src/features/cleanfeed.js +++ b/src/features/cleanfeed.js @@ -21,11 +21,13 @@ const processPosts = postElements => filterPostElements(postElements).forEach(as return; } - const { blog: { name, isAdult }, communityLabels, trail, tags } = await timelineObject(postElement); + const { blog, authorBlog, communityLabels, trail, tags } = await timelineObject(postElement); - if (isAdult || + if (blog.isAdult || + authorBlog?.isAdult || communityLabels.hasCommunityLabel || - localFlaggedBlogs.includes(name) || + localFlaggedBlogs.includes(blog.name) || + localFlaggedBlogs.includes(authorBlog?.name) || localFlaggedTags.some(t => tags.map(tag => tag.toLowerCase()).includes(t))) { postElement.classList.add(hiddenClass); return; diff --git a/src/features/quick_reblog.js b/src/features/quick_reblog.js index d48ae26f7..872b0e3d3 100644 --- a/src/features/quick_reblog.js +++ b/src/features/quick_reblog.js @@ -144,9 +144,10 @@ const showPopupOnHover = ({ currentTarget }) => { commentInput.value = ''; [...quickTagsList.children].forEach(({ dataset }) => delete dataset.checked); tagsInput.value = ''; - timelineObject(thisPost).then(({ tags, trail, content, layout, blogName, rebloggedRootName }) => { + timelineObject(thisPost).then(({ tags, trail, content, layout, blogName, postAuthor, rebloggedRootName }) => { suggestableTags = tags; if (blogName) suggestableTags.push(blogName); + if (postAuthor) suggestableTags.push(postAuthor); if (rebloggedRootName) suggestableTags.push(rebloggedRootName); suggestableTags.push(postType({ trail, content, layout })); renderTagSuggestions(); From bc4c315d343d8b6b6cdfb687a83dc1baee086dad Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Sun, 18 Aug 2024 13:33:34 -0700 Subject: [PATCH 21/50] Quick Tags, Quick Reblog: Fix popup horizontal cutoff (#1536) Co-authored-by: April Sylph <28949509+AprilSylph@users.noreply.github.com> --- src/features/quick_reblog.css | 54 +++++++---------------------------- src/features/quick_reblog.js | 7 ++--- src/features/quick_tags.css | 27 ++++-------------- src/features/quick_tags.js | 14 ++------- src/utils/interface.js | 29 +++++++++++++++++++ 5 files changed, 51 insertions(+), 80 deletions(-) diff --git a/src/features/quick_reblog.css b/src/features/quick_reblog.css index 5e0c9df0b..2db158ca8 100644 --- a/src/features/quick_reblog.css +++ b/src/features/quick_reblog.css @@ -20,56 +20,22 @@ display: none !important; } -@media (max-width: 650px) { - #quick-reblog { - top: 50%; - right: 100%; - transform: translateY(-50%); - } +#quick-reblog { + --icon-spacing: 12px; } - -@media (min-width: 650px) { +@media (max-width: 990px) { #quick-reblog { - transform: translateX(50%); - } - - #quick-reblog.above { - bottom: 100%; - right: 50%; - } - - #quick-reblog.below { - top: 100%; - right: 50%; + --icon-spacing: 0px; } } -@media (min-width: 990px) { - #quick-reblog.above { - transform: translate(50%, -12px) - } - - #quick-reblog.below { - transform: translate(50%, 12px); - } +#quick-reblog.below { + inset: 100% 50% auto auto; + transform: translate(calc(50% + var(--horizontal-offset, 0%)), var(--icon-spacing)); } - -@media (max-width: 990px) { - [role="dialog"] #quick-reblog { - top: 50%; - bottom: unset; - right: 100%; - transform: translateY(-50%); - } -} - -@media (min-width: 990px) and (max-width: 1145px) { - [role="dialog"] #quick-reblog { - top: 50%; - bottom: unset; - right: 100%; - transform: translate(-20px, -50%); - } +#quick-reblog.above { + inset: auto 50% 100% auto; + transform: translate(calc(50% + var(--horizontal-offset, 0%)), calc(0px - var(--icon-spacing))); } div:first-child + span + #quick-reblog, diff --git a/src/features/quick_reblog.js b/src/features/quick_reblog.js index 872b0e3d3..a92361315 100644 --- a/src/features/quick_reblog.js +++ b/src/features/quick_reblog.js @@ -1,7 +1,7 @@ import { sha256 } from '../utils/crypto.js'; import { timelineObject } from '../utils/react_props.js'; import { apiFetch } from '../utils/tumblr_helpers.js'; -import { postSelector, filterPostElements, postType } from '../utils/interface.js'; +import { postSelector, filterPostElements, postType, appendWithoutOverflow } from '../utils/interface.js'; import { userBlogs } from '../utils/user.js'; import { getPreferences } from '../utils/preferences.js'; import { onNewPosts } from '../utils/mutations.js'; @@ -9,6 +9,7 @@ import { notify } from '../utils/notifications.js'; import { translate } from '../utils/language_data.js'; import { dom } from '../utils/dom.js'; import { showErrorModal } from '../utils/modals.js'; +import { keyToCss } from '../utils/css_map.js'; const popupElement = dom('div', { id: 'quick-reblog' }); const blogSelector = dom('select'); @@ -131,7 +132,7 @@ tagsInput.addEventListener('input', checkLength); const showPopupOnHover = ({ currentTarget }) => { clearTimeout(timeoutID); - currentTarget.closest('div').appendChild(popupElement); + appendWithoutOverflow(popupElement, currentTarget.closest(keyToCss('controlIcon')), popupPosition); popupElement.parentNode.addEventListener('mouseleave', removePopupOnLeave); const thisPost = currentTarget.closest(postSelector); @@ -330,8 +331,6 @@ export const main = async function () { alreadyRebloggedLimit } = await getPreferences('quick_reblog')); - popupElement.className = popupPosition; - blogSelector.replaceChildren( ...userBlogs.map(({ name, uuid }) => dom('option', { value: uuid }, null, [name])) ); diff --git a/src/features/quick_tags.css b/src/features/quick_tags.css index 6ce55c4ab..73007cb21 100644 --- a/src/features/quick_tags.css +++ b/src/features/quick_tags.css @@ -42,28 +42,13 @@ } } -@media (min-width: 650px) { - #quick-tags.below { - inset: 100% 50% auto auto; - transform: translate(50%, var(--icon-spacing)); - } - #quick-tags.above { - inset: auto 50% 100% auto; - transform: translate(50%, calc(0px - var(--icon-spacing))); - } +#quick-tags.below { + inset: 100% 50% auto auto; + transform: translate(calc(50% + var(--horizontal-offset, 0%)), var(--icon-spacing)); } - -@media (max-width: 650px) { - #quick-tags { - inset: 50% 100% auto auto; - transform: translate(calc(0px - var(--icon-spacing)), -50%); - } -} -@media (max-width: 1145px) { - [role="dialog"] #quick-tags { - inset: 50% 100% auto auto; - transform: translate(calc(0px - var(--icon-spacing)), -50%); - } +#quick-tags.above { + inset: auto 50% 100% auto; + transform: translate(calc(50% + var(--horizontal-offset, 0%)), calc(0px - var(--icon-spacing))); } #quick-tags:empty::before, diff --git a/src/features/quick_tags.js b/src/features/quick_tags.js index ec4f1f109..689c95ae1 100644 --- a/src/features/quick_tags.js +++ b/src/features/quick_tags.js @@ -1,7 +1,7 @@ import { cloneControlButton, createControlButtonTemplate } from '../utils/control_buttons.js'; import { keyToCss } from '../utils/css_map.js'; import { dom } from '../utils/dom.js'; -import { filterPostElements, getTimelineItemWrapper, postSelector } from '../utils/interface.js'; +import { appendWithoutOverflow, filterPostElements, getTimelineItemWrapper, postSelector } from '../utils/interface.js'; import { megaEdit } from '../utils/mega_editor.js'; import { modalCancelButton, modalCompleteButton, showErrorModal, showModal } from '../utils/modals.js'; import { onNewPosts, pageModifications } from '../utils/mutations.js'; @@ -115,14 +115,6 @@ export const onStorageChanged = async function (changes, areaName) { } }; -const appendWithoutViewportOverflow = (element, target) => { - element.className = 'below'; - target.appendChild(element); - if (element.getBoundingClientRect().bottom > document.documentElement.clientHeight) { - element.className = 'above'; - } -}; - const togglePopupDisplay = async function ({ target, currentTarget: controlButton }) { if (target === popupElement || popupElement.contains(target)) { return; } @@ -131,7 +123,7 @@ const togglePopupDisplay = async function ({ target, currentTarget: controlButto if (buttonContainer.contains(popupElement)) { buttonContainer.removeChild(popupElement); } else { - appendWithoutViewportOverflow(popupElement, buttonContainer); + appendWithoutOverflow(popupElement, buttonContainer); } }; @@ -141,7 +133,7 @@ const togglePostOptionPopupDisplay = async function ({ target, currentTarget }) if (currentTarget.contains(postOptionPopupElement)) { currentTarget.removeChild(postOptionPopupElement); } else { - appendWithoutViewportOverflow(postOptionPopupElement, currentTarget); + appendWithoutOverflow(postOptionPopupElement, currentTarget); } }; diff --git a/src/utils/interface.js b/src/utils/interface.js index 3eb252ca5..86b67680b 100644 --- a/src/utils/interface.js +++ b/src/utils/interface.js @@ -115,3 +115,32 @@ export const postType = ({ trail = [], content = [], layout = [] }) => { else if (content.some(({ type }) => type === 'link')) return 'link'; else return 'text'; }; + +const getClosestWithOverflow = element => { + const parent = element.parentElement; + if (!parent) { + return element; + } else if (getComputedStyle(parent).overflowX !== 'visible') { + return parent; + } else { + return getClosestWithOverflow(parent); + } +}; + +export const appendWithoutOverflow = (element, target, defaultPosition = 'below') => { + element.className = defaultPosition; + element.style.removeProperty('--horizontal-offset'); + + target.appendChild(element); + + const preventOverflowTarget = getClosestWithOverflow(target); + const preventOverflowTargetRect = preventOverflowTarget.getBoundingClientRect(); + const elementRect = element.getBoundingClientRect(); + + if (elementRect.bottom > document.documentElement.clientHeight) { + element.className = 'above'; + } + if (elementRect.right > preventOverflowTargetRect.right - 15) { + element.style.setProperty('--horizontal-offset', `${preventOverflowTargetRect.right - 15 - elementRect.right}px`); + } +}; From 619be5fa30bd26b19365c51512b4939ca9e9e717 Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Sun, 18 Aug 2024 13:33:57 -0700 Subject: [PATCH 22/50] Collapsed Queue: Remove outdated erroneous CSS (#1548) --- src/features/collapsed_queue.css | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/features/collapsed_queue.css b/src/features/collapsed_queue.css index c2c84adfa..a7626bbea 100644 --- a/src/features/collapsed_queue.css +++ b/src/features/collapsed_queue.css @@ -1,12 +1,3 @@ -.sortableContainer header { - padding-top: 10px; - padding-left: calc(var(--post-padding) - 10px); - margin-bottom: 10px; -} -.sortableContainer footer { - margin-top: 5px; - padding-bottom: 5px; -} .xkit-collapsed-queue-wrapper { position: relative; } From 498a8a2871b201f10c0992d5c7f1473224c74e45 Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Sun, 18 Aug 2024 13:34:16 -0700 Subject: [PATCH 23/50] Seen Posts: Fix soft refresh (#1545) Co-authored-by: April Sylph <28949509+AprilSylph@users.noreply.github.com> --- src/features/seen_posts.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/features/seen_posts.js b/src/features/seen_posts.js index 8b16ecedc..d75dbdf70 100644 --- a/src/features/seen_posts.js +++ b/src/features/seen_posts.js @@ -1,8 +1,8 @@ import { filterPostElements, getTimelineItemWrapper, postSelector } from '../utils/interface.js'; import { getPreferences } from '../utils/preferences.js'; -import { onNewPosts } from '../utils/mutations.js'; +import { onNewPosts, pageModifications } from '../utils/mutations.js'; import { keyToCss } from '../utils/css_map.js'; -import { followingTimelineFilter, followingTimelineSelector } from '../utils/timeline_id.js'; +import { followingTimelineFilter, followingTimelineSelector, timelineSelector } from '../utils/timeline_id.js'; const excludeAttribute = 'data-seen-posts-done'; const timeline = followingTimelineFilter; @@ -13,6 +13,8 @@ const onlyDimAvatarsClass = 'xkit-seen-posts-only-dim-avatar'; const hideClass = 'xkit-seen-posts-hide'; const lengthenedClass = 'xkit-seen-posts-lengthened'; +const softRefreshLoaderSelector = `${followingTimelineSelector} ${keyToCss('container')}:has(~ div ${postSelector}) > ${keyToCss('knightRiderLoader')}`; + const storageKey = 'seen_posts.seenPosts'; let seenPosts = []; @@ -52,7 +54,7 @@ const lengthenTimelines = () => } }); -const dimPosts = function (postElements) { +const dimPosts = function (postElements, reprocessPosts = false) { lengthenTimelines(); for (const postElement of filterPostElements(postElements, { timeline, includeFiltered })) { @@ -64,12 +66,19 @@ const dimPosts = function (postElements) { if (seenPosts.includes(id) === false) { observer.observe(postElement.querySelector('article header + *')); - } else if (isFirstRender) { + } else if (isFirstRender || reprocessPosts) { timelineItem.setAttribute(dimAttribute, ''); } } }; +const onSoftRefresh = loaderElements => { + const refreshedPostElements = loaderElements.flatMap( + element => [...element.closest(timelineSelector).querySelectorAll(postSelector)] + ); + dimPosts(refreshedPostElements, true); +}; + export const onStorageChanged = async function (changes, areaName) { const { 'seen_posts.preferences.hideSeenPosts': hideSeenPostsChanges, @@ -106,10 +115,12 @@ export const main = async function () { } onNewPosts.addListener(dimPosts); + pageModifications.register(softRefreshLoaderSelector, onSoftRefresh); }; export const clean = async function () { onNewPosts.removeListener(dimPosts); + pageModifications.unregister(onSoftRefresh); observer.disconnect(); timers.forEach((timerId) => clearTimeout(timerId)); From 579aa4e356e8a14bb9f99146e401dd23633b5a97 Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Sun, 18 Aug 2024 13:34:28 -0700 Subject: [PATCH 24/50] Tag Tracking+: minor `onStorageChanged` refactor (#1554) --- src/features/tag_tracking_plus.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/features/tag_tracking_plus.js b/src/features/tag_tracking_plus.js index 2e838c2ac..9a75bb620 100644 --- a/src/features/tag_tracking_plus.js +++ b/src/features/tag_tracking_plus.js @@ -128,12 +128,16 @@ const processPosts = async function (postElements) { }; export const onStorageChanged = async (changes, areaName) => { - if (Object.keys(changes).includes(storageKey)) { - timestamps = changes[storageKey].newValue; + const { + [storageKey]: timestampsChanges, + 'tag_tracking_plus.preferences.onlyShowNew': onlyShowNewChanges + } = changes; + + if (timestampsChanges) { + timestamps = timestampsChanges.newValue; } - if (Object.keys(changes).some(key => key.startsWith('tag_tracking_plus.preferences'))) { - const { onlyShowNew } = await getPreferences('tag_tracking_plus'); - sidebarItem.dataset.onlyShowNew = onlyShowNew; + if (onlyShowNewChanges) { + sidebarItem.dataset.onlyShowNew = onlyShowNewChanges.newValue; } }; From 0349151f03e744d2223601212a899af7c1ad829e Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Sun, 18 Aug 2024 13:34:59 -0700 Subject: [PATCH 25/50] Quote Replies: Fix feature on threaded replies (#1561) Co-authored-by: April Sylph <28949509+AprilSylph@users.noreply.github.com> --- src/features/quote_replies.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/features/quote_replies.js b/src/features/quote_replies.js index 88c13badd..a29fc940d 100644 --- a/src/features/quote_replies.js +++ b/src/features/quote_replies.js @@ -31,7 +31,7 @@ const processNotifications = notifications => notifications.forEach(async notifi notification ); - if (!['reply', 'note_mention'].includes(notificationProps.type)) return; + if (!['reply', 'reply_to_comment', 'note_mention'].includes(notificationProps.type)) return; const activityElement = notification.querySelector(activitySelector); if (!activityElement) return; @@ -58,7 +58,6 @@ const quoteReply = async (tumblelogName, notificationProps) => { const uuid = userBlogs.find(({ name }) => name === tumblelogName).uuid; const { type, targetPostId, targetPostSummary, targetTumblelogName, targetTumblelogUuid, timestamp } = notificationProps; - const isReply = type === 'reply'; const { response } = await apiFetch( `/v2/blog/${targetTumblelogUuid}/post/${targetPostId}/notes/timeline`, { queryParams: { mode: 'replies', before_timestamp: `${timestamp + 1}000000` } } @@ -69,9 +68,12 @@ const quoteReply = async (tumblelogName, notificationProps) => { if (!reply) throw new Error('No replies found on target post.'); if (Math.floor(reply.timestamp) !== timestamp) throw new Error('Reply not found.'); - const text = isReply - ? `@${reply.blog.name} replied to your post \u201C${targetPostSummary.replace(/\n/g, ' ')}\u201D:` - : `@${reply.blog.name} mentioned you on a post \u201C${targetPostSummary.replace(/\n/g, ' ')}\u201D:`; + const verbiage = { + reply: 'replied to your post', + reply_to_comment: 'replied to you in a post', + note_mention: 'mentioned you on a post' + }[type]; + const text = `@${reply.blog.name} ${verbiage} \u201C${targetPostSummary.replace(/\n/g, ' ')}\u201D:`; const formatting = [ { start: 0, end: reply.blog.name.length + 1, type: 'mention', blog: { uuid: reply.blog.uuid } }, { start: text.indexOf('\u201C'), end: text.length - 1, type: 'link', url: `https://${targetTumblelogName}.tumblr.com/post/${targetPostId}` } From f6329c78919d7c6ba6284e9a49ffe2d52b86985e Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Sun, 18 Aug 2024 13:35:11 -0700 Subject: [PATCH 26/50] Sidebar util: Prevent erroneous sidebar in communities settings (#1569) --- src/utils/sidebar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/sidebar.js b/src/utils/sidebar.js index 6ada9a1e7..cfd1f3bc8 100644 --- a/src/utils/sidebar.js +++ b/src/utils/sidebar.js @@ -96,7 +96,7 @@ const updateSidebarItemVisibility = () => [...sidebarItems.children] .forEach(sidebarItem => { sidebarItem.hidden = !conditions.get(sidebarItem)(); }); const addSidebarToPage = (siblingCandidates) => { - if (/^\/settings/.test(location.pathname)) { return; } + if (/^\/settings/.test(location.pathname) || /^\/communities.*\/settings$/.test(location.pathname)) { return; } updateSidebarItemVisibility(); From 244109c8c7034186ce96a8d8ae3e7c2892ffee04 Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Sun, 18 Aug 2024 13:35:46 -0700 Subject: [PATCH 27/50] Tweaks: Fix subtle following/mutuals tweak in communities (#1568) --- src/features/tweaks/subtle_activity_mutuals.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/tweaks/subtle_activity_mutuals.js b/src/features/tweaks/subtle_activity_mutuals.js index e7bd0a736..b18bd13ea 100644 --- a/src/features/tweaks/subtle_activity_mutuals.js +++ b/src/features/tweaks/subtle_activity_mutuals.js @@ -3,7 +3,7 @@ import { keyToCss } from '../../utils/css_map.js'; import { buildStyle } from '../../utils/interface.js'; import { dom } from '../../utils/dom.js'; -const labelSelector = keyToCss('followingBadgeContainer', 'mutualsBadgeContainer'); +const labelSelector = `${keyToCss('followingBadgeContainer', 'mutualsBadgeContainer')}:has(> svg)`; const spanClass = 'xkit-tweaks-subtle-activity-span'; From b50f456cca9d9307bb1d997d274e0c87da266c1b Mon Sep 17 00:00:00 2001 From: marcustyphoon Date: Sun, 18 Aug 2024 13:44:19 -0700 Subject: [PATCH 28/50] Automatic `styleElement` addition/removal (#1464) --- docs/Chapter 4.2 - Feature modules.md | 14 ++++++++--- src/content_scripts/main.js | 25 +++++++++++++------ src/features/accesskit.css | 4 --- src/features/accesskit.js | 18 ++++++++++--- src/features/accesskit/blue_links.js | 5 +--- src/features/accesskit/disable_animations.js | 5 +--- src/features/accesskit/disable_gifs.js | 5 +--- src/features/accesskit/no_user_colours.js | 9 ++++--- src/features/accesskit/no_user_fonts.js | 10 ++++---- src/features/accesskit/visible_alt_text.js | 5 +--- src/features/anti_capitalism.js | 5 ++-- src/features/cleanfeed.js | 5 ++-- src/features/hide_avatars.js | 8 +----- src/features/no_recommended.js | 18 ++++++++++--- .../no_recommended/hide_answertime.js | 7 +++--- .../no_recommended/hide_blog_carousels.js | 5 ++-- .../no_recommended/hide_lightbox_related.js | 5 +--- src/features/no_recommended/hide_radar.js | 5 ++-- .../no_recommended/hide_recommended_blogs.js | 5 +--- .../hide_recommended_blogs_modal.js | 5 +--- .../no_recommended/hide_recommended_posts.js | 5 ++-- .../no_recommended/hide_tag_carousels.js | 5 ++-- src/features/notificationblock.js | 5 ++-- src/features/scroll_to_bottom.js | 4 +-- src/features/shorten_posts.js | 6 ++--- src/features/themed_posts.js | 8 +++--- src/features/tweaks.js | 18 ++++++++++--- src/features/tweaks/caught_up_line.js | 5 ++-- src/features/tweaks/hide_activity_mutuals.js | 5 +--- .../hide_activity_notification_badge.js | 5 +--- src/features/tweaks/hide_blaze_tip_labels.js | 5 +--- src/features/tweaks/hide_filtered_posts.js | 5 ++-- src/features/tweaks/hide_follower_counts.js | 5 +--- .../hide_following_notification_badge.js | 5 ++-- src/features/tweaks/hide_footer_tooltips.js | 5 +--- src/features/tweaks/hide_liked_posts.js | 5 ++-- src/features/tweaks/hide_mini_follow.js | 7 +++--- src/features/tweaks/hide_my_posts.js | 5 ++-- .../tweaks/highlight_contributed_content.js | 9 ++++--- src/features/tweaks/no_focus_shadow.js | 5 +--- src/features/tweaks/no_where_were_we.js | 7 +++--- src/features/tweaks/show_all_tags.js | 10 +------- src/features/tweaks/slim_filtered_screens.js | 5 +--- .../tweaks/subtle_activity_mutuals.js | 4 +-- src/features/tweaks/unsticky_tab_bar.js | 5 +--- 45 files changed, 142 insertions(+), 179 deletions(-) diff --git a/docs/Chapter 4.2 - Feature modules.md b/docs/Chapter 4.2 - Feature modules.md index 94f2f17c0..94d092626 100644 --- a/docs/Chapter 4.2 - Feature modules.md +++ b/docs/Chapter 4.2 - Feature modules.md @@ -1,16 +1,16 @@ # Feature modules -Each module is required to export at least two async functions. +Modules may export any of the following: ## `main()` - Type: Async Function -- Required: Yes +- Required: No The main function of the feature. Will be called whenever the user enables the feature, even if the tab(s) XKit is running in is/are not focused. Will also be called upon pageload if the script is enabled. ## `clean()` - Type: Async Function -- Required: Yes +- Required: No The cleanup function of the feature. Called whenever the user disables the feature, also regardless of tab focus. @@ -24,4 +24,10 @@ The preference-handling code of the feature. Added as a `browser.storage.onChang - Type: Boolean - Required: No -Whether the feature has a stylesheet. If true, there should be a `.css` file of matching name in the same directory level. The stylesheet is automatically added and removed during the feature's lifecycle. +Whether the feature has a static stylesheet. If true, there should be a `.css` file of matching name in the same directory level. The stylesheet is automatically added and removed during the feature's lifecycle. + +## `styleElement` +- Type: HTMLStyleElement +- Required: No + +An HTML `