├── src-tauri ├── scripts │ ├── .gitkeep │ └── owoifier.js ├── build.rs ├── assets │ └── UI.png ├── icons │ ├── icon.ico │ ├── 128x128.png │ ├── 32x32.png │ ├── 512x512.png │ ├── 64x64.png │ ├── icon.icns │ ├── 128x128@2x.png │ ├── setupBanner.bmp │ └── setupBackground.bmp ├── src │ ├── assets │ │ └── UI.png │ ├── icons │ │ ├── 32x32.png │ │ ├── 64x64.png │ │ ├── icon.icns │ │ ├── icon.ico │ │ ├── 128x128.png │ │ ├── 512x512.png │ │ ├── 128x128@2x.png │ │ ├── setupBanner.bmp │ │ └── setupBackground.bmp │ ├── config │ │ └── default.toml │ ├── settings.rs │ ├── main.rs │ └── scripts.rs ├── app │ ├── stylesheets │ │ ├── base │ │ │ ├── _helpers.scss │ │ │ └── _base.scss │ │ ├── main.scss │ │ └── components │ │ │ ├── _window.scss │ │ │ ├── _editor.scss │ │ │ ├── _spotlight.scss │ │ │ ├── _titlebar.scss │ │ │ └── _icons.scss │ ├── js │ │ ├── components │ │ │ ├── action.ts │ │ │ ├── editor.ts │ │ │ ├── titlebar.ts │ │ │ ├── settings.ts │ │ │ ├── editorObj.ts │ │ │ ├── bloopMode.ts │ │ │ └── spotlight.ts │ │ └── app.ts │ ├── index.html │ └── icons │ │ └── cog.svg ├── rustfmt.toml ├── config │ └── default.toml ├── Cargo.toml └── tauri.conf.json ├── .github ├── FUNDING.yml └── workflows │ └── main.yml ├── .gitignore ├── vite.config.ts ├── package.json └── README.md /src-tauri/scripts/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | ko_fi: blainesensei 4 | -------------------------------------------------------------------------------- /src-tauri/assets/UI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blakeinstein/Bloop/HEAD/src-tauri/assets/UI.png -------------------------------------------------------------------------------- /src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blakeinstein/Bloop/HEAD/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blakeinstein/Bloop/HEAD/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blakeinstein/Bloop/HEAD/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /src-tauri/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blakeinstein/Bloop/HEAD/src-tauri/icons/512x512.png -------------------------------------------------------------------------------- /src-tauri/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blakeinstein/Bloop/HEAD/src-tauri/icons/64x64.png -------------------------------------------------------------------------------- /src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blakeinstein/Bloop/HEAD/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /src-tauri/src/assets/UI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blakeinstein/Bloop/HEAD/src-tauri/src/assets/UI.png -------------------------------------------------------------------------------- /src-tauri/src/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blakeinstein/Bloop/HEAD/src-tauri/src/icons/32x32.png -------------------------------------------------------------------------------- /src-tauri/src/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blakeinstein/Bloop/HEAD/src-tauri/src/icons/64x64.png -------------------------------------------------------------------------------- /src-tauri/src/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blakeinstein/Bloop/HEAD/src-tauri/src/icons/icon.icns -------------------------------------------------------------------------------- /src-tauri/src/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blakeinstein/Bloop/HEAD/src-tauri/src/icons/icon.ico -------------------------------------------------------------------------------- /src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blakeinstein/Bloop/HEAD/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/setupBanner.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blakeinstein/Bloop/HEAD/src-tauri/icons/setupBanner.bmp -------------------------------------------------------------------------------- /src-tauri/src/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blakeinstein/Bloop/HEAD/src-tauri/src/icons/128x128.png -------------------------------------------------------------------------------- /src-tauri/src/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blakeinstein/Bloop/HEAD/src-tauri/src/icons/512x512.png -------------------------------------------------------------------------------- /src-tauri/src/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blakeinstein/Bloop/HEAD/src-tauri/src/icons/128x128@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/setupBackground.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blakeinstein/Bloop/HEAD/src-tauri/icons/setupBackground.bmp -------------------------------------------------------------------------------- /src-tauri/src/icons/setupBanner.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blakeinstein/Bloop/HEAD/src-tauri/src/icons/setupBanner.bmp -------------------------------------------------------------------------------- /src-tauri/src/icons/setupBackground.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blakeinstein/Bloop/HEAD/src-tauri/src/icons/setupBackground.bmp -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #Added by cargo 2 | 3 | target 4 | node_modules 5 | cargo.lock 6 | bloop.code-workspace 7 | 8 | dist 9 | .cache 10 | .dnconfig 11 | WixTools 12 | keys 13 | *.log -------------------------------------------------------------------------------- /src-tauri/app/stylesheets/base/_helpers.scss: -------------------------------------------------------------------------------- 1 | .selected { 2 | background-color: #333; 3 | } 4 | 5 | .hidden { 6 | display: none; 7 | } 8 | 9 | .shaded { 10 | opacity: 0.9; 11 | } 12 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | 3 | export default defineConfig({ 4 | root: 'src-tauri/app', 5 | build: { 6 | target: 'es2021', 7 | outDir: '../dist/', 8 | } 9 | }) 10 | -------------------------------------------------------------------------------- /src-tauri/app/stylesheets/main.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | @import "base/base", "base/helpers"; 4 | 5 | @import "components/titlebar", "components/spotlight", "components/icons", 6 | "components/editor", "components/window"; 7 | -------------------------------------------------------------------------------- /src-tauri/app/stylesheets/components/_window.scss: -------------------------------------------------------------------------------- 1 | .window-body { 2 | flex-grow: 1; 3 | width: 100%; 4 | order: 2; 5 | overflow-x: hidden; 6 | overflow-y: auto; 7 | backdrop-filter: blur(2px); 8 | background: transparent; 9 | cursor: text; 10 | } 11 | -------------------------------------------------------------------------------- /src-tauri/app/js/components/action.ts: -------------------------------------------------------------------------------- 1 | class Action { 2 | name: string; 3 | tags: string[]; 4 | desc: string; 5 | dom: HTMLElement; 6 | 7 | constructor(name: string, tags: string, desc: string, listItem: HTMLElement) { 8 | this.name = name; 9 | this.tags = tags.split(","); 10 | this.desc = desc; 11 | this.dom = listItem; 12 | } 13 | } 14 | 15 | export default Action; 16 | -------------------------------------------------------------------------------- /src-tauri/rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 100 2 | hard_tabs = false 3 | tab_spaces = 2 4 | newline_style = "Auto" 5 | use_small_heuristics = "Default" 6 | reorder_imports = true 7 | reorder_modules = true 8 | remove_nested_parens = true 9 | edition = "2018" 10 | merge_derives = true 11 | use_try_shorthand = false 12 | use_field_init_shorthand = false 13 | force_explicit_abi = true 14 | imports_granularity = "Crate" 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bloop", 3 | "version": "0.4.8", 4 | "license": "MIT", 5 | "source": "src/web/index.html", 6 | "scripts": { 7 | "serve": "vite", 8 | "build": "vite build", 9 | "dev": "tauri dev", 10 | "release": "tauri build" 11 | }, 12 | "dependencies": { 13 | "@tauri-apps/api": "1.0.0-beta.8", 14 | "ace-builds": "^1.4.12", 15 | "fuse.js": "^6.4.6" 16 | }, 17 | "devDependencies": { 18 | "@tauri-apps/cli": "1.0.0-beta.10", 19 | "npm-run-all": "^4.1.5", 20 | "sass": "^1.38.0", 21 | "vite": "^2.5.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src-tauri/config/default.toml: -------------------------------------------------------------------------------- 1 | [global] 2 | custom_css = "" 3 | width = 900 4 | height = 400 5 | always_on_top = false 6 | 7 | [vars] 8 | font = '"SF mono", "Cascadia Code", "CascadiaCode Nerd Font", monaco, monospace' 9 | spotlight_font = '"HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif' 10 | font_size = '30px' 11 | editor_font_size = '20px' 12 | gutter_size = '4.2rem' 13 | gutter_color = '#161616c2' 14 | editor_color = '#1f1f1fc2' 15 | gutter_text_color = '#999' 16 | editor_text_color = 'white' 17 | seperator_width = '1.2px' 18 | seperator_color = '#f0f0fe2' 19 | # syntax 20 | syntax_comment = '#888ea6' 21 | syntax_string = '#ff4c7c' 22 | syntax_attribute = '#4cffb2' 23 | syntax_number = '#ffb24c' 24 | syntax_extra = '#3586ff' 25 | syntax_keyword = '#32ff47' -------------------------------------------------------------------------------- /src-tauri/src/config/default.toml: -------------------------------------------------------------------------------- 1 | [global] 2 | custom_css = "" 3 | width = 900 4 | height = 400 5 | always_on_top = false 6 | 7 | [vars] 8 | font = '"SF mono", "Cascadia Code", "CascadiaCode Nerd Font", monaco, monospace' 9 | spotlight_font = '"HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif' 10 | font_size = '30px' 11 | editor_font_size = '20px' 12 | gutter_size = '4.2rem' 13 | gutter_color = '#161616c2' 14 | editor_color = '#1f1f1fc2' 15 | gutter_text_color = '#999' 16 | editor_text_color = 'white' 17 | seperator_width = '1.2px' 18 | seperator_color = '#f0f0fe2' 19 | # syntax 20 | syntax_comment = '#888ea6' 21 | syntax_string = '#ff4c7c' 22 | syntax_attribute = '#4cffb2' 23 | syntax_number = '#ffb24c' 24 | syntax_extra = '#3586ff' 25 | syntax_keyword = '#32ff47' -------------------------------------------------------------------------------- /src-tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bloop" 3 | version = "0.4.8" 4 | authors = [ "Blaine " ] 5 | edition = "2018" 6 | build = "build.rs" 7 | description = "A hackable scratchpad for developers" 8 | 9 | [build-dependencies] 10 | tauri-build = { version = "1.0.0-beta.4" } 11 | 12 | [features] 13 | default = [ "custom-protocol" ] 14 | custom-protocol = [ "tauri/custom-protocol" ] 15 | 16 | [dependencies] 17 | serde_json = "1.0.56" 18 | serde = { version = "1.0.114", features = [ "derive" ] } 19 | glob = "0.3.0" 20 | once_cell = "1.7.2" 21 | config = "0.11.0" 22 | 23 | [dependencies.tauri] 24 | version = "1.0.0-beta.8" 25 | features = ["fs-read-dir", "fs-read-text-file", "fs-write-file", "path-all", "shell-open", "window-all"] 26 | 27 | [profile.release] 28 | panic = "abort" 29 | codegen-units = 1 30 | lto = true 31 | incremental = false 32 | opt-level = "s" 33 | -------------------------------------------------------------------------------- /src-tauri/app/stylesheets/base/_base.scss: -------------------------------------------------------------------------------- 1 | *, 2 | *:before, 3 | *:after { 4 | box-sizing: border-box; 5 | scrollbar-width: thin !important; 6 | scrollbar-color: var(--gutter_color) #afafaf52 !important; 7 | } 8 | 9 | * { 10 | &::-webkit-scrollbar { 11 | width: 8px; 12 | } 13 | 14 | &::-webkit-scrollbar-track { 15 | background: var(--gutter_color); 16 | } 17 | 18 | &::-webkit-scrollbar-thumb { 19 | background-color: #afafaf52; 20 | border-radius: 20px; 21 | border: 1px solid black; 22 | } 23 | } 24 | 25 | :root { 26 | font-family: var(--font); 27 | } 28 | 29 | html { 30 | height: 100%; 31 | } 32 | 33 | body { 34 | background-color: transparent; 35 | height: 100vh; 36 | width: 100vw; 37 | margin: 0px; 38 | cursor: default; 39 | } 40 | 41 | #content { 42 | height: 100%; 43 | width: 100%; 44 | display: flex; 45 | flex-direction: column; 46 | } 47 | -------------------------------------------------------------------------------- /src-tauri/app/stylesheets/components/_editor.scss: -------------------------------------------------------------------------------- 1 | .editor-container { 2 | height: 100%; 3 | } 4 | 5 | .ace-tm { 6 | background-color: var(--editor_color); 7 | color: var(--editor_text_color); 8 | font-size: var(--editor_font_size); 9 | 10 | .ace_gutter { 11 | background: var(--gutter_color); 12 | color: var(--gutter_text_color); 13 | border-right: var(--seperator_width) solid var(--seperator_color); 14 | } 15 | .ace_gutter-active-line { 16 | background: initial; 17 | } 18 | .ace_cursor { 19 | color: white; 20 | } 21 | .ace_indent-guide { 22 | opacity: 0.05; 23 | } 24 | // syntax highlight 25 | .ace_comment { 26 | color: var(--syntax_comment); 27 | } 28 | .ace_string { 29 | color: var(--syntax_string); 30 | } 31 | .ace_attribute { 32 | color: var(--syntax_attribute); 33 | } 34 | .ace_number { 35 | color: var(--syntax_number); 36 | } 37 | .ace_extra { 38 | color: var(--syntax_extra); 39 | } 40 | .ace_keyword { 41 | color: var(--syntax_keyword); 42 | } 43 | } 44 | 45 | .ace_folding-enabled > .ace_gutter-cell { 46 | padding: none; 47 | } 48 | -------------------------------------------------------------------------------- /src-tauri/scripts/owoifier.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"OWOifier", 5 | "description":"OWOify!", 6 | "author":"Blaine", 7 | "icon":"color-wheel", 8 | "tags":"meme,text,joke,fun", 9 | "bias":0.0 10 | } 11 | **/ 12 | const faces=["(・`ω´・)",";;w;;","owo","UwU",">w<","^w^"]; 13 | 14 | function owoify(text) 15 | { 16 | 17 | let v = text; 18 | 19 | v = v.replace(/(?:r|l)/g, "w"); 20 | v = v.replace(/(?:R|L)/g, "W"); 21 | v = v.replace(/n([aeiou])/g, 'ny$1'); 22 | v = v.replace(/N([aeiou])/g, 'Ny$1'); 23 | v = v.replace(/N([AEIOU])/g, 'Ny$1'); 24 | v = v.replace(/ove/g, "uv"); 25 | 26 | 27 | let exclamationPointCount = 0; 28 | let i; 29 | let stringsearch = "!"; 30 | //for loop counts the # of individual exclamation points 31 | for(let i=0; i < v.length; i++) { 32 | stringsearch===v[exclamationPointCount++] 33 | }; 34 | for (i = 0; i < exclamationPointCount; i++) { 35 | v = v.replace("!", " "+ faces[Math.floor(Math.random()*faces.length)]+ " "); 36 | } 37 | return (v); 38 | } 39 | 40 | function main(state) { 41 | try { 42 | state.text = owoify(state.text); 43 | } 44 | catch(error) { 45 | state.postError("Explain what went wrong here...") 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /src-tauri/app/js/components/editor.ts: -------------------------------------------------------------------------------- 1 | import ace from "ace-builds"; 2 | import "ace-builds/src-min-noconflict/keybinding-sublime"; 3 | import "./bloopMode"; 4 | 5 | const EditorElement: HTMLElement = document.querySelector(".editor-container"); 6 | 7 | const Config: Record = { 8 | cursorStyle: "ace", 9 | fontFamily: `var(--font)`, 10 | fontSize: `var(--editor_font_size)`, 11 | useWorker: false, 12 | indentedSoftWrap: false, 13 | wrap: true, 14 | hScrollBarAlwaysVisible: false, 15 | vScrollBarAlwaysVisible: false, 16 | autoScrollEditorIntoView: true, 17 | showPrintMargin: false, 18 | }; 19 | 20 | const editor = ace.edit(EditorElement); 21 | editor.setOptions(Config); 22 | editor.setKeyboardHandler("ace/keyboard/sublime"); 23 | editor.session.setMode("ace/mode/bloop"); 24 | 25 | editor.setValue(localStorage.getItem("bloopTextData") ?? ""); 26 | 27 | window.addEventListener("drop", (e) => { 28 | e.preventDefault(); 29 | 30 | for (let i in e.dataTransfer.files) { 31 | let reader = new FileReader(); 32 | reader.onload = (ev) => { 33 | editor.setValue(ev.target.result as string); 34 | }; 35 | reader.readAsText(e.dataTransfer.files[i]); 36 | } 37 | }); 38 | 39 | window.addEventListener("dragover", (e) => { 40 | e.preventDefault(); 41 | }); 42 | 43 | export default editor; 44 | -------------------------------------------------------------------------------- /src-tauri/app/js/components/titlebar.ts: -------------------------------------------------------------------------------- 1 | import { appWindow } from "@tauri-apps/api/window"; 2 | 3 | class TitleBar { 4 | titlebar: HTMLElement; 5 | close: HTMLElement; 6 | minimize: HTMLElement; 7 | maximize: HTMLElement; 8 | fullscreenSvg: HTMLElement; 9 | maximizeSvg: HTMLElement; 10 | maximizeState: boolean; 11 | 12 | constructor() { 13 | this.close = document.querySelector(".titlebar-close"); 14 | this.minimize = document.querySelector(".titlebar-minimize"); 15 | this.maximize = document.querySelector(".titlebar-fullscreen"); 16 | this.fullscreenSvg = document.querySelector(".fullscreen-svg"); 17 | this.maximizeSvg = document.querySelector(".maximize-svg"); 18 | this.titlebar = document.querySelector(".titlebar"); 19 | this.maximizeState = false; 20 | 21 | this.init(); 22 | } 23 | 24 | maximizeEvent() { 25 | this.maximizeState = !this.maximizeState; 26 | this.fullscreenSvg.classList.toggle("hidden"); 27 | this.maximizeSvg.classList.toggle("hidden"); 28 | this.maximizeState ? appWindow.unmaximize() : appWindow.maximize(); 29 | } 30 | 31 | init() { 32 | this.close.onclick = (e) => { 33 | e.stopPropagation(); 34 | appWindow.close(); 35 | }; 36 | this.minimize.onclick = (e) => { 37 | appWindow.minimize(); 38 | e.stopPropagation(); 39 | }; 40 | this.maximize.onclick = (e) => this.maximizeEvent(); 41 | this.titlebar.ondblclick = () => this.maximizeEvent(); 42 | } 43 | } 44 | 45 | export default TitleBar; 46 | -------------------------------------------------------------------------------- /src-tauri/app/js/components/settings.ts: -------------------------------------------------------------------------------- 1 | import { invoke } from "@tauri-apps/api/tauri"; 2 | import { open } from "@tauri-apps/api/shell"; 3 | import { configDir } from "@tauri-apps/api/path"; 4 | class Settings { 5 | root: HTMLElement; 6 | settingsButton: HTMLElement; 7 | cssElement: HTMLStyleElement; 8 | 9 | constructor() { 10 | this.root = document.documentElement; 11 | this.cssElement = document.createElement("style") as HTMLStyleElement; 12 | document.head.append(this.cssElement); 13 | this.settingsButton = document.querySelector(".titlebar-settings"); 14 | this.settingsButton.addEventListener("click", this.openSettingsFile); 15 | invoke("settings").then( 16 | (config) => this.updateConfig(config as string), 17 | (err) => console.log(err) 18 | ); 19 | document.body.style.display = "initial"; 20 | } 21 | updateConfig(config: string) { 22 | let settings: Record = JSON.parse(config); 23 | Object.entries(settings["vars"] as Record).forEach( 24 | ([prop, value]) => { 25 | this.root.style.setProperty("--" + prop, value); 26 | } 27 | ); 28 | if (settings["global"]["custom_css"]) { 29 | invoke("custom_css").then( 30 | (css) => (this.cssElement.textContent = css as string), 31 | (err) => console.log(err) 32 | ); 33 | } 34 | } 35 | openSettingsFile() { 36 | configDir() 37 | .then((dir) => open(dir + "bloop/config.toml")) 38 | .catch((err) => console.log(err)); 39 | } 40 | } 41 | 42 | export default Settings; 43 | -------------------------------------------------------------------------------- /src-tauri/app/js/app.ts: -------------------------------------------------------------------------------- 1 | import { invoke } from "@tauri-apps/api/tauri"; 2 | import { appWindow, PhysicalSize, PhysicalPosition } from '@tauri-apps/api/window' 3 | import { Ace } from "ace-builds"; 4 | 5 | import editor from "./components/editor"; 6 | import Spotlight from "./components/spotlight"; 7 | import EditorObj from "./components/editorObj"; 8 | import TitleBar from "./components/titlebar"; 9 | import Settings from "./components/settings"; 10 | 11 | declare global { 12 | interface Window { 13 | editor: Ace.Editor; 14 | editorObj: EditorObj; 15 | titlebar: TitleBar; 16 | spotlight: Spotlight; 17 | settings: Settings; 18 | requireMod: Function; 19 | } 20 | } 21 | window.settings = new Settings(); 22 | 23 | window.editor = editor; 24 | 25 | window.titlebar = new TitleBar(); 26 | 27 | window.editorObj = new EditorObj(editor); 28 | // * Implement Spotlight 29 | window.spotlight = new Spotlight(window.editorObj, editor); 30 | 31 | window.requireMod = async (path: string) => { 32 | if (!path.endsWith(".js")) path += ".js"; 33 | try { 34 | const code = await invoke("require", { scriptName: path }); 35 | const func = new Function( 36 | `let exports = {}; const module = { exports }; ${code}; return module.exports;` 37 | ); 38 | return func(); 39 | } catch (err) { 40 | window.editorObj.postError(err); 41 | } 42 | }; 43 | 44 | window.onload = () => { 45 | invoke("doc_ready"); 46 | window.editorObj.focus(); 47 | }; 48 | 49 | setInterval( async () => { 50 | localStorage.setItem("bloopTextData", window.editor.getValue()); 51 | }, 5000); -------------------------------------------------------------------------------- /src-tauri/app/js/components/editorObj.ts: -------------------------------------------------------------------------------- 1 | import { invoke } from "@tauri-apps/api/tauri"; 2 | import { Ace } from "ace-builds"; 3 | import Spotlight from "./spotlight"; 4 | 5 | class EditorObj { 6 | _script: String; 7 | editor: Ace.Editor; 8 | spotlight: Spotlight; 9 | 10 | constructor(editor: Ace.Editor) { 11 | this.editor = editor; 12 | } 13 | 14 | get script() { 15 | return this._script; 16 | } 17 | get isSelection() { 18 | return !this.editor.getSelection().isEmpty(); 19 | } 20 | get fullText() { 21 | return this.editor.getValue(); 22 | } 23 | get text() { 24 | return this.isSelection ? this.selection : this.fullText; 25 | } 26 | get selection() { 27 | return this.editor.getSelectedText(); 28 | } 29 | 30 | set script(value) { 31 | this._script = value; 32 | invoke("exec", { scriptName: value }).catch((error) => 33 | this.postError(error) 34 | ); 35 | } 36 | set selection(value) { 37 | let range = this.editor.getSelection().getRange(); 38 | this.editor.session.replace(range, value); 39 | } 40 | set fullText(value) { 41 | this.editor.setValue(value); 42 | this.editor.clearSelection(); 43 | } 44 | set text(value) { 45 | if (this.isSelection) this.selection = value; 46 | else this.fullText = value; 47 | } 48 | 49 | focus() { 50 | this.editor.focus(); 51 | this.editor.navigateFileEnd(); 52 | } 53 | 54 | postMessage(message) { 55 | this.spotlight.labelText[1].innerText = message; 56 | this.spotlight.labelText[0].classList.add("labelHidden"); 57 | this.spotlight.labelText[1].classList.remove("labelHidden"); 58 | } 59 | 60 | postInfo(message) { 61 | this.postMessage(message); 62 | this.spotlight.label.classList.add("postInfo"); 63 | } 64 | 65 | postError(message) { 66 | this.postMessage(message); 67 | this.spotlight.labelText[1].classList.remove("labelHidden"); 68 | this.spotlight.label.classList.add("postError"); 69 | } 70 | } 71 | 72 | export default EditorObj; 73 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Release 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the master branch 7 | on: 8 | workflow_dispatch: 9 | push: 10 | branches: [Release] 11 | 12 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 13 | jobs: 14 | # This workflow contains a single job called "build" 15 | build: 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | platform: [macos-latest, ubuntu-latest, windows-latest] 20 | 21 | runs-on: ${{ matrix.platform }} 22 | steps: 23 | # Set up core dependencies 24 | - uses: actions/checkout@v2 25 | - name: setup node 26 | uses: actions/setup-node@v1 27 | with: 28 | node-version: 14 29 | - name: install Rust stable 30 | uses: actions-rs/toolchain@v1 31 | with: 32 | toolchain: stable 33 | - name: install webkit2gtk (ubuntu only) 34 | if: matrix.platform == 'ubuntu-latest' 35 | run: | 36 | sudo apt-get update 37 | sudo apt-get install -y webkit2gtk-4.0 38 | 39 | # Get Boop scripts 40 | - name: Get Boop scripts 41 | run: | 42 | git clone http://github.com/ivanmathy/boop 43 | mv boop/Boop/Boop/scripts/* src-tauri/scripts/ 44 | 45 | # Build web component 46 | - name: install app dependencies and build it 47 | run: yarn 48 | 49 | # Create release 50 | - uses: tauri-apps/tauri-action@v0 51 | env: 52 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 53 | TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} 54 | TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} 55 | with: 56 | tagName: v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version 57 | releaseName: "Release v__VERSION__" 58 | releaseBody: "Release v__VERSION__." 59 | draft: true 60 | prerelease: false 61 | -------------------------------------------------------------------------------- /src-tauri/src/settings.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::error::Error; 3 | use std::fs::{create_dir_all, read_to_string, File as FileWriter}; 4 | use std::io::Write; 5 | 6 | use config::{Config, File, FileFormat}; 7 | use serde::{Deserialize, Serialize}; 8 | use tauri::api::dialog::message; 9 | use tauri::api::path::config_dir; 10 | 11 | #[derive(Debug, Serialize, Deserialize)] 12 | pub struct Global { 13 | pub custom_css: String, 14 | pub width: u32, 15 | pub height: u32, 16 | pub always_on_top: bool, 17 | } 18 | 19 | #[derive(Debug, Serialize, Deserialize)] 20 | pub struct BloopConfig { 21 | pub global: Global, 22 | pub vars: HashMap, 23 | } 24 | 25 | impl BloopConfig { 26 | pub fn new() -> Result> { 27 | let mut settings = Config::default(); 28 | const DEFAULT_CONFIG: &'static str = include_str!("../config/default.toml"); 29 | if let Err(err) = settings.merge(File::from_str(DEFAULT_CONFIG, FileFormat::Toml)) { 30 | message(Option::<&tauri::Window>::None, "Error parsing default config", err.to_string()); 31 | } 32 | if let Some(config_path) = config_dir() { 33 | let config_dir = config_path.join("bloop"); 34 | let bloop_config = config_dir.join("config.toml"); 35 | if config_dir.exists() && bloop_config.exists() { 36 | if let Err(err) = settings.merge(File::from(bloop_config)) { 37 | message(Option::<&tauri::Window>::None, "Error parsing config", err.to_string()); 38 | } 39 | } else { 40 | create_dir_all(config_dir)?; 41 | let mut file = FileWriter::create(bloop_config)?; 42 | file.write_all(DEFAULT_CONFIG.as_bytes())?; 43 | } 44 | } 45 | settings.try_into().map_err(|err| err.into()) 46 | } 47 | } 48 | 49 | pub fn custom_css(css_file_name: String) -> Option { 50 | if let Some(config_path) = config_dir() { 51 | let css_file = config_path 52 | .join("bloop/themes") 53 | .join(css_file_name + ".css"); 54 | if css_file.exists() { 55 | return Some(read_to_string(&css_file).unwrap()); 56 | } 57 | } 58 | None 59 | } 60 | -------------------------------------------------------------------------------- /src-tauri/app/stylesheets/components/_spotlight.scss: -------------------------------------------------------------------------------- 1 | #spotlight-wrapper { 2 | position: absolute; 3 | top: 15%; 4 | left: 0; 5 | right: 0; 6 | margin: 0 auto; 7 | width: 628px; 8 | } 9 | 10 | #spotlight { 11 | display: block; 12 | width: 100%; 13 | height: 56px; 14 | margin: auto; 15 | 16 | border-radius: 5px; 17 | -moz-border-radius: 5px; 18 | -webkit-border-radius: 5px; 19 | border-top-left-radius: 5px; 20 | border-top-right-radius: 5px; 21 | 22 | -moz-appearance: none; 23 | -webkit-appearance: none; 24 | 25 | -moz-box-shadow: 0 25px 60px 10px rgba(0, 0, 0, 0.3); 26 | -webkit-box-shadow: 0 25px 60px 10px rgba(0, 0, 0, 0.3); 27 | box-shadow: 0 25px 60px 10px rgba(0, 0, 0, 0.3); 28 | 29 | border: 1px solid rgba(0, 0, 0, 0.2); 30 | outline: none; 31 | font-size: 1.3rem; 32 | font-family: var(--spotlight_font); 33 | color: white; 34 | background-color: rgb(15, 15, 15); 35 | padding: 0 12px; 36 | 37 | &::placeholder { 38 | color: white; 39 | } 40 | } 41 | #action-list-placeholder { 42 | > div { 43 | grid-template-columns: auto; 44 | } 45 | } 46 | 47 | .action-list { 48 | display: flex; 49 | flex-flow: column; 50 | width: 100%; 51 | outline: none; 52 | font-size: 1.3rem; 53 | color: white; 54 | background-color: #0f0f0f; 55 | padding: 0 12px; 56 | margin: auto; 57 | list-style: none; 58 | max-height: 60vh; 59 | 60 | border-bottom-left-radius: 5px; 61 | border-bottom-right-radius: 5px; 62 | 63 | -moz-appearance: none; 64 | -webkit-appearance: none; 65 | 66 | box-shadow: 0 25px 60px 10px rgba(0, 0, 0, 0.3); 67 | 68 | overflow-x: hidden; 69 | 70 | li { 71 | width: 100%; 72 | height: 56px; 73 | padding: 0 12px; 74 | margin: auto 0; 75 | 76 | > div { 77 | height: 50px; 78 | grid-template-columns: 50px auto; 79 | gap: 5px; 80 | } 81 | } 82 | div { 83 | display: grid; 84 | align-items: center; 85 | gap: 4px; 86 | } 87 | } 88 | 89 | name { 90 | display: block; 91 | font-family: var(--spotlight_font); 92 | font-size: 1em; 93 | color: #dadada; 94 | } 95 | 96 | description { 97 | font-family: var(--spotlight_font); 98 | display: block; 99 | font-size: 0.7em; 100 | color: #606060; 101 | text-overflow: ellipsis; 102 | overflow: hidden; 103 | white-space: nowrap; 104 | } 105 | -------------------------------------------------------------------------------- /src-tauri/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Bloop 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 |
15 | 16 | 17 | 18 |
19 |
20 | 21 | 22 | 23 |
24 |
25 | 26 | 27 | 28 | 29 | 32 |
33 |
34 |
35 |
Press Ctrl+B to get started
36 |
37 |
Select your action
38 |
39 |
40 | 41 |
42 |
43 |
44 |
45 |
46 |
47 | 52 | 53 | -------------------------------------------------------------------------------- /src-tauri/tauri.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "package": { 3 | "productName": "Bloop", 4 | "version": "0.4.8" 5 | }, 6 | "build": { 7 | "distDir": "dist", 8 | "devPath": "http://localhost:3000", 9 | "beforeDevCommand": "yarn serve", 10 | "beforeBuildCommand": "yarn build" 11 | }, 12 | "tauri": { 13 | "bundle": { 14 | "active": true, 15 | "targets": "all", 16 | "identifier": "Blaine", 17 | "icon": [ 18 | "icons/32x32.png", 19 | "icons/128x128.png", 20 | "icons/128x128@2x.png", 21 | "icons/icon.icns", 22 | "icons/icon.ico" 23 | ], 24 | "resources": ["./scripts", "./config/default.toml"], 25 | "externalBin": [], 26 | "copyright": "Blaine", 27 | "category": "DeveloperTool", 28 | "shortDescription": "A hackable scratchpad for developers!", 29 | "longDescription": "A hackable scratchpad for developers!", 30 | "deb": { 31 | "depends": [], 32 | "useBootstrapper": false 33 | }, 34 | "macOS": { 35 | "frameworks": [], 36 | "minimumSystemVersion": "", 37 | "useBootstrapper": false, 38 | "exceptionDomain": "", 39 | "signingIdentity": null, 40 | "entitlements": null 41 | }, 42 | "windows": { 43 | "certificateThumbprint": null, 44 | "digestAlgorithm": "sha256", 45 | "timestampUrl": "", 46 | "wix": { 47 | "dialogImagePath": "icons/setupBackground.bmp", 48 | "bannerPath": "icons/setupBanner.bmp" 49 | } 50 | } 51 | }, 52 | "updater": { 53 | "active": false, 54 | "endpoints": [ 55 | "https://bloop-updates.azurewebsites.net/api/{{target}}/{{current_version}}" 56 | ], 57 | "dialog": false, 58 | "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDQ4Q0RGQzI4QUFBOTJCMwpSV1N6a3FxS3d0K01CTC9nck1WSVcwczhha3NyZ3dER2hsNFN6WDluandDdXlrOGNoc1hrNjhmVgo=" 59 | }, 60 | "allowlist": { 61 | "all": false, 62 | "window": { 63 | "all": true 64 | }, 65 | "fs": { 66 | "all": false, 67 | "readTextFile": true, 68 | "writeFile": true, 69 | "readDir": true 70 | }, 71 | "shell": { 72 | "all": false, 73 | "open": true 74 | }, 75 | "path": { 76 | "all": true 77 | } 78 | }, 79 | "windows": [ 80 | { 81 | "title": "Bloop", 82 | "resizable": true, 83 | "fullscreen": false, 84 | "decorations": false, 85 | "transparent": true, 86 | "focus": true, 87 | "center": true, 88 | "fileDropEnabled": false 89 | } 90 | ], 91 | "security": { 92 | "csp": "default-src blob: data: filesystem: ws: http: https: 'unsafe-eval' 'unsafe-inline' 'self' img-src: 'self'" 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src-tauri/app/stylesheets/components/_titlebar.scss: -------------------------------------------------------------------------------- 1 | .titlebar { 2 | padding: 0; 3 | background-color: #3a3a3af6; 4 | border-top-left-radius: 2px; 5 | border-top-right-radius: 2px; 6 | width: 100%; 7 | height: 28px; 8 | z-index: 10; 9 | order: 1; 10 | user-select: none; 11 | flex-shrink: 0; 12 | display: flex; 13 | align-items: center; 14 | } 15 | 16 | .titlebar-stoplight { 17 | cursor: default; 18 | display: inline-grid; 19 | grid-template-columns: 1fr 1fr 1fr; 20 | place-items: center; 21 | position: absolute; 22 | width: 70px; 23 | float: left; 24 | } 25 | 26 | .titlebar:after, 27 | .titlebar-stoplight:after { 28 | content: " "; 29 | display: table; 30 | clear: both; 31 | } 32 | 33 | .titlebar-stoplight:hover svg { 34 | opacity: 1; 35 | } 36 | 37 | .titlebar-close, 38 | .titlebar-minimize, 39 | .titlebar-fullscreen { 40 | border-radius: 50%; 41 | line-height: 0; 42 | padding: 2px; 43 | } 44 | 45 | .titlebar svg { 46 | width: 8px; 47 | height: 8px; 48 | margin: auto; 49 | opacity: 0; 50 | } 51 | 52 | .titlebar-close { 53 | border: 1px solid #e2463f; 54 | background-color: #ff5f57; 55 | } 56 | 57 | .titlebar-close:active { 58 | border-color: #ad3934; 59 | background-color: #bf4943; 60 | } 61 | 62 | .titlebar-minimize { 63 | border: 1px solid #e1a116; 64 | background-color: #ffbd2e; 65 | } 66 | 67 | .titlebar-minimize:active { 68 | border-color: #ad7d15; 69 | background-color: #bf9123; 70 | } 71 | 72 | .titlebar-fullscreen, 73 | .titlebar-maximize { 74 | border: 1px solid #12ac28; 75 | background-color: #28c940; 76 | } 77 | 78 | .titlebar-fullscreen:active { 79 | border-color: #128622; 80 | background-color: #1f9a31; 81 | } 82 | 83 | .titlebar-spotlight { 84 | background: #1e1e1e; 85 | color: #e0e0e0; 86 | border-radius: 5px; 87 | height: 70%; 88 | width: 30%; 89 | margin: 0 auto; 90 | text-align: center; 91 | display: inline-grid; 92 | place-items: center; 93 | grid-template-areas: "label"; 94 | grid-template-rows: 100%; 95 | } 96 | 97 | .titlebar-spotlight .label { 98 | font-size: 10px; 99 | width: 30%; 100 | transition: visibility 0s, opacity 0.4s ease-in-out; 101 | visibility: visible; 102 | position: absolute; 103 | z-index: 10; 104 | margin: 5px; 105 | text-overflow: clip; 106 | overflow: hidden; 107 | white-space: nowrap; 108 | grid-area: label; 109 | } 110 | 111 | .titlebar-settings { 112 | float: right; 113 | display: grid; 114 | margin-right: 0.2em; 115 | 116 | img { 117 | opacity: 0.4; 118 | transition: all 0.5s ease-in-out; 119 | 120 | &:hover { 121 | filter: invert(0.4); 122 | opacity: 0.7; 123 | } 124 | } 125 | 126 | &:hover { 127 | cursor: pointer; 128 | } 129 | } 130 | 131 | .postInfo { 132 | background-color: #3586ff; 133 | color: #dadada; 134 | } 135 | 136 | .postError { 137 | background-color: #ff4c7c; 138 | color: #dadada; 139 | } 140 | 141 | .labelHidden { 142 | visibility: hidden; 143 | opacity: 0; 144 | } 145 | -------------------------------------------------------------------------------- /src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr( 2 | all(not(debug_assertions), target_os = "windows"), 3 | windows_subsystem = "windows" 4 | )] 5 | mod scripts; 6 | mod settings; 7 | 8 | extern crate config; 9 | extern crate serde; 10 | 11 | use once_cell::sync::OnceCell; 12 | use std::collections::HashMap; 13 | use std::sync::{Arc, Mutex}; 14 | use tauri::Manager; 15 | 16 | pub static PACKAGE_INFO: OnceCell = OnceCell::new(); 17 | pub static GLOBAL_CONFIG: OnceCell = OnceCell::new(); 18 | 19 | #[derive(Default)] 20 | struct Scripts(Arc>>); 21 | 22 | #[tauri::command] 23 | fn doc_ready(window: tauri::Window, scripts: tauri::State) -> Result { 24 | match scripts::build_scripts(window, &mut scripts.0.lock().unwrap()) { 25 | Ok(_) => Ok("".to_string()), 26 | Err(err) => Err(err.to_string()), 27 | } 28 | } 29 | 30 | #[tauri::command] 31 | fn exec( 32 | window: tauri::Window, 33 | scripts: tauri::State, 34 | script_name: String, 35 | ) -> Result { 36 | match scripts::script_eval( 37 | scripts 38 | .0 39 | .lock() 40 | .unwrap() 41 | .get(&script_name) 42 | .ok_or(format!("Script, {}, not found", &script_name))?, 43 | &window, 44 | ) { 45 | Ok(_) => Ok("".to_string()), 46 | Err(err) => Err(err.to_string()), 47 | } 48 | } 49 | 50 | #[tauri::command] 51 | fn require(scripts: tauri::State, script_name: String) -> Result { 52 | match scripts.0.lock().unwrap().get(&script_name) { 53 | Some(script) => Ok(script.string.clone()), 54 | None => Err("lib not found".into()), 55 | } 56 | } 57 | 58 | #[tauri::command] 59 | fn settings() -> Result { 60 | let config = GLOBAL_CONFIG.get().ok_or("Error reading config")?; 61 | serde_json::to_string(config).map_err(|err| err.to_string()) 62 | } 63 | 64 | #[tauri::command] 65 | fn custom_css() -> String { 66 | let config = GLOBAL_CONFIG.get().unwrap(); 67 | settings::custom_css(config.global.custom_css.clone()).unwrap_or_default() 68 | } 69 | 70 | fn main() { 71 | let context = tauri::generate_context!(); 72 | PACKAGE_INFO.set(context.package_info().clone()).unwrap(); 73 | let app_config = settings::BloopConfig::new().unwrap(); 74 | const MIN_SIZE: tauri::Size = tauri::Size::Physical(tauri::PhysicalSize { 75 | width: 900, 76 | height: 400, 77 | }); 78 | let width = app_config.global.width; 79 | let height = app_config.global.height; 80 | let always_on_top = app_config.global.always_on_top; 81 | GLOBAL_CONFIG.set(app_config).unwrap(); 82 | 83 | tauri::Builder::default() 84 | .setup(move |app| { 85 | let window = app.get_window("main").unwrap(); 86 | window.set_min_size(Some(MIN_SIZE)).unwrap(); 87 | window 88 | .set_size(tauri::Size::Physical(tauri::PhysicalSize { width, height })) 89 | .unwrap(); 90 | window 91 | .set_always_on_top(always_on_top) 92 | .unwrap(); 93 | Ok(()) 94 | }) 95 | .manage(Scripts(Default::default())) 96 | .invoke_handler(tauri::generate_handler![ 97 | doc_ready, exec, require, settings, custom_css 98 | ]) 99 | .run(context) 100 | .expect("error while running tauri application"); 101 | } 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

BLOOP

4 | A light weight hackable scratch pad for developers inspired from boop.
5 | Run scripts (written in JS) directly over any piece of text!
6 | Base64? EZ. Got an unreadable JSON? parse it! Just want to count characters? Well we got you covered.
7 | Or just use it to take notes, data persists in local storage :)
8 | The tool aims to be an exact imitation of boop built using web technologies and powered by Tauri. 9 |

10 | 11 |

12 | UI screenshot 13 |

14 | 15 |

16 | 17 |

18 | 19 |

20 | Inspired from BoopDocumentationFind more scripts 21 |

22 | 23 | 24 | ### Documentation 25 | New? Read how to use bloop [here](https://github.com/Blakeinstein/Bloop/wiki/Getting-Started) 26 | #### Suggestions 27 | To add custom scripts add the scripts to your documents directory which you can confirm [here](https://docs.rs/dirs-next/2.0.0/dirs_next/fn.document_dir.html) 28 | 29 | Bloop supports ligaturized fonts, Some fonts are set up by default, but require you to install them manually. You can also use your own fonts, by changing `config.toml`. Suggested fonts => 30 | * [SF Mono ligaturized](https://github.com/kube/sf-mono-ligaturized/tree/master/ligaturized) 31 | * [Cascadia Code](https://github.com/microsoft/cascadia-code) 32 | 33 | 34 | ### How to get Bloop 35 | You can obtain installer/binaries for your platform [here](https://github.com/Blakeinstein/Bloop/releases). 36 | 37 | --- 38 | Alternatively you can compile it yourself. 39 | 40 | > Only for developers. 41 | 42 | ### How to build from source 43 | 44 | - #### Config 45 | - ##### Setup Tauri and Yarn 46 | - Follow the setup guide from their [docs](https://tauri.studio/en/docs/getting-started/intro) 47 | - ##### Get Boop scripts 48 | - Get the base scripts from boop 49 | ```bash 50 | mkdir src-tauri 51 | git clone http://github.com/ivanmathy/boop 52 | mv boop/Boop/Boop/scripts src-tauri/ 53 | ``` 54 | - ##### Build web component 55 | - Install app dependencies and build it 56 | ```bash 57 | yarn && yarn build 58 | ``` 59 | - ##### Copy data over to replicate tauri config 60 | ```bash 61 | mv dist src-tauri/ 62 | mv src src-tauri/ 63 | mv config src-tauri/ 64 | mv icons src-tauri/ 65 | mv ./Cargo.toml src-tauri/ 66 | mv ./tauri.conf.json src-tauri/ 67 | mv ./build.rs src-tauri/ 68 | ``` 69 | - #### Build 70 | - To build release binaries, use ``` yarn release ```, The resultant binaries would be located in ```src-tauri/target/release``` 71 | 72 | 73 | 75 | -------------------------------------------------------------------------------- /src-tauri/src/scripts.rs: -------------------------------------------------------------------------------- 1 | use glob::{glob, GlobError}; 2 | use serde::{Deserialize, Serialize}; 3 | use std::collections::HashMap; 4 | use std::fs::{create_dir_all, read_to_string}; 5 | use std::path::PathBuf; 6 | use tauri::api::path::{document_dir, resource_dir}; 7 | use tauri::Window; 8 | 9 | #[derive(Serialize, Deserialize)] 10 | struct Metadata { 11 | api: i16, 12 | name: String, 13 | description: String, 14 | author: String, 15 | icon: String, 16 | tags: String, 17 | } 18 | 19 | pub struct Script { 20 | metadata: Metadata, 21 | pub string: String, 22 | } 23 | 24 | impl Script { 25 | fn new(string: &str) -> Result { 26 | let start_bytes = string.find("/**").map(|x| x+3); 27 | let end_bytes = string.find("**/"); 28 | parse_meta(&string[start_bytes.unwrap_or(0)..end_bytes.unwrap_or(0)]) 29 | .map(|meta| Script { 30 | metadata: meta, 31 | string: string.to_string().replace("require(", "await requireMod(") 32 | } 33 | ) 34 | } 35 | } 36 | 37 | fn parse_meta(string: &str) -> serde_json::Result { 38 | serde_json::from_str(&string) 39 | } 40 | 41 | fn append_script( 42 | window: &Window, 43 | script_list: &mut HashMap, 44 | script: Result<&PathBuf, &GlobError>, 45 | ) -> tauri::Result<()> { 46 | let file = script.unwrap(); 47 | let script_string = read_to_string(&file).unwrap(); 48 | match Script::new(&script_string) { 49 | Err(_) => Ok(()), 50 | Ok(val) => { 51 | let meta = &val.metadata; 52 | window.eval(&format!( 53 | "spotlight.addAction({:?}, {:?}, {:?}, {:?})", 54 | meta.name, meta.description, meta.icon, meta.tags 55 | ))?; 56 | script_list.insert(meta.name.to_string(), val); 57 | Ok(()) 58 | } 59 | } 60 | } 61 | 62 | fn append_lib( 63 | script_list: &mut HashMap, 64 | script: Result<&PathBuf, &GlobError>, 65 | builtin: bool, 66 | ) -> tauri::Result<()> { 67 | let file = script.unwrap(); 68 | let script_string = read_to_string(&file).unwrap(); 69 | let file_name = file.file_name().unwrap().to_str().unwrap(); 70 | let lib = Script { 71 | metadata: Metadata { 72 | api: 1, 73 | name: if builtin { 74 | String::from("@boop/") + file_name 75 | } else { 76 | String::from("lib/") + file_name 77 | }, 78 | description: "Is lib".into(), 79 | author: "Is lib".into(), 80 | icon: "Is lib".into(), 81 | tags: "lib".into(), 82 | }, 83 | string: script_string.to_string(), 84 | }; 85 | script_list.insert(lib.metadata.name.to_string(), lib); 86 | Ok(()) 87 | } 88 | 89 | pub fn build_scripts( 90 | window: Window, 91 | script_list: &mut HashMap, 92 | ) -> tauri::Result<()> { 93 | script_list.clear(); // flush scripts on reload 94 | if let Some(resource_dir) = resource_dir(&crate::PACKAGE_INFO.get().unwrap()) { 95 | let script_path = &resource_dir.join("scripts"); 96 | install_scripts(&window, script_list, script_path, true)?; 97 | } 98 | if let Some(user_dir) = document_dir() { 99 | let bloop_dir = user_dir.join("bloop"); 100 | if bloop_dir.exists() { 101 | install_scripts(&window, script_list, &bloop_dir, false)?; 102 | } else { 103 | create_dir_all(bloop_dir).unwrap(); 104 | } 105 | } 106 | 107 | Ok(()) 108 | } 109 | 110 | pub fn install_scripts( 111 | window: &Window, 112 | script_list: &mut HashMap, 113 | pattern: &PathBuf, 114 | builtin: bool, 115 | ) -> tauri::Result<()> { 116 | for script in glob(pattern.join("*.js").to_str().unwrap()).unwrap() { 117 | append_script(window, script_list, script.as_ref())?; 118 | } 119 | for script in glob(pattern.join("lib").join("*.js").to_str().unwrap()).unwrap() { 120 | append_lib(script_list, script.as_ref(), builtin)?; 121 | } 122 | Ok(()) 123 | } 124 | 125 | pub fn script_eval(script_obj: &Script, window: &Window) -> tauri::Result<()> { 126 | let js = format!( 127 | "(async () => {{ {}; main(editorObj) }})()", 128 | &script_obj.string 129 | ); 130 | window.eval(&js)?; 131 | window.eval("spotlight.Ok()")?; 132 | Ok(()) 133 | } 134 | -------------------------------------------------------------------------------- /src-tauri/app/js/components/bloopMode.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | ace.define("ace/mode/bloop", [], function (require, exports, module) { 3 | var oop = require("ace/lib/oop"); 4 | var TextMode = require("ace/mode/text").Mode; 5 | var Tokenizer = require("ace/tokenizer").Tokenizer; 6 | var BloopHighlight = require("ace/mode/bloop_highlight").BloopHighlight; 7 | 8 | var Mode = function () { 9 | this.HighlightRules = BloopHighlight; 10 | }; 11 | oop.inherits(Mode, TextMode); 12 | 13 | (function () { 14 | this.lineCommentStart = "--"; 15 | this.blockComment = { start: "->", end: "<-" }; 16 | }.call(Mode.prototype)); 17 | 18 | exports.Mode = Mode; 19 | }); 20 | 21 | const commonAttributes = [ 22 | "var", 23 | "val", 24 | "let", 25 | "if", 26 | "else", 27 | "export", 28 | "import", 29 | "return", 30 | "static", 31 | "fun", 32 | "function", 33 | "func", 34 | "class", 35 | "open", 36 | "new", 37 | "as", 38 | "where", 39 | "select", 40 | "delete", 41 | "add", 42 | "limit", 43 | "update", 44 | "insert", 45 | ]; 46 | 47 | const moreAttributes = [ 48 | "true", 49 | "false", 50 | "to", 51 | "string", 52 | "int", 53 | "float", 54 | "double", 55 | "bool", 56 | "boolean", 57 | "from", 58 | ]; 59 | 60 | const standalonePrefix = "(?:[\\s]|[\\(,:])"; 61 | 62 | const standaloneSuffix = "(?=[\\s\\?\\!,:\\)\\();]|$)"; 63 | 64 | const quoteLookahead = '(?=(?:(?:[^"]*"){2})*[^"]*$)'; 65 | 66 | const quotes = '("|@")(?:[^"\\\\\\n]|\\\\.)*[^"\\n]*(@"|")'; 67 | 68 | const number = 69 | "\\b(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)\\b"; 70 | 71 | const UTCDate = 72 | "(?:(Sun|Mon|Tue|Wed|Thu|Fri|Sat),\\s+)?(0[1-9]|[1-2]?[0-9]|3[01])\\s+(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\\s+(19[0-9]{2}|[2-9][0-9]{3})\\s+(2[0-3]|[0-1][0-9]):([0-5][0-9])(?::(60|[0-5][0-9]))?\\s+([-\\+][0-9]{2}[0-5][0-9]|(?:UT|GMT|(?:E|C|M|P)(?:ST|DT)|[A-IK-Z]))"; 73 | 74 | const states = { 75 | start: [ 76 | { 77 | regex: `${standalonePrefix}(${UTCDate})${standaloneSuffix}`, 78 | token: "number", 79 | }, 80 | { 81 | regex: `(?:^)(${UTCDate})${standaloneSuffix}`, 82 | sol: true, 83 | token: "number", 84 | }, 85 | // MD5 strings 86 | { 87 | regex: `${standalonePrefix}([a-f0-9]{32})${standaloneSuffix}`, 88 | token: "keyword", 89 | }, 90 | { 91 | regex: `(?:^)([a-f0-9]{32})${standaloneSuffix}`, 92 | token: "keyword", 93 | }, 94 | // Match JSON labelss and generic parameters 95 | { 96 | regex: `["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]\\s*(?=:)`, 97 | token: "extra", 98 | }, 99 | // XML-like tags 100 | { 101 | regex: `${quoteLookahead}<(?:.*?)\\b[^>]*\\/?>`, 102 | token: "attribute", 103 | }, 104 | //regulars 105 | { 106 | regex: number, 107 | token: "number", 108 | }, 109 | // common attributes 110 | { 111 | regex: `${standalonePrefix}(${commonAttributes.join( 112 | "|" 113 | )})${standaloneSuffix}`, 114 | token: "attribute", 115 | }, 116 | { 117 | regex: `${standalonePrefix}(${moreAttributes.join( 118 | "|" 119 | )})${standaloneSuffix}`, 120 | caseInsensitive: true, 121 | token: "keyword", 122 | }, 123 | { 124 | regex: `(?:^)(${commonAttributes.join("|")})${standaloneSuffix}`, 125 | caseInsensitive: true, 126 | sol: true, 127 | token: "attribute", 128 | }, 129 | { 130 | regex: `(?:^)(${moreAttributes.join("|")})${standaloneSuffix}`, 131 | caseInsensitive: true, 132 | sol: true, 133 | token: "keyword", 134 | }, 135 | // Strings 136 | { 137 | token: "string", // single line 138 | regex: `"""|'''|\``, 139 | next: "string", 140 | }, 141 | { 142 | regex: `("|')(?:(?!\\1)(?:\\\\.|[^\\\\]))*\\1`, 143 | token: "string", 144 | }, 145 | // Comments 146 | { 147 | regex: `${quoteLookahead}//(.*)`, 148 | token: "extra", 149 | }, 150 | { 151 | regex: `${quoteLookahead}\/\\*`, 152 | caseInsensitive: true, 153 | token: "comment", 154 | next: "comment", 155 | }, 156 | { 157 | regex: `${quoteLookahead}