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