Skip to content

Commit 7b342cf

Browse files
committed
Implemented About modal, replaced chatgpt.notify() call w/ siteAlert() for simplicity, added .5s sleep for proper DOM load ↞ [auto-sync from https://github.com/KudoAI/chatgpt.js-chrome-starter]
1 parent 5f94d83 commit 7b342cf

File tree

6 files changed

+221
-28
lines changed

6 files changed

+221
-28
lines changed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Requires lib/chatgpt.js + lib/dom.js
2+
3+
window.modals = {
4+
stack: [], // of types of undismissed modals
5+
6+
import(dependencies) { // { app, siteAlert }
7+
Object.entries(dependencies).forEach(([name, dependency]) => this[name] = dependency) },
8+
9+
open(modalType) {
10+
this.stack.unshift(modalType) // add to stack
11+
const modal = this[modalType]() // show modal
12+
modal.classList.add('chatgpt-infinity-modal')
13+
modal.onmousedown = this.dragHandlers.mousedown
14+
dom.fillStarryBG(modal) // fill BG w/ rising stars
15+
this.observeRemoval(modal, modalType) // to maintain stack for proper nav
16+
},
17+
18+
observeRemoval(modal, modalType) { // to maintain stack for proper nav
19+
const modalBG = modal.parentNode
20+
new MutationObserver(([mutation], obs) => {
21+
mutation.removedNodes.forEach(removedNode => { if (removedNode == modalBG) {
22+
if (this.stack[0] == modalType) { // new modal not launched, implement nav back logic
23+
this.stack.shift() // remove this modal type from stack
24+
const prevModalType = this.stack[0]
25+
if (prevModalType) { // open it
26+
this.stack.shift() // remove type from stack since re-added on open
27+
this.open(prevModalType)
28+
}
29+
}
30+
obs.disconnect()
31+
}})
32+
}).observe(modalBG.parentNode, { childList: true, subtree: true })
33+
},
34+
35+
dragHandlers: {
36+
mousedown(event) { // find modal, attach listeners, init XY offsets
37+
if (event.button != 0) return // prevent non-left-click drag
38+
if (getComputedStyle(event.target).cursor == 'pointer') return // prevent drag on interactive elems
39+
modals.dragHandlers.draggableElem = event.currentTarget
40+
modals.dragHandlers.draggableElem.style.cursor = 'grabbing'
41+
event.preventDefault(); // prevent sub-elems like icons being draggable
42+
['mousemove', 'mouseup'].forEach(event => document.addEventListener(event, modals.dragHandlers[event]))
43+
const draggableElemRect = modals.dragHandlers.draggableElem.getBoundingClientRect()
44+
modals.dragHandlers.offsetX = event.clientX - draggableElemRect.left +21
45+
modals.dragHandlers.offsetY = event.clientY - draggableElemRect.top +12
46+
},
47+
48+
mousemove(event) { // drag modal
49+
if (modals.dragHandlers.draggableElem) {
50+
const newX = event.clientX - modals.dragHandlers.offsetX,
51+
newY = event.clientY - modals.dragHandlers.offsetY
52+
Object.assign(modals.dragHandlers.draggableElem.style, { left: `${newX}px`, top: `${newY}px` })
53+
}
54+
},
55+
56+
mouseup() { // remove listeners, reset modals.dragHandlers.draggableElem
57+
modals.dragHandlers.draggableElem.style.cursor = 'inherit';
58+
['mousemove', 'mouseup'].forEach(event =>
59+
document.removeEventListener(event, modals.dragHandlers[event]))
60+
modals.dragHandlers.draggableElem = null
61+
}
62+
},
63+
64+
about() {
65+
66+
// Init styles
67+
const headingStyle = 'font-size: 1.15rem',
68+
pStyle = 'position: relative ; left: 3px',
69+
pBrStyle = 'position: relative ; left: 4px ',
70+
aStyle = 'color: ' + ( chatgpt.isDarkMode() ? '#c67afb' : '#8325c4' ) // purple
71+
72+
// Init buttons
73+
const modalBtns = [
74+
function getSupport(){ modals.safeWinOpen(`${modals.app.urls.gitHub}/issues`) },
75+
function rateUs() { modals.safeWinOpen(`${modals.app.urls.gitHub}/discussions`) },
76+
function moreAiExtensions(){ modals.safeWinOpen(modals.app.urls.relatedExtensions) }
77+
]
78+
79+
// Show modal
80+
const aboutModal = this.siteAlert(
81+
`${this.app.symbol} ${chrome.runtime.getManifest().name}`, // title
82+
`<span style="${headingStyle}"><b>🏷️ <i>Version</i></b>: </span>`
83+
+ `<span style="${pStyle}">${this.app.version}</span>\n`
84+
+ `<span style="${headingStyle}"><b>⚡ <i>Powered by</i></b>: </span>`
85+
+ `<span style="${pStyle}">`
86+
+ `<a style="${aStyle}" href="${this.app.urls.chatgptJS}" target="_blank" rel="noopener">`
87+
+ 'chatgpt.js</a></span>\n'
88+
+ `<span style="${headingStyle}"><b>📜 <i>Open source code</i></b>:</span>\n`
89+
+ `<span style="${pBrStyle}"><a href="${this.app.urls.gitHub}" target="_blank" rel="nopener">`
90+
+ this.app.urls.gitHub + '</a></span>',
91+
modalBtns, '', 451
92+
)
93+
94+
// Format text
95+
aboutModal.querySelector('h2').style.cssText = 'text-align: center ; font-size: 37px ; padding: 9px'
96+
aboutModal.querySelector('p').style.cssText = 'text-align: center'
97+
98+
// Hack buttons
99+
aboutModal.querySelector('.modal-buttons').style.justifyContent = 'center'
100+
aboutModal.querySelectorAll('button').forEach(btn => {
101+
btn.style.cssText = 'cursor: pointer !important ; height: 43px'
102+
103+
// Prepend emoji
104+
if (/support/i.test(btn.textContent))
105+
btn.textContent = '🧠 ' + btn.textContent
106+
else if (/rate/i.test(btn.textContent))
107+
btn.textContent = '⭐ ' + btn.textContent
108+
else if (/extensions/i.test(btn.textContent))
109+
btn.textContent = '🧠 ' + btn.textContent
110+
111+
// Hide Dismiss button
112+
else btn.style.display = 'none'
113+
})
114+
115+
return aboutModal
116+
},
117+
118+
safeWinOpen(url) { open(url, '_blank', 'noopener') } // to prevent backdoor vulnerabilities
119+
};

starters/chrome/extension/content.js

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,28 @@
33

44
(async () => {
55

6-
// Import LIBS
7-
await import(chrome.runtime.getURL('lib/chatgpt.js'))
8-
await import(chrome.runtime.getURL('lib/settings.js'))
6+
// Import JS resources
7+
for (const resource of ['components/modals.js', 'lib/chatgpt.js', 'lib/dom.js', 'lib/settings.js'])
8+
await import(chrome.runtime.getURL(resource))
9+
10+
// Init ENV context
11+
const env = { browser: { isMobile: chatgpt.browser.isMobile() }}
912

1013
// Import APP data
1114
const { app } = await chrome.storage.sync.get('app')
15+
modals.import({ app, siteAlert })
1216

1317
// Add CHROME MSG listener
1418
chrome.runtime.onMessage.addListener(req => {
1519
if (req.action == 'notify')
1620
notify(...['msg', 'pos', 'notifDuration', 'shadow'].map(arg => req.options[arg]))
1721
else if (req.action == 'alert')
1822
siteAlert(...['title', 'msg', 'btns', 'checkbox', 'width'].map(arg => req.options[arg]))
23+
else if (req.action == 'showAbout') chatgpt.isLoaded().then(() => { modals.open('about') })
1924
else if (req.action == 'syncConfigToUI') syncConfigToUI(req.options)
2025
})
2126

22-
// Init CONFIG
27+
// Init SETTINGS
2328
await settings.load(Object.keys(settings.controls), 'skipAlert')
2429

2530
// Define FEEDBACK functions
@@ -31,7 +36,7 @@
3136
if (foundState) msg = msg.replace(foundState, '')
3237

3338
// Show notification
34-
chatgpt.notify(`${app.symbol} ${msg}`, pos, notifDuration,
39+
siteAlert(`${app.symbol} ${msg}`, pos, notifDuration,
3540
shadow || chatgpt.isDarkMode() ? '' : 'shadow' )
3641
const notif = document.querySelector('.chatgpt-notif:last-child')
3742

@@ -46,7 +51,9 @@
4651
}
4752

4853
function siteAlert(title = '', msg = '', btns = '', checkbox = '', width = '') {
49-
return chatgpt.alert(`${app.symbol} ${title}`, msg, btns, checkbox, width )}
54+
const alertID = chatgpt.alert(title, msg, btns, checkbox, width)
55+
return document.getElementById(alertID).firstChild
56+
}
5057

5158
// Define SYNC function
5259

@@ -65,10 +72,44 @@
6572

6673
// Run MAIN routine
6774

68-
if (config.extensionDisabled) return
69-
7075
chatgpt.printAllFunctions() // to console
71-
await chatgpt.isLoaded() // if your hacks depend on delayed DOM content
76+
77+
// Chill a bit if your hacks depend on delayed DOM content
78+
await chatgpt.isLoaded()
79+
await new Promise(resolve => setTimeout(resolve, 500)) // sleep .5s
80+
81+
// Add/update TWEAKS style
82+
const tweaksStyleUpdated = 1732627011377 // timestamp of last edit for this file's tweaksStyle
83+
let tweaksStyle = document.getElementById('tweaks-style') // try to select existing style (from your other extensions)
84+
if (!tweaksStyle || parseInt(tweaksStyle.getAttribute('last-updated')) < tweaksStyleUpdated) {
85+
if (!tweaksStyle) { // outright missing, create/id/attr/append it first
86+
tweaksStyle = dom.create.elem('style', {
87+
id: 'tweaks-style', 'last-updated': tweaksStyleUpdated.toString() })
88+
document.head.append(tweaksStyle)
89+
}
90+
tweaksStyle.innerText = (
91+
'[class$="-modal"] { z-index: 13456 ; position: absolute }' // to be click-draggable
92+
+ ( chatgpt.isDarkMode() ? '.chatgpt-modal > div { border: 1px solid white }' : '' )
93+
+ '.chatgpt-modal button {'
94+
+ 'font-size: 0.77rem ; text-transform: uppercase ;' // shrink/uppercase labels
95+
+ 'border-radius: 0 !important ;' // square borders
96+
+ 'transition: transform 0.1s ease-in-out, box-shadow 0.1s ease-in-out ;' // smoothen hover fx
97+
+ 'cursor: pointer !important ;' // add finger cursor
98+
+ 'padding: 5px !important ; min-width: 102px }' // resize
99+
+ '.chatgpt-modal button:hover {' // add zoom, re-scheme
100+
+ 'transform: scale(1.055) ; color: black !important ;'
101+
+ `background-color: #${ chatgpt.isDarkMode() ? '00cfff' : '9cdaff' } !important }`
102+
+ ( !env.browser.isMobile ? '.modal-buttons { margin-left: -13px !important }' : '' )
103+
)
104+
}; // eslint-disable-line
105+
106+
// Add STARS styles for modals
107+
['black', 'white'].forEach(color => document.head.append(
108+
dom.create.elem('link', { rel: 'stylesheet',
109+
href: `https://assets.aiwebextensions.com/styles/css/${color}-rising-stars.min.css?v=542104c`
110+
})))
111+
112+
if (config.extensionDisabled) return
72113

73114
if (!config.skipAlert) // alert to extension load
74115
chatgpt.alert('≫ ChatGPT extension loaded! 🚀', // title

starters/chrome/extension/lib/dom.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,18 @@ window.dom = {
1111
for (const attr in attrs) elem.setAttributeNS(null, attr, attrs[attr])
1212
return elem
1313
}
14+
},
15+
16+
fillStarryBG(targetNode) { // requires https://assets.aiwebextensions.com/styles/css/<black|white>-rising-stars.min.css
17+
const starsDivsContainer = document.createElement('div')
18+
starsDivsContainer.style.cssText = 'position: absolute ; top: 0 ; left: 0 ;' // hug targetNode's top-left corner
19+
+ 'height: 100% ; width: 100% ; border-radius: 15px ; overflow: clip ;' // bound innards exactly by targetNode
20+
+ 'z-index: -1'; // allow interactive elems to be clicked
21+
['sm', 'med', 'lg'].forEach(starSize => {
22+
const starsDiv = document.createElement('div')
23+
starsDiv.id = `${ chatgpt.isDarkMode() ? 'white' : 'black' }-stars-${starSize}`
24+
starsDivsContainer.append(starsDiv)
25+
})
26+
targetNode.prepend(starsDivsContainer)
1427
}
1528
};

starters/chrome/extension/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"action": { "default_popup": "popup/index.html" },
1717
"web_accessible_resources": [{
1818
"matches": [ "<all_urls>" ],
19-
"resources": [ "lib/chatgpt.js", "lib/dom.js", "lib/settings.js" ]
19+
"resources": [ "components/modals.js", "lib/chatgpt.js", "lib/dom.js", "lib/settings.js" ]
2020
}],
2121
"content_scripts": [{
2222
"matches": [ "https://chatgpt.com/*" ],

starters/chrome/extension/popup/controller.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,13 @@
116116
cjsLogo.onclick = () => chrome.tabs.create({ url: app.urls.chatgptJS })
117117
cjsDiv.append(cjsLogo) ; footer.append(cjsDiv)
118118

119-
// Create/append SUPPORT footer button
120-
const supportSpan = dom.create.elem('span', {
121-
title: 'Get Support',
119+
// Create/append ABOUT footer button
120+
const aboutSpan = dom.create.elem('span', {
121+
title: 'About ChatGPT Extension',
122122
class: 'menu-icon menu-area', style: 'right:30px ; padding-top: 2px' })
123-
const supportIcon = icons.create({ name: 'questionMark', width: 15, height: 13, style: 'margin-bottom: 0.04rem' })
124-
supportSpan.onclick = () => { chrome.tabs.create({ url: app.urls.support }) ; close() }
125-
supportSpan.append(supportIcon) ; footer.append(supportSpan)
123+
const aboutIcon = icons.create({ name: 'questionMark', width: 15, height: 13, style: 'margin-bottom: 0.04rem' })
124+
aboutSpan.onclick = () => { chrome.runtime.sendMessage({ action: 'showAbout' }) ; close() }
125+
aboutSpan.append(aboutIcon) ; footer.append(aboutSpan)
126126

127127
// Create/append RELATED EXTENSIONS footer button
128128
const moreExtensionsSpan = dom.create.elem('span', {
Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,3 @@
1-
// Init APP data
2-
const app = {
3-
symbol: '🤖',
4-
urls: {
5-
assetHost: 'https://cdn.jsdelivr.net/gh/KudoAI/chatgpt.js-chrome-starter',
6-
cjsMediaHost: 'https://media.chatgptjs.org',
7-
gitHub: 'https://github.com/KudoAI/chatgpt.js-chrome-starter',
8-
relatedExtensions: 'https://aiwebextensions.com',
9-
support: 'https://github.com/KudoAI/chatgpt.js-chrome-starter/issues'
10-
}
11-
} ; chrome.storage.sync.set({ app }) // save to Chrome storage
12-
131
// Launch ChatGPT on install
142
chrome.runtime.onInstalled.addListener(details => {
153
if (details.reason == 'install')
@@ -19,3 +7,35 @@ chrome.runtime.onInstalled.addListener(details => {
197
// Sync settings to activated tabs
208
chrome.tabs.onActivated.addListener(activeInfo =>
219
chrome.tabs.sendMessage(activeInfo.tabId, { action: 'syncConfigToUI' }))
10+
11+
// Show ABOUT modal on ChatGPT when toolbar button clicked
12+
chrome.runtime.onMessage.addListener(async req => {
13+
if (req.action == 'showAbout') {
14+
const [activeTab] = await chrome.tabs.query({ active: true, currentWindow: true })
15+
const chatgptTab = new URL(activeTab.url).hostname == 'chatgpt.com' ? activeTab
16+
: await chrome.tabs.create({ url: 'https://chatgpt.com/' })
17+
if (activeTab != chatgptTab) await new Promise(resolve => // after new tab loads
18+
chrome.tabs.onUpdated.addListener(async function statusListener(tabId, info) {
19+
if (tabId == chatgptTab.id && info.status == 'complete') {
20+
chrome.tabs.onUpdated.removeListener(statusListener)
21+
await new Promise(resolve => setTimeout(resolve, 2500))
22+
resolve()
23+
}}))
24+
chrome.tabs.sendMessage(chatgptTab.id, { action: 'showAbout' })
25+
}
26+
});
27+
28+
// Init APP data
29+
(async () => {
30+
const app = {
31+
symbol: '🤖', version: chrome.runtime.getManifest().version,
32+
urls: {
33+
assetHost: 'https://cdn.jsdelivr.net/gh/KudoAI/chatgpt.js-chrome-starter',
34+
cjsMediaHost: 'https://media.chatgptjs.org',
35+
gitHub: 'https://github.com/KudoAI/chatgpt.js-chrome-starter',
36+
relatedExtensions: 'https://aiwebextensions.com',
37+
support: 'https://github.com/KudoAI/chatgpt.js-chrome-starter/issues'
38+
}
39+
}
40+
chrome.storage.sync.set({ app }) // save to Chrome storage
41+
})()

0 commit comments

Comments
 (0)