first iteration
This commit is contained in:
parent
36f12d4ea6
commit
1692d789dd
8
.editorconfig
Normal file
8
.editorconfig
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
20
.eslintrc.json
Normal file
20
.eslintrc.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"es2021": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"prettier",
|
||||||
|
"plugin:prettier/recommended"
|
||||||
|
],
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": "latest",
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"eqeqeq": "error",
|
||||||
|
"semi": "off",
|
||||||
|
"prettier/prettier": "off"
|
||||||
|
}
|
||||||
|
}
|
4
.prettierrc.json
Normal file
4
.prettierrc.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"semi": false
|
||||||
|
}
|
8
.vscode/extensions.json
vendored
Normal file
8
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"esbenp.prettier-vscode",
|
||||||
|
"ms-vscode.live-server",
|
||||||
|
"davidanson.vscode-markdownlint"
|
||||||
|
]
|
||||||
|
}
|
20
.vscode/settings.json
vendored
Normal file
20
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"[html]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[javascript]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[json]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[markdown]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[yaml]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"editor.rulers": [
|
||||||
|
80
|
||||||
|
]
|
||||||
|
}
|
10
index.css
Normal file
10
index.css
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#cursor {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#message {
|
||||||
|
transition-property: opacity;
|
||||||
|
transition-duration: 0.3s;
|
||||||
|
}
|
106
index.html
Normal file
106
index.html
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
href="https://icons.getbootstrap.com/assets/icons/google.svg"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Bootstrap -->
|
||||||
|
<link
|
||||||
|
href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
|
||||||
|
crossorigin="anonymous"
|
||||||
|
/>
|
||||||
|
<script
|
||||||
|
src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.10.2/dist/umd/popper.min.js"
|
||||||
|
integrity="sha384-7+zCNj/IqJ95wo16oMtfsKbZ9ccEh31eOz1HGyDuCQ6wgnyJNSYdrPa03rtR1zdB"
|
||||||
|
crossorigin="anonymous"
|
||||||
|
></script>
|
||||||
|
<script
|
||||||
|
src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.min.js"
|
||||||
|
integrity="sha384-QJHtvGhmr9XOIpI6YVutG+2QOK9T+ZnN4kzFN1RtK3zEFEIsxhlmWl5/YESvpZ13"
|
||||||
|
crossorigin="anonymous"
|
||||||
|
></script>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.6.1/font/bootstrap-icons.css"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="index.css" />
|
||||||
|
<script src="index.js"></script>
|
||||||
|
|
||||||
|
<title>Let me Google that for you</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<form class="row g-3 text-center mt-5">
|
||||||
|
<!-- Google -->
|
||||||
|
<label class="display-1 form-label fw-normal" for="input">
|
||||||
|
<span class="text-primary">G</span><span class="text-danger">o</span
|
||||||
|
><span class="text-warning">o</span><span class="text-primary">g</span
|
||||||
|
><span class="text-success">l</span><span class="text-danger">e</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<!-- Input -->
|
||||||
|
<div
|
||||||
|
class="
|
||||||
|
bg-body
|
||||||
|
border border-1 border-light
|
||||||
|
input-group
|
||||||
|
rounded-pill
|
||||||
|
shadow-sm
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="input-group-text bi-search bg-body border-0"
|
||||||
|
for="input"
|
||||||
|
id="searchicon"
|
||||||
|
></label>
|
||||||
|
<input
|
||||||
|
class="form-control border-0 bg-body"
|
||||||
|
id="input"
|
||||||
|
maxlength="2048"
|
||||||
|
name="search"
|
||||||
|
placeholder="Search"
|
||||||
|
required
|
||||||
|
title="Search"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Actions -->
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
id="search"
|
||||||
|
class="btn btn-light"
|
||||||
|
type="submit"
|
||||||
|
onclick="onClickSearch()"
|
||||||
|
>
|
||||||
|
Google Search
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
id="lucky"
|
||||||
|
class="btn btn-light"
|
||||||
|
title="Open the first search result"
|
||||||
|
type="submit"
|
||||||
|
onclick="onClickLucky()"
|
||||||
|
>
|
||||||
|
I'm feeling lucky
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Alert -->
|
||||||
|
<div id="message" class="alert text-start opacity-0">
|
||||||
|
<h5 id="message-heading" class="alert-heading"></h5>
|
||||||
|
<div id="message-content"></div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
128
index.js
Normal file
128
index.js
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
"use strict"
|
||||||
|
|
||||||
|
/** @type {{search?: string; lucky?: boolean;}} */
|
||||||
|
// @ts-ignore
|
||||||
|
const query = window.location.search
|
||||||
|
.substr(1)
|
||||||
|
.split("&")
|
||||||
|
.map(keyValue => keyValue.split("="))
|
||||||
|
.map(([key, value]) => ({
|
||||||
|
[decodeURIComponent(key)]: decodeURIComponent(value?.replace("+", "%20")),
|
||||||
|
}))
|
||||||
|
.reduce((previous, current) => ({ ...previous, ...current }), {})
|
||||||
|
|
||||||
|
/** @type {HTMLInputElement} */
|
||||||
|
let input
|
||||||
|
|
||||||
|
window.addEventListener("load", async () => {
|
||||||
|
// @ts-ignore
|
||||||
|
input = document.getElementById("input")
|
||||||
|
input.value = ""
|
||||||
|
|
||||||
|
if (!query.search) return
|
||||||
|
|
||||||
|
await setMessage("Step 1", "Type in your search")
|
||||||
|
const cursor = makeCursor()
|
||||||
|
await move(cursor, input)
|
||||||
|
input.focus()
|
||||||
|
await write()
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||||
|
input.blur()
|
||||||
|
|
||||||
|
await setMessage("Step 2", "Click on the search button")
|
||||||
|
const button = query.lucky
|
||||||
|
? document.getElementById("lucky")
|
||||||
|
: document.getElementById("search")
|
||||||
|
await move(cursor, button)
|
||||||
|
button.focus()
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||||
|
|
||||||
|
await setMessage("Come on", "Was it that hard?", "alert-success")
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 3000))
|
||||||
|
|
||||||
|
window.location.href = encodeURI(
|
||||||
|
`https://www.google.com/search?q=${query.search}${
|
||||||
|
query.lucky ? "&btnI" : ""
|
||||||
|
}`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
function makeCursor() {
|
||||||
|
const cursor = document.createElement("span")
|
||||||
|
cursor.className = "bi-cursor-fill"
|
||||||
|
cursor.id = "cursor"
|
||||||
|
document.body.appendChild(cursor)
|
||||||
|
return cursor
|
||||||
|
}
|
||||||
|
|
||||||
|
async function move(cursor, target) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const diffX =
|
||||||
|
target.getBoundingClientRect().left +
|
||||||
|
target.clientWidth / 2 -
|
||||||
|
cursor.getBoundingClientRect().left
|
||||||
|
const diffY =
|
||||||
|
target.getBoundingClientRect().top +
|
||||||
|
target.clientHeight / 2 -
|
||||||
|
cursor.getBoundingClientRect().top
|
||||||
|
|
||||||
|
const steps = 60
|
||||||
|
const stepX = diffX / steps
|
||||||
|
const stepY = diffY / steps
|
||||||
|
|
||||||
|
let step = 0
|
||||||
|
const interval = setInterval(frame, 1000 / 60)
|
||||||
|
|
||||||
|
function frame() {
|
||||||
|
if (step >= steps) {
|
||||||
|
clearInterval(interval)
|
||||||
|
resolve()
|
||||||
|
} else {
|
||||||
|
step++
|
||||||
|
cursor.style.top = (parseFloat(cursor.style.top) || 0) + stepY + "px"
|
||||||
|
cursor.style.left = (parseFloat(cursor.style.left) || 0) + stepX + "px"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function write() {
|
||||||
|
for (const letter of query.search) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, Math.random() * 200 + 100))
|
||||||
|
input.value += letter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setMessage(heading, content, type = "alert-primary") {
|
||||||
|
const message = document.getElementById("message")
|
||||||
|
|
||||||
|
message.classList.add("opacity-0")
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 300))
|
||||||
|
|
||||||
|
message.classList.remove("alert-primary")
|
||||||
|
message.classList.remove("alert-success")
|
||||||
|
message.classList.add(type)
|
||||||
|
document.getElementById("message-heading").innerText = heading
|
||||||
|
document.getElementById("message-content").innerText = content
|
||||||
|
|
||||||
|
message.classList.remove("opacity-0")
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 300))
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
function onClickSearch() {
|
||||||
|
onClickButton(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
function onClickLucky() {
|
||||||
|
onClickButton(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClickButton(lucky = false) {
|
||||||
|
if (!input.validity.valid) return
|
||||||
|
const url = new URL(window.location.href)
|
||||||
|
url.searchParams.set("search", input.value.trim())
|
||||||
|
if (lucky) url.searchParams.set("lucky", "true")
|
||||||
|
window.location.href = url.href
|
||||||
|
}
|
27
package.json
Normal file
27
package.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "lmgtfy",
|
||||||
|
"description": "Let me Google that for you",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/NatoBoram/lmgtfy.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"Google",
|
||||||
|
"LMGTFY"
|
||||||
|
],
|
||||||
|
"author": "Nato Boram",
|
||||||
|
"license": "GPL-3.0-or-later",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/NatoBoram/lmgtfy/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/NatoBoram/lmgtfy#readme",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bootstrap": "^5.1.6",
|
||||||
|
"eslint": "^8.1.0",
|
||||||
|
"eslint-config-prettier": "^8.3.0",
|
||||||
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
|
"prettier": "^2.4.1",
|
||||||
|
"prettier-eslint": "^13.0.0"
|
||||||
|
}
|
||||||
|
}
|
1190
pnpm-lock.yaml
Normal file
1190
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user