You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

494 lines
12 KiB

3 years ago
let p = console.log
import { orderBy } from 'lodash'
2 years ago
import { version } from '../package.json'
import db from './db'
import fzi from 'fzi'
import download from 'downloadjs'
2 years ago
import { nanoid } from 'nanoid'
import { parse_url } from './utils'
let state = {
query: ''
links: []
scored_links: []
}
let config = {
search_engine: {}
}
global._fuzzyhome_delete_everything = do
return unless window.confirm "This will delete everything. Are you sure?"
indexedDB.deleteDatabase("fuzzyhome")
delete localStorage.fuzzyhome_config
delete localStorage.fuzzyhome_visited
p "fuzzyhome version {version}"
3 years ago
tag app
selection_index = 0
settings_active = no
loading = no
fatal_error = no
2 years ago
bang = no
get render? do mounted?
3 years ago
def mount
try
await reload_db!
p state.links
catch e
err "loading database", e
fatal_error = yes
return
unless global.localStorage.fuzzyhome_visited
add_initial_links!
global.localStorage.fuzzyhome_visited = yes
2 years ago
await load_config!
def add_initial_links
add_link { name: "click here to learn how to use this tool effectively", url: "github.com/familyfriendlymikey/fuzzyhome", frequency: 1 }
add_link { name: "google", url: "google.com" }
add_link { name: "youtube", url: "youtube.com" }
add_link { name: "photopea", url: "photopea.com" }
add_link { name: "twitch", url: "twitch.tv" }
add_link { name: "messenger", url: "messenger.com" }
add_link { name: "instagram", url: "instagram.com" }
add_link { name: "localhost 3000", url: "http://localhost:3000" }
def validate_config
2 years ago
throw 'config error' unless config..search_engine.hasOwnProperty 'url'
throw 'config error' unless config..search_engine.hasOwnProperty 'icon'
throw 'config error' unless config..search_engine.hasOwnProperty 'frequency'
def reset_config
2 years ago
p "resetting config"
let url = 'https://www.google.com/search?q='
let frequency = 0
let icon = await fetch_image_as_base_64 'google.com'
config.search_engine = { url, icon, frequency }
save_config!
def save_config
global.localStorage.fuzzyhome_config = JSON.stringify(config)
def load_config
try
config = JSON.parse(global.localStorage.fuzzyhome_config)
validate_config!
catch
2 years ago
await reset_config!
def err s, e
p e
window.alert("Error {s}:\n\n{e}")
3 years ago
def reload_db
state.links = await db.links.toArray()
sort_links!
3 years ago
get can_add
return no if loading
return no if settings_active
get_valid_link(state.query)
def sort_links
if state.query.trim!.length > 0
state.scored_links = fzi state.links, state.query
else
state.scored_links = orderBy(state.links, 'frequency', 'desc')
def increment_link_frequency link
try
await db.links.update link.id, { frequency: link.frequency + 1 }
catch e
err "putting link", e
2 years ago
def toggle_settings
settings_active = !settings_active
def increment_selection_index
selection_index = Math.min(state.scored_links.length - 1, selection_index + 1)
def decrement_selection_index
selection_index = Math.max(0, selection_index - 1)
def increment_search_engine_frequency
config.search_engine.frequency += 1
save_config!
3 years ago
get encoded_search_query
2 years ago
"{config.search_engine.url}{window.encodeURIComponent(state.query)}"
get encoded_bang_query
"{bang.url}{window.encodeURIComponent(state.query)}"
3 years ago
def use_search_engine
increment_search_engine_frequency!
window.location.href = encoded_search_query
3 years ago
def fetch_image_as_base_64 host
let fallback = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAH0lEQVR42mO8seXffwYqAsZRA0cNHDVw1MBRA0eqgQCDRkbJSQHxEQAAAABJRU5ErkJggg=='
return new Promise! do |resolve|
3 years ago
let res
try
res = await global.fetch("https://icon.horse/icon/{host}")
3 years ago
catch
p "Failed to get icon from icon horse."
resolve fallback
3 years ago
return
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
3 years ago
return
reader.readAsDataURL(blob)
def get_valid_link text
text = text.trim!
return no if text === ''
let split_text = text.split(/\s+/)
return no if split_text.length < 2
let url = split_text.pop!
let name = split_text.join(" ")
{ name, url }
3 years ago
def add_link { url, name, frequency=0 }
name = name.trim!
let host
try
{ href:url, host } = parse_url url
catch e
return err "parsing url", e
let img = await fetch_image_as_base_64 host
2 years ago
let id = nanoid!
let link = { id, name, url, frequency, img }
try
2 years ago
await db.links.add link
await reload_db!
catch e
err "adding link", e
imba.commit!
3 years ago
def handle_click_link link
await increment_link_frequency link
window.location.href = link.url
3 years ago
def handle_click_search
increment_search_engine_frequency!
2 years ago
def handle_bang
return unless bang
let url = encoded_bang_query
await increment_link_frequency bang
window.location.href = url
def handle_click_bang
handle_bang!
def handle_return
2 years ago
if bang
handle_bang!
elif state.scored_links.length < 1
use_search_engine!
else
let link = state.scored_links[selection_index]
await increment_link_frequency link
window.location.href = link.url
2 years ago
def handle_tab
return bang = no if bang
return unless state.scored_links.length > 0
state.query = ''
bang = state.scored_links[selection_index]
def handle_click_delete link
handle_delete link
def handle_shift_backspace
return unless state.scored_links.length > 0
handle_delete state.scored_links[selection_index]
def handle_delete link
loading = yes
let delete_link = do
return unless window.confirm "Do you really want to delete {link..name}?"
try
await db.links.delete(link.id)
catch e
err "deleting link", e
try
await reload_db!
catch e
err "reloading db after successful delete", e
selection_index = Math.min selection_index, state.scored_links.length - 1
await delete_link!
loading = no
def handle_shift_return
handle_add!
def handle_click_add
handle_add!
def handle_add
loading = yes
let link = get_valid_link(state.query)
unless link
err "adding link", "Invalid link."
return
await add_link(link)
state.query = ''
sort_links!
loading = no
3 years ago
def handle_input
selection_index = 0
sort_links!
3 years ago
def handle_click_import e
loading = yes
let id_exists = do |newid|
state.links.some! do |{id}| newid === id
let filter = do |table, value, key|
table === 'links' and not id_exists value.id
try
await reload_db!
await db.import(e.target.files[0], { filter })
await reload_db!
catch e
err "importing db", e
settings_active = no
loading = no
3 years ago
def handle_click_export
loading = yes
let datetime = new Date!.toString!.split(" ")
let date = datetime.slice(1, 4).join("-").toLowerCase!
let time = datetime[4].split(":").join("-")
let filename = "fuzzyhome_{date}_{time}.json"
const blob = await db.export({ prettyJson: yes })
download(blob, filename, "application/json")
settings_active = no
loading = no
def handle_click_config
loading = yes
await (do
let input = window.prompt "Please enter the URL of your search engine."
return if input === null
try
var { href: url, host } = parse_url input
catch e
return err "changing search engine", e
let icon = await fetch_image_as_base_64 host
Object.assign config.search_engine, { url, icon }
save_config!)!
settings_active = no
loading = no
3 years ago
def handle_click_github
global.location.href = "https://github.com/familyfriendlymikey/fuzzyhome"
def handle_paste e
return if state.query.length > 0
global.setTimeout(&, 0) do
use_search_engine!
get pretty_date
Date!.toString!.split(" ").slice(0, 4).join(" ")
3 years ago
def render
<self>
css body
d:flex fld:column jc:flex-start ai:center
m:0 w:100% h:100% bg:#20222f
ff:sans-serif fw:1
user-select:none
3 years ago
css self
d:flex fld:column jc:flex-start ai:center
w:80vw max-width:700px max-height:80vh
3 years ago
bxs:0px 0px 10px rgba(0,0,0,0.35)
box-sizing:border-box p:30px rd:10px mt:10vh
3 years ago
css .fatal
c:blue2
3 years ago
css $main-input
w:100% h:50px px:20px
fs:20px ta:center
3 years ago
bd:1px solid purple4
bg:purple4/10 c:blue3 caret-color:blue3
outline:none rd:5px
@placeholder fs:10px c:blue3
3 years ago
css .loading-container
d:flex fld:row jc:space-around ai:center
w:100% h:50px
bg:purple4/10 rd:5px c:gray4
css .settings-container
d:flex fld:row jc:space-around ai:center
w:100% h:50px
bg:purple4/10 rd:5px
css .settings-button
d:flex fld:column jc:center ai:center fl:1
bg:none c:purple4 bd:none cursor:pointer fs:14px
css .middle-button
d:flex fld:column jc:center ai:center
h:35px c:purple4 fs:20px cursor:pointer
fs:20px
css .disabled
c:gray4 cursor:default user-select:none
3 years ago
css .links
d:flex fld:column jc:flex-start fl:1
w:100% ofy:auto
3 years ago
css .link
d:flex fld:row jc:space-between ai:center
px:15px py:10px rd:5px cursor:pointer c:blue3
css .link-left
d:flex fl:1
css .selected
bg:blue3/5
3 years ago
css a
2 years ago
td:none
css .link-icon
w:20px h:20px mr:10px rd:3px
css .name
tt:capitalize fs:20px
overflow-wrap:anywhere
css .search
tt:none word-break:break-all
css .link-right
d:flex fld:row jc:space-between ai:center w:70px
3 years ago
css .delete
o:0
px:7px rd:3px c:purple4 fs:15px cursor:pointer
bd:1px solid purple4/50
css .selected .delete
o:100
css .frequency
fs:15px
if fatal_error
<.fatal>
"""
There was an error 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.
"""
else
if settings_active
<.settings-container>
<label.settings-button .disabled=loading>
"IMPORT"
<input[d:none]
disabled=loading
@change=handle_click_import
@click=(this.value = '')
type="file"
>
<.settings-button
.disabled=loading
@click.if(!loading)=handle_click_export
> "EXPORT"
<.settings-button
.disabled=loading
@click.if(!loading)=handle_click_config
> "CONFIG"
<.settings-button
.disabled=loading
@click.if(!loading)=handle_click_github
> "GITHUB"
else
<input$main-input
bind=state.query
placeholder=pretty_date
@hotkey('return').capture=handle_return
@hotkey('shift+return').capture.if(can_add)=handle_shift_return
@hotkey('shift+backspace').capture=handle_shift_backspace
@hotkey('down').capture=increment_selection_index
@hotkey('up').capture=decrement_selection_index
2 years ago
@hotkey('tab').capture=handle_tab
@input=handle_input
@paste=handle_paste
@blur=this.focus
.disabled=loading
disabled=loading
>
if state.query.trim!.split(/\s+/).length < 2
<.middle-button
[mt:-10px py:5px fs:25px]
.disabled=loading
@click.if(!loading)=toggle_settings
> "..."
elif can_add
<.middle-button@click=handle_click_add> "+"
3 years ago
else
<.middle-button.disabled> "+"
<.links>
2 years ago
if bang
<a.link.selected
href=encoded_bang_query
@click=handle_click_bang
>
<.link-left>
<img.link-icon src=bang.img>
<.name.search> encoded_bang_query
2 years ago
<.link-right[jc:flex-end]>
<.frequency> bang.frequency
elif state.scored_links.length > 0
for link, index in state.scored_links
<a.link
href=link.url
@pointerover=(selection_index = index)
@click.prevent=handle_click_link(link)
.selected=(index == selection_index)
>
<.link-left>
<img.link-icon src=link.img>
<.name> link.name
<.link-right>
<.delete@click.prevent.stop=handle_click_delete(link)> "x"
<.frequency> link.frequency
else
<a.link.selected
href=encoded_search_query
@click=handle_click_search
>
<.link-left>
<img.link-icon src=config.search_engine.icon>
<.name.search> encoded_search_query
<.link-right[jc:flex-end]>
<.frequency> config.search_engine.frequency
$main-input.focus!
3 years ago
imba.mount <app>