first iteration

This commit is contained in:
Nato Boram 2021-10-24 22:41:37 -04:00
parent 36f12d4ea6
commit 1692d789dd
No known key found for this signature in database
GPG Key ID: 478E3C64BF88AFFA
10 changed files with 1521 additions and 0 deletions

8
.editorconfig Normal file
View 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
View 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
View File

@ -0,0 +1,4 @@
{
"arrowParens": "avoid",
"semi": false
}

8
.vscode/extensions.json vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff