├── .editorconfig ├── .eslintrc.json ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yaml │ └── config.yml ├── pull_request_template.md └── workflows │ └── ci.yml ├── .gitignore ├── .stylelintrc.json ├── .vscode └── settings.json ├── ISSUE_TEMPLATE.md ├── LICENSE ├── README.md ├── etc ├── App Store Screenshot.png ├── app_ios │ ├── .gitignore │ ├── README.md │ ├── gulpfile.js │ ├── output │ │ └── .gitignore │ ├── package-lock.json │ ├── package.json │ └── src │ │ ├── css │ │ ├── _main.css │ │ └── reset.css │ │ ├── html │ │ └── meta.html │ │ ├── img │ │ ├── icon.png │ │ └── logo.svg │ │ ├── index.html │ │ └── js │ │ └── scripts.js ├── assets.sketch ├── popover-original.png ├── popover.png ├── screenshot.png ├── settings-original.png ├── settings.png ├── ui01-original.png ├── ui01.png └── uilayout.sketch ├── extension ├── Shared.swift ├── Userscripts Extension │ ├── Functions.swift │ ├── Info.plist │ ├── Resources │ │ ├── _locales │ │ │ └── en │ │ │ │ └── messages.json │ │ ├── background.js │ │ ├── content.js │ │ ├── images │ │ │ ├── icon-128.png │ │ │ ├── icon-256.png │ │ │ ├── icon-48.png │ │ │ ├── icon-512.png │ │ │ ├── icon-96.png │ │ │ ├── toolbar-icon-16.png │ │ │ └── toolbar-icon-32.png │ │ ├── manifest.json │ │ ├── page.html │ │ ├── page.js │ │ ├── popup.html │ │ └── popup.js │ ├── SafariWebExtensionHandler.swift │ └── Userscripts Extension.entitlements ├── Userscripts-iOS Extension │ ├── Info.plist │ └── Userscripts-iOS Extension.entitlements ├── Userscripts-iOS │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── 1024.png │ │ │ ├── 120-1.png │ │ │ ├── 120.png │ │ │ ├── 152.png │ │ │ ├── 167.png │ │ │ ├── 180.png │ │ │ ├── 20.png │ │ │ ├── 29.png │ │ │ ├── 40-1.png │ │ │ ├── 40-2.png │ │ │ ├── 40.png │ │ │ ├── 58-1.png │ │ │ ├── 58.png │ │ │ ├── 60.png │ │ │ ├── 76.png │ │ │ ├── 80-1.png │ │ │ ├── 80.png │ │ │ ├── 87.png │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── LargeIcon.imageset │ │ │ └── Contents.json │ │ └── LaunchScreen.imageset │ │ │ ├── Contents.json │ │ │ └── LaunchScreen.pdf │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ ├── Main.html │ │ └── Main.storyboard │ ├── Info.plist │ ├── Resources │ │ └── Icon.png │ ├── SceneDelegate.swift │ ├── Userscripts-iOS.entitlements │ └── ViewController.swift ├── Userscripts.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ ├── Userscripts Extension.xcscheme │ │ └── Userscripts.xcscheme ├── Userscripts │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── 1024.png │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 256-1.png │ │ │ ├── 256.png │ │ │ ├── 32-1.png │ │ │ ├── 32.png │ │ │ ├── 512-1.png │ │ │ ├── 512.png │ │ │ ├── 64.png │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ └── Main.storyboard │ ├── Info.plist │ ├── Userscripts.entitlements │ └── ViewController.swift └── UserscriptsTests │ └── UserscriptsTests.swift ├── gulpfile.js ├── index.html ├── package-lock.json ├── package.json ├── public ├── page │ ├── codemirror │ │ ├── addon │ │ │ ├── active-line.js │ │ │ ├── closebrackets.js │ │ │ ├── comment.js │ │ │ ├── continuecomment.js │ │ │ ├── javascript-hint.js │ │ │ ├── show-hint.css │ │ │ └── show-hint.js │ │ ├── codemirror.css │ │ ├── codemirror.js │ │ ├── jshint.js │ │ └── mode │ │ │ └── javascript.js │ ├── css │ │ ├── cm.css │ │ └── global.css │ └── index.html ├── popup │ ├── css │ │ └── global.css │ └── index.html └── shared │ ├── reset.css │ └── variables.css ├── rollup.config.js └── src ├── page ├── App.svelte ├── Components │ ├── Editor │ │ ├── CodeMirror.svelte │ │ ├── Editor.svelte │ │ └── EditorSearch.svelte │ ├── Notification.svelte │ ├── Settings.svelte │ └── Sidebar │ │ ├── Sidebar.svelte │ │ ├── SidebarFilter.svelte │ │ └── SidebarItem.svelte ├── codemirror.js ├── main.js ├── store.js └── utils.js ├── popup ├── App.svelte ├── Components │ ├── PopupItem.svelte │ ├── View.svelte │ └── Views │ │ ├── AllItemsView.svelte │ │ ├── InstallView.svelte │ │ └── UpdateView.svelte └── main.js └── shared ├── Components ├── Dropdown.svelte ├── IconButton.svelte ├── Loader.svelte ├── Tag.svelte └── Toggle.svelte ├── dev.js ├── img ├── icon-arrow-down.svg ├── icon-arrow-left.svg ├── icon-arrow-up.svg ├── icon-check.svg ├── icon-clear.svg ├── icon-close.svg ├── icon-download.svg ├── icon-edit.svg ├── icon-error.svg ├── icon-info.svg ├── icon-loader.svg ├── icon-loupe.svg ├── icon-open.svg ├── icon-plus.svg ├── icon-power.svg ├── icon-refresh.svg ├── icon-settings.svg ├── icon-sort.svg ├── icon-sync.svg ├── icon-trash.svg ├── icon-update.svg ├── icon-warn.svg └── logo.svg └── settings.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | max_line_length = 80 10 | trim_trailing_whitespace = true 11 | 12 | [package.json] 13 | indent_size = 2 14 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true, 5 | "node": true 6 | }, 7 | "extends": ["eslint:recommended"], 8 | "parserOptions": { 9 | "ecmaVersion": 12, 10 | "sourceType": "module" 11 | }, 12 | "plugins": ["svelte3"], 13 | "overrides": [ 14 | { 15 | "files": ["*.svelte"], 16 | "processor": "svelte3/svelte3" 17 | } 18 | ], 19 | "globals": { 20 | "_browser": "writable", 21 | "browser": "readonly" 22 | }, 23 | "ignorePatterns": [ 24 | "node_modules/", 25 | "public/*/build/", 26 | "codemirror/", 27 | "etc/", 28 | "extension/Userscripts Extension/Resources/page.js", 29 | "extension/Userscripts Extension/Resources/popup.js" 30 | ], 31 | "rules": { 32 | "array-bracket-spacing": ["error", "never"], 33 | "arrow-parens": ["error", "as-needed"], 34 | "brace-style": ["error", "1tbs", {"allowSingleLine": false}], 35 | "comma-dangle": ["error", "never"], 36 | "comma-spacing": ["error", { 37 | "before": false, 38 | "after": true 39 | }], 40 | "curly": ["error", "multi-line"], 41 | "dot-notation": "error", 42 | "eqeqeq": ["error", "smart"], 43 | "indent": ["error", 4, {"SwitchCase": 1}], 44 | "linebreak-style": ["error","unix"], 45 | "key-spacing": ["error", {"afterColon": true}], 46 | "keyword-spacing": ["error", { 47 | "after": true, 48 | "before": true 49 | }], 50 | "no-bitwise": "error", 51 | "no-mixed-operators": "error", 52 | "no-multi-spaces": "error", 53 | "no-multi-str": "error", 54 | "no-multiple-empty-lines": ["error", {"max": 1, "maxBOF": 2}], // https://github.com/sveltejs/eslint-plugin-svelte3/issues/41 55 | "no-tabs": "error", 56 | "no-useless-concat": "error", 57 | "no-unused-vars": ["error", {"args": "none"}], 58 | "no-use-before-define": ["error", { 59 | "classes": true, 60 | "functions": false, 61 | "variables": true 62 | }], 63 | "no-var": "error", 64 | "nonblock-statement-body-position": ["error", "below", { 65 | "overrides": {"if": "any"} 66 | }], 67 | "object-curly-spacing": ["error", "never"], 68 | "operator-assignment": ["error", "always"], 69 | "operator-linebreak": ["error", "before"], 70 | "prefer-const": "error", 71 | "prefer-template": "error", 72 | "quotes": ["error", "double"], 73 | "semi": ["error", "always", {"omitLastInOneLineBlock": true}], 74 | "space-before-blocks": "error", 75 | "space-before-function-paren": ["error", { 76 | "anonymous": "never", 77 | "named": "never", 78 | "asyncArrow": "always" 79 | }], 80 | "space-in-parens": ["error", "never"], 81 | "template-curly-spacing": ["error", "never"], 82 | "space-infix-ops": "error", 83 | "jsx-a11y/a11y-missing-attribute": "off" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | public/page/codemirror/** linguist-vendored 2 | extension/Userscripts-iOS/Base.lproj/Main.html linguist-generated 3 | /index.html linguist-generated 4 | **/Resources/page.html linguist-generated 5 | **/Resources/page.js linguist-generated 6 | **/Resources/popup.html linguist-generated 7 | **/Resources/popup.js linguist-generated 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yaml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Report a bug in the app or project itself 3 | labels: bug, needs-triage 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to fill out this bug report! 9 | - type: checkboxes 10 | attributes: 11 | label: Is there an existing issue for this? 12 | description: Please search to see if an issue already exists for the bug you encountered. 13 | options: 14 | - label: I have searched the existing issues 15 | required: true 16 | - type: checkboxes 17 | attributes: 18 | label: Are you sure this is NOT an issue about external scripts not working? 19 | description: Please replace and choose the correct type for issues about third-party user scripts. 20 | options: 21 | - label: I am sure it is NOT a user script issue 22 | required: true 23 | - type: textarea 24 | attributes: 25 | label: Current Behavior 26 | description: A concise description of what you're experiencing. 27 | validations: 28 | required: false 29 | - type: textarea 30 | attributes: 31 | label: Expected Behavior 32 | description: A concise description of what you expected to happen. 33 | validations: 34 | required: false 35 | - type: textarea 36 | attributes: 37 | label: Steps To Reproduce 38 | description: Steps to reproduce the behavior. 39 | placeholder: | 40 | 1. In this environment... 41 | 2. With this config... 42 | 3. Run '...' 43 | 4. See error... 44 | validations: 45 | required: false 46 | - type: checkboxes 47 | id: platform 48 | attributes: 49 | label: OS (Device) 50 | description: In which operating systems did you test this issue? 51 | options: 52 | - label: macOS (Mac) 53 | - label: iOS (iPhone) 54 | - label: iPadOS (iPad) 55 | - type: textarea 56 | attributes: 57 | label: Environment 58 | description: | 59 | examples: 60 | - **OS/Safari version**: macOS 13.0.1 (22A400), Safari 16.1 (18614.2.9.1.12) 61 | - **Userscripts version**: 4.3.3 (65) 62 | - **OS/Safari version**: iOS 16.1.1 63 | - **Userscripts version**: 1.3.3 (38) 64 | - **OS/Safari version**: iPadOS 16.1.1 65 | - **Userscripts version**: 1.3.3 (38) 66 | value: | 67 | - OS/Safari version: 68 | - Userscripts version: 69 | render: markdown 70 | validations: 71 | required: false 72 | - type: textarea 73 | id: logs 74 | attributes: 75 | label: Relevant log output 76 | description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. 77 | render: shell 78 | - type: textarea 79 | attributes: 80 | label: Anything else? 81 | description: | 82 | Links? References? Anything that will give us more context about the issue you are encountering! 83 | 84 | Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. 85 | validations: 86 | required: false 87 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Questions & support 4 | url: https://matrix.to/#/#xxx:matrix.org 5 | about: Please ask and answer questions here. 6 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Welcome to Userscripts contributing guide 2 | 3 | ### Please note: This project is currently temporarily suspended from receiving direct code contributions. 4 | 5 | Please do not submit any PR (it will be closed directly). 6 | 7 | If you have any ideas and suggestions, please submit them to us through [`Issues`](https://github.com/quoid/userscripts/issues) or [`Discussions`](https://github.com/quoid/userscripts/discussions). 8 | 9 | We apologize for the inconvenience and thank you for your understanding. 10 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | - pull_request 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | - name: Install modules 10 | run: yarn 11 | - name: Run ESLint 12 | run: yarn run eslint . --ext .js,.jsx,.ts,.tsx 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS generated files 2 | .DS_Store 3 | .DS_Store? 4 | ._* 5 | .Spotlight-V100 6 | .Trashes 7 | ehthumbs.db 8 | Thumbs.db 9 | 10 | /node_modules/ 11 | /public/*/build/ 12 | /temp/ 13 | 14 | # xcode 15 | xcuserdata/ 16 | *.xcuserstate 17 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "stylelint-config-standard", 4 | "stylelint-config-html/svelte" 5 | ], 6 | "ignoreFiles": ["**/codemirror/*", "**/reset.css", "**/*.js"], 7 | "rules": { 8 | "alpha-value-notation": "number", 9 | "comment-empty-line-before": ["always",{ 10 | "ignore": ["after-comment", "stylelint-commands"] 11 | }], 12 | "custom-property-empty-line-before": null, 13 | "indentation": 4, 14 | "max-empty-lines": 1, 15 | "no-descending-specificity": null, 16 | "property-no-vendor-prefix": null, 17 | "selector-class-pattern": null, 18 | "selector-pseudo-class-no-unknown": [true, { 19 | "ignorePseudoClasses": ["global"] 20 | }] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | ".htmlhintrc": "json" 4 | }, 5 | "eslint.validate": [ 6 | "svelte" 7 | ], 8 | "stylelint.validate": [ 9 | "css", 10 | "postcss", 11 | "svelte" 12 | ], 13 | "svelte.plugin.svelte.compilerWarnings": { 14 | "a11y-missing-attribute": "error" 15 | }, 16 | "svelte.plugin.svelte.rename.enable": false 17 | } 18 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 16 | 17 | _System Information:_ 18 | > macOS or iOS version: 19 | > Userscripts version: 20 | > Safari version: 21 | > Is this issue related to script injection? 22 | > Did the test script (pasted above) successfully run on your machine? 23 | -------------------------------------------------------------------------------- /etc/App Store Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/etc/App Store Screenshot.png -------------------------------------------------------------------------------- /etc/app_ios/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | -------------------------------------------------------------------------------- /etc/app_ios/README.md: -------------------------------------------------------------------------------- 1 | # static-website-builder 2 | 3 | ### Requirements: 4 | - [node](https://nodejs.org/en/) 5 | - [gulp](https://gulpjs.com) 6 | 7 | ### Installation: 8 | 1. Have the requirements installed on your local machine 9 | 1. Clone this repository locally 10 | 1. Navigate to where you cloned this repository 11 | 1. Run `npm install` 12 | 13 | ### Usage: 14 | - `npm run server` starts the dev server 15 | - **Only edit files with the `src` folder** 16 | - Navigate to `http://localhost:8888` to view output 17 | - LiveReload *is* enabled 18 | - `npm run build` builds the website to the `output` folder 19 | - **Do *not* rely on the server command to build the website** 20 | - `npm run clean` cleans the output folder 21 | - **The clean task is built into *both* the server and build commands**, you won't need to often run this command 22 | 23 | ### Features 24 | - autoprefixing for css 25 | - @import enabled for css 26 | - minifying of css content 27 | - inlining and compression of all files (css, js, svgs, etc...) 28 | - easy deployment to Netlify 29 | - build command = `npm run build` 30 | - publish directory = `output/` 31 | -------------------------------------------------------------------------------- /etc/app_ios/gulpfile.js: -------------------------------------------------------------------------------- 1 | const { dest, parallel, series, src, watch } = require("gulp"); 2 | var assets = require("postcss-assets"); 3 | const atImport = require("postcss-import"); 4 | const autoprefixer = require("autoprefixer"); 5 | const connect = require("gulp-connect"); 6 | const del = require("del"); 7 | const htmlmin = require("gulp-htmlmin"); 8 | const inline = require("gulp-inline-source"); 9 | const nano = require("cssnano"); 10 | const nunjucksRender = require("gulp-nunjucks-render"); 11 | const postcss = require("gulp-postcss"); 12 | const sourcemaps = require("gulp-sourcemaps"); 13 | 14 | const sourcePath = "./src"; 15 | const outputPath = "./output"; 16 | 17 | function clean() { 18 | // delete all files at the output path, except .gitignore 19 | const paths = [ 20 | `${outputPath}/**/*` 21 | ]; 22 | return del(paths); 23 | } 24 | 25 | function css() { 26 | // NODE_ENV production is set when using the "npm run build" command 27 | // if NODE_ENV === production, no sourcemaps and minify the css 28 | if (process.env.NODE_ENV != "production") { 29 | return src(`${sourcePath}/css/_main.css`) 30 | .pipe(sourcemaps.init()) 31 | .pipe(postcss([ 32 | atImport(), 33 | assets(), 34 | autoprefixer({overrideBrowserslist: ["defaults"]}) 35 | ])) 36 | .pipe(sourcemaps.write()) 37 | .pipe(dest(outputPath)); 38 | } else { 39 | return src(`${sourcePath}/css/_main.css`) 40 | .pipe(postcss([ 41 | atImport(), 42 | assets(), 43 | autoprefixer({overrideBrowserslist: ["defaults"]}), 44 | nano() 45 | ])) 46 | .pipe(dest(outputPath)); 47 | } 48 | } 49 | 50 | function js() { 51 | // copy over the javascript files, as is 52 | return src(`${sourcePath}/js/**/*.js`) 53 | .pipe(dest(`${outputPath}/js/`)); 54 | } 55 | 56 | function html() { 57 | // set a var for whether or not we are in dev mode 58 | // development var will be false when running the npm run build command 59 | // during development we will NOT inline css or js files 60 | // that way we can properly debug 61 | // on builds everything will inlined to a single html file 62 | var development = true; 63 | var ignore = ["css", "js"]; 64 | if (process.env.NODE_ENV === "production") { 65 | development = false; 66 | ignore = []; 67 | } 68 | return src(`${sourcePath}/index.html`) 69 | .pipe(nunjucksRender({ 70 | // pass custom data to nunjucks for use throughout template(s) 71 | // can use custom data from package.json (ex. title) 72 | data: { 73 | development: development, 74 | title: process.env.npm_package_websiteTitle 75 | }, 76 | path: `${sourcePath}/html` 77 | })) 78 | .pipe(inline({ 79 | compress: true, 80 | ignore: ignore 81 | })) 82 | .pipe(htmlmin({ 83 | collapseWhitespace: true, 84 | minifyJS: false, 85 | removeComments: true 86 | })) 87 | .pipe(dest(outputPath)) 88 | .pipe(connect.reload()); 89 | } 90 | 91 | function complete() { 92 | // delete all the files at the output path except the index.html file 93 | // this is a separate function than the clean function above because 94 | // on the intial clean we want to remove EVERYTHING 95 | // on the build complete, we want to leave the index.html 96 | const paths = [ 97 | `${outputPath}/**/*`, 98 | `!${outputPath}/index.html` 99 | ]; 100 | return del(paths); 101 | } 102 | 103 | function server(cb) { 104 | connect.server({ 105 | root: outputPath, 106 | livereload: true, 107 | port: 8888 108 | }); 109 | cb(); 110 | } 111 | 112 | function watcher(cb) { 113 | const paths = [ 114 | `${sourcePath}/**/*.html`, 115 | `${sourcePath}/**/*.css`, 116 | `${sourcePath}/**/*.js`, 117 | `${sourcePath}/**/*.svg` 118 | ]; 119 | watch(paths, series(css, js, html)); 120 | cb(); 121 | } 122 | 123 | exports.build = series(clean, css, js, html, complete); 124 | exports.clean = clean; 125 | exports.server = series(clean, css, js, html, parallel(server, watcher)); 126 | -------------------------------------------------------------------------------- /etc/app_ios/output/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /etc/app_ios/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "static-website-builder", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "websiteTitle": "My Website", 7 | "scripts": { 8 | "build": "NODE_ENV=production gulp build", 9 | "clean": "gulp clean", 10 | "server": "gulp server", 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "devDependencies": { 17 | "autoprefixer": "^9.7.1", 18 | "cssnano": "^4.1.10", 19 | "del": "^5.1.0", 20 | "gulp": "^4.0.2", 21 | "gulp-connect": "^5.7.0", 22 | "gulp-htmlmin": "^5.0.1", 23 | "gulp-inline-source": "^4.0.0", 24 | "gulp-nunjucks-render": "^2.2.3", 25 | "gulp-postcss": "^8.0.0", 26 | "gulp-sourcemaps": "^2.6.5", 27 | "postcss-assets": "^6.0.0", 28 | "postcss-import": "^12.0.1" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /etc/app_ios/src/css/_main.css: -------------------------------------------------------------------------------- 1 | @import "./reset.css"; 2 | 3 | :root { 4 | --border-radius: 0.188rem; 5 | --box-shadow: 0 0.5rem 1rem 0 rgba(0, 0, 0, 0.25); 6 | --color-bg-primary: #323639; 7 | --color-bg-secondary: #2f3337; 8 | --color-script-highlighted: rgba(116, 178, 235, 0.1); 9 | --color-black: #1d2023; 10 | --color-blue: #74b1eb; 11 | --color-green: #60f36c; 12 | --color-grey: rgba(255, 255, 255, 0.15); 13 | --color-red: #ff453a; 14 | --color-yellow: #e4f360; 15 | --letter-spacing-large: -0.031rem; 16 | --letter-spacing-default: -0.029rem; 17 | --letter-spacing-medium: -0.018rem; 18 | --letter-spacing-small: -0.008rem; 19 | --opacity-disabled: 0.3; 20 | --text-color-primary: rgb(255, 255, 255); 21 | --text-color-secondary: rgba(255, 255, 255, 0.65); 22 | --text-color-disabled: rgba(255, 255, 255, 0.4); 23 | --font-family: system-ui, -apple-system, "Helvetica Neue", "Helvetica", sans-serif; 24 | --text-default: 1rem/1.5rem var(--font-family); 25 | --text-large: 1.25rem/1.5rem var(--font-family); 26 | --text-medium: 0.875rem/1.313rem var(--font-family); 27 | --text-small: 0.719rem/1rem var(--font-family); 28 | 29 | /* editor variables */ 30 | --editor-font: monaco, monospace; 31 | --editor-font-size: 14px; 32 | --editor-line-height: 24px; 33 | --editor-invisible: rgba(255, 255, 255, 0.15); 34 | --editor-active-line: var(--color-bg-secondary); 35 | --editor-selected-bg: rgba(116, 178, 235, 0.35); 36 | --editor-matched-highlight: rgba(116, 178, 235, 0.2); 37 | --editor-search-highlight: rgba(255, 166, 0, 0.3); 38 | --editor-number: #77e26a; 39 | --editor-comment: rgba(255, 255, 255, 0.35); 40 | --editor-def: #efc371; 41 | --editor-default: #cdcfd1; 42 | --editor-keyword: #96c3ed; 43 | --editor-atom: #59ebf5; 44 | --editor-operator: #8c99a7; 45 | --editor-property: #e86c8a; 46 | --editor-string: #f5eea2; 47 | --editor-string-2: #cdabff; 48 | --editor-error: var(--color-red); 49 | --editor-cursor: #e3e7eb; 50 | --editor-matching-bracket-color: #fff; 51 | --editor-matching-bracket-border: var(--editor-number); 52 | --editor-non-matching-bracket: var(--editor-error); 53 | } 54 | 55 | html { 56 | font-size: 100%; 57 | height: 100vh; 58 | overflow: hidden; 59 | } 60 | 61 | body { 62 | background-color: var(--color-bg-secondary); 63 | color: var(--text-color-primary); 64 | font: var(--text-medium); 65 | height: 100%; 66 | letter-spacing: var(--letter-spacing-medium); 67 | position: relative; 68 | text-rendering: optimizeLegibility; 69 | -webkit-font-smoothing: antialiased; 70 | } 71 | 72 | main { 73 | align-items: center; 74 | display: flex; 75 | flex-direction: column; 76 | height: 100%; 77 | justify-content: center; 78 | padding: 0 1rem; 79 | text-align: center; 80 | } 81 | 82 | a { 83 | color: var(--color-blue); 84 | } 85 | 86 | .icon { 87 | height: 8.0rem; 88 | width: 8.0rem; 89 | } 90 | 91 | .logo { 92 | align-items: flex-end; 93 | display: flex; 94 | margin: 1rem 0; 95 | } 96 | 97 | .logo svg { 98 | height: 1.5rem; 99 | } 100 | 101 | .logo > span { 102 | margin-left: 0.5rem; 103 | } 104 | 105 | .logo span, 106 | .current { 107 | color: var(--text-color-disabled); 108 | font: var(--text-small); 109 | font-weight: bold; 110 | letter-spacing: var(--letter-spacing-small); 111 | } 112 | 113 | button { 114 | background-color: var(--color-blue); 115 | border: none; 116 | border-radius: var(--border-radius); 117 | color: var(--color-bg-secondary); 118 | font: var(--text-default); 119 | font-weight: 500; 120 | letter-spacing: var(--letter-spacing-default); 121 | margin: 2rem 0 1rem 0; 122 | padding: 0.5rem 1rem; 123 | } 124 | 125 | button:active { 126 | background-color: #6296c7; 127 | } 128 | 129 | #directory { 130 | color: var(--editor-default); 131 | font-family: var(--editor-font); 132 | font-size: 0.875rem; 133 | font-weight: 400; 134 | word-break: break-all; 135 | } 136 | 137 | @media screen and (max-height: 400px) { 138 | html { 139 | font-size: 80%; 140 | } 141 | 142 | p { 143 | font: var(--text-large); 144 | letter-spacing: var(--letter-spacing-large); 145 | } 146 | } 147 | 148 | @media screen and (min-height: 700px) and (min-width: 600px) { 149 | html { 150 | font-size: 150%; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /etc/app_ios/src/css/reset.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::before, 3 | *::after { 4 | box-sizing: border-box; 5 | } 6 | 7 | body, 8 | p { 9 | margin: 0; 10 | } 11 | 12 | ul { 13 | list-style: none; 14 | margin: 0; 15 | padding: 0; 16 | } 17 | 18 | button, 19 | input, 20 | select, 21 | textarea { 22 | font-family: inherit; 23 | font-size: 100%; 24 | line-height: inherit; 25 | margin: 0; 26 | } 27 | 28 | button, 29 | input[type="button"], 30 | input[type="reset"], 31 | input[type="submit"] { 32 | -webkit-appearance: button; 33 | } 34 | 35 | ::-webkit-search-decoration, 36 | ::-webkit-search-cancel-button, 37 | ::-webkit-search-results-button, 38 | ::-webkit-search-results-decoration { 39 | -webkit-appearance: none; 40 | } 41 | 42 | img { 43 | border-style: none; 44 | } 45 | -------------------------------------------------------------------------------- /etc/app_ios/src/html/meta.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ title }} 5 | -------------------------------------------------------------------------------- /etc/app_ios/src/img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/etc/app_ios/src/img/icon.png -------------------------------------------------------------------------------- /etc/app_ios/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% include "meta.html" %} 5 | {%- if development %} 6 | 7 | {% else %} 8 | 9 | {% endif -%} 10 | 11 | 12 |
13 | Userscripts App Icon 14 | 18 |

You can turn on the Userscripts iOS Safari extension in Settings. Read the docs.

19 | 20 |
CURRENT DIRECTORY:
21 |
init
22 |
23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /etc/app_ios/src/js/scripts.js: -------------------------------------------------------------------------------- 1 | const directory = document.querySelector("#directory"); 2 | const button = document.querySelector("#set_directory"); 3 | const version = document.querySelector("#version"); 4 | const build = document.querySelector("#build"); 5 | const setDirectory = () => webkit.messageHandlers.controller.postMessage("SET_READ_LOCATION"); 6 | function printDirectory(location) { 7 | directory.innerText = location; 8 | } 9 | function printVersion(v, b) { 10 | version.innerText = v; 11 | build.innerText = b; 12 | } 13 | button.addEventListener("click", setDirectory); 14 | -------------------------------------------------------------------------------- /etc/assets.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/etc/assets.sketch -------------------------------------------------------------------------------- /etc/popover-original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/etc/popover-original.png -------------------------------------------------------------------------------- /etc/popover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/etc/popover.png -------------------------------------------------------------------------------- /etc/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/etc/screenshot.png -------------------------------------------------------------------------------- /etc/settings-original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/etc/settings-original.png -------------------------------------------------------------------------------- /etc/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/etc/settings.png -------------------------------------------------------------------------------- /etc/ui01-original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/etc/ui01-original.png -------------------------------------------------------------------------------- /etc/ui01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/etc/ui01.png -------------------------------------------------------------------------------- /etc/uilayout.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/etc/uilayout.sketch -------------------------------------------------------------------------------- /extension/Shared.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SafariServices 3 | import os 4 | 5 | struct SharedDefaults { 6 | // https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_application-groups 7 | #if os(iOS) 8 | static let suiteName = "group.com.userscripts.ios" 9 | static let keyName = "iosReadLocation" 10 | #elseif os(macOS) 11 | static let suiteName = "J74Q8V8V8N.com.userscripts.macos" 12 | static let keyName = "hostSelectedSaveLocation" 13 | #endif 14 | } 15 | 16 | func err(_ message: String) { 17 | let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "general") 18 | os_log("%{public}@", log: log, type: .error, "Error: \(message)") 19 | } 20 | 21 | func logText(_ message: String) { 22 | // create helper log func to easily disable logging 23 | // NSLog(message) 24 | let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "general") 25 | os_log("%{public}@", log: log, type: .default, message) 26 | } 27 | 28 | func getDocumentsDirectory() -> URL { 29 | let fm = FileManager.default 30 | let paths = fm.urls(for: .documentDirectory, in: .userDomainMask) 31 | let documentsDirectory = paths[0] 32 | return documentsDirectory 33 | } 34 | 35 | func directoryExists(path: String) -> Bool { 36 | var isDirectory = ObjCBool(true) 37 | let exists = FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory) 38 | let inTrash = path.contains(".Trash") ? false : true 39 | return exists && inTrash && isDirectory.boolValue 40 | } 41 | 42 | func getPlatform() -> String { 43 | var platform:String 44 | #if os(iOS) 45 | if UIDevice.current.userInterfaceIdiom == .pad { 46 | platform = "ipados" 47 | } 48 | else { 49 | platform = "ios" 50 | } 51 | #elseif os(macOS) 52 | platform = "macos" 53 | #endif 54 | return platform 55 | } 56 | 57 | func saveBookmark(url: URL, isShared: Bool, keyName: String, isSecure: Bool) -> Bool { 58 | #if os(iOS) 59 | let options:URL.BookmarkCreationOptions = [] 60 | #elseif os(macOS) 61 | let options:URL.BookmarkCreationOptions = isSecure ? [.withSecurityScope] : [] 62 | #endif 63 | do { 64 | let bookmark = try url.bookmarkData( 65 | options: options, 66 | includingResourceValuesForKeys: nil, 67 | relativeTo: nil 68 | ) 69 | #if os(iOS) 70 | UserDefaults(suiteName: SharedDefaults.suiteName)?.set(bookmark, forKey: keyName) 71 | #elseif os(macOS) 72 | if isShared { 73 | UserDefaults(suiteName: SharedDefaults.suiteName)?.set(bookmark, forKey: keyName) 74 | } else { 75 | UserDefaults.standard.set(bookmark, forKey: keyName) 76 | } 77 | #endif 78 | return true 79 | } catch let error { 80 | err("\(error)") 81 | return false 82 | } 83 | } 84 | 85 | func readBookmark(data: Data, isSecure: Bool) -> URL? { 86 | #if os(iOS) 87 | let options:URL.BookmarkResolutionOptions = [] 88 | #elseif os(macOS) 89 | let options:URL.BookmarkResolutionOptions = isSecure ? [.withSecurityScope] : [] 90 | #endif 91 | do { 92 | var bookmarkIsStale = false 93 | let url = try URL( 94 | resolvingBookmarkData: data, 95 | options: options, 96 | relativeTo: nil, 97 | bookmarkDataIsStale: &bookmarkIsStale 98 | ) 99 | if bookmarkIsStale { 100 | NSLog("Stale bookmark, renewing it \(url)") 101 | if saveBookmark(url: url, isShared: true, keyName: SharedDefaults.keyName, isSecure: false) { 102 | NSLog("Successfully renewed stale bookmark - \(url)") 103 | } else { 104 | NSLog("Could not renew stale bookmark - \(url)") 105 | } 106 | } 107 | return url 108 | } catch let error { 109 | err("Error: \(error)") 110 | return nil 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /extension/Userscripts Extension/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Userscripts 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSExtension 26 | 27 | NSExtensionPointIdentifier 28 | com.apple.Safari.web-extension 29 | NSExtensionPrincipalClass 30 | $(PRODUCT_MODULE_NAME).SafariWebExtensionHandler 31 | 32 | NSHumanReadableCopyright 33 | Copyright © 2021 Justin Wasack. All rights reserved. 34 | NSHumanReadableDescription 35 | Save and run javascript for the web pages you visit 36 | 37 | 38 | -------------------------------------------------------------------------------- /extension/Userscripts Extension/Resources/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extension_name": { 3 | "message": "Userscripts", 4 | "description": "The display name for the extension." 5 | }, 6 | "extension_description": { 7 | "message": "Save and run javascript for the web pages you visit", 8 | "description": "Description of what the extension does." 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /extension/Userscripts Extension/Resources/images/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts Extension/Resources/images/icon-128.png -------------------------------------------------------------------------------- /extension/Userscripts Extension/Resources/images/icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts Extension/Resources/images/icon-256.png -------------------------------------------------------------------------------- /extension/Userscripts Extension/Resources/images/icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts Extension/Resources/images/icon-48.png -------------------------------------------------------------------------------- /extension/Userscripts Extension/Resources/images/icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts Extension/Resources/images/icon-512.png -------------------------------------------------------------------------------- /extension/Userscripts Extension/Resources/images/icon-96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts Extension/Resources/images/icon-96.png -------------------------------------------------------------------------------- /extension/Userscripts Extension/Resources/images/toolbar-icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts Extension/Resources/images/toolbar-icon-16.png -------------------------------------------------------------------------------- /extension/Userscripts Extension/Resources/images/toolbar-icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts Extension/Resources/images/toolbar-icon-32.png -------------------------------------------------------------------------------- /extension/Userscripts Extension/Resources/manifest.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "manifest_version": 2, 4 | "default_locale": "en", 5 | "name": "__MSG_extension_name__", 6 | "description": "__MSG_extension_description__", 7 | "version": "4.3.3", 8 | "icons": { 9 | "48": "images/icon-48.png", 10 | "96": "images/icon-96.png", 11 | "128": "images/icon-128.png", 12 | "256": "images/icon-256.png", 13 | "512": "images/icon-512.png" 14 | }, 15 | "background": { 16 | "scripts": ["background.js"], 17 | "persistent": false 18 | }, 19 | "browser_action": { 20 | "default_popup": "popup.html", 21 | "default_icon": { 22 | "16": "images/toolbar-icon-16.png", 23 | "32": "images/toolbar-icon-32.png" 24 | } 25 | }, 26 | "content_scripts": [ 27 | { 28 | "js": ["content.js"], 29 | "matches": [""], 30 | "run_at": "document_start", 31 | "all_frames": true 32 | } 33 | ], 34 | "permissions": [ 35 | "", 36 | "clipboardWrite", 37 | "contextMenus", 38 | "declarativeNetRequest", 39 | "menus", 40 | "nativeMessaging", 41 | "storage", 42 | "tabs", 43 | "unlimitedStorage", 44 | "webNavigation" 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /extension/Userscripts Extension/Userscripts Extension.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.application-groups 8 | 9 | $(TeamIdentifierPrefix)com.userscripts.macos 10 | 11 | com.apple.security.files.bookmarks.app-scope 12 | 13 | com.apple.security.files.user-selected.read-write 14 | 15 | com.apple.security.network.client 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /extension/Userscripts-iOS Extension/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSExtension 6 | 7 | NSExtensionPointIdentifier 8 | com.apple.Safari.web-extension 9 | NSExtensionPrincipalClass 10 | $(PRODUCT_MODULE_NAME).SafariWebExtensionHandler 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /extension/Userscripts-iOS Extension/Userscripts-iOS Extension.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.com.userscripts.ios 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /extension/Userscripts-iOS/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Userscripts-iOS 4 | // 5 | // Created by Justin Wasack on 10/3/21. 6 | // Copyright © 2021 Justin Wasack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @main 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 22 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /extension/Userscripts-iOS/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/120-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/120-1.png -------------------------------------------------------------------------------- /extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/20.png -------------------------------------------------------------------------------- /extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/40-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/40-1.png -------------------------------------------------------------------------------- /extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/40-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/40-2.png -------------------------------------------------------------------------------- /extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/58-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/58-1.png -------------------------------------------------------------------------------- /extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/80-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/80-1.png -------------------------------------------------------------------------------- /extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /extension/Userscripts-iOS/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "40.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "60.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "58.png", 17 | "idiom" : "iphone", 18 | "scale" : "2x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "87.png", 23 | "idiom" : "iphone", 24 | "scale" : "3x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "80.png", 29 | "idiom" : "iphone", 30 | "scale" : "2x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "filename" : "120.png", 35 | "idiom" : "iphone", 36 | "scale" : "3x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "120-1.png", 41 | "idiom" : "iphone", 42 | "scale" : "2x", 43 | "size" : "60x60" 44 | }, 45 | { 46 | "filename" : "180.png", 47 | "idiom" : "iphone", 48 | "scale" : "3x", 49 | "size" : "60x60" 50 | }, 51 | { 52 | "filename" : "20.png", 53 | "idiom" : "ipad", 54 | "scale" : "1x", 55 | "size" : "20x20" 56 | }, 57 | { 58 | "filename" : "40-1.png", 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "20x20" 62 | }, 63 | { 64 | "filename" : "29.png", 65 | "idiom" : "ipad", 66 | "scale" : "1x", 67 | "size" : "29x29" 68 | }, 69 | { 70 | "filename" : "58-1.png", 71 | "idiom" : "ipad", 72 | "scale" : "2x", 73 | "size" : "29x29" 74 | }, 75 | { 76 | "filename" : "40-2.png", 77 | "idiom" : "ipad", 78 | "scale" : "1x", 79 | "size" : "40x40" 80 | }, 81 | { 82 | "filename" : "80-1.png", 83 | "idiom" : "ipad", 84 | "scale" : "2x", 85 | "size" : "40x40" 86 | }, 87 | { 88 | "filename" : "76.png", 89 | "idiom" : "ipad", 90 | "scale" : "1x", 91 | "size" : "76x76" 92 | }, 93 | { 94 | "filename" : "152.png", 95 | "idiom" : "ipad", 96 | "scale" : "2x", 97 | "size" : "76x76" 98 | }, 99 | { 100 | "filename" : "167.png", 101 | "idiom" : "ipad", 102 | "scale" : "2x", 103 | "size" : "83.5x83.5" 104 | }, 105 | { 106 | "filename" : "1024.png", 107 | "idiom" : "ios-marketing", 108 | "scale" : "1x", 109 | "size" : "1024x1024" 110 | } 111 | ], 112 | "info" : { 113 | "author" : "xcode", 114 | "version" : 1 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /extension/Userscripts-iOS/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /extension/Userscripts-iOS/Assets.xcassets/LargeIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "scale" : "3x" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /extension/Userscripts-iOS/Assets.xcassets/LaunchScreen.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "LaunchScreen.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /extension/Userscripts-iOS/Assets.xcassets/LaunchScreen.imageset/LaunchScreen.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts-iOS/Assets.xcassets/LaunchScreen.imageset/LaunchScreen.pdf -------------------------------------------------------------------------------- /extension/Userscripts-iOS/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /extension/Userscripts-iOS/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /extension/Userscripts-iOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationSupportsMultipleScenes 8 | 9 | UISceneConfigurations 10 | 11 | UIWindowSceneSessionRoleApplication 12 | 13 | 14 | UISceneConfigurationName 15 | Default Configuration 16 | UISceneDelegateClassName 17 | $(PRODUCT_MODULE_NAME).SceneDelegate 18 | UISceneStoryboardFile 19 | Main 20 | 21 | 22 | 23 | 24 | UIStatusBarStyle 25 | UIStatusBarStyleLightContent 26 | UIViewControllerBasedStatusBarAppearance 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /extension/Userscripts-iOS/Resources/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts-iOS/Resources/Icon.png -------------------------------------------------------------------------------- /extension/Userscripts-iOS/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Userscripts-iOS 4 | // 5 | // Created by Justin Wasack on 10/3/21. 6 | // Copyright © 2021 Justin Wasack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | 13 | var window: UIWindow? 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | guard let _ = (scene as? UIWindowScene) else { return } 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /extension/Userscripts-iOS/Userscripts-iOS.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.com.userscripts.ios 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /extension/Userscripts-iOS/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Userscripts-iOS 4 | // 5 | // Created by Justin Wasack on 10/3/21. 6 | // Copyright © 2021 Justin Wasack. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import WebKit 11 | import UniformTypeIdentifiers 12 | 13 | class ViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler, UIDocumentPickerDelegate { 14 | 15 | @IBOutlet var webView: WKWebView! 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | let backgroundColor = UIColor.init(red: (47/255.0), green: (51/255.0), blue: (55/255.0), alpha: 1.0) 20 | view.setValue(backgroundColor, forKey: "backgroundColor") 21 | self.webView.isOpaque = false 22 | self.webView.backgroundColor = backgroundColor 23 | self.webView.navigationDelegate = self 24 | self.webView.scrollView.isScrollEnabled = false 25 | self.webView.configuration.userContentController.add(self, name: "controller") 26 | 27 | self.webView.loadFileURL(Bundle.main.url(forResource: "Main", withExtension: "html")!, allowingReadAccessTo: Bundle.main.resourceURL!) 28 | } 29 | 30 | func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { 31 | let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "??" 32 | let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "??" 33 | var readLocation:String 34 | if let sharedBookmarkData = UserDefaults(suiteName: SharedDefaults.suiteName)?.data(forKey: SharedDefaults.keyName) { 35 | if let bookmarkUrl = readBookmark(data: sharedBookmarkData, isSecure: true) { 36 | readLocation = bookmarkUrl.absoluteString 37 | } else { 38 | readLocation = "Failed to get read directory" 39 | } 40 | } else { 41 | readLocation = "Select a directory to load userscripts" 42 | } 43 | webView.evaluateJavaScript("printDirectory('\(readLocation)')") 44 | webView.evaluateJavaScript("printVersion('v\(appVersion)', '(\(buildNumber))')") 45 | } 46 | 47 | func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { 48 | if navigationAction.navigationType == .linkActivated { 49 | guard 50 | let url = navigationAction.request.url, 51 | UIApplication.shared.canOpenURL(url) 52 | else { 53 | return 54 | } 55 | UIApplication.shared.open(url) 56 | decisionHandler(.cancel) 57 | } else { 58 | decisionHandler(.allow) 59 | } 60 | } 61 | 62 | func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { 63 | guard let name = message.body as? String else { 64 | err("Userscripts iOS received a message without a name") 65 | return 66 | } 67 | if name == "SET_READ_LOCATION" { 68 | logText("Userscripts iOS has requested to set the readLocation") 69 | let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: [.folder]) 70 | documentPicker.delegate = self 71 | present(documentPicker, animated: true, completion: nil) 72 | } 73 | } 74 | 75 | func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) { 76 | // https://developer.apple.com/videos/play/wwdc2018/216 77 | do { 78 | let shouldStopAccessing = url.startAccessingSecurityScopedResource() 79 | defer { 80 | if shouldStopAccessing {url.stopAccessingSecurityScopedResource()} 81 | } 82 | if saveBookmark(url: url, isShared: true, keyName: SharedDefaults.keyName, isSecure: false) { 83 | webView.evaluateJavaScript("printDirectory('\(url.absoluteString)')") 84 | } else { 85 | throw NSError(domain: "Failed to saved bookmark", code: 0, userInfo: [:]) 86 | } 87 | } catch let error { 88 | err("\(error)") 89 | return 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /extension/Userscripts.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /extension/Userscripts.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /extension/Userscripts.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /extension/Userscripts.xcodeproj/xcshareddata/xcschemes/Userscripts Extension.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 6 | 9 | 10 | 16 | 22 | 23 | 24 | 30 | 36 | 37 | 38 | 39 | 40 | 45 | 46 | 52 | 53 | 54 | 55 | 57 | 63 | 64 | 65 | 66 | 67 | 79 | 83 | 84 | 85 | 91 | 92 | 93 | 94 | 98 | 99 | 100 | 101 | 109 | 111 | 117 | 118 | 119 | 120 | 122 | 123 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /extension/Userscripts.xcodeproj/xcshareddata/xcschemes/Userscripts.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 42 | 48 | 49 | 50 | 51 | 52 | 62 | 64 | 70 | 71 | 72 | 73 | 79 | 81 | 87 | 88 | 89 | 90 | 92 | 93 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /extension/Userscripts/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | @NSApplicationMain 4 | class AppDelegate: NSObject, NSApplicationDelegate { 5 | 6 | func applicationDidFinishLaunching(_ aNotification: Notification) { 7 | // Insert code here to initialize your application 8 | } 9 | 10 | func applicationWillTerminate(_ aNotification: Notification) { 11 | // Insert code here to tear down your application 12 | } 13 | 14 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 15 | return true 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /extension/Userscripts/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /extension/Userscripts/Assets.xcassets/AppIcon.appiconset/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts/Assets.xcassets/AppIcon.appiconset/128.png -------------------------------------------------------------------------------- /extension/Userscripts/Assets.xcassets/AppIcon.appiconset/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts/Assets.xcassets/AppIcon.appiconset/16.png -------------------------------------------------------------------------------- /extension/Userscripts/Assets.xcassets/AppIcon.appiconset/256-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts/Assets.xcassets/AppIcon.appiconset/256-1.png -------------------------------------------------------------------------------- /extension/Userscripts/Assets.xcassets/AppIcon.appiconset/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts/Assets.xcassets/AppIcon.appiconset/256.png -------------------------------------------------------------------------------- /extension/Userscripts/Assets.xcassets/AppIcon.appiconset/32-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts/Assets.xcassets/AppIcon.appiconset/32-1.png -------------------------------------------------------------------------------- /extension/Userscripts/Assets.xcassets/AppIcon.appiconset/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts/Assets.xcassets/AppIcon.appiconset/32.png -------------------------------------------------------------------------------- /extension/Userscripts/Assets.xcassets/AppIcon.appiconset/512-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts/Assets.xcassets/AppIcon.appiconset/512-1.png -------------------------------------------------------------------------------- /extension/Userscripts/Assets.xcassets/AppIcon.appiconset/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts/Assets.xcassets/AppIcon.appiconset/512.png -------------------------------------------------------------------------------- /extension/Userscripts/Assets.xcassets/AppIcon.appiconset/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ACTCD/userscripts-lab/5c599ec2ebe330feb7f4fce689afce3375dad7ff/extension/Userscripts/Assets.xcassets/AppIcon.appiconset/64.png -------------------------------------------------------------------------------- /extension/Userscripts/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "32-1.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "256-1.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "512-1.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /extension/Userscripts/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /extension/Userscripts/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleURLTypes 22 | 23 | 24 | CFBundleTypeRole 25 | Viewer 26 | CFBundleURLName 27 | com.userscripts.macos 28 | CFBundleURLSchemes 29 | 30 | userscriptsurlscheme 31 | 32 | 33 | 34 | CFBundleVersion 35 | $(CURRENT_PROJECT_VERSION) 36 | LSApplicationCategoryType 37 | public.app-category.developer-tools 38 | LSMinimumSystemVersion 39 | $(MACOSX_DEPLOYMENT_TARGET) 40 | NSHumanReadableCopyright 41 | Copyright © 2021 Justin Wasack. All rights reserved. 42 | NSMainStoryboardFile 43 | Main 44 | NSPrincipalClass 45 | NSApplication 46 | 47 | 48 | -------------------------------------------------------------------------------- /extension/Userscripts/Userscripts.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.application-groups 8 | 9 | $(TeamIdentifierPrefix)com.userscripts.macos 10 | 11 | com.apple.security.files.user-selected.read-write 12 | 13 | com.apple.security.network.client 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /extension/Userscripts/ViewController.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import SafariServices.SFSafariApplication 3 | 4 | class ViewController: NSViewController { 5 | 6 | @IBOutlet var appName: NSTextField! 7 | @IBOutlet var saveLocation: NSTextField! 8 | @IBOutlet weak var enabledText: NSTextField! 9 | @IBOutlet weak var enabledIcon: NSView! 10 | 11 | let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "??" 12 | let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "??" 13 | let hostID = Bundle.main.bundleIdentifier! 14 | let foo = Bundle.main.bundleIdentifier 15 | let extensionID = "com.userscripts.macos.Userscripts-Extension" 16 | let documentsDirectory = getDocumentsDirectory().appendingPathComponent("scripts").absoluteString 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | let location = documentsDirectory.replacingOccurrences(of: hostID, with: extensionID) 21 | self.appName.stringValue = "Userscripts Safari Version \(appVersion) (\(buildNumber))" 22 | setExtensionState() 23 | NotificationCenter.default.addObserver( 24 | self, selector: #selector(setExtensionState), name: NSApplication.didBecomeActiveNotification, object: nil 25 | ) 26 | // set the save location url to default location 27 | self.saveLocation.stringValue = location 28 | self.saveLocation.toolTip = location 29 | // check if bookmark data exists 30 | guard 31 | let sharedBookmark = UserDefaults(suiteName: SharedDefaults.suiteName)?.data(forKey: SharedDefaults.keyName) 32 | else { 33 | // bookmark data doesn't exist, no need to update url 34 | return 35 | } 36 | // at this point it's known bookmark data does exist, try to read it 37 | guard let url = readBookmark(data: sharedBookmark, isSecure: false) else { 38 | // bookmark data does exist, but it can not be read, log an error 39 | err("shared bookmark data exists, but it can not be read") 40 | return 41 | } 42 | // shared bookmark data does exist and it can be read, check if the directory where it leads to exists 43 | guard directoryExists(path: url.path) else { 44 | // sharedBookmark removed, or in trash 45 | // renamed directories retain association 46 | // moved directories retain association 47 | UserDefaults(suiteName: SharedDefaults.suiteName)?.removeObject(forKey: SharedDefaults.keyName) 48 | NSLog("removed shared bookmark because it's directory is non-existent, permanently deleted or in trash") 49 | return 50 | } 51 | // shared bookmark can be read and directory exists, update url 52 | self.saveLocation.stringValue = url.absoluteString 53 | self.saveLocation.toolTip = url.absoluteString 54 | } 55 | 56 | @objc func setExtensionState() { 57 | SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: extensionID) { (state, error) in 58 | guard let state = state, error == nil else { 59 | self.enabledText.stringValue = "Safari Extension State Unknown" 60 | err(error?.localizedDescription ?? "couldn't get safari extension state in containing app") 61 | return 62 | } 63 | DispatchQueue.main.async { 64 | self.enabledIcon.layer?.backgroundColor = state.isEnabled ? NSColor.green.cgColor : NSColor.red.cgColor 65 | self.enabledText.stringValue = state.isEnabled ? "Safari Extension Enabled" : "Safari Extension Disabled" 66 | } 67 | } 68 | } 69 | 70 | @IBAction func changeSaveLocation(_ sender: NSButton) { 71 | guard let window = self.view.window else { return } 72 | let panel = NSOpenPanel() 73 | panel.allowsMultipleSelection = false 74 | panel.canChooseDirectories = true 75 | panel.canCreateDirectories = true 76 | panel.canChooseFiles = false 77 | panel.beginSheetModal(for: window, completionHandler: { response in 78 | if let url: URL = panel.urls.first { 79 | // check it is a writeable path 80 | let canWrite = FileManager.default.isWritableFile(atPath: url.path) 81 | if !canWrite { 82 | // display error message 83 | let alert = NSAlert() 84 | alert.messageText = "Can not write to path. Choose a different path." 85 | alert.runModal() 86 | } else { 87 | if !saveBookmark(url: url, isShared: true, keyName: SharedDefaults.keyName, isSecure: false) { 88 | err("couldn't save new location from host app") 89 | return 90 | } 91 | self.saveLocation.stringValue = url.absoluteString 92 | self.saveLocation.toolTip = url.absoluteString 93 | } 94 | } 95 | }) 96 | } 97 | 98 | @IBAction func openSafariExtensionPreferences(_ sender: AnyObject?) { 99 | SFSafariApplication.showPreferencesForExtension(withIdentifier: extensionID) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /extension/UserscriptsTests/UserscriptsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserscriptsTests.swift 3 | // UserscriptsTests 4 | // 5 | // Created by Justin Wasack on 1/23/22. 6 | // Copyright © 2022 Justin Wasack. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Userscripts_Extension 11 | 12 | class UserscriptsTests: XCTestCase { 13 | 14 | override func setUpWithError() throws { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDownWithError() throws { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | 23 | func testExample() throws { 24 | // This is an example of a functional test case. 25 | // Use XCTAssert and related functions to verify your tests produce the correct results. 26 | // Any test you write for XCTest can be annotated as throws and async. 27 | // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. 28 | // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. 29 | } 30 | 31 | func testStringSanitization() throws { 32 | // given 33 | let strs = [ 34 | "String", 35 | "https://something.com/?foo=12", 36 | "I have backslashes \\\\", 37 | ".....ok", 38 | ":Akneh.,><>dkie:lm", 39 | "..解锁B站大会员番剧、", 40 | "解锁B站大会员番剧、B站视频解析下载;全网VIP视频免费破解去广告;全网音乐直接下载;油管、Facebook等国外视频解析下载;网盘搜索引擎破解无限下载等", 41 | "5CLksm3AAbb2F2F2f----___--+87363&^#%o%3O3", 42 | "Example Userscript Name" 43 | ] 44 | 45 | // when 46 | var result = [String]() 47 | for str in strs { 48 | let sanitizedString = sanitize(str) 49 | let unsanitizedString = unsanitize(sanitizedString) 50 | result.append(unsanitizedString) 51 | } 52 | 53 | // then 54 | XCTAssert(result.elementsEqual(strs)) 55 | } 56 | 57 | func testEncodedCheck() throws { 58 | let urls = [ 59 | "https://greasyfork.org/scripts/416338-redirect-外链跳转/code/redirect%20外链跳转.user.js", 60 | "https://raw.githubusercontent.com/Anarios/return-youtube-dislike/main/Extensions/UserScript/Return%20Youtube%20Dislike.user.js", 61 | "https://cdn.frankerfacez.com/static/ffz_injector.user.js", 62 | "http://www.k21p.com/example.user.js", // add http protocol 63 | "https://greasyfork.org/scripts/416338-redirect-外链跳转/code/redirect 外链跳转.user.js" 64 | ] 65 | var result = [String]() 66 | for url in urls { 67 | if isEncoded(url) { 68 | result.append(url) 69 | } 70 | } 71 | // 2 urls already percent encoded 72 | XCTAssert(result.count == 2) 73 | } 74 | 75 | func testGetRemoteFileContents() throws { 76 | let urls = [ 77 | "https://greasyfork.org/scripts/416338-redirect-外链跳转/code/redirect%20外链跳转.user.js", 78 | "https://greasyfork.org/scripts/416338-redirect-外链跳转/code/redirect 外链跳转.user.js", 79 | "https://raw.githubusercontent.com/Anarios/return-youtube-dislike/main/Extensions/UserScript/Return%20Youtube%20Dislike.user.js", 80 | "https://cdn.frankerfacez.com/static/ffz_injector.user.js", 81 | "http://www.k21p.com/example.user.js" // add http protocol 82 | ] 83 | var result = [String]() 84 | for url in urls { 85 | if let contents = getRemoteFileContents(url) { 86 | result.append(contents) 87 | } 88 | } 89 | XCTAssert(result.count == urls.count) 90 | } 91 | 92 | func testFileRemoteUpdate() throws { 93 | let urls = [ 94 | "https://www.k21p.com/example.user.js", 95 | "https://www.k21p.com/example.user.js?foo=bar", // query string 96 | "http://www.k21p.com/example.user.js", // http protocol 97 | "https://greasyfork.org/scripts/416338-redirect-外链跳转/code/redirect 外链跳转.user.js", // non latin chars 98 | "https://www.k21p.com/example.user.jsx" // should fail 99 | 100 | ] 101 | var result = [Int]() 102 | for url in urls { 103 | let content = """ 104 | // ==UserScript== 105 | // @name test 106 | // @match *://*/* 107 | // @version 0.1 108 | // @updateURL http://www.k21p.com/example.user.js 109 | // @downloadURL \(url) 110 | // ==/UserScript== 111 | """; 112 | let response = getFileRemoteUpdate(content) 113 | if !response.keys.contains("error") { 114 | result.append(1) 115 | } 116 | } 117 | XCTAssert(result.count == (urls.count - 1)) 118 | } 119 | 120 | func testMatching() throws { 121 | var count = 0 122 | var result = [String]() 123 | let patternDict = [ 124 | "*://*/*": [ 125 | "https://www.bing.com/", 126 | "https://example.org/foo/bar.html", 127 | "https://a.org/some/path/" 128 | ], 129 | "*://*.mozilla.org/*": [ 130 | "http://mozilla.org/", 131 | "https://mozilla.org/", 132 | "https://b.mozilla.org/path/" 133 | ], 134 | "*://www.google.com/*": [ 135 | "https://www.google.com/://aa", 136 | "https://www.google.com/preferences?prev=https://www.google.com/", 137 | "https://www.google.com/preferences?prev=", 138 | "https://www.google.com/" 139 | ], 140 | "*://localhost/*": [ 141 | "http://localhost:8000/", 142 | "https://localhost:3000/foo.html" 143 | ], 144 | "http://127.0.0.1/*": [ 145 | "http://127.0.0.1/", 146 | "http://127.0.0.1/foo/bar.html" 147 | ] 148 | ] 149 | let patternDictFails = [ 150 | "https://www.example.com/*": [ 151 | "file://www.example.com/", 152 | "ftp://www.example.com/", 153 | "ws://www.example.com/", 154 | "http://www.example.com/" 155 | ], 156 | "http://www.example.com/index.html": [ 157 | "http://www.example.com/", 158 | "https://www.example.com/index.html" 159 | ], 160 | "*://localhost/*": [ 161 | "https://localhost.com/", 162 | "ftp://localhost:8080/" 163 | ], 164 | "https://www.example*/*": [ 165 | "https://www.example.com/" 166 | ] 167 | ] 168 | for (pattern, urls) in patternDict { 169 | count = count + urls.count 170 | for url in urls { 171 | if 172 | let parts = getUrlProps(url), 173 | let ptcl = parts["protocol"], 174 | let host = parts["host"], 175 | let path = parts["pathname"] 176 | { 177 | if match(ptcl, host, path, pattern) { 178 | result.append("1") 179 | } 180 | } 181 | } 182 | } 183 | for (pattern, urls) in patternDictFails { 184 | // don't increment count since these tests should fail 185 | for url in urls { 186 | if 187 | let parts = getUrlProps(url), 188 | let ptcl = parts["protocol"], 189 | let host = parts["host"], 190 | let path = parts["pathname"] 191 | { 192 | if match(ptcl, host, path, pattern) { 193 | // if these match, results will get an extra element 194 | // and then the test will fail 195 | result.append("1") 196 | } 197 | } 198 | } 199 | } 200 | XCTAssert(result.count == count) 201 | } 202 | 203 | func testPerformanceExample() throws { 204 | // This is an example of a performance test case. 205 | measure { 206 | // Put the code you want to measure the time of here. 207 | } 208 | } 209 | 210 | } 211 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const {dest, series, src} = require("gulp"); 2 | const del = require("del"); 3 | const inline = require("gulp-inline-source"); 4 | const postcss = require("gulp-postcss"); 5 | const autoprefixer = require("autoprefixer"); 6 | const htmlmin = require("gulp-html-minifier-terser"); 7 | const dom = require("gulp-dom"); 8 | const rename = require("gulp-rename"); 9 | const directory = process.env.NODE_ENV === "popup" ? "popup" : "page"; 10 | 11 | const copyLocation = "./temp"; 12 | const destLocation = "./extension/Userscripts Extension/Resources"; 13 | 14 | // clone public directory to avoid prefixing development assets 15 | function copy() { 16 | return src(`./public/${directory}/**/*`) 17 | .pipe(dest(copyLocation)); 18 | } 19 | 20 | // autoprefix select stylesheets and overwrite in place, at copy location 21 | function autoprefix() { 22 | return src([`${copyLocation}/build/bundle.css`, `${copyLocation}/css/global.css`]) 23 | .pipe(postcss([ 24 | autoprefixer({overrideBrowserslist: ["safari >= 13"]}) 25 | ])) 26 | .pipe(dest(file => file.base)); 27 | } 28 | 29 | // inline assets, minify css and remove comment in monolithic html file 30 | function inlineAssets() { 31 | return src(`${copyLocation}/index.html`) 32 | .pipe(inline({ 33 | attribute: false, 34 | compress: false, 35 | ignore: [] 36 | })) 37 | .pipe(htmlmin({ 38 | collapseWhitespace: true, 39 | minifyCSS: true, 40 | minifyJS: false, 41 | removeComments: true 42 | })) 43 | .pipe(dest(file => file.base)); 44 | } 45 | 46 | // remove the inlined javascript and save to singular js file 47 | function bundleJS() { 48 | return src(`${copyLocation}/index.html`) 49 | .pipe(dom(function() { 50 | const scripts = this.querySelectorAll("script"); 51 | let result = ""; 52 | scripts.forEach(function(script) { 53 | result += script.innerHTML; 54 | }); 55 | return result; 56 | }, false)) 57 | .pipe(rename(`${directory}.js`)) 58 | .pipe(dest(destLocation)); 59 | } 60 | 61 | // remove the scripts tags from source file and move/rename html file 62 | function removeTags() { 63 | return src(`${copyLocation}/index.html`) 64 | .pipe(dom(function() { 65 | const scripts = this.querySelectorAll("script"); 66 | scripts.forEach(function(script) { 67 | const parent = script.parentNode; 68 | parent.removeChild(script); 69 | }); 70 | const f = this.createElement("script"); 71 | f.setAttribute("src", `${directory}.js`); 72 | this.body.appendChild(f); 73 | return this; 74 | }, false)) 75 | .pipe(rename(`${directory}.html`)) 76 | .pipe(dest(destLocation)); 77 | } 78 | 79 | // remove the temp folder 80 | function clean() { 81 | return del(copyLocation); 82 | } 83 | 84 | exports.build = series(copy, autoprefix, inlineAssets, bundleJS, removeTags, clean); 85 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "build:popup": "NODE_ENV=popup rollup -c && NODE_ENV=popup gulp build", 4 | "build:page": "NODE_ENV=page rollup -c && NODE_ENV=page gulp build", 5 | "dev:page": "NODE_ENV=page rollup -c -w", 6 | "dev:popup": "NODE_ENV=popup rollup -c -w", 7 | "lint-css": "npx stylelint ./", 8 | "lint-js": "eslint ./", 9 | "start:page": "sirv public/page", 10 | "start:popup": "sirv public/popup" 11 | }, 12 | "devDependencies": { 13 | "@rollup/plugin-commonjs": "^15.1.0", 14 | "@rollup/plugin-multi-entry": "^4.0.0", 15 | "@rollup/plugin-node-resolve": "^9.0.0", 16 | "autoprefixer": "<10.0.0", 17 | "cm-show-invisibles": "^3.1.0", 18 | "codemirror": "^5.58.2", 19 | "del": "^6.0.0", 20 | "eslint": "^8.10.0", 21 | "eslint-plugin-svelte3": "^2.7.3", 22 | "gulp": "^4.0.2", 23 | "gulp-dom": "^1.0.0", 24 | "gulp-html-minifier-terser": "^6.0.1", 25 | "gulp-inline-source": "^4.0.0", 26 | "gulp-postcss": "^9.0.0", 27 | "gulp-rename": "^2.0.0", 28 | "postcss-html": "^1.2.0", 29 | "rollup": "^2.29.0", 30 | "rollup-plugin-css-only": "^2.1.0", 31 | "rollup-plugin-inline-svg": "^2.0.0", 32 | "rollup-plugin-livereload": "^2.0.0", 33 | "rollup-plugin-svelte": "^6.1.1", 34 | "stylelint": "^14.1.0", 35 | "stylelint-config-html": "^1.0.0", 36 | "stylelint-config-standard": "^24.0.0", 37 | "svelte": "^3.49.0" 38 | }, 39 | "dependencies": { 40 | "sirv-cli": "^1.0.6" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /public/page/codemirror/addon/active-line.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: https://codemirror.net/LICENSE 3 | 4 | (function(mod) { 5 | if (typeof exports == "object" && typeof module == "object") // CommonJS 6 | mod(require("../../lib/codemirror")); 7 | else if (typeof define == "function" && define.amd) // AMD 8 | define(["../../lib/codemirror"], mod); 9 | else // Plain browser env 10 | mod(CodeMirror); 11 | })(function(CodeMirror) { 12 | "use strict"; 13 | var WRAP_CLASS = "CodeMirror-activeline"; 14 | var BACK_CLASS = "CodeMirror-activeline-background"; 15 | var GUTT_CLASS = "CodeMirror-activeline-gutter"; 16 | 17 | CodeMirror.defineOption("styleActiveLine", false, function(cm, val, old) { 18 | var prev = old == CodeMirror.Init ? false : old; 19 | if (val == prev) return 20 | if (prev) { 21 | cm.off("beforeSelectionChange", selectionChange); 22 | clearActiveLines(cm); 23 | delete cm.state.activeLines; 24 | } 25 | if (val) { 26 | cm.state.activeLines = []; 27 | updateActiveLines(cm, cm.listSelections()); 28 | cm.on("beforeSelectionChange", selectionChange); 29 | } 30 | }); 31 | 32 | function clearActiveLines(cm) { 33 | for (var i = 0; i < cm.state.activeLines.length; i++) { 34 | cm.removeLineClass(cm.state.activeLines[i], "wrap", WRAP_CLASS); 35 | cm.removeLineClass(cm.state.activeLines[i], "background", BACK_CLASS); 36 | cm.removeLineClass(cm.state.activeLines[i], "gutter", GUTT_CLASS); 37 | } 38 | } 39 | 40 | function sameArray(a, b) { 41 | if (a.length != b.length) return false; 42 | for (var i = 0; i < a.length; i++) 43 | if (a[i] != b[i]) return false; 44 | return true; 45 | } 46 | 47 | function updateActiveLines(cm, ranges) { 48 | var active = []; 49 | for (var i = 0; i < ranges.length; i++) { 50 | var range = ranges[i]; 51 | var option = cm.getOption("styleActiveLine"); 52 | if (typeof option == "object" && option.nonEmpty ? range.anchor.line != range.head.line : !range.empty()) 53 | continue 54 | var line = cm.getLineHandleVisualStart(range.head.line); 55 | if (active[active.length - 1] != line) active.push(line); 56 | } 57 | if (sameArray(cm.state.activeLines, active)) return; 58 | cm.operation(function() { 59 | clearActiveLines(cm); 60 | for (var i = 0; i < active.length; i++) { 61 | cm.addLineClass(active[i], "wrap", WRAP_CLASS); 62 | cm.addLineClass(active[i], "background", BACK_CLASS); 63 | cm.addLineClass(active[i], "gutter", GUTT_CLASS); 64 | } 65 | cm.state.activeLines = active; 66 | }); 67 | } 68 | 69 | function selectionChange(cm, sel) { 70 | updateActiveLines(cm, sel.ranges); 71 | } 72 | }); 73 | -------------------------------------------------------------------------------- /public/page/codemirror/addon/closebrackets.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: https://codemirror.net/LICENSE 3 | 4 | (function(mod) { 5 | if (typeof exports == "object" && typeof module == "object") // CommonJS 6 | mod(require("../../lib/codemirror")); 7 | else if (typeof define == "function" && define.amd) // AMD 8 | define(["../../lib/codemirror"], mod); 9 | else // Plain browser env 10 | mod(CodeMirror); 11 | })(function(CodeMirror) { 12 | var defaults = { 13 | pairs: "()[]{}''\"\"", 14 | closeBefore: ")]}'\":;>", 15 | triples: "", 16 | explode: "[]{}" 17 | }; 18 | 19 | var Pos = CodeMirror.Pos; 20 | 21 | CodeMirror.defineOption("autoCloseBrackets", false, function(cm, val, old) { 22 | if (old && old != CodeMirror.Init) { 23 | cm.removeKeyMap(keyMap); 24 | cm.state.closeBrackets = null; 25 | } 26 | if (val) { 27 | ensureBound(getOption(val, "pairs")) 28 | cm.state.closeBrackets = val; 29 | cm.addKeyMap(keyMap); 30 | } 31 | }); 32 | 33 | function getOption(conf, name) { 34 | if (name == "pairs" && typeof conf == "string") return conf; 35 | if (typeof conf == "object" && conf[name] != null) return conf[name]; 36 | return defaults[name]; 37 | } 38 | 39 | var keyMap = {Backspace: handleBackspace, Enter: handleEnter}; 40 | function ensureBound(chars) { 41 | for (var i = 0; i < chars.length; i++) { 42 | var ch = chars.charAt(i), key = "'" + ch + "'" 43 | if (!keyMap[key]) keyMap[key] = handler(ch) 44 | } 45 | } 46 | ensureBound(defaults.pairs + "`") 47 | 48 | function handler(ch) { 49 | return function(cm) { return handleChar(cm, ch); }; 50 | } 51 | 52 | function getConfig(cm) { 53 | var deflt = cm.state.closeBrackets; 54 | if (!deflt || deflt.override) return deflt; 55 | var mode = cm.getModeAt(cm.getCursor()); 56 | return mode.closeBrackets || deflt; 57 | } 58 | 59 | function handleBackspace(cm) { 60 | var conf = getConfig(cm); 61 | if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass; 62 | 63 | var pairs = getOption(conf, "pairs"); 64 | var ranges = cm.listSelections(); 65 | for (var i = 0; i < ranges.length; i++) { 66 | if (!ranges[i].empty()) return CodeMirror.Pass; 67 | var around = charsAround(cm, ranges[i].head); 68 | if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass; 69 | } 70 | for (var i = ranges.length - 1; i >= 0; i--) { 71 | var cur = ranges[i].head; 72 | cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1), "+delete"); 73 | } 74 | } 75 | 76 | function handleEnter(cm) { 77 | var conf = getConfig(cm); 78 | var explode = conf && getOption(conf, "explode"); 79 | if (!explode || cm.getOption("disableInput")) return CodeMirror.Pass; 80 | 81 | var ranges = cm.listSelections(); 82 | for (var i = 0; i < ranges.length; i++) { 83 | if (!ranges[i].empty()) return CodeMirror.Pass; 84 | var around = charsAround(cm, ranges[i].head); 85 | if (!around || explode.indexOf(around) % 2 != 0) return CodeMirror.Pass; 86 | } 87 | cm.operation(function() { 88 | var linesep = cm.lineSeparator() || "\n"; 89 | cm.replaceSelection(linesep + linesep, null); 90 | cm.execCommand("goCharLeft"); 91 | ranges = cm.listSelections(); 92 | for (var i = 0; i < ranges.length; i++) { 93 | var line = ranges[i].head.line; 94 | cm.indentLine(line, null, true); 95 | cm.indentLine(line + 1, null, true); 96 | } 97 | }); 98 | } 99 | 100 | function contractSelection(sel) { 101 | var inverted = CodeMirror.cmpPos(sel.anchor, sel.head) > 0; 102 | return {anchor: new Pos(sel.anchor.line, sel.anchor.ch + (inverted ? -1 : 1)), 103 | head: new Pos(sel.head.line, sel.head.ch + (inverted ? 1 : -1))}; 104 | } 105 | 106 | function handleChar(cm, ch) { 107 | var conf = getConfig(cm); 108 | if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass; 109 | 110 | var pairs = getOption(conf, "pairs"); 111 | var pos = pairs.indexOf(ch); 112 | if (pos == -1) return CodeMirror.Pass; 113 | 114 | var closeBefore = getOption(conf,"closeBefore"); 115 | 116 | var triples = getOption(conf, "triples"); 117 | 118 | var identical = pairs.charAt(pos + 1) == ch; 119 | var ranges = cm.listSelections(); 120 | var opening = pos % 2 == 0; 121 | 122 | var type; 123 | for (var i = 0; i < ranges.length; i++) { 124 | var range = ranges[i], cur = range.head, curType; 125 | var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1)); 126 | if (opening && !range.empty()) { 127 | curType = "surround"; 128 | } else if ((identical || !opening) && next == ch) { 129 | if (identical && stringStartsAfter(cm, cur)) 130 | curType = "both"; 131 | else if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch) 132 | curType = "skipThree"; 133 | else 134 | curType = "skip"; 135 | } else if (identical && cur.ch > 1 && triples.indexOf(ch) >= 0 && 136 | cm.getRange(Pos(cur.line, cur.ch - 2), cur) == ch + ch) { 137 | if (cur.ch > 2 && /\bstring/.test(cm.getTokenTypeAt(Pos(cur.line, cur.ch - 2)))) return CodeMirror.Pass; 138 | curType = "addFour"; 139 | } else if (identical) { 140 | var prev = cur.ch == 0 ? " " : cm.getRange(Pos(cur.line, cur.ch - 1), cur) 141 | if (!CodeMirror.isWordChar(next) && prev != ch && !CodeMirror.isWordChar(prev)) curType = "both"; 142 | else return CodeMirror.Pass; 143 | } else if (opening && (next.length === 0 || /\s/.test(next) || closeBefore.indexOf(next) > -1)) { 144 | curType = "both"; 145 | } else { 146 | return CodeMirror.Pass; 147 | } 148 | if (!type) type = curType; 149 | else if (type != curType) return CodeMirror.Pass; 150 | } 151 | 152 | var left = pos % 2 ? pairs.charAt(pos - 1) : ch; 153 | var right = pos % 2 ? ch : pairs.charAt(pos + 1); 154 | cm.operation(function() { 155 | if (type == "skip") { 156 | cm.execCommand("goCharRight"); 157 | } else if (type == "skipThree") { 158 | for (var i = 0; i < 3; i++) 159 | cm.execCommand("goCharRight"); 160 | } else if (type == "surround") { 161 | var sels = cm.getSelections(); 162 | for (var i = 0; i < sels.length; i++) 163 | sels[i] = left + sels[i] + right; 164 | cm.replaceSelections(sels, "around"); 165 | sels = cm.listSelections().slice(); 166 | for (var i = 0; i < sels.length; i++) 167 | sels[i] = contractSelection(sels[i]); 168 | cm.setSelections(sels); 169 | } else if (type == "both") { 170 | cm.replaceSelection(left + right, null); 171 | cm.triggerElectric(left + right); 172 | cm.execCommand("goCharLeft"); 173 | } else if (type == "addFour") { 174 | cm.replaceSelection(left + left + left + left, "before"); 175 | cm.execCommand("goCharRight"); 176 | } 177 | }); 178 | } 179 | 180 | function charsAround(cm, pos) { 181 | var str = cm.getRange(Pos(pos.line, pos.ch - 1), 182 | Pos(pos.line, pos.ch + 1)); 183 | return str.length == 2 ? str : null; 184 | } 185 | 186 | function stringStartsAfter(cm, pos) { 187 | var token = cm.getTokenAt(Pos(pos.line, pos.ch + 1)) 188 | return /\bstring/.test(token.type) && token.start == pos.ch && 189 | (pos.ch == 0 || !/\bstring/.test(cm.getTokenTypeAt(pos))) 190 | } 191 | }); 192 | -------------------------------------------------------------------------------- /public/page/codemirror/addon/continuecomment.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: https://codemirror.net/LICENSE 3 | 4 | (function(mod) { 5 | if (typeof exports == "object" && typeof module == "object") // CommonJS 6 | mod(require("../../lib/codemirror")); 7 | else if (typeof define == "function" && define.amd) // AMD 8 | define(["../../lib/codemirror"], mod); 9 | else // Plain browser env 10 | mod(CodeMirror); 11 | })(function(CodeMirror) { 12 | var nonspace = /\S/g; 13 | var repeat = String.prototype.repeat || function (n) { return Array(n + 1).join(this); }; 14 | function continueComment(cm) { 15 | if (cm.getOption("disableInput")) return CodeMirror.Pass; 16 | var ranges = cm.listSelections(), mode, inserts = []; 17 | for (var i = 0; i < ranges.length; i++) { 18 | var pos = ranges[i].head 19 | if (!/\bcomment\b/.test(cm.getTokenTypeAt(pos))) return CodeMirror.Pass; 20 | var modeHere = cm.getModeAt(pos) 21 | if (!mode) mode = modeHere; 22 | else if (mode != modeHere) return CodeMirror.Pass; 23 | 24 | var insert = null, line, found; 25 | var blockStart = mode.blockCommentStart, lineCmt = mode.lineComment; 26 | if (blockStart && mode.blockCommentContinue) { 27 | line = cm.getLine(pos.line); 28 | var end = line.lastIndexOf(mode.blockCommentEnd, pos.ch - mode.blockCommentEnd.length); 29 | // 1. if this block comment ended 30 | // 2. if this is actually inside a line comment 31 | if (end != -1 && end == pos.ch - mode.blockCommentEnd.length || 32 | lineCmt && (found = line.lastIndexOf(lineCmt, pos.ch - 1)) > -1 && 33 | /\bcomment\b/.test(cm.getTokenTypeAt({line: pos.line, ch: found + 1}))) { 34 | // ...then don't continue it 35 | } else if (pos.ch >= blockStart.length && 36 | (found = line.lastIndexOf(blockStart, pos.ch - blockStart.length)) > -1 && 37 | found > end) { 38 | // reuse the existing leading spaces/tabs/mixed 39 | // or build the correct indent using CM's tab/indent options 40 | if (nonspaceAfter(0, line) >= found) { 41 | insert = line.slice(0, found); 42 | } else { 43 | var tabSize = cm.options.tabSize, numTabs; 44 | found = CodeMirror.countColumn(line, found, tabSize); 45 | insert = !cm.options.indentWithTabs ? repeat.call(" ", found) : 46 | repeat.call("\t", (numTabs = Math.floor(found / tabSize))) + 47 | repeat.call(" ", found - tabSize * numTabs); 48 | } 49 | } else if ((found = line.indexOf(mode.blockCommentContinue)) > -1 && 50 | found <= pos.ch && 51 | found <= nonspaceAfter(0, line)) { 52 | insert = line.slice(0, found); 53 | } 54 | if (insert != null) insert += mode.blockCommentContinue 55 | } 56 | if (insert == null && lineCmt && continueLineCommentEnabled(cm)) { 57 | if (line == null) line = cm.getLine(pos.line); 58 | found = line.indexOf(lineCmt); 59 | // cursor at pos 0, line comment also at pos 0 => shift it down, don't continue 60 | if (!pos.ch && !found) insert = ""; 61 | // continue only if the line starts with an optional space + line comment 62 | else if (found > -1 && nonspaceAfter(0, line) >= found) { 63 | // don't continue if there's only space(s) after cursor or the end of the line 64 | insert = nonspaceAfter(pos.ch, line) > -1; 65 | // but always continue if the next line starts with a line comment too 66 | if (!insert) { 67 | var next = cm.getLine(pos.line + 1) || '', 68 | nextFound = next.indexOf(lineCmt); 69 | insert = nextFound > -1 && nonspaceAfter(0, next) >= nextFound || null; 70 | } 71 | if (insert) { 72 | insert = line.slice(0, found) + lineCmt + 73 | line.slice(found + lineCmt.length).match(/^\s*/)[0]; 74 | } 75 | } 76 | } 77 | if (insert == null) return CodeMirror.Pass; 78 | inserts[i] = "\n" + insert; 79 | } 80 | 81 | cm.operation(function() { 82 | for (var i = ranges.length - 1; i >= 0; i--) 83 | cm.replaceRange(inserts[i], ranges[i].from(), ranges[i].to(), "+insert"); 84 | }); 85 | } 86 | 87 | function nonspaceAfter(ch, str) { 88 | nonspace.lastIndex = ch; 89 | var m = nonspace.exec(str); 90 | return m ? m.index : -1; 91 | } 92 | 93 | function continueLineCommentEnabled(cm) { 94 | var opt = cm.getOption("continueComments"); 95 | if (opt && typeof opt == "object") 96 | return opt.continueLineComment !== false; 97 | return true; 98 | } 99 | 100 | CodeMirror.defineOption("continueComments", null, function(cm, val, prev) { 101 | if (prev && prev != CodeMirror.Init) 102 | cm.removeKeyMap("continueComment"); 103 | if (val) { 104 | var key = "Enter"; 105 | if (typeof val == "string") 106 | key = val; 107 | else if (typeof val == "object" && val.key) 108 | key = val.key; 109 | var map = {name: "continueComment"}; 110 | map[key] = continueComment; 111 | cm.addKeyMap(map); 112 | } 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /public/page/codemirror/addon/javascript-hint.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: https://codemirror.net/LICENSE 3 | 4 | (function(mod) { 5 | if (typeof exports == "object" && typeof module == "object") // CommonJS 6 | mod(require("../../lib/codemirror")); 7 | else if (typeof define == "function" && define.amd) // AMD 8 | define(["../../lib/codemirror"], mod); 9 | else // Plain browser env 10 | mod(CodeMirror); 11 | })(function(CodeMirror) { 12 | var Pos = CodeMirror.Pos; 13 | 14 | function forEach(arr, f) { 15 | for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]); 16 | } 17 | 18 | function arrayContains(arr, item) { 19 | if (!Array.prototype.indexOf) { 20 | var i = arr.length; 21 | while (i--) { 22 | if (arr[i] === item) { 23 | return true; 24 | } 25 | } 26 | return false; 27 | } 28 | return arr.indexOf(item) != -1; 29 | } 30 | 31 | function scriptHint(editor, keywords, getToken, options) { 32 | // Find the token at the cursor 33 | var cur = editor.getCursor(), token = getToken(editor, cur); 34 | if (/\b(?:string|comment)\b/.test(token.type)) return; 35 | var innerMode = CodeMirror.innerMode(editor.getMode(), token.state); 36 | if (innerMode.mode.helperType === "json") return; 37 | token.state = innerMode.state; 38 | 39 | // If it's not a 'word-style' token, ignore the token. 40 | if (!/^[\w$_]*$/.test(token.string)) { 41 | token = {start: cur.ch, end: cur.ch, string: "", state: token.state, 42 | type: token.string == "." ? "property" : null}; 43 | } else if (token.end > cur.ch) { 44 | token.end = cur.ch; 45 | token.string = token.string.slice(0, cur.ch - token.start); 46 | } 47 | 48 | var tprop = token; 49 | // If it is a property, find out what it is a property of. 50 | while (tprop.type == "property") { 51 | tprop = getToken(editor, Pos(cur.line, tprop.start)); 52 | if (tprop.string != ".") return; 53 | tprop = getToken(editor, Pos(cur.line, tprop.start)); 54 | if (!context) var context = []; 55 | context.push(tprop); 56 | } 57 | return {list: getCompletions(token, context, keywords, options), 58 | from: Pos(cur.line, token.start), 59 | to: Pos(cur.line, token.end)}; 60 | } 61 | 62 | function javascriptHint(editor, options) { 63 | return scriptHint(editor, javascriptKeywords, 64 | function (e, cur) {return e.getTokenAt(cur);}, 65 | options); 66 | }; 67 | CodeMirror.registerHelper("hint", "javascript", javascriptHint); 68 | 69 | function getCoffeeScriptToken(editor, cur) { 70 | // This getToken, it is for coffeescript, imitates the behavior of 71 | // getTokenAt method in javascript.js, that is, returning "property" 72 | // type and treat "." as indepenent token. 73 | var token = editor.getTokenAt(cur); 74 | if (cur.ch == token.start + 1 && token.string.charAt(0) == '.') { 75 | token.end = token.start; 76 | token.string = '.'; 77 | token.type = "property"; 78 | } 79 | else if (/^\.[\w$_]*$/.test(token.string)) { 80 | token.type = "property"; 81 | token.start++; 82 | token.string = token.string.replace(/\./, ''); 83 | } 84 | return token; 85 | } 86 | 87 | function coffeescriptHint(editor, options) { 88 | return scriptHint(editor, coffeescriptKeywords, getCoffeeScriptToken, options); 89 | } 90 | CodeMirror.registerHelper("hint", "coffeescript", coffeescriptHint); 91 | 92 | var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " + 93 | "toUpperCase toLowerCase split concat match replace search").split(" "); 94 | var arrayProps = ("length concat join splice push pop shift unshift slice reverse sort indexOf " + 95 | "lastIndexOf every some filter forEach map reduce reduceRight ").split(" "); 96 | var funcProps = "prototype apply call bind".split(" "); 97 | var javascriptKeywords = ("break case catch class const continue debugger default delete do else export extends false finally for function " + 98 | "if in import instanceof new null return super switch this throw true try typeof var void while with yield").split(" "); 99 | var coffeescriptKeywords = ("and break catch class continue delete do else extends false finally for " + 100 | "if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" "); 101 | 102 | function forAllProps(obj, callback) { 103 | if (!Object.getOwnPropertyNames || !Object.getPrototypeOf) { 104 | for (var name in obj) callback(name) 105 | } else { 106 | for (var o = obj; o; o = Object.getPrototypeOf(o)) 107 | Object.getOwnPropertyNames(o).forEach(callback) 108 | } 109 | } 110 | 111 | function getCompletions(token, context, keywords, options) { 112 | var found = [], start = token.string, global = options && options.globalScope || window; 113 | function maybeAdd(str) { 114 | if (str.lastIndexOf(start, 0) == 0 && !arrayContains(found, str)) found.push(str); 115 | } 116 | function gatherCompletions(obj) { 117 | if (typeof obj == "string") forEach(stringProps, maybeAdd); 118 | else if (obj instanceof Array) forEach(arrayProps, maybeAdd); 119 | else if (obj instanceof Function) forEach(funcProps, maybeAdd); 120 | forAllProps(obj, maybeAdd) 121 | } 122 | 123 | if (context && context.length) { 124 | // If this is a property, see if it belongs to some object we can 125 | // find in the current environment. 126 | var obj = context.pop(), base; 127 | if (obj.type && obj.type.indexOf("variable") === 0) { 128 | if (options && options.additionalContext) 129 | base = options.additionalContext[obj.string]; 130 | if (!options || options.useGlobalScope !== false) 131 | base = base || global[obj.string]; 132 | } else if (obj.type == "string") { 133 | base = ""; 134 | } else if (obj.type == "atom") { 135 | base = 1; 136 | } else if (obj.type == "function") { 137 | if (global.jQuery != null && (obj.string == '$' || obj.string == 'jQuery') && 138 | (typeof global.jQuery == 'function')) 139 | base = global.jQuery(); 140 | else if (global._ != null && (obj.string == '_') && (typeof global._ == 'function')) 141 | base = global._(); 142 | } 143 | while (base != null && context.length) 144 | base = base[context.pop().string]; 145 | if (base != null) gatherCompletions(base); 146 | } else { 147 | // If not, just look in the global object, any local scope, and optional additional-context 148 | // (reading into JS mode internals to get at the local and global variables) 149 | for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name); 150 | for (var c = token.state.context; c; c = c.prev) 151 | for (var v = c.vars; v; v = v.next) maybeAdd(v.name) 152 | for (var v = token.state.globalVars; v; v = v.next) maybeAdd(v.name); 153 | if (options && options.additionalContext != null) 154 | for (var key in options.additionalContext) 155 | maybeAdd(key); 156 | if (!options || options.useGlobalScope !== false) 157 | gatherCompletions(global); 158 | forEach(keywords, maybeAdd); 159 | } 160 | return found; 161 | } 162 | }); 163 | -------------------------------------------------------------------------------- /public/page/codemirror/addon/show-hint.css: -------------------------------------------------------------------------------- 1 | .CodeMirror-hints { 2 | position: absolute; 3 | z-index: 10; 4 | overflow: hidden; 5 | list-style: none; 6 | 7 | margin: 0; 8 | padding: 2px; 9 | 10 | -webkit-box-shadow: 2px 3px 5px rgba(0,0,0,.2); 11 | -moz-box-shadow: 2px 3px 5px rgba(0,0,0,.2); 12 | box-shadow: 2px 3px 5px rgba(0,0,0,.2); 13 | border-radius: 3px; 14 | border: 1px solid silver; 15 | 16 | background: white; 17 | font-size: 90%; 18 | font-family: monospace; 19 | 20 | max-height: 20em; 21 | overflow-y: auto; 22 | } 23 | 24 | .CodeMirror-hint { 25 | margin: 0; 26 | padding: 0 4px; 27 | border-radius: 2px; 28 | white-space: pre; 29 | color: black; 30 | cursor: pointer; 31 | } 32 | 33 | li.CodeMirror-hint-active { 34 | background: #08f; 35 | color: white; 36 | } 37 | -------------------------------------------------------------------------------- /public/page/css/cm.css: -------------------------------------------------------------------------------- 1 | .CodeMirror { 2 | background-color: transparent; 3 | color: var(--editor-default); 4 | font-family: var(--editor-font); 5 | font-size: var(--editor-font-size); 6 | font-weight: 400; 7 | height: 100%; 8 | line-height: var(--editor-line-height); 9 | text-rendering: unset; 10 | -webkit-font-smoothing: antialiased; 11 | } 12 | 13 | .CodeMirror-gutters { 14 | background-color: transparent; 15 | border-right: 0; 16 | } 17 | 18 | .CodeMirror-activeline-background, 19 | .CodeMirror-activeline-gutter { 20 | background-color: var(--editor-active-line); 21 | } 22 | 23 | .CodeMirror-linenumber { 24 | color: var(--text-color-primary); 25 | font-size: var(--editor-font-size); 26 | opacity: 0.25; 27 | } 28 | 29 | .CodeMirror-activeline .CodeMirror-linenumber { 30 | opacity: 0.5; 31 | } 32 | 33 | .CodeMirror-cursor { 34 | border-left: 2px solid var(--text-color-secondary); /* check */ 35 | } 36 | 37 | .CodeMirror-hints { 38 | background-color: var(--color-black); 39 | border: 1px solid black; 40 | box-shadow: var(--box-shadow); 41 | font: var(--text-small); 42 | font-family: var(--editor-font); 43 | line-height: 1.5rem; 44 | } 45 | 46 | .CodeMirror-hint { 47 | color: var(--text-color-secondary); 48 | } 49 | 50 | li.CodeMirror-hint-active { 51 | background-color: var(--color-blue); 52 | color: var(--color-black); 53 | } 54 | 55 | .cm-s-default .CodeMirror-selected { 56 | background-color: var(--editor-selected-bg); 57 | } 58 | 59 | div.CodeMirror span.CodeMirror-matchingbracket { 60 | color: var(--editor-matching-bracket-color); 61 | border-bottom: 2px solid var(--editor-matching-bracket-border); 62 | } 63 | 64 | div.CodeMirror span.CodeMirror-nonmatchingbracket { 65 | color: var(--editor-non-matching-bracket); 66 | } 67 | 68 | .CodeMirror .CodeMirror-placeholder, 69 | .cm-s-default .cm-comment { 70 | color: var(--editor-comment); 71 | } 72 | 73 | .cm-s-default .cm-keyword { 74 | color: var(--editor-keyword); 75 | } 76 | 77 | .cm-s-default .cm-def { 78 | color: var(--editor-def); 79 | } 80 | 81 | .cm-s-default .cm-variable, 82 | .cm-s-default .cm-variable-2 { 83 | color: inherit; 84 | } 85 | 86 | .cm-s-default .cm-operator { 87 | color: var(--editor-operator); 88 | } 89 | 90 | .cm-s-default .cm-property { 91 | color: var(--editor-property); 92 | } 93 | 94 | .cm-s-default .cm-string { 95 | color: var(--editor-string); 96 | } 97 | 98 | .cm-s-default .cm-string-2 { 99 | color: var(--editor-string-2); 100 | } 101 | 102 | .cm-s-default .cm-number { 103 | color: var(--editor-number); 104 | } 105 | 106 | .cm-s-default .cm-atom { 107 | color: var(--editor-atom); 108 | } 109 | 110 | .cm-s-default .cm-error { 111 | color: var(--color-red); 112 | } 113 | 114 | .CodeMirror .cm-whitespace::before, 115 | .CodeMirror .CodeMirror-line > span::after, 116 | .CodeMirror .CodeMirror-code > div > pre > span::after { 117 | color: var(--editor-invisible); 118 | } 119 | 120 | .cm-s-default .cm-variable-3, 121 | .cm-s-default .cm-builtin, 122 | .cm-s-default .cm-qualifier, 123 | .cm-s-default .cm-tag { 124 | color: var(--editor-def); 125 | } 126 | 127 | .cm-s-default .cm-searching { 128 | background-color: var(--editor-search-highlight); 129 | padding: 3px 0; 130 | } 131 | 132 | .cm-s-default .cm-search-mark { 133 | border-bottom: 2px solid var(--editor-matching-bracket-border); 134 | } 135 | 136 | /* 137 | .cm-s-default .cm-matchhighlight { 138 | background-color: var(--editor-matched-highlight); 139 | } 140 | */ 141 | 142 | .CodeMirror-lint-markers { 143 | width: 8px; 144 | } 145 | 146 | .CodeMirror-lint-marker { 147 | border-radius: 50%; 148 | cursor: default; 149 | height: 0.375rem; 150 | left: 0.25rem; 151 | position: relative; 152 | width: 0.375rem; 153 | } 154 | 155 | .CodeMirror-lint-marker-error, 156 | .CodeMirror-lint-marker-warning { 157 | background: var(--color-yellow); 158 | top: -1px; 159 | } 160 | 161 | .CodeMirror-lint-marker-error { 162 | background-color: var(--editor-error); 163 | } 164 | 165 | .CodeMirror-lint-marker-multiple { 166 | background: var(--editor-def); 167 | left: 0; 168 | position: absolute; 169 | top: 0; 170 | } 171 | 172 | .CodeMirror-lint-mark-warning { 173 | background: none; 174 | border-bottom: 2px solid var(--color-yellow); 175 | } 176 | 177 | .CodeMirror-lint-mark-error { 178 | background: none; 179 | border-bottom: 2px solid var(--editor-error); 180 | } 181 | 182 | .CodeMirror-lint-tooltip { 183 | background-color: var(--color-black); 184 | border: 1px solid black; 185 | border-radius: var(--border-radius); 186 | box-shadow: var(--box-shadow); 187 | color: var(--text-color-secondary); 188 | font: var(--text-small); 189 | padding: 0.5rem; 190 | transition: opacity 200ms; 191 | } 192 | 193 | .CodeMirror-lint-message-error, 194 | .CodeMirror-lint-message-warning { 195 | background: none; 196 | padding-left: 1rem; 197 | position: relative; 198 | } 199 | 200 | .CodeMirror-lint-message-error::before, 201 | .CodeMirror-lint-message-warning::before { 202 | background-color: var(--color-yellow); 203 | border-radius: 50%; 204 | content: ""; 205 | height: 0.5rem; 206 | left: 0; 207 | position: absolute; 208 | top: 0.25rem; 209 | width: 0.5rem; 210 | } 211 | 212 | .CodeMirror-lint-message-error::before { 213 | background-color: var(--editor-error); 214 | } 215 | -------------------------------------------------------------------------------- /public/page/css/global.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 100%; 3 | height: 100vh; 4 | overflow: hidden; 5 | } 6 | 7 | body { 8 | background-color: var(--color-bg-primary); 9 | color: var(--text-color-primary); 10 | font: var(--text-default); 11 | height: 100%; 12 | text-rendering: optimizeLegibility; 13 | -webkit-font-smoothing: antialiased; 14 | } 15 | 16 | noscript { 17 | display: block; 18 | } 19 | 20 | main { 21 | display: flex; 22 | flex-direction: column; 23 | height: 100%; 24 | } 25 | 26 | a, 27 | .link { 28 | color: var(--color-blue); 29 | cursor: pointer; 30 | text-decoration: underline; 31 | } 32 | 33 | .truncate { 34 | overflow: hidden; 35 | text-overflow: ellipsis; 36 | white-space: nowrap; 37 | } 38 | 39 | button { 40 | border: none; 41 | cursor: pointer; 42 | padding: 0; 43 | user-select: none; 44 | } 45 | 46 | button:disabled { 47 | opacity: var(--opacity-disabled); 48 | pointer-events: none; 49 | } 50 | -------------------------------------------------------------------------------- /public/page/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Userscripts Safari 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /public/popup/css/global.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 100%; 3 | overflow: hidden; 4 | } 5 | 6 | body { 7 | background-color: var(--color-bg-secondary); 8 | color: var(--text-color-primary); 9 | font: var(--text-medium); 10 | letter-spacing: var(--letter-spacing-medium); 11 | position: relative; 12 | text-rendering: optimizeLegibility; 13 | width: 18rem; 14 | -webkit-font-smoothing: antialiased; 15 | } 16 | 17 | /* attempts to detect iOS */ 18 | @media (hover: none) { 19 | html { 20 | font-size: 125%; 21 | } 22 | 23 | body { 24 | width: 100vw; 25 | } 26 | } 27 | 28 | @media screen and (min-device-width: 481px) and (orientation: portrait) and (hover: none) { 29 | body { 30 | width: 18rem; 31 | } 32 | } 33 | 34 | @media screen and (min-device-width: 1024px) and (orientation: landscape) and (hover: none) { 35 | body { 36 | width: 18rem; 37 | } 38 | } 39 | 40 | body.ipados { 41 | width: 18rem; 42 | height: 100vh; 43 | } 44 | 45 | body.ipados #app { 46 | height: 100%; 47 | } 48 | 49 | body.ios { 50 | height: 100vh; 51 | width: 100vw; 52 | } 53 | 54 | body.ios #app { 55 | height: 100%; 56 | } 57 | 58 | noscript { 59 | display: block; 60 | } 61 | 62 | button { 63 | border: none; 64 | cursor: pointer; 65 | padding: 0; 66 | user-select: none; 67 | } 68 | 69 | button:disabled { 70 | opacity: var(--opacity-disabled); 71 | pointer-events: none; 72 | } 73 | 74 | a, 75 | .link { 76 | color: var(--color-blue); 77 | cursor: pointer; 78 | text-decoration: underline; 79 | } 80 | 81 | .truncate { 82 | overflow: hidden; 83 | text-overflow: ellipsis; 84 | white-space: nowrap; 85 | } 86 | -------------------------------------------------------------------------------- /public/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Userscripts Safari 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /public/shared/reset.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::before, 3 | *::after { 4 | box-sizing: border-box; 5 | } 6 | 7 | body, 8 | p { 9 | margin: 0; 10 | } 11 | 12 | ul { 13 | list-style: none; 14 | margin: 0; 15 | padding: 0; 16 | } 17 | 18 | button, 19 | input, 20 | select, 21 | textarea { 22 | font-family: inherit; 23 | font-size: 100%; 24 | line-height: inherit; 25 | margin: 0; 26 | } 27 | 28 | button, 29 | input[type="button"], 30 | input[type="reset"], 31 | input[type="submit"] { 32 | -webkit-appearance: button; 33 | } 34 | 35 | input[type="search"] { 36 | -webkit-appearance: textfield; 37 | } 38 | 39 | ::-webkit-input-placeholder { 40 | color: inherit; 41 | opacity: 0.54; 42 | } 43 | 44 | ::-webkit-search-decoration, 45 | ::-webkit-search-cancel-button, 46 | ::-webkit-search-results-button, 47 | ::-webkit-search-results-decoration { 48 | -webkit-appearance: none; 49 | } 50 | 51 | img { 52 | border-style: none; 53 | } 54 | -------------------------------------------------------------------------------- /public/shared/variables.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --border-radius: 0.188rem; 3 | --box-shadow: 0 0.5rem 1rem 0 rgb(0 0 0 / 0.25); 4 | --color-bg-primary: #323639; 5 | --color-bg-secondary: #2f3337; 6 | --color-script-highlighted: #364049; /* rgba(116, 178, 235, 0.1); */ 7 | --color-black: #1d2023; 8 | --color-blue: #74b1eb; 9 | --color-green: #60f36c; 10 | --color-grey: rgb(255 255 255 / 0.15); 11 | --color-red: #ff453a; 12 | --color-yellow: #e4f360; 13 | --letter-spacing-large: -0.031rem; 14 | --letter-spacing-default: -0.029rem; 15 | --letter-spacing-medium: -0.018rem; 16 | --letter-spacing-small: -0.008rem; 17 | --opacity-disabled: 0.3; 18 | --text-color-primary: rgb(255 255 255); 19 | --text-color-secondary: rgb(255 255 255 / 0.65); 20 | --text-color-disabled: rgb(255 255 255 / 0.4); 21 | --font-family: system-ui, -apple-system, "Helvetica Neue", "Helvetica", sans-serif; 22 | --text-default: 1rem/1.5rem var(--font-family); 23 | --text-large: 1.25rem/1.5rem var(--font-family); 24 | --text-medium: 0.875rem/1.313rem var(--font-family); 25 | --text-small: 0.719rem/1rem var(--font-family); 26 | 27 | /* editor variables */ 28 | --editor-font: monaco, monospace; 29 | --editor-font-size: 14px; 30 | --editor-line-height: 24px; 31 | --editor-invisible: rgb(255 255 255 / 0.15); 32 | --editor-active-line: var(--color-bg-secondary); 33 | --editor-selected-bg: rgb(116 178 235 / 0.35); 34 | --editor-matched-highlight: rgb(116 178 235 / 0.2); 35 | --editor-search-highlight: rgb(255 166 0 / 0.3); 36 | --editor-number: #77e26a; 37 | --editor-comment: rgb(255 255 255 / 0.35); 38 | --editor-def: #efc371; 39 | --editor-default: #cdcfd1; 40 | --editor-keyword: #96c3ed; 41 | --editor-atom: #59ebf5; 42 | --editor-operator: #8c99a7; 43 | --editor-property: #e86c8a; 44 | --editor-string: #f5eea2; 45 | --editor-string-2: #cdabff; 46 | --editor-error: var(--color-red); 47 | --editor-cursor: #e3e7eb; 48 | --editor-matching-bracket-color: #fff; 49 | --editor-matching-bracket-border: var(--editor-number); 50 | --editor-non-matching-bracket: var(--editor-error); 51 | } 52 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import svelte from "rollup-plugin-svelte"; 3 | import resolve from "@rollup/plugin-node-resolve"; 4 | import commonjs from "@rollup/plugin-commonjs"; 5 | import livereload from "rollup-plugin-livereload"; 6 | import css from "rollup-plugin-css-only"; 7 | import inlineSvg from "rollup-plugin-inline-svg"; 8 | import multi from "@rollup/plugin-multi-entry"; 9 | 10 | const production = !process.env.ROLLUP_WATCH; 11 | const directory = process.env.NODE_ENV === "popup" ? "popup" : "page"; 12 | 13 | //let input = [`src/${directory}/dev.js`, `src/${directory}/main.js`]; 14 | let input = ["src/shared/dev.js", `src/${directory}/main.js`]; 15 | if (production) input = `src/${directory}/main.js`; 16 | 17 | function serve() { 18 | let server; 19 | 20 | function toExit() { 21 | if (server) server.kill(0); 22 | } 23 | 24 | return { 25 | writeBundle() { 26 | if (server) return; 27 | server = require("child_process").spawn( 28 | "npm", 29 | ["run", `start:${directory}`, "--", "--dev"], 30 | { 31 | stdio: ["ignore", "inherit", "inherit"], 32 | shell: true 33 | } 34 | ); 35 | process.on("SIGTERM", toExit); 36 | process.on("exit", toExit); 37 | } 38 | }; 39 | } 40 | 41 | export default { 42 | input: input, 43 | output: { 44 | sourcemap: !production, 45 | format: "iife", 46 | name: "app", 47 | file: `public/${directory}/build/bundle.js` 48 | }, 49 | inlineDynamicImports: true, 50 | plugins: [ 51 | multi(), 52 | css({ 53 | output: `public/${directory}/build/lib.css` 54 | }), 55 | copyAndWatch("./public/shared/variables.css", "variables.css"), 56 | copyAndWatch("./public/shared/reset.css", "reset.css"), 57 | inlineSvg({}), 58 | svelte({ 59 | // enable run-time checks when not in production 60 | dev: !production, 61 | // we'll extract any component CSS out into 62 | // a separate file - better for performance 63 | css: css => { 64 | css.write("bundle.css", !production); 65 | } 66 | }), 67 | // If you have external dependencies installed from 68 | // npm, you'll most likely need these plugins. In 69 | // some cases you'll need additional configuration - 70 | // consult the documentation for details: 71 | // https://github.com/rollup/plugins/tree/master/packages/commonjs 72 | resolve({ 73 | browser: true, 74 | dedupe: ["svelte"] 75 | }), 76 | commonjs(), 77 | // In dev mode, call `npm run start` once 78 | // the bundle has been generated 79 | !production && serve(), 80 | // Watch the `public` directory and refresh the 81 | // browser on changes when not in production 82 | !production && livereload({ 83 | watch: [`public/${directory}`] 84 | }) 85 | ], 86 | watch: { 87 | clearScreen: false 88 | } 89 | }; 90 | 91 | /** 92 | * Simple plugin for watching and copying asset for use in app. 93 | * @see {@link https://dev.to/lukap/creating-a-rollup-plugin-to-copy-and-watch-a-file-3hi2 Tutorial} 94 | * @see {@link https://rollupjs.org/guide/en/#build-hooks Rollup Build Hooks} 95 | * @param {string} inputFilePath 96 | * @param {string} outputFilename 97 | */ 98 | function copyAndWatch(inputFilePath, outputFilename) { 99 | return { 100 | name: "copy-and-watch", 101 | async buildStart() { 102 | this.addWatchFile(inputFilePath); 103 | }, 104 | async generateBundle() { 105 | this.emitFile({ 106 | type: "asset", 107 | fileName: outputFilename, 108 | source: fs.readFileSync(inputFilePath) 109 | }); 110 | } 111 | }; 112 | } 113 | -------------------------------------------------------------------------------- /src/page/App.svelte: -------------------------------------------------------------------------------- 1 | 57 | 58 | 59 | 60 | {#if $state.includes("init")} 61 |
62 | {@html logo} 63 | {#if $state.includes("init-error")} 64 | Failed to initialize app, check the browser console 65 | {:else} 66 | Initializing app... 67 | {/if} 68 |
69 | {/if} 70 |
71 | 72 | 73 |
74 |
    75 | {#each $notifications as item (item.id)} 76 | notifications.remove(item.id)} {item}/> 77 | {/each} 78 |
79 | {#if $state.includes("settings")}{/if} 80 | 81 | 122 | -------------------------------------------------------------------------------- /src/page/Components/Editor/EditorSearch.svelte: -------------------------------------------------------------------------------- 1 | 134 | 135 | {#if active} 136 | 150 | {/if} 151 | 152 | 197 | -------------------------------------------------------------------------------- /src/page/Components/Notification.svelte: -------------------------------------------------------------------------------- 1 | 34 | 35 |
  • 44 | 45 |
    46 |
    {@html icon}
    47 | {item.message} 48 | 49 |
    50 |
  • 51 | 52 | 147 | -------------------------------------------------------------------------------- /src/page/Components/Sidebar/SidebarFilter.svelte: -------------------------------------------------------------------------------- 1 | 28 | 29 |
    30 | 40 | 41 |
  • updateSortOrder("lastModifiedAsc")} 44 | > 45 | Last Modified: Asc{sortOrder === "lastModifiedAsc" ? " *" : ""} 46 |
  • 47 |
  • updateSortOrder("lastModifiedDesc")} 50 | > 51 | Last Modified: Desc{sortOrder === "lastModifiedDesc" ? " *" : ""} 52 |
  • 53 |
  • updateSortOrder("nameAsc")} 56 | > 57 | Name: Asc{sortOrder === "nameAsc" ? " *" : ""} 58 |
  • 59 |
  • updateSortOrder("nameDesc")} 62 | > 63 | Name: Desc{sortOrder === "nameDesc" ? " *" : ""} 64 |
  • 65 |
    66 | {#if query} 67 | query = ""} {disabled}/> 68 | {/if} 69 |
    70 | 71 | 118 | -------------------------------------------------------------------------------- /src/page/Components/Sidebar/SidebarItem.svelte: -------------------------------------------------------------------------------- 1 | 26 | 27 |
    37 |
    38 | 39 |
    {data.name}
    40 | 41 |
    42 | {#if description} 43 |
    44 | {description} 45 |
    46 | {/if} 47 |
    48 | 49 | 101 | -------------------------------------------------------------------------------- /src/page/codemirror.js: -------------------------------------------------------------------------------- 1 | import CodeMirror from "codemirror"; 2 | import "codemirror/mode/javascript/javascript.js"; 3 | import "codemirror/mode/css/css.js"; 4 | import "codemirror/addon/comment/comment.js"; 5 | import "codemirror/addon/comment/continuecomment.js"; 6 | import "codemirror/addon/selection/active-line.js"; 7 | import "codemirror/addon/edit/closebrackets.js"; 8 | import "codemirror/addon/edit/matchbrackets.js"; 9 | import "codemirror/addon/hint/show-hint.js"; 10 | import "codemirror/addon/hint/javascript-hint.js"; 11 | import "codemirror/addon/fold/foldcode.js"; 12 | import "codemirror/addon/fold/foldgutter.js"; 13 | import "codemirror/addon/fold/brace-fold.js"; 14 | import "codemirror/addon/fold/indent-fold.js"; 15 | import "codemirror/addon/search/searchcursor.js"; 16 | import "codemirror/addon/search/match-highlighter.js"; // disabled 17 | import "codemirror/addon/lint/lint.js"; 18 | import "codemirror/addon/lint/javascript-lint.js"; 19 | 20 | import "codemirror/lib/codemirror.css"; 21 | import "codemirror/addon/hint/show-hint.css"; 22 | import "codemirror/addon/fold/foldgutter.css"; 23 | import "codemirror/addon/lint/lint.css"; 24 | 25 | import "cm-show-invisibles/lib/show-invisibles.js"; 26 | 27 | export default CodeMirror; 28 | -------------------------------------------------------------------------------- /src/page/main.js: -------------------------------------------------------------------------------- 1 | import App from "./App.svelte"; 2 | 3 | const app = new App({ 4 | target: document.getElementById("app"), 5 | props: {} 6 | }); 7 | 8 | export default app; 9 | -------------------------------------------------------------------------------- /src/page/store.js: -------------------------------------------------------------------------------- 1 | import {writable} from "svelte/store"; 2 | import {uniqueId} from "./utils.js"; 3 | import * as settingsStorage from "../shared/settings.js"; 4 | 5 | function notificationStore() { 6 | const {subscribe, update} = writable([]); 7 | const add = item => { 8 | update(a => { 9 | a.push(item); 10 | return a; 11 | }); 12 | }; 13 | const remove = id => update(a => a.filter(b => b.id !== id)); 14 | return {subscribe, add, remove}; 15 | } 16 | export const notifications = notificationStore(); 17 | 18 | function logStore() { 19 | const {subscribe, set, update} = writable([]); 20 | const add = (message, type, notify) => { 21 | const item = {id: uniqueId(), message: message, time: Date.now(), type: type}; 22 | if (type === "error") notify = true; // always notify on error 23 | if (notify) notifications.add(item); 24 | update(a => { 25 | a.push(item); 26 | return a; 27 | }); 28 | }; 29 | const remove = id => update(a => a.filter(b => b.id !== id)); 30 | const reset = () => set([]); 31 | return {subscribe, add, remove, reset}; 32 | } 33 | export const log = logStore(); 34 | 35 | function stateStore() { 36 | const {subscribe, update} = writable(["init"]); 37 | // store oldState to see how state transitioned 38 | // ex. if (newState === foo && oldState === bar) baz(); 39 | let oldState = []; 40 | const add = stateModifier => update(state => { 41 | // list of acceptable states, mostly for state definition tracking 42 | const states = [ 43 | "init", // the uninitialized app state (start screen) 44 | "init-error", // unique error when initialization fails, shows error on load screen 45 | "settings", // when the settings modal is shown 46 | "items-loading", // when the sidebar items are loading 47 | "saving", // when a file in the editor is being saved 48 | "fetching", // when a new remote file has been added and it being fetched 49 | "trashing", // when deleting the file in the editor 50 | "updating" // when the file in the editor is being updating 51 | ]; 52 | // disallow adding undefined states 53 | if (!states.includes(stateModifier)) return console.error("invalid state"); 54 | // save pre-changed state to oldState var 55 | oldState = [...state]; 56 | // if current state modifier not present, add it to state array 57 | if (!state.includes(stateModifier)) state.push(stateModifier); 58 | // ready state only when no other states present, remove it if present 59 | if (state.includes("ready")) state.splice(state.indexOf("ready"), 1); 60 | log.add(`App state updated to: ${state} from: ${oldState}`, "info", false); 61 | return state; 62 | }); 63 | const remove = stateModifier => update(state => { 64 | // save pre-changed state to oldState var 65 | oldState = [...state]; 66 | // if current state modifier present, remove it from state array 67 | if (state.includes(stateModifier)) state.splice(state.indexOf(stateModifier), 1); 68 | // if no other states, push ready state 69 | if (state.length === 0) state.push("ready"); 70 | log.add(`App state updated to: ${state} from: ${oldState}`, "info", false); 71 | return state; 72 | }); 73 | const getOldState = () => oldState; 74 | return {subscribe, add, getOldState, remove}; 75 | } 76 | export const state = stateStore(); 77 | 78 | function settingsStore() { 79 | const {subscribe, update, set} = writable({}); 80 | const init = async initData => { 81 | // import legacy settings data just one-time 82 | await settingsStorage.legacy_import(); 83 | // for compatibility with legacy getting names only 84 | // once all new name is used, use settingsStorage.get() 85 | const settings = await settingsStorage.legacy_get(); 86 | console.info("store.js settingsStore init", initData, settings); 87 | set(Object.assign({}, initData, settings)); 88 | // sync popup, backgound, etc... settings changes 89 | settingsStorage.onChanged((settings, area) => { 90 | console.log(`store.js storage.${area}.onChanged`, settings); 91 | update(obj => Object.assign(obj, settings)); 92 | }); 93 | }; 94 | const reset = async keys => { 95 | await settingsStorage.reset(keys); 96 | // once all new name is used, use settingsStorage.get() 97 | const settings = await settingsStorage.legacy_get(); 98 | console.info("store.js settingsStore reset", settings); 99 | update(obj => Object.assign(obj, settings)); 100 | }; 101 | const updateSingleSetting_old = (key, value) => { 102 | // blacklist not stored in normal setting object in manifest, so handle differently 103 | if (key === "blacklist") { 104 | // update blacklist on swift side 105 | const message = {name: "PAGE_UPDATE_BLACKLIST", blacklist: value}; 106 | browser.runtime.sendNativeMessage(message, response => { 107 | if (response.error) { 108 | log.add("Failed to save blacklist to disk", "error", true); 109 | } 110 | }); 111 | } 112 | }; 113 | const updateSingleSetting = (key, value) => { 114 | update(settings => (settings[key] = value, settings)); 115 | // for compatibility with legacy setting names only 116 | // once all new name is used, use settingsStorage.set() 117 | settingsStorage.legacy_set({[key]: value}); // Durable Storage 118 | // temporarily keep the old storage method until it is confirmed that all dependencies are removed 119 | updateSingleSetting_old(key, value); 120 | }; 121 | return {subscribe, set, init, reset, updateSingleSetting}; 122 | } 123 | export const settings = settingsStore(); 124 | 125 | export const items = writable([]); 126 | -------------------------------------------------------------------------------- /src/page/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} ms millisecond timestamp 3 | * @returns {string} 4 | */ 5 | export function formatDate(ms) { 6 | const d = new Date(ms); 7 | const yr = new Intl.DateTimeFormat("en", {year: "numeric"}).format(d); 8 | const mo = new Intl.DateTimeFormat("en", {month: "short"}).format(d); 9 | const dd = new Intl.DateTimeFormat("en", {day: "2-digit"}).format(d); 10 | const hr = d.getHours(); 11 | const mn = d.getMinutes(); 12 | return `${mo} ${dd}, ${yr} at ${hr}:${mn}`; 13 | } 14 | 15 | export function uniqueId() { 16 | return Math.random().toString(36).substring(2, 10); 17 | } 18 | 19 | /** 20 | * awaitable function for waiting an arbitrary amount of time 21 | * @param {number} ms the amount of time to wait in milliseconds 22 | * @returns {Promise} 23 | */ 24 | export function wait(ms) { 25 | return new Promise(resolve => setTimeout(resolve, ms)); 26 | } 27 | 28 | // TODO: describe the items array that should get passed to this function 29 | /** 30 | * @param {Array} array 31 | * @param {("lastModifiedAsc"|"lastModifiedDesc"|"nameAsc"|"nameDesc")} order 32 | * @returns 33 | */ 34 | export function sortBy(array, order) { 35 | if (order === "nameAsc") { 36 | array.sort((a, b) => a.name.localeCompare(b.name)); 37 | } else if (order === "nameDesc") { 38 | array.sort((a, b) => b.name.localeCompare(a.name)); 39 | } else if (order === "lastModifiedAsc") { 40 | array.sort((a, b) => (a.lastModified < b.lastModified) ? -1 : 1); 41 | } else if (order === "lastModifiedDesc") { 42 | array.sort((a, b) => (a.lastModified > b.lastModified) ? -1 : 1); 43 | } 44 | // always keep temp file pinned to the top, should only ever have one temp script 45 | // if (array.find(f => f.temp)) array.sort((a, b) => a.temp ? -1 : b.temp ? 1 : 0); 46 | return array; 47 | } 48 | 49 | /** 50 | * 51 | * @param {string} description 52 | * @param {string} name 53 | * @param {("css"|"js")} type 54 | * @returns {string} 55 | */ 56 | export function newScriptDefault(description, name, type) { 57 | if (type === "css") { 58 | return `/* ==UserStyle==\n@name ${name}\n@description ${description}\n@match \n==/UserStyle== */`; 59 | } else if (type === "js") { 60 | return `// ==UserScript==\n// @name ${name}\n// @description ${description}\n// @match *://*/*\n// ==/UserScript==`; 61 | } 62 | } 63 | 64 | /** 65 | * @param {string} str 66 | * @returns {?{code: string, content: str, metablock: string, metadata: {string: string[]}}} 67 | */ 68 | export function parse(str) { 69 | if (typeof str != "string") return null; 70 | const blocksReg = /(?:(\/\/ ==UserScript==[ \t]*?\r?\n([\S\s]*?)\r?\n\/\/ ==\/UserScript==)([\S\s]*)|(\/\* ==UserStyle==[ \t]*?\r?\n([\S\s]*?)\r?\n==\/UserStyle== \*\/)([\S\s]*))/; 71 | const blocks = str.match(blocksReg); 72 | 73 | if (!blocks) return null; 74 | 75 | const metablock = (blocks[1] != null) ? blocks[1] : blocks[4]; 76 | const metas = (blocks[2] != null) ? blocks[2] : blocks[5]; 77 | const code = (blocks[3] != null) ? blocks[3].trim() : blocks[6].trim(); 78 | 79 | const metadata = {}; 80 | const metaArray = metas.split("\n"); 81 | metaArray.forEach(function(m) { 82 | const parts = m.trim().match(/^(?:[ \t]*(?:\/\/)?[ \t]*@)([\w-]+)[ \t]+([^\s]+[^\r\n\t\v\f]*)/); 83 | const parts2 = m.trim().match(/^(?:[ \t]*(?:\/\/)?[ \t]*@)(noframes)[ \t]*$/); 84 | if (parts) { 85 | metadata[parts[1]] = metadata[parts[1]] || []; 86 | metadata[parts[1]].push(parts[2]); 87 | } else if (parts2) { 88 | metadata[parts2[1]] = metadata[parts2[1]] || []; 89 | metadata[parts2[1]].push(true); 90 | } 91 | 92 | }); 93 | // fail if @name is missing or name is empty 94 | if (!metadata.name || metadata.name[0].length < 2) return; 95 | 96 | return { 97 | code: code, 98 | content: str, 99 | metablock: metablock, 100 | metadata: metadata 101 | }; 102 | } 103 | 104 | /** 105 | * @param {string} text editor code 106 | * @returns {{match: boolean, meta: boolean} | {key: string, value: string, text: string}[]} 107 | */ 108 | export function parseMetadata(text) { 109 | const groupsRe = /(\/\/ ==UserScript==[ \t]*?\r?\n([\S\s]*?)\r?\n\/\/ ==\/UserScript==)([\S\s]*)/; 110 | const groups = text.match(groupsRe); 111 | // userscript code doesn't match the regex expression 112 | // could be missing opening/closing tags, malformed 113 | // or missing metadata between opening/closing tags (group 2 in regex exp) 114 | if (!groups) { 115 | return {match: false}; 116 | } 117 | 118 | // userscript code matches but content between opening/closing tag missing 119 | // ex. opening/closing tags present, but newline characters between the tags 120 | const metas = groups[2]; 121 | if (!metas) return {match: true, meta: false}; 122 | 123 | const metadata = []; 124 | const metaArray = metas.split("\n"); 125 | 126 | for (let i = 0; i < metaArray.length; i++) { 127 | const metaRegex = /^(?:[ \t]*(?:\/\/)?[ \t]*@)([\w-]+)[ \t]*([^\s]+[^\r\n\t\v\f]*)?/; 128 | const meta = metaArray[i]; 129 | const parts = meta.match(metaRegex); 130 | if (parts) metadata.push({key: parts[1], value: parts[2], text: parts[0]}); 131 | } 132 | 133 | // if there is content between the opening/closing tags, match will be found 134 | // this additionally checks that there's at least one properly formed key 135 | // if not keys found, assume metadata is missing 136 | // checking that required keys are present will happen elsewhere 137 | if (!Object.keys(metadata).length) return {match: true, meta: false}; 138 | 139 | return metadata; 140 | } 141 | 142 | export const validGrants = new Set([ 143 | "GM.info", 144 | "GM_info", 145 | "GM.addStyle", 146 | "GM.openInTab", 147 | "GM.closeTab", 148 | "GM.setValue", 149 | "GM.getValue", 150 | "GM.deleteValue", 151 | "GM.listValues", 152 | "GM.setClipboard", 153 | "GM.getTab", 154 | "GM.saveTab", 155 | "GM_xmlhttpRequest", 156 | "GM.xmlHttpRequest", 157 | "none" 158 | ]); 159 | 160 | export const validKeys = new Set([ 161 | "author", 162 | "description", 163 | "downloadURL", 164 | "exclude", 165 | "exclude-match", 166 | "grant", 167 | "icon", 168 | "include", 169 | "inject-into", 170 | "match", 171 | "name", 172 | "noframes", 173 | "require", 174 | "run-at", 175 | "updateURL", 176 | "version", 177 | "weight" 178 | ]); 179 | -------------------------------------------------------------------------------- /src/popup/Components/PopupItem.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 |
    11 | 12 |
    {name}
    13 | {#if subframe}
    SUB
    {/if} 14 | 15 |
    16 | 17 | 86 | -------------------------------------------------------------------------------- /src/popup/Components/View.svelte: -------------------------------------------------------------------------------- 1 | 23 | 24 |
    25 |
    26 | {headerTitle} 27 | 32 |
    33 |
    34 | {#if loading && showLoaderOnDisabled} 35 | 36 | {:else} 37 |
    Slot content is required...
    38 | {/if} 39 |
    40 |
    41 | 42 | 81 | -------------------------------------------------------------------------------- /src/popup/Components/Views/AllItemsView.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 | {#if allItems.length} 21 |
    22 | {#each list as item (item.filename)} 23 | allItemsToggleItem(item)} 30 | /> 31 | {/each} 32 |
    33 | {:else} 34 |
    No valid files found in directory
    35 | {/if} 36 | 37 | 61 | -------------------------------------------------------------------------------- /src/popup/Components/Views/InstallView.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 |
    11 | {#if installError} 12 |
    13 | {@html iconError} 14 |
    Couldn't install userscript
    15 |

    {installError}

    16 |
    17 | {:else if userscript} 18 |
      19 |
    • {userscript.name}
    • 20 | {#if userscript.description} 21 |
    • {userscript.description}
    • 22 | {/if} 23 | {#if userscript.match} 24 |
    • 25 |
      @match
      26 | {#each userscript.match as match} 27 |
      {match}
      28 | {/each} 29 |
    • 30 | {/if} 31 | {#if userscript.include} 32 |
    • 33 |
      @include
      34 | {#each userscript.include as include} 35 |
      {include}
      36 | {/each} 37 |
    • 38 | {/if} 39 | {#if userscript.require} 40 |
    • 41 |
      @require
      42 | {#each userscript.require as require} 43 |
      {require}
      44 | {/each} 45 |
    • 46 | {/if} 47 | {#if userscript.grant} 48 |
    • 49 |
      @grant
      50 | {#each userscript.grant as grant} 51 |
      {grant}
      52 | {/each} 53 |
    • 54 | {/if} 55 |
    56 |
    57 |
    {@html iconWarn}
    58 |
    Be sure you trust the author before installing. Nefarious code can exploit your security and privacy.
    59 |
    60 |
    61 | 64 | 67 |
    68 | {/if} 69 |
    70 | 71 | 180 | -------------------------------------------------------------------------------- /src/popup/Components/Views/UpdateView.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | {#if updates.length} 11 | {#each updates as item (item.name)} 12 | 13 |
    14 |
    {item.name}
    15 | 16 | Source 17 | 18 | updateSingleClick(item)}> 19 | Update 20 | 21 |
    22 | {/each} 23 |

    Be sure you trust the author before saving remote code to your device.

    24 | 27 | {:else} 28 |
    29 | {@html iconUpdate} 30 |
    31 | There are no file updates available 32 |
    33 | 36 |
    37 |
    38 | {/if} 39 | 40 | 89 | -------------------------------------------------------------------------------- /src/popup/main.js: -------------------------------------------------------------------------------- 1 | import App from "./App.svelte"; 2 | 3 | const app = new App({ 4 | target: document.getElementById("app"), 5 | props: {} 6 | }); 7 | 8 | export default app; 9 | -------------------------------------------------------------------------------- /src/shared/Components/Dropdown.svelte: -------------------------------------------------------------------------------- 1 | 37 | 38 |
    39 | 40 |
      41 | 42 |
    • At least one slot is required...
    • 43 |
      44 |
    45 |
    46 | 47 | 102 | -------------------------------------------------------------------------------- /src/shared/Components/IconButton.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 16 | 17 | 63 | -------------------------------------------------------------------------------- /src/shared/Components/Loader.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 |
    11 | {@html iconLoader} 12 | {#if abort} 13 |
    14 | Fetching resources, cancel request 15 |
    16 | {/if} 17 |
    18 | 19 | 45 | -------------------------------------------------------------------------------- /src/shared/Components/Tag.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
    6 | 7 | 56 | -------------------------------------------------------------------------------- /src/shared/Components/Toggle.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 71 | -------------------------------------------------------------------------------- /src/shared/img/icon-arrow-down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/shared/img/icon-arrow-left.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/shared/img/icon-arrow-up.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/shared/img/icon-check.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/shared/img/icon-clear.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/shared/img/icon-close.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/shared/img/icon-download.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/shared/img/icon-edit.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/shared/img/icon-error.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/shared/img/icon-info.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/shared/img/icon-loader.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/shared/img/icon-loupe.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/shared/img/icon-open.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/shared/img/icon-plus.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/shared/img/icon-power.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/shared/img/icon-refresh.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/shared/img/icon-settings.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/shared/img/icon-sort.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/shared/img/icon-sync.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/shared/img/icon-trash.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/shared/img/icon-update.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/shared/img/icon-warn.svg: -------------------------------------------------------------------------------- 1 | 2 | --------------------------------------------------------------------------------