@ -0,0 +1 @@ |
|||
.fdignore |
@ -1,318 +0,0 @@ |
|||
let p = console.log |
|||
import { err } from './utils' |
|||
|
|||
import db from './db' |
|||
import state from './state' |
|||
|
|||
import config from './config' |
|||
import { find, omit, orderBy } from 'lodash' |
|||
import { nanoid } from 'nanoid' |
|||
import fzi from 'fzi' |
|||
import { evaluate as eval_math } from 'mathjs' |
|||
import { cloneDeep } from 'lodash' |
|||
|
|||
export default new class api |
|||
|
|||
def add_link text |
|||
let link = await create_link_from_text text |
|||
link.id = nanoid! |
|||
await db.links.add link |
|||
await reload_db! |
|||
imba.commit! |
|||
p omit(link, "icon") |
|||
return link |
|||
|
|||
def update_link old_link, new_link_text |
|||
let new_link = await create_link_from_text new_link_text |
|||
new_link.frequency = old_link.frequency |
|||
let result = await db.links.update old_link.id, new_link |
|||
throw "Link id not found." if result is 0 |
|||
await reload_db! |
|||
imba.commit! |
|||
p omit(old_link, "icon") |
|||
p omit(new_link, "icon") |
|||
return new_link |
|||
|
|||
def put_link link |
|||
try |
|||
await db.links.update link.id, link |
|||
if link.is_bang and config.data.default_bang.id is link.id |
|||
config.set_default_bang link |
|||
await reload_db! |
|||
catch e |
|||
err "putting link", e |
|||
|
|||
def delete_link link |
|||
def go |
|||
try |
|||
await db.links.delete(link.id) |
|||
catch e |
|||
return err "deleting link", e |
|||
try |
|||
await reload_db! |
|||
catch e |
|||
return err "reloading db after successful delete", e |
|||
state.loading = yes |
|||
await go! |
|||
state.link_selection_index = Math.min state.link_selection_index, state.sorted_links.length - 1 |
|||
state.loading = no |
|||
|
|||
def pin_link link |
|||
link.is_pinned = !link.is_pinned |
|||
try |
|||
let result = await db.links.update link.id, link |
|||
throw "Link id not found." if result is 0 |
|||
catch e |
|||
return err "pinning link", e |
|||
await reload_db! |
|||
imba.commit! |
|||
|
|||
def reload_db |
|||
state.links = await db.links.toArray() |
|||
if state.active_bang |
|||
let id = state.active_bang.id |
|||
state.active_bang = find state.links, { id } |
|||
let id = config.data.default_bang.id |
|||
let link = find state.links, { id } |
|||
if link |
|||
config.data.default_bang = link |
|||
config.save! |
|||
sort_links! |
|||
|
|||
def increment_link_frequency link |
|||
link.frequency += 1 |
|||
try |
|||
await put_link link |
|||
catch e |
|||
err "putting link", e |
|||
|
|||
def sort_links |
|||
if state.query.trim!.length <= 0 |
|||
state.sorted_links = orderBy(state.links, ['is_pinned', 'frequency'], ['desc', 'desc']) |
|||
elif config.data.enable_effective_names |
|||
state.sorted_links = fzi.sort state.query, state.links, do |x| x.name |
|||
else |
|||
state.sorted_links = fzi.sort state.query, state.links, do |x| x.display_name |
|||
|
|||
def name_exists new_name |
|||
state.links.some! do |{name}| new_name is name |
|||
|
|||
def add_initial_links |
|||
let initial_links = [ |
|||
"tutorial github.com/familyfriendlymikey/fuzzyhome" |
|||
"!brave search `b search.brave.com/search?q=" |
|||
"!youtube youtube.com/results?search_query=" |
|||
"photopea photopea.com" |
|||
"twitch twitch.tv" |
|||
"messenger `me messenger.com" |
|||
"instagram `in instagram.com" |
|||
"localhost `3000 http://localhost:3000" |
|||
] |
|||
for link_text in initial_links |
|||
try |
|||
add_link link_text |
|||
catch e |
|||
err "adding link", e |
|||
|
|||
def create_link_from_text text, get_icon=yes |
|||
text = text.trim! |
|||
throw "Text is empty." if text is '' |
|||
let split_text = text.split(/\s+/) |
|||
throw "No url provided." if split_text.length < 2 |
|||
let url = split_text.pop! |
|||
let host |
|||
{ href:url, host } = parse_url url |
|||
let name |
|||
if split_text[-1].startsWith "`" |
|||
name = split_text.pop!.slice(1) |
|||
let display_name = split_text.join(" ") |
|||
let is_bang = no |
|||
let is_pinned = no |
|||
if display_name.startsWith "!" |
|||
is_bang = yes |
|||
display_name = display_name.slice(1) |
|||
name ||= display_name |
|||
let link = { name, display_name, is_bang, is_pinned, url, frequency:0, history:[] } |
|||
if get_icon |
|||
link.icon = await fetch_image_as_base_64 host |
|||
return link |
|||
|
|||
def fetch_image_as_base_64 host |
|||
let fallback = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAH0lEQVR42mO8seXffwYqAsZRA0cNHDVw1MBRA0eqgQCDRkbJSQHxEQAAAABJRU5ErkJggg==' |
|||
return new Promise! do |resolve| |
|||
let res |
|||
try |
|||
res = await global.fetch("https://icon.horse/icon/{host}") |
|||
catch |
|||
p "Failed to get icon from icon horse." |
|||
return resolve fallback |
|||
# todo: can i use .text() on this or something |
|||
let blob = await res.blob! |
|||
let reader = new FileReader! |
|||
reader.onload = do |
|||
resolve this.result |
|||
reader.onerror = do |
|||
p "Failed to get data from reader." |
|||
resolve fallback |
|||
return |
|||
reader.readAsDataURL(blob) |
|||
|
|||
def toggle_effective_names |
|||
config.data.enable_effective_names = !config.data.enable_effective_names |
|||
config.save! |
|||
sort_links! |
|||
|
|||
def construct_link_text link |
|||
link.display_name = link.display_name.trim! |
|||
link.name = link.name.trim! |
|||
link.url = link.url.trim! |
|||
let link_text = "" |
|||
link_text += "!" if link.is_bang |
|||
link_text += link.display_name |
|||
link_text += " `{link.name}" if link.name isnt link.display_name |
|||
link_text += " {link.url}" |
|||
link_text |
|||
|
|||
def parse_url url |
|||
throw "invalid url" if url === null |
|||
let get_url = do |s| |
|||
let url = new URL s |
|||
throw _ unless (url.host and url.href) |
|||
url |
|||
try |
|||
return get_url url |
|||
try |
|||
return get_url "https://{url}" |
|||
throw "invalid url" |
|||
|
|||
def get_pretty_date |
|||
Date!.toString!.split(" ").slice(0, 4).join(" ") |
|||
|
|||
get selected_link |
|||
state.sorted_links[state.link_selection_index] |
|||
|
|||
def set_link_selection_index index |
|||
state.link_selection_index = index |
|||
|
|||
def increment_link_selection_index |
|||
set_link_selection_index Math.min(state.sorted_links.length - 1, state.link_selection_index + 1) |
|||
|
|||
def decrement_link_selection_index |
|||
set_link_selection_index Math.max(0, state.link_selection_index - 1) |
|||
|
|||
def navigate link |
|||
await increment_link_frequency link |
|||
window.location.href = link.url |
|||
|
|||
get math_result |
|||
try |
|||
let result = Number(eval_math state.query) |
|||
throw _ if isNaN result |
|||
throw _ if result.toString! is state.query.trim! |
|||
result |
|||
catch |
|||
no |
|||
|
|||
def handle_cut e |
|||
return unless e.target.selectionStart == e.target.selectionEnd |
|||
let s = math_result |
|||
s ||= state.query |
|||
await window.navigator.clipboard.writeText(s) |
|||
state.query = '' |
|||
sort_links! |
|||
|
|||
def handle_add_link |
|||
state.loading = yes |
|||
try |
|||
await add_link state.query |
|||
state.query = '' |
|||
sort_links! |
|||
catch e |
|||
err "adding link", e |
|||
state.loading = no |
|||
|
|||
def handle_click_link |
|||
let link = selected_link |
|||
if link.is_bang |
|||
state.query = '' |
|||
state.active_bang = link |
|||
else |
|||
navigate link |
|||
|
|||
get bang |
|||
state.active_bang or config.data.default_bang |
|||
|
|||
get encoded_bang_query |
|||
"{bang.url}{window.encodeURIComponent(state.query)}" |
|||
|
|||
get encoded_bang_query_nourl |
|||
"{window.encodeURIComponent(state.query)}" |
|||
|
|||
def update_history bang |
|||
let text |
|||
if state.bang_selection_index > -1 |
|||
text = sorted_bang_history.splice(state.bang_selection_index, 1)[0] |
|||
text ||= state.query.trim! |
|||
return unless text |
|||
let i = bang.history.indexOf(text) |
|||
if i > -1 |
|||
bang.history.splice(i, 1) |
|||
bang.history.unshift text |
|||
try |
|||
await put_link bang |
|||
catch e |
|||
err "updating bang history", e |
|||
|
|||
def delete_bang_history_item |
|||
let text = sorted_bang_history[state.bang_selection_index] |
|||
return unless text |
|||
let i = bang.history.indexOf(text) |
|||
return unless i > -1 |
|||
bang.history.splice(i, 1) |
|||
try |
|||
await put_link bang |
|||
state.bang_selection_index = Math.min state.bang_selection_index, sorted_bang_history.length - 1 |
|||
catch e |
|||
err "updating bang history", e |
|||
|
|||
def handle_bang |
|||
return if state.loading |
|||
if state.bang_selection_index > -1 |
|||
state.query = sorted_bang_history[state.bang_selection_index] |
|||
state.bang_selection_index = -1 |
|||
return |
|||
await increment_link_frequency bang |
|||
await update_history bang |
|||
window.location.href = encoded_bang_query |
|||
|
|||
def unset_active_bang |
|||
state.active_bang = no |
|||
sort_links! |
|||
|
|||
def increment_bang_selection_index |
|||
state.bang_selection_index = Math.min(sorted_bang_history.length - 1, state.bang_selection_index + 1) |
|||
|
|||
def decrement_bang_selection_index |
|||
state.bang_selection_index = Math.max(-1, state.bang_selection_index - 1) |
|||
|
|||
get sorted_bang_history |
|||
fzi.sort state.query, bang.history |
|||
|
|||
def delete_bang_history |
|||
bang.history = [] |
|||
try |
|||
await put_link bang |
|||
state.bang_selection_index = -1 |
|||
catch e |
|||
err "deleting bang history", e |
|||
config.data.default_bang.history = [] |
|||
config.save! |
|||
|
|||
def delete_all_bang_history |
|||
return unless window.confirm "Are you sure you want to delete all bang history?" |
|||
try |
|||
await db.links.toCollection!.modify! do |link| link.history = [] |
|||
await reload_db! |
|||
catch e |
|||
err "deleting some link histories", e |
|||
imba.commit! |
@ -1,14 +0,0 @@ |
|||
export default """ |
|||
!google search google.com/search?q= |
|||
!youtube search youtube.com/results?search_query= |
|||
!brave search search.brave.com/search?q= |
|||
!amazon search amazon.com/s?k= |
|||
!google site:reddit google.com/search?q=site%3Areddit.com%20 |
|||
!google site:stackoverflow google.com/search?q=site%3Astackoverflow.com%20 |
|||
twitch twitch.tv |
|||
instagram instagram.com |
|||
messenger messenger.com |
|||
photopea photopea.com |
|||
youtube youtube.com |
|||
localhost:3000 http://localhost:3000 |
|||
""".split '\n' |
Before Width: | Height: | Size: 460 B |
@ -1,115 +0,0 @@ |
|||
let p = console.log |
|||
import pkg from '../package.json' |
|||
let version = pkg.version |
|||
p "fuzzyhome version {version}" |
|||
|
|||
# import sw from './sw.imba?serviceworker' |
|||
# navigator..serviceWorker..register(sw).then! do |reg| reg.update! |
|||
|
|||
import { nanoid } from 'nanoid' |
|||
import { err } from './utils' |
|||
|
|||
import db from './db' |
|||
import state from './state' |
|||
let refs = {} |
|||
import api from './api' |
|||
import config from './config' |
|||
|
|||
import './components/app-home' |
|||
import './components/app-community-links' |
|||
import './components/app-settings' |
|||
import './components/app-prompt' |
|||
import './components/app-edit' |
|||
import './components/app-links' |
|||
import './components/app-link' |
|||
import './components/app-bang' |
|||
import './components/app-tips' |
|||
import './styles' |
|||
|
|||
extend tag element |
|||
get state |
|||
state |
|||
get api |
|||
api |
|||
get config |
|||
config |
|||
get refs |
|||
refs |
|||
get err |
|||
err |
|||
get version |
|||
version |
|||
get p |
|||
console.log |
|||
|
|||
tag app |
|||
|
|||
fatal_error = no |
|||
|
|||
get render? do mounted? |
|||
|
|||
def mount |
|||
|
|||
refs.settings = $as |
|||
refs.edit = $ae |
|||
refs.community-links = $acl |
|||
refs.links = $ah |
|||
|
|||
unless global.localStorage.fuzzyhome_visited |
|||
await api.add_initial_links! |
|||
try |
|||
let default_bang = await api.add_link '!google search https://www.google.com/search?q=' |
|||
config.data.default_bang = default_bang |
|||
global.localStorage.fuzzyhome_visited = yes |
|||
|
|||
try |
|||
await api.reload_db! |
|||
p "links:", state.links |
|||
catch e |
|||
err "state.loading database", e |
|||
fatal_error = yes |
|||
return |
|||
|
|||
def render |
|||
|
|||
<self |
|||
.light=(config.theme is "light") |
|||
.dark=(config.theme is "dark") |
|||
.disabled=state.loading |
|||
> |
|||
css d:flex fld:column jc:start ai:center |
|||
m:0 w:100% h:100% bg:$bodybg |
|||
ff:sans-serif fw:1 |
|||
user-select:none |
|||
|
|||
<.main> |
|||
css d:flex fld:column jc:start ai:center |
|||
bg:$appbg |
|||
w:80vw max-width:700px max-height:80vh |
|||
bxs:0px 0px 10px rgba(0,0,0,0.35) |
|||
box-sizing:border-box p:30px rd:10px mt:10vh |
|||
|
|||
if fatal_error |
|||
<.fatal> |
|||
css c:$text-c |
|||
""" |
|||
There was an error state.loading the database. |
|||
This could be due to a user setting |
|||
disallowing local storage, or a random error. |
|||
Consider refreshing. |
|||
Check developer console for more information. |
|||
""" |
|||
|
|||
elif $acl.active |
|||
<app-community-links$acl> |
|||
|
|||
elif $as.active |
|||
<app-settings$as> |
|||
|
|||
elif $ae.active |
|||
<app-edit$ae> |
|||
|
|||
else |
|||
<app-home$ah> |
|||
|
|||
imba.mount <app> |
@ -1,129 +0,0 @@ |
|||
tag app-bang |
|||
|
|||
def unmount |
|||
state.bang_selection_index = -1 |
|||
|
|||
get tips |
|||
let result = [] |
|||
let temp |
|||
|
|||
temp = { |
|||
click_handler: api.handle_bang.bind(api) |
|||
hotkey_handler: api.handle_bang.bind(api) |
|||
hotkey: 'return' |
|||
hotkey_display_name: "Return" |
|||
} |
|||
if state.bang_selection_index > -1 |
|||
temp.content = "Use History Item" |
|||
else |
|||
temp.content = "Search" |
|||
result.push temp |
|||
|
|||
temp = { |
|||
click_handler: api.handle_add_link.bind(api) |
|||
hotkey_handler: api.handle_add_link.bind(api) |
|||
hotkey: 'shift+return' |
|||
hotkey_display_name: 'Shift + Return' |
|||
content: "Create Link \"{state.query.trim!}\"" |
|||
} |
|||
result.push temp |
|||
|
|||
if state.bang_selection_index > -1 |
|||
temp = { |
|||
click_handler: api.delete_bang_history_item.bind(api) |
|||
hotkey_handler: api.delete_bang_history_item.bind(api) |
|||
hotkey: 'shift+backspace' |
|||
hotkey_display_name: "Shift + Backspace" |
|||
content: "Delete History Item" |
|||
} |
|||
result.push temp |
|||
|
|||
if state.active_bang |
|||
temp = { |
|||
click_handler: api.unset_active_bang.bind(api) |
|||
hotkey_handler: api.unset_active_bang.bind(api) |
|||
hotkey: 'esc' |
|||
hotkey_display_name: "Esc" |
|||
content: "Back" |
|||
} |
|||
result.push temp |
|||
|
|||
def handle_delete_bang_history |
|||
api.delete_bang_history! |
|||
$tips.show_more = no |
|||
temp = { |
|||
click_handler: handle_delete_bang_history |
|||
hotkey_display_name: "Click" |
|||
content: "Delete Bang History" |
|||
} |
|||
result.push temp |
|||
|
|||
temp = { |
|||
click_handler: api.handle_cut.bind(api) |
|||
} |
|||
if api.math_result |
|||
temp.hotkey_display_name = "Cut (If No Selection)" |
|||
temp.content = "Cut All Text" |
|||
else |
|||
temp.hotkey_display_name = "Cut (Math, If No Selection)" |
|||
temp.content = "Cut Math Result" |
|||
result.push temp |
|||
|
|||
temp = { |
|||
hotkey_display_name: "Paste (If Input Empty)" |
|||
content: "Instant Search" |
|||
} |
|||
result.push temp |
|||
|
|||
result |
|||
|
|||
def render |
|||
|
|||
<self |
|||
@hotkey("tab").force=api.increment_bang_selection_index |
|||
@hotkey("up").force=api.decrement_bang_selection_index |
|||
@hotkey("down").force=api.increment_bang_selection_index |
|||
@hotkey("shift+tab").force=api.decrement_bang_selection_index |
|||
> |
|||
css w:100% d:flex fld:column gap:15px ofy:hidden |
|||
|
|||
<app-tips$tips tips=tips> |
|||
|
|||
unless $tips.show_more |
|||
|
|||
<.bang |
|||
.selected=(state.bang_selection_index is -1) |
|||
[c:$bang-c]=(state.bang_selection_index is -1) |
|||
@pointerover=(state.bang_selection_index = -1) |
|||
@click=api.handle_bang |
|||
> |
|||
css d:flex fld:row jc:space-between ai:center |
|||
px:16px py:11px rd:5px cursor:pointer c:$text-c |
|||
|
|||
<.link-left> |
|||
css d:flex fl:1 ofy:hidden |
|||
|
|||
<img.link-icon src=api.bang.icon> |
|||
css w:20px h:20px mr:10px rd:3px |
|||
|
|||
<.display-name> "...{api.encoded_bang_query_nourl}" |
|||
css fs:20px of:hidden text-overflow:ellipsis |
|||
|
|||
<.link-right> |
|||
css d:flex fld:row jc:space-between ai:center |
|||
|
|||
<.frequency> api.bang.frequency |
|||
css fs:15px ml:7px |
|||
|
|||
<.history> |
|||
css d:flex fld:column jc:start ai:center ofy:auto |
|||
|
|||
for item, index in api.sorted_bang_history |
|||
<.item |
|||
@pointerover=(state.bang_selection_index = index) |
|||
@click=api.handle_bang |
|||
[c:$bang-c]=(state.bang_selection_index is index) |
|||
.selected=(state.bang_selection_index is index) |
|||
> item |
|||
css w:100% fs:17px c:$text-c rd:5px p:10px 10px |
|||
box-sizing:border-box cursor:pointer |
@ -1,131 +0,0 @@ |
|||
let p = console.log |
|||
import all_links from '../assets/community_links' |
|||
import fzi from 'fzi' |
|||
import api from '../api' |
|||
|
|||
tag app-community-links |
|||
|
|||
active = no |
|||
selection_index = 0 |
|||
query = "" |
|||
|
|||
get render? do mounted? |
|||
|
|||
def mount |
|||
$cli.focus! |
|||
links = await get_links! |
|||
render! |
|||
|
|||
def get_links |
|||
let result = [] |
|||
for link_text in all_links |
|||
let link = await api.create_link_from_text(link_text, no) |
|||
link.link_text = link_text |
|||
result.push link |
|||
result |
|||
|
|||
get filtered_links |
|||
links.filter! do |link| !api.name_exists(link.name) |
|||
|
|||
get sorted_links |
|||
fzi.sort query, filtered_links, do |x| x.name |
|||
|
|||
get selected_link |
|||
sorted_links[selection_index] |
|||
|
|||
def open |
|||
active = yes |
|||
|
|||
def close |
|||
active = no |
|||
|
|||
def increment_selection_index |
|||
selection_index = Math.min(links.length - 1, selection_index + 1) |
|||
|
|||
def decrement_selection_index |
|||
selection_index = Math.max(0, selection_index - 1) |
|||
|
|||
def add_community_link |
|||
return if state.loading |
|||
state.loading = yes |
|||
try |
|||
await api.add_link selected_link.link_text |
|||
catch e |
|||
err "adding link", e |
|||
state.loading = no |
|||
|
|||
get tips |
|||
let result = [] |
|||
let temp |
|||
|
|||
temp = { |
|||
click_handler: close.bind(this) |
|||
hotkey_handler: close.bind(this) |
|||
hotkey: "esc" |
|||
hotkey_display_name: "Esc" |
|||
content: "Exit Community Links" |
|||
} |
|||
result.push temp |
|||
|
|||
temp = { |
|||
click_handler: add_community_link.bind(this) |
|||
hotkey_handler: add_community_link.bind(this) |
|||
hotkey: "shift+return" |
|||
hotkey_display_name: "Shift + Return Or Click" |
|||
content: "Add To Your Links" |
|||
} |
|||
result.push temp |
|||
|
|||
temp = { |
|||
click_handler: increment_selection_index.bind(this) |
|||
hotkey_handler: increment_selection_index.bind(this) |
|||
hotkey: 'down' |
|||
hotkey_display_name: "Down Arrow" |
|||
content: "Move Selection Down" |
|||
} |
|||
result.push temp |
|||
|
|||
temp = { |
|||
click_handler: decrement_selection_index.bind(this) |
|||
hotkey_handler: decrement_selection_index.bind(this) |
|||
hotkey: 'up' |
|||
hotkey_display_name: "Up Arrow" |
|||
content: "Move Selection Up" |
|||
} |
|||
result.push temp |
|||
|
|||
result |
|||
|
|||
def render |
|||
|
|||
<self> |
|||
css d:flex fld:column jc:start gap:15px fl:1 w:100% ofy:hidden |
|||
|
|||
<div> |
|||
<input$cli |
|||
autofocus |
|||
bind=query |
|||
> |
|||
|
|||
<app-tips tips=tips> |
|||
|
|||
<.links> |
|||
css ofy:auto |
|||
|
|||
for link, index in sorted_links |
|||
<.link |
|||
.selected=(selection_index == index) |
|||
@pointerover=(selection_index = index) |
|||
@click=add_community_link |
|||
> |
|||
css d:flex fld:row jc:space-between ai:center px:16px |
|||
py:2px rd:5px cursor:pointer c:$text-c min-height:35px |
|||
|
|||
if link.is_bang |
|||
css c:$bang-c |
|||
|
|||
<.link-left> link.name |
|||
css fl:1 |
|||
|
|||
<link-right.ellipsis> link.url |
|||
css fl:1 c:inherit |
@ -1,84 +0,0 @@ |
|||
tag app-edit |
|||
|
|||
active = no |
|||
|
|||
def mount |
|||
$dn.setSelectionRange 0, 0 |
|||
$dn.focus! |
|||
|
|||
def open data |
|||
link = data |
|||
new_link_text = value=api.construct_link_text(link) |
|||
active = yes |
|||
|
|||
def close |
|||
active = no |
|||
|
|||
def handle_click_set_default_bang |
|||
config.set_default_bang link |
|||
close! |
|||
|
|||
def handle_delete |
|||
try |
|||
await api.delete_link link |
|||
close! |
|||
catch e |
|||
err "deleting link", e |
|||
|
|||
def save |
|||
try |
|||
api.update_link link, new_link_text |
|||
close! |
|||
catch e |
|||
err "saving link", e |
|||
|
|||
get tips |
|||
let result = [] |
|||
let temp |
|||
|
|||
temp = { |
|||
click_handler: handle_delete.bind(this) |
|||
hotkey_handler: handle_delete.bind(this) |
|||
hotkey: 'shift+backspace' |
|||
hotkey_display_name: "Shift + Backspace" |
|||
content: "Delete Link" |
|||
} |
|||
result.push temp |
|||
|
|||
temp = { |
|||
click_handler: save.bind(this) |
|||
hotkey_handler: save.bind(this) |
|||
hotkey: 'return' |
|||
hotkey_display_name: "Return" |
|||
content: "Update Link" |
|||
} |
|||
result.push temp |
|||
|
|||
if link.is_bang |
|||
temp = { |
|||
click_handler: handle_click_set_default_bang.bind(this) |
|||
hotkey_display_name: "Click" |
|||
content: "Set Default Bang" |
|||
} |
|||
result.push temp |
|||
|
|||
temp = { |
|||
click_handler: close.bind(this) |
|||
hotkey_handler: close.bind(this) |
|||
hotkey: 'esc' |
|||
hotkey_display_name: "Esc" |
|||
content: "Cancel" |
|||
} |
|||
result.push temp |
|||
|
|||
result |
|||
|
|||
def render |
|||
|
|||
<self> |
|||
css d:flex fld:column gap:20px w:100% |
|||
|
|||
<div> |
|||
<input$dn autofocus bind=new_link_text> |
|||
|
|||
<app-tips tips=tips> |
@ -1,73 +0,0 @@ |
|||
tag app-home |
|||
|
|||
def mount |
|||
$home-input.focus! |
|||
|
|||
def blur |
|||
setTimeout(&, 100) do $home-input.focus! |
|||
|
|||
def handle_paste e |
|||
return unless config.data.enable_search_on_paste |
|||
return if state.query.length > 0 |
|||
global.setTimeout(&, 0) do |
|||
return if api.math_result isnt no |
|||
bang ||= config.data.default_bang |
|||
api.handle_bang! |
|||
|
|||
def handle_click_copy s |
|||
try |
|||
await window.navigator.clipboard.writeText(s) |
|||
state.query = '' |
|||
api.sort_links! |
|||
|
|||
def handle_input |
|||
state.bang_selection_index = -1 |
|||
api.set_link_selection_index 0 |
|||
api.sort_links! |
|||
|
|||
def render |
|||
|
|||
<self> |
|||
css w:100% d:flex fld:column ofy:hidden gap:20px |
|||
|
|||
<.header> |
|||
css d:flex fld:row w:100% |
|||
css .side c:$button-c fs:15px d:flex ja:center w:30px cursor:pointer |
|||
css .side svg w:15px d:flex |
|||
css .left jc:left |
|||
css .right jc:right |
|||
|
|||
<.side.left@click=api.toggle_effective_names> |
|||
|
|||
if config.data.enable_effective_names |
|||
<svg src="../assets/eye.svg"> |
|||
|
|||
else |
|||
<svg src="../assets/eye-off.svg"> |
|||
|
|||
<input$home-input |
|||
autofocus |
|||
bind=state.query |
|||
@input=handle_input |
|||
@paste=handle_paste |
|||
@cut=api.handle_cut |
|||
disabled=state.loading |
|||
@blur=blur |
|||
> |
|||
if state.query.startsWith "!" |
|||
css c:$bang-c |
|||
|
|||
if (let m = api.math_result) isnt no |
|||
<.side.right@click=handle_click_copy(m)> |
|||
"= {Math.round(m * 100)/100}" |
|||
css c:$text-c fs:20px ml:10px w:unset |
|||
|
|||
else |
|||
<.side.right @click=refs.settings.open> |
|||
<svg src="../assets/settings.svg"> |
|||
|
|||
if state.active_bang or state.sorted_links.length < 1 |
|||
<app-bang> |
|||
|
|||
else |
|||
<app-links> |
@ -1,67 +0,0 @@ |
|||
tag app-link |
|||
|
|||
def handle_delete link |
|||
return unless window.confirm "Do you really want to delete {link..display_name}?" |
|||
api.delete_link link |
|||
|
|||
def handle_pin link |
|||
api.pin_link link |
|||
|
|||
def render |
|||
<self |
|||
@pointerover=api.set_link_selection_index(index) |
|||
@click=api.handle_click_link |
|||
.selected=(index is state.link_selection_index) |
|||
> |
|||
css d:flex fld:row jc:space-between ai:center |
|||
px:16px py:11px rd:5px cursor:pointer c:$text-c |
|||
if link.is_bang |
|||
css c:$bang-c |
|||
|
|||
<.link-left> |
|||
css d:flex fl:3 |
|||
|
|||
<img.link-icon src=link.icon> |
|||
css w:20px h:20px mr:10px rd:3px |
|||
|
|||
<.display-name> link.display_name |
|||
css tt:capitalize fs:20px overflow-wrap:anywhere |
|||
|
|||
if link.display_name isnt link.name and config.data.enable_effective_names |
|||
<.name> |
|||
css d:flex ja:center c:$effective-name-c ml:10px fs:14px |
|||
css .parens fs:10px c:$effective-name-parens-c |
|||
|
|||
<span.parens> "(" |
|||
<span> link.name |
|||
<span.parens> ")" |
|||
|
|||
<.link-right> |
|||
css fl:1 d:flex fld:row jc:space-between ai:center |
|||
|
|||
css .buttons-disabled .link-button visibility:hidden |
|||
css .selected .link-button visibility:visible |
|||
|
|||
<.link-buttons .buttons-disabled=!config.data.enable_buttons> |
|||
css d:flex fld:row jc:start ai:center gap:5px |
|||
|
|||
css .link-button visibility:hidden rd:3px c:$button-c fs:15px cursor:pointer px:3px |
|||
if index is state.link_selection_index |
|||
css .link-button visibility:visible |
|||
|
|||
css .link-button svg w:15px |
|||
|
|||
<.link-button@click.prevent.stop=handle_edit> |
|||
<svg src='../assets/edit-2.svg'> |
|||
|
|||
<.link-button@click.prevent.stop=handle_delete(link)> |
|||
<svg src='../assets/trash.svg'> |
|||
|
|||
<.link-button @click.prevent.stop=handle_pin(link)> |
|||
if link.is_pinned |
|||
css visibility:visible c:$button-dim-c |
|||
|
|||
<svg src='../assets/star.svg'> |
|||
|
|||
<.frequency> link.frequency |
|||
css fs:15px ml:7px |
@ -1,104 +0,0 @@ |
|||
tag app-links |
|||
|
|||
def handle_edit |
|||
return unless state.sorted_links.length > 0 |
|||
refs.edit.open api.selected_link |
|||
|
|||
get tips |
|||
let result = [] |
|||
let temp |
|||
|
|||
temp = { |
|||
click_handler: api.handle_click_link.bind(api) |
|||
hotkey_handler: api.handle_click_link.bind(api) |
|||
hotkey: 'return' |
|||
hotkey_display_name: 'Return' |
|||
} |
|||
temp.content = api.selected_link.is_bang ? "Use Bang" : "Navigate To Link" |
|||
result.push temp |
|||
|
|||
temp = { |
|||
click_handler: api.handle_add_link.bind(api) |
|||
hotkey_handler: api.handle_add_link.bind(api) |
|||
hotkey: 'shift+return' |
|||
hotkey_display_name: 'Shift + Return' |
|||
content: "Create Link \"{state.query.trim!}\"" |
|||
} |
|||
result.push temp |
|||
|
|||
temp = { |
|||
click_handler: handle_edit.bind(this) |
|||
hotkey_handler: handle_edit.bind(this) |
|||
hotkey: 'shift+backspace' |
|||
hotkey_display_name: "Shift + Backspace" |
|||
content: "Edit Link" |
|||
} |
|||
result.push temp |
|||
|
|||
temp = { |
|||
click_handler: api.toggle_effective_names.bind(api) |
|||
hotkey_handler: api.toggle_effective_names.bind(api) |
|||
hotkey: 'tab' |
|||
hotkey_display_name: "Tab" |
|||
content: "Toggle Effective Names" |
|||
} |
|||
result.push temp |
|||
|
|||
temp = { |
|||
click_handler: (do refs.settings.open!).bind(this) |
|||
hotkey_handler: (do refs.settings.open!).bind(this) |
|||
hotkey: 'shift+tab' |
|||
hotkey_display_name: "Shift + Tab" |
|||
content: "Toggle Settings" |
|||
} |
|||
result.push temp |
|||
|
|||
temp = { |
|||
click_handler: api.handle_cut.bind(api) |
|||
} |
|||
if api.math_result |
|||
temp.hotkey_display_name = "Cut (If No Selection)" |
|||
temp.content = "Cut All Text" |
|||
else |
|||
temp.hotkey_display_name = "Cut (Math, If No Selection)" |
|||
temp.content = "Cut Math Result" |
|||
result.push temp |
|||
|
|||
temp = { |
|||
click_handler: api.increment_link_selection_index.bind(api) |
|||
hotkey_handler: api.increment_link_selection_index.bind(api) |
|||
hotkey: 'down' |
|||
hotkey_display_name: "Down Arrow" |
|||
content: "Move Selection Down" |
|||
} |
|||
result.push temp |
|||
|
|||
temp = { |
|||
click_handler: api.decrement_link_selection_index.bind(api) |
|||
hotkey_handler: api.decrement_link_selection_index.bind(api) |
|||
hotkey: 'up' |
|||
hotkey_display_name: "Up Arrow" |
|||
content: "Move Selection Up" |
|||
} |
|||
result.push temp |
|||
|
|||
temp = { |
|||
hotkey_display_name: "Paste (If Input Empty)" |
|||
content: "Instant Search" |
|||
} |
|||
result.push temp |
|||
|
|||
result |
|||
|
|||
def render |
|||
|
|||
<self> |
|||
css w:100% d:flex fld:column gap:15px ofy:hidden |
|||
|
|||
<app-tips$tips tips=tips> |
|||
|
|||
<.links> |
|||
css ofy:auto |
|||
for link, index in state.sorted_links |
|||
<app-link link=link index=index handle_edit=handle_edit> |
|||
|
@ -1,16 +0,0 @@ |
|||
import { cloneDeep } from 'lodash' |
|||
|
|||
tag app-prompt |
|||
|
|||
def get_input d |
|||
data = cloneDeep d |
|||
active = yes |
|||
let result = await new Promise! do |resolve| |
|||
self.addEventListener('end') do |e| |
|||
self.removeEventListener('end', this) |
|||
resolve(e.detail) |
|||
active = no |
|||
result |
|||
|
|||
def end |
|||
emit('end', data) |
@ -1,153 +0,0 @@ |
|||
import download from 'downloadjs' |
|||
|
|||
tag app-settings |
|||
|
|||
active = no |
|||
|
|||
def close |
|||
active = no |
|||
|
|||
def open |
|||
active = yes |
|||
|
|||
def handle_click_github |
|||
global.location.href = "https://github.com/familyfriendlymikey/fuzzyhome" |
|||
|
|||
def handle_click_toggle_tips |
|||
config.data.enable_tips = not config.data.enable_tips |
|||
config.save! |
|||
active = no |
|||
|
|||
def handle_click_toggle_buttons |
|||
config.data.enable_buttons = not config.data.enable_buttons |
|||
config.save! |
|||
active = no |
|||
|
|||
def handle_click_toggle_search_on_paste |
|||
config.data.enable_search_on_paste = not config.data.enable_search_on_paste |
|||
config.save! |
|||
active = no |
|||
|
|||
def handle_toggle_light_theme |
|||
config.data.enable_light_theme = not config.data.enable_light_theme |
|||
config.save! |
|||
active = no |
|||
|
|||
def handle_import e |
|||
|
|||
def handle_import |
|||
let errors = [] |
|||
try |
|||
let text = await e.target.files[0].text! |
|||
var links = text.split "\n" |
|||
catch e |
|||
return err "importing db", e |
|||
for link_text in links |
|||
try |
|||
let link = await api.create_link_from_text link_text |
|||
if api.name_exists link.name |
|||
throw "Name already exists, add manually if you don't mind duplicates." |
|||
api.add_link link_text |
|||
catch e |
|||
errors.push "{link_text}\n{e}" |
|||
if errors.length > 0 |
|||
err "importing some links", errors.join("\n\n") |
|||
|
|||
state.loading = yes |
|||
await handle_import! |
|||
active = no |
|||
state.loading = no |
|||
close! |
|||
|
|||
def handle_click_export |
|||
state.loading = yes |
|||
await api.reload_db! |
|||
let links = state.links.map do |link| |
|||
api.construct_link_text link |
|||
let datetime = new Date!.toString!.split(" ") |
|||
let date = datetime.slice(1, 4).join("-").toLowerCase! |
|||
let time = datetime[4].split(":").join("-") |
|||
let filename = "fuzzyhome_v{version}_{date}_{time}.txt" |
|||
download(links.join("\n"), filename, "text/plain") |
|||
active = no |
|||
state.loading = no |
|||
|
|||
def render |
|||
|
|||
<self> |
|||
css w:100% |
|||
|
|||
css .settings-container |
|||
d:flex fld:row jc:space-around ai:center |
|||
w:100% h:50px |
|||
mt:10px |
|||
gap:10px |
|||
|
|||
css .settings-button, .settings-container button |
|||
d:flex fld:column jc:center ai:center fl:1 |
|||
bg:none bd:none cursor:pointer fs:14px |
|||
rd:5px |
|||
transition:background 100ms |
|||
h:100% |
|||
bg:$button-bg c:$button-c |
|||
@hover bg:$button-hover-bg |
|||
|
|||
if refs.community-links.active |
|||
<app-community-links> |
|||
|
|||
else |
|||
<.settings-container> |
|||
|
|||
<.settings-button |
|||
@click=close |
|||
@hotkey("esc")=close |
|||
@hotkey("shift+tab")=close |
|||
> "BACK" |
|||
|
|||
<.settings-container> |
|||
|
|||
<.settings-button @click=(refs.community-links.open! and close!)> |
|||
"VIEW COMMUNITY LINKS" |
|||
|
|||
<.settings-container> |
|||
|
|||
<label.settings-button> |
|||
"IMPORT" |
|||
<input[d:none] |
|||
disabled=state.loading |
|||
@change=handle_import |
|||
@click=(this.value = '') |
|||
type="file" |
|||
> |
|||
|
|||
<.settings-button @click=handle_click_export> |
|||
"EXPORT" |
|||
|
|||
<.settings-container> |
|||
|
|||
<.settings-button @click=handle_click_github> |
|||
"TUTORIAL" |
|||
|
|||
<.settings-button @click=handle_click_github> |
|||
"GITHUB" |
|||
|
|||
<.settings-container> |
|||
|
|||
<.settings-button @click=handle_click_toggle_tips> |
|||
config.data.enable_tips ? "DISABLE TIPS" : "ENABLE TIPS" |
|||
|
|||
<.settings-button @click=handle_click_toggle_buttons> |
|||
config.data.enable_buttons ? "DISABLE BUTTONS" : "ENABLE BUTTONS" |
|||
|
|||
<.settings-container> |
|||
|
|||
<.settings-button @click=handle_click_toggle_search_on_paste> |
|||
config.data.enable_search_on_paste ? "DISABLE SEARCH ON PASTE" : "ENABLE SEARCH ON PASTE" |
|||
|
|||
<.settings-button @click=config.cycle_theme> |
|||
"THEME: {config.data.theme.toUpperCase!}" |
|||
|
|||
<.settings-container> |
|||
|
|||
<.settings-button @click=(api.delete_all_bang_history! and close!)> |
|||
"DELETE ALL BANG HISTORY" |
@ -1,86 +0,0 @@ |
|||
import { chunk, fill } from 'lodash' |
|||
|
|||
tag app-tip |
|||
|
|||
<self |
|||
@click.if(tip.click_handler)=tip.click_handler |
|||
> |
|||
css d:flex fld:column jc:start fl:1 |
|||
bdr:1px solid |
|||
bc:$tip-bc |
|||
min-width:0 ta:center p:10px |
|||
cursor:pointer transition:background 100ms |
|||
@first ta:left rdl:3px |
|||
@last ta:right bd:none rdr:3px |
|||
@hover bg:$tip-hover-c |
|||
if tip.placeholder or not tip.click_handler |
|||
css |
|||
@hover @important cursor:auto bg:none |
|||
|
|||
if tip.hotkey_handler and tip.hotkey |
|||
<@hotkey(tip.hotkey).force=tip.hotkey_handler> |
|||
css d:none |
|||
|
|||
<.tip-hotkey> tip.hotkey_display_name |
|||
css fs:12px c:$tip-hotkey-c |
|||
|
|||
<.tip-content> tip.content |
|||
css pt:2px fs:14px c:$tip-content-c |
|||
|
|||
tag app-tips |
|||
|
|||
def unmount |
|||
show_more = no |
|||
|
|||
def toggle |
|||
show_more = not show_more |
|||
|
|||
def pad arr |
|||
let i = arr.length |
|||
while i < 3 |
|||
arr.push { placeholder: yes } |
|||
i += 1 |
|||
|
|||
def get_chunks |
|||
let chunks = chunk(tips, 3) |
|||
pad(chunks[-1]) |
|||
chunks |
|||
|
|||
def render |
|||
let chunks = get_chunks! |
|||
|
|||
<self[d:none]=!config.data.enable_tips> |
|||
css d:flex fld:column gap:15px max-height:75% |
|||
|
|||
css .tip-row |
|||
d:flex fld:row w:100% fl:1 |
|||
fs:20px fs:14px |
|||
jc:end ta:center |
|||
|
|||
<.tip-row> |
|||
for tip in chunks[0] |
|||
<app-tip tip=tip> |
|||
|
|||
if chunks.length > 1 |
|||
|
|||
<@click=toggle> |
|||
css w:100% d:flex ja:center c:$button-c rdb:4px cursor:pointer |
|||
transition:background 100ms |
|||
@hover bg:$tip-hover-c |
|||
if show_more |
|||
css rd:0 |
|||
|
|||
<svg src="../assets/chevron-down.svg"> |
|||
css w:15px transition:transform 150ms |
|||
if show_more |
|||
css transform:rotate(180deg) |
|||
|
|||
<.more> |
|||
css d:flex fld:column gap:15px ofy:auto |
|||
unless show_more |
|||
css d:none |
|||
|
|||
for row in chunks.slice(1) |
|||
<.tip-row> |
|||
for tip in row |
|||
<app-tip tip=tip> |
@ -1,51 +0,0 @@ |
|||
let p = console.log |
|||
import Dexie from 'dexie' |
|||
import 'dexie-export-import' |
|||
import { nanoid } from 'nanoid' |
|||
import api from './api' |
|||
|
|||
let db = new Dexie 'fuzzyhome' |
|||
|
|||
db.version(1).stores({ |
|||
links: "++id,name,link" |
|||
}) |
|||
|
|||
db.version(2).stores({ |
|||
links: "++id,name,url,frequency,img" |
|||
}).upgrade! do |trans| |
|||
p "upgrading to fuzzyhome db version 2" |
|||
trans.links.toCollection!.modify! do |link| |
|||
let id = nanoid! |
|||
let name = link.name |
|||
let url = link.link |
|||
let frequency = link.frequency |
|||
let img = link.img |
|||
this.value = { id, name, url, frequency, img } |
|||
|
|||
db.version(3).stores({ |
|||
links: "++id,name,url,frequency,img" |
|||
}).upgrade! do |trans| |
|||
p "upgrading to fuzzyhome db version 3" |
|||
trans.links.toCollection!.modify! do |link| |
|||
try |
|||
link.url = api.parse_url(link.url).href |
|||
|
|||
db.version(4).stores({ |
|||
links: "++id,display_name,name,is_bang,url,frequency,icon" |
|||
}).upgrade! do |trans| |
|||
p "upgrading to fuzzyhome db version 4" |
|||
trans.links.toCollection!.modify! do |link| |
|||
link.display_name = link.name |
|||
link.is_bang = no |
|||
link.icon = link.img |
|||
delete link.img |
|||
|
|||
db.version(5).stores({ |
|||
links: "++id,display_name,name,is_bang,is_pinned,url,frequency,history,icon" |
|||
}).upgrade! do |trans| |
|||
p "upgrading to fuzzyhome db version 5" |
|||
trans.links.toCollection!.modify! do |link| |
|||
link.is_pinned = no |
|||
link.history = [] |
|||
|
|||
export default db |
@ -1,41 +0,0 @@ |
|||
const app_name = "fuzzyhome" |
|||
import { version } from '../package.json' |
|||
const app_prefix = "{app_name}_cache" |
|||
const cache_name = "sw-{app_prefix}-{version}" |
|||
let p = do |s| console.log "{cache_name} {s}" |
|||
p "loaded" |
|||
|
|||
let urls = [ |
|||
'./' |
|||
] |
|||
|
|||
self.addEventListener('fetch') do |e| |
|||
p "fetch" |
|||
def intercept request |
|||
if request |
|||
p "responding with cache {e.request.url}" |
|||
request |
|||
else |
|||
p "not cached, fetching {e.request.url}" |
|||
fetch e.request |
|||
e.respondWith(caches.match(e.request.url).then(intercept)) |
|||
|
|||
self.addEventListener('install') do |e| |
|||
p "install" |
|||
def add_urls_to_cache cache |
|||
p "adding urls to cache" |
|||
cache.addAll urls |
|||
skipWaiting! |
|||
e.waitUntil(caches.open(cache_name).then(add_urls_to_cache)) |
|||
|
|||
self.addEventListener('activate') do |e| |
|||
p "activate" |
|||
def delete_cached keys |
|||
let temp = keys.map! do |key, i| |
|||
p "checking cache {key}" |
|||
if key !== cache_name |
|||
p "deleting cache {key}" |
|||
let result = await caches.delete key |
|||
p "deletion of {key} result: {result}" |
|||
Promise.all(temp) |
|||
e.waitUntil(caches.keys().then(delete_cached)) |
@ -1,13 +0,0 @@ |
|||
let p = console.log |
|||
|
|||
export def err s, e |
|||
p "error:" |
|||
p e |
|||
window.alert("Error {s}:\n\n{e}") |
|||
|
|||
global._fuzzyhome_delete_everything = do |prompt=yes| |
|||
return if prompt and window.confirm "This will delete everything. Are you sure?" |
|||
indexedDB.deleteDatabase("fuzzyhome") |
|||
delete localStorage.fuzzyhome_config |
|||
delete localStorage.fuzzyhome_visited |
|||
location.reload! |
@ -0,0 +1,13 @@ |
|||
{ |
|||
"name": "fuzzyhome", |
|||
"description": "A power user oriented new-tab page that enables lightning speed navigation through the dark magic of fuzzy finding.", |
|||
"version": "2.1.0", |
|||
"manifest_version": 3, |
|||
"permissions": [ |
|||
"bookmarks", |
|||
"storage" |
|||
], |
|||
"chrome_url_overrides": { |
|||
"newtab": "dist/index.html" |
|||
} |
|||
} |
@ -1,26 +1,19 @@ |
|||
{ |
|||
"name": "fuzzyhome", |
|||
"version": "0.1.43", |
|||
"version": "0.2.1", |
|||
"description": "A lightweight new-tab page that lets you very quickly fuzzy find links and navigate to a result.", |
|||
"homepage": "https://fuzzyho.me/", |
|||
"author": "Mikey Oz (https://github.com/familyfriendlymikey)", |
|||
"scripts": { |
|||
"build": "imba build app/index.html", |
|||
"publish-preview": "npx gh-pages --no-history --dotfiles --dist dist/ --branch preview", |
|||
"publish-release": "npx gh-pages --no-history --dotfiles --dist dist/ --branch release", |
|||
"start": "imba -w app/index.html", |
|||
"static": "npm run build && cd dist && python3 -m http.server", |
|||
"preview": "npm run build && npm run publish-preview", |
|||
"deploy-only-after-preview": "npm run publish-release" |
|||
"dev": "vite build -w --outDir chrome/dist --minify false", |
|||
"build": "vite build --outDir chrome/dist" |
|||
}, |
|||
"dependencies": { |
|||
"dexie": "^3.2.2", |
|||
"dexie-export-import": "^1.0.3", |
|||
"downloadjs": "^1.4.7", |
|||
"fzi": "^1.1.0", |
|||
"imba": "^2.0.0-alpha.220", |
|||
"fzi": "^1.5.0", |
|||
"imba": "^2.0.0-alpha.225", |
|||
"lodash": "^4.17.21", |
|||
"mathjs": "^11.1.0", |
|||
"nanoid": "^4.0.0" |
|||
}, |
|||
"description": "A lightweight new-tab page that lets you very quickly fuzzy find links and navigate to a result.", |
|||
"homepage": "https://fuzzyho.me/", |
|||
"author": "Mikey Oz (https://github.com/familyfriendlymikey)" |
|||
"math-expression-evaluator": "^1.4.0", |
|||
"vite": "^3.2.5", |
|||
"vite-plugin-imba": "^0.10.1" |
|||
} |
|||
} |
|||
|
@ -1,158 +1,14 @@ |
|||
<h1 align="center"> |
|||
<a href="https://fuzzyho.me/"> |
|||
fuzzyhome |
|||
</a> |
|||
</h1> |
|||
useful modifications: |
|||
|
|||
A power user oriented new-tab page that enables lightning speed navigation through the dark magic of fuzzy finding. |
|||
cmd+option+b for bookmark manager |
|||
cmd+h for home instead of closing tab and opening new tab |
|||
|
|||
## Installation |
|||
1. Copy this link: `https://fuzzyho.me/`: |
|||
1. Change your homepage in your browser settings. |
|||
1. Install a browser extension that lets you change your new-tab page url. These have worked fine for me: |
|||
- Firefox: [New Tab Override](https://addons.mozilla.org/en-US/firefox/addon/new-tab-override) |
|||
- Chrome: [New Tab Redirect](https://chrome.google.com/webstore/detail/new-tab-redirect/icpgjfneehieebagbmdbhnlpiopdcmna) |
|||
This is the latest working preview of fuzzyhome which can be |
|||
found at https://preview.fuzzyhome.pages.dev/ |
|||
|
|||
If the extension has an option for setting focus to the webpage instead of the address bar, be sure to enable it. |
|||
|
|||
## Usage |
|||
|
|||
### Create |
|||
Create a new link by typing a name and a url separated by a space. |
|||
For example: |
|||
``` |
|||
imba home page https://imba.io/ |
|||
``` |
|||
The last space-separated string will be used as the url for your link. |
|||
In most cases if you do not specify a protocol, `https` will be used. |
|||
|
|||
You can also use the hotkey `shift+return` to create a new link. |
|||
|
|||
### Fuzzy Find |
|||
Search for a link by typing. |
|||
The fuzzy sorting algorithm makes searching very fast, |
|||
as you can usually just type the first letter of each word to get to a link (`ihp` to get to `imba home page`, for example). |
|||
|
|||
### Navigate |
|||
Navigate to the currently selected search result by pressing `return`. |
|||
You can also click on a link to navigate to it. |
|||
You can also press the up or down arrow keys to move your selection up and down. |
|||
|
|||
### Search |
|||
If there are no matching links, a search will be performed with your query. |
|||
|
|||
### Bangs |
|||
There may be some websites you've created links for, such as amazon, where you almost always search for something. |
|||
This means you have to go to that website, click the search bar, and type in your query. |
|||
With fuzzyhome you can cut out the slow parts by prefixing your link name with `!` to create a "bank link": |
|||
``` |
|||
!amazon amazon.com/s?k= |
|||
``` |
|||
Notice the `/s?k=` at the end, |
|||
you'll likewise have to find the proper URL for your new bang link. |
|||
Typing instructions for that would be too verbose, so please see the video tutorial (coming soon). |
|||
|
|||
When you click on or press return on a selected bang link, |
|||
instead of navigating directly to that link, |
|||
you'll be able to enter a search query for that link. |
|||
Pressing enter again will bring you to the link with your encoded search query appended to it. |
|||
|
|||
**LIST OF BANGS FOR YOUR CONVENIENCE BELOW** |
|||
|
|||
### Effective Names |
|||
After using fuzzyhome enough, you may come to realize |
|||
that there are some links you'd prefer be "hardcoded" |
|||
with certain names so to speak. |
|||
For example, perhaps you visit `instagram` extremely often |
|||
but also have a link named `indeed` which gets sorted above |
|||
`instagram` when you type `in` even though you visit it much less often. |
|||
You could change the `instagram` link's name to `in`, |
|||
but now it looks bad. |
|||
To solve this, fuzzyhome allows you to add an "effective name" |
|||
to a link: |
|||
``` |
|||
instagram `in instagram.com |
|||
``` |
|||
To add an effective name to a link, |
|||
simply add the name prefixed with a backtick right before the URL. |
|||
|
|||
This also works for bang links. |
|||
Let's say we wanted `a` to correspond to `!amazon`: |
|||
``` |
|||
!amazon `a amazon.com/s?k= |
|||
``` |
|||
Now when you type `in` or `a`, |
|||
you can have confidence that your |
|||
intended link will be given priority every time. |
|||
|
|||
Mind you, typing `am` will no longer show `amazon` in the results, |
|||
because that's just the display name for the link. |
|||
The actual name is `a`. |
|||
This might seem confusing but once you |
|||
get the hang of it won't matter to you at all. |
|||
|
|||
### Delete |
|||
You can delete notes by clicking the purple `x` on the currently selected link. |
|||
You can also use the hotkey `shift+backspace` to delete the currently selected link. |
|||
|
|||
### Edit |
|||
You can edit notes by clicking the edit icon. |
|||
|
|||
### Move Selection |
|||
You can move your selection up and down with the arrow keys. |
|||
|
|||
### Quick Search |
|||
If you paste while the input is empty, fuzzyhome will immediately make a search with your pasted query. |
|||
|
|||
### Customize Search Engine |
|||
The default search engine is Google Search, however you can customize it by clicking the three dots to go to settings, |
|||
clicking `config`, and pasting in your search engine url, such as `https://search.brave.com/search?q=`. |
|||
Your search query simply gets encoded and pasted to the end of your configured search engine url. |
|||
|
|||
### Importing / Exporting Links |
|||
If you want to export your links to use them on another computer, go to the settings menu and click `EXPORT`. |
|||
This downloads a `.json` file, which you can then send to your other computer and import by clicking the `IMPORT` button |
|||
and selecting your file. |
|||
|
|||
### Reset Everything To Default |
|||
Not sure why anyone except me would do this, but if for some reason you want to delete everything and restore the default config, |
|||
you can do so by bringing up your developer console and running the function `_fuzzyhome_delete_everything()`, |
|||
and confirming that you do indeed want to delete all your links on the prompt that pops up. |
|||
|
|||
## Hotkeys |
|||
Hotkey | Action |
|||
-|- |
|||
Return | Navigate to the currently selected link, or perform a search if there are no matching links. |
|||
Up Arrow | Move selection up. |
|||
Down Arrow | Move selection down. |
|||
Paste | If input is empty, immediately search with pasted query. |
|||
Shift + Return | Create new link. |
|||
Shift + Backspace | Delete currently selected link. |
|||
|
|||
## FAQ |
|||
|
|||
### The Link I Want Is Showing Up Last |
|||
Change the effective name of the link. |
|||
Let's say you've been typing `in` for `instagram`, but recently added `indeed` as a link, |
|||
and `indeed` keeps showing up first. |
|||
Simply change the effective name of the `instagram` link to `in`: |
|||
``` |
|||
instagram `in instagram.com |
|||
``` |
|||
|
|||
### A Link Is Blocking My Search |
|||
This happens very rarely if at all. Just throw some spaces at the end of your query. |
|||
|
|||
### The Quick Search Function Is Stopping Me From Finishing My Query |
|||
Just type a single space before you paste in text. |
|||
|
|||
### My Localhost Link Isn't Working |
|||
If you want to make a link that points to `localhost`, you likely need to specify the `http` protocol when creating your link. |
|||
|
|||
## Bang List |
|||
Website | Bang Text |
|||
-|- |
|||
youtube | !youtube ~y https://www.youtube.com/results?search_query= |
|||
amazon | !amazon amazon.com/s?k= |
|||
google site:reddit.com | !google reddit https://www.google.com/search?q=site%3Areddit.com%20 |
|||
google site:reddit.com | !google stackoverflow https://www.google.com/search?q=site%3Astackoverflow.com%20 |
|||
I have big plans for fuzzyhome but don't have time to act on them |
|||
right now. As things stand it works really well and is a huge |
|||
productivity booster if you use it properly. I haven't |
|||
experienced any runtime errors using it dozens or hundreds of |
|||
times a day for months now, so it seems to be pretty stable. |
|||
Export your links just in case :) |
|||
|
@ -1 +0,0 @@ |
|||
printf "\033c" && rg --pcre2 -g '!api.imba' '(?<!api.)'"$1" |
@ -1 +0,0 @@ |
|||
printf "\033c" && rg --pcre2 -g '!config.imba' 'config\.(?!data.)'"$1" |
@ -0,0 +1,137 @@ |
|||
import state from './state.imba' |
|||
|
|||
import config from './config.imba' |
|||
import { find, omit, orderBy } from 'lodash' |
|||
import fzi from 'fzi' |
|||
import { cloneDeep } from 'lodash' |
|||
import mexp from 'math-expression-evaluator' |
|||
|
|||
export default new class api |
|||
|
|||
def pin_link link |
|||
Pins[link.url] ^= 1 |
|||
sort_links! |
|||
global.chrome.storage.sync.set {pins:Pins} |
|||
imba.commit! |
|||
|
|||
def increment_link_frequency link |
|||
Frequencies[link.url] ??= 0 |
|||
Frequencies[link.url] += 1 |
|||
global.chrome.storage.sync.set {frequencies:Frequencies} |
|||
|
|||
def get-link-from-node node |
|||
return unless let url = node..url |
|||
let split_text = node.title.split /\s+/ |
|||
let alias |
|||
let last = split_text[-1] |
|||
if last.startsWith("(") and last.endsWith(")") |
|||
alias = split_text.pop!.slice(1,-1) |
|||
let name = split_text.join(" ") |
|||
let is_bang = no |
|||
if name.startsWith "!" |
|||
is_bang = yes |
|||
name = name.slice(1) |
|||
{ name, alias, is_bang, url } |
|||
|
|||
def traverse stack |
|||
const links = [] |
|||
while stack.length > 0 |
|||
const node = stack.pop! |
|||
const link = get-link-from-node(node) |
|||
links.push(link) if link |
|||
node..children..forEach do stack.push $1 |
|||
links |
|||
|
|||
def bfs title, queue |
|||
while queue.length > 0 |
|||
const node = queue.shift! |
|||
return node.children if node.title.toLowerCase! is title.toLowerCase! |
|||
if node.children |
|||
queue = queue.concat(node.children) |
|||
|
|||
def sort_links |
|||
if state.query.trim!.length <= 0 |
|||
const pinned = do Pins[$1.url] or no |
|||
const freq = do Frequencies[$1.url] or 0 |
|||
state.sorted_links = orderBy(state.links, [pinned, freq], ['desc', 'desc']) |
|||
else |
|||
state.sorted_links = fzi.search state.query, state.links, (do $1.name), (do $1.alias) |
|||
|
|||
def parse_url url |
|||
throw "invalid url" if url === null |
|||
let get_url = do |s| |
|||
let url = new URL s |
|||
throw _ unless (url.host and url.href) |
|||
url |
|||
try |
|||
return get_url url |
|||
try |
|||
return get_url "https://{url}" |
|||
throw "invalid url" |
|||
|
|||
def get_pretty_date |
|||
Date!.toString!.split(" ").slice(0, 4).join(" ") |
|||
|
|||
get selected_link |
|||
state.sorted_links[state.link_selection_index] |
|||
|
|||
def set_link_selection_index index |
|||
state.link_selection_index = index |
|||
|
|||
def increment_link_selection_index |
|||
set_link_selection_index Math.min(state.sorted_links.length - 1, state.link_selection_index + 1) |
|||
|
|||
def decrement_link_selection_index |
|||
set_link_selection_index Math.max(0, state.link_selection_index - 1) |
|||
|
|||
def navigate link |
|||
await increment_link_frequency link |
|||
window.location.href = link.url |
|||
|
|||
get math_result |
|||
try |
|||
mexp.eval(state.query) |
|||
catch |
|||
no |
|||
|
|||
def handle_cut e |
|||
return unless e.target.selectionStart == e.target.selectionEnd |
|||
let s = math_result |
|||
s ||= state.query |
|||
await window.navigator.clipboard.writeText(s) |
|||
state.query = '' |
|||
sort_links! |
|||
|
|||
def handle_click_link |
|||
let link = selected_link |
|||
if link.is_bang |
|||
state.query = '' |
|||
state.active_bang = link |
|||
else |
|||
navigate link |
|||
|
|||
get bang |
|||
state.active_bang or config.data.default_bang |
|||
|
|||
get encoded_bang_query |
|||
"{bang.url}{window.encodeURIComponent(state.query)}" |
|||
|
|||
get encoded_bang_query_nourl |
|||
"{window.encodeURIComponent(state.query)}" |
|||
|
|||
def handle_bang |
|||
return if state.loading |
|||
await increment_link_frequency bang |
|||
window.location.href = encoded_bang_query |
|||
|
|||
def unset_active_bang |
|||
state.active_bang = no |
|||
sort_links! |
|||
|
|||
def get-icon url |
|||
let { host } = parse_url url |
|||
"https://icon.horse/icon/{host}" |
|||
|
|||
def help |
|||
let url = "https://github.com/familyfriendlymikey/fuzzyhome" |
|||
window.open url,'_blank' |
Before Width: | Height: | Size: 269 B After Width: | Height: | Size: 269 B |
Before Width: | Height: | Size: 268 B After Width: | Height: | Size: 268 B |
Before Width: | Height: | Size: 291 B After Width: | Height: | Size: 291 B |
Before Width: | Height: | Size: 316 B After Width: | Height: | Size: 365 B |
Before Width: | Height: | Size: 308 B After Width: | Height: | Size: 308 B |
Before Width: | Height: | Size: 1011 B After Width: | Height: | Size: 1011 B |
Before Width: | Height: | Size: 339 B After Width: | Height: | Size: 339 B |
Before Width: | Height: | Size: 356 B After Width: | Height: | Size: 356 B |
Before Width: | Height: | Size: 368 B After Width: | Height: | Size: 368 B |
Before Width: | Height: | Size: 299 B After Width: | Height: | Size: 299 B |
@ -0,0 +1,63 @@ |
|||
tag app-bang |
|||
|
|||
get tips |
|||
let result = [] |
|||
let temp |
|||
|
|||
temp = { |
|||
click_handler: api.handle_bang.bind(api) |
|||
hotkey_handler: api.handle_bang.bind(api) |
|||
hotkey: 'return' |
|||
hotkey_display_name: "Return" |
|||
content: "Search" |
|||
} |
|||
result.push temp |
|||
|
|||
temp = { |
|||
click_handler: api.handle_cut.bind(api) |
|||
} |
|||
if api.math_result |
|||
temp.hotkey_display_name = "Cut Math" |
|||
temp.content = "Cut Math Result" |
|||
else |
|||
temp.hotkey_display_name = "Cut" |
|||
temp.content = "Cut All Text" |
|||
result.push temp |
|||
|
|||
if state.active_bang |
|||
temp = { |
|||
click_handler: api.unset_active_bang.bind(api) |
|||
hotkey_handler: api.unset_active_bang.bind(api) |
|||
hotkey: 'esc' |
|||
hotkey_display_name: "Esc" |
|||
content: "Back" |
|||
} |
|||
result.push temp |
|||
|
|||
result |
|||
|
|||
<self> |
|||
css w:100% d:flex fld:column gap:15px ofy:hidden |
|||
|
|||
<app-tips$tips tips=tips> |
|||
|
|||
unless $tips.show_more |
|||
|
|||
<.bang .selected [c:$bang-c] @click=api.handle_bang> |
|||
css d:flex fld:row jc:space-between ai:center |
|||
px:16px py:11px rd:5px c:$text-c |
|||
|
|||
<.link-left> |
|||
css d:flex fl:1 ofy:hidden |
|||
|
|||
<img.link-icon src=api.get-icon(api.bang.url)> |
|||
css w:20px h:20px mr:10px rd:3px |
|||
|
|||
<.display-name> "...{api.encoded_bang_query_nourl}" |
|||
css fs:20px of:hidden text-overflow:ellipsis |
|||
|
|||
<.link-right> |
|||
css d:flex fld:row jc:space-between ai:center |
|||
|
|||
<.frequency> api.bang.frequency |
|||
css fs:15px ml:7px |
@ -0,0 +1,77 @@ |
|||
tag app-home |
|||
|
|||
def mount |
|||
$home-input.focus! |
|||
|
|||
def blur |
|||
setTimeout(&, 100) do $home-input.focus! |
|||
|
|||
def handle_click_copy s |
|||
try |
|||
await window.navigator.clipboard.writeText(s) |
|||
state.query = '' |
|||
api.sort_links! |
|||
|
|||
def handle_input |
|||
api.set_link_selection_index 0 |
|||
api.sort_links! |
|||
|
|||
<self> |
|||
css w:100% d:flex fld:column ofy:hidden gap:20px |
|||
|
|||
<.header> |
|||
css |
|||
d:hflex |
|||
|
|||
.side |
|||
c:$button-c fs:15px d:box e:300ms |
|||
|
|||
> |
|||
w:15px d:flex |
|||
|
|||
.left |
|||
pr:15px |
|||
jc:left |
|||
|
|||
.right |
|||
pl:15px |
|||
jc:right |
|||
|
|||
<.side.left @click=(state.view = 'settings')> |
|||
<svg src="../assets/settings.svg"> |
|||
|
|||
<input$home-input |
|||
autofocus |
|||
bind=state.query |
|||
@input=handle_input |
|||
@cut=api.handle_cut |
|||
disabled=state.loading |
|||
@blur=blur |
|||
> |
|||
css h:50px px:20px fl:1 |
|||
fs:20px ta:center |
|||
bd:1px solid $input-bc |
|||
outline:none rd:5px |
|||
bg:$input-bg c:$text-c |
|||
caret-color:$input-caret-c |
|||
|
|||
<.side.right@click=handle_click_copy(api.math_result or 0)> |
|||
<$math> |
|||
css d:box fs:17px min-width:min-content |
|||
"{Math.round(api.math_result * 100)/100}" |
|||
|
|||
css e:200ms eaf:circ-out |
|||
max-width:{$math.offsetWidth or 30}px |
|||
min-width:{$math.offsetWidth}px |
|||
|
|||
if state.loaded |
|||
<div ease> |
|||
css e:400ms of:hidden |
|||
@off o:0 |
|||
|
|||
if state.active_bang or state.sorted_links.length < 1 |
|||
<app-bang> |
|||
|
|||
else |
|||
<app-links> |
|||
|
@ -0,0 +1,55 @@ |
|||
tag app-link |
|||
|
|||
frequency = 0 |
|||
|
|||
<self |
|||
@pointerover=api.set_link_selection_index(index) |
|||
@click=api.handle_click_link |
|||
.selected=(index is state.link_selection_index) |
|||
> |
|||
css d:flex fld:row jc:space-between ai:center |
|||
px:16px py:11px rd:5px c:$text-c |
|||
if link.is_bang |
|||
css c:$bang-c |
|||
|
|||
<.link-left> |
|||
css d:flex w:100% |
|||
|
|||
<img.link-icon src=api.get-icon(link.url)> |
|||
|
|||
css w:20px h:20px mr:10px rd:3px |
|||
|
|||
<.name> link.name |
|||
css tt:capitalize fs:20px overflow-wrap:anywhere |
|||
|
|||
if link.alias |
|||
<.name> |
|||
css d:flex ja:center c:$effective-name-c ml:10px fs:14px |
|||
css .parens fs:10px c:$effective-name-parens-c |
|||
|
|||
<span.parens> "(" |
|||
<span> link.alias |
|||
<span.parens> ")" |
|||
|
|||
<.link-right> |
|||
css d:hflex jc:space-between w:70px ai:center |
|||
|
|||
css .selected .link-button visibility:visible |
|||
|
|||
<.link-buttons> |
|||
css d:flex fld:row jc:start ai:center gap:5px |
|||
|
|||
css .link-button visibility:hidden rd:3px c:$button-c fs:15px px:3px |
|||
if index is state.link_selection_index |
|||
css .link-button visibility:visible |
|||
|
|||
css .link-button svg w:15px |
|||
|
|||
<.link-button @click.prevent.stop=api.pin_link(link)> |
|||
if Pins[link.url] |
|||
css visibility:visible c:$button-dim-c |
|||
|
|||
<svg src='../assets/star.svg'> |
|||
|
|||
<.frequency> Frequencies[link.url] or 0 |
|||
css fs:15px ml:7px |
@ -0,0 +1,55 @@ |
|||
tag app-links |
|||
|
|||
get tips |
|||
let result = [] |
|||
let temp |
|||
|
|||
temp = { |
|||
click_handler: api.handle_click_link.bind(api) |
|||
hotkey_handler: api.handle_click_link.bind(api) |
|||
hotkey: 'return' |
|||
hotkey_display_name: 'Return' |
|||
} |
|||
temp.content = api.selected_link.is_bang ? "Use Bang" : "Navigate To Link" |
|||
result.push temp |
|||
|
|||
temp = { |
|||
click_handler: api.handle_cut.bind(api) |
|||
} |
|||
if api.math_result |
|||
temp.hotkey_display_name = "Cut Math" |
|||
temp.content = "Cut Math Result" |
|||
else |
|||
temp.hotkey_display_name = "Cut" |
|||
temp.content = "Cut All Text" |
|||
result.push temp |
|||
|
|||
temp = { |
|||
click_handler: api.increment_link_selection_index.bind(api) |
|||
hotkey_handler: api.increment_link_selection_index.bind(api) |
|||
hotkey: 'down' |
|||
hotkey_display_name: "Down Arrow" |
|||
content: "Move Selection Down" |
|||
} |
|||
result.push temp |
|||
|
|||
temp = { |
|||
click_handler: api.decrement_link_selection_index.bind(api) |
|||
hotkey_handler: api.decrement_link_selection_index.bind(api) |
|||
hotkey: 'up' |
|||
hotkey_display_name: "Up Arrow" |
|||
content: "Move Selection Up" |
|||
} |
|||
result.push temp |
|||
|
|||
result |
|||
|
|||
<self> |
|||
css w:100% d:flex fld:column gap:15px ofy:hidden max-height:100% |
|||
|
|||
<app-tips$tips tips=tips> |
|||
|
|||
<.links> |
|||
css ofy:auto |
|||
for link, index in state.sorted_links |
|||
<app-link link=link index=index> |
@ -0,0 +1,53 @@ |
|||
tag app-settings |
|||
|
|||
<self> |
|||
css w:100% |
|||
|
|||
css .settings-container |
|||
d:flex fld:row jc:space-around ai:center |
|||
w:100% h:50px |
|||
mt:10px |
|||
gap:10px |
|||
|
|||
css .settings-button |
|||
bg:none bd:none fs:14px d:box fl:1 |
|||
rd:5px tt:uppercase |
|||
transition:background 100ms |
|||
h:100% px:5px |
|||
of:hidden text-overflow:ellipsis white-space:nowrap |
|||
bg:$button-bg c:$button-c |
|||
@hover bg:$button-hover-bg |
|||
|
|||
<.settings-container> |
|||
|
|||
<.settings-button |
|||
@click=(state.view = 'home') |
|||
@hotkey("esc") |
|||
> "BACK" |
|||
|
|||
<.settings-container> |
|||
|
|||
<.settings-button @click=api.help> |
|||
"HELP" |
|||
|
|||
<.settings-button @click=config.cycle_theme> |
|||
"THEME: {config.data.theme.toUpperCase!}" |
|||
|
|||
if config.data.theme is 'timed' |
|||
|
|||
<.settings-container> |
|||
|
|||
<.settings-button@click=config.set_timed_theme_start> |
|||
"light theme start: {config.data.timed_theme_start}" |
|||
|
|||
<.settings-button@click=config.set_timed_theme_end> |
|||
"light theme end: {config.data.timed_theme_end}" |
|||
|
|||
<.settings-container> |
|||
|
|||
<.settings-button @click=config.toggle_focus> |
|||
"FOCUS ON OPEN: {config.data.focus}" |
|||
|
|||
<.settings-button @click=config.set_default_bang> |
|||
"change default bang" |
|||
|
@ -0,0 +1,85 @@ |
|||
import { chunk, fill } from 'lodash' |
|||
|
|||
tag app-tip |
|||
|
|||
<self |
|||
@click.if(tip.click_handler)=tip.click_handler |
|||
> |
|||
css d:flex fld:column jc:start fl:1 |
|||
bdr:1px solid |
|||
bc:$tip-bc |
|||
min-width:0 ta:center p:10px |
|||
transition:background 100ms |
|||
@first ta:left rdl:3px |
|||
@last ta:right bd:none rdr:3px |
|||
@hover bg:$tip-hover-c |
|||
if tip.placeholder or not tip.click_handler |
|||
css |
|||
@hover @important bg:none |
|||
|
|||
if tip.hotkey_handler and tip.hotkey |
|||
<@hotkey(tip.hotkey).force=tip.hotkey_handler> |
|||
css d:none |
|||
|
|||
<.tip-hotkey> tip.hotkey_display_name |
|||
css fs:12px c:$tip-hotkey-c |
|||
|
|||
<.tip-content> tip.content |
|||
css pt:2px fs:14px c:$tip-content-c |
|||
|
|||
tag app-tips |
|||
|
|||
@observable tips |
|||
|
|||
def unmount |
|||
show_more = no |
|||
|
|||
def toggle |
|||
show_more = not show_more |
|||
|
|||
def pad arr |
|||
let i = arr.length |
|||
while i < 3 |
|||
arr.push { placeholder: yes } |
|||
i += 1 |
|||
|
|||
@computed get chunks |
|||
let chunks = chunk(tips, 3) |
|||
pad(chunks[-1]) |
|||
chunks |
|||
|
|||
<self> |
|||
css d:flex fld:column gap:15px max-height:75% |
|||
|
|||
css .tip-row |
|||
d:flex fld:row w:100% fl:1 |
|||
fs:20px fs:14px |
|||
jc:end ta:center |
|||
|
|||
<.tip-row> |
|||
for tip in chunks[0] |
|||
<app-tip tip=tip> |
|||
|
|||
if chunks.length > 1 |
|||
|
|||
<@click=toggle> |
|||
css w:100% d:flex ja:center c:$button-c rdb:4px |
|||
transition:background 100ms |
|||
@hover bg:$tip-hover-c |
|||
if show_more |
|||
css rd:0 |
|||
|
|||
<svg src="../assets/chevron-down.svg"> |
|||
css w:15px transition:transform 150ms |
|||
if show_more |
|||
css transform:rotate(180deg) |
|||
|
|||
# hotkeys depend on the presence of tips in the dom so |
|||
# can't ease this as is |
|||
<.more [d:none]=!show_more> |
|||
css d:flex fld:column gap:15px ofy:auto e:300ms |
|||
|
|||
for row in chunks.slice(1) |
|||
<.tip-row> |
|||
for tip in row |
|||
<app-tip tip=tip> |
@ -0,0 +1,71 @@ |
|||
export default new class config |
|||
|
|||
def save |
|||
global.localStorage.fuzzyhome_config = JSON.stringify(data) |
|||
|
|||
def constructor |
|||
data = {} |
|||
|
|||
try |
|||
data = JSON.parse(global.localStorage.fuzzyhome_config) |
|||
|
|||
data.focus ??= yes |
|||
|
|||
data.theme ??= "timed" |
|||
|
|||
data.timed_theme_start ??= 8 |
|||
data.timed_theme_end ??= 18 |
|||
|
|||
data.default_bang ??= {} |
|||
data.default_bang.name ??= "" |
|||
data.default_bang.url ??= "https://www.google.com/search?q=" |
|||
data.default_bang.frequency ??= 0 |
|||
data.default_bang.is_bang ??= yes |
|||
save! |
|||
|
|||
def cycle_theme |
|||
if data.theme is "dark" |
|||
data.theme = "light" |
|||
elif data.theme is "light" |
|||
data.theme = "timed" |
|||
else |
|||
data.theme = "dark" |
|||
save! |
|||
|
|||
def set_default_bang |
|||
let res = window.prompt('Insert a new search URL. For example:\nhttps://search.brave.com/search?q=')..trim! |
|||
return unless res |
|||
data.default_bang.url = res |
|||
save! |
|||
|
|||
def toggle_focus |
|||
data.focus = !data.focus |
|||
save! |
|||
|
|||
def set_timed_theme_end |
|||
let res = parseInt(window.prompt!) |
|||
return unless res |
|||
return unless res > 0 |
|||
return unless res < 24 |
|||
data.timed_theme_end = res |
|||
save! |
|||
|
|||
def set_timed_theme_start |
|||
let res = parseInt(window.prompt!) |
|||
return unless res |
|||
return unless res > 0 |
|||
return unless res < 24 |
|||
data.timed_theme_start = res |
|||
save! |
|||
|
|||
get theme |
|||
if data.theme is "light" |
|||
"light" |
|||
elif data.theme is "timed" |
|||
let hour = new Date!.getHours! |
|||
if hour > data.timed_theme_end or hour < data.timed_theme_start |
|||
"dark" |
|||
else |
|||
"light" |
|||
else |
|||
"dark" |
@ -0,0 +1 @@ |
|||
import "./main.imba" |
@ -0,0 +1,76 @@ |
|||
global.L = console.log |
|||
|
|||
import pkg from '../package.json' |
|||
let version = pkg.version |
|||
L "fuzzyhome version {version}" |
|||
|
|||
import state from './state.imba' |
|||
import api from './api.imba' |
|||
import config from './config.imba' |
|||
|
|||
import './components/app-home.imba' |
|||
import './components/app-settings.imba' |
|||
import './components/app-links.imba' |
|||
import './components/app-link.imba' |
|||
import './components/app-bang.imba' |
|||
import './components/app-tips.imba' |
|||
import './styles.imba' |
|||
|
|||
extend tag element |
|||
get state |
|||
state |
|||
get api |
|||
api |
|||
get config |
|||
config |
|||
|
|||
if config.data.focus and location.search =? "?x" |
|||
throw new Error |
|||
|
|||
global.Pins = {} |
|||
global.Frequencies = {} |
|||
|
|||
def init |
|||
let { pins } = await global.chrome.storage.sync.get 'pins' |
|||
Pins = pins or {} |
|||
|
|||
let { frequencies } = await global.chrome.storage.sync.get 'frequencies' |
|||
Frequencies = frequencies or {} |
|||
|
|||
global.chrome.bookmarks.getTree! do(bookmarks) |
|||
const folder = api.bfs 'Bookmarks Bar', bookmarks |
|||
state.links = api.traverse folder |
|||
api.sort_links! |
|||
state.loaded = yes |
|||
imba.commit! |
|||
|
|||
init! |
|||
|
|||
tag app |
|||
|
|||
<self |
|||
.light=(config.theme is "light") |
|||
.dark=(config.theme is "dark") |
|||
.disabled=state.loading |
|||
ease |
|||
> |
|||
css d:flex fld:column jc:start ai:center |
|||
m:0 w:100% h:100% bg:$bodybg |
|||
ff:sans-serif fw:1 |
|||
us:none |
|||
e:100ms |
|||
@off o:0 |
|||
|
|||
<.main> |
|||
css d:flex fld:column jc:start ai:center |
|||
bg:$appbg |
|||
w:80vw max-width:700px max-height:80vh |
|||
bxs:0px 0px 10px rgba(0,0,0,0.35) |
|||
box-sizing:border-box p:30px rd:10px mt:10vh |
|||
|
|||
if state.view is 'settings' |
|||
<app-settings> |
|||
else |
|||
<app-home> |
|||
|
|||
imba.mount <app> |
@ -1,9 +1,10 @@ |
|||
export default { |
|||
view: 'home' |
|||
query: '' |
|||
links: [] |
|||
sorted_links: [] |
|||
loading: no |
|||
link_selection_index: 0 |
|||
active_bang: no |
|||
bang_selection_index: -1 |
|||
loaded:no |
|||
} |
@ -0,0 +1,7 @@ |
|||
import { imba } from 'vite-plugin-imba'; |
|||
import { defineConfig } from 'vite'; |
|||
|
|||
export default defineConfig({ |
|||
base: '', |
|||
plugins: [imba()], |
|||
}); |