├── .eslintignore ├── .eslintrc.cjs ├── .gitignore ├── .npmrc ├── README.md ├── package.json ├── playwright.config.ts ├── renovate.json ├── src ├── app.d.ts ├── app.html ├── index.test.ts ├── lib │ ├── components │ │ ├── accordion.svelte │ │ ├── constants.ts │ │ ├── editors │ │ │ └── js-editor.svelte │ │ ├── heart-icon │ │ │ ├── heart.svelte │ │ │ └── heart.svg │ │ └── page-meta.svelte │ └── styles │ │ └── global.scss ├── routes │ ├── (app) │ │ ├── +layout.svelte │ │ └── +page.svelte │ ├── +error.svelte │ ├── +layout.ts │ └── bookmarklets │ │ └── esseeoh │ │ ├── +layout.svelte │ │ └── +page.svelte └── service-worker.ts ├── static ├── chat-bubble.svg ├── default-og-image.webp ├── default-twitter-image.webp ├── favicon.png ├── file.svg ├── flame-no-connection.webp ├── flat-bookmark.svg ├── fonts │ ├── LexendDeca-Bold.woff2 │ ├── LexendDeca-ExtraLight.woff2 │ └── LexendDeca-Regular.woff2 ├── planning.webp ├── robots.txt └── triangle.webp ├── svelte.config.js ├── tests └── test.ts ├── tsconfig.json ├── vite.config.ts └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], 5 | plugins: ['svelte3', '@typescript-eslint'], 6 | ignorePatterns: ['*.cjs'], 7 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], 8 | settings: { 9 | 'svelte3/typescript': () => require('typescript') 10 | }, 11 | parserOptions: { 12 | sourceType: 'module', 13 | ecmaVersion: 2020 14 | }, 15 | env: { 16 | browser: true, 17 | es2017: true, 18 | node: true 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | /.vscode 10 | .cert 11 | vite.config.js.timestamp-* 12 | vite.config.ts.timestamp-* 13 | 14 | # Ignore files for PNPM, NPM and YARN 15 | pnpm-lock.yaml 16 | package-lock.json 17 | yarn-error.log 18 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![MadeWithSvelte.com shield](https://madewithsvelte.com/storage/repo-shields/4283-shield.svg)](https://madewithsvelte.com/p/make-bookmarklets/shield-link) 2 | # Make Bookmarklets 3 | ![This is an image](https://raw.githubusercontent.com/Blumed/make-bookmarklets/main/static/default-og-image.webp) 4 | 5 | ## A simple bookmarklet generator 🤖 6 | 7 | Bookmarklets are great for micro to macro automation. Extend what you can do in your browser any way you see fit. 8 | 9 | - [x] All code is ready to go as a bookmarklet, so just write some Javascript and have fun 10 | 11 | - [x] Eslint in the browser 12 | 13 | - [x] Javascript intellisense 14 | 15 | - [x] Automatic minification and uglification 16 | 17 | - [x] Works great on all devices 18 | 19 | --- 20 | 21 | # Working together 22 | 23 | Just a heads up if you are going to run this site locally you will need to generate a local SSL. 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "make-bookmarklets", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite dev", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "test": "playwright test", 10 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 11 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 12 | "test:unit": "vitest", 13 | "clean": "find . -name \".DS_Store\" -print -delete", 14 | "lint": "eslint .", 15 | "postbuild": "svelte-sitemap --domain https://make-bookmarklets.com --change-freq weekly --reset-time -i '404.html'" 16 | }, 17 | "devDependencies": { 18 | "@codemirror/lang-javascript": "^6.1.4", 19 | "@codemirror/lint": "^6.1.0", 20 | "@codemirror/theme-one-dark": "^6.1.0", 21 | "@playwright/test": "^1.28.1", 22 | "@sveltejs/adapter-static": "^2.0.1", 23 | "@sveltejs/kit": "^1.5.0", 24 | "@types/uglify-js": "^3.17.1", 25 | "@typescript-eslint/eslint-plugin": "^5.45.0", 26 | "@typescript-eslint/parser": "^5.45.0", 27 | "codemirror": "^6.0.1", 28 | "eslint": "^8.28.0", 29 | "eslint-linter-browserify": "^8.34.0", 30 | "eslint-plugin-svelte3": "^4.0.0", 31 | "sass": "^1.58.2", 32 | "svelte": "^4.0.0", 33 | "svelte-check": "^3.0.1", 34 | "svelte-codemirror-editor": "^1.1.0", 35 | "svelte-sitemap": "^2.6.0", 36 | "terser": "^5.16.3", 37 | "tslib": "^2.4.1", 38 | "typescript": "^5.0.0", 39 | "vite": "^4.0.0", 40 | "vitest": "^0.32.0" 41 | }, 42 | "type": "module", 43 | "dependencies": {} 44 | } 45 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import type { PlaywrightTestConfig } from '@playwright/test'; 2 | 3 | const config: PlaywrightTestConfig = { 4 | webServer: { 5 | command: 'npm run build && npm run preview', 6 | port: 4173 7 | }, 8 | testDir: 'tests' 9 | }; 10 | 11 | export default config; 12 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface Platform {} 9 | } 10 | } 11 | 12 | export {}; 13 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | %sveltekit.head% 12 | 13 | 14 | 15 | 16 |
%sveltekit.body%
17 | 18 | 19 | -------------------------------------------------------------------------------- /src/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | describe('sum test', () => { 4 | it('adds 1 + 2 to equal 3', () => { 5 | expect(1 + 2).toBe(3); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/lib/components/accordion.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |

9 | 16 |

17 | 18 | {#key isExpanded} 19 | 28 | {/key} 29 | 30 | 90 | -------------------------------------------------------------------------------- /src/lib/components/constants.ts: -------------------------------------------------------------------------------- 1 | export const useExampleBookmarklet1 = `const source = "https://assets.make-bookmarklets.com/thanks-ac42db36.mp3"; 2 | const audio = new Audio(); 3 | 4 | audio.addEventListener("load", () => { 5 | audio.play(); 6 | }, true); 7 | 8 | audio.src = source; 9 | audio.autoplay = true;`; 10 | 11 | export const useExampleBookmarklet2 = `const capture = async () => { 12 | const canvas = document.createElement("canvas"); 13 | const context = canvas.getContext("2d"); 14 | const video = document.createElement("video"); 15 | 16 | try { 17 | const captureStream = await navigator.mediaDevices.getDisplayMedia(); 18 | video.srcObject = captureStream; 19 | context.drawImage(video, 0, 0, window.width, window.height); 20 | const frame = canvas.toDataURL("image/png"); 21 | captureStream.getTracks().forEach(track => track.stop()); 22 | window.location.href = frame; 23 | } catch (err) { 24 | console.error('Full page screenshot bookmarklet did not work: ' + err); 25 | } 26 | }; 27 | 28 | capture();`; 29 | export const useExampleBookmarklet3 = `function pageSpeedInsights(currentSite){ 30 | try { 31 | window.open('https://pagespeed.web.dev/report?url=' + currentSite, '_blank', 'noopener,noreferrer'); 32 | } catch (error){ 33 | console.log('pagespeed insights bookmarklet did not work: ', error); 34 | } 35 | }; 36 | pageSpeedInsights(window.location.href);`; 37 | export const useExampleBookmarklet4 = `try { 38 | navigator.clipboard.writeText('ADD WHAT EVER TEXT YOU WANT HERE'); 39 | } catch(error) { 40 | console.log('Copy To Clipboard Bookmarklet did not work: ', error); 41 | }`; 42 | export const useExampleBookmarklet5 = `try { 43 | const highlightedText = window.getSelection().toString(); 44 | window.open('http://google.com/search?q=site:' + window.location.hostname + ' "' + highlightedText + '"'); 45 | } catch(error) { 46 | console.log('Google If Text Indexed Bookmarklet did not work: ', error); 47 | }`; 48 | export const useExampleSnippet1 = `const container = document.createElement('div'); 49 | container.className = 'xxxxxx_wraper_xxxxx'; 50 | container.textContent = 'Give me some data, or maybe some buttons?'; 51 | container.styles = 'width: 100%;letter-spacing:1px;background-color:pink;text-align:right;font-family:helvetica !important;font-weight:100;padding-inline: 60px;color:#161613;margin:auto;top:0;right:0;left:0;position:fixed;z-index:2147483647;font-size:18px;text-rendering:optimizeLegibility;text-align:center;line-height:36px;'; 52 | container.setAttribute('style', container.styles); 53 | document.body.appendChild(container); 54 | 55 | const close = document.createElement('button'); 56 | close.setAttribute('type', 'button'); 57 | close.className = 'xxxxxx_close_xxxxx'; 58 | close.textContent = 'x'; 59 | close.setAttribute('style', 'position:absolute;left:30px;top:8px !important;background-color:white;border-radius:50%;color:black;z-index:30;padding:0;font-size:15px;font-weight:100;width:20px;height:20px;cursor:pointer;display:flex;justify-content:center;align-items:flex-start;border:1px solid black;line-height:16px;'); 60 | close.onclick = () => container.remove(); 61 | container.appendChild(close);`; 62 | export const useExampleSnippet2 = `const container = document.createElement('dialog'); 63 | 64 | function createDialog() { 65 | const close = document.createElement('button'); 66 | 67 | container.setAttribute('style', 'width: 100%;letter-spacing:1px;background-color:pink;font-family:helvetica !important;font-weight:100;border-radius:12px;padding-inline:60px;color:#161613;border-color:#fff;position:fixed;inset:0;margin:auto;width:100%;max-width:400px;z-index:2147483647;font-size:18px;text-rendering:optimizeLegibility;text-align:left;line-height:36px;'); 68 | 69 | close.setAttribute('style', 'position:absolute;right:10px;top:8px !important;background-color:white;border-radius:50%;color:black;z-index:30;padding:0 0 0 1px;font-size:15px;font-weight:100;width:20px;height:20px;cursor:pointer;display:flex;justify-content:center;align-items:flex-start;border:1px solid black;line-height:16px;'); 70 | close.textContent = 'x'; 71 | close.onclick = () => container.remove(); 72 | 73 | document.body.appendChild(container); 74 | container.appendChild(close); 75 | 76 | createContent('h1'); 77 | createContent('h2'); 78 | createContent('h3'); 79 | createContent('h4'); 80 | createContent('h5'); 81 | createContent('h6'); 82 | container.showModal(); 83 | } 84 | 85 | function createContent(header) { 86 | const p = document.createElement('p'); 87 | const strong = document.createElement('strong'); 88 | 89 | strong.textContent = document.getElementsByTagName(header).length; 90 | 91 | p.textContent = header + ': '; 92 | 93 | container.appendChild(p); 94 | p.appendChild(strong); 95 | } 96 | 97 | createDialog();`; 98 | export const config = { 99 | parserOptions: { 100 | ecmaVersion: 2019, 101 | sourceType: "module", 102 | }, 103 | env: { 104 | browser: true, 105 | node: true, 106 | }, 107 | rules: { 108 | semi: ["warn", "always"], 109 | "valid-typeof": ["error", "always"], 110 | "no-unused-vars": ["error", "always"], 111 | "no-unreachable": ["error", "always"], 112 | "no-dupe-args": ["error", "always"], 113 | "no-dupe-else-if": ["error", "always"], 114 | "no-console": ["warn", "always"], 115 | }, 116 | }; -------------------------------------------------------------------------------- /src/lib/components/editors/js-editor.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 21 | -------------------------------------------------------------------------------- /src/lib/components/heart-icon/heart.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | increaseLove()} 10 | tabindex="0" 11 | role="button" 12 | aria-label="Click and my heart grows" 13 | > 14 | 27 | 28 | 29 | 52 | -------------------------------------------------------------------------------- /src/lib/components/heart-icon/heart.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/lib/components/page-meta.svelte: -------------------------------------------------------------------------------- 1 | 22 | 23 | 24 | {pageTitle} 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/lib/styles/global.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-display: swap; 3 | font-family: "Lexend Deca"; 4 | font-style: normal; 5 | font-weight: 200; 6 | src: url("/fonts/LexendDeca-ExtraLight.woff2") format('woff2'); 7 | } 8 | @font-face { 9 | font-display: swap; 10 | font-family: "Lexend Deca"; 11 | font-style: bold; 12 | font-weight: 700; 13 | src: url("/fonts/LexendDeca-Bold.woff2") format('woff2'); 14 | } 15 | @font-face { 16 | font-display: swap; 17 | font-family: "Lexend Deca"; 18 | font-style: normal; 19 | font-weight: normal; 20 | src: url("/fonts/LexendDeca-Regular.woff2") format('woff2'); 21 | } 22 | 23 | :root { 24 | font-family: 'Lexend Deca', Arial, -apple-system, sans-serif; 25 | --font-base: 16px; 26 | --clay-color: #edece8; 27 | --pink-color: #ec6e89; 28 | --red-color: #d53253; 29 | --dark-red-color: #b80000; 30 | --turquoise-color: #b9e7de; 31 | --purple-color: #bc98cb; 32 | --global-transition: all 0.3s ease-in-out; 33 | --black-color: #161613; 34 | } 35 | 36 | *, :after, :before { 37 | box-sizing: border-box;; 38 | } 39 | 40 | body { 41 | font-family: inherit; 42 | font-size: var(--font-base); 43 | background-color: var(--clay-color); 44 | color: var(--black-color); 45 | margin: 0; 46 | overflow-x: hidden; 47 | } 48 | 49 | p { 50 | font-weight: 300; 51 | letter-spacing: .7px; 52 | line-height: 25px; 53 | font-weight: 200; 54 | } 55 | li { 56 | font-weight: 200; 57 | letter-spacing: 0.7px; 58 | 59 | } 60 | 61 | a { 62 | color: var(--red-color); 63 | font-weight: 400; 64 | text-decoration: none; 65 | } 66 | 67 | 68 | .section { 69 | padding: 60px 5%; 70 | } 71 | 72 | .container { 73 | max-width: 1250px; 74 | margin-inline: auto; 75 | } 76 | 77 | .input { 78 | padding: 10px; 79 | width: 100%; 80 | border: 2px solid var(--black-color); 81 | border-radius: 0.25rem; 82 | } 83 | 84 | .container-small { 85 | max-width: 900px; 86 | margin-inline: auto; 87 | } 88 | 89 | .button, [role="button"] { 90 | font-family: inherit; 91 | display: inline-block; 92 | font-size: 16px; 93 | font-weight: 500; 94 | letter-spacing: 1px; 95 | padding: 15px 20px; 96 | border: 2px solid currentColor; 97 | color: var(--black-color); 98 | border-radius: 0.25rem; 99 | background-color: transparent; 100 | box-shadow: 6px 6px 0 0 var(--black-color); 101 | position: relative; 102 | user-select: none; 103 | -webkit-user-select: none; 104 | touch-action: manipulation; 105 | text-decoration: none; 106 | transition: var(--global-transition); 107 | line-height: 1; 108 | cursor: pointer; 109 | 110 | &:hover { 111 | transform: translate(2px,2px); 112 | box-shadow: 3px 3px 0 0 var(--black-color) !important; 113 | } 114 | 115 | &:active { 116 | transform: translate(4px,4px); 117 | box-shadow: 1px 1px 0 0 var(--black-color) !important; 118 | } 119 | 120 | &.fill-white { 121 | background-color: #ffffff; 122 | &:hover { 123 | background-color: var(--turquoise-color); 124 | } 125 | } 126 | 127 | &.fill-blue { 128 | background-color: var(--turquoise-color); 129 | &:hover { 130 | background-color: var(--turquoise-color); 131 | } 132 | } 133 | 134 | &.fill-clay { 135 | background-color: var(--clay-color); 136 | &:hover { 137 | background-color: var(--clay-color); 138 | } 139 | } 140 | 141 | &.button-small { 142 | padding: 12px 15px; 143 | font-size: 0.875rem; 144 | } 145 | } 146 | 147 | .button-close { 148 | display: inline-block; 149 | z-index: 8; 150 | top: 16px; 151 | right: 32px; 152 | background-color: #fff; 153 | margin: 0; 154 | cursor: pointer; 155 | width: 20px; 156 | height: 20px; 157 | border: 0.15em solid var(--black-color); 158 | border-radius: 0.15rem; 159 | overflow: hidden; 160 | &:checked { 161 | display: inline-block !important; 162 | } 163 | &:after { 164 | content: ""; 165 | width: 2px; 166 | height: 12px; 167 | background-color: var(--black-color); 168 | display: block; 169 | top: 2px; 170 | left: calc(50% - 1px); 171 | position: absolute; 172 | rotate: 45deg; 173 | animation-name: slideIn; 174 | animation-duration: .5s; 175 | } 176 | &:before { 177 | content: ""; 178 | width: 2px; 179 | height: 12px; 180 | background-color: black; 181 | display: block; 182 | top: 2px; 183 | left: calc(50% - 1px); 184 | position: absolute; 185 | rotate: -45deg; 186 | animation-name: oppositeSlideIn; 187 | animation-duration: .5s; 188 | } 189 | } 190 | 191 | @keyframes slideIn { 192 | from { 193 | top: 100%; 194 | left: -30%; 195 | } 196 | to { 197 | top: 2px; 198 | left: calc(50% - 1px); 199 | } 200 | } 201 | 202 | @keyframes oppositeSlideIn { 203 | from { 204 | top: 100%; 205 | left: 130%; 206 | } 207 | to { 208 | top: 2px; 209 | left: calc(50% - 1px); 210 | } 211 | } 212 | 213 | .button-inline { 214 | color: var(--red-color); 215 | font-weight: 400; 216 | cursor: pointer; 217 | border: 0; 218 | padding: 0; 219 | font-size: 1rem; 220 | line-height: 25px; 221 | letter-spacing: 0.7px; 222 | background-color: transparent; 223 | } 224 | 225 | .button + .button { 226 | margin-left: 20px; 227 | } 228 | 229 | @media (max-width: 768px) { 230 | .section { 231 | padding-block: 30px; 232 | } 233 | 234 | .button + .button { 235 | margin-top: 20px; 236 | margin-left: 0; 237 | } 238 | } -------------------------------------------------------------------------------- /src/routes/(app)/+layout.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 |
21 | 22 |
23 | 24 | 60 | 61 | 62 | 92 | 93 | 94 |