Skip to content

Commit c5047f9

Browse files
authored
Merge pull request #154 from sheepzh/safari_support
Safari support #152
2 parents c13d6aa + 16b17f6 commit c5047f9

File tree

20 files changed

+263
-54
lines changed

20 files changed

+263
-54
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
node_modules
22
dist
33
dist_dev
4+
dist_dev_safari
45
dist_prod
6+
dist_prod_safari
57
dist_analyze
68

9+
Timer_Safari_DEV
10+
Timer
11+
712
firefox_dev
813

914
market_packages

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ Timer can
3737
[![](https://img.shields.io/amo/rating/2690100?color=green)](https://addons.mozilla.org/en-US/firefox/addon/2690100)
3838
[![Mozilla Add-on](https://img.shields.io/amo/users/2690100?color=green)](https://addons.mozilla.org/en-US/firefox/addon/2690100)
3939

40+
[How to install manually for Safari](./doc/safari-install.md)
41+
4042
![User Count](https://gist.githubusercontent.com/sheepzh/6aaf4c22f909db73b533491167da129b/raw/user_count.svg)
4143

4244
## Contribution

doc/dev-guide.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,25 @@ yarn run test
5353
```
5454
7. 提交代码,并 PR 主仓库的 main 分支
5555

56-
## 3. 应用架构设计
56+
## 3. 在 Safari 里运行
57+
58+
1. 重复上述步骤 1-4
59+
2. 编译兼容 Safari 的代码,替换上述步骤 5 的指令即可
60+
```shell
61+
npm run dev:safari
62+
```
63+
3. 使用 Xcode 内置工具 safari-web-extension-converter 将 Chrome 扩展转换成 Safari 扩展
64+
```shell
65+
[YOUR_PATH]/Xcode.app/Contents/Developer/usr/bin/safari-web-extension-converter dist_dev_safari
66+
```
67+
项目根目录下会生成一个文件夹 Timer_Safari_DEV,同时 Xcode 会自动打开该文件夹
68+
4. 在 Xcode 里运行打开的项目即可
69+
70+
## 4. 应用架构设计
5771

5872
> todo
5973
60-
## 4. 目录结构
74+
## 5. 目录结构
6175

6276
```plain
6377
project
@@ -117,6 +131,7 @@ project
117131
└───webpack # webpack 打包配置
118132
| webpack.common.ts # 基础配置
119133
| webpack.dev.ts # 开发环境配置
134+
| webpack.dev.safari.ts # Safari 开发环境配置
120135
| webpack.prod.ts # 生产配置
121136
122137
```

doc/safari-install.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# How to install for Safari
2+
3+
This is a too poor developer to pay $99 per year for distribution of an opensource and free browser extension in Apple App Store.
4+
So please intall it **manually**, GG Safari.
5+
6+
## 0. Download this repository
7+
8+
```shell
9+
git clone https://github.com/sheepzh/timer.git
10+
cd timer
11+
```
12+
13+
## 1. Install tools
14+
15+
Some tools are required to compile this project to an executable software for Safari.
16+
17+
* [nodejs & npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
18+
* [Xcode (compitable for your version of macOS)](https://developer.apple.com/xcode/)
19+
20+
## 2. Compile source code, install & run
21+
22+
There are serveral steps.
23+
24+
1. Compile the sourcecode programmed with TypeScript to js bundles.
25+
26+
```shell
27+
# Install dependencies
28+
npm install
29+
# Compile
30+
npm run build:safari
31+
```
32+
Then there will be one folder called **Timer**.
33+
34+
Also, you can download the archived file from [the release page](https://github.com/sheepzh/timer/releases), and unzip it to gain this folder.
35+
36+
2. Convert js bundles to Xcode project
37+
38+
```shell
39+
[YOUR_PATH]/Xcode.app/Contents/Developer/usr/bin/safari-web-extension-converter ./Timer
40+
```
41+
3. Run Xcode project and one extension app will installed on your macOS
42+
4. Enable this extension
43+
5. Finnally, open your Safari

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"homepage": "https://github.com/sheepzh/timer",
66
"scripts": {
77
"dev": "webpack --config=webpack/webpack.dev.ts --watch",
8+
"dev:safari": "webpack --config=webpack/webpack.dev.safari.ts --watch",
89
"build": "webpack --config=webpack/webpack.prod.ts",
910
"analyze": "webpack --config=webpack/webpack.analyze.ts",
1011
"test": "jest --env=jsdom",

src/app/components/common/host-alert.ts

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* https://opensource.org/licenses/MIT
66
*/
77

8+
import { IS_SAFARI } from "@util/constant/environment"
89
import { ElLink } from "element-plus"
910
import { computed, ComputedRef, defineComponent, h } from "vue"
1011

@@ -39,20 +40,27 @@ const _default = defineComponent({
3940
const href: ComputedRef<string> = computed(() => props.clickable ? `http://${props.host}` : '')
4041
const target: ComputedRef<string> = computed(() => props.clickable ? '_blank' : '')
4142
const cursor: ComputedRef<string> = computed(() => props.clickable ? "cursor" : "default")
42-
return () => h('div', [
43-
h(ElLink,
44-
{
45-
href: href.value,
46-
target: target.value,
47-
underline: props.clickable,
48-
style: { cursor: cursor.value }
49-
},
50-
() => props.host
51-
), h('span',
52-
{ style: HOST_ICON_STYLE },
53-
h('img', { src: props.iconUrl, width: 12, height: 12 })
54-
)
55-
])
43+
return IS_SAFARI
44+
? () => h(ElLink, {
45+
href: href.value,
46+
target: target.value,
47+
underline: props.clickable,
48+
style: { cursor: cursor.value }
49+
}, () => props.host)
50+
: () => h('div', [
51+
h(ElLink,
52+
{
53+
href: href.value,
54+
target: target.value,
55+
underline: props.clickable,
56+
style: { cursor: cursor.value }
57+
},
58+
() => props.host
59+
), h('span',
60+
{ style: HOST_ICON_STYLE },
61+
h('img', { src: props.iconUrl, width: 12, height: 12 })
62+
)
63+
])
5664
}
5765
})
5866

src/app/components/option/components/statistics.ts

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { defaultStatistics } from "@util/constant/option"
1313
import { defineComponent, h, reactive, unref } from "vue"
1414
import { t } from "@app/locale"
1515
import { renderOptionItem, tagText, tooltip } from "../common"
16+
import { IS_SAFARI } from "@util/constant/environment"
1617

1718
function updateOptionVal(key: keyof timer.option.StatisticsOption, newVal: boolean, option: UnwrapRef<timer.option.StatisticsOption>) {
1819
option[key] = newVal
@@ -40,6 +41,35 @@ function copy(target: timer.option.StatisticsOption, source: timer.option.Statis
4041
target.countLocalFiles = source.countLocalFiles
4142
}
4243

44+
function renderOptionItems(option: timer.option.StatisticsOption) {
45+
const result = []
46+
if (!IS_SAFARI) {
47+
// chrome.idle does not work in Safari, so not to display this option
48+
result.push(
49+
renderOptionItem({
50+
input: countWhenIdle(option),
51+
idleTime: tagText(msg => msg.option.statistics.idleTime),
52+
info: tooltip(msg => msg.option.statistics.idleTimeInfo)
53+
}, msg => msg.statistics.countWhenIdle, t(msg => msg.option.no)),
54+
h(ElDivider)
55+
)
56+
}
57+
result.push(
58+
renderOptionItem({
59+
input: countLocalFiles(option),
60+
localFileTime: tagText(msg => msg.option.statistics.localFileTime),
61+
info: tooltip(msg => msg.option.statistics.localFilesInfo)
62+
}, msg => msg.statistics.countLocalFiles, t(msg => msg.option.no)),
63+
h(ElDivider),
64+
renderOptionItem({
65+
input: collectSiteName(option),
66+
siteName: tagText(msg => msg.option.statistics.siteName),
67+
siteNameUsage: tooltip(msg => msg.option.statistics.siteNameUsage)
68+
}, msg => msg.statistics.collectSiteName, t(msg => msg.option.yes))
69+
)
70+
return result
71+
}
72+
4373
const _default = defineComponent({
4474
name: "StatisticsOptionContainer",
4575
setup(_props, ctx) {
@@ -51,25 +81,7 @@ const _default = defineComponent({
5181
await optionService.setStatisticsOption(unref(option))
5282
}
5383
})
54-
return () => h('div', [
55-
renderOptionItem({
56-
input: countWhenIdle(option),
57-
idleTime: tagText(msg => msg.option.statistics.idleTime),
58-
info: tooltip(msg => msg.option.statistics.idleTimeInfo)
59-
}, msg => msg.statistics.countWhenIdle, t(msg => msg.option.no)),
60-
h(ElDivider),
61-
renderOptionItem({
62-
input: countLocalFiles(option),
63-
localFileTime: tagText(msg => msg.option.statistics.localFileTime),
64-
info: tooltip(msg => msg.option.statistics.localFilesInfo)
65-
}, msg => msg.statistics.countLocalFiles, t(msg => msg.option.no)),
66-
h(ElDivider),
67-
renderOptionItem({
68-
input: collectSiteName(option),
69-
siteName: tagText(msg => msg.option.statistics.siteName),
70-
siteNameUsage: tooltip(msg => msg.option.statistics.siteNameUsage)
71-
}, msg => msg.statistics.collectSiteName, t(msg => msg.option.yes))
72-
])
84+
return () => h('div', renderOptionItems(option))
7385
}
7486
})
7587

src/app/components/report/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { useRoute, useRouter } from "vue-router"
2727
import { groupBy, sum } from "@util/array"
2828
import { formatTime } from "@util/time"
2929
import TimerDatabase from "@db/timer-database"
30+
import { IS_SAFARI } from "@util/constant/environment"
3031

3132
const timerDatabase = new TimerDatabase(chrome.storage.local)
3233

@@ -38,7 +39,7 @@ async function queryData(
3839
) {
3940
const loading = ElLoadingService({ target: `.container-card>.el-card__body`, text: "LOADING..." })
4041
const pageInfo = { size: page.size, num: page.num }
41-
const fillFlag = { alias: true, iconUrl: true }
42+
const fillFlag = { alias: true, iconUrl: !IS_SAFARI }
4243
const param = {
4344
...queryParam.value,
4445
inclusiveRemote: readRemote.value

src/background/badge-text-manager.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,16 +50,29 @@ async function updateFocus(host?: string) {
5050
if (!host) {
5151
host = await findActiveHost()
5252
}
53-
const milliseconds = host ? (await timerDb.get(host, new Date)).focus : undefined
53+
const milliseconds = host ? (await timerDb.get(host, new Date())).focus : undefined
5454
setBadgeText(milliseconds)
5555
}
5656

57+
const ALARM_NAME = 'timer-badge-text-manager-alarm'
58+
const ALARM_INTERVAL = 1000
59+
function createAlarm(beforeAction?: () => void) {
60+
beforeAction?.()
61+
chrome.alarms.create(ALARM_NAME, { when: Date.now() + ALARM_INTERVAL })
62+
}
63+
5764
class BadgeTextManager {
5865
timer: NodeJS.Timer
5966
isPaused: boolean
6067

6168
async init() {
62-
this.timer = setInterval(() => !this.isPaused && updateFocus(), 1000)
69+
createAlarm()
70+
chrome.alarms.onAlarm.addListener(alarm => {
71+
if (ALARM_NAME === alarm.name) {
72+
createAlarm(() => !this.isPaused && updateFocus())
73+
}
74+
})
75+
// this.timer = setInterval(() => !this.isPaused && updateFocus(), 1000)
6376

6477
const option: Partial<timer.option.AllOption> = await optionService.getAllOption()
6578
this.pauseOrResumeAccordingToOption(!!option.displayBadgeText)

src/background/browser-action-menu-manager.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/**
1+
/**
22
* Copyright (c) 2021 Hengyang Zhang
33
*
44
* This software is released under the MIT License.
@@ -8,6 +8,7 @@
88
import { OPTION_ROUTE } from "../app/router/constants"
99
import { getAppPageUrl, SOURCE_CODE_PAGE, TU_CAO_PAGE } from "@util/constant/url"
1010
import { t2Chrome } from "@util/i18n/chrome/t"
11+
import { IS_SAFARI } from "@util/constant/environment"
1112

1213
const APP_PAGE_URL = getAppPageUrl(true)
1314

@@ -19,30 +20,39 @@ const baseProps: Partial<chrome.contextMenus.CreateProperties> = {
1920
visible: true
2021
}
2122

23+
function titleOf(prefixEmoji: string, title: string) {
24+
if (IS_SAFARI) {
25+
// Emoji does not work in Safari's context menu
26+
return title
27+
} else {
28+
return `${prefixEmoji} ${title}`
29+
}
30+
}
31+
2232
const allFunctionProps: chrome.contextMenus.CreateProperties = {
2333
id: chrome.runtime.id + '_timer_menu_item_app_link',
24-
title: '🏷️ ' + t2Chrome(msg => msg.contextMenus.allFunctions),
34+
title: titleOf('🏷️', t2Chrome(msg => msg.contextMenus.allFunctions)),
2535
onclick: () => chrome.tabs.create({ url: APP_PAGE_URL }),
2636
...baseProps
2737
}
2838

2939
const optionPageProps: chrome.contextMenus.CreateProperties = {
3040
id: chrome.runtime.id + '_timer_menu_item_option_link',
31-
title: '🥰 ' + t2Chrome(msg => msg.contextMenus.optionPage),
41+
title: titleOf('🥰', t2Chrome(msg => msg.contextMenus.optionPage)),
3242
onclick: () => chrome.tabs.create({ url: APP_PAGE_URL + '#' + OPTION_ROUTE }),
3343
...baseProps
3444
}
3545

3646
const repoPageProps: chrome.contextMenus.CreateProperties = {
3747
id: chrome.runtime.id + '_timer_menu_item_repo_link',
38-
title: '🍻 ' + t2Chrome(msg => msg.contextMenus.repoPage),
48+
title: titleOf('🍻', t2Chrome(msg => msg.contextMenus.repoPage)),
3949
onclick: () => chrome.tabs.create({ url: SOURCE_CODE_PAGE }),
4050
...baseProps
4151
}
4252

4353
const feedbackPageProps: chrome.contextMenus.CreateProperties = {
4454
id: chrome.runtime.id + '_timer_menu_item_feedback_link',
45-
title: '😿 ' + t2Chrome(msg => msg.contextMenus.feedbackPage),
55+
title: titleOf('😿', t2Chrome(msg => msg.contextMenus.feedbackPage)),
4656
onclick: () => chrome.tabs.create({ url: TU_CAO_PAGE }),
4757
...baseProps
4858
}

0 commit comments

Comments
 (0)