├── .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 |
14 |
15 |

16 |
v0.1 (0)
17 |
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 |
137 |
145 | {rangesIndex}/{query ? ranges.length : "?"}
146 | next()}/>
147 | previous()}/>
148 |
149 |
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 |
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 |
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 |
25 | Update All
26 |
27 | {:else}
28 |
29 | {@html iconUpdate}
30 |
31 | There are no file updates available
32 |
33 |
34 | Check Updates
35 |
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 |
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 |
--------------------------------------------------------------------------------