diff options
author | 2019-08-01 23:12:46 +0530 | |
---|---|---|
committer | 2019-08-02 00:42:46 +0700 | |
commit | f36d4f10c725b4511e0d28d5c1fec2cf1b2a22b6 (patch) | |
tree | fdfa7f5c02cfe77f3c02bcc744b340ae3ebd28a5 | |
parent | e03473b5f08333d00f052578361b78b54d3eab7c (diff) | |
download | refined-github-f36d4f10c725b4511e0d28d5c1fec2cf1b2a22b6.tar.gz refined-github-f36d4f10c725b4511e0d28d5c1fec2cf1b2a22b6.tar.zst refined-github-f36d4f10c725b4511e0d28d5c1fec2cf1b2a22b6.zip |
Add `minimize-user-comments` feature (#2146)
Co-Authored-By: Federico Brigante <github@bfred.it>
-rw-r--r-- | package-lock.json | 5 | ||||
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | readme.md | 1 | ||||
-rw-r--r-- | source/content.ts | 1 | ||||
-rw-r--r-- | source/features/minimize-user-comments.tsx | 104 | ||||
-rw-r--r-- | source/globals.d.ts | 1 | ||||
-rw-r--r-- | source/libs/anchor-scroll.ts | 18 | ||||
-rw-r--r-- | source/options-storage.ts | 2 | ||||
-rw-r--r-- | source/options.css | 19 | ||||
-rw-r--r-- | source/options.html | 5 | ||||
-rw-r--r-- | source/options.tsx | 29 |
11 files changed, 167 insertions, 20 deletions
diff --git a/package-lock.json b/package-lock.json index 8180ee5e..34dd8c34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16398,11 +16398,6 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.5.2.tgz", "integrity": "sha512-DWkS49EQKVX//Tbupb9TFa19c7+MK1XmzkrZUR8TAktmE/DizXoaoJV6TZ/tSIPXipqNiRI6CyAe7x69Jb6RSw==" - }, - "webext-detect-page": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/webext-detect-page/-/webext-detect-page-0.9.1.tgz", - "integrity": "sha512-/QoN7CIlobiTqilq721l5cpROBJxbkwCc6UE6KeH+xLZReAgSRyM0Q9r2T0SC+wX8HoNnFKrMDNYGYsq/SgJ4w==" } } }, diff --git a/package.json b/package.json index ea29aadc..3c224e6f 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "webext-additional-permissions": "^0.1.0", "webext-detect-page": "^1.0.2", "webext-domain-permission-toggle": "^1.0.0-1", - "webext-options-sync": "^0.21.1", + "webext-options-sync": "^0.21.2", "webext-permissions-events-polyfill": "^1.0.1", "webext-storage-cache": "^1.0.2", "webextension-polyfill": "^0.4.0", @@ -179,6 +179,7 @@ Thanks for contributing! 🦋🙌 - [](# "scrollable-code-and-blockquote") [Limits the height of tall code blocks and quotes.](https://github.com/sindresorhus/refined-github/issues/1123) - [](# "hide-comments-faster") [Simplifies the UI to hide comments.](https://user-images.githubusercontent.com/1402241/43039221-1ddc91f6-8d29-11e8-9ed4-93459191a510.gif) - [](# "open-issue-to-latest-comment") [Links the comments icon to the latest comment.](https://user-images.githubusercontent.com/14323370/57962709-7019de00-78e8-11e9-8398-7e617ba7a96f.png) +- [](# "minimize-user-comments") [Adds ability to minimize comments of certain users.](https://user-images.githubusercontent.com/37769974/61595681-d6d4b400-ac17-11e9-98b9-03f27b004a94.gif) <!-- Refer to style guide above. Keep this message between sections. --> diff --git a/source/content.ts b/source/content.ts index 0eb26b72..ab6f4a05 100644 --- a/source/content.ts +++ b/source/content.ts @@ -123,6 +123,7 @@ import './features/open-issue-to-latest-comment'; import './features/limit-commit-title-length'; import './features/highest-rated-comment'; import './features/clean-issue-filters'; +import './features/minimize-user-comments'; import './features/minimize-upload-bar'; import './features/cycle-lists-with-keyboard-shortcuts'; import './features/forked-to'; diff --git a/source/features/minimize-user-comments.tsx b/source/features/minimize-user-comments.tsx new file mode 100644 index 00000000..90c73dc9 --- /dev/null +++ b/source/features/minimize-user-comments.tsx @@ -0,0 +1,104 @@ +import React from 'dom-chef'; +import select from 'select-dom'; +import delegate, {DelegateEvent} from 'delegate-it'; +import features from '../libs/features'; +import {appendBefore} from '../libs/dom-utils'; +import optionsStorage from '../options-storage'; +import {getUsername} from '../libs/utils'; +import onNewComments from '../libs/on-new-comments'; +import anchorScroll from '../libs/anchor-scroll'; + +const getLabel = (restore: boolean): string => `${restore ? 'Restore' : 'Minimize'} user’s comments`; + +function getUsernameFromComment(comment: Element): string { + return select<HTMLAnchorElement>('.author', comment)!.pathname.slice(1); +} + +async function getMinimizedUsers(): Promise<string[]> { + return (await optionsStorage.getAll()).minimizedUsers.trim().split(/\s+/); +} + +async function setMinimizedUsers(minimizedUsers: string[]): Promise<void> { + return optionsStorage.set({minimizedUsers: minimizedUsers.join(' ')}); +} + +function toggleComment(comment: HTMLElement, minimize: boolean): void { + if (comment.id.startsWith('issue-') || comment.id.startsWith('pullrequestreview-')) { + return; + } + + select('.unminimized-comment', comment)!.classList.toggle('d-none', minimize); + select('.minimized-comment', comment)!.classList.toggle('d-none', !minimize); + select('.minimized-comment .Details-element', comment)!.toggleAttribute('open', !minimize); +} + +async function onButtonClick(event: React.MouseEvent<HTMLButtonElement>): Promise<void> { + const comment = event.currentTarget.closest('.js-targetable-comment')!; + const user = getUsernameFromComment(comment); + + let minimizedUsers = await getMinimizedUsers(); + if (minimizedUsers.includes(user)) { + minimizedUsers = minimizedUsers.filter(mutedUser => mutedUser !== user); + } else { + minimizedUsers.push(user); + } + + await setMinimizedUsers(minimizedUsers); + anchorScroll(minimizeMutedUserComments, comment); +} + +async function handleMenuOpening(event: DelegateEvent): Promise<void> { + const dropdown = event.delegateTarget.nextElementSibling!; + const user = getUsernameFromComment(dropdown.closest('.js-targetable-comment')!); + if (user === getUsername()) { + return; + } + + const minimizedUsers = await getMinimizedUsers(); + const shouldMinimizeComment = minimizedUsers.includes(user); + + const existingButton = select('.rgh-minimize-user-comments-button', dropdown); + if (existingButton) { + existingButton.textContent = getLabel(shouldMinimizeComment); + return; + } + + // Add option to mute or unmute user + appendBefore(dropdown, 'a[data-ga-click^="Report"]', + <button + className="dropdown-item btn-link rgh-minimize-user-comments-button" + role="menuitem" + type="button" + onClick={onButtonClick}> + {getLabel(shouldMinimizeComment)} + </button> + ); +} + +async function minimizeMutedUserComments(): Promise<void> { + const minimizedUsers = await getMinimizedUsers(); + + for (const comment of select.all('.js-discussion .js-minimizable-comment-group')) { + if (minimizedUsers.includes(getUsernameFromComment(comment))) { + toggleComment(comment, true); + } + } +} + +function init(): void { + minimizeMutedUserComments(); + onNewComments(minimizeMutedUserComments); + delegate('.timeline-comment-action', 'click', handleMenuOpening); +} + +features.add({ + id: __featureName__, + description: 'Adds ability to minimize comments of certain users.', + screenshot: 'https://user-images.githubusercontent.com/37769974/61595681-d6d4b400-ac17-11e9-98b9-03f27b004a94.gif', + include: [ + features.isIssue, + features.isPRConversation + ], + load: features.onAjaxedPages, + init +}); diff --git a/source/globals.d.ts b/source/globals.d.ts index a88d8dd0..5b7634a2 100644 --- a/source/globals.d.ts +++ b/source/globals.d.ts @@ -1,4 +1,5 @@ type AnyObject = Record<string, any>; +type AsyncVoidFunction = () => Promise<void>; interface FeatureInfo { name: string; diff --git a/source/libs/anchor-scroll.ts b/source/libs/anchor-scroll.ts new file mode 100644 index 00000000..559864da --- /dev/null +++ b/source/libs/anchor-scroll.ts @@ -0,0 +1,18 @@ +export default async function anchorScroll( + action: VoidFunction | AsyncVoidFunction, + anchor: Element = document.elementFromPoint(innerWidth / 2, innerHeight / 2)! +): Promise<void> { + if (anchor) { + const originalPosition = anchor.getBoundingClientRect().top; + + await action(); + + requestAnimationFrame(() => { + const newPositon = anchor.getBoundingClientRect().top; + window.scrollBy(0, newPositon - originalPosition); + }); + } else { + // Anchor not found; proceed without anchoring + action(); + } +} diff --git a/source/options-storage.ts b/source/options-storage.ts index 7cab0b01..0fc0722f 100644 --- a/source/options-storage.ts +++ b/source/options-storage.ts @@ -4,6 +4,7 @@ export interface RGHOptions { customCSS: string; personalToken: string; logging: boolean; + minimizedUsers: string; [featureName: string]: string | boolean; } @@ -27,6 +28,7 @@ export default new OptionsSync({ customCSS: '', personalToken: '', logging: false, + minimizedUsers: '', ...featureOptions } as RGHOptions, migrations: [ diff --git a/source/options.css b/source/options.css index 70b8483a..718ce0b0 100644 --- a/source/options.css +++ b/source/options.css @@ -1,5 +1,7 @@ html { min-width: 550px; + max-width: 700px; + margin: auto; overflow-x: hidden; } @@ -55,8 +57,9 @@ h2 ~ h2 { margin-top: 2em; } -.js-features p { +.js-features .feature { display: flex; + margin-top: 1em; } .js-features input { @@ -69,8 +72,11 @@ h2 ~ h2 { text-decoration: line-through; } +.js-features :not(:checked) + .info .extended-options { + display: none; +} + .js-features .description { - font-style: 0.8em; opacity: 0.8; } @@ -78,6 +84,15 @@ h2 ~ h2 { font-size: 0.9em; } +.js-features .info { + flex-grow: 1; +} + +.js-features .info p { + margin-top: 0.25em; + margin-bottom: 0.125em; +} + .feature-name + a { margin-left: 0.6em; } diff --git a/source/options.html b/source/options.html index 41401590..f94c2a70 100644 --- a/source/options.html +++ b/source/options.html @@ -42,6 +42,11 @@ <div class="js-features"></div> </div> + <div class="js-minimized-users-container extended-options"> + <p>User list:</p> + <textarea name="minimizedUsers" rows="2" spellcheck="false"></textarea> + </div> + <hr> <p> diff --git a/source/options.tsx b/source/options.tsx index 89ee563b..c3388dee 100644 --- a/source/options.tsx +++ b/source/options.tsx @@ -32,25 +32,30 @@ function buildFeatureCheckbox({name, description, screenshot, disabled}: Feature ); return ( - <p> + <div className="feature"> <input type="checkbox" name={id} id={id} disabled={Boolean(disabled)} /> - <label for={id} className="info"> - <span className="feature-name">{name}</span> - {' '} - <a href={`https://github.com/sindresorhus/refined-github/blob/master/source/features/${name}.tsx`}> - source - </a> - {screenshot ? <>, <a href={screenshot}>screenshot</a></> : ''} - <br/> - <span className="description">{parsedDescription}</span> - </label> - </p> + <div className="info"> + <label for={id}> + <span className="feature-name">{name}</span> + {' '} + <a href={`https://github.com/sindresorhus/refined-github/blob/master/source/features/${name}.tsx`}> + source + </a> + {screenshot ? <>, <a href={screenshot}>screenshot</a></> : ''} + <br/> + <p className="description">{parsedDescription}</p> + </label> + </div> + </div> ); } async function init(): Promise<void> { select('.js-features')!.append(...__featuresInfo__.map(buildFeatureCheckbox)); + // Move minimized users input field below the respective feature checkbox + select('[for="feature:minimize-user-comments"]')!.after(select('.js-minimized-users-container')!); + await optionsStorage.syncForm('#options-form'); fitTextarea.watch('textarea'); |