initial commit
This commit is contained in:
commit
7e466e29d2
28 changed files with 17360 additions and 0 deletions
26
.gitignore
vendored
Normal file
26
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.output
|
||||||
|
stats.html
|
||||||
|
stats-*.json
|
||||||
|
.wxt
|
||||||
|
web-ext.config.ts
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
5
README.md
Normal file
5
README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# let me focus
|
||||||
|
|
||||||
|
go back to your work!
|
||||||
|
|
||||||
|
reviving old project [let-me-focus-browser](https://github.com/cryw0rks/let-me-focus-browser)
|
||||||
BIN
assets/stop.png
Normal file
BIN
assets/stop.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
114
components/BlockedSite.vue
Normal file
114
components/BlockedSite.vue
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { BLOCKED_SITES_REPO_KEY } from "@/utils/proxy-service-keys";
|
||||||
|
import { BlockedSite } from "@/utils/types";
|
||||||
|
import { createProxyService } from '@webext-core/proxy-service';
|
||||||
|
import { v4 as uuidv4 } from "uuid"
|
||||||
|
|
||||||
|
const blockedSitesRepo = createProxyService(BLOCKED_SITES_REPO_KEY);
|
||||||
|
|
||||||
|
const props = defineProps<{}>()
|
||||||
|
|
||||||
|
const blockedSites = ref<BlockedSite[]>([]);
|
||||||
|
const inputNewSite = ref<string | null>();
|
||||||
|
|
||||||
|
const refreshBlockedSite = async () => {
|
||||||
|
blockedSites.value = await blockedSitesRepo.getAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeMount(async () => refreshBlockedSite())
|
||||||
|
|
||||||
|
const insertBlockedSite = async () => {
|
||||||
|
if (!inputNewSite.value) return
|
||||||
|
if (!inputNewSite.value.trim()) return
|
||||||
|
const generateTodoId = uuidv4()
|
||||||
|
await blockedSitesRepo.insert({
|
||||||
|
id: generateTodoId,
|
||||||
|
url: inputNewSite.value,
|
||||||
|
blocked: true
|
||||||
|
})
|
||||||
|
inputNewSite.value = null
|
||||||
|
refreshBlockedSite()
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateBlockedSite = async (blockedSite: BlockedSite) => {
|
||||||
|
await blockedSitesRepo.update(toRaw(blockedSite))
|
||||||
|
refreshBlockedSite()
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteBlockedSite = async (blockedSite: BlockedSite) => {
|
||||||
|
await blockedSitesRepo.delete(toRaw(blockedSite))
|
||||||
|
refreshBlockedSite()
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<h2>[blocked sites]</h2>
|
||||||
|
<div class="form-input">
|
||||||
|
<input @keydown.enter="insertBlockedSite" v-model="inputNewSite" placeholder="ba-ka.org" type="input"/>
|
||||||
|
<button @click="insertBlockedSite">[add site]</button>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<div v-if="blockedSites.length === 0">no blocked sites :(</div>
|
||||||
|
<ul v-else v-for="blockedSite in blockedSites">
|
||||||
|
<li :key="blockedSite.id">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" @change="updateBlockedSite(blockedSite)" v-model="blockedSite.blocked" :id="blockedSite.id"/>
|
||||||
|
{{ blockedSite.url }}
|
||||||
|
<button class="button-delete" @click="deleteBlockedSite(blockedSite)">x</button>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.container {
|
||||||
|
min-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
list-style: none;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 14pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
li label {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
li label:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
li .button-delete {
|
||||||
|
display: none;
|
||||||
|
padding: 1px 4px;
|
||||||
|
font-size: 10pt;
|
||||||
|
float: right
|
||||||
|
}
|
||||||
|
|
||||||
|
li:hover .button-delete {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input input,
|
||||||
|
.form-input button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 2px 4px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input input {
|
||||||
|
font-size: 12pt;
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-box-sizing:border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
140
components/Todo.vue
Normal file
140
components/Todo.vue
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { TODOS_REPO_KEY } from "@/utils/proxy-service-keys";
|
||||||
|
import { Todo } from "@/utils/types";
|
||||||
|
import { createProxyService } from '@webext-core/proxy-service';
|
||||||
|
import { v4 as uuidv4 } from "uuid"
|
||||||
|
|
||||||
|
const todosRepo = createProxyService(TODOS_REPO_KEY);
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
showCompleted?: boolean,
|
||||||
|
showOnlyCompleted?: boolean,
|
||||||
|
showInput?: boolean,
|
||||||
|
hoverShowDeleteButton?: boolean,
|
||||||
|
showClearAllTodo?: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const todos = ref<Todo[]>([]);
|
||||||
|
const inputNewTodo = ref<string | null>();
|
||||||
|
|
||||||
|
const refreshTodo = async () => {
|
||||||
|
const allTodos = await todosRepo.getAll();
|
||||||
|
todos.value = allTodos.filter(
|
||||||
|
(todo: Todo) =>
|
||||||
|
(props.showCompleted && todo.completed) ||
|
||||||
|
(props.showOnlyCompleted && todo.completed) ||
|
||||||
|
(!props.showOnlyCompleted && !todo.completed)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeMount(async () => refreshTodo())
|
||||||
|
|
||||||
|
const insertTodo = async () => {
|
||||||
|
if (!inputNewTodo.value) return
|
||||||
|
if (!inputNewTodo.value.trim()) return
|
||||||
|
const generateTodoId = uuidv4()
|
||||||
|
await todosRepo.insert({
|
||||||
|
id: generateTodoId,
|
||||||
|
name: inputNewTodo.value,
|
||||||
|
completed: false
|
||||||
|
})
|
||||||
|
inputNewTodo.value = null
|
||||||
|
refreshTodo()
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateTodo = async (todo: Todo) => {
|
||||||
|
await todosRepo.update(toRaw(todo))
|
||||||
|
refreshTodo()
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteTodo = async (todo: Todo) => {
|
||||||
|
await todosRepo.delete(toRaw(todo))
|
||||||
|
refreshTodo()
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearAllCompletedTodo = async () => {
|
||||||
|
const allTodos = await todosRepo.getAll()
|
||||||
|
const completedTodos = allTodos.filter((todo) => todo.completed)
|
||||||
|
const deletePromises = completedTodos.map((todo) => todosRepo.delete(todo))
|
||||||
|
await Promise.all(deletePromises)
|
||||||
|
refreshTodo()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="container">
|
||||||
|
<h2 v-if="props.showOnlyCompleted">[completed todos]</h2>
|
||||||
|
<h2 v-if="!props.showOnlyCompleted">[todos]</h2>
|
||||||
|
<div v-if="showInput" class="form-input">
|
||||||
|
<input @keydown.enter="insertTodo" v-model="inputNewTodo" placeholder="playing skateboard at 5am" type="input"/>
|
||||||
|
<button @click="insertTodo">[add todo]</button>
|
||||||
|
</div>
|
||||||
|
<div v-if="showClearAllTodo">
|
||||||
|
<button @click="clearAllCompletedTodo">[clear all completed todos]</button>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<div v-if="todos.length === 0">no todos :D</div>
|
||||||
|
<ul v-else v-for="todo in todos">
|
||||||
|
<li :class="{ completed: todo.completed }" :key="todo.id">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" @change="updateTodo(todo)" v-model="todo.completed" :id="todo.id"/>
|
||||||
|
{{ todo.name }}
|
||||||
|
<button class="button-delete" v-if="props.hoverShowDeleteButton" @click="deleteTodo(todo)">x</button>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.container {
|
||||||
|
min-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
list-style: none;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 14pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
li label {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
li label:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
li.completed {
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
li .button-delete {
|
||||||
|
display: none;
|
||||||
|
padding: 1px 4px;
|
||||||
|
font-size: 10pt;
|
||||||
|
float: right
|
||||||
|
}
|
||||||
|
|
||||||
|
li:hover .button-delete {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input input,
|
||||||
|
.form-input button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 2px 4px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input input {
|
||||||
|
font-size: 12pt;
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-box-sizing:border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
54
components/ToggleOnOff.vue
Normal file
54
components/ToggleOnOff.vue
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { featureEnableBlock } from "@/utils/storage"
|
||||||
|
|
||||||
|
const isEnabled = ref(false)
|
||||||
|
let unwatchStorage: (() => void | undefined)
|
||||||
|
|
||||||
|
onMounted(async() => {
|
||||||
|
isEnabled.value = await featureEnableBlock.getValue()
|
||||||
|
|
||||||
|
unwatchStorage = featureEnableBlock.watch((newValue) => {
|
||||||
|
if (newValue !== isEnabled.value) {
|
||||||
|
isEnabled.value = newValue
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (unwatchStorage) unwatchStorage();
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(isEnabled, async (newValue) => {
|
||||||
|
await featureEnableBlock.setValue(newValue)
|
||||||
|
})
|
||||||
|
|
||||||
|
const toggleOnOff = () => {
|
||||||
|
isEnabled.value = !isEnabled.value
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<button @click="toggleOnOff" class="button-toggle">
|
||||||
|
<img src="/icon/128.png" class="logo" :class="{ 'logo-on': isEnabled }" alt="let me focus logo" />
|
||||||
|
</button>
|
||||||
|
<br/>
|
||||||
|
{{ isEnabled ? '[on]' : '[off]' }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.button-toggle {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
.button-toggle .logo {
|
||||||
|
width: 80%;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-toggle .logo-on {
|
||||||
|
filter: drop-shadow(0 0 1em #ffffff);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
17
entrypoints/background.ts
Normal file
17
entrypoints/background.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { registerService } from "@webext-core/proxy-service";
|
||||||
|
import { createTodosRepo } from "@/utils/todo-repo";
|
||||||
|
import { createBlockedSitesRepo } from "@/utils/blocked-site-repo";
|
||||||
|
import { BLOCKED_SITES_REPO_KEY, TODOS_REPO_KEY } from "@/utils/proxy-service-keys";
|
||||||
|
import { openExtensionDatabase } from "@/utils/database";
|
||||||
|
|
||||||
|
export default defineBackground(() => {
|
||||||
|
console.log('[ba-ka] starting let me focus service extension', { id: browser.runtime.id });
|
||||||
|
|
||||||
|
const idbProimise = openExtensionDatabase();
|
||||||
|
|
||||||
|
const todosRepo = createTodosRepo(idbProimise)
|
||||||
|
registerService(TODOS_REPO_KEY, todosRepo)
|
||||||
|
|
||||||
|
const blockedSitesRepo = createBlockedSitesRepo(idbProimise)
|
||||||
|
registerService(BLOCKED_SITES_REPO_KEY, blockedSitesRepo)
|
||||||
|
});
|
||||||
42
entrypoints/content/App.vue
Normal file
42
entrypoints/content/App.vue
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import stopImage from "~/assets/stop.png";
|
||||||
|
import Todo from "@/components/Todo.vue";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="main">
|
||||||
|
<img class="logo" :src="stopImage" />
|
||||||
|
<h1 class="message">[go back to your work]</h1>
|
||||||
|
<Todo />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
font-family: monospace;
|
||||||
|
position: fixed;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: #222222;
|
||||||
|
color: #ffffff;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 99999;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.logo {
|
||||||
|
width: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1.message,
|
||||||
|
img.logo {
|
||||||
|
filter: drop-shadow(0 0 1em #ffffff);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
56
entrypoints/content/index.ts
Normal file
56
entrypoints/content/index.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { ContentScriptContext } from "#imports";
|
||||||
|
import App from "./App.vue";
|
||||||
|
import { featureEnableBlock } from "@/utils/storage";
|
||||||
|
import { createApp } from "vue";
|
||||||
|
import { createProxyService } from '@webext-core/proxy-service';
|
||||||
|
import { BlockedSite } from "@/utils/types";
|
||||||
|
const blockedSitesRepo = createProxyService(BLOCKED_SITES_REPO_KEY);
|
||||||
|
|
||||||
|
export default defineContentScript({
|
||||||
|
matches: ["*://*/*"],
|
||||||
|
cssInjectionMode: "ui",
|
||||||
|
|
||||||
|
async main(ctx) {
|
||||||
|
const ui = await defineOverlay(ctx);
|
||||||
|
const isBlockEnabled = await featureEnableBlock.getValue();
|
||||||
|
const currentDomain = window.location.hostname;
|
||||||
|
const blockedSites = await blockedSitesRepo.getAll()
|
||||||
|
|
||||||
|
function isOnBlockList() {
|
||||||
|
return (
|
||||||
|
blockedSites.filter(function (blockedSite: BlockedSite) {
|
||||||
|
return (blockedSite.url == currentDomain || "www." + blockedSite.url == currentDomain) && blockedSite.blocked;
|
||||||
|
}).length > 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function injectToWebsite() {
|
||||||
|
if (!isBlockEnabled || !isOnBlockList()) return;
|
||||||
|
console.log("[ba-ka] site blocked! please go back to your work lol");
|
||||||
|
ui.mount();
|
||||||
|
}
|
||||||
|
|
||||||
|
injectToWebsite();
|
||||||
|
|
||||||
|
ctx.addEventListener(window, "wxt:locationchange", (event) => {
|
||||||
|
injectToWebsite();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function defineOverlay(ctx: ContentScriptContext) {
|
||||||
|
return createShadowRootUi(ctx, {
|
||||||
|
name: "let-me-focus-block",
|
||||||
|
position: "overlay",
|
||||||
|
zIndex: 9999,
|
||||||
|
onMount(container, _shadow, shadowHost) {
|
||||||
|
const app = createApp(App);
|
||||||
|
app.mount(container);
|
||||||
|
shadowHost.style.pointerEvents = "none";
|
||||||
|
return app;
|
||||||
|
},
|
||||||
|
onRemove(app) {
|
||||||
|
app?.unmount();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
51
entrypoints/popup/App.vue
Normal file
51
entrypoints/popup/App.vue
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import ToggleOnOff from "@/components/ToggleOnOff.vue";
|
||||||
|
import Todo from "@/components/Todo.vue"
|
||||||
|
import BlockedSite from "@/components/BlockedSite.vue"
|
||||||
|
const currentMenu = ref("main")
|
||||||
|
const menuLists = [
|
||||||
|
"main",
|
||||||
|
"completed",
|
||||||
|
"blocked sites",
|
||||||
|
]
|
||||||
|
|
||||||
|
const changeMenu = (menu: string) => {
|
||||||
|
currentMenu.value = menu
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ToggleOnOff />
|
||||||
|
<h1>let me focus</h1>
|
||||||
|
<ul style="text-align: center;" v-for="menu in menuLists">
|
||||||
|
<li>
|
||||||
|
<button @click="changeMenu(menu)">[{{ menu }}]</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<Todo v-if="currentMenu === 'main'" showInput hoverShowDeleteButton />
|
||||||
|
<Todo v-if="currentMenu === 'completed'" showOnlyCompleted showClearAllTodo />
|
||||||
|
<BlockedSite v-if="currentMenu === 'blocked sites'" />
|
||||||
|
<br/>
|
||||||
|
[{{browser.runtime.getManifest().version}}]
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
ul {
|
||||||
|
padding: 0;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
li button {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
height: 6em;
|
||||||
|
filter: drop-shadow(0 0 1em #ffffff);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
13
entrypoints/popup/index.html
Normal file
13
entrypoints/popup/index.html
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>let me focus</title>
|
||||||
|
<meta name="manifest.type" content="browser_action" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="./main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
5
entrypoints/popup/main.ts
Normal file
5
entrypoints/popup/main.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { createApp } from 'vue';
|
||||||
|
import './style.css';
|
||||||
|
import App from './App.vue';
|
||||||
|
|
||||||
|
createApp(App).mount('#app');
|
||||||
42
entrypoints/popup/style.css
Normal file
42
entrypoints/popup/style.css
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
:root {
|
||||||
|
font-family: monospace;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-weight: 400;
|
||||||
|
color-scheme: light dark;
|
||||||
|
color: #ffffff;
|
||||||
|
background-color: #222222;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
min-width: 400px;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
:root {
|
||||||
|
color: #222222;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
}
|
||||||
16635
package-lock.json
generated
Normal file
16635
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
31
package.json
Normal file
31
package.json
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"name": "let-me-focus",
|
||||||
|
"description": "go back to your work!",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.1",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "wxt",
|
||||||
|
"dev:firefox": "wxt -b firefox",
|
||||||
|
"build": "wxt build",
|
||||||
|
"build:firefox": "wxt build -b firefox",
|
||||||
|
"zip": "wxt zip",
|
||||||
|
"zip:firefox": "wxt zip -b firefox",
|
||||||
|
"compile": "vue-tsc --noEmit",
|
||||||
|
"postinstall": "wxt prepare"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@webext-core/proxy-service": "^3.0.2",
|
||||||
|
"idb": "^8.0.3",
|
||||||
|
"uuid": "^14.0.1",
|
||||||
|
"vue": "^3.5.29"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@wxt-dev/module-vue": "^1.0.3",
|
||||||
|
"typescript": "^5.9.3",
|
||||||
|
"vite": "^8.1.5",
|
||||||
|
"vue-tsc": "^3.2.5",
|
||||||
|
"web-ext": "^5.5.0",
|
||||||
|
"wxt": "^0.20.27"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
public/icon/128.png
Normal file
BIN
public/icon/128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.4 KiB |
BIN
public/icon/16.png
Normal file
BIN
public/icon/16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 773 B |
BIN
public/icon/32.png
Normal file
BIN
public/icon/32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/icon/48.png
Normal file
BIN
public/icon/48.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
BIN
public/icon/96.png
Normal file
BIN
public/icon/96.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
3
tsconfig.json
Normal file
3
tsconfig.json
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"extends": "./.wxt/tsconfig.json"
|
||||||
|
}
|
||||||
35
utils/blocked-site-repo.ts
Normal file
35
utils/blocked-site-repo.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import type { BlockedSite } from "./types"
|
||||||
|
import type { ExtensionDatabase } from "./database"
|
||||||
|
|
||||||
|
export interface BlockedSitesRepo {
|
||||||
|
getAll(): Promise<BlockedSite[]>
|
||||||
|
getOne(id: string): Promise<BlockedSite | undefined>
|
||||||
|
insert(input: BlockedSite): Promise<void>
|
||||||
|
update(input: BlockedSite): Promise<void>
|
||||||
|
delete(input: BlockedSite): Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createBlockedSitesRepo(_db: Promise<ExtensionDatabase>): BlockedSitesRepo {
|
||||||
|
return {
|
||||||
|
async getAll() {
|
||||||
|
const db = await _db;
|
||||||
|
return await db.getAll("blocked_sites");
|
||||||
|
},
|
||||||
|
async getOne(id) {
|
||||||
|
const db = await _db;
|
||||||
|
return await db.get("blocked_sites", id);
|
||||||
|
},
|
||||||
|
async insert(input) {
|
||||||
|
const db = await _db;
|
||||||
|
await db.add("blocked_sites", input)
|
||||||
|
},
|
||||||
|
async update(input) {
|
||||||
|
const db = await _db;
|
||||||
|
await db.put("blocked_sites", input)
|
||||||
|
},
|
||||||
|
async delete(input) {
|
||||||
|
const db = await _db;
|
||||||
|
await db.delete("blocked_sites", input.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
utils/database.ts
Normal file
24
utils/database.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { DBSchema, IDBPDatabase, openDB } from "idb";
|
||||||
|
import { Todo, BlockedSite } from "./types";
|
||||||
|
|
||||||
|
interface ExtensionDatabaseScheme extends DBSchema {
|
||||||
|
todos: {
|
||||||
|
key: string;
|
||||||
|
value: Todo;
|
||||||
|
};
|
||||||
|
blocked_sites: {
|
||||||
|
key: string;
|
||||||
|
value: BlockedSite;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ExtensionDatabase = IDBPDatabase<ExtensionDatabaseScheme>;
|
||||||
|
|
||||||
|
export function openExtensionDatabase(): Promise<ExtensionDatabase> {
|
||||||
|
return openDB<ExtensionDatabaseScheme>("let-me-focus", 1, {
|
||||||
|
upgrade(database) {
|
||||||
|
database.createObjectStore("todos", { keyPath: "id" });
|
||||||
|
database.createObjectStore("blocked_sites", { keyPath: "id" });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
6
utils/proxy-service-keys.ts
Normal file
6
utils/proxy-service-keys.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import type { ProxyServiceKey } from "@webext-core/proxy-service";
|
||||||
|
import type { BlockedSitesRepo } from "./blocked-site-repo";
|
||||||
|
import type { TodosRepo } from "./todo-repo";
|
||||||
|
|
||||||
|
export const BLOCKED_SITES_REPO_KEY = 'blocked-sites-repo' as ProxyServiceKey<BlockedSitesRepo>;
|
||||||
|
export const TODOS_REPO_KEY = 'todos-repo' as ProxyServiceKey<TodosRepo>;
|
||||||
5
utils/storage.ts
Normal file
5
utils/storage.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { storage } from "#imports"
|
||||||
|
|
||||||
|
export const featureEnableBlock = storage.defineItem<boolean>("local:enableBlock", {
|
||||||
|
fallback: false
|
||||||
|
})
|
||||||
35
utils/todo-repo.ts
Normal file
35
utils/todo-repo.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import type { Todo } from "./types"
|
||||||
|
import type { ExtensionDatabase } from "./database"
|
||||||
|
|
||||||
|
export interface TodosRepo {
|
||||||
|
getAll(): Promise<Todo[]>
|
||||||
|
getOne(id: string): Promise<Todo | undefined>
|
||||||
|
insert(input: Todo): Promise<void>
|
||||||
|
update(input: Todo): Promise<void>
|
||||||
|
delete(input: Todo): Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createTodosRepo(_db: Promise<ExtensionDatabase>): TodosRepo {
|
||||||
|
return {
|
||||||
|
async getAll() {
|
||||||
|
const db = await _db;
|
||||||
|
return await db.getAll("todos");
|
||||||
|
},
|
||||||
|
async getOne(id) {
|
||||||
|
const db = await _db;
|
||||||
|
return await db.get("todos", id);
|
||||||
|
},
|
||||||
|
async insert(input) {
|
||||||
|
const db = await _db;
|
||||||
|
await db.add("todos", input)
|
||||||
|
},
|
||||||
|
async update(input) {
|
||||||
|
const db = await _db;
|
||||||
|
await db.put("todos", input)
|
||||||
|
},
|
||||||
|
async delete(input) {
|
||||||
|
const db = await _db;
|
||||||
|
await db.delete("todos", input.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
utils/types.ts
Normal file
11
utils/types.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
export interface Todo {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
completed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BlockedSite {
|
||||||
|
id: string;
|
||||||
|
url: string;
|
||||||
|
blocked: boolean;
|
||||||
|
}
|
||||||
10
wxt.config.ts
Normal file
10
wxt.config.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { defineConfig } from 'wxt';
|
||||||
|
|
||||||
|
// See https://wxt.dev/api/config.html
|
||||||
|
export default defineConfig({
|
||||||
|
modules: ['@wxt-dev/module-vue'],
|
||||||
|
manifest: {
|
||||||
|
name: "let me focus",
|
||||||
|
permissions: ['storage']
|
||||||
|
}
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue