├── .gitattributes ├── .gitignore ├── Boop ├── Boop.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── swiftpm │ │ │ └── Package.resolved │ └── xcshareddata │ │ └── xcschemes │ │ └── Boop.xcscheme ├── Boop │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── icon_128x128.png │ │ │ ├── icon_128x128@2x.png │ │ │ ├── icon_16x16.png │ │ │ ├── icon_16x16@2x.png │ │ │ ├── icon_256x256.png │ │ │ ├── icon_256x256@2x.png │ │ │ ├── icon_32x32.png │ │ │ ├── icon_32x32@2x.png │ │ │ ├── icon_512x512.png │ │ │ └── icon_512x512@2x.png │ │ ├── Contents.json │ │ └── Icons │ │ │ ├── Contents.json │ │ │ ├── icons8-HTML.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-source-code-filled(1).pdf │ │ │ ├── icons8-abacus.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-abacus.pdf │ │ │ ├── icons8-broom.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-broom.pdf │ │ │ ├── icons8-camel.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-camel.pdf │ │ │ ├── icons8-collapse.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-merge_horizontal.pdf │ │ │ ├── icons8-color-wheel.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-color_mode.pdf │ │ │ ├── icons8-colosseum.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-colosseum.pdf │ │ │ ├── icons8-command.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-run_command.pdf │ │ │ ├── icons8-counter.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-counter.pdf │ │ │ ├── icons8-dice.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-dice.pdf │ │ │ ├── icons8-elephant.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-elephant.pdf │ │ │ ├── icons8-filtration.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-filtration.pdf │ │ │ ├── icons8-fingerprint.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-fingerprint.pdf │ │ │ ├── icons8-flask.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-test_tube.pdf │ │ │ ├── icons8-flip.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-flip-vertical.pdf │ │ │ ├── icons8-globe.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-globe.pdf │ │ │ ├── icons8-identification.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-identification_documents.pdf │ │ │ ├── icons8-kebab.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-kebab.pdf │ │ │ ├── icons8-link.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-link.pdf │ │ │ ├── icons8-metamorphose.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-metamorphose.pdf │ │ │ ├── icons8-percentage.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-percentage.pdf │ │ │ ├── icons8-pineapple.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-pineapple.pdf │ │ │ ├── icons8-quote.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-get-quote.pdf │ │ │ ├── icons8-roman.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-roman_helmet.pdf │ │ │ ├── icons8-scissors.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-scissors.pdf │ │ │ ├── icons8-snake.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-snake.pdf │ │ │ ├── icons8-sort-characters.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-alphabetical_sorting.pdf │ │ │ ├── icons8-sort-numbers.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-reversed_numerical_sorting.pdf │ │ │ ├── icons8-table.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-merge_cells.pdf │ │ │ ├── icons8-term.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-term.pdf │ │ │ ├── icons8-translation.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-translation.pdf │ │ │ ├── icons8-type.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-font_size.pdf │ │ │ ├── icons8-unknown.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-question-mark.pdf │ │ │ ├── icons8-watch.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-pocket-watch.pdf │ │ │ └── icons8-website.imageset │ │ │ ├── Contents.json │ │ │ └── icons8-website.pdf │ ├── Boop.entitlements │ ├── BoopRelease.entitlements │ ├── Controllers │ │ ├── MainViewController.swift │ │ ├── PopoverViewController.swift │ │ ├── Preferences │ │ │ ├── PreferencesTabViewController.swift │ │ │ ├── ScriptsSettingsViewController.swift │ │ │ └── ThemeSettingsViewController.swift │ │ └── ScriptsTableViewController.swift │ ├── Editor │ │ ├── BoopLexer.swift │ │ ├── BoopToken.swift │ │ └── Themes │ │ │ ├── Colors.swift │ │ │ └── DefaultTheme.swift │ ├── Info.plist │ ├── System │ │ ├── Models │ │ │ ├── AppVersion.swift │ │ │ ├── Script+Require.swift │ │ │ ├── Script.swift │ │ │ └── ScriptExecution.swift │ │ ├── ScriptManager.swift │ │ └── UpdateBuddy.swift │ ├── Views │ │ ├── OverlayView.swift │ │ ├── PopoverContainerView.swift │ │ ├── PopoverView.swift │ │ ├── ScriptTableView.swift │ │ ├── ScriptTableViewCell.swift │ │ ├── SearchField.swift │ │ ├── StatusView.swift │ │ └── UpdateTextField.swift │ └── scripts │ │ ├── ASCIIToHex.js │ │ ├── AddSlashes.js │ │ ├── AndroidIOSStrings.js │ │ ├── Base64Decode.js │ │ ├── Base64Encode.js │ │ ├── BinaryToDecimal.js │ │ ├── CSVtoJSON.js │ │ ├── CamelCase.js │ │ ├── Collapse.js │ │ ├── CountCharacters.js │ │ ├── CountLines.js │ │ ├── CountWords.js │ │ ├── DateToTimestamp.js │ │ ├── DateToUTC.js │ │ ├── Deburr.js │ │ ├── DecimalToBinary.js │ │ ├── DecimalToHex.js │ │ ├── Downcase.js │ │ ├── EvalJavascript.js │ │ ├── FishHexPathConverter.js │ │ ├── FormatCSS.js │ │ ├── FormatJSON.js │ │ ├── FormatSQL.js │ │ ├── FormatXML.js │ │ ├── HTMLDecode.js │ │ ├── HTMLEncode.js │ │ ├── HTMLEncodeAll.js │ │ ├── HexToASCII.js │ │ ├── HexToDecimal.js │ │ ├── IOSAndroidStrings.js │ │ ├── JSONtoCSV.js │ │ ├── JSONtoYAML.js │ │ ├── JWTDecode.js │ │ ├── JsonToQuery.js │ │ ├── KebabCase.js │ │ ├── LoremIpsum.js │ │ ├── MD5.js │ │ ├── MarkdownQuote.js │ │ ├── MinifyCSS.js │ │ ├── MinifyJSON.js │ │ ├── MinifySQL.js │ │ ├── MinifyXML.js │ │ ├── NatSort.js │ │ ├── PhpUnserialize.js │ │ ├── QueryToJson.js │ │ ├── RemoveDuplicates.js │ │ ├── RemoveSlashes.js │ │ ├── ReplaceSmartQuotes.js │ │ ├── ReverseLines.js │ │ ├── ReverseString.js │ │ ├── Rot13.js │ │ ├── SHA1.js │ │ ├── SHA256.js │ │ ├── SHA512.js │ │ ├── ShuffleLines.js │ │ ├── SnakeCase.js │ │ ├── Sort.js │ │ ├── SortJSON.js │ │ ├── SpongeCase.js │ │ ├── StartCase.js │ │ ├── SumAll.js │ │ ├── Test.js │ │ ├── Trim.js │ │ ├── URLDecode.js │ │ ├── URLDefang.js │ │ ├── URLEncode.js │ │ ├── URLEntitiesDecode.js │ │ ├── URLEntitiesEncode.js │ │ ├── URLRefang.js │ │ ├── Upcase.js │ │ ├── YAMLtoJSON.js │ │ ├── hex2rgb.js │ │ └── lib │ │ ├── base64.js │ │ ├── hashes.js │ │ ├── he.js │ │ ├── js-yaml.js │ │ ├── lodash.boop.js │ │ ├── papaparse.js │ │ └── vkBeautify.js ├── BoopTests │ ├── BoopTests.swift │ ├── Info.plist │ └── ScriptManagerTests.swift ├── Documentation │ ├── ConvertingNodeModules.md │ ├── CustomScripts.md │ ├── Debugging.md │ ├── Images │ │ ├── UI.png │ │ ├── breakpoints.png │ │ ├── console.png │ │ ├── debugger.png │ │ ├── developMenu.png │ │ ├── modules.png │ │ └── safari.png │ ├── Modules.md │ └── Readme.md └── UI │ ├── Base.lproj │ └── MainMenu.xib │ └── Preferences.storyboard ├── DOING.md ├── LICENSE ├── README.md └── Scripts ├── CSVtoJSONheaderless.js ├── CalculateSize.js ├── CreateProjectGlossaryMarkdown.js ├── DIGI2ASCII.js ├── FromUnicode.js ├── JoinLines.js ├── JoinLinesWithComma.js ├── JoinLinesWithSpace.js ├── JsObjectToJSON.js ├── LineComparer.js ├── NewBoopScript.js ├── README.md ├── ShuffleCharacters.js ├── TimeToSecond.js ├── TrimEnd.js ├── TrimStart.js ├── Wadsworth.js ├── WkbToWkt.js ├── WktToWkb.js ├── contrastingColor.js ├── convertToMarkdownTable.js ├── generateHashtag.js ├── jsToPhp.js ├── listToHTMLList.js ├── rgb2hex.js ├── toUnicode.js ├── toggleCamelHyphen.js └── tsvToJson.js /.gitattributes: -------------------------------------------------------------------------------- 1 | Boop/Boop/scripts/* linguist-vendored -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | # Package.resolved 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | # Pods/ 50 | 51 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 64 | 65 | fastlane/report.xml 66 | fastlane/Preview.html 67 | fastlane/screenshots/**/*.png 68 | fastlane/test_output 69 | 70 | # MacOS 71 | .DS_Store 72 | 73 | -------------------------------------------------------------------------------- /Boop/Boop.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Boop/Boop.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Boop/Boop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "fuse-swift", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/IvanMathy/fuse-swift", 7 | "state" : { 8 | "revision" : "acb9a2ec2789ce1dcca4d01872a20da801e7ac55", 9 | "version" : "2.0.0" 10 | } 11 | }, 12 | { 13 | "identity" : "savannakit", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/IvanMathy/savannakit", 16 | "state" : { 17 | "branch" : "main", 18 | "revision" : "bb51ee074152a7361e7085d10723f699c79fe673" 19 | } 20 | } 21 | ], 22 | "version" : 2 23 | } 24 | -------------------------------------------------------------------------------- /Boop/Boop.xcodeproj/xcshareddata/xcschemes/Boop.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 55 | 61 | 62 | 63 | 64 | 70 | 72 | 78 | 79 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /Boop/Boop/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Boop 4 | // 5 | // Created by Ivan on 1/26/19. 6 | // Copyright © 2019 OKatBest. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import SavannaKit 11 | 12 | @NSApplicationMain 13 | class AppDelegate: NSObject, NSApplicationDelegate { 14 | 15 | @IBOutlet weak var window: NSWindow! 16 | @IBOutlet weak var openPickerMenuItem: NSMenuItem! 17 | @IBOutlet weak var closePickerMenuItem: NSMenuItem! 18 | 19 | @IBOutlet weak var popoverViewController: PopoverViewController! 20 | @IBOutlet weak var scriptManager: ScriptManager! 21 | @IBOutlet weak var editor: SyntaxTextView! 22 | 23 | // Frame auto save name for app window frame restoration. 24 | private static let appWindowName = "boop.app.window" 25 | 26 | func applicationWillFinishLaunching(_ notification: Notification) { 27 | ThemeSettingsViewController.applyTheme() 28 | 29 | NSWindow.allowsAutomaticWindowTabbing = false 30 | NSApp.servicesProvider = self 31 | 32 | // Restore app window frame. 33 | window.setFrameUsingName(AppDelegate.appWindowName) 34 | } 35 | 36 | func applicationWillTerminate(_ aNotification: Notification) { 37 | // Memorize app window frame for restoration. 38 | window.saveFrame(usingName: AppDelegate.appWindowName) 39 | } 40 | 41 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 42 | return true 43 | } 44 | 45 | @IBAction func showPreferencesWindow(_ sender: NSMenuItem) { 46 | let controller = NSStoryboard.init(name: "Preferences", bundle: nil).instantiateInitialController() as? NSWindowController 47 | 48 | controller?.showWindow(sender) 49 | 50 | } 51 | 52 | // Menu Stuff 53 | 54 | @IBAction func openPickerMenu(_ sender: NSMenuItem) { 55 | popoverViewController.show() 56 | } 57 | 58 | @IBAction func closePickerMenu(_ sender: Any) { 59 | popoverViewController.hide() 60 | } 61 | 62 | @IBAction func executeLastScript(_ sender: Any) { 63 | popoverViewController.runScriptAgain() 64 | } 65 | 66 | @IBAction func reloadScripts(_ sender: Any) { 67 | scriptManager.reloadScripts() 68 | } 69 | 70 | func setPopover(isOpen: Bool) { 71 | closePickerMenuItem.isHidden = !isOpen 72 | openPickerMenuItem.isHidden = isOpen 73 | } 74 | 75 | @objc func textServiceHandler(_ pboard: NSPasteboard, userData: String, error: NSErrorPointer) { 76 | if let string = pboard.string(forType: NSPasteboard.PasteboardType.string) { 77 | editor.contentTextView.string = string 78 | } 79 | } 80 | 81 | } 82 | 83 | -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icon_16x16.png", 5 | "idiom" : "mac", 6 | "scale" : "1x", 7 | "size" : "16x16" 8 | }, 9 | { 10 | "filename" : "icon_16x16@2x.png", 11 | "idiom" : "mac", 12 | "scale" : "2x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "icon_32x32.png", 17 | "idiom" : "mac", 18 | "scale" : "1x", 19 | "size" : "32x32" 20 | }, 21 | { 22 | "filename" : "icon_32x32@2x.png", 23 | "idiom" : "mac", 24 | "scale" : "2x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "icon_128x128.png", 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "filename" : "icon_128x128@2x.png", 35 | "idiom" : "mac", 36 | "scale" : "2x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "icon_256x256.png", 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "256x256" 44 | }, 45 | { 46 | "filename" : "icon_256x256@2x.png", 47 | "idiom" : "mac", 48 | "scale" : "2x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "icon_512x512.png", 53 | "idiom" : "mac", 54 | "scale" : "1x", 55 | "size" : "512x512" 56 | }, 57 | { 58 | "filename" : "icon_512x512@2x.png", 59 | "idiom" : "mac", 60 | "scale" : "2x", 61 | "size" : "512x512" 62 | } 63 | ], 64 | "info" : { 65 | "author" : "xcode", 66 | "version" : 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/AppIcon.appiconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/AppIcon.appiconset/icon_128x128.png -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/AppIcon.appiconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/AppIcon.appiconset/icon_16x16.png -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/AppIcon.appiconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/AppIcon.appiconset/icon_256x256.png -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/AppIcon.appiconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/AppIcon.appiconset/icon_32x32.png -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/AppIcon.appiconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/AppIcon.appiconset/icon_512x512.png -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-HTML.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-source-code-filled(1).pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template", 14 | "preserves-vector-representation" : true 15 | } 16 | } -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-HTML.imageset/icons8-source-code-filled(1).pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-HTML.imageset/icons8-source-code-filled(1).pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-abacus.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-abacus.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true, 14 | "template-rendering-intent" : "template" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-abacus.imageset/icons8-abacus.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-abacus.imageset/icons8-abacus.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-broom.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-broom.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template", 14 | "preserves-vector-representation" : true 15 | } 16 | } -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-broom.imageset/icons8-broom.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-broom.imageset/icons8-broom.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-camel.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-camel.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true, 14 | "template-rendering-intent" : "template" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-camel.imageset/icons8-camel.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-camel.imageset/icons8-camel.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-collapse.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-merge_horizontal.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-collapse.imageset/icons8-merge_horizontal.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-collapse.imageset/icons8-merge_horizontal.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-color-wheel.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-color_mode.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-color-wheel.imageset/icons8-color_mode.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-color-wheel.imageset/icons8-color_mode.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-colosseum.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-colosseum.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true, 14 | "template-rendering-intent" : "template" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-colosseum.imageset/icons8-colosseum.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-colosseum.imageset/icons8-colosseum.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-command.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-run_command.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true, 14 | "template-rendering-intent" : "template" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-command.imageset/icons8-run_command.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-command.imageset/icons8-run_command.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-counter.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-counter.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template", 14 | "preserves-vector-representation" : true 15 | } 16 | } -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-counter.imageset/icons8-counter.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-counter.imageset/icons8-counter.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-dice.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-dice.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-dice.imageset/icons8-dice.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-dice.imageset/icons8-dice.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-elephant.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-elephant.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true, 14 | "template-rendering-intent" : "template" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-elephant.imageset/icons8-elephant.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-elephant.imageset/icons8-elephant.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-filtration.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-filtration.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-filtration.imageset/icons8-filtration.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-filtration.imageset/icons8-filtration.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-fingerprint.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-fingerprint.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template", 14 | "preserves-vector-representation" : true 15 | } 16 | } -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-fingerprint.imageset/icons8-fingerprint.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-fingerprint.imageset/icons8-fingerprint.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-flask.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-test_tube.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true, 14 | "template-rendering-intent" : "template" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-flask.imageset/icons8-test_tube.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-flask.imageset/icons8-test_tube.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-flip.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-flip-vertical.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template", 14 | "preserves-vector-representation" : true 15 | } 16 | } -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-flip.imageset/icons8-flip-vertical.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-flip.imageset/icons8-flip-vertical.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-globe.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-globe.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-globe.imageset/icons8-globe.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-globe.imageset/icons8-globe.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-identification.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-identification_documents.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-identification.imageset/icons8-identification_documents.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-identification.imageset/icons8-identification_documents.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-kebab.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-kebab.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true, 14 | "template-rendering-intent" : "template" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-kebab.imageset/icons8-kebab.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-kebab.imageset/icons8-kebab.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-link.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-link.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template", 14 | "preserves-vector-representation" : true 15 | } 16 | } -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-link.imageset/icons8-link.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-link.imageset/icons8-link.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-metamorphose.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-metamorphose.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template", 14 | "preserves-vector-representation" : true 15 | } 16 | } -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-metamorphose.imageset/icons8-metamorphose.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-metamorphose.imageset/icons8-metamorphose.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-percentage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-percentage.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-percentage.imageset/icons8-percentage.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-percentage.imageset/icons8-percentage.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-pineapple.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-pineapple.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-pineapple.imageset/icons8-pineapple.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-pineapple.imageset/icons8-pineapple.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-quote.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-get-quote.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template", 14 | "preserves-vector-representation" : true 15 | } 16 | } -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-quote.imageset/icons8-get-quote.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-quote.imageset/icons8-get-quote.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-roman.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-roman_helmet.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true, 14 | "template-rendering-intent" : "template" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-roman.imageset/icons8-roman_helmet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-roman.imageset/icons8-roman_helmet.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-scissors.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-scissors.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true, 14 | "template-rendering-intent" : "template" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-scissors.imageset/icons8-scissors.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-scissors.imageset/icons8-scissors.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-snake.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-snake.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true, 14 | "template-rendering-intent" : "template" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-snake.imageset/icons8-snake.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-snake.imageset/icons8-snake.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-sort-characters.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-alphabetical_sorting.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true, 14 | "template-rendering-intent" : "template" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-sort-characters.imageset/icons8-alphabetical_sorting.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-sort-characters.imageset/icons8-alphabetical_sorting.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-sort-numbers.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-reversed_numerical_sorting.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true, 14 | "template-rendering-intent" : "template" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-sort-numbers.imageset/icons8-reversed_numerical_sorting.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-sort-numbers.imageset/icons8-reversed_numerical_sorting.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-table.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-merge_cells.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-table.imageset/icons8-merge_cells.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-table.imageset/icons8-merge_cells.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-term.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-term.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-term.imageset/icons8-term.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-term.imageset/icons8-term.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-translation.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-translation.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true, 14 | "template-rendering-intent" : "template" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-translation.imageset/icons8-translation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-translation.imageset/icons8-translation.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-type.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-font_size.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-type.imageset/icons8-font_size.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-type.imageset/icons8-font_size.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-unknown.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-question-mark.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template", 14 | "preserves-vector-representation" : true 15 | } 16 | } -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-unknown.imageset/icons8-question-mark.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-unknown.imageset/icons8-question-mark.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-watch.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-pocket-watch.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-watch.imageset/icons8-pocket-watch.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-watch.imageset/icons8-pocket-watch.pdf -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-website.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-website.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Boop/Boop/Assets.xcassets/Icons/icons8-website.imageset/icons8-website.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Boop/Assets.xcassets/Icons/icons8-website.imageset/icons8-website.pdf -------------------------------------------------------------------------------- /Boop/Boop/Boop.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.bookmarks.app-scope 8 | 9 | com.apple.security.files.user-selected.read-only 10 | 11 | com.apple.security.network.client 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Boop/Boop/BoopRelease.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.bookmarks.app-scope 8 | 9 | com.apple.security.files.user-selected.read-only 10 | 11 | com.apple.security.network.client 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Boop/Boop/Controllers/MainViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewController.swift 3 | // Boop 4 | // 5 | // Created by Ivan on 1/26/19. 6 | // Copyright © 2019 OKatBest. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import SavannaKit 11 | 12 | class MainViewController: NSViewController { 13 | 14 | @IBOutlet weak var editorView: SyntaxTextView! 15 | @IBOutlet weak var updateBuddy: UpdateBuddy! 16 | @IBOutlet weak var checkUpdateMenuItem: NSMenuItem! 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | 21 | #if APPSTORE 22 | 23 | checkUpdateMenuItem.isHidden = true 24 | 25 | #endif 26 | 27 | editorView.delegate = self 28 | 29 | editorView.contentTextView.selectedTextAttributes = [.backgroundColor:NSColor(red:0.19, green:0.44, blue:0.71, alpha:1.0), .foregroundColor: NSColor.white] 30 | 31 | } 32 | @IBAction func openHelp(_ sender: Any) { 33 | open(url: "https://boop.okat.best/docs/") 34 | } 35 | 36 | 37 | @IBAction func openScripts(_ sender: Any) { 38 | open(url: "https://boop.okat.best/scripts/") 39 | } 40 | 41 | 42 | func open(url: String) { 43 | guard let url = URL(string: url) else { 44 | assertionFailure("Could not generate help URL.") 45 | return 46 | } 47 | NSWorkspace.shared.open(url) 48 | } 49 | 50 | @IBAction func clear(_ sender: Any) { 51 | let textView = editorView.contentTextView 52 | textView.textStorage?.beginEditing() 53 | 54 | let range = NSRange(location: 0, length: textView.textStorage?.length ?? textView.string.count) 55 | 56 | guard textView.shouldChangeText(in: range, replacementString: "") else { 57 | return 58 | } 59 | 60 | textView.textStorage?.replaceCharacters(in: range, with: "") 61 | 62 | textView.textStorage?.endEditing() 63 | textView.didChangeText() 64 | } 65 | 66 | 67 | @IBAction func checkForUpdates(_ sender: Any) { 68 | updateBuddy.check() 69 | } 70 | } 71 | 72 | extension MainViewController: SyntaxTextViewDelegate { 73 | func theme(for appearance: NSAppearance) -> SyntaxColorTheme { 74 | return DefaultTheme(appearance: appearance) 75 | } 76 | func didChangeText(_ syntaxTextView: SyntaxTextView) { 77 | 78 | } 79 | 80 | func didChangeSelectedRange(_ syntaxTextView: SyntaxTextView, selectedRange: NSRange) { 81 | 82 | } 83 | 84 | func didChangeFont(_ font: Font) { 85 | // 86 | } 87 | 88 | func lexerForSource(_ source: String) -> Lexer { 89 | return BoopLexer() 90 | } 91 | 92 | 93 | } 94 | -------------------------------------------------------------------------------- /Boop/Boop/Controllers/PopoverViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PopoverViewController.swift 3 | // Boop 4 | // 5 | // Created by Ivan on 1/27/19. 6 | // Copyright © 2019 OKatBest. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import SavannaKit 11 | 12 | class PopoverViewController: NSViewController { 13 | 14 | @IBOutlet weak var overlayView: OverlayView! 15 | @IBOutlet weak var popoverView: PopoverContainerView! 16 | @IBOutlet weak var searchField: SearchField! 17 | @IBOutlet weak var editorView: SyntaxTextView! 18 | @IBOutlet weak var statusView: StatusView! 19 | 20 | @IBOutlet weak var scriptManager: ScriptManager! 21 | 22 | @IBOutlet weak var tableView: ScriptTableView! 23 | @IBOutlet weak var tableHeightConstraint: NSLayoutConstraint! 24 | @IBOutlet weak var tableViewController: ScriptsTableViewController! 25 | @IBOutlet weak var appDelegate: AppDelegate! 26 | 27 | var enabled = false // Closed by default 28 | 29 | override func viewDidLoad() { 30 | super.viewDidLoad() 31 | 32 | // Double-click script selection 33 | tableView.doubleAction = #selector(runSelectedScript) 34 | 35 | // Dismiss popover on background view click 36 | overlayView.onMouseDown = { [weak self] in 37 | self?.hide() 38 | } 39 | 40 | setupKeyHandlers() 41 | } 42 | 43 | func setupKeyHandlers() { 44 | 45 | var keyHandler: (_: NSEvent) -> NSEvent? 46 | keyHandler = { 47 | (_ theEvent: NSEvent) -> NSEvent? in 48 | 49 | var didSomething = false 50 | 51 | // Key codes: 52 | let kVKTab = 0x30 53 | // 125 is down arrow 54 | // 126 is up 55 | // 53 is escape 56 | // 36 is enter 57 | 58 | if theEvent.keyCode == 53 && self.enabled { // ESCAPE 59 | 60 | // Let's dismiss the popover 61 | self.hide() 62 | 63 | didSomething = true 64 | } 65 | 66 | if theEvent.keyCode == 36 && self.enabled { // ENTER 67 | 68 | guard self.tableViewController.selectedScript != nil else { 69 | return theEvent 70 | } 71 | 72 | self.runSelectedScript() 73 | 74 | didSomething = true 75 | } 76 | 77 | let window = self.view.window 78 | 79 | if theEvent.keyCode == kVKTab && self.enabled { 80 | if window?.firstResponder is NSTextView && 81 | (window?.firstResponder as! NSTextView).delegate is SearchField { 82 | let offset = theEvent.modifierFlags.contains(.shift) ? -1 : 1 83 | let newSel = IndexSet([self.tableView.selectedRow + offset]) 84 | self.tableView.selectRowIndexes(newSel, byExtendingSelection: false) 85 | self.tableView.scrollRowToVisible(self.tableView.selectedRow) 86 | } 87 | didSomething = true // prevent tabbing back into text document 88 | } 89 | 90 | if window?.firstResponder is NSTextView && 91 | (window?.firstResponder as! NSTextView).delegate is SearchField && 92 | theEvent.keyCode == 125 { // DOWN 93 | 94 | // Why -1? I don't know, and I don't even care. 95 | let indexSet = IndexSet(integer: -1) 96 | self.tableView.selectRowIndexes(indexSet, byExtendingSelection: false) 97 | window?.makeFirstResponder(self.tableView) 98 | } 99 | 100 | // Oh hey look now somehow it's 0. 101 | if window?.firstResponder is NSTableView && 102 | self.tableView.selectedRow == 0 && 103 | theEvent.keyCode == 126 { // UP 104 | 105 | window?.makeFirstResponder(self.searchField) 106 | // This doesn't work for some reason. 107 | //self.searchField.moveToEndOfLine(nil) 108 | } 109 | 110 | guard didSomething else { 111 | return theEvent 112 | } 113 | 114 | // Return an empty event to avoid the funk sound 115 | return nil 116 | } 117 | 118 | // Creates an object we do not own, but must keep track 119 | // of it so that it can be "removed" when we're done 120 | NSEvent.addLocalMonitorForEvents(matching: .keyDown, handler: keyHandler) 121 | 122 | } 123 | 124 | func show() { 125 | overlayView.show() 126 | popoverView.show() 127 | 128 | // FIXME: Use localized strings 129 | statusView.setStatus(.help("Select your action")) 130 | 131 | self.searchField.stringValue = "" 132 | self.tableHeightConstraint.constant = 0 133 | 134 | self.view.window?.makeFirstResponder(self.searchField) 135 | self.enabled = true 136 | 137 | appDelegate.setPopover(isOpen: true) 138 | 139 | } 140 | 141 | func hide() { 142 | overlayView.hide() 143 | popoverView.hide() 144 | 145 | statusView.setStatus(.normal) 146 | 147 | self.view.window?.makeFirstResponder(self.editorView.contentTextView) 148 | self.enabled = false 149 | self.tableHeightConstraint.animator().constant = 0 150 | 151 | tableViewController.results = [] 152 | 153 | appDelegate.setPopover(isOpen: false) 154 | } 155 | 156 | func runScriptAgain() { 157 | self.scriptManager.runScriptAgain(editor: self.editorView) 158 | } 159 | 160 | @objc private func runSelectedScript() { 161 | guard let script = tableViewController.selectedScript else { 162 | return 163 | } 164 | 165 | // Let's dismiss the popover 166 | hide() 167 | 168 | // Run the script afterwards in case we need to show a status 169 | scriptManager.runScript(script, into: editorView) 170 | } 171 | 172 | } 173 | 174 | extension PopoverViewController: NSTextFieldDelegate { 175 | func controlTextDidChange(_ obj: Notification) { 176 | guard (obj.object as? SearchField) == searchField else { 177 | return 178 | } 179 | 180 | let results = scriptManager.search(searchField.stringValue) 181 | tableViewController.results = results 182 | 183 | self.tableHeightConstraint.constant = CGFloat(45 * min(5, results.count) + ((results.count != 0) ? 20 : 0)) 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /Boop/Boop/Controllers/Preferences/PreferencesTabViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreferencesTabViewController.swift 3 | // Boop 4 | // 5 | // Created by Ivan on 11/2/19. 6 | // Copyright © 2019 OKatBest. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class PreferencesTabViewController: NSTabViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | // Do view setup here. 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Boop/Boop/Controllers/Preferences/ScriptsSettingsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScriptsSettingsViewController.swift 3 | // Boop 4 | // 5 | // Created by Ivan on 11/3/19. 6 | // Copyright © 2019 OKatBest. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import Foundation 11 | 12 | class ScriptsSettingsViewController: NSViewController { 13 | 14 | @IBAction func didClickBrowseButton(_ sender: Any) { 15 | let panel = NSOpenPanel() 16 | 17 | panel.canChooseFiles = false 18 | panel.canChooseDirectories = true 19 | panel.allowsMultipleSelection = false 20 | 21 | panel.begin { result in 22 | do { 23 | 24 | guard let url = panel.url, result == NSApplication.ModalResponse.OK else { 25 | return 26 | } 27 | 28 | UserDefaults.standard.set(url, forKey: ScriptManager.userPreferencesPathKey) 29 | 30 | try ScriptManager.setBookmarkData(url: url) 31 | 32 | } catch let error { 33 | print(error) 34 | } 35 | 36 | } 37 | 38 | } 39 | 40 | // Currently disabled since this is not Sandbox friendly... 41 | @IBAction func didClickDefaultLocation(_ sender: Any) { 42 | let alert = NSAlert() 43 | alert.messageText = "The default Boop folder does not exist. Would you like to create it?" 44 | alert.informativeText = "The folder will be created at ~/Documents/Boop." 45 | alert.addButton(withTitle: "Create") 46 | alert.addButton(withTitle: "Cancel") 47 | 48 | guard alert.runModal() == .alertSecondButtonReturn else { 49 | return 50 | } 51 | 52 | 53 | } 54 | 55 | @IBAction func didClickHelpButton(_ sender: Any) { 56 | 57 | guard let url = URL(string: "https://boop.okat.best/docs/scripts") else { 58 | assertionFailure("Could not generate help URL.") 59 | return 60 | } 61 | NSWorkspace.shared.open(url) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Boop/Boop/Controllers/Preferences/ThemeSettingsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ThemeSettingsViewController.swift 3 | // Boop 4 | // 5 | // Created by Ivan on 6/18/20. 6 | // Copyright © 2020 OKatBest. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Cocoa 11 | 12 | enum BoopColorScheme: Int { 13 | case system 14 | case light 15 | case dark 16 | } 17 | 18 | class ThemeSettingsViewController: NSViewController { 19 | static let userPreferencesSchemeKey = "boopColorScheme" 20 | 21 | static func applyTheme() { 22 | switch BoopColorScheme(rawValue: UserDefaults.standard.integer(forKey: "boopColorScheme")) { 23 | case .dark: 24 | NSApp.appearance = NSAppearance(named: .darkAqua) 25 | case .light: 26 | NSApp.appearance = NSAppearance(named: .aqua) 27 | default: 28 | NSApp.appearance = nil 29 | } 30 | } 31 | 32 | override func viewWillAppear() { 33 | super.viewWillAppear() 34 | preferredContentSize = view.fittingSize 35 | } 36 | @IBAction func didChangeColorTheme(_ sender: Any) { 37 | ThemeSettingsViewController.applyTheme() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Boop/Boop/Controllers/ScriptsTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScriptsTableViewController.swift 3 | // Boop 4 | // 5 | // Created by Ivan on 2/13/19. 6 | // Copyright © 2019 OKatBest. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class ScriptsTableViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource { 12 | 13 | @IBOutlet weak var tableView: ScriptTableView! 14 | 15 | var results: [Script] = [] { 16 | didSet { 17 | tableView.reloadData() 18 | } 19 | } 20 | 21 | func numberOfRows(in tableView: NSTableView) -> Int { 22 | return results.count 23 | } 24 | 25 | private func scriptIcon(identifier: String?) -> NSImage? { 26 | guard let identifier = identifier else { 27 | return NSImage(named: "icons8-unknown") 28 | } 29 | if let namedImage = NSImage(named: "icons8-\(identifier)") { 30 | return namedImage 31 | } 32 | if #available(macOS 11.0, *), 33 | let systemImage = NSImage(systemSymbolName: identifier, accessibilityDescription: nil) { 34 | return systemImage 35 | } 36 | return nil 37 | } 38 | 39 | func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { 40 | 41 | let view = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "scriptCell"), owner: self) as! ScriptTableViewCell 42 | 43 | guard let script = scriptAt(row) else { 44 | fatalError("Missing script for index \(row).") 45 | } 46 | 47 | view.titleLabel.stringValue = script.name ?? "No Name 🤔" 48 | view.subtitleLabel.stringValue = script.desc ?? "No Description 😢" 49 | 50 | view.imageView?.image = self.scriptIcon(identifier: script.icon) 51 | 52 | return view 53 | 54 | } 55 | 56 | func scriptAt(_ index: Int) -> Script? { 57 | guard index < results.count else { 58 | return nil 59 | } 60 | return results[index] 61 | } 62 | 63 | var selectedScript:Script? { 64 | guard tableView.selectedRow >= 0 else { 65 | // Nothing selected, return first item 66 | return scriptAt(0) 67 | } 68 | return scriptAt(tableView.selectedRow) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Boop/Boop/Editor/BoopLexer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BoopLexer.swift 3 | // Boop 4 | // 5 | // Created by Ivan on 1/26/19. 6 | // Copyright © 2019 OKatBest. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import SavannaKit 11 | 12 | class BoopLexer: RegexLexer { 13 | 14 | // This is not really an exhaustive list, it's more of a rough estimation of 15 | // what you can encounter in a lot/most of languages. Add more to it! 16 | // BTW is attribute the correct name? Token was taken. 17 | var commonAttributes = ["var", "val", "let", "if", "else", "export", "import", "return", "static", "fun", "function", "func", "class", "open", "new", "as", "where", "select", "delete", "add", "limit", "update", "insert"] 18 | 19 | 20 | var moreAttributes = ["true", "false", "to", "string", "int", "float", "double", "bool", "boolean", "from"] 21 | 22 | func generators(source: String) -> [TokenGenerator] { 23 | 24 | let number = #"\b(?:0x[a-f0-9]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+\-]?\d+)?)\b"# 25 | 26 | 27 | var generators = [TokenGenerator?]() 28 | 29 | generators.append(regexToken(.number, number)) 30 | 31 | // Find common attributes 32 | 33 | generators.append(regexToken(.attribute, #"\b(\#(commonAttributes.joined(separator: "|")))\b"#, options: .caseInsensitive)) 34 | 35 | generators.append(regexToken(.keyword, #"\b(\#(moreAttributes.joined(separator: "|")))\b"#, options: .caseInsensitive)) 36 | 37 | 38 | // Extras 39 | 40 | let UTCDate = "(?:(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]))" 41 | 42 | generators.append(regexToken(.number, UTCDate)) 43 | 44 | // - Match MD5 strings 45 | generators.append(regexToken(.keyword, "[a-f0-9]{32}")) 46 | 47 | // - Bootleg XML-like tags match: 48 | 49 | generators.append(regexToken(.attribute, "<(?:.*?)\\b[^>]*\\/?>")) 50 | 51 | 52 | // - Match JSON labels and generic parameters 53 | generators.append(regexToken(.extra, #""([^"]+?)"\s*(?=:)"#, greedy: true)) 54 | 55 | // Strings 56 | 57 | let quotes = #"(["'`])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1"# 58 | 59 | generators.append(regexToken(.string, quotes, greedy: true)) 60 | 61 | generators.append(regexToken(.string, "(\"\"\")(.*?)(\"\"\")", options: [.dotMatchesLineSeparators, .caseInsensitive], greedy: true)) 62 | 63 | 64 | 65 | 66 | // Comments 67 | 68 | generators.append(regexToken(.comment, #"(?=(\/\/.*))"#)) 69 | 70 | generators.append(regexToken(.comment, #"(?=(\/\*[\s\S]*?(?:\*\/|$)))"#, options: [.dotMatchesLineSeparators], greedy: true)) 71 | 72 | generators.append(regexToken(.comment, #""#, options: [.dotMatchesLineSeparators, .caseInsensitive], greedy: true)) 73 | 74 | 75 | 76 | 77 | return generators.compactMap( { $0 }) 78 | } 79 | 80 | func regexToken(_ type: BoopToken.TokenType, _ pattern:String, options: NSRegularExpression.Options = .caseInsensitive, greedy: Bool = false) -> TokenGenerator? { 81 | guard let regex = try? NSRegularExpression(pattern: pattern, options: options) else { 82 | return nil 83 | } 84 | let generator = RegexTokenGenerator(regularExpression: regex, tokenTransformer: { (range) -> Token in 85 | return BoopToken(type: type, range: range, greedy: greedy) 86 | }) 87 | return TokenGenerator.regex(generator) 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /Boop/Boop/Editor/BoopToken.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BoopToken.swift 3 | // Boop 4 | // 5 | // Created by Ivan on 1/27/19. 6 | // Copyright © 2019 OKatBest. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import SavannaKit 11 | 12 | class BoopToken: Token, CustomStringConvertible { 13 | 14 | 15 | 16 | public enum TokenType: String { 17 | case comment 18 | case string 19 | case attribute 20 | case keyword 21 | case number 22 | case extra 23 | } 24 | 25 | // There is no support for placeholder yet or planned. 26 | var isEditorPlaceholder = false 27 | 28 | // Plain tokens are not even parsed in the first place. 29 | var isPlain = false 30 | 31 | var isActive = true 32 | var isGreedy: Bool 33 | 34 | var range: NSRange 35 | var type: BoopToken.TokenType 36 | 37 | init(type:TokenType, range: NSRange, greedy: Bool = false) { 38 | self.range = range 39 | self.type = type 40 | self.isGreedy = greedy 41 | } 42 | 43 | 44 | var description: String { 45 | return type.rawValue 46 | } 47 | 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Boop/Boop/Editor/Themes/Colors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Colors.swift 3 | // Boop 4 | // 5 | // Created by Ivan on 9/29/19. 6 | // Copyright © 2019 OKatBest. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | struct Colors { 12 | 13 | static let redey: NSColor = #colorLiteral(red: 1, green: 0.2980392157, blue: 0.4862745098, alpha: 1) // #ff4c7c 14 | static let bluish: NSColor = #colorLiteral(red: 0.5, green: 0.8870540044, blue: 1, alpha: 1) // #3586FF 15 | static let cyanish: NSColor = #colorLiteral(red: 0.1595847315, green: 0.951053901, blue: 0.7986315017, alpha: 1) // #4cffb2 16 | static let greenish: NSColor = #colorLiteral(red: 0.6049238592, green: 1, blue: 0, alpha: 1) // #32FF47 17 | static let orangeish: NSColor = #colorLiteral(red: 1, green: 0.6980392157, blue: 0.2980392157, alpha: 1) // #ffb24c 18 | static let yellowishy: NSColor = #colorLiteral(red: 0.9882352941, green: 1, blue: 0.2980392157, alpha: 1) // #fcff4c 19 | static let commentGrey: NSColor = #colorLiteral(red: 0.7008966023, green: 0.7301803691, blue: 0.8538076556, alpha: 1) // #9BCCE3 20 | static let redButDarker: NSColor = #colorLiteral(red: 0.7254901961, green: 0.09411764706, blue: 0.262745098, alpha: 1) // #B91843 21 | static let blueButDarker: NSColor = #colorLiteral(red: 0, green: 0.4156862745, blue: 0.7176470588, alpha: 1) // #006AB7 22 | static let greenButDarker: NSColor = #colorLiteral(red: 0, green: 0.6980392157, blue: 0, alpha: 1) // #00B200 23 | static let purpleButDarker: NSColor = #colorLiteral(red: 0.462745098, green: 0, blue: 0.462745098, alpha: 1) // #760076 24 | static let orangeABitDarker: NSColor = #colorLiteral(red: 1, green: 0.3960784314, blue: 0.1764705882, alpha: 1) // #FF652D 25 | static let cyanTinyBitDarker: NSColor = #colorLiteral(red: 0, green: 0.6941176471, blue: 0.7176470588, alpha: 1) // #00B1B7 26 | static let commentGreyDarkest: NSColor = #colorLiteral(red: 0.5333333333, green: 0.5568627451, blue: 0.6509803922, alpha: 1) // #888EA6 27 | static let purpleButItsLighter: NSColor = #colorLiteral(red: 0.9725490196, green: 0.6470588235, blue: 0.9725490196, alpha: 1) // #F8A5F8 28 | 29 | static func dynamicColor(light: NSColor, dark: NSColor, for appearance: NSAppearance) -> NSColor { 30 | if appearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua { 31 | return dark 32 | } else { 33 | return light 34 | } 35 | } 36 | } 37 | 38 | struct ColorPair { 39 | 40 | static let red = ColorPair(light: Colors.redButDarker, dark: Colors.redey) 41 | static let blue = ColorPair(light: Colors.blueButDarker, dark: Colors.bluish) 42 | static let green = ColorPair(light: Colors.greenButDarker, dark: Colors.greenish) 43 | static let purple = ColorPair(light: Colors.purpleButItsLighter, dark: Colors.purpleButDarker) 44 | static let cyanish = ColorPair(light: Colors.cyanTinyBitDarker, dark: Colors.cyanish) 45 | static let orangish = ColorPair(light: Colors.orangeABitDarker, dark: Colors.orangeish) 46 | 47 | static let body = ColorPair(light: .init(white: 0.1, alpha: 1), dark: .white) 48 | static let normal = ColorPair(light: .init(white: 0.85, alpha: 1), dark: .init(white: 0.3, alpha: 1)) 49 | static let gutter = ColorPair(light: .init(white: 240/255, alpha: 1), dark: .init(white: 27/255, alpha: 1)) 50 | static let popover = ColorPair(light: .init(white: 0.95, alpha: 1), dark: .init(white: 0.12, alpha: 1)) 51 | 52 | static let comments = ColorPair(light: Colors.commentGreyDarkest, dark: Colors.commentGreyDarkest) 53 | static let separator = ColorPair(light: .init(white: 220/255, alpha: 1), dark: .init(white: 45/255, alpha: 1)) 54 | static let background = ColorPair(light: .init(white: 0.95, alpha: 1.0), dark: .init(white: 31/255, alpha: 1.0)) 55 | static let overlayColor = ColorPair(light: .init(white: 0.85, alpha: 0.6), dark: .init(white: 0.08, alpha: 0.6)) 56 | static let popoverBorder = ColorPair(light: .init(white: 220/255, alpha: 1), dark: .init(white: 75/255, alpha: 1)) 57 | static let popoverOutline = ColorPair(light: .init(white: 150/255, alpha: 1), dark: .init(white: 20/255, alpha: 1)) 58 | 59 | 60 | let light: NSColor 61 | let dark: NSColor 62 | 63 | func value(for appearance: NSAppearance) -> NSColor { 64 | return Colors.dynamicColor(light: self.light, dark: self.dark, for: appearance) 65 | } 66 | 67 | var swap: ColorPair { 68 | return ColorPair(light: self.dark, dark: self.light) 69 | } 70 | } 71 | 72 | 73 | -------------------------------------------------------------------------------- /Boop/Boop/Editor/Themes/DefaultTheme.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultTheme.swift 3 | // Boop 4 | // 5 | // Created by Ivan on 1/26/19. 6 | // Copyright © 2019 OKatBest. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import SavannaKit 11 | 12 | class DefaultTheme: SyntaxColorTheme { 13 | 14 | let appearance: NSAppearance 15 | 16 | var tabWidth: Int = 4 17 | 18 | var gutterStyle: GutterStyle { 19 | GutterStyle( 20 | backgroundColor: ColorPair.gutter.value(for: appearance), 21 | separatorColor: ColorPair.separator.value(for: appearance), 22 | minimumWidth: 40 23 | ) 24 | } 25 | 26 | var font: Font = NSFont(name: "SFMono-Regular", size: 15) ?? Font(name: "Menlo", size: 15)! 27 | 28 | let lineNumbersStyle: LineNumbersStyle? 29 | 30 | var backgroundColor: Color { 31 | return ColorPair.background.value(for: appearance) 32 | } 33 | 34 | 35 | init(appearance: NSAppearance) { 36 | self.appearance = appearance 37 | lineNumbersStyle = LineNumbersStyle(font: font, textColor: Color(red: 100/255, green: 100/255, blue: 100/255, alpha: 1.0)) 38 | } 39 | 40 | func globalAttributes() -> [NSAttributedString.Key: Any] { 41 | 42 | var attributes = [NSAttributedString.Key: Any]() 43 | 44 | attributes[.font] = self.font 45 | attributes[.foregroundColor] = ColorPair.body.value(for: appearance) 46 | 47 | return attributes 48 | } 49 | 50 | func attributes(for token: Token) -> [NSAttributedString.Key : Any] { 51 | 52 | guard let token = token as? BoopToken else { 53 | return [:] 54 | } 55 | 56 | switch token.type { 57 | case .comment: 58 | return [.foregroundColor: ColorPair.comments.value(for: appearance)] 59 | case .string: 60 | return [.foregroundColor: ColorPair.red.value(for: appearance)] 61 | case .attribute: 62 | return [.foregroundColor: ColorPair.cyanish.value(for: appearance)] 63 | case .number: 64 | return [.foregroundColor: ColorPair.orangish.value(for: appearance)] 65 | case .extra: 66 | return [.foregroundColor: ColorPair.blue.value(for: appearance)] 67 | case .keyword: 68 | return [.foregroundColor: ColorPair.green.value(for: appearance)] 69 | } 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /Boop/Boop/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | LSApplicationCategoryType 22 | public.app-category.developer-tools 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2020 OKatBest. All rights reserved. 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | NSServices 32 | 33 | 34 | NSKeyEquivalent 35 | 36 | default 37 | ~@^B 38 | 39 | NSMenuItem 40 | 41 | default 42 | Send to Boop 43 | 44 | NSMessage 45 | textServiceHandler 46 | NSPortName 47 | Boop 48 | NSRequiredContext 49 | 50 | NSSendTypes 51 | 52 | NSStringPboardType 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /Boop/Boop/System/Models/AppVersion.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppVersion.swift 3 | // Boop 4 | // 5 | // Created by Ivan on 5/30/20. 6 | // Copyright © 2020 OKatBest. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct VersionContainer: Codable { 12 | let mas: Version 13 | let standalone: Version 14 | } 15 | 16 | struct Version: Codable { 17 | let link: String 18 | let version: String 19 | } 20 | -------------------------------------------------------------------------------- /Boop/Boop/System/Models/Script+Require.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Script+Require.swift 3 | // Boop 4 | // 5 | // Created by Ivan on 6/29/20. 6 | // Copyright © 2020 OKatBest. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import JavaScriptCore 11 | 12 | extension Script { 13 | 14 | static private let boopPrefix = "@boop/" 15 | static private let moduleExt = ".js" 16 | 17 | func setupRequire(context: JSContext) { 18 | let require: @convention(block) (String) -> (JSValue?) = { 19 | [unowned self] path in 20 | 21 | var path = path 22 | 23 | if !path.hasSuffix(Script.moduleExt) { 24 | path += Script.moduleExt 25 | } 26 | 27 | guard 28 | let url = self.url(for: path), 29 | let rawCode = try? String(contentsOf: url) 30 | else { 31 | return nil 32 | } 33 | 34 | // This is not ideal, I tried using native JSC bindings 35 | // but no luck getting it to play nice. TODO I guess? 36 | 37 | let wrappedCode = 38 | """ 39 | /*********************************** 40 | * Start of Boop's wrapper * 41 | ***********************************/ 42 | 43 | (function() { 44 | var module = { 45 | exports: {} 46 | }; 47 | 48 | const moduleWrapper = (function (exports, module) { 49 | 50 | /*********************************** 51 | * End of Boop's wrapper * 52 | ***********************************/ 53 | 54 | \(rawCode) 55 | 56 | /*********************************** 57 | * Start of Boop's wrapper * 58 | ***********************************/ 59 | 60 | }).apply(module.exports, [module.exports, module]); 61 | 62 | return module.exports; 63 | })(); 64 | 65 | /*********************************** 66 | * End of Boop's wrapper * 67 | ***********************************/ 68 | 69 | """ 70 | 71 | return JSContext.current().evaluateScript(wrappedCode, withSourceURL: url) 72 | } 73 | 74 | context.setObject(require, forKeyedSubscript: "require" as NSString) 75 | 76 | } 77 | 78 | private func url(for path: String) -> URL? { 79 | if path.starts(with: Script.boopPrefix) { 80 | let fileName = String(path.dropFirst(Script.boopPrefix.count).dropLast(3)) 81 | return Bundle.main.url(forResource: fileName, withExtension: Script.moduleExt, subdirectory: "scripts/lib") 82 | } 83 | 84 | guard 85 | !self.isBuiltInt, 86 | let url = try? ScriptManager.getBookmarkURL() 87 | else { 88 | // For now, built in scripts can't import custom stuff. 89 | return nil 90 | } 91 | 92 | return url.appendingPathComponent(path, isDirectory: false) 93 | 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Boop/Boop/System/Models/Script.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Script.swift 3 | // Boop 4 | // 5 | // Created by Ivan on 2/13/19. 6 | // Copyright © 2019 OKatBest. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import JavaScriptCore 11 | import Fuse 12 | 13 | class Script: NSObject { 14 | 15 | var isBuiltInt: Bool 16 | var url: URL 17 | var scriptCode: String 18 | 19 | 20 | lazy var context: JSContext = { [unowned self] in 21 | let context: JSContext = JSContext() 22 | context.name = self.name ?? "Unknown Script" 23 | 24 | context.exceptionHandler = { [unowned self] context, exception in 25 | let message = "[\(self.name ?? "Unknown Script")] Error: \(exception?.toString() ?? "Unknown Error") " 26 | print(message) 27 | self.onScriptError(message: message) 28 | } 29 | 30 | self.setupRequire(context: context) 31 | 32 | context.setObject(ScriptExecution.self, forKeyedSubscript: "ScriptExecution" as NSString) 33 | 34 | context.evaluateScript(self.scriptCode, withSourceURL: url) 35 | 36 | return context 37 | }() 38 | 39 | lazy var main: JSValue = { 40 | return context.objectForKeyedSubscript("main") 41 | }() 42 | 43 | var info:[String: Any] 44 | 45 | var name: String? 46 | var tags: String? 47 | var desc: String? 48 | var icon: String? 49 | var bias: Double? 50 | 51 | weak var delegate: ScriptDelegate? 52 | 53 | init(url: URL, script:String, parameters: [String: Any], builtIn: Bool, delegate: ScriptDelegate? = nil) { 54 | 55 | 56 | self.scriptCode = script 57 | self.info = parameters 58 | self.url = url 59 | self.isBuiltInt = builtIn 60 | 61 | self.name = parameters["name"] as? String 62 | self.tags = parameters["tags"] as? String 63 | self.desc = parameters["description"] as? String 64 | self.icon = (parameters["icon"] as? String)?.lowercased() 65 | self.bias = parameters["bias"] as? Double 66 | 67 | 68 | 69 | // We set the delegate after the initial eval to avoid 70 | // showing init errors from scripts at launch. 71 | self.delegate = delegate 72 | 73 | } 74 | 75 | func onScriptError(message: String) { 76 | self.delegate?.onScriptError(message: message) 77 | } 78 | 79 | func onScriptInfo(message: String) { 80 | self.delegate?.onScriptInfo(message: message) 81 | } 82 | 83 | func run(with execution: ScriptExecution) { 84 | main.call(withArguments: [execution]) 85 | } 86 | 87 | } 88 | 89 | extension Script: Fuseable { 90 | 91 | var properties: [FuseProperty] { 92 | return [ 93 | FuseProperty(value: name, weight: 0.9), 94 | FuseProperty(value: tags, weight: 0.6), 95 | FuseProperty(value: desc, weight: 0.2) 96 | ] 97 | } 98 | } 99 | 100 | protocol ScriptDelegate: class { 101 | func onScriptError(message: String) 102 | func onScriptInfo(message: String) 103 | } 104 | -------------------------------------------------------------------------------- /Boop/Boop/System/Models/ScriptExecution.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScriptExecution.swift 3 | // Boop 4 | // 5 | // Created by Ivan on 4/21/19. 6 | // Copyright © 2019 OKatBest. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import JavaScriptCore 11 | 12 | @objc protocol ScriptExecutionJSExport: JSExport { 13 | var selection: String? { get set } 14 | var fullText: String? { get set } 15 | var text: String? { get set } 16 | var isSelection: Bool { get } 17 | func postError(_ error: String) 18 | func postInfo(_ info: String) 19 | func insert(_ newValue: String) 20 | } 21 | 22 | 23 | @objc class ScriptExecution: NSObject, ScriptExecutionJSExport { 24 | 25 | var isSelection: Bool 26 | var selection: String? 27 | var fullText: String? 28 | let insertIndex: Int? 29 | 30 | var insertOffset: Int = 0 31 | 32 | private weak var script: Script? 33 | 34 | init(selection: String?, fullText: String, script: Script, insertIndex: Int?) { 35 | self.isSelection = (selection != nil) 36 | self.selection = selection 37 | self.fullText = fullText 38 | self.script = script 39 | self.insertIndex = insertIndex 40 | } 41 | 42 | var text: String? { 43 | get { 44 | return isSelection ? selection : fullText 45 | } 46 | set{ 47 | if isSelection { 48 | selection = newValue 49 | } else { 50 | fullText = newValue 51 | } 52 | } 53 | } 54 | 55 | 56 | func postError(_ error: String) { 57 | self.script?.onScriptError(message: error) 58 | } 59 | func postInfo(_ info: String) { 60 | self.script?.onScriptInfo(message: info) 61 | } 62 | 63 | func insert(_ newValue: String) { 64 | guard !isSelection else { 65 | selection = newValue 66 | return 67 | } 68 | guard 69 | let insertIndex = self.insertIndex, fullText != nil, 70 | let fullText = fullText, 71 | let range = Range(NSMakeRange(insertIndex, 0), in: fullText) 72 | else { 73 | self.fullText = newValue 74 | return 75 | } 76 | 77 | let point = fullText.index(range.lowerBound, offsetBy: self.insertOffset) 78 | 79 | self.fullText?.insert(contentsOf: newValue, at: point) 80 | 81 | self.insertOffset += newValue.count 82 | 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Boop/Boop/System/UpdateBuddy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UpdateBuddy.swift 3 | // Boop 4 | // 5 | // Created by Ivan on 5/30/20. 6 | // Copyright © 2020 OKatBest. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class UpdateBuddy: NSObject { 12 | 13 | @IBOutlet weak var statusView: StatusView! 14 | 15 | var firstCheck = true 16 | 17 | override init() { 18 | super.init() 19 | self.check() 20 | } 21 | 22 | func check() { 23 | 24 | guard let url = URL(string: "https://boop.okat.best/version.json") else { 25 | print("Cannot create update checker URL...") 26 | return 27 | } 28 | 29 | let config = URLSessionConfiguration.default 30 | config.requestCachePolicy = .reloadIgnoringLocalCacheData 31 | config.urlCache = nil 32 | 33 | let session = URLSession.init(configuration: config) 34 | 35 | session.dataTask(with: URLRequest(url: url), completionHandler: { data, response, error -> Void in 36 | guard let data = data else { 37 | return 38 | } 39 | 40 | DispatchQueue.main.async { 41 | try? self.handleResponse(data: data) 42 | } 43 | 44 | }).resume() 45 | } 46 | 47 | private func handleResponse(data: Data) throws { 48 | let decoder = JSONDecoder() 49 | let payload = try decoder.decode(VersionContainer.self, from: data) 50 | 51 | #if APPSTORE 52 | 53 | let latest = payload.mas 54 | 55 | #else 56 | 57 | let latest = payload.standalone 58 | 59 | #endif 60 | 61 | guard let thisVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else { 62 | return 63 | } 64 | 65 | if latest.version.compare(thisVersion, options: .numeric) == .orderedDescending { 66 | self.statusView.setStatus(.updateAvailable(latest.link)) 67 | } else if !firstCheck { 68 | self.statusView.setStatus(.success("Boop is up to date!")) 69 | } 70 | 71 | self.firstCheck = false 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Boop/Boop/Views/OverlayView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OverlayView.swift 3 | // Boop 4 | // 5 | // Created by Ivan on 1/27/19. 6 | // Copyright © 2019 OKatBest. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class OverlayView: NSView { 12 | 13 | var onMouseDown: (() -> Void)? 14 | 15 | required init?(coder decoder: NSCoder) { 16 | super.init(coder: decoder) 17 | self.wantsLayer = true 18 | self.animator().isHidden = true 19 | self.alphaValue = 0; 20 | 21 | setBackground() 22 | } 23 | 24 | func setBackground() { 25 | 26 | self.layer?.backgroundColor = ColorPair.overlayColor.value(for: self.effectiveAppearance).cgColor 27 | 28 | 29 | } 30 | 31 | 32 | func show() { 33 | 34 | self.animator().alphaValue = 1 35 | self.animator().isHidden = false 36 | } 37 | 38 | func hide() { 39 | 40 | self.animator().alphaValue = 0 41 | self.animator().isHidden = true 42 | } 43 | 44 | override func mouseDown(with event: NSEvent) { 45 | onMouseDown?() 46 | } 47 | 48 | override func viewDidChangeEffectiveAppearance() { 49 | super.viewDidChangeEffectiveAppearance() 50 | self.setBackground() 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /Boop/Boop/Views/PopoverContainerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PopoverContainerView.swift 3 | // Boop 4 | // 5 | // Created by Ivan on 7/8/21. 6 | // Copyright © 2021 OKatBest. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AppKit 11 | 12 | 13 | class PopoverContainerView: NSView { 14 | 15 | // This is a wrapper view so that the child popver actually clips 16 | // the contained table. The issue was the shadow is technically 17 | // part of the mask so rounded corners would get ignored. 18 | 19 | required init?(coder decoder: NSCoder) { 20 | super.init(coder: decoder) 21 | 22 | self.wantsLayer = true 23 | self.layer?.cornerRadius = 12.0 24 | 25 | let dropShadow = NSShadow() 26 | dropShadow.shadowColor = NSColor(calibratedWhite: 0, alpha: 0.45) 27 | dropShadow.shadowOffset = NSMakeSize(0, -20.0) 28 | dropShadow.shadowBlurRadius = 10.0 29 | 30 | 31 | self.layer?.borderWidth = 0.5 32 | 33 | self.shadow = dropShadow 34 | 35 | self.isHidden = true 36 | 37 | self.setOutline() 38 | 39 | } 40 | 41 | func setOutline() { 42 | self.layer?.borderColor = ColorPair.popoverOutline.value(for: self.effectiveAppearance).cgColor 43 | } 44 | 45 | override func viewDidChangeEffectiveAppearance() { 46 | super.viewDidChangeEffectiveAppearance() 47 | self.setOutline() 48 | } 49 | 50 | func show() { 51 | self.animator().alphaValue = 1 52 | self.animator().isHidden = false 53 | } 54 | 55 | func hide() { 56 | self.animator().alphaValue = 0 57 | self.animator().isHidden = true; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /Boop/Boop/Views/PopoverView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PopoverView.swift 3 | // Boop 4 | // 5 | // Created by Ivan on 1/27/19. 6 | // Copyright © 2019 OKatBest. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class PopoverView: NSView { 12 | 13 | required init?(coder decoder: NSCoder) { 14 | super.init(coder: decoder) 15 | 16 | // UI set up 17 | self.wantsLayer = true 18 | self.layer?.cornerRadius = 12.0 19 | self.layer?.masksToBounds = true 20 | 21 | self.layer?.borderWidth = 1.5 22 | 23 | self.setBackground() 24 | 25 | 26 | } 27 | 28 | func setBackground() { 29 | 30 | self.layer?.backgroundColor = ColorPair.popover.value(for: self.effectiveAppearance).cgColor 31 | self.layer?.borderColor = ColorPair.popoverBorder.value(for: self.effectiveAppearance).cgColor 32 | 33 | } 34 | 35 | override func viewDidChangeEffectiveAppearance() { 36 | super.viewDidChangeEffectiveAppearance() 37 | self.setBackground() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Boop/Boop/Views/ScriptTableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScriptTableView.swift 3 | // Boop 4 | // 5 | // Created by Ivan on 3/24/19. 6 | // Copyright © 2019 OKatBest. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Cocoa 11 | 12 | class ScriptTableView: NSTableView { 13 | override func resignFirstResponder() -> Bool { 14 | self.deselectAll(self) 15 | return true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Boop/Boop/Views/ScriptTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScriptTableViewCell.swift 3 | // Boop 4 | // 5 | // Created by Ivan on 2/13/19. 6 | // Copyright © 2019 OKatBest. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class ScriptTableViewCell: NSTableCellView { 12 | weak var script:Script! 13 | 14 | @IBOutlet weak var titleLabel:NSTextField! 15 | @IBOutlet weak var subtitleLabel:NSTextField! 16 | 17 | } 18 | -------------------------------------------------------------------------------- /Boop/Boop/Views/SearchField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchField.swift 3 | // Boop 4 | // 5 | // Created by Ivan on 1/27/19. 6 | // Copyright © 2019 OKatBest. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class SearchField: NSTextField { 12 | 13 | override func draw(_ dirtyRect: NSRect) { 14 | super.draw(dirtyRect) 15 | 16 | // Drawing code here. 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Boop/Boop/Views/StatusView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StatusView.swift 3 | // Boop 4 | // 5 | // Created by Ivan on 1/27/19. 6 | // Copyright © 2019 OKatBest. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | enum Status { 12 | case normal 13 | case updateAvailable(String) 14 | case help(String) 15 | case info(String) 16 | case error(String) 17 | case success(String) 18 | } 19 | 20 | @IBDesignable 21 | class StatusView: NSView { 22 | 23 | let transitionLength = 0.3 24 | let messageLength = 10.0 25 | 26 | @IBOutlet weak var textLabel: NSTextField! 27 | @IBOutlet weak var updateLabel: UpdateTextField! 28 | 29 | var queue = [Status]() 30 | var running = false 31 | var current = Status.normal 32 | 33 | 34 | override func awakeFromNib() { 35 | super.awakeFromNib() 36 | 37 | self.wantsLayer = true 38 | 39 | self.layer?.backgroundColor = ColorPair.normal.value(for: self.effectiveAppearance).cgColor 40 | self.layer?.cornerRadius = 5 41 | 42 | } 43 | 44 | func setStatus(_ newStatus: Status) { 45 | 46 | switch newStatus { 47 | case .normal, .help(_), .updateAvailable: 48 | // Skip the queue for those statuses 49 | running = false 50 | queue.removeAll() 51 | self.current = newStatus 52 | self.updateText(newStatus) 53 | self.updateColor(newStatus) 54 | default: 55 | queue.append(newStatus) 56 | queueUpdated() 57 | } 58 | 59 | 60 | } 61 | 62 | func queueUpdated() { 63 | guard !running else { 64 | return 65 | } 66 | 67 | guard !queue.isEmpty else { 68 | running = false 69 | self.updateText(.normal) 70 | self.updateColor(.normal) 71 | self.current = .normal 72 | return 73 | } 74 | 75 | running = true 76 | 77 | let next = queue.removeFirst() 78 | 79 | self.updateText(next) 80 | self.updateColor(next) 81 | self.current = next 82 | 83 | DispatchQueue.main.asyncAfter(deadline: .now() + messageLength, execute: { 84 | self.running = false 85 | self.queueUpdated() 86 | }) 87 | } 88 | 89 | fileprivate func updateText(_ newStatus: Status) { 90 | 91 | var text = "" 92 | 93 | self.updateLabel.isHidden = true 94 | 95 | switch newStatus { 96 | case .help(let value): 97 | text = value 98 | case .info(let value): 99 | text = value 100 | case .error(let value): 101 | text = value 102 | case .success(let value): 103 | text = value 104 | case .normal: 105 | text = "Press ⌘+B to get started" 106 | case .updateAvailable(let link): 107 | text = "New version available! " 108 | 109 | self.updateLabel.isHidden = false 110 | self.updateLabel.link = link 111 | 112 | } 113 | 114 | self.textLabel.stringValue = text 115 | } 116 | 117 | fileprivate func fadeText(to alphaValue: CGFloat, completionHandler: (() -> Void)? = nil) { 118 | NSAnimationContext.runAnimationGroup({ (context) in 119 | context.duration = self.transitionLength / 2.5 120 | self.textLabel.animator().alphaValue = alphaValue 121 | }) { 122 | completionHandler?() 123 | } 124 | } 125 | 126 | fileprivate func updateColor(_ newStatus: Status) { 127 | 128 | var color = ColorPair.normal 129 | 130 | switch newStatus { 131 | case .normal, .help(_): 132 | break 133 | case .success(_): 134 | color = ColorPair.green.swap 135 | case .info(_): 136 | color = ColorPair.blue.swap 137 | case .error(_): 138 | color = ColorPair.red.swap 139 | case .updateAvailable: 140 | color = ColorPair.purple 141 | } 142 | 143 | self.layer?.backgroundColor = color.value(for: self.effectiveAppearance).cgColor 144 | } 145 | 146 | override func viewDidChangeEffectiveAppearance() { 147 | self.updateColor(self.current) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /Boop/Boop/Views/UpdateTextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UpdateTextField.swift 3 | // Boop 4 | // 5 | // Created by Ivan on 6/2/20. 6 | // Copyright © 2020 OKatBest. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AppKit 11 | 12 | class UpdateTextField: NSTextField { 13 | 14 | var link: String? 15 | 16 | override func awakeFromNib() { 17 | 18 | self.attributedStringValue = NSAttributedString(string: "Learn More", attributes: [ 19 | NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue 20 | ]) 21 | } 22 | 23 | override func resetCursorRects() { 24 | super.resetCursorRects() 25 | 26 | self.addCursorRect(self.visibleRect, cursor: .pointingHand) 27 | } 28 | 29 | override func mouseDown(with event: NSEvent) { 30 | guard let link = link, let url = URL(string: link) else { 31 | return 32 | } 33 | 34 | NSWorkspace.shared.open(url) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/ASCIIToHex.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"ASCII To Hex", 5 | "description":"Converts ASCII characters to hexadecimal codes.", 6 | "author":"aWZHY0yQH81uOYvH", 7 | "icon":"metamorphose", 8 | "tags":"ascii,hex,convert" 9 | } 10 | **/ 11 | 12 | function main(state) { 13 | buf = ""; 14 | for(i = 0; i < state.fullText.length; i ++) { 15 | code = state.fullText.charCodeAt(i).toString(16); 16 | if(code.length < 2) buf += "0"; 17 | buf += code; 18 | } 19 | state.fullText = buf.toUpperCase(); 20 | } 21 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/AddSlashes.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Add Slashes", 5 | "description":"Escapes your text.", 6 | "author":"Ivan", 7 | "icon":"quote", 8 | "tags":"add,slashes,escape" 9 | } 10 | **/ 11 | 12 | function main(input) { 13 | // discuss at: http://locutus.io/php/addslashes/ 14 | // original by: Kevin van Zonneveld (http://kvz.io) 15 | // improved by: Ates Goral (http://magnetiq.com) 16 | // improved by: marrtins 17 | // improved by: Nate 18 | // improved by: Onno Marsman (https://twitter.com/onnomarsman) 19 | // improved by: Brett Zamir (http://brett-zamir.me) 20 | // improved by: Oskar Larsson Högfeldt (http://oskar-lh.name/) 21 | // input by: Denny Wardhana 22 | // example 1: addslashes("kevin's birthday") 23 | // returns 1: "kevin\\'s birthday" 24 | 25 | input.text = (input.text + '') 26 | .replace(/[\\"']/g, '\\$&') 27 | .replace(/\u0000/g, '\\0') 28 | } 29 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/AndroidIOSStrings.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Android Strings to iOS Localizables", 5 | "description":"Converts Android Strings to iOS localizables.", 6 | "author":"Manuel Kunz (https://github.com/KunzManuel)", 7 | "icon":"translation", 8 | "tags":"string,android,ios" 9 | } 10 | **/ 11 | 12 | function main(input) { 13 | let lines = input.fullText.split('\n') 14 | var result = [] 15 | lines.forEach(element => { 16 | var temp = element 17 | temp = temp.replace("", "\";") 19 | temp = temp.replace(">", " = \"") 20 | result.push(temp) 21 | }) 22 | 23 | input.fullText = result.join('\n') 24 | } 25 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/Base64Decode.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Base64 Decode", 5 | "description":"Decodes your text from Base64", 6 | "author":"See Source", 7 | "icon":"metamorphose", 8 | "tags":"base64,btoa,decode" 9 | } 10 | **/ 11 | 12 | const { decode } = require('@boop/base64') 13 | 14 | function main(input) { 15 | 16 | input.text = decode(input.text) 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/Base64Encode.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Base64 Encode", 5 | "description":"Encodes your text to Base64", 6 | "author":"See Source", 7 | "icon":"metamorphose", 8 | "tags":"base64,atob,encode" 9 | } 10 | **/ 11 | 12 | const { encode } = require('@boop/base64') 13 | 14 | function main(input) { 15 | 16 | input.text = encode(input.text) 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/BinaryToDecimal.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api": 1, 4 | "name": "Binary to Decimal", 5 | "description": "Converts binary values to decimal.", 6 | "author": "Maurice", 7 | "icon": "metamorphose", 8 | "tags": "decimal,binary,dec,bin" 9 | } 10 | **/ 11 | 12 | function main(state) { 13 | var text = state.text; 14 | var lines = text.split(/\n/); 15 | var result = ""; 16 | 17 | for (const index in lines) { 18 | var text = lines[index].trim(); 19 | var decimal = parseInt(text, 2); 20 | 21 | if (isNaN(decimal)) { 22 | result += text; 23 | } else { 24 | result += decimal; 25 | } 26 | 27 | result += "\n"; 28 | } 29 | 30 | state.text = result.trim(); 31 | } 32 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/CSVtoJSON.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":2, 4 | "name":"CSV to JSON", 5 | "description":"Converts comma-separated tables to JSON.", 6 | "author":"Ivan", 7 | "icon":"table", 8 | "tags":"table,convert", 9 | "bias": -0.2 10 | } 11 | **/ 12 | const Papa = require('@boop/papaparse.js'); 13 | 14 | function main(state) { 15 | try { 16 | const { data } = Papa.parse(state.text, { header:true }); 17 | state.text = JSON.stringify(data, null, 2); 18 | } 19 | catch(error) { 20 | state.postError("Invalid CSV") 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/CamelCase.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Camel Case", 5 | "description":"convertsYourTextToCamelCase", 6 | "author":"Ivan", 7 | "icon":"camel", 8 | "tags":"camel,case,function,lodash" 9 | } 10 | **/ 11 | 12 | const { camelCase } = require('@boop/lodash.boop') 13 | 14 | function main(input) { 15 | 16 | input.text = camelCase(input.text) 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/Collapse.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Collapse lines", 5 | "description":"Removes all linebreaks from your text", 6 | "author":"Dennis", 7 | "icon":"collapse", 8 | "tags":"strip,remove,collapse,join" 9 | } 10 | **/ 11 | 12 | function main(input) { 13 | let split = input.text.split(/\r\n|\r|\n/) 14 | input.postInfo(`${split.length} lines collapsed`) 15 | input.fullText = split.join() 16 | } 17 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/CountCharacters.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Count Characters", 5 | "description":"Get the length of your text", 6 | "author":"Ivan", 7 | "icon":"counter", 8 | "tags":"count,length,size,character" 9 | } 10 | **/ 11 | 12 | 13 | const { size } = require('@boop/lodash.boop') 14 | 15 | function main(input) { 16 | 17 | input.postInfo(`${size(input.text)} characters`) 18 | 19 | } 20 | 21 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/CountLines.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Count Lines", 5 | "description":"Get the line count of your text", 6 | "author":"andipaetzold", 7 | "icon":"counter", 8 | "tags":"count,length,size,line" 9 | } 10 | **/ 11 | 12 | function main(input) { 13 | 14 | input.postInfo(`${input.text.split('\n').length} lines`) 15 | 16 | } 17 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/CountWords.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Count Words", 5 | "description":"Get the word count of your text", 6 | "author":"Daniel Stone", 7 | "icon":"counter", 8 | "tags":"count,length,size,words" 9 | } 10 | **/ 11 | function main(input) { 12 | let words = input.text.trim().match(/\S+/g) 13 | input.postInfo(`${words && words.length || 0} words`) 14 | } -------------------------------------------------------------------------------- /Boop/Boop/scripts/DateToTimestamp.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Date to Timestamp", 5 | "description":"Converts dates to Unix timestamp.", 6 | "author":"Noah Halford", 7 | "icon":"watch", 8 | "tags":"date,time,calendar,unix,timestamp" 9 | } 10 | **/ 11 | 12 | function main(input) { 13 | 14 | let parsedDate = Date.parse(input.text) 15 | 16 | if (isNaN(parsedDate)) { 17 | input.postError("Invalid Date") 18 | } else { 19 | input.text = parsedDate / 1000 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/DateToUTC.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Date to UTC", 5 | "description":"Converts dates and timestamps to UTC dates", 6 | "author":"Ivan", 7 | "icon":"watch", 8 | "tags":"date,time,calendar,unix,timestamp" 9 | } 10 | **/ 11 | 12 | function main(input) { 13 | 14 | let string = input.text 15 | 16 | let parsedDate = Date.parse(string) 17 | 18 | if (isNaN(parsedDate)) { 19 | parsedDate = new Date(parseInt(string * 1000)) 20 | } else { 21 | parsedDate = new Date(parsedDate) 22 | } 23 | 24 | let out = parsedDate.toUTCString() 25 | 26 | if (out === "Invalid Date") { 27 | input.postError(out) 28 | return 29 | } 30 | 31 | input.text = out 32 | } 33 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/Deburr.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Deburr", 5 | "description":"Converts your text to basic latin characters.", 6 | "author":"Ivan", 7 | "icon":"colosseum", 8 | "tags":"burr,special,characters,function,lodash" 9 | } 10 | **/ 11 | 12 | const { deburr } = require('@boop/lodash.boop') 13 | 14 | function main(input) { 15 | 16 | input.text = deburr(input.text) 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/DecimalToBinary.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api": 1, 4 | "name": "Decimal to Binary", 5 | "description": "Converts decimal values to binary.", 6 | "author": "Maurice", 7 | "icon": "metamorphose", 8 | "tags": "decimal,binary,dec,bin" 9 | } 10 | **/ 11 | 12 | function main(state) { 13 | var text = state.text; 14 | var lines = text.split(/\n/); 15 | var result = ""; 16 | 17 | for (const index in lines) { 18 | var text = lines[index].trim(); 19 | var bin = parseInt(text).toString(2).toUpperCase(); 20 | 21 | if (isNaN(bin)) { 22 | result += text; 23 | } else { 24 | result += bin; 25 | } 26 | 27 | result += "\n"; 28 | } 29 | 30 | state.text = result.trim(); 31 | } 32 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/DecimalToHex.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api": 1, 4 | "name": "Decimal to Hex", 5 | "description": "Converts decimal values to hexadecimal.", 6 | "author": "Maurice", 7 | "icon": "metamorphose", 8 | "tags": "decimal,hexadecimal,dec,hex" 9 | } 10 | **/ 11 | 12 | function main(state) { 13 | var text = state.text; 14 | var lines = text.split(/\n/); 15 | var result = ""; 16 | 17 | for (const index in lines) { 18 | var text = lines[index].trim(); 19 | 20 | if (isNaN(text)) { 21 | result += text; 22 | } else { 23 | result += parseInt(text).toString(16).toUpperCase(); 24 | } 25 | 26 | result += "\n"; 27 | } 28 | 29 | state.text = result.trim(); 30 | } 31 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/Downcase.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Downcase", 5 | "description":"Converts your text to lowercase.", 6 | "author":"Dan2552", 7 | "icon":"type", 8 | "tags":"downcase,lowercase" 9 | } 10 | **/ 11 | 12 | function main(input) { 13 | 14 | input.text = input.text.toLowerCase(); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/EvalJavascript.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Eval Javascript", 5 | "description":"Runs your text as Javascript Code.", 6 | "author":"Sebastiaan Besselsen", 7 | "icon":"command", 8 | "tags":"js,script,run" 9 | } 10 | **/ 11 | 12 | function main(input) { 13 | const script = input.text.replace(/\n\n\/\/ Result:[\s\S]*$/, ''); 14 | 15 | let output = ''; 16 | try { 17 | output = eval(script); 18 | if (typeof output !== 'string') { 19 | output = JSON.stringify(output, null, 2); 20 | } 21 | } catch (e) { 22 | input.postError(e.toString()); 23 | } 24 | 25 | input.text = script + "\n\n// Result:\n\n" + output; 26 | } 27 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/FishHexPathConverter.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api": 1, 4 | "name": "Fish PATH Hex Converter", 5 | "description": "Escapes terminal characters.", 6 | "author": "Paul Seelman", 7 | "icon": "broom", 8 | "tags": "fish_user_paths, fish, hex, ascii, path, var" 9 | } 10 | **/ 11 | function convert(string) { 12 | var chars = string.split(""); 13 | var dict = { 14 | " ": ":", 15 | "%": "25", 16 | "&": "26", 17 | "+": "2b", 18 | "-": "2d", 19 | ".": "2e", 20 | "*": "2a", 21 | ":": "3a", 22 | "@": "40", 23 | ";": "3b" 24 | }; 25 | 26 | for (var i = chars.length - 1; i >= 0; i--) { 27 | var char = chars[i]; 28 | var hex = dict[char]; 29 | 30 | if (hex !== undefined) { 31 | var slash_x = '\\x'; 32 | chars[i] = slash_x.concat(hex); 33 | } 34 | } 35 | 36 | return chars.join(""); 37 | } 38 | 39 | function main(input) { 40 | input.text = convert(input.text); 41 | } 42 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/FormatCSS.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Format CSS", 5 | "description":"Cleans and format CSS stylesheets.", 6 | "author":"Ivan", 7 | "icon":"broom", 8 | "tags":"css,prettify,clean,indent", 9 | "bias": -0.1 10 | } 11 | **/ 12 | 13 | const { css } = require('@boop/vkBeautify') 14 | 15 | 16 | function main(state) { 17 | state.text = css(state.text) 18 | } 19 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/FormatJSON.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Format JSON", 5 | "description":"Cleans and format JSON documents.", 6 | "author":"Ivan", 7 | "icon":"broom", 8 | "tags":"json,prettify,clean,indent" 9 | } 10 | **/ 11 | 12 | function main(state) { 13 | try { 14 | // I feel like this should have a real parser/formatter 15 | // but hey, it works so who am I to judge? 16 | state.text = JSON.stringify(JSON.parse(state.text), null, 2); 17 | } 18 | catch(error) { 19 | state.postError("Invalid JSON") 20 | } 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/FormatSQL.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Format SQL", 5 | "description":"Cleans and format SQL queries.", 6 | "author":"Ivan", 7 | "icon":"broom", 8 | "tags":"mysql,sql,prettify,clean,indent", 9 | "bias": -0.1 10 | } 11 | **/ 12 | 13 | const { sql } = require('@boop/vkBeautify') 14 | 15 | 16 | function main(state) { 17 | state.text = sql(state.text) 18 | } 19 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/FormatXML.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Format XML", 5 | "description":"Cleans and format XML/HTML documents.", 6 | "author":"Ivan", 7 | "icon":"broom", 8 | "tags":"html,prettify,clean,indent", 9 | "bias": -0.1 10 | } 11 | **/ 12 | 13 | const { xml } = require('@boop/vkBeautify') 14 | 15 | 16 | function main(state) { 17 | state.text = xml(state.text) 18 | } 19 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/HTMLDecode.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"HTML Decode", 5 | "description":"Decodes HTML entities in your text", 6 | "author":"See Source", 7 | "icon":"HTML", 8 | "tags":"html,decode,web" 9 | } 10 | **/ 11 | 12 | const { decode } = require('@boop/he') 13 | 14 | function main(input) { 15 | input.text = decode(input.text) 16 | } 17 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/HTMLEncode.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"HTML Encode", 5 | "description":"Encodes HTML entities in your text", 6 | "author":"See Source", 7 | "icon":"HTML", 8 | "tags":"html,encode,web" 9 | } 10 | **/ 11 | 12 | const { encode } = require('@boop/he') 13 | 14 | function main(input) { 15 | input.text = encode(input.text) 16 | } 17 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/HTMLEncodeAll.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"HTML Encode all characters", 5 | "description":"HTML Encodes every character in your text", 6 | "author":"Ivan", 7 | "icon":"HTML", 8 | "tags":"html,encode,web,email", 9 | "bias":-0.1 10 | } 11 | **/ 12 | 13 | function main(input) { 14 | let str = input.text; 15 | var out = ""; 16 | for (var i = 0; i < str.length; i++) { 17 | out += `&#${str.charCodeAt(i)};`; 18 | } 19 | input.text = out; 20 | } 21 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/HexToASCII.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Hex To ASCII", 5 | "description":"Converts hexadecimal values into ASCII characters", 6 | "author":"aWZHY0yQH81uOYvH", 7 | "icon":"metamorphose", 8 | "tags":"hex,ascii,convert" 9 | } 10 | **/ 11 | 12 | function main(state) { 13 | input = state.fullText.toUpperCase(); 14 | buf = ""; 15 | hexBuf = ""; 16 | for(i = 0; i < input.length; i ++) { 17 | c = input.charAt(i); 18 | if("0123456789ABCDEF".includes(c)) { 19 | hexBuf += c; 20 | if(hexBuf.length >= 2) { 21 | buf += String.fromCharCode(parseInt(hexBuf, 16)); 22 | hexBuf = ""; 23 | } 24 | } else if(c != ' ' && c != '\t' && c != '\n' && c != '\r') { 25 | state.postError("Text is not hex") 26 | throw "Not hex"; 27 | } 28 | } 29 | state.fullText = buf; 30 | } 31 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/HexToDecimal.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api": 1, 4 | "name": "Hex to Dec", 5 | "description": "Converts hexadecimal to decimal.", 6 | "author": "Maurice", 7 | "icon": "metamorphose", 8 | "tags": "decimal,hexadecimal,dec,hex" 9 | } 10 | **/ 11 | 12 | function main(state) { 13 | var text = state.text; 14 | var lines = text.split(/\n/); 15 | var result = ""; 16 | 17 | for (const index in lines) { 18 | var text = lines[index].trim(); 19 | var decimal = parseInt(text, 16); 20 | 21 | if (isNaN(decimal)) { 22 | result += text; 23 | } else { 24 | result += decimal; 25 | } 26 | 27 | result += "\n"; 28 | } 29 | 30 | state.text = result.trim(); 31 | } 32 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/IOSAndroidStrings.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"iOS Localizables to Android Strings", 5 | "description":"Converts iOS Localizables to Android Strings", 6 | "author":"Manuel Kunz (https://github.com/KunzManuel)", 7 | "icon":"translation", 8 | "tags":"string,android,ios" 9 | } 10 | **/ 11 | 12 | function main(input) { 13 | let lines = input.fullText.split('\n') 14 | var result = [] 15 | lines.forEach(element => { 16 | if(element !== "") { 17 | var regex = /"(.*?)"/g 18 | var matches = []; 19 | var match = regex.exec(element); 20 | while (match != null) { 21 | matches.push(match[1]); 22 | match = regex.exec(element); 23 | } 24 | result.push("" + matches[1] + "") 25 | } 26 | }) 27 | input.fullText = result.join('\n') 28 | } 29 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/JSONtoCSV.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"JSON to CSV", 5 | "description":"Converts JSON to comma-separated tables.", 6 | "author":"Ivan", 7 | "icon":"table", 8 | "tags":"table,convert", 9 | "bias": -0.2 10 | } 11 | **/ 12 | 13 | // Inspired by https://stackoverflow.com/a/31536517/2053038 14 | // Note: it would be good to escape commas, and maybe not just get keys from the first object. 15 | 16 | function main(state) { 17 | try { 18 | const delimiter = ','; 19 | const data = JSON.parse(state.text); 20 | const replacer = (_, value) => value === null ? '' : value 21 | const header = Object.keys(data[0]) 22 | let csv = data.map(row => header.map(fieldName => JSON.stringify(row[fieldName], replacer)).join(delimiter)) 23 | csv.unshift(header.join(delimiter)) 24 | state.text = csv.join('\r\n') 25 | } catch (error) { 26 | state.postError("Invalid JSON.") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/JSONtoYAML.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"JSON to YAML", 5 | "description":"Converts JSON to YAML.", 6 | "author":"Ivan", 7 | "icon":"metamorphose", 8 | "tags":"markup,convert" 9 | } 10 | **/ 11 | 12 | const yaml = require('@boop/js-yaml') 13 | 14 | function main(input) { 15 | try { 16 | input.text = yaml.safeDump(JSON.parse(input.text)) 17 | } 18 | catch(error) { 19 | input.postError("Invalid JSON") 20 | } 21 | } -------------------------------------------------------------------------------- /Boop/Boop/scripts/JWTDecode.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"JWT Decode", 5 | "description":"Converts JWTs to JSON", 6 | "author":"Nils Sonemann", 7 | "icon":"identification", 8 | "tags":"decode,jwt,token" 9 | } 10 | **/ 11 | 12 | 13 | const { decode } = require('@boop/base64'); 14 | 15 | function main(input) { 16 | var t = input.text; 17 | var jwtParts = t.split("."); 18 | if (jwtParts.length != 3) { 19 | input.postError("Invalid Token"); 20 | return; 21 | } 22 | 23 | var header = decode(jwtParts[0]); 24 | var payload = decode(jwtParts[1]); 25 | var signature = jwtParts[2]; 26 | 27 | try { 28 | var fullJson = { 29 | "header": JSON.parse(header), 30 | "payload": JSON.parse(payload), 31 | "signature": signature 32 | }; 33 | 34 | // Prettyprint the JSOM 35 | input.text = JSON.stringify(fullJson, null, 2); 36 | } catch(err) { 37 | input.postError("Error while parsing JSON"); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/JsonToQuery.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"JSON to Query String", 5 | "description":"Converts JSON to URL query string.", 6 | "author":"Ota Mares ", 7 | "icon":"website", 8 | "tags":"url,query,params,json,convert,encode" 9 | } 10 | **/ 11 | 12 | /** 13 | * Credit goes to https://stackoverflow.com/a/1714899 14 | */ 15 | function convertToQuery(obj, prefix) { 16 | let queryParts = [] 17 | 18 | for (param in obj) { 19 | if (obj.hasOwnProperty(param)) { 20 | let key = prefix ? prefix + "[]" : param; 21 | let value = obj[param]; 22 | 23 | queryParts.push( 24 | (value !== null && typeof value === "object") ? 25 | convertToQuery(value, key) : 26 | key + "=" + value 27 | ); 28 | } 29 | } 30 | 31 | return queryParts.join("&"); 32 | } 33 | 34 | function main(input) 35 | { 36 | try { 37 | input.text = convertToQuery(JSON.parse(input.text)); 38 | } catch (error) { 39 | input.postError("Unable to convert JSON to URL params") 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/KebabCase.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Kebab Case", 5 | "description":"converts-your-text-to-kebab-case.", 6 | "author":"Ivan", 7 | "icon":"kebab", 8 | "tags":"kebab,case,function,lodash" 9 | } 10 | **/ 11 | 12 | const { kebabCase } = require('@boop/lodash.boop') 13 | 14 | function main(input) { 15 | 16 | input.text = kebabCase(input.text) 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/LoremIpsum.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Lorem Ipsum", 5 | "description":"Generates Lorem Ipsum placeholder text.", 6 | "author":"luisfontes19", 7 | "icon":"type", 8 | "tags":"generate,lorem,ipsum,text", 9 | "bias": -0.1 10 | } 11 | **/ 12 | 13 | 14 | function main(state) { 15 | const words = ["ad", "adipisicing", "aliqua", "aliquip", "amet", "anim", "aute", "cillum", "commodo", "consectetur", "consequat", "culpa", "cupidatat", "deserunt", "do", "dolor", "dolore", "duis", "ea", "eiusmod", "elit", "enim", "esse", "est", "et", "eu", "ex", "excepteur", "exercitation", "fugiat", "id", "in", "incididunt", "ipsum", "irure", "labore", "laboris", "laborum", "Lorem", "magna", "minim", "mollit", "nisi", "non", "nostrud", "nulla", "occaecat", "officia", "pariatur", "proident", "qui", "quis", "reprehenderit", "sint", "sit", "sunt", "tempor", "ullamco", "ut", "velit", "veniam", "voluptate"]; 16 | let sentence = ""; 17 | 18 | for (let i = 0; i < 100; i++) { 19 | const pos = Math.floor(Math.random() * (words.length - 1)); 20 | sentence += words[pos] + " "; 21 | } 22 | 23 | sentence = sentence.charAt(0).toUpperCase() + sentence.slice(1).trim() + "."; 24 | 25 | state.text = sentence; 26 | } 27 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/MD5.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"MD5 Checksum", 5 | "description":"Computes the checksum of your text (Hex encoded).", 6 | "author":"Ivan", 7 | "icon":"fingerprint", 8 | "tags":"strip,slashes,remove" 9 | } 10 | **/ 11 | 12 | const Hashes = require('@boop/hashes') 13 | 14 | function main(state) { 15 | var MD5 = new Hashes.MD5; 16 | state.text = MD5.hex(state.text) 17 | } 18 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/MarkdownQuote.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Markdown Quote", 5 | "description":"Adds > to the start of every line of your text.", 6 | "author":"Dan2552", 7 | "icon":"term", 8 | "tags":"quote,markdown" 9 | } 10 | **/ 11 | 12 | function main(input) { 13 | input.text = input.text.split("\n").map(line => "> " + line).join("\n"); 14 | } 15 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/MinifyCSS.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Minify CSS", 5 | "description":"Cleans and minifies CSS stylesheets.", 6 | "author":"Ivan", 7 | "icon":"broom", 8 | "tags":"css,minify,clean,indent", 9 | "bias": -0.1 10 | } 11 | **/ 12 | 13 | const { cssmin } = require('@boop/vkBeautify') 14 | 15 | 16 | function main(state) { 17 | state.text = cssmin(state.text) 18 | } 19 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/MinifyJSON.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Minify JSON", 5 | "description":"Cleans and minifies JSON documents.", 6 | "author":"riesentoaster", 7 | "icon":"broom", 8 | "tags":"html,minify,clean,indent", 9 | "bias": -0.1 10 | } 11 | **/ 12 | 13 | function main(input) { 14 | input.text = JSON.stringify(JSON.parse(input.text)); 15 | } 16 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/MinifySQL.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Minify SQL", 5 | "description":"Cleans and minifies SQL queries.", 6 | "author":"Ivan", 7 | "icon":"broom", 8 | "tags":"mysql,sql,minify,clean,indent", 9 | "bias": -0.1 10 | } 11 | **/ 12 | 13 | const { sqlmin } = require('@boop/vkBeautify') 14 | 15 | 16 | function main(state) { 17 | state.text = sqlmin(state.text) 18 | } 19 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/MinifyXML.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Minify XML", 5 | "description":"Cleans and minifies XML/HTML documents.", 6 | "author":"Ivan", 7 | "icon":"broom", 8 | "tags":"html,minify,clean,indent", 9 | "bias": -0.1 10 | } 11 | **/ 12 | 13 | const { xmlmin } = require('@boop/vkBeautify') 14 | 15 | 16 | function main(state) { 17 | state.text = xmlmin(state.text) 18 | } 19 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/NatSort.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Natural Sort Lines", 5 | "description":"Sort lines with smart handling of numbers.", 6 | "author":"Sebastiaan Besselsen", 7 | "icon":"sort-numbers", 8 | "tags":"sort,natural,natsort" 9 | } 10 | **/ 11 | 12 | function main(input) { 13 | let sorted = input.text.replace(/\n$/, '').split('\n') 14 | .sort((a, b) => a.localeCompare(b, undefined, {numeric: true, sensitivity: 'base'})) 15 | .join('\n'); 16 | 17 | if (sorted === input.text) { 18 | sorted = sorted.split('\n').reverse().join('\n'); 19 | } 20 | input.text = sorted; 21 | } 22 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/PhpUnserialize.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"PHP Unserialize", 5 | "description":"Convert PHP serialized data to JSON", 6 | "author":"Rob Bogie", 7 | "icon":"elephant", 8 | "tags":"php,serialize,unserialize,json" 9 | } 10 | **/ 11 | 12 | function main(state) { 13 | try { 14 | const input = state.text 15 | const unserialized = unserialize(input) 16 | const data = unserialized[0] 17 | 18 | if(unserialized[1] != input.length) { 19 | throw new Error("Invalid serialized string") 20 | } 21 | 22 | if(data === null || data === undefined) { 23 | state.text = null 24 | } else if(typeof data === 'object') { 25 | state.text = JSON.stringify(data, null, 2) 26 | } else { 27 | state.text = data.toString() 28 | } 29 | } catch (e) { 30 | state.postError(e.message) 31 | } 32 | } 33 | 34 | function decodeInt(text, startPos) { 35 | const lastChar = text.indexOf(';', startPos) 36 | if(lastChar <= 0) { 37 | throw new Error("decodeInt: unexpected end of string") 38 | } 39 | return [Number.parseInt(text.slice(startPos, lastChar)), lastChar + 1] 40 | } 41 | 42 | function decodeBool(text, startPos) { 43 | const lastChar = text.indexOf(';', startPos) 44 | if(lastChar != startPos + 1) { 45 | throw new Error("decodeBool: unexpected data length") 46 | } 47 | switch (text.charAt(startPos)) { 48 | case '0': 49 | return [false, startPos + 2] 50 | case '1': 51 | return [true, startPos + 2] 52 | default: 53 | throw new Error("decodeBool: found unexpected data") 54 | } 55 | } 56 | 57 | function decodeFloat(text, startPos) { 58 | const lastChar = text.indexOf(';', startPos) 59 | if(lastChar <= 0) { 60 | throw new Error("decodeFloat: unexpected end of string") 61 | } 62 | return [Number.parseFloat(text.slice(startPos, lastChar)), lastChar + 1] 63 | } 64 | 65 | function decodeString(text, startPos) { 66 | const lengthEnd = text.indexOf(':', startPos) 67 | if(lengthEnd <= 0) { 68 | throw new Error("decodeString: no string length found") 69 | } 70 | const byteLength = Number.parseInt(text.slice(startPos, lengthEnd)) 71 | 72 | startPos = lengthEnd + 2 73 | let currentStrLength = 0 74 | let numBytes = 0; 75 | while((currentStrLength + startPos) < text.length && numBytes < byteLength) { 76 | const nextPos = text.indexOf('";', startPos+currentStrLength+1)-startPos 77 | if(nextPos > currentStrLength) { 78 | currentStrLength = nextPos 79 | } else { 80 | // No end will be found anymore, exit and do our safety checks as if we reached the end 81 | break 82 | } 83 | 84 | const subStr = text.slice(startPos, startPos + currentStrLength) 85 | try { 86 | const encodedStr = encodeURI(subStr) 87 | numBytes = encodedStr.split(/%..|./).length - 1 88 | } catch(e) { 89 | // encodeURI will fail when an invalid UTF16 character is found, which happens with 4 byte characters (e.g. emoji) 90 | // We will simply try again on the next position 91 | } 92 | } 93 | 94 | if(numBytes != byteLength) { 95 | throw new Error("Could not decode string: field length mismatch") 96 | } 97 | 98 | return [text.slice(startPos, startPos + currentStrLength), startPos + currentStrLength + 2] 99 | } 100 | 101 | function decodeArray(text, startPos) { 102 | const lengthEnd = text.indexOf(':', startPos) 103 | if(lengthEnd <= 0) { 104 | throw new Error("decodeArray: no arraylength found") 105 | } 106 | const numItems = Number.parseInt(text.slice(startPos, lengthEnd)) 107 | let data = {} 108 | startPos = lengthEnd + 2 109 | let continuous = true 110 | for(let i = 0; i < numItems; i++) { 111 | const keyData = unserialize(text, startPos) 112 | const valueData = unserialize(text, keyData[1]) 113 | startPos = valueData[1] 114 | 115 | if(keyData[0] !== i) { 116 | continuous = false 117 | } 118 | 119 | data[keyData[0]] = valueData[0] 120 | } 121 | 122 | if(continuous) { 123 | // Convert non key-value maps to array 124 | const array = new Array(numItems) 125 | for(let i = 0; i < numItems; i++) { 126 | array[i] = data[i] 127 | } 128 | data = array 129 | } 130 | return [data, startPos+1] 131 | } 132 | 133 | function decodeObject(text, startPos) { 134 | const classNameLengthEnd = text.indexOf(':', startPos) 135 | if(classNameLengthEnd <= 0) { 136 | throw new Error("decodeObject: no arraylength found") 137 | } 138 | const classNameLength = Number.parseInt(text.slice(startPos, classNameLengthEnd)) 139 | startPos = classNameLengthEnd + 2 140 | if(classNameLength !== 8 || text.slice(startPos, startPos + 8) !== 'stdClass') { 141 | throw new Error("decodeObject: object type not supported") 142 | } 143 | 144 | startPos += 10 145 | 146 | return decodeArray(text, startPos) 147 | } 148 | 149 | function unserialize(text, startPos = 0) { 150 | const type = text[startPos] 151 | switch(type) { 152 | case 'i': 153 | return decodeInt(text, startPos + 2) 154 | case 'b': 155 | return decodeBool(text, startPos + 2) 156 | case 'N': 157 | return [null, startPos + 2] 158 | case 'd': 159 | return decodeFloat(text, startPos + 2) 160 | case 's': 161 | return decodeString(text, startPos + 2) 162 | case 'a': 163 | return decodeArray(text, startPos + 2) 164 | case 'O': 165 | return decodeObject(text, startPos + 2) 166 | default: 167 | throw new Error("unknown type found: " + type + " at "+startPos) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/QueryToJson.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Query String to JSON", 5 | "description":"Converts URL query string to JSON.", 6 | "author":"Ota Mares ", 7 | "icon":"website", 8 | "tags":"url,query,params,json,convert,decode" 9 | } 10 | **/ 11 | 12 | function convertToJson(urlParams) { 13 | 14 | return urlParams 15 | .replace(/\[\d?\]=/gi, '=') 16 | .split('&') 17 | .reduce((result, param) => { 18 | var [key, value] = param.split('='); 19 | value = decodeURIComponent(value || ''); 20 | 21 | if (!result.hasOwnProperty(key)) { 22 | result[key] = value; 23 | 24 | return result; 25 | } 26 | 27 | result[key] = [...[].concat(result[key]), value] 28 | 29 | return result 30 | }, {}); 31 | } 32 | 33 | function main(input) 34 | { 35 | try { 36 | input.text = JSON.stringify(convertToJson(input.text)); 37 | } catch (error) { 38 | input.postError("Unable to parse given string") 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/RemoveDuplicates.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Remove Duplicate Lines", 5 | "description":"Ensures each line of your text is unique.", 6 | "author":"andipaetzold", 7 | "icon":"filtration", 8 | "tags":"unique,duplicate" 9 | } 10 | **/ 11 | 12 | function main(input) { 13 | let lines = input.text.split('\n') 14 | let out = unique(lines) 15 | 16 | input.text = out.join('\n') 17 | 18 | input.postInfo(`${lines.length - out.length} lines removed`) 19 | 20 | } 21 | 22 | function unique(array) { 23 | return [...new Set(array)] 24 | } 25 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/RemoveSlashes.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Remove Slashes", 5 | "description":"Unescapes your text.", 6 | "author":"Ivan", 7 | "icon":"quote", 8 | "tags":"strip,slashes,remove,unescape" 9 | } 10 | **/ 11 | 12 | function main(input){ 13 | 14 | // discuss at: http://locutus.io/php/stripslashes/ 15 | // original by: Kevin van Zonneveld (http://kvz.io) 16 | // improved by: Ates Goral (http://magnetiq.com) 17 | // improved by: marrtins 18 | // improved by: rezna 19 | // fixed by: Mick@el 20 | // bugfixed by: Onno Marsman (https://twitter.com/onnomarsman) 21 | // bugfixed by: Brett Zamir (http://brett-zamir.me) 22 | // input by: Rick Waldron 23 | // input by: Brant Messenger (http://www.brantmessenger.com/) 24 | // reimplemented by: Brett Zamir (http://brett-zamir.me) 25 | // example 1: stripslashes('Kevin\'s code') 26 | // returns 1: "Kevin's code" 27 | // example 2: stripslashes('Kevin\\\'s code') 28 | // returns 2: "Kevin\'s code" 29 | 30 | input.text = (input.text + '') 31 | .replace(/\\(.?)/g, function (s, n1) { 32 | switch (n1) { 33 | case '\\': 34 | return '\\' 35 | case '0': 36 | return '\u0000' 37 | case '': 38 | return '' 39 | default: 40 | return n1 41 | } 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/ReplaceSmartQuotes.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Replace Smart Quotes", 5 | "description":"Replace Smart Quotes with their simpler values.", 6 | "author":"Thomas Bauer (https://github.com/tbauer428)", 7 | "icon":"broom", 8 | "tags":"smart,quotes,quotations,quotation,smart-quotes,smart-quotations" 9 | } 10 | **/ 11 | 12 | function main(input) { 13 | input.text = input.text 14 | .replace(/[\u2018\u2019]/g, "'") 15 | .replace(/[\u201C\u201D]/g, '"') 16 | .replace(/“”/g, '"'); 17 | } 18 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/ReverseLines.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Reverse Lines", 5 | "description":"Flips every line of your text.", 6 | "author":"@Clarko", 7 | "icon":"flip", 8 | "tags":"reverse,order,invert,mirror,flip,upside,down" 9 | } 10 | **/ 11 | 12 | function main(input) { 13 | input.text = input.text.split('\n').reverse().join('\n') 14 | 15 | } 16 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/ReverseString.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Reverse String", 5 | "description":"!seod ti tahw sseuG", 6 | "author":"See Source", 7 | "icon":"flip", 8 | "tags":"flip,mirror,invert" 9 | } 10 | **/ 11 | 12 | function main(input) { 13 | 14 | input.text = reverse(input.text) 15 | 16 | } 17 | 18 | /* 19 | 20 | Snippet from https://github.com/mathiasbynens/esrever 21 | 22 | Copyright Mathias Bynens 23 | 24 | Permission is hereby granted, free of charge, to any person obtaining 25 | a copy of this software and associated documentation files (the 26 | "Software"), to deal in the Software without restriction, including 27 | without limitation the rights to use, copy, modify, merge, publish, 28 | distribute, sublicense, and/or sell copies of the Software, and to 29 | permit persons to whom the Software is furnished to do so, subject to 30 | the following conditions: 31 | 32 | The above copyright notice and this permission notice shall be 33 | included in all copies or substantial portions of the Software. 34 | 35 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 36 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 37 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 38 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 39 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 40 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 41 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 42 | 43 | */ 44 | 45 | 46 | var regexSymbolWithCombiningMarks = /([\0-\u02FF\u0370-\u1AAF\u1B00-\u1DBF\u1E00-\u20CF\u2100-\uD7FF\uE000-\uFE1F\uFE30-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])([\u0300-\u036F\u1AB0-\u1AFF\u1DC0-\u1DFF\u20D0-\u20FF\uFE20-\uFE2F]+)/g; 47 | var regexSurrogatePair = /([\uD800-\uDBFF])([\uDC00-\uDFFF])/g; 48 | 49 | var reverse = function(string) { 50 | // Step 1: deal with combining marks and astral symbols (surrogate pairs) 51 | string = string 52 | // Swap symbols with their combining marks so the combining marks go first 53 | .replace(regexSymbolWithCombiningMarks, function($0, $1, $2) { 54 | // Reverse the combining marks so they will end up in the same order 55 | // later on (after another round of reversing) 56 | return reverse($2) + $1; 57 | }) 58 | // Swap high and low surrogates so the low surrogates go first 59 | .replace(regexSurrogatePair, '$2$1'); 60 | // Step 2: reverse the code units in the string 61 | var result = []; 62 | var index = string.length; 63 | while (index--) { 64 | result.push(string.charAt(index)); 65 | } 66 | return result.join(''); 67 | }; 68 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/Rot13.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Rot13", 5 | "description":"Applies the Rot13 cypher to your text.", 6 | "author":"Paul Starr", 7 | "icon":"roman", 8 | "tags":"spoilers,encryption,plaintext" 9 | } 10 | **/ 11 | 12 | function main(state) { 13 | let myText = state.text 14 | // adapted from Sophie Alpert's solution: https://stackoverflow.com/questions/617647/where-is-my-implementation-of-rot13-in-javascript-going-wrong 15 | state.text = myText.replace(/[a-z]/gi, function (c) { 16 | return String.fromCharCode( 17 | (c <= "Z" ? 90 : 122) >= (c=c.charCodeAt(0)+13) ? c : c - 26 18 | ); 19 | }); 20 | return state; 21 | } 22 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/SHA1.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"SHA1 Hash", 5 | "description":"Computes the SHA1 hash of your text (Hex encoded)", 6 | "icon":"fingerprint", 7 | "tags":"strip,slashes,remove" 8 | } 9 | **/ 10 | 11 | const Hashes = require('@boop/hashes') 12 | 13 | function main(state) { 14 | var SHA1 = new Hashes.SHA1; 15 | state.text = SHA1.hex(state.text) 16 | } 17 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/SHA256.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"SHA256 Hash", 5 | "description":"Computes the SHA256 hash of your text (Hex encoded)", 6 | "icon":"fingerprint", 7 | "tags":"strip,slashes,remove" 8 | } 9 | **/ 10 | const Hashes = require('@boop/hashes') 11 | 12 | function main(state) { 13 | var SHA256 = new Hashes.SHA256; 14 | state.text = SHA256.hex(state.text) 15 | } 16 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/SHA512.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"SHA512 Hash", 5 | "description":"Computes the SHA512 hash of your text (Hex encoded)", 6 | "icon":"fingerprint", 7 | "tags":"strip,slashes,remove" 8 | } 9 | **/ 10 | 11 | const Hashes = require('@boop/hashes') 12 | 13 | function main(state) { 14 | var SHA512 = new Hashes.SHA512; 15 | state.text = SHA512.hex(state.text) 16 | } 17 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/ShuffleLines.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Shuffle Lines", 5 | "description":"Randomize each line of your text.", 6 | "author":"@Clarko", 7 | "icon":"dice", 8 | "tags":"shuffle,random" 9 | } 10 | **/ 11 | 12 | function main(input) { 13 | let lines = input.text.split('\n'); 14 | let j = lines.length; 15 | 16 | // Fisher-Yates Shuffle 17 | while (j) { 18 | i = Math.floor(Math.random() * j--); 19 | temp = lines[j]; 20 | lines[j] = lines[i]; 21 | lines[i] = temp; 22 | } 23 | 24 | input.text = lines.join('\n'); 25 | } 26 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/SnakeCase.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Snake Case", 5 | "description":"converts_your_text_to_snake_case.", 6 | "author":"Ivan", 7 | "icon":"snake", 8 | "tags":"snake,case,function,lodash" 9 | } 10 | **/ 11 | 12 | const { snakeCase } = require('@boop/lodash.boop') 13 | 14 | function main(input) { 15 | 16 | input.text = snakeCase(input.text) 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/Sort.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Sort lines", 5 | "description":"Sort lines alphabetically.", 6 | "author":"Sebastiaan Besselsen", 7 | "icon":"sort-characters", 8 | "tags":"sort,alphabet" 9 | } 10 | **/ 11 | 12 | function main(input) { 13 | let sorted = input.text.replace(/\n$/, '').split('\n') 14 | .sort((a, b) => a.localeCompare(b)) 15 | .join('\n'); 16 | 17 | if (sorted === input.text) { 18 | sorted = sorted.split('\n').reverse().join('\n'); 19 | } 20 | input.text = sorted; 21 | } 22 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/SortJSON.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Sort JSON", 5 | "description":"Sort JSON", 6 | "author":"MaDnh", 7 | "icon":"sort-characters", 8 | "tags":"json,sort" 9 | } 10 | **/ 11 | 12 | function main(state) { 13 | let value = state.text; 14 | 15 | try { 16 | value = JSON.parse(value); 17 | } catch (e) { 18 | state.postError("Invalid JSON"); 19 | return; 20 | } 21 | 22 | value = sort(value); 23 | 24 | state.text = JSON.stringify(value, null, 2); 25 | } 26 | 27 | 28 | function sort(obj) { 29 | if (obj instanceof Array) { 30 | let out = obj.map(item => sort(item)); 31 | out.sort((a, b) => { 32 | let fa = JSON.stringify(a), 33 | fb = JSON.stringify(b); 34 | 35 | if (fa < fb) { 36 | return -1; 37 | } 38 | if (fa > fb) { 39 | return 1; 40 | } 41 | return 0; 42 | }); 43 | return out; 44 | } 45 | 46 | if (!isPlainObject(obj)) { 47 | return obj 48 | } 49 | 50 | const result = {}; 51 | const keys = Object.keys(obj); 52 | 53 | keys.sort(); 54 | keys.forEach(key => { 55 | result[key] = sort(obj[key]) 56 | }); 57 | 58 | return result; 59 | } 60 | 61 | function isPlainObject(value) { 62 | return Object.prototype.toString.call(value) === '[object Object]' 63 | } 64 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/SpongeCase.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api": 1, 4 | "name": "Sponge Case", 5 | "description": "CoNvERtS yoUR Text To A HIghER fOrM Of CoMMUnICAtIOn", 6 | "author": "Paul Seelman", 7 | "icon": "pineapple", 8 | "tags": "bob,sarcasm,no,this,is,patrick" 9 | } 10 | **/ 11 | function spongeText(string) { 12 | const chars = string.split(""); 13 | for (let i = chars.length - 1; i > 0; i--) { 14 | const j = Math.floor(Math.random() * Math.floor(2)); 15 | if (j == 0) { 16 | chars[i] = chars[i].toLowerCase(); 17 | } else { 18 | chars[i] = chars[i].toUpperCase(); 19 | } 20 | } 21 | 22 | return chars.join(""); 23 | } 24 | 25 | function main(input) { 26 | input.text = spongeText(input.text); 27 | } 28 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/StartCase.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Start Case", 5 | "description":"Converts Your Text To Start Case.", 6 | "author":"Ivan", 7 | "icon":"type", 8 | "tags":"start,case,function,lodash" 9 | } 10 | **/ 11 | 12 | const { startCase } = require('@boop/lodash.boop') 13 | 14 | function main(input) { 15 | 16 | input.text = startCase(input.text) 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/SumAll.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Sum All", 5 | "description":"Sums up a list of numbers.", 6 | "author":"Annie Tran", 7 | "icon":"abacus", 8 | "tags":"sum,calculator,addition,add" 9 | } 10 | **/ 11 | 12 | function main(input) { 13 | if (!input.text) { 14 | input.postError('') 15 | } else { 16 | input.text = calculate(input.text) 17 | } 18 | } 19 | 20 | function looksLikeFraction(s) { 21 | return /^[\d\.]+\/[\d\.]+$/.test(s) 22 | } 23 | 24 | function getFraction(s) { 25 | const frac = s.split('/') 26 | return frac[0] / frac[1] 27 | } 28 | 29 | function getNumber(s) { 30 | if (looksLikeFraction(s)) { 31 | return getFraction(s) 32 | } 33 | return isNaN(Number(s)) ? '' : Number(s) 34 | } 35 | 36 | function numStringToArray(s) { 37 | return s 38 | .replace(/\/\/.*/g, '') 39 | .split(/[\n\s,;=]/g) 40 | .map((e) => (getNumber(e) ? getNumber(e) : '')) 41 | .filter(Boolean) 42 | } 43 | 44 | function calculate(s) { 45 | const comment = '\t// ' 46 | const numbers = numStringToArray(s) 47 | 48 | var sumOutput = numbers.reduce((a, b) => a + b) 49 | 50 | if (numbers.length > 1) { 51 | sumOutput += comment + numbers.join(' + ') 52 | } 53 | 54 | return s 55 | .split(/[\n,;]/g) 56 | .map((e) => { 57 | e = e.trim() 58 | if (e.charAt(0) === '=' || e === '' || e.toString() === Number(e).toString()) { 59 | return e 60 | } 61 | return `${e}${getNumber(e) && comment + getNumber(e)}` 62 | }) 63 | .concat('\n= ' + sumOutput) 64 | .join('\n') 65 | } 66 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/Test.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Test Script", 5 | "description":"Testing script", 6 | "author":"Ivan", 7 | "icon":"flask", 8 | "tags":"test,test,one,two" 9 | } 10 | **/ 11 | 12 | function main(input) { 13 | 14 | input.postInfo("Hello this is a test!") 15 | 16 | input.fullText = `Hello, World! Let's try some syntax highlighting shall we? 17 | 18 | var test: String? = "Toast" 19 | 20 | { 21 | "name": "Boop", 22 | "type": "software", 23 | "info": { 24 | "tags": ["software", "editor"] 25 | }, 26 | "useful": false, 27 | "version": 1.2345e-10 28 | } 29 | 30 | The MD5 of \`truth\` is 68934a3e9455fa72420237eb05902327 31 | 32 | SELECT "Hello" FROM table LIMIT 2 33 | 34 | /* 35 | haha you can't see me 👻 36 | */ 37 | 38 | if(false) return; // this doesn't work 39 | 40 | This line was added on Fri, 19 Jun 2020 01:01:30 GMT 41 | 42 |
World
43 | 44 | 45 | "This is quote-unquote \\"escaped\\" if you will." 46 | ` 47 | 48 | } 49 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/Trim.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Trim", 5 | "description":"Trims leading and trailing whitespace.", 6 | "author":"Joshua Nozzi", 7 | "icon":"scissors", 8 | "tags":"trim,whitespace,empty,space", 9 | } 10 | **/ 11 | 12 | function main(state) { 13 | 14 | state.text = state.text.trim(); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/URLDecode.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"URL Decode", 5 | "description":"Decodes URL entities in your text.", 6 | "author":"Ivan", 7 | "icon":"link", 8 | "tags":"url,decode,convert" 9 | } 10 | **/ 11 | 12 | function main(input) { 13 | 14 | input.text = decodeURIComponent(input.text) 15 | 16 | } -------------------------------------------------------------------------------- /Boop/Boop/scripts/URLDefang.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Defang", 5 | "description":"Defangs dangerous URLs and other IOCs", 6 | "author":"Ross", 7 | "icon":"link", 8 | "tags":"defang,url,ioc" 9 | } 10 | **/ 11 | 12 | function main(input) { 13 | url = input.text; 14 | url = url.replace(/\./g, "[.]"); 15 | url = url.replace(/http/gi, "hXXp"); 16 | url = url.replace(/:\/\//g, "[://]"); 17 | input.text = url; 18 | } -------------------------------------------------------------------------------- /Boop/Boop/scripts/URLEncode.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"URL Encode", 5 | "description":"Encodes URL entities in your text.", 6 | "author":"Ivan", 7 | "icon":"link", 8 | "tags":"url,encode,convert" 9 | } 10 | **/ 11 | 12 | function main(input) { 13 | 14 | input.text = encodeURIComponent(input.text) 15 | 16 | } -------------------------------------------------------------------------------- /Boop/Boop/scripts/URLEntitiesDecode.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"URL Entities Decode", 5 | "description":"URL Decodes all characters in your text.", 6 | "author":"luisfontes19", 7 | "icon":"percentage", 8 | "tags":"url,decode,full", 9 | "bias": -0.1 10 | } 11 | **/ 12 | 13 | 14 | function fullUrlDecode(str) { 15 | var codes = str.split("%"); 16 | var decoded = ''; 17 | 18 | for (var i = 0; i < codes.length; i++) { 19 | decoded += String.fromCharCode(parseInt(codes[i], 16)); 20 | } 21 | 22 | return decoded; 23 | } 24 | 25 | function main(state) { 26 | state.text = fullUrlDecode(state.text); 27 | } 28 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/URLEntitiesEncode.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"URL Entity Encode", 5 | "description":"URL Encodes all characters in your text.", 6 | "author":"luisfontes19", 7 | "icon":"percentage", 8 | "tags":"url,encode,full", 9 | "bias": -0.1 10 | } 11 | **/ 12 | 13 | 14 | function fullUrlEncode(str) { 15 | var encoded = ''; 16 | 17 | for (var i = 0; i < str.length; i++) { 18 | var h = parseInt(str.charCodeAt(i)).toString(16); 19 | encoded += '%' + h; 20 | } 21 | 22 | return encoded; 23 | } 24 | 25 | function main(state) { 26 | state.text = fullUrlEncode(state.text); 27 | } 28 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/URLRefang.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Refang", 5 | "description":"Removes defanging from dangerous URLs and other IOCs", 6 | "author":"Ross", 7 | "icon":"link", 8 | "tags":"refang,url,ioc" 9 | } 10 | **/ 11 | 12 | function main(input) { 13 | url = input.text; 14 | url = url.replace(/\[\.\]/g, "."); 15 | url = url.replace(/hXXp/gi, "http"); 16 | url = url.replace(/\[:\/\/\]/g, "://"); 17 | input.text = url; 18 | } -------------------------------------------------------------------------------- /Boop/Boop/scripts/Upcase.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Upcase", 5 | "description":"Converts your text to uppercase.", 6 | "author":"Dan2552", 7 | "icon":"type", 8 | "tags":"upcase,uppercase,capital,capitalize,capitalization" 9 | } 10 | **/ 11 | 12 | function main(input) { 13 | 14 | input.text = input.text.toUpperCase(); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /Boop/Boop/scripts/YAMLtoJSON.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"YAML to JSON", 5 | "description":"Converts YAML to JSON.", 6 | "author":"Ivan", 7 | "icon":"metamorphose", 8 | "tags":"markup,convert" 9 | } 10 | **/ 11 | 12 | const yaml = require('@boop/js-yaml') 13 | 14 | function main(input) { 15 | 16 | try { 17 | input.text = JSON.stringify(yaml.safeLoad(input.text), null, 2) 18 | } 19 | catch(error) { 20 | input.postError("Invalid YAML") 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /Boop/Boop/scripts/hex2rgb.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Hex to RGB", 5 | "description":"Convert color in hexadecimal to RGB.", 6 | "author":"Venkat", 7 | "icon":"color-wheel", 8 | "tags":"hex,color,rgb,convert" 9 | } 10 | **/ 11 | 12 | function main(input) { 13 | R = hexToR(input.text); 14 | G = hexToG(input.text); 15 | B = hexToB(input.text); 16 | 17 | input.text = R.toString().concat(','). 18 | concat(G.toString()).concat(','). 19 | concat(B.toString()); 20 | } 21 | 22 | function hexToR(h) { return parseInt((cutHex(h)).substring(0,2),16) } 23 | function hexToG(h) { return parseInt((cutHex(h)).substring(2,4),16) } 24 | function hexToB(h) { return parseInt((cutHex(h)).substring(4,6),16) } 25 | function cutHex(h) { return (h.charAt(0)=="#") ? h.substring(1,7) : h} -------------------------------------------------------------------------------- /Boop/BoopTests/BoopTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BoopTests.swift 3 | // BoopTests 4 | // 5 | // Created by Ivan on 3/21/20. 6 | // Copyright © 2020 OKatBest. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class BoopTests: XCTestCase { 12 | 13 | override func setUp() { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | } 16 | 17 | override func tearDown() { 18 | // Put teardown code here. This method is called after the invocation of each test method in the class. 19 | } 20 | 21 | func testExample() { 22 | // This is an example of a functional test case. 23 | // Use XCTAssert and related functions to verify your tests produce the correct results. 24 | } 25 | 26 | func testPerformanceExample() { 27 | // This is an example of a performance test case. 28 | measure { 29 | // Put the code you want to measure the time of here. 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /Boop/BoopTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Boop/BoopTests/ScriptManagerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScriptManagerTests.swift 3 | // BoopTests 4 | // 5 | // Created by Ivan on 3/21/20. 6 | // Copyright © 2020 OKatBest. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class ScriptManagerTests: XCTestCase { 12 | 13 | let manager = ScriptManager() 14 | 15 | 16 | func testManager() { 17 | XCTAssertFalse(manager.scripts.isEmpty) 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /Boop/Documentation/ConvertingNodeModules.md: -------------------------------------------------------------------------------- 1 | # Converting Node Modules 2 | 3 | For more complex scripts it can be nice to leverage packages from NPM. 4 | However, most modules are not compatible with Boop. Luckily any script 5 | that can be in a browser can work with Boop! 6 | 7 | ## Crafting Your Script 8 | 9 | First write your script like any normal Boop Script 10 | except declare your main in the `global` namespace and dont add 11 | the declarative json document yet. 12 | 13 | ``` 14 | const curlconverter = require('curlconverter'); 15 | 16 | global.main = function(state) { 17 | try { 18 | state.text = curlconverter.toPython(state.text); 19 | } catch (error) { 20 | state.postError("Failed to Convert"); 21 | } 22 | } 23 | ``` 24 | 25 | Then use [Browserify](http://browserify.org/) to bundle 26 | the script. 27 | 28 | `browserify curlToPython_orig.js -o curlToPythonBundle.js` 29 | 30 | Boop is not a browser. That means you need to add a window var. Just add this line 31 | to the top of the script. 32 | ``` 33 | const window = this; 34 | ``` 35 | 36 | Finally, add the declarative JSON document 37 | (as described in [Custom Scripts](CustomScripts.md)) to the top, and you are done! 38 | 39 | -------------------------------------------------------------------------------- /Boop/Documentation/Debugging.md: -------------------------------------------------------------------------------- 1 | # Debugging Scripts 2 | 3 | 4 | ___ 5 | **

Debugging is only available in dev builds, in Boop version 1.2.0 and above.

** 6 | ___ 7 | 8 | ## Getting a dev build 9 | 10 | To use debugging tools, you need a dev build of Boop. This is a restriction of JavascriptCore (the thing that runs scripts) that I have not yet found a way to avoid. 11 | 12 | Since dev builds cannot be signed, they cannot be distributed and you need to make your own. For this, you'll need Xcode (free on the Mac App Store or the [Apple Developer Website](https://developer.apple.com)). Once you have it, follow these steps: 13 | 14 | - Download the Boop source code from Github or clone it locally using git. You can find a compressed version of the source code on the [releases page](https://github.com/IvanMathy/Boop/releases/). 15 | 16 | - In the source code, find and open `Boop.xcodeproj` with Xcode (it's in the `Boop` folder, surprisingly). 17 | 18 | - On the top left corner of the Xcode window, hit the play button. The dev build should start. 19 | 20 | ## Preparing the debugger 21 | 22 | To see your script in the debugger, you'll need to run that script at least once. It does not matter if it doesn't work, Boop just needs to know you want to use it. 23 | 24 | After running your script, open Safari (the web browser that comes bundled on macOS). If you've never used the developer tools, open Safari's preferences, go to the `Advanced` tab and enable `Show Develop menu in menu bar`. 25 | 26 | 27 |

28 | UI Screenshot 29 |

30 | 31 | 32 | 33 | ## Connecting the debugger 34 | 35 | Once set up, you should be able to see your script in the `Develop` menu, in the submenu with the same name as your computer (in the screenshot, it's `Ivan MKII` - yours will be different, most likely), under the `Boop Section`. Select the script you'd like to debug. 36 | 37 |

38 | UI Screenshot 39 |

40 | 41 | If you see `No Inspectable Applications`, please double check that: 42 | 43 | - Boop is running 44 | - It is a dev build 45 | - You used your script at least once 46 | 47 | ## Using the debugger 48 | 49 | The debugger is the same as the built-in one from Safari, and works just like any in-browser inspector. The central panel shows the source of your script. *Editing it there will not affect the script within Boop, and any change will be lost.* 50 | 51 |

52 | UI Screenshot 53 |

54 | 55 | 56 | ## Modules 57 | 58 | Modules are show as separate files in the left panel of the debugger. You'll notice that the module's source will have some added code above and below, surrounded with tags like this: 59 | 60 | ```javascript 61 | /*********************************** 62 | * Start of Boop's wrapper * 63 | ***********************************/ 64 | ``` 65 | 66 | This is how Boop makes sure modules are safe and compatible. The wrapper is added at runtime. It ain't pretty but it gets the job done. 67 | 68 |

69 | UI Screenshot 70 |

71 | 72 | 73 | ## Console 74 | 75 | When the debugger is active, the `console` object gets enabled. You can use the bottom panel or the `Console` tab to read the output and use the interactive prompt. 76 | 77 |

78 | UI Screenshot 79 |

80 | 81 | Anything posted to the console prior to opening the debugger is recorded and will be displayed when you open it. Therefore, you don't need to rush to open the debugger to see initialization messages for example. 82 | 83 | Please note that `console` is not available in non-debug builds, and using it will cause your script to throw an exception. 84 | 85 | ## Breakpoints 86 | 87 | You can use breakpoints in the debugger, just like you would in any programming environment. The best way to do that is to place it in somewhere called by the `main()` function. 88 | 89 |

90 | UI Screenshot 91 |

92 | 93 | When a breakpoint is active, you can see the scope in the right panel, step over/into in the left panel, or interact with the console in the bottom panel. 94 | 95 | While the breakpoint is active, Boop will become unresponsive. To make Boop usable again, continue the script execution by pressing the play button at the top left of the debugger. 96 | 97 | ## Reloading scripts 98 | 99 | When reloading scripts within Boop, the debugger **will not** automatically reload. If you want to check a new version of your script, close the debugger, run the scripts again for it to register, then re-open the debugger. 100 | 101 | Leaving the debugger open will not release the previous version of the script, and therefore duplicates will show in the debugging menu. 102 | 103 | When reopening previously opened scripts (even from a previous development session), the debugger will remember any previously applied breakpoint. Keep that in mind when debugging a module, as it's easy to forget you had a breakpoint in there. 104 | 105 | 106 | ## Final wisdom 107 | 108 | Just like any software development environment, there is inherent danger in using those tools if you don't know what you're doing. Use at your own risk, avoid clicking stuff you don't know, and be nice to people. 109 | -------------------------------------------------------------------------------- /Boop/Documentation/Images/UI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Documentation/Images/UI.png -------------------------------------------------------------------------------- /Boop/Documentation/Images/breakpoints.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Documentation/Images/breakpoints.png -------------------------------------------------------------------------------- /Boop/Documentation/Images/console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Documentation/Images/console.png -------------------------------------------------------------------------------- /Boop/Documentation/Images/debugger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Documentation/Images/debugger.png -------------------------------------------------------------------------------- /Boop/Documentation/Images/developMenu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Documentation/Images/developMenu.png -------------------------------------------------------------------------------- /Boop/Documentation/Images/modules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Documentation/Images/modules.png -------------------------------------------------------------------------------- /Boop/Documentation/Images/safari.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanMathy/Boop/5cf6914381f6ed6ef004c3a7d470f241a248fcb6/Boop/Documentation/Images/safari.png -------------------------------------------------------------------------------- /Boop/Documentation/Modules.md: -------------------------------------------------------------------------------- 1 | # Modules 2 | 3 | 4 | 5 | ___ 6 | *

Modules are available in Boop version 1.2.0 and above.

* 7 | ___ 8 | 9 | Just to be very clear before we get started: **Boop does not support commonJS/node/ES6 modules**. It has a custom import system that may or may not be compatible with some existing modules. 10 | 11 | ## Importing modules 12 | 13 | To import a module, use the require function. It'll will return the contents of `module.exports`: 14 | 15 | ```javascript 16 | const test = require('lib/testLib') 17 | const test = require('modules/otherLib.js') 18 | ``` 19 | 20 | If you do not include a `.js` extension, the system will add one automatically. 21 | 22 | You can also use destructuring to only get a single function out: 23 | 24 | ```javascript 25 | const { base64decode } = require('lib/base64') 26 | // or 27 | 28 | const { base64encode } = require('lib/base64') 29 | ``` 30 | 31 | ## Creating modules 32 | 33 | Modules are run in a pseudo-sandbox, in somewhat similar (but not equal) way to CommonJS. The sandbox contains a `module` object with an `exports` property. Whatever that property contains is what will be returned by the require function. 34 | 35 | ```javascript 36 | // testModule.js 37 | 38 | module.exports = "Hello World" 39 | ``` 40 | ```javascript 41 | // script.js 42 | const greeting = require('testModule.js') 43 | ``` 44 | 45 | The above example is essentially the same as: 46 | 47 | ```javascript 48 | // script.js 49 | const greeting = "Hello World" 50 | ``` 51 | 52 | Module imports can also be nested, though you should probably avoid that. 53 | 54 | ```javascript 55 | // lib/deepModule.js 56 | 57 | module.exports = "Hello World" 58 | ``` 59 | 60 | ```javascript 61 | // lib/shallowModule.js 62 | const greeting = require('lib/deepModule') 63 | 64 | module.exports = greeting 65 | ``` 66 | 67 | ```javascript 68 | // script.js 69 | const greeting = require('lib/shallowModule') 70 | ``` 71 | 72 | When using nested requires, the module name is based on the path of your script, not of the current module. 73 | 74 | ## Built in modules 75 | 76 | Boop ships with a couple of modules, with the prefix `@boop/`, which you can use in your own scripts. 77 | 78 | | Module name | Description | 79 | | ------------------ | ------------- | 80 | | `@boop/base64` | Base 64 encoding and decoding | 81 | | `@boop/lodash.boop`| Subset of lodash string functions (see below) | 82 | | `@boop/he` | HTML entities encoder/decoder | 83 | | `@boop/vkBeautify` | XML, CSS, and SQL formatter and minifier | 84 | | `@boop/js-yaml` | Parses and Stringifies YAML objects | 85 | | `@boop/hashes` | Common crypto hashes | 86 | 87 | If you need a new module that you think could be used by more scripts, feel free to open a PR adding more functionality. 88 | 89 | #### Lodash 90 | 91 | The built in lodash module was created with the following command and only includes a few functions and their direct dependencies: 92 | 93 | ```bash 94 | $ lodash include=camelCase,deburr,escapeRegExp,kebabCase,snakeCase,startCase,size 95 | ``` 96 | 97 | If you'd like to add more functions, feel free to rebuild with additional parameters and submit a PR. 98 | -------------------------------------------------------------------------------- /Boop/Documentation/Readme.md: -------------------------------------------------------------------------------- 1 | # Boop 2 | 3 | Hey there! Thanks for trying out Boop. This documentation should hopefully help you understand how it works, how you can extend it, and how you can contribute back. 4 | 5 | ## Child Pages 6 | 7 | - [Custom Scripts](CustomScripts.md) 8 | - [Modules](Modules.md) 9 | - [Debugging Scripts](Debugging.md) 10 | - [Converting Node Modules](ConvertingNodeModules.md) 11 | 12 | ## Getting Boop 13 | 14 | You can download Boop from the Mac App Store, or from the [Releases page on GitHub](https://github.com/IvanMathy/Boop/releases). If you'd like to roll your own, you can clone the repository and follow the build instructions in the README. 15 | 16 | ## Using Boop 17 | 18 | Boop is pretty easy to use: Open it, paste some text, run some scripts, optionally copy the text out. 19 | 20 | To run scripts, simply open the script picker by pressing `⌘B` or in the top menu under `Scripts > Open Picker`. 21 | 22 | From the script picker, start typing to search for a script. You can then press `Enter ⏎` to pick the first script, or use the arrow keys to select another one. 23 | 24 | You can run the last script again by pressing `⇧⌘B` or from the option in the scripts menu. 25 | 26 | To start over, you can clear the editor by pressing `⌘N`. 27 | 28 | If you are developing scripts, you can reload all the script by pressing `⇧⌘R` or from the script menu as well. 29 | 30 | ## Questions 31 | 32 | ### Can I see a list of all scripts? 33 | 34 | Yes! Simply open the script picker and search for `*`. 35 | 36 | ### Why can't I open/Save a file? 37 | 38 | Because that's not the goal of Boop. It's not really an editor, more of an unstructured limbo for your plain text pasted content. 39 | 40 | ### Where can I find more scripts? 41 | 42 | You can find more functions in the [Boop Script Repository](https://github.com/IvanMathy/Boop/tree/main/Scripts). It contains scripts suggested by the community that are not in the built-in script library. You can go there to find new functionality, or suggest your own! 43 | 44 | ### Can I make my own scripts? 45 | 46 | Yes! Simply follow the instruction in the [Custom Scripts page](CustomScripts.md) to know how to get started. 47 | 48 | ### Does Boop collect data on me? 49 | 50 | No. The only time Boop communicates outside of itself is to check whether a new version is available. This is done by fetching a static .json file, with no additional data passed along. If you downloaded Boop through the Mac App Store, it's possible that standard data and/or crash reports get sent back to Apple and shared with me if you enabled App Analytics sharing, though I have not seen that happen yet. 51 | 52 | ### How can I report a problem? 53 | 54 | The best way to do that is to [file an issue on GitHub](https://github.com/IvanMathy/Boop/issues/new). Otherwise, you can [talk to me on Twitter](https://twitter.com/OKatBest), as long as you're nice. 55 | 56 | ### How is Boop built? 57 | 58 | Boop is mostly built using a custom fork of [SavannaKit](https://github.com/IvanMathy/savannakit), originally created by [Louis D'hauwe](http://twitter.com/LouisDhauwe). The search is powered by a custom fork of [Fuse-swift](https://github.com/IvanMathy/fuse-swift). The rest of Boop is simply built in Swift, besides scripts which are Javascript. Go ahead and open some of them to check their license! 59 | 60 | ### Do I have to say "Boop" out loud when I press ⌘+B? 61 | 62 | Yes. 63 | -------------------------------------------------------------------------------- /DOING.md: -------------------------------------------------------------------------------- 1 | # DOING.md 2 | 3 | This document, inspired by [this tweet](https://twitter.com/drwave/status/1564063331341590529), contains the currently in progress or planned features for Boop. 4 | 5 | This document was last updated *August, 30th 2022*. Work may be happening in other repositories or branches making this one appear stale, but Boop is still very much an active project. 6 | 7 | ## In progress 8 | 9 | ### Multi Cursor Editing 10 | 11 | The ability to have more than one active cursor to type and edit content. 12 | 13 | ### Split Scripts 14 | 15 | Separating scripts from the main Boop Github repository, into a dedicated one. 16 | 17 | ## Planned 18 | 19 | ### Color Settings 20 | 21 | The ability to customize colors beyond light and dark mode. Maybe themes? 22 | 23 | ### Font Settings 24 | 25 | The ability to change font, and more importantly font-size. 26 | 27 | ## Not Planned 28 | 29 | ### Load / Save 30 | 31 | There is no plan to support files within Boop. 32 | 33 | ### Tabs 34 | 35 | There is no plan to support tabs or multiple windows within Boop. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ivan Mathy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Boop. 3 | 4 | 5 |

6 | 7 | UI Screenshot 8 |

9 | 10 | 11 |

12 | 13 | 14 |

15 |

16 | WebsiteDownload from GitHubGet on the Mac App Store
17 | DocumentationFind more scripts 18 |

19 | 20 | ### How to get Boop 21 | 22 | There are four ways to get Boop. Your best bet is either to 23 | 24 | - Download from GitHub releases or 25 | - Download on the Mac App Store
. 26 | 27 | You can also build it from source, or get it from Homebrew, although that is not officially supported. 28 | 29 | ### How to build from source 30 | 31 | If you're just trying to get Boop, building from source might not be your best bet. Developing new scripts does not require building from source. 32 | 33 | - Clone or download a copy of the repository 34 | - Open `Boop/Boop.xcodeproj` 35 | - Press play 36 | 37 | 38 | ### Documentation 39 | 40 | - [Documentation](Boop/Documentation/Readme.md) 41 | - [Custom scripts](Boop/Documentation/CustomScripts.md) 42 | -------------------------------------------------------------------------------- /Scripts/CSVtoJSONheaderless.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"CSV to JSON (headerless)", 5 | "description":"Converts comma-separated, headerless tables to JSON.", 6 | "author":"Flare576", 7 | "icon":"table", 8 | "tags":"table,convert", 9 | "bias": -0.2 10 | } 11 | **/ 12 | const Papa = require('@boop/papaparse.js'); 13 | 14 | function main(state) { 15 | try { 16 | const { data } = Papa.parse(state.text, { header:false }); 17 | state.text = JSON.stringify(data, null, 2); 18 | } 19 | catch(error) { 20 | state.text = error; 21 | state.postError("Invalid CSV") 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Scripts/CalculateSize.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api": 1, 4 | "name": "Calculate Size (Bytes)", 5 | "description": "Calculates size of text in Bytes", 6 | "author": "zzz", 7 | "icon": "counter", 8 | "tags": "calc,size,bytes,storage" 9 | } 10 | **/ 11 | 12 | //From https://stackoverflow.com/a/12206089 13 | function getUTF8Length(s) { 14 | var len = 0; 15 | for (var i = 0; i < s.length; i++) { 16 | var code = s.charCodeAt(i); 17 | if (code <= 0x7f) { 18 | len += 1; 19 | } else if (code <= 0x7ff) { 20 | len += 2; 21 | } else if (code >= 0xd800 && code <= 0xdfff) { 22 | // Surrogate pair: These take 4 bytes in UTF-8 and 2 chars in UCS-2 23 | // (Assume next char is the other [valid] half and just skip it) 24 | len += 4; i++; 25 | } else if (code < 0xffff) { 26 | len += 3; 27 | } else { 28 | len += 4; 29 | } 30 | } 31 | return len; 32 | } 33 | 34 | function main(input) { 35 | let bytes = getUTF8Length(input.text); 36 | if (bytes > 1000000) 37 | { 38 | bytes /= 1000000; 39 | input.postInfo(`${bytes.toFixed(2)} Mb`) 40 | } 41 | if (bytes > 100000) { 42 | bytes /= 1000; 43 | input.postInfo(`${bytes.toFixed(2)} Kb`) 44 | } 45 | else { 46 | input.postInfo(`${bytes} bytes`) 47 | } 48 | } -------------------------------------------------------------------------------- /Scripts/CreateProjectGlossaryMarkdown.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Create Project Glossary Markdown File", 5 | "description":"Type 'help' and run this script for instructions.", 6 | "author":"Terry L. Lewis", 7 | "icon":"colosseum", 8 | "tags":"markdown,glossary", 9 | "bias":0.0 10 | } 11 | **/ 12 | 13 | 14 | /// 15 | /// Generates a Markdown Glossary file for the project specified in the input parameters. 16 | /// Run the script with no input text to create the input JSON required by the glossary generator. 17 | /// Fill in the required values, and re-run the Boop Script to generate the glossary. 18 | /// 19 | function main(state) { 20 | 21 | try { 22 | var options = {}; 23 | if (state.text.trim().toLowerCase() === "help"){ 24 | state.fullText = getHelpText(); 25 | } else if (state.fullText.trim() === ""){ 26 | state.fullText = JSON.stringify(getDefaultParameters()); 27 | } else { 28 | options = JSON.parse(state.text); 29 | 30 | var indexEntries = "0ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 31 | var header = getHeader(); 32 | var sectionTemplate = getSectionTemplate(); 33 | var sampleEntries = getSampleEntries(); 34 | 35 | var index = "\r\n"; 36 | var body = ""; 37 | for (var x = 0; x < indexEntries.length; x++) 38 | { 39 | var c = indexEntries.charAt(x); 40 | 41 | // Build Index 42 | index = index + "[" + c + "](#" + c.toLowerCase() + ") " 43 | if (c === "H" || c === "Q" || c === "Z") index = index + "\r\n"; 44 | 45 | // Build Sections 46 | var section = sectionTemplate.replace("{idx}", c) 47 | if (c === "E" && options.includeSamples){ 48 | section = section.replace("{samples}", sampleEntries); 49 | } else { 50 | section = section.replace("{samples}", ""); 51 | } 52 | body = body + section 53 | } 54 | 55 | // Put it all together 56 | var glossary = header + index + body; 57 | 58 | state.fullText = glossary.replace("{projectName}", options.projectName); 59 | } 60 | } 61 | catch(error) { 62 | options = getDefaultParameters(); 63 | options.error = error.toString(); 64 | state.fullText = JSON.stringify(options); 65 | //state.text = error.toString() 66 | state.postError(error.toString()) 67 | } 68 | 69 | } 70 | 71 | function getDefaultParameters(){ 72 | return { 73 | projectName: "Project Name", 74 | includeSamples: false 75 | } 76 | } 77 | 78 | function getHeader(){ 79 | return ` 80 | # {projectName} 81 | ## Glossary Of Terms 82 | `.trim() 83 | } 84 | 85 | function getSectionTemplate(){ 86 | return ` 87 | ## {idx} 88 | {samples} 89 | [Back to Top](#glossary-of-terms) 90 | 91 | --- 92 | `; 93 | } 94 | 95 | function getSampleEntries() { 96 | return ` 97 | 98 | ### Example Entry 99 | This example provides a template for how glossary entries should be formatted. 100 | 101 | ### Example Entry 2 102 | Sample definition of Example Entry 2. See also [Example Entry](#example-entry). 103 | 104 | `; 105 | } 106 | 107 | function getHelpText(){ 108 | return ` 109 | Create Project Glossary Markdown File 110 | ===================================== 111 | 112 | This script takes a JSON input string and reads its 113 | properties to produce an empty Glossary file. Use this 114 | when starting a new project to ensure that everyone on 115 | the team knows the proper definitions of the jargon used. 116 | 117 | Running the script without an input string generates the 118 | required JSON structure. 119 | 120 | You can delete all but the JSON below and run the script 121 | to see example results. 122 | 123 | Input structure: 124 | 125 | { 126 | "projectName" : "--Project Name Here--", 127 | "includeSamples" : false 128 | } 129 | 130 | The "includeSamples" parameter is optional and defaults to [false]. 131 | `; 132 | } -------------------------------------------------------------------------------- /Scripts/DIGI2ASCII.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api": 1, 4 | "name": "Digi to ASCII", 5 | "description": "Digi to ASCII", 6 | "author": "Joseph Ng Rong En", 7 | "icon": "dice", 8 | "tags": "ascii,digi" 9 | } 10 | **/ 11 | 12 | function digi2a(str) { 13 | var split = str.split(/[ ,]+/); 14 | var arr = []; 15 | for (var i = 0, l = split.length; i < l; i ++) { 16 | var ascii = String.fromCharCode(split[i]); 17 | arr.push(ascii); 18 | } 19 | return arr.join(''); 20 | } 21 | 22 | function main(input) { 23 | input.text = digi2a(input.text); 24 | } -------------------------------------------------------------------------------- /Scripts/FromUnicode.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | { 4 | "api":1, 5 | "name":"From string from unicode scaped", 6 | "description":"Returns a readable string from the unicode scaped string (js format)", 7 | "author":"luisfontes19", 8 | "icon":"broom", 9 | "tags":"string,normalize,convert,readable,unicode" 10 | } 11 | **/ 12 | 13 | function main(state) { 14 | state.text = fromUnicode(state.text); 15 | } 16 | 17 | function fromUnicode(str) { 18 | return str.split("\\u").map(u => { 19 | return String.fromCharCode(parseInt(u, 16)); 20 | }).join(""); 21 | } -------------------------------------------------------------------------------- /Scripts/JoinLines.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Join Lines", 5 | "description":"Joins all lines without any delimiter.", 6 | "author":"riesentoaster", 7 | "icon":"collapse", 8 | "tags":"join" 9 | } 10 | **/ 11 | 12 | function main(input) { 13 | input.text = input.text.replace(/\n/g, ''); 14 | } 15 | -------------------------------------------------------------------------------- /Scripts/JoinLinesWithComma.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Join Lines With Comma", 5 | "description":"Joins all lines with a comma.", 6 | "author":"riesentoaster", 7 | "icon":"collapse", 8 | "tags":"join, comma", 9 | "bias": -0.1 10 | } 11 | **/ 12 | 13 | function main(input) { 14 | input.text = input.text.replace(/\n/g, ','); 15 | } 16 | -------------------------------------------------------------------------------- /Scripts/JoinLinesWithSpace.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Join Lines With Space", 5 | "description":"Joins all lines with a space", 6 | "author":"riesentoaster", 7 | "icon":"collapse", 8 | "tags":"join, space", 9 | "bias": -0.1 10 | } 11 | **/ 12 | 13 | function main(input) { 14 | input.text = input.text.replace(/\n/g, ' '); 15 | } 16 | -------------------------------------------------------------------------------- /Scripts/JsObjectToJSON.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"JS Object to JSON", 5 | "description":"Converts a javascript object to JSON format", 6 | "author":"luisfontes19", 7 | "icon":"HTML", 8 | "tags":"json,js,object,convert", 9 | "bias": -0.1 10 | } 11 | **/ 12 | 13 | function main(state) { 14 | 15 | try { 16 | const data = state.text; 17 | eval("parsed = " + data); 18 | state.text = JSON.stringify(parsed); 19 | } 20 | catch (ex) { 21 | state.postError(ex.message); 22 | } 23 | } -------------------------------------------------------------------------------- /Scripts/LineComparer.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Line compare", 5 | "description":"Check if existing lines have all the same content", 6 | "author":"Luis Fontes", 7 | "icon":"type", 8 | "tags":"string,match,text,compare,line", 9 | "bias": -0.1 10 | } 11 | **/ 12 | 13 | function main(state) { 14 | 15 | const lines = state.text.split(/\n/); 16 | const first = lines[0]; 17 | const differentLines = []; 18 | 19 | for (let i = 1; i < lines.length; i++) { 20 | const line = lines[i]; 21 | 22 | if (first !== line) differentLines.push(i + 1); 23 | } 24 | 25 | if (differentLines.length === 0) 26 | state.postInfo('Lines are equal') 27 | else if (differentLines.length === 1) 28 | state.postError(`Line ${differentLines[0]} is not equal to the line 1`); 29 | else 30 | state.postError(`Lines [${differentLines.join(", ")}] are not equal to line 1`); 31 | } -------------------------------------------------------------------------------- /Scripts/NewBoopScript.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"New Boop Script", 5 | "description":"Returns a basic Boop script.", 6 | "author":"tlewis", 7 | "icon":"quote", 8 | "tags":"boop,state,script,debug,new,create" 9 | } 10 | **/ 11 | 12 | function main(state) { 13 | try { 14 | state.text = script 15 | } 16 | catch(error) { 17 | state.postError("Something strange happened here...") 18 | } 19 | } 20 | 21 | var script = ` 22 | /** 23 | { 24 | "api":1, 25 | "name":"New Boop Script", 26 | "description":"What does your script do?", 27 | "author":"Whooooooo are you?", 28 | "icon":"broom", 29 | "tags":"place,tags,here", 30 | "bias":0.0 31 | } 32 | **/ 33 | 34 | function main(state) { 35 | try { 36 | 37 | /* 38 | The 'state' object has three properties to deal with text: text, fullText, and selection. 39 | 40 | state.fullText will contain or set the entire string from the Boop editor, regardless of whether a selection is made or not. 41 | state.selection will contain or set the currently selected text, one at a time if more that one selection exists. 42 | state.text will behave like selection if there is one or more selected piece of text, otherwise it will behave like fullText. 43 | */ 44 | 45 | state.fullText = state.selection; // Remove all but selected text 46 | } 47 | catch(error) { 48 | state.postError("Explain what went wrong here...") 49 | } 50 | 51 | } 52 | ` 53 | -------------------------------------------------------------------------------- /Scripts/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Additional scripts 3 | 4 | This folder contains useful scripts not included in the default Boop library you might want to download. 5 | 6 | ## Installing new scripts 7 | 8 | To install a new script, simply download the .js file and place it into the same folder as your custom scripts. If Boop is already open, reload scripts from the `Scripts` menu. 9 | 10 | ## Contributing 11 | 12 | Made something useful? Think of a way to improve an existing script? Feel free to open a pull request or a new issue on GitHub! -------------------------------------------------------------------------------- /Scripts/ShuffleCharacters.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api": 1, 4 | "name": "Shuffle characters", 5 | "description": "Shuffles characters randomly", 6 | "author": "Christian Petersen", 7 | "icon": "dice", 8 | "tags": "shuffle,random,character,char" 9 | } 10 | **/ 11 | function shuffleString(string) { 12 | const chars = string.split(""); 13 | 14 | for (let i = chars.length - 1; i > 0; i--) { 15 | const j = Math.floor(Math.random() * (i + 1)); 16 | [chars[i], chars[j]] = [chars[j], chars[i]]; 17 | } 18 | 19 | return chars.join(""); 20 | } 21 | 22 | function main(input) { 23 | input.text = shuffleString(input.text); 24 | } 25 | -------------------------------------------------------------------------------- /Scripts/TimeToSecond.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Time to seconds", 5 | "description":"Convert hh:mm:ss to seconds", 6 | "author":"PeteChu", 7 | "icon":"watch", 8 | "tags":"transform,convert" 9 | } 10 | **/ 11 | 12 | function timeToSeconds(durationText) { 13 | const [hours = 0, minutes = 0, seconds = 0] = String(durationText).split(':'); 14 | return Number(hours) * 3600 + Number(minutes) * 60 + Number(seconds); 15 | } 16 | 17 | function main(input) { 18 | input.insert('\n\n' + timeToSeconds(input.text)); 19 | } -------------------------------------------------------------------------------- /Scripts/TrimEnd.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Trim End", 5 | "description":"Trims trailing whitespace.", 6 | "author":"Joshua Nozzi", 7 | "icon":"scissors", 8 | "tags":"trim,end,right,trailing,whitespace,empty,space", 9 | } 10 | **/ 11 | 12 | function main(state) { 13 | 14 | state.text = state.text.trimEnd(); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /Scripts/TrimStart.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Trim Start", 5 | "description":"Trims leading whitespace.", 6 | "author":"Joshua Nozzi", 7 | "icon":"scissors", 8 | "tags":"trim,start,left,leading,beginning,whitespace,empty,space", 9 | } 10 | **/ 11 | 12 | function main(state) { 13 | 14 | state.text = state.text.trimStart(); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /Scripts/Wadsworth.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Wadsworth Constant", 5 | "description":"first 30% of your text.", 6 | "author":"Ivan", 7 | "icon":"scissors", 8 | "tags":"snap" 9 | } 10 | **/ 11 | 12 | 13 | function main(state) { 14 | var all = state.text.split(" ") 15 | all.splice(0, Math.ceil(all.length * 0.3)) 16 | state.text = all.join(" ") 17 | } 18 | -------------------------------------------------------------------------------- /Scripts/WkbToWkt.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Well-Known Binary to Text", 5 | "description":"Converts your hex encoded WKB (any endian) to WKB, wkb2wkt", 6 | "author":"Mikael Brassman (Twitter: @spoike)", 7 | "icon":"globe", 8 | "tags":"wkb,convert,wkt,binary,hex,wkb2wkt" 9 | } 10 | **/ 11 | 12 | function main(input) { 13 | try { 14 | input.text = input.text.replace(/0[01]0[1-6][0-9a-f]+/g, convertHex); 15 | } catch (err) { 16 | input.postError(err); 17 | return; 18 | } 19 | } 20 | 21 | function convertHex(hexStr) { 22 | let pos = 0; 23 | let output = ""; 24 | while (pos < hexStr.length) { 25 | const littleEndian = getIsLittleEndian(hexStr, pos); 26 | pos += 2; 27 | const geoType = getUint32(hexStr, pos, littleEndian); 28 | pos += 8; 29 | switch (geoType) { 30 | case 1: { 31 | // POINT 32 | const point = getPoint(hexStr, pos, littleEndian); 33 | pos += 32; 34 | output += `POINT (${point})`; 35 | break; 36 | } 37 | case 2: { 38 | // LINESTRING 39 | const length = getUint32(hexStr, pos, littleEndian); 40 | pos += 8; 41 | const points = []; 42 | for (let i = 0; i < length; i++) { 43 | points.push(getPoint(hexStr, pos, littleEndian)); 44 | pos += 32; 45 | } 46 | output += `LINESTRING (${points.join(", ")})`; 47 | break; 48 | } 49 | case 3: { 50 | // POLYGON 51 | const count = getUint32(hexStr, pos, littleEndian); 52 | pos += 8; 53 | const rings = []; 54 | for (let i = 0; i < count; i++) { 55 | const length = getUint32(hexStr, pos, littleEndian); 56 | pos += 8; 57 | const points = []; 58 | for (let j = 0; j < length; j++) { 59 | points.push(getPoint(hexStr, pos, littleEndian)); 60 | pos += 32; 61 | } 62 | rings.push(points.join(", ")); 63 | } 64 | output += `POLYGON (${rings.map(wrapParens).join(", ")})`; 65 | break; 66 | } 67 | case 4: { 68 | // MULTIPOINT 69 | const points = []; 70 | const count = getUint32(hexStr, pos, littleEndian); 71 | pos += 8; 72 | for (let i = 0; i < count; i++) { 73 | const innerLE = getIsLittleEndian(hexStr, pos); 74 | pos += 2 + 8; 75 | points.push(getPoint(hexStr, pos, innerLE)); 76 | pos += 32; 77 | } 78 | output += `MULTIPOINT (${points.join(", ")})`; 79 | break; 80 | } 81 | case 5: { 82 | // MULTILINESTRING 83 | const lineStrings = []; 84 | const count = getUint32(hexStr, pos, littleEndian); 85 | pos += 8; 86 | for (let i = 0; i < count; i++) { 87 | const innerLE = getIsLittleEndian(hexStr, pos); 88 | pos += 2 + 8; 89 | const points = []; 90 | const length = getUint32(hexStr, pos, littleEndian); 91 | pos += 8; 92 | for (let j = 0; j < length; j++) { 93 | points.push(getPoint(hexStr, pos, innerLE)); 94 | pos += 32; 95 | } 96 | lineStrings.push(points.join(", ")); 97 | } 98 | output += `MULTILINESTRING (${lineStrings.map(wrapParens).join(", ")})`; 99 | break; 100 | } 101 | case 6: { 102 | // MULTIPOLYGON 103 | const polys = []; 104 | const polyCount = getUint32(hexStr, pos, littleEndian); 105 | pos += 8; 106 | for (let i = 0; i < polyCount; i++) { 107 | const innerLE = getIsLittleEndian(hexStr, pos); 108 | pos += 2 + 8; 109 | const rings = []; 110 | const ringCount = getUint32(hexStr, pos, innerLE); 111 | pos += 8; 112 | for (let j = 0; j < ringCount; j++) { 113 | const points = []; 114 | const pointCount = getUint32(hexStr, pos, innerLE); 115 | pos += 8; 116 | for (let k = 0; k < pointCount; k++) { 117 | points.push(getPoint(hexStr, pos, innerLE)); 118 | pos += 32; 119 | } 120 | rings.push(points.join(", ")); 121 | } 122 | polys.push(rings.map(wrapParens).join(", ")); 123 | } 124 | output += `MULTIPOLYGON (${polys.map(wrapParens).join(", ")})`; 125 | break; 126 | } 127 | default: 128 | throw geoType + " is not supported"; 129 | } 130 | } 131 | return output; 132 | } 133 | 134 | function wrapParens(el) { 135 | return `(${el})`; 136 | } 137 | 138 | function getIsLittleEndian(str, pos) { 139 | const byteString = str.substr(pos, 2); 140 | if (byteString === "00") { 141 | return false; 142 | } else if (byteString === "01") { 143 | return true; 144 | } 145 | throw byteString + " is unknown byte order"; 146 | } 147 | 148 | function getPoint(str, pos, littleEndian) { 149 | const numbers = []; 150 | numbers.push(getDouble(str, pos, littleEndian)); 151 | numbers.push(getDouble(str, pos + 16, littleEndian)); 152 | return numbers.join(" "); 153 | } 154 | 155 | function getUint32(str, pos, littleEndian) { 156 | const view = new DataView(new ArrayBuffer(4)); 157 | let data = str.substr(pos, 8).match(/../g); 158 | for (let i = 0; i < data.length; i++) { 159 | view.setUint8(i, parseInt(data[i], 16)); 160 | } 161 | return view.getUint32(0, littleEndian); 162 | } 163 | 164 | function getDouble(str, pos, littleEndian) { 165 | const view = new DataView(new ArrayBuffer(8)); 166 | let data = str.substr(pos, 16).match(/../g); 167 | for (let i = 0; i < data.length; i++) { 168 | view.setUint8(i, parseInt(data[i], 16)); 169 | } 170 | return view.getFloat64(0, littleEndian); 171 | } 172 | -------------------------------------------------------------------------------- /Scripts/WktToWkb.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Well-Known Text to Binary", 5 | "description":"Converts your WKT to little endian WKB (hex encoded), wkt2wkb", 6 | "author":"Mikael Brassman (Twitter: @spoike)", 7 | "icon":"globe", 8 | "tags":"wkb,convert,wkt,binary,little endian,hex,wkt2wkb" 9 | } 10 | **/ 11 | 12 | const re = /(?:(?:MULTI)?POINT|(?:MULTI)?LINESTRING|(?:MULTI)?POLYGON)\s*\([()0-9\s,\.\s]+\)/g; 13 | 14 | function main(input) { 15 | try { 16 | input.text = input.text.replace(re, convert); 17 | } catch (err) { 18 | input.postError(err); 19 | return; 20 | } 21 | } 22 | 23 | function convert(text) { 24 | const littleEndian = true; 25 | let tokens = []; 26 | for (let i = 0; i < text.length; i++) { 27 | tokenize(tokens, text[i]); 28 | } 29 | tokens = tokens.filter(Boolean); 30 | let output = ""; 31 | while (tokens.length > 0) { 32 | const token = tokens.shift(); 33 | if (tokens.shift() !== "(") { 34 | throw token + "is missing ("; 35 | } 36 | switch (token) { 37 | case "POINT": 38 | output += handlePoint(tokens, littleEndian); 39 | break; 40 | case "LINESTRING": 41 | output += handleLineString(tokens, littleEndian); 42 | break; 43 | case "POLYGON": 44 | output += handlePolygon( 45 | getParensSlice(tokens, "POLYGON", false), 46 | littleEndian 47 | ); 48 | break; 49 | case "MULTIPOINT": 50 | output += handleMultipoint(tokens, littleEndian); 51 | break; 52 | case "MULTILINESTRING": 53 | output += handleMultilinestring(tokens, littleEndian); 54 | break; 55 | case "MULTIPOLYGON": 56 | output += handleMultipolygon(tokens, littleEndian); 57 | break; 58 | default: 59 | throw "Unrecognized token " + token; 60 | } 61 | if (tokens.shift() !== ")") { 62 | throw token + " is missing )"; 63 | } 64 | } 65 | return output; 66 | } 67 | 68 | function tokenize(memo, char) { 69 | if (memo.length === 0) { 70 | memo.push(char); 71 | return; 72 | } 73 | if (/[A-Z0-9\.\-]/.test(char)) { 74 | memo[memo.length - 1] = memo[memo.length - 1] + char; 75 | } else if (/[()]/.test(char)) { 76 | memo.push(char); 77 | memo.push(""); 78 | } else { 79 | memo.push(""); 80 | } 81 | } 82 | 83 | function handlePoint(arr, littleEndian) { 84 | let out = toByteOrder(littleEndian) + toUint32(1, littleEndian); 85 | out += handleDouble(arr.shift(), littleEndian); 86 | out += handleDouble(arr.shift(), littleEndian); 87 | return out; 88 | } 89 | 90 | function handleLineString(arr, littleEndian) { 91 | let out = toByteOrder(littleEndian) + toUint32(2, littleEndian); 92 | const slice = getParensSlice(arr, "LINESTRING", true); 93 | const pairs = Math.floor(slice.length / 2); 94 | out += toUint32(pairs, littleEndian); 95 | for (const token of slice) { 96 | out += handleDouble(token, littleEndian); 97 | } 98 | return out; 99 | } 100 | 101 | function handlePolygon(rings, littleEndian) { 102 | let out = toByteOrder(littleEndian) + toUint32(3, littleEndian); 103 | out += toUint32(rings.length, littleEndian); 104 | for (let ring of rings) { 105 | out += handleRing(ring, littleEndian); 106 | } 107 | return out; 108 | } 109 | 110 | function handleMultipoint(arr, littleEndian) { 111 | let out = toByteOrder(littleEndian) + toUint32(4, littleEndian); 112 | const slice = getParensSlice(arr, "MULTIPOINT", true); 113 | const pairs = slice.length / 2; 114 | out += toUint32(pairs, littleEndian); 115 | for (let i = 0; i < slice.length; i = i + 2) { 116 | out += toByteOrder(littleEndian); 117 | out += toUint32(1, littleEndian); 118 | out += toDouble(slice[i], littleEndian); 119 | out += toDouble(slice[i + 1], littleEndian); 120 | } 121 | return out; 122 | } 123 | 124 | function handleMultilinestring(arr, littleEndian) { 125 | let out = toByteOrder(littleEndian) + toUint32(5, littleEndian); 126 | const slices = getParensSlice(arr, "MULTILINESTRING", false); 127 | out += toUint32(slices.length, littleEndian); 128 | for (let slice of slices) { 129 | const pairs = Math.floor(slice.length / 2); 130 | out += 131 | toByteOrder(littleEndian) + 132 | toUint32(2, littleEndian) + 133 | toUint32(pairs, littleEndian); 134 | for (let token of slice) { 135 | out += toDouble(token, littleEndian); 136 | } 137 | } 138 | return out; 139 | } 140 | 141 | function handleMultipolygon(arr, littleEndian) { 142 | let out = toByteOrder(littleEndian) + toUint32(6, littleEndian); 143 | const polygons = getParensSlice(arr, "MULTIPOLYGON", false); 144 | out += toUint32(polygons.length, littleEndian); 145 | for (let polygon of polygons) { 146 | out += handlePolygon(polygon, littleEndian); 147 | } 148 | return out; 149 | } 150 | 151 | function handleRing(tokens, littleEndian) { 152 | let out = ""; 153 | const pairs = Math.floor(tokens.length / 2); 154 | out += toUint32(pairs, littleEndian); 155 | for (let token of tokens) { 156 | out += handleDouble(token, littleEndian); 157 | } 158 | return out; 159 | } 160 | 161 | function getParensSlice(arr, type, flatten) { 162 | let slices = []; 163 | while (arr[0] === "(") { 164 | arr.shift(); // remove ( 165 | const innerSlice = getParensSlice(arr, type, flatten); 166 | slices.push(flatten ? innerSlice.flat() : innerSlice); 167 | arr.shift(); // remove ) 168 | } 169 | let seek = arr.findIndex((token) => /^\)$/.test(token)); 170 | if (seek === -1) { 171 | throw type + " missing matching )"; 172 | } 173 | if (seek > 0) { 174 | slices = slices.concat(arr.splice(0, seek)); 175 | } 176 | return flatten ? slices.flat() : slices; 177 | } 178 | 179 | function handleDouble(token, littleEndian) { 180 | const number = parseFloat(token); 181 | if (isNaN(number)) { 182 | throw token + " is NaN"; 183 | } 184 | return toDouble(number, littleEndian); 185 | } 186 | 187 | function toByteOrder(littleEndian) { 188 | return littleEndian ? "01" : "00"; 189 | } 190 | 191 | function toUint32(number, littleEndian) { 192 | const view = new DataView(new ArrayBuffer(4)); 193 | view.setUint32(0, number, littleEndian); 194 | return asHex(view, 4); 195 | } 196 | 197 | function toDouble(number, littleEndian) { 198 | const view = new DataView(new ArrayBuffer(8)); 199 | view.setFloat64(0, number, littleEndian); 200 | return asHex(view, 8); 201 | } 202 | 203 | function getHex(i) { 204 | return ("00" + i.toString(16)).slice(-2); 205 | } 206 | 207 | function asHex(view, length) { 208 | return Array.apply(null, { length }) 209 | .map((_, i) => getHex(view.getUint8(i))) 210 | .join(""); 211 | } 212 | -------------------------------------------------------------------------------- /Scripts/contrastingColor.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api": 1, 4 | "name": "Contrasting Color", 5 | "description": "Determine whether black or white contrasts better with the given color(s) (one per line).", 6 | "author": "Sunny Walker", 7 | "icon": "color-wheel", 8 | "tags": "contrast,color,wcag" 9 | } 10 | **/ 11 | 12 | function main(input) { 13 | let lines = input.fullText.split("\n"); 14 | let o = lines.map(c => betterColor(c)); 15 | input.fullText = o.join("\n"); 16 | } 17 | 18 | // convert #rrggbb into its integer r, g, b components 19 | const hex2Rgb = hex => { 20 | const rgb = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 21 | return rgb ? { 22 | r: parseInt(rgb[1], 16), 23 | g: parseInt(rgb[2], 16), 24 | b: parseInt(rgb[3], 16) 25 | } : null; 26 | }; 27 | 28 | // calculate the luminance of a color 29 | const luminance = hex => { 30 | var rgb = hex2Rgb(hex); 31 | var a = [rgb.r, rgb.g, rgb.b].map(v => { 32 | v /= 255; 33 | return (v <= 0.03928) ? v / 12.92 : Math.pow(((v + 0.055) / 1.055), 2.4); 34 | }); 35 | return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722; 36 | }; 37 | 38 | // calculate the contrast ratio between two colors 39 | const contrast = (c1, c2) => { 40 | const l1 = luminance(c1) + .05; 41 | const l2 = luminance(c2) + .05; 42 | let ratio = l1 / l2; 43 | if (l2 > l1) { 44 | ratio = 1 / ratio; 45 | } 46 | return Math.floor(ratio * 100) / 100; 47 | }; 48 | 49 | // convert #rbg to #rrggbb 50 | const normalizeHex = hex => hex.replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i, '#$1$1$2$2$3$3'); 51 | 52 | // determine the WCAG 2.0 contrast ratio level 53 | const wcagLevel = ratio => ratio >= 7 ? 'AAA' : ratio >= 4.5 ? 'AA' : 'fail'; 54 | 55 | // determine the better contrasting color for a color 56 | const betterColor = hex => { 57 | const h = normalizeHex(hex); 58 | if (!h.match(/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i)) { 59 | return hex; 60 | } 61 | var w = contrast(h, '#ffffff'); 62 | var b = contrast(h, '#000000'); 63 | var r = Math.max(w, b); 64 | return hex + ' // contrasts best with ' + (w > b ? '#fff' : '#000') + ' with a ratio of ' + r + ' to 1; WCAG 2.0: ' + wcagLevel(r); 65 | }; 66 | -------------------------------------------------------------------------------- /Scripts/convertToMarkdownTable.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"Convert to pretty markdown table", 5 | "description":"Converts csv, tsv or markdown table into pretty markdown table format.", 6 | "author":"xshoji", 7 | "icon":"term", 8 | "tags":"csv,tsv,md,markdown" 9 | } 10 | **/ 11 | function main(input) { 12 | input.text = convertToPrettyMarkdownTableFormat(input.text); 13 | } 14 | 15 | function convertToPrettyMarkdownTableFormat(input) { 16 | const list = input.trim().replace(/^(\r?\n)+$/g, "\n").split("\n").map(v => v.replace(/^\||\|$/g, "")); 17 | const delimiter = [`|`, `\t`, `","`, `,`].find(v => list[0].split(v).length > 1); 18 | if (delimiter === `|`) { 19 | // If input text is markdown table format, removes header separator. 20 | list.splice(1, 1); 21 | } 22 | const tableElements = list.map(record => record.split(delimiter).map(v => v.trim())); 23 | const calcBytes = (character) => { 24 | let length = 0; 25 | for (let i = 0; i < character.length; i++) { 26 | const c = character.charCodeAt(i); 27 | // Multibyte handling 28 | (c >= 0x0 && c < 0x81) || (c === 0xf8f0) || (c >= 0xff61 && c < 0xffa0) || (c >= 0xf8f1 && c < 0xf8f4) ? length += 1 : length += 2; 29 | } 30 | return length; 31 | }; 32 | const columnMaxLengthList = tableElements[0].map((v, i) => i).reduce((map, columnIndex) => { 33 | let maxLength = 0; 34 | tableElements.forEach(record => maxLength < calcBytes(record[columnIndex]) ? maxLength = calcBytes(record[columnIndex]) : null); 35 | if (maxLength === 1) { 36 | // Avoids markdown header line becomes only ":" ( ":-" is correct. ). 37 | maxLength = 2; 38 | } 39 | map[columnIndex] = maxLength; 40 | return map; 41 | }, {}) 42 | const formattedTableElements = tableElements.map(record => record.map((value, columnIndex) => value + "".padEnd(columnMaxLengthList[columnIndex] - calcBytes(value), " "))); 43 | const headerValues = formattedTableElements.shift(); 44 | const tableLine = headerValues.map(v => "".padStart(calcBytes(v), "-").replace(/^./, ":")); 45 | formattedTableElements.unshift(tableLine); 46 | formattedTableElements.unshift(headerValues); 47 | return formattedTableElements.map(record => "| " + record.join(" | ") + " |").join("\n"); 48 | } 49 | -------------------------------------------------------------------------------- /Scripts/generateHashtag.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api": 1, 4 | "name": "Generate hashtag", 5 | "description": "Generate hashtag from a word or sentence", 6 | "author": "Armand Salle", 7 | "icon": "metamorphose", 8 | "tags": "hashtag,word" 9 | } 10 | **/ 11 | 12 | function capitalize(str) { 13 | return str.charAt(0).toUpperCase() + str.slice(1); 14 | } 15 | 16 | function createHashtag(str) { 17 | if (str === "") { 18 | throw new Error("Invalid text :("); 19 | } else { 20 | const result = str.replace(/\n+/gm, " "); 21 | const text = result.replace(/[^A-Za-zÀ-ÖØ-öø-ÿ0-9\s]+/gm, " "); 22 | 23 | return "#" + text.toLowerCase().split(" ").map(capitalize).join(""); 24 | } 25 | } 26 | 27 | function main(input) { 28 | try { 29 | const generatedHashatag = createHashtag(input.text); 30 | input.text = generatedHashatag; 31 | input.postInfo("Nice!"); 32 | } catch (e) { 33 | input.postError(e.message); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Scripts/jsToPhp.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"JS To PHP", 5 | "description":"Convert JS Object or Array to PHP.", 6 | "author":"jtolj", 7 | "icon":"elephant", 8 | "tags":"js,php,convert" 9 | } 10 | **/ 11 | 12 | function main(state) { 13 | const js = state.text.replace(/\n\n\/\/ Result:[\s\S]*$/, ''); 14 | let output = ''; 15 | try { 16 | const result = new Function(`return ${js}`)(); 17 | output = convert(result) + ';'; 18 | } catch (error) { 19 | state.postError(error.message); 20 | } 21 | state.text = js + "\n\n// Result:\n\n" + output; 22 | } 23 | 24 | const toPHP = function (value, indentation) { 25 | switch (typeof value) { 26 | case 'undefined': 27 | value = null; 28 | break; 29 | case 'object': 30 | if(value !== null) { 31 | value = convert(value, indentation + 1); 32 | } 33 | break; 34 | case 'string': 35 | value = value.replace(/"/g, '\\"'); 36 | value = `"${value}"`; 37 | break; 38 | } 39 | 40 | return value; 41 | }; 42 | 43 | const convert = function (result, indentation = 1) { 44 | const isArray = Array.isArray(result); 45 | let str = Object.keys(result).reduce((acc, key) => { 46 | const value = toPHP(result[key], indentation); 47 | acc += ' '.repeat(indentation * 4); 48 | acc += isArray ? value : `'${key}' => ${value}`; 49 | acc += ',\n'; 50 | return acc; 51 | }, ''); 52 | const endingIndentation = ' '.repeat((indentation - 1) * 4); 53 | return `[\n${str}${endingIndentation}]`; 54 | }; 55 | -------------------------------------------------------------------------------- /Scripts/listToHTMLList.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api": 1, 4 | "name": "List to HTML list", 5 | "description": "Turns comma separated list to HTML Lists", 6 | "author": "Christian Heilmann", 7 | "icon": "table", 8 | "tags": "HTML,Lists" 9 | } 10 | **/ 11 | 12 | const listToHTML = (str) => { 13 | if (str.indexOf('
    ') === -1) { 14 | let chunks = str.split(','); 15 | let out = `
      16 |
    • ${chunks.join("
    • \n
    • ")}`; 17 | return out + "
    • \n
    "; 18 | } else { 19 | let chunks = str.split('
  • '); 20 | let out = []; 21 | chunks.forEach(c => { 22 | out.push(c.match(/[^<]*/)); 23 | }); 24 | out.shift(); 25 | return out.join(','); 26 | } 27 | } 28 | function main(input) { 29 | input.text = listToHTML(input.text); 30 | } 31 | -------------------------------------------------------------------------------- /Scripts/rgb2hex.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"RGB to Hex", 5 | "description":"Convert color in RGB to hexadecimal", 6 | "author":"luisfontes19", 7 | "icon":"color-wheel", 8 | "tags":"rgb,hex,convert,color" 9 | } 10 | **/ 11 | 12 | function main(state) { 13 | const rgb = state.text; 14 | const rgbArray = rgb.includes(",") ? rgb.split(",") : rgb.split(" "); 15 | 16 | if (rgbArray.length !== 3) return state.postError("Invalid RGB format"); 17 | 18 | let hex = "#"; 19 | 20 | try { 21 | rgbArray.forEach(c => { 22 | hex += parseInt(c).toString(16); 23 | }); 24 | } 25 | catch (error) { 26 | return state.postError("Invalid RGB value");; 27 | } 28 | 29 | state.text = hex.toUpperCase(); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Scripts/toUnicode.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | { 4 | "api":1, 5 | "name":"To Unicode Escaped String", 6 | "description":"Converts a UTF8 string to unicode escape chars(js format)", 7 | "author":"luisfontes19", 8 | "icon":"broom", 9 | "tags":"string,unicode,convert,escape" 10 | } 11 | **/ 12 | 13 | function main(state) { 14 | state.text = toUnicode(state.text); 15 | } 16 | 17 | function toUnicode(str) { 18 | return [...str].map(c => { 19 | let hex = c.charCodeAt(0).toString(16); 20 | if (hex.length == 2) hex = "00" + hex; 21 | return ("\\u" + hex).slice(-7); 22 | }).join(""); 23 | } 24 | 25 | -------------------------------------------------------------------------------- /Scripts/toggleCamelHyphen.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api": 1, 4 | "name": "Toggle Camel and Hyphen", 5 | "description": "Turns camelCase to camel-case and vice versa", 6 | "author": "Christian Heilmann", 7 | "icon": "table", 8 | "tags": "camelcase,hyphencase,syntax,codestandards" 9 | } 10 | **/ 11 | 12 | const toggleCamelHyphen = (str) => { 13 | let chunks = str.split(/\n/); 14 | chunks.forEach((c,k) => { 15 | if (c.indexOf('-') !== -1) { 16 | chunks[k] = c.replace(/\W+(.)/g, (x, chr) => { 17 | return chr.toUpperCase(); 18 | }); 19 | } else { 20 | chunks[k] = c 21 | .replace(/[^a-zA-Z0-9]+/g, '-') 22 | .replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2') 23 | .replace(/([a-z])([A-Z])/g, '$1-$2') 24 | .replace(/([0-9])([^0-9])/g, '$1-$2') 25 | .replace(/([^0-9])([0-9])/g, '$1-$2') 26 | .replace(/-+/g, '-') 27 | .toLowerCase(); 28 | } 29 | }); 30 | return chunks.join("\n"); 31 | } 32 | 33 | function main(input) { 34 | input.text = toggleCamelHyphen(input.text); 35 | } 36 | -------------------------------------------------------------------------------- /Scripts/tsvToJson.js: -------------------------------------------------------------------------------- 1 | /** 2 | { 3 | "api":1, 4 | "name":"TSV to JSON", 5 | "description":"Converts TSV to JSON", 6 | "author":"Quddus George", 7 | "icon":"table", 8 | "tags":"tab, tsv, json, table" 9 | } 10 | **/ 11 | 12 | //credit for tsv function: https://gist.github.com/iwek/7154706#gistcomment-3369283 13 | 14 | function main(state) { 15 | function tsvJSON(tsv) { 16 | return tsv 17 | .split("\n") 18 | .map((line) => line.split("\t")) 19 | .reduce((a, c, i, d) => { 20 | if (i) { 21 | const item = Object.fromEntries(c.map((val, j) => [d[0][j], val])); 22 | return a ? [...a, item] : [item]; 23 | } 24 | }, []); 25 | } 26 | let json = JSON.stringify(tsvJSON(state.fullText)); 27 | state.fullText = json; 28 | } 29 | --------------------------------------------------------------------------------