Skip to content

Commit 665509e

Browse files
committed
Add context menu to add or remove whitelist
1 parent 67a68b7 commit 665509e

File tree

16 files changed

+349
-144
lines changed

16 files changed

+349
-144
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import whitelistDatabase from '../database/whitelist-database'
2+
import { t2Chrome } from '../util/i18n/chrome/t'
3+
import { extractHostname, isBrowserUrl } from '../util/pattern'
4+
5+
const db = whitelistDatabase
6+
7+
const menuId = '_timer_menu_item_' + Date.now()
8+
9+
let whitelist: string[] = []
10+
11+
const whitelistSetter = (_whitelist: string[]) => whitelist = _whitelist
12+
13+
const removeOrAdd = (removeOrAddFlag: boolean, host: string) => removeOrAddFlag ? db.remove(host) : db.add(host)
14+
15+
class ContextMenusManager {
16+
private currentActiveId: number
17+
18+
init() {
19+
db.selectAll().then(whitelistSetter)
20+
db.addChangeListener(_whitelist => {
21+
whitelistSetter(_whitelist)
22+
this.updateContextMenu(this.currentActiveId)
23+
})
24+
chrome.contextMenus.create(
25+
{
26+
contexts: ['page', 'frame', 'selection', 'link', 'editable', 'image', 'video', 'audio', 'launcher'],
27+
id: menuId,
28+
checked: true,
29+
title: 'foobar',
30+
visible: false
31+
}
32+
)
33+
chrome.tabs.onUpdated.addListener(
34+
(tabId, changeInfo, tab) =>
35+
// Current active tab updated
36+
tabId === this.currentActiveId
37+
&& changeInfo.status === 'loading'
38+
&& this.updateContextMenu(tab)
39+
)
40+
chrome.tabs.onActivated.addListener(activeInfo => {
41+
this.currentActiveId = activeInfo.tabId
42+
this.updateContextMenu(this.currentActiveId)
43+
})
44+
}
45+
46+
/**
47+
* @param currentActiveTab tab info
48+
*/
49+
private updateContextMenu(currentActiveTab: chrome.tabs.Tab | number) {
50+
if (typeof currentActiveTab === 'number') {
51+
// If number, get the tabInfo first
52+
chrome.tabs.get(this.currentActiveId, tab => tab && this.updateContextMenu(tab))
53+
} else {
54+
const tab = currentActiveTab as chrome.tabs.Tab
55+
const { url } = tab
56+
const targetHost = url && !isBrowserUrl(url) ? extractHostname(tab.url).host : ''
57+
const changeProp: chrome.contextMenus.UpdateProperties = {}
58+
if (!targetHost) {
59+
// If not a valid host, hide this menu
60+
changeProp.visible = false
61+
} else {
62+
// Else change the title
63+
const existsInWhitelist = whitelist.includes(targetHost)
64+
changeProp.visible = true
65+
changeProp.title = existsInWhitelist
66+
? t2Chrome(root => root.contextMenus.removeFromWhitelist)
67+
: t2Chrome(root => root.contextMenus.add2Whitelist)
68+
changeProp.onclick = () => removeOrAdd(existsInWhitelist, targetHost)
69+
}
70+
chrome.contextMenus.update(menuId, changeProp)
71+
}
72+
}
73+
}
74+
75+
export default ContextMenusManager
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import IconUrlDatabase from "../database/icon-url-database"
2+
import { IS_CHROME } from "../util/constant/environment"
3+
import { extractHostname } from "../util/pattern"
4+
5+
const iconUrlDatabase = new IconUrlDatabase(chrome.storage.local)
6+
7+
/**
8+
* Collect the favicon of host
9+
*/
10+
class IconUrlCollector {
11+
listen() {
12+
chrome.webNavigation.onCompleted.addListener((detail) => {
13+
if (detail.frameId > 0) {
14+
// we don't care about activity occurring within a subframe of a tab
15+
return
16+
}
17+
chrome.tabs.get(detail.tabId, tab => {
18+
if (!tab) return
19+
const url = tab.url
20+
if (!url) return
21+
const hostInfo = extractHostname(url)
22+
const domain = hostInfo.host
23+
const protocol = hostInfo.protocol
24+
if (!domain) return
25+
let favIconUrl = tab.favIconUrl
26+
// localhost hosts with Chrome use cache, so keep the favIconurl undefined
27+
IS_CHROME && /^localhost(:.+)?/.test(domain) && (favIconUrl = undefined)
28+
const iconUrl = favIconUrl
29+
|| (IS_CHROME ? `chrome://favicon/${protocol ? protocol + '://' : ''}${domain}` : '')
30+
iconUrlDatabase.put(domain, iconUrl)
31+
})
32+
})
33+
}
34+
}
35+
36+
export default IconUrlCollector

src/background/index.ts

Lines changed: 14 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,21 @@
11
import { openLog } from '../common/logger'
2-
import IconUrlDatabase from '../database/icon-url-database'
3-
import timerService from '../service/timer-service'
4-
import { IS_CHROME } from '../util/constant/environment'
5-
import { extractHostname, isBrowserUrl } from '../util/pattern'
6-
import versionManager from './version-manager'
2+
import { t2Chrome } from '../util/i18n/chrome/t'
3+
import ContextMenusManager from './context-menus-manager'
4+
import IconUrlCollector from './icon-url-collector'
5+
import Timer from './timer'
6+
import VersionManager from './version-manager'
77

8+
// Open the log of console
89
openLog()
910

10-
const iconUrlDatabase = new IconUrlDatabase(chrome.storage.local)
11+
// Start the timer
12+
new Timer().start()
1113

12-
let lastLoopTime = Date.now()
14+
// Collect the icon url
15+
new IconUrlCollector().listen()
1316

14-
let timeMap: { [host: string]: { focus: number, run: number } } = {}
17+
// Process version
18+
new VersionManager().init()
1519

16-
setInterval(() => {
17-
const now = Date.now()
18-
const realInterval = now - lastLoopTime
19-
lastLoopTime = now
20-
chrome.windows.getAll(windows => {
21-
const hostSet: Set<string> = new Set()
22-
let focusHost = ''
23-
Promise.all(
24-
windows.map(w => {
25-
const isFocusWindow = !!w.focused
26-
return new Promise<void>(resolve => {
27-
chrome.tabs.query({ windowId: w.id }, tabs => {
28-
tabs.forEach(tab => {
29-
const url = tab.url
30-
if (!url) return
31-
if (isBrowserUrl(url)) return
32-
const host = extractHostname(url).host
33-
if (host) {
34-
hostSet.add(host)
35-
isFocusWindow && tab.active && (focusHost = host)
36-
} else {
37-
console.log('Detect blank host:', url)
38-
}
39-
})
40-
resolve()
41-
})
42-
})
43-
})
44-
).then(() => {
45-
hostSet.forEach(host => {
46-
let data = timeMap[host]
47-
!data && (timeMap[host] = data = { focus: 0, run: 0 })
48-
focusHost === host && (data.focus += realInterval)
49-
data.run += realInterval
50-
})
51-
})
52-
})
53-
}, 555)
54-
55-
setInterval(() => {
56-
timerService.addFocusAndTotal(timeMap)
57-
timeMap = {}
58-
}, 2048)
59-
60-
chrome.runtime.onInstalled.addListener(detail => versionManager.onChromeInstalled(detail.reason))
61-
62-
chrome.webNavigation.onCompleted.addListener((detail) => {
63-
if (detail.frameId > 0) {
64-
// we don't care about activity occurring within a subframe of a tab
65-
return
66-
}
67-
chrome.tabs.get(detail.tabId, tab => {
68-
if (!tab) return
69-
const url = tab.url
70-
if (!url) return
71-
const hostInfo = extractHostname(url)
72-
const domain = hostInfo.host
73-
const protocol = hostInfo.protocol
74-
if (!domain) return
75-
let favIconUrl = tab.favIconUrl
76-
// localhost hosts with Chrome use cache, so keep the favIconurl undefined
77-
IS_CHROME && /^localhost(:.+)?/.test(domain) && (favIconUrl = undefined)
78-
const iconUrl = favIconUrl
79-
|| (IS_CHROME ? `chrome://favicon/${protocol ? protocol + '://' : ''}${domain}` : '')
80-
iconUrlDatabase.put(domain, iconUrl)
81-
})
82-
})
20+
// Mange the context menus
21+
new ContextMenusManager().init()

src/background/timer.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import timerService from "../service/timer-service"
2+
import { isBrowserUrl, extractHostname } from "../util/pattern"
3+
4+
let lastCollectTime: number
5+
let timeMap: { [host: string]: { focus: number, run: number } } = {}
6+
7+
class Timer {
8+
/**
9+
* Collect the time once
10+
*/
11+
private collect() {
12+
const now = Date.now()
13+
const realInterval = now - lastCollectTime
14+
lastCollectTime = now
15+
chrome.windows.getAll(windows => {
16+
const hostSet: Set<string> = new Set()
17+
let focusHost = ''
18+
Promise.all(
19+
windows.map(w => {
20+
const isFocusWindow = !!w.focused
21+
return new Promise<void>(resolve => {
22+
chrome.tabs.query({ windowId: w.id }, tabs => {
23+
tabs.forEach(tab => {
24+
const url = tab.url
25+
if (!url) return
26+
if (isBrowserUrl(url)) return
27+
const host = extractHostname(url).host
28+
if (host) {
29+
hostSet.add(host)
30+
isFocusWindow && tab.active && (focusHost = host)
31+
} else {
32+
console.log('Detect blank host:', url)
33+
}
34+
})
35+
resolve()
36+
})
37+
})
38+
})
39+
).then(() => {
40+
hostSet.forEach(host => {
41+
let data = timeMap[host]
42+
!data && (timeMap[host] = data = { focus: 0, run: 0 })
43+
focusHost === host && (data.focus += realInterval)
44+
data.run += realInterval
45+
})
46+
})
47+
})
48+
}
49+
50+
/**
51+
* Save data and reset
52+
*/
53+
private save() {
54+
timerService.addFocusAndTotal(timeMap)
55+
timeMap = {}
56+
}
57+
58+
start() {
59+
lastCollectTime = Date.now()
60+
setInterval(this.collect, 555)
61+
setInterval(this.save, 2048)
62+
}
63+
}
64+
65+
export default Timer

src/background/version-manager/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class VersionManager {
1414
this.processorChain = this.processorChain.sort((a, b) => a.since() >= b.since() ? 1 : 0)
1515
}
1616

17-
onChromeInstalled(reason: string) {
17+
private onChromeInstalled(reason: string) {
1818
const version: string = chrome.runtime.getManifest().version
1919
if (reason === 'update') {
2020
// Update, process the latest version, which equals to current version
@@ -26,6 +26,10 @@ class VersionManager {
2626
this.processorChain.forEach(processor => processor.process(reason))
2727
}
2828
}
29+
30+
init() {
31+
chrome.runtime.onInstalled.addListener(detail => this.onChromeInstalled(detail.reason))
32+
}
2933
}
3034

31-
export default new VersionManager()
35+
export default VersionManager

src/common/logger.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ let OPEN_LOG = false
55
* @param args arguments
66
*/
77
export function log(...args: any) {
8-
OPEN_LOG && console.log(args)
8+
OPEN_LOG && console.log(...args)
99
}
1010

1111
/**

src/content-script.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,31 @@ import TimerDatabase from "./database/timer-database"
22
import WastePerDay from "./entity/dao/waste-per-day"
33
import timeService from './service/timer-service'
44
import whitelistService from "./service/whitelist-service"
5+
import { t2Chrome } from "./util/i18n/chrome/t"
56
import { formatPeriod } from "./util/time"
67

78
const timerDatabase = new TimerDatabase(chrome.storage.local)
89
const host = document.location.host
910

10-
timeService.addOneTime(host)
11+
host && timeService.addOneTime(host)
1112

12-
whitelistService.include(host)
13+
host && whitelistService.include(host)
1314
.then(including => {
1415
if (including) return
1516

16-
const hourMsg = chrome.i18n.getMessage('message_timeWithHour')
17-
const minuteMsg = chrome.i18n.getMessage('message_timeWithMinute')
18-
const secondMsg = chrome.i18n.getMessage('message_timeWithSecond')
17+
const hourMsg = t2Chrome(root => root.message.timeWithHour)
18+
const minuteMsg = t2Chrome(root => root.message.timeWithMinute)
19+
const secondMsg = t2Chrome(root => root.message.timeWithSecond)
1920
timerDatabase
2021
.get(host, new Date())
2122
.then((waste: WastePerDay) => {
22-
const info0 = chrome.i18n.getMessage('message_openTimesConsoleLog')
23+
const info0 = t2Chrome(root => root.message.openTimesConsoleLog)
2324
.replace('{time}', waste.time ? '' + waste.time : '-')
2425
.replace('{host}', host)
25-
const info1 = chrome.i18n.getMessage('message_usedTimeInConsoleLog')
26+
const info1 = t2Chrome(root => root.message.usedTimeInConsoleLog)
2627
.replace('{focus}', formatPeriod(waste.focus, hourMsg, minuteMsg, secondMsg))
2728
.replace('{total}', formatPeriod(waste.total, hourMsg, minuteMsg, secondMsg))
2829
console.log(info0)
2930
console.log(info1)
3031
})
31-
})
32+
})

src/database/whitelist-database.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { WHITELIST_KEY } from "./constant"
22

3+
4+
const ruleId = '_timer_whitelist_db_change_rule_id'
5+
36
class WhitelistDatabase {
47

58
private localStorage = chrome.storage.local
@@ -41,6 +44,22 @@ class WhitelistDatabase {
4144
const selectAll = await this.selectAll()
4245
return Promise.resolve(selectAll.includes(url))
4346
}
47+
48+
/**
49+
* Add listener to listen changes
50+
*
51+
* @since 0.1.9
52+
*/
53+
addChangeListener(listener: (whitelist: string[]) => void) {
54+
const storageListener = (
55+
changes: { [key: string]: chrome.storage.StorageChange; },
56+
_areaName: "sync" | "local" | "managed"
57+
) => {
58+
const changeInfo = changes[WHITELIST_KEY]
59+
changeInfo && listener(changeInfo.newValue || [])
60+
}
61+
chrome.storage.onChanged.addListener(storageListener)
62+
}
4463
}
4564

4665
export default new WhitelistDatabase()

src/manifest.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ export default {
3939
'storage',
4040
'tabs',
4141
'webNavigation',
42-
'chrome://favicon/*'
42+
'chrome://favicon/*',
43+
'contextMenus'
4344
],
4445
browser_action: {
4546
default_popup: "static/popup.html",

0 commit comments

Comments
 (0)