Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
node_modules
dist
dist_dev
dist_dev_safari
dist_prod
dist_prod_safari
dist_analyze

Timer_Safari_DEV
Timer

firefox_dev

market_packages
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ Timer can
[![](https://img.shields.io/amo/rating/2690100?color=green)](https://addons.mozilla.org/en-US/firefox/addon/2690100)
[![Mozilla Add-on](https://img.shields.io/amo/users/2690100?color=green)](https://addons.mozilla.org/en-US/firefox/addon/2690100)

[How to install manually for Safari](./doc/safari-install.md)

![User Count](https://gist.githubusercontent.com/sheepzh/6aaf4c22f909db73b533491167da129b/raw/user_count.svg)

## Contribution
Expand Down
19 changes: 17 additions & 2 deletions doc/dev-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,25 @@ yarn run test
```
7. 提交代码,并 PR 主仓库的 main 分支

## 3. 应用架构设计
## 3. 在 Safari 里运行

1. 重复上述步骤 1-4
2. 编译兼容 Safari 的代码,替换上述步骤 5 的指令即可
```shell
npm run dev:safari
```
3. 使用 Xcode 内置工具 safari-web-extension-converter 将 Chrome 扩展转换成 Safari 扩展
```shell
[YOUR_PATH]/Xcode.app/Contents/Developer/usr/bin/safari-web-extension-converter dist_dev_safari
```
项目根目录下会生成一个文件夹 Timer_Safari_DEV,同时 Xcode 会自动打开该文件夹
4. 在 Xcode 里运行打开的项目即可

## 4. 应用架构设计

> todo

## 4. 目录结构
## 5. 目录结构

```plain
project
Expand Down Expand Up @@ -117,6 +131,7 @@ project
└───webpack # webpack 打包配置
| webpack.common.ts # 基础配置
| webpack.dev.ts # 开发环境配置
| webpack.dev.safari.ts # Safari 开发环境配置
| webpack.prod.ts # 生产配置

```
43 changes: 43 additions & 0 deletions doc/safari-install.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# How to install for Safari

This is a too poor developer to pay $99 per year for distribution of an opensource and free browser extension in Apple App Store.
So please intall it **manually**, GG Safari.

## 0. Download this repository

```shell
git clone https://github.com/sheepzh/timer.git
cd timer
```

## 1. Install tools

Some tools are required to compile this project to an executable software for Safari.

* [nodejs & npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
* [Xcode (compitable for your version of macOS)](https://developer.apple.com/xcode/)

## 2. Compile source code, install & run

There are serveral steps.

1. Compile the sourcecode programmed with TypeScript to js bundles.

```shell
# Install dependencies
npm install
# Compile
npm run build:safari
```
Then there will be one folder called **Timer**.

Also, you can download the archived file from [the release page](https://github.com/sheepzh/timer/releases), and unzip it to gain this folder.

2. Convert js bundles to Xcode project

```shell
[YOUR_PATH]/Xcode.app/Contents/Developer/usr/bin/safari-web-extension-converter ./Timer
```
3. Run Xcode project and one extension app will installed on your macOS
4. Enable this extension
5. Finnally, open your Safari
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"homepage": "https://github.com/sheepzh/timer",
"scripts": {
"dev": "webpack --config=webpack/webpack.dev.ts --watch",
"dev:safari": "webpack --config=webpack/webpack.dev.safari.ts --watch",
"build": "webpack --config=webpack/webpack.prod.ts",
"analyze": "webpack --config=webpack/webpack.analyze.ts",
"test": "jest --env=jsdom",
Expand Down
36 changes: 22 additions & 14 deletions src/app/components/common/host-alert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* https://opensource.org/licenses/MIT
*/

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

Expand Down Expand Up @@ -39,20 +40,27 @@ const _default = defineComponent({
const href: ComputedRef<string> = computed(() => props.clickable ? `http://${props.host}` : '')
const target: ComputedRef<string> = computed(() => props.clickable ? '_blank' : '')
const cursor: ComputedRef<string> = computed(() => props.clickable ? "cursor" : "default")
return () => h('div', [
h(ElLink,
{
href: href.value,
target: target.value,
underline: props.clickable,
style: { cursor: cursor.value }
},
() => props.host
), h('span',
{ style: HOST_ICON_STYLE },
h('img', { src: props.iconUrl, width: 12, height: 12 })
)
])
return IS_SAFARI
? () => h(ElLink, {
href: href.value,
target: target.value,
underline: props.clickable,
style: { cursor: cursor.value }
}, () => props.host)
: () => h('div', [
h(ElLink,
{
href: href.value,
target: target.value,
underline: props.clickable,
style: { cursor: cursor.value }
},
() => props.host
), h('span',
{ style: HOST_ICON_STYLE },
h('img', { src: props.iconUrl, width: 12, height: 12 })
)
])
}
})

Expand Down
50 changes: 31 additions & 19 deletions src/app/components/option/components/statistics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { defaultStatistics } from "@util/constant/option"
import { defineComponent, h, reactive, unref } from "vue"
import { t } from "@app/locale"
import { renderOptionItem, tagText, tooltip } from "../common"
import { IS_SAFARI } from "@util/constant/environment"

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

function renderOptionItems(option: timer.option.StatisticsOption) {
const result = []
if (!IS_SAFARI) {
// chrome.idle does not work in Safari, so not to display this option
result.push(
renderOptionItem({
input: countWhenIdle(option),
idleTime: tagText(msg => msg.option.statistics.idleTime),
info: tooltip(msg => msg.option.statistics.idleTimeInfo)
}, msg => msg.statistics.countWhenIdle, t(msg => msg.option.no)),
h(ElDivider)
)
}
result.push(
renderOptionItem({
input: countLocalFiles(option),
localFileTime: tagText(msg => msg.option.statistics.localFileTime),
info: tooltip(msg => msg.option.statistics.localFilesInfo)
}, msg => msg.statistics.countLocalFiles, t(msg => msg.option.no)),
h(ElDivider),
renderOptionItem({
input: collectSiteName(option),
siteName: tagText(msg => msg.option.statistics.siteName),
siteNameUsage: tooltip(msg => msg.option.statistics.siteNameUsage)
}, msg => msg.statistics.collectSiteName, t(msg => msg.option.yes))
)
return result
}

const _default = defineComponent({
name: "StatisticsOptionContainer",
setup(_props, ctx) {
Expand All @@ -51,25 +81,7 @@ const _default = defineComponent({
await optionService.setStatisticsOption(unref(option))
}
})
return () => h('div', [
renderOptionItem({
input: countWhenIdle(option),
idleTime: tagText(msg => msg.option.statistics.idleTime),
info: tooltip(msg => msg.option.statistics.idleTimeInfo)
}, msg => msg.statistics.countWhenIdle, t(msg => msg.option.no)),
h(ElDivider),
renderOptionItem({
input: countLocalFiles(option),
localFileTime: tagText(msg => msg.option.statistics.localFileTime),
info: tooltip(msg => msg.option.statistics.localFilesInfo)
}, msg => msg.statistics.countLocalFiles, t(msg => msg.option.no)),
h(ElDivider),
renderOptionItem({
input: collectSiteName(option),
siteName: tagText(msg => msg.option.statistics.siteName),
siteNameUsage: tooltip(msg => msg.option.statistics.siteNameUsage)
}, msg => msg.statistics.collectSiteName, t(msg => msg.option.yes))
])
return () => h('div', renderOptionItems(option))
}
})

Expand Down
3 changes: 2 additions & 1 deletion src/app/components/report/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { useRoute, useRouter } from "vue-router"
import { groupBy, sum } from "@util/array"
import { formatTime } from "@util/time"
import TimerDatabase from "@db/timer-database"
import { IS_SAFARI } from "@util/constant/environment"

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

Expand All @@ -38,7 +39,7 @@ async function queryData(
) {
const loading = ElLoadingService({ target: `.container-card>.el-card__body`, text: "LOADING..." })
const pageInfo = { size: page.size, num: page.num }
const fillFlag = { alias: true, iconUrl: true }
const fillFlag = { alias: true, iconUrl: !IS_SAFARI }
const param = {
...queryParam.value,
inclusiveRemote: readRemote.value
Expand Down
17 changes: 15 additions & 2 deletions src/background/badge-text-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,29 @@ async function updateFocus(host?: string) {
if (!host) {
host = await findActiveHost()
}
const milliseconds = host ? (await timerDb.get(host, new Date)).focus : undefined
const milliseconds = host ? (await timerDb.get(host, new Date())).focus : undefined
setBadgeText(milliseconds)
}

const ALARM_NAME = 'timer-badge-text-manager-alarm'
const ALARM_INTERVAL = 1000
function createAlarm(beforeAction?: () => void) {
beforeAction?.()
chrome.alarms.create(ALARM_NAME, { when: Date.now() + ALARM_INTERVAL })
}

class BadgeTextManager {
timer: NodeJS.Timer
isPaused: boolean

async init() {
this.timer = setInterval(() => !this.isPaused && updateFocus(), 1000)
createAlarm()
chrome.alarms.onAlarm.addListener(alarm => {
if (ALARM_NAME === alarm.name) {
createAlarm(() => !this.isPaused && updateFocus())
}
})
// this.timer = setInterval(() => !this.isPaused && updateFocus(), 1000)

const option: Partial<timer.option.AllOption> = await optionService.getAllOption()
this.pauseOrResumeAccordingToOption(!!option.displayBadgeText)
Expand Down
20 changes: 15 additions & 5 deletions src/background/browser-action-menu-manager.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/**
/**
* Copyright (c) 2021 Hengyang Zhang
*
* This software is released under the MIT License.
Expand All @@ -8,6 +8,7 @@
import { OPTION_ROUTE } from "../app/router/constants"
import { getAppPageUrl, SOURCE_CODE_PAGE, TU_CAO_PAGE } from "@util/constant/url"
import { t2Chrome } from "@util/i18n/chrome/t"
import { IS_SAFARI } from "@util/constant/environment"

const APP_PAGE_URL = getAppPageUrl(true)

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

function titleOf(prefixEmoji: string, title: string) {
if (IS_SAFARI) {
// Emoji does not work in Safari's context menu
return title
} else {
return `${prefixEmoji} ${title}`
}
}

const allFunctionProps: chrome.contextMenus.CreateProperties = {
id: chrome.runtime.id + '_timer_menu_item_app_link',
title: '🏷️ ' + t2Chrome(msg => msg.contextMenus.allFunctions),
title: titleOf('🏷️', t2Chrome(msg => msg.contextMenus.allFunctions)),
onclick: () => chrome.tabs.create({ url: APP_PAGE_URL }),
...baseProps
}

const optionPageProps: chrome.contextMenus.CreateProperties = {
id: chrome.runtime.id + '_timer_menu_item_option_link',
title: '🥰 ' + t2Chrome(msg => msg.contextMenus.optionPage),
title: titleOf('🥰', t2Chrome(msg => msg.contextMenus.optionPage)),
onclick: () => chrome.tabs.create({ url: APP_PAGE_URL + '#' + OPTION_ROUTE }),
...baseProps
}

const repoPageProps: chrome.contextMenus.CreateProperties = {
id: chrome.runtime.id + '_timer_menu_item_repo_link',
title: '🍻 ' + t2Chrome(msg => msg.contextMenus.repoPage),
title: titleOf('🍻', t2Chrome(msg => msg.contextMenus.repoPage)),
onclick: () => chrome.tabs.create({ url: SOURCE_CODE_PAGE }),
...baseProps
}

const feedbackPageProps: chrome.contextMenus.CreateProperties = {
id: chrome.runtime.id + '_timer_menu_item_feedback_link',
title: '😿 ' + t2Chrome(msg => msg.contextMenus.feedbackPage),
title: titleOf('😿', t2Chrome(msg => msg.contextMenus.feedbackPage)),
onclick: () => chrome.tabs.create({ url: TU_CAO_PAGE }),
...baseProps
}
Expand Down
6 changes: 3 additions & 3 deletions src/background/icon-and-alias-collector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import HostAliasDatabase from "@db/host-alias-database"
import IconUrlDatabase from "@db/icon-url-database"
import OptionDatabase from "@db/option-database"
import { IS_CHROME } from "@util/constant/environment"
import { IS_CHROME, IS_SAFARI } from "@util/constant/environment"
import { iconUrlOfBrowser } from "@util/constant/url"
import { extractHostname, isBrowserUrl, isHomepage } from "@util/pattern"
import { defaultStatistics } from "@util/constant/option"
Expand Down Expand Up @@ -51,7 +51,7 @@ async function processTabInfo(tab: chrome.tabs.Tab): Promise<void> {
// localhost hosts with Chrome use cache, so keep the favIcon url undefined
IS_CHROME && /^localhost(:.+)?/.test(host) && (favIconUrl = undefined)
const iconUrl = favIconUrl || iconUrlOfBrowser(protocol, host)
iconUrlDatabase.put(host, iconUrl)
iconUrl && iconUrlDatabase.put(host, iconUrl)
collectAliasEnabled && !isBrowserUrl(url) && isHomepage(url) && collectAlias(host, tab.title)
}

Expand All @@ -67,7 +67,7 @@ function handleWebNavigationCompleted(detail: chrome.webNavigation.WebNavigation
}

function listen() {
chrome.webNavigation.onCompleted.addListener(handleWebNavigationCompleted)
!IS_SAFARI && chrome.webNavigation.onCompleted.addListener(handleWebNavigationCompleted)
}

/**
Expand Down
6 changes: 5 additions & 1 deletion src/background/timer/idle-listener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* https://opensource.org/licenses/MIT
*/

import { IS_SAFARI } from "@util/constant/environment"
import { formatTime } from "@util/time"
import TimerContext from "./context"

Expand All @@ -24,6 +25,9 @@ export default class IdleListener {
}

listen() {
chrome.idle.onStateChanged.addListener(newState => listen(this.context, newState))
if (!IS_SAFARI) {
// Idle does not work in macOs
chrome.idle.onStateChanged.addListener(newState => listen(this.context, newState))
}
}
}
Loading