Skip to content

Commit 4cb5bb1

Browse files
committed
Fix button not injecting when dynamic comments are added
1 parent 1192352 commit 4cb5bb1

File tree

6 files changed

+129
-37
lines changed

6 files changed

+129
-37
lines changed

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,11 @@
4040
},
4141
"dependencies": {
4242
"@giphy/js-fetch-api": "^1.7.0",
43+
"code-tag": "^1.1.0",
4344
"debounce-fn": "^1.0.0",
4445
"delegate": "^3.2.0",
4546
"dom-chef": "^3.3.0",
46-
"github-injection": "^1.0.1",
47+
"github-injection": "^1.1.0",
4748
"masonry-layout": "^4.2.2",
4849
"mem": "^8.1.1",
4950
"onetime": "^2.0.1",

src/lib/caller-id.js

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Lovingly copied from https://github.com/refined-github/refined-github/blob/main/source/helpers/caller-id.ts
2+
import hashString from './hash-string.js'
3+
4+
/**
5+
Get unique ID by using the line:column of the call (or its parents) as seed. Every call from the same place will return the same ID, as long as the index is set to the parents that matters to you.
6+
@param ancestor Which call in the stack should be used as key. 0 means the exact line where getCallerID is called. Defaults to 1 because it's usually used inside a helper.
7+
*/
8+
export default function getCallerID(ancestor = 1) {
9+
/* +1 because the first line comes from this function */
10+
return hashString(getStackLine(new Error('Get stack').stack, ancestor + 1))
11+
}
12+
13+
export function getStackLine(stack, line) {
14+
return (
15+
stack
16+
// Remove non-stacktrace line from array (missing in Firefox) #6032
17+
.replace('Error: Get stack\n', '')
18+
.split('\n')
19+
.at(line) ?? warn(stack, line)
20+
)
21+
}
22+
23+
function warn(stack, line) {
24+
console.warn('The stack doesn’t have the line', {line, stack})
25+
return Math.random().toString(16)
26+
}

src/lib/hash-string.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Lovingly copied from https://github.com/refined-github/refined-github/blob/main/source/helpers/hash-string.ts
2+
export default function hashString(string) {
3+
let hash = 0
4+
5+
for (const character of string) {
6+
hash = (hash << 5) - hash + character.codePointAt(0)
7+
}
8+
9+
return String(Math.trunc(hash))
10+
}

src/lib/selector-observer.js

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Lovingly copied from https://github.com/refined-github/refined-github/blob/main/source/helpers/selector-observer.tsx
2+
// eslint-disable-next-line no-unused-vars
3+
import React from 'dom-chef'
4+
import {css} from 'code-tag'
5+
import onetime from 'onetime'
6+
7+
import getCallerID from './caller-id.js'
8+
9+
const animation = 'rgh-selector-observer'
10+
const getListener = (seenMark, selector, callback) =>
11+
function (event) {
12+
const target = event.target
13+
// The target can match a selector even if the animation actually happened on a ::before pseudo-element, so it needs an explicit exclusion here
14+
if (target.classList.contains(seenMark) || !target.matches(selector)) {
15+
return
16+
}
17+
18+
// Removes this specific selector’s animation once it was seen
19+
target.classList.add(seenMark)
20+
21+
callback(target)
22+
}
23+
24+
const registerAnimation = onetime(() => {
25+
document.head.append(<style>{`@keyframes ${animation} {}`}</style>)
26+
})
27+
28+
export default function observe(selectors, listener, {signal} = {}) {
29+
if (signal?.aborted) {
30+
return
31+
}
32+
33+
const selector = String(selectors) // Array#toString() creates a comma-separated string
34+
const seenMark = 'rgh-seen-' + getCallerID()
35+
36+
registerAnimation()
37+
38+
const rule = document.createElement('style')
39+
40+
rule.textContent = css`
41+
:where(${String(selector)}):not(.${seenMark}) {
42+
animation: 1ms ${animation};
43+
}
44+
`
45+
document.body.prepend(rule)
46+
signal?.addEventListener('abort', () => {
47+
rule.remove()
48+
})
49+
window.addEventListener(
50+
'animationstart',
51+
getListener(seenMark, selector, listener),
52+
{signal}
53+
)
54+
}

src/main.js

+28-32
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,7 @@ import LoadingIndicator from './components/loading-indicator.js'
1313
import GiphyToolbarItem from './components/giphy-toolbar-item.js'
1414
import Giphy from './lib/giphy.js'
1515

16-
import {
17-
onDiffFileLoad,
18-
onCommentEdit
19-
} from './lib/github-events/on-fragment-load.js'
16+
import observe from './lib/selector-observer.js'
2017

2118
// Create a new Giphy Client
2219
const giphyClient = new Giphy('Mpy5mv1k9JRY2rt7YBME2eFRGNs7EGvQ')
@@ -36,6 +33,7 @@ async function watchGiphyModals(element) {
3633

3734
// If the modal has been opened and there is no search term,
3835
// and no search results, load the trending gifs
36+
console.log(resultsContainer.dataset.hasResults, searchInput.value)
3937
if (
4038
searchInput.value === '' &&
4139
resultsContainer.dataset.hasResults === 'false'
@@ -65,7 +63,6 @@ async function watchGiphyModals(element) {
6563
columnWidth: 145,
6664
gutter: 10,
6765
transitionDuration: '0.2s'
68-
// FitWidth: true
6966
},
7067
2000
7168
),
@@ -82,6 +79,7 @@ function addToolbarButton() {
8279
'form:not(.ghg-has-giphy-field) markdown-toolbar'
8380
)) {
8481
const form = toolbar.closest('form')
82+
8583
const reviewChangesModal = toolbar.closest(
8684
'#review-changes-modal .SelectMenu-modal'
8785
)
@@ -111,8 +109,16 @@ function addToolbarButton() {
111109
if (toolbarGroup) {
112110
// Append the Giphy button to the toolbar
113111
// cloneNode is necessary, without it, it will only be appended to the last toolbarGroup
114-
toolbarGroup.append(GiphyToolbarItem.cloneNode(true))
112+
const clonedNode = GiphyToolbarItem.cloneNode(true)
113+
toolbarGroup.append(clonedNode)
114+
select('.ghg-giphy-results', clonedNode)
115+
115116
form.classList.add('ghg-has-giphy-field')
117+
118+
// Clears the gif search input field and results.
119+
// We have to do this because when navigating, github will refuse to
120+
// load the giphy URLs as it violates their Content Security Policy.
121+
resetGiphyModals()
116122
}
117123
})
118124
}
@@ -122,11 +128,7 @@ function addToolbarButton() {
122128
* Watches for comments that might be dynamically added, then adds the button the the WYSIWYG when they are.
123129
*/
124130
function observeDiscussion() {
125-
const discussionTimeline = select('.js-discussion')
126-
127-
observeEl(discussionTimeline, () => {
128-
addToolbarButton()
129-
})
131+
observe('md-task-list', () => addToolbarButton())
130132
}
131133

132134
/**
@@ -137,6 +139,7 @@ function resetGiphyModals() {
137139
for (const ghgModal of select.all('.ghg-modal')) {
138140
const resultContainer = select('.ghg-giphy-results', ghgModal)
139141
const searchInput = select('.ghg-search-input', ghgModal)
142+
console.log({resultContainer})
140143
searchInput.value = ''
141144
resultContainer.innerHTML = ''
142145
resultContainer.dataset.offset = 0
@@ -207,7 +210,6 @@ function getFormattedGif(gif) {
207210
)
208211

209212
// Generate a random pastel colour to use as an image placeholder
210-
211213
const hsl = `hsl(${360 * Math.random()}, ${25 + 70 * Math.random()}%,${
212214
85 + 10 * Math.random()
213215
}%)`
@@ -245,18 +247,19 @@ function appendResults(resultsContainer, gifs) {
245247
resultsContainer.append(img)
246248
}
247249

248-
// eslint-disable-next-line no-new
249-
new Masonry(
250-
resultsContainer,
251-
{
252-
itemSelector: '.ghg-giphy-results div',
253-
columnWidth: 145,
254-
gutter: 10,
255-
transitionDuration: '0.2s'
256-
// FitWidth: true
257-
},
258-
2000
259-
)
250+
setTimeout(() => {
251+
// eslint-disable-next-line no-new
252+
new Masonry(
253+
resultsContainer,
254+
{
255+
itemSelector: '.ghg-giphy-results div',
256+
columnWidth: 145,
257+
gutter: 10,
258+
transitionDuration: '0.2s'
259+
},
260+
10
261+
)
262+
})
260263
}
261264

262265
/**
@@ -350,6 +353,7 @@ function listen() {
350353
// The `open` attribute is added after this handler is run,
351354
// so the selector is inverted
352355
delegate('.ghg-trigger:not([open]) > summary', 'click', (event) => {
356+
console.log('clicked?', event.delegateTarget)
353357
// What comes after <summary> is the dropdown
354358
watchGiphyModals(event.delegateTarget)
355359
})
@@ -361,14 +365,6 @@ const listenOnce = onetime(listen)
361365
// GitHubInjection fires when there's a pjax:end event
362366
// on github, this happens when a page is loaded
363367
gitHubInjection(() => {
364-
addToolbarButton()
365368
listenOnce()
366369
observeDiscussion()
367-
onDiffFileLoad(addToolbarButton)
368-
onCommentEdit(addToolbarButton)
369-
370-
// Clears all gif search input fields and results.
371-
// We have to do this because when navigating, github will refuse to
372-
// load the giphy URLs as it violates their Content Security Policy.
373-
resetGiphyModals()
374370
})

yarn.lock

+9-4
Original file line numberDiff line numberDiff line change
@@ -1974,6 +1974,11 @@ clone@^2.1.1:
19741974
resolved "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz"
19751975
integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=
19761976

1977+
code-tag@^1.1.0:
1978+
version "1.1.0"
1979+
resolved "https://registry.yarnpkg.com/code-tag/-/code-tag-1.1.0.tgz#8dc979c2bd2e2ac44d5a799abd07953e39cadf3d"
1980+
integrity sha512-qqvyRC9Fmnqy/1nK2Jz6FIk6F24nliVIVtQFg0r7PuZCZHfWO/c7eZHVlPxFKRSnOSIUUf/jrF1FG8j67FinPg==
1981+
19771982
collection-visit@^1.0.0:
19781983
version "1.0.0"
19791984
resolved "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz"
@@ -3492,10 +3497,10 @@ get-value@^2.0.3, get-value@^2.0.6:
34923497
resolved "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz"
34933498
integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=
34943499

3495-
github-injection@^1.0.1:
3496-
version "1.0.1"
3497-
resolved "https://registry.npmjs.org/github-injection/-/github-injection-1.0.1.tgz"
3498-
integrity sha1-+tS+5nuYiZPFJ1VhAahxOCRe0Nw=
3500+
github-injection@^1.1.0:
3501+
version "1.1.0"
3502+
resolved "https://registry.yarnpkg.com/github-injection/-/github-injection-1.1.0.tgz#3804bcb65cfdc302df49c8a5152bd43ac00aa95f"
3503+
integrity sha512-XoypofQSVTLaMlMlcp52Y6v+E58FhonB2iK2FzlBPRP/XxrnfJa4rEDgiD5phiki2jeW5oaP+tcwplbiiA3rMg==
34993504

35003505
glob-parent@^3.1.0:
35013506
version "3.1.0"

0 commit comments

Comments
 (0)