@ -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", |
"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": { |
"scripts": { |
||||
"build": "imba build app/index.html", |
"dev": "vite build -w --outDir chrome/dist --minify false", |
||||
"publish-preview": "npx gh-pages --no-history --dotfiles --dist dist/ --branch preview", |
"build": "vite build --outDir chrome/dist" |
||||
"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" |
|
||||
}, |
}, |
||||
"dependencies": { |
"dependencies": { |
||||
"dexie": "^3.2.2", |
"fzi": "^1.5.0", |
||||
"dexie-export-import": "^1.0.3", |
"imba": "^2.0.0-alpha.225", |
||||
"downloadjs": "^1.4.7", |
|
||||
"fzi": "^1.1.0", |
|
||||
"imba": "^2.0.0-alpha.220", |
|
||||
"lodash": "^4.17.21", |
"lodash": "^4.17.21", |
||||
"mathjs": "^11.1.0", |
"math-expression-evaluator": "^1.4.0", |
||||
"nanoid": "^4.0.0" |
"vite": "^3.2.5", |
||||
}, |
"vite-plugin-imba": "^0.10.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)" |
|
||||
} |
} |
||||
|
@ -1,158 +1,14 @@ |
|||||
<h1 align="center"> |
useful modifications: |
||||
<a href="https://fuzzyho.me/"> |
|
||||
fuzzyhome |
|
||||
</a> |
|
||||
</h1> |
|
||||
|
|
||||
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 |
This is the latest working preview of fuzzyhome which can be |
||||
1. Copy this link: `https://fuzzyho.me/`: |
found at https://preview.fuzzyhome.pages.dev/ |
||||
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) |
|
||||
|
|
||||
If the extension has an option for setting focus to the webpage instead of the address bar, be sure to enable it. |
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 |
||||
## Usage |
productivity booster if you use it properly. I haven't |
||||
|
experienced any runtime errors using it dozens or hundreds of |
||||
### Create |
times a day for months now, so it seems to be pretty stable. |
||||
Create a new link by typing a name and a url separated by a space. |
Export your links just in case :) |
||||
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 |
|
||||
|
@ -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 { |
export default { |
||||
|
view: 'home' |
||||
query: '' |
query: '' |
||||
links: [] |
links: [] |
||||
sorted_links: [] |
sorted_links: [] |
||||
loading: no |
loading: no |
||||
link_selection_index: 0 |
link_selection_index: 0 |
||||
active_bang: no |
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()], |
||||
|
}); |