├── .gitignore ├── icon.png ├── postcss.config.js ├── .vscode ├── settings.json ├── run-webpack.js ├── server.crt ├── server.key ├── getNet.js ├── pack-zip.js ├── start-dev.js └── start-server.js ├── dist ├── 6c667b391e97122c845a.js.LICENSE.txt ├── 7ab53a38be8d6aa4e715.js.LICENSE.txt ├── main.js.LICENSE.txt └── main.js ├── .babelrc ├── plugin.json ├── readme.md ├── package.json ├── webpack.config.js ├── src ├── utils │ ├── Uri.js │ ├── Path.js │ └── Url.js └── main.js └── index.d.ts /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist.zip -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Acode-Foundation/acode-plugin-sass/HEAD/icon.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('autoprefixer')({}) 4 | ] 5 | }; -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "prettier.tabWidth": 2, 3 | "prettier.singleQuote": true, 4 | "prettier.printWidth": 120, 5 | } -------------------------------------------------------------------------------- /dist/6c667b391e97122c845a.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! sass.js - v0.11.1 (f286436) - built 2019-10-20 2 | providing libsass 3.6.2 (4da7c4bd) 3 | via emscripten 1.38.31 (040e49a) 4 | */ 5 | -------------------------------------------------------------------------------- /dist/7ab53a38be8d6aa4e715.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! sass.js - v0.11.1 (f286436) - built 2019-10-20 2 | providing libsass 3.6.2 (4da7c4bd) 3 | via emscripten 1.38.31 (040e49a) 4 | */ 5 | -------------------------------------------------------------------------------- /dist/main.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ 2 | 3 | /*! sass.js - v0.11.1 (f286436) - built 2019-10-20 4 | providing libsass 3.6.2 (4da7c4bd) 5 | via emscripten 1.38.31 (040e49a) 6 | */ 7 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ], 5 | "env": { 6 | "test": { 7 | "presets": [ 8 | "@babel/env" 9 | ] 10 | } 11 | }, 12 | "plugins": [ 13 | "html-tag-js/jsx/jsx-to-tag.js", 14 | "html-tag-js/jsx/syntax-parser.js" 15 | ], 16 | "compact": false 17 | } -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "acode.plugin.sass", 3 | "name": "Live SASS", 4 | "type": "freemium", 5 | "main": "dist/main.js", 6 | "version": "1.0.5", 7 | "readme": "readme.md", 8 | "icon": "icon.png", 9 | "files": [ 10 | "7ab53a38be8d6aa4e715.js", 11 | "382316b7268260cd9ead.js", 12 | "7ab53a38be8d6aa4e715.js.LICENSE.txt", 13 | "main.js.LICENSE.txt", 14 | "6c667b391e97122c845a.js", 15 | "6c667b391e97122c845a.js.LICENSE.txt" 16 | ], 17 | "author": { 18 | "name": "Acode", 19 | "email": "acode@foxdebug.com", 20 | "github": "https://github.com/deadlyjack" 21 | } 22 | } -------------------------------------------------------------------------------- /.vscode/run-webpack.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const { spawn } = require('child_process'); 3 | const path = require('path'); 4 | 5 | const webpack = spawn('npx.cmd', ['webpack', '--mode=development', '--watch'], { cwd: path.resolve(__dirname, '../') }); 6 | 7 | webpack.on('error', (webpackError) => { 8 | if (webpackError) { 9 | console.error(webpackError); 10 | process.exit(1); 11 | } 12 | }); 13 | 14 | webpack.stdout.on('data', (chunk) => { 15 | const stdout = chunk.toString(); 16 | console.log(stdout); 17 | process.send(stdout); 18 | }); 19 | 20 | webpack.stdout.on('error', (error) => { 21 | console.log(error); 22 | }); 23 | 24 | webpack.stderr.on('data', (chunk) => { 25 | const stderr = chunk.toString(); 26 | console.log(stderr); 27 | }); 28 | -------------------------------------------------------------------------------- /.vscode/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICfjCCAecCFGoKwe9jqvLXZUsAKK8R9rBoxQBVMA0GCSqGSIb3DQEBCwUAMH4x 3 | CzAJBgNVBAYTAklOMRMwEQYDVQQIDApBaml0IEt1bWFyMQwwCgYDVQQHDANCU1Ax 4 | DjAMBgNVBAoMBUZYREJHMQwwCgYDVQQLDANERVYxDTALBgNVBAMMBEFqaXQxHzAd 5 | BgkqhkiG9w0BCQEWEG1lQGFqaXRrdW1hci5kZXYwHhcNMjIwODIxMDc0NjI1WhcN 6 | MjMwODIxMDc0NjI1WjB+MQswCQYDVQQGEwJJTjETMBEGA1UECAwKQWppdCBLdW1h 7 | cjEMMAoGA1UEBwwDQlNQMQ4wDAYDVQQKDAVGWERCRzEMMAoGA1UECwwDREVWMQ0w 8 | CwYDVQQDDARBaml0MR8wHQYJKoZIhvcNAQkBFhBtZUBhaml0a3VtYXIuZGV2MIGf 9 | MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQClD9GyID1GNGCdsq8vshf2h/tMUzxW 10 | mWqOoBi8ZSUrQGoZJ4vxSk5+kPkSvaZzj4zs+LnUMqIXPu1KSIXflVRXzAvh5VIE 11 | 7M0ithYYbGPSyviUWpatdLCvLCOPXZPTXc+66mY7RnCVzlBqGKOR2H4goeGWq0zG 12 | Q+V3pAM7gLUJsQIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAHpMjw8nYPtJ20jO2i6M 13 | kJg09Hk91g63HEx4noV8WallM80exNYWJgzNI69UIRdh78QAEknJh43TSF61bVpo 14 | i+mtBHzZLyPv4LHKNN+6eBoMxi58tbWVZXQB4awW6d1AstHY10aSygOmOUKLtGxr 15 | lYt6v4QwWrm3j7oNCDRDwxlj 16 | -----END CERTIFICATE----- 17 | -------------------------------------------------------------------------------- /.vscode/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: DES-EDE3-CBC,F6E1E7807FC07585 4 | 5 | 1RxeEBdxtQ0+Erd+wmDLuaHy07d81+5uqCBZ1FVkzFOReCwDHFvqT9pyo00soIBJ 6 | ECcUOQyVoV7XyQKZVna+XwQJ8WoiF7R0dVeP7q1E8whFhVD+ybnwvCHSe9Zv1DTo 7 | 8R74rrAqRRKOf0aFEt2DR3sO9vdljOQY0JSTOefFisJs++FSDGSMPzyoUjyGzix+ 8 | jOcbA9BjPoossVRNSNta9q7IMZRvYnF+mqbeKrlQ7dDV6BBCICJ15syzp0FFZcry 9 | 7Upmstp+HtFphDr1ABaXlbSzPIzj+lYBro4vV4v/FuyGigwzYhiftTzypz0sVV2u 10 | yOSIGkQkNrg+0iaD35BuLzuZnKvlmjwBeFL0xlN0oh2yUSqveTUwiyGXhJxqjuKe 11 | lK9LEkKFtkj+BB0gwKy0aHNYM7Z3F2FfNGd/FlxxEbZMfORm03W/I3ploJLKk6kO 12 | H69Rkh+5lPsO0q89YBuqptiJH4cgKSup+yWH8LASbz+nuxLEKJTasJZJFEFxO62v 13 | gVHARgwv/V5HYqE4FF860mQs/ZiRVJfTN1HWZ4OpEHjJMuDhWLCyqxHeLMvL8nxd 14 | 5qm9cGoguHWmv7JLe/R238AZhYg6eBybg+WAqOJZ2LdMQjAKFa5+oWezCAk1uLI9 15 | v12C5EBYZFI7znx2C4A+YAN7a3HAf+p6o467c1aL/8CQdb37soSpdnAKApx1uFBp 16 | TBxPrNXBOkND/bdU/w4E1lqMPg5KPFNn3gYe7YTB0fG4YqrBfpA0uswBdNHf4u4E 17 | u2Q99Fw9dIsj/BMkwFxTWM0Mb119VPyZm5nd5L4Y0GZmhND4UyVV0A== 18 | -----END RSA PRIVATE KEY----- 19 | -------------------------------------------------------------------------------- /.vscode/getNet.js: -------------------------------------------------------------------------------- 1 | const { networkInterfaces } = require('os'); 2 | 3 | module.exports = async (mode = 'dev') => { 4 | const { WiFi, Ethernet } = getIp(); 5 | const [ip] = WiFi || Ethernet; 6 | const port = '5500'; 7 | const src = `https://${ip || '10.0.0'}:${port}`; 8 | console.log('Server starting at: ', src); 9 | return { ip, port }; 10 | }; 11 | 12 | function getIp() { 13 | const nets = networkInterfaces(); 14 | const results = {}; // Or just '{}', an empty object 15 | 16 | Object.keys(nets).forEach((name) => { 17 | nets[name].forEach((net) => { 18 | // Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses 19 | // 'IPv4' is in Node <= 17, from 18 it's a number 4 or 6 20 | const familyV4Value = typeof net.family === 'string' ? 'IPv4' : 4; 21 | if (net.family === familyV4Value && !net.internal) { 22 | if (!results[name]) { 23 | results[name] = []; 24 | } 25 | results[name].push(net.address); 26 | } 27 | }); 28 | }); 29 | return results; 30 | } 31 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # SASS compiler 2 | 3 | This plugin compile scss to css on file save. **Note**, this plugin is monetized with ads in _free version_ of the app. 4 | 5 | ## Updates 6 | 7 | ### 1.0.5 8 | 9 | - Bug fixes 10 | - Added settings 11 | - Fix error reporting 12 | 13 | ## Ignore file 14 | 15 | To prevent file from compiling add `//--ignore-compile` at the top of your scss file. 16 | 17 | ## Settings 18 | 19 | To edit settings, search command 'live sass settings' in command palette. 20 | 21 | - style: Format output: nested, expanded, compact, compressed 22 | - precision: Decimal point precision for outputting fractional numbers 23 | - comments: If you want inline source comments 24 | - indent: String to be used for indentation 25 | - linefeed: String to be used to for line feeds 26 | - sourceMapContents: Embed included contents in maps 27 | - sourceMapEmbed: Embed sourceMappingUrl as data uri 28 | - sourceMapOmitUrl: Disable sourceMappingUrl in css output 29 | 30 | - outputDir: Path to write output file 31 | - watch: Weather to comple scss/sass file on save 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "acode-plugin-sass", 3 | "version": "1.0.5", 4 | "description": "SASS compiler for Acode.", 5 | "main": "dist/main.js", 6 | "repository": "https://github.com/deadlyjack/acode-plugin.git", 7 | "author": "Ajit ", 8 | "license": "MIT", 9 | "dependencies": { 10 | "html-tag-js": "^1.1.36", 11 | "sass.js": "^0.11.1", 12 | "url-parse": "^1.5.10" 13 | }, 14 | "devDependencies": { 15 | "@babel/cli": "^7.20.7", 16 | "@babel/core": "^7.20.12", 17 | "@babel/preset-env": "^7.20.2", 18 | "babel-loader": "^9.1.2", 19 | "jszip": "^3.10.1", 20 | "live-server": "^1.2.2", 21 | "webpack": "^5.76.0", 22 | "webpack-cli": "^5.0.1" 23 | }, 24 | "scripts": { 25 | "build": "webpack", 26 | "build-release": "webpack --mode production", 27 | "start-dev": "node .vscode/start-dev" 28 | }, 29 | "browserslist": [ 30 | "> 0.25%, not dead" 31 | ], 32 | "resolutions": { 33 | "terser": ">=5.16.3 ", 34 | "glob-parent": ">=5.1.2" 35 | } 36 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const { exec } = require('child_process'); 2 | const path = require('path'); 3 | 4 | module.exports = (env, options) => { 5 | const { mode = 'development' } = options; 6 | const jsLoader = [ 7 | 'html-tag-js/jsx/tag-loader.js', 8 | { 9 | loader: 'babel-loader', 10 | options: { 11 | presets: ['@babel/preset-env'], 12 | }, 13 | } 14 | ]; 15 | const rules = [ 16 | { 17 | test: /\.m?js$/, 18 | exclude: /(node_modules)/, 19 | use: jsLoader, 20 | }, 21 | ]; 22 | 23 | const main = { 24 | mode, 25 | entry: { 26 | main: './src/main.js', 27 | }, 28 | output: { 29 | path: path.resolve(__dirname, 'dist'), 30 | filename: '[name].js', 31 | chunkFilename: '[name].js', 32 | }, 33 | module: { 34 | rules, 35 | }, 36 | plugins: [ 37 | { 38 | apply: (compiler) => { 39 | compiler.hooks.afterDone.tap('pack-zip', () => { 40 | // run pack-zip.js 41 | exec('node .vscode/pack-zip.js', (err, stdout, stderr) => { 42 | if (err) { 43 | console.error(err); 44 | return; 45 | } 46 | console.log(stdout); 47 | }); 48 | }); 49 | } 50 | } 51 | ], 52 | }; 53 | 54 | return [main]; 55 | } -------------------------------------------------------------------------------- /.vscode/pack-zip.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const jszip = require('jszip'); 4 | 5 | const iconFile = path.join(__dirname, '../icon.png'); 6 | const pluginJSON = path.join(__dirname, '../plugin.json'); 7 | const distFolder = path.join(__dirname, '../dist'); 8 | let readmeDotMd = path.join(__dirname, '../readme.md'); 9 | 10 | if (!fs.existsSync(readmeDotMd)) { 11 | readmeDotMd = path.join(__dirname, '../README.md'); 12 | } 13 | 14 | // create zip file of dist folder 15 | 16 | const zip = new jszip(); 17 | 18 | zip.file('icon.png', fs.readFileSync(iconFile)); 19 | zip.file('plugin.json', fs.readFileSync(pluginJSON)); 20 | zip.file('readme.md', fs.readFileSync(readmeDotMd)); 21 | 22 | loadFile('', distFolder); 23 | 24 | zip 25 | .generateNodeStream({ type: 'nodebuffer', streamFiles: true }) 26 | .pipe(fs.createWriteStream(path.join(__dirname, '../dist.zip'))) 27 | .on('finish', () => { 28 | console.log('dist.zip written.'); 29 | }); 30 | 31 | function loadFile(root, folder) { 32 | const distFiles = fs.readdirSync(folder); 33 | distFiles.forEach((file) => { 34 | 35 | const stat = fs.statSync(path.join(folder, file)); 36 | 37 | if (stat.isDirectory()) { 38 | zip.folder(file); 39 | loadFile(path.join(root, file), path.join(folder, file)); 40 | return; 41 | } 42 | 43 | if (!/LICENSE.txt/.test(file)) { 44 | zip.file(path.join(root, file), fs.readFileSync(path.join(folder, file))); 45 | } 46 | }); 47 | } -------------------------------------------------------------------------------- /.vscode/start-dev.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const { fork, spawn } = require('child_process'); 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const pluginJson = require('../plugin.json'); 6 | 7 | main(); 8 | 9 | async function main() { 10 | let serverStarted = false; 11 | console.log('+--------------+'); 12 | console.log('| Starting dev |'); 13 | console.log('+--------------+'); 14 | const webpack = fork(path.resolve(__dirname, './run-webpack.js')); 15 | webpack.on('message', (chunk) => { 16 | if (chunk.search(/compiled\ssuccessfully/)) { 17 | if (!serverStarted) { 18 | startServer(); 19 | serverStarted = true; 20 | } 21 | 22 | moveFiles(); 23 | } 24 | }); 25 | 26 | webpack.on('error', (err) => { 27 | console.log('WEBPACK ERROR', err); 28 | webpack.kill(1); 29 | process.exit(1); 30 | }); 31 | } 32 | 33 | async function startServer() { 34 | const server = fork(path.resolve(__dirname, './start-server.js')); 35 | server.on('error', (err) => { 36 | console.log('SERVER ERROR', err); 37 | server.kill(1); 38 | process.exit(1); 39 | }); 40 | } 41 | 42 | function moveFiles() { 43 | let fileUpdate = false; 44 | const files = fs.readdirSync(path.resolve(__dirname, '../dist')); 45 | files.forEach((file) => { 46 | if (file === 'main.js') return; 47 | if (pluginJson.files.includes(file)) return; 48 | pluginJson.files.push(file); 49 | fileUpdate = true; 50 | }); 51 | 52 | if (fileUpdate) { 53 | fs.writeFileSync(path.resolve(__dirname, '../plugin.json'), JSON.stringify(pluginJson, null, 2)); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /.vscode/start-server.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const liveServer = require('live-server'); 5 | const getNet = require('./getNet'); 6 | 7 | const serverCrt = path.resolve(__dirname, 'server.crt'); 8 | const serverKey = path.resolve(__dirname, 'server.key'); 9 | 10 | main(); 11 | 12 | async function main() { 13 | const { ip: host, port } = await getNet('dev'); 14 | process.cwd = () => __dirname; 15 | liveServer.start({ 16 | open: false, 17 | port, 18 | host, 19 | cors: true, 20 | root: '../', 21 | ignore: 'node_modules,platforms,plugins', 22 | file: 'index.html', 23 | https: { 24 | cert: fs.readFileSync(serverCrt), 25 | key: fs.readFileSync(serverKey), 26 | passphrase: '1234', 27 | }, 28 | middleware: [(req, res, next) => { 29 | const url = req.originalUrl; 30 | const www = '../platforms/android/app/src/main/assets/www/'; 31 | 32 | if (url === '/cordova.js') { 33 | const file = path.resolve(__dirname, www, 'cordova.js'); 34 | sendFile(res, file); 35 | return; 36 | } 37 | 38 | if (url === '/cordova_plugins.js') { 39 | const file = path.resolve(__dirname, www, 'cordova_plugins.js'); 40 | sendFile(res, file); 41 | return; 42 | } 43 | 44 | next(); 45 | }], 46 | }); 47 | 48 | process.send('OK'); 49 | } 50 | 51 | function sendFile(res, filePath) { 52 | if (fs.existsSync(filePath)) { 53 | const stat = fs.statSync(filePath); 54 | 55 | res.writeHead(200, { 56 | 'Content-Type': 'application/javascript', 57 | 'Content-Length': stat.size, 58 | }); 59 | 60 | const readStream = fs.createReadStream(filePath); 61 | readStream.pipe(res); 62 | return; 63 | } 64 | 65 | res.writeHead(404, { 'Content-Type': 'text/plain' }); 66 | res.end(`ERROR cannot get ${filePath}`); 67 | } 68 | -------------------------------------------------------------------------------- /src/utils/Uri.js: -------------------------------------------------------------------------------- 1 | import path from './Path'; 2 | 3 | export default { 4 | /** 5 | * Parse content uri to rootUri and docID 6 | * 7 | * eg. 8 | *```js 9 | * parse("content://.../AA98-181D%3A::.../index.html") 10 | *``` 11 | * `returns` {rootUri: "content://.../AA98-181D%3A", docId: "...index.html"} 12 | * 13 | * @param {string} contentUri 14 | * @returns {{rootUri: string, docId: string, isFileUri: boolean}} 15 | */ 16 | parse(contentUri) { 17 | let rootUri, 18 | docId = ''; 19 | 20 | const DOC_PROVIDER = 21 | /^content:\/\/com\.((?![:<>"\/\\\|\?\*]).)*\.documents\//; 22 | const TREE_URI = 23 | /^content:\/\/com\.((?![:<>"\/\\\|\?\*]).)*\.documents\/tree\//; 24 | const SINGLE_URI = 25 | /^content:\/\/com\.(((?![:<>"\/\\\|\?\*]).)*)\.documents\/document/; 26 | 27 | if (DOC_PROVIDER.test(contentUri)) { 28 | if (TREE_URI.test(contentUri)) { 29 | if (/::/.test(contentUri)) { 30 | [rootUri, docId] = contentUri.split('::'); 31 | } else { 32 | rootUri = contentUri; 33 | docId = decodeURIComponent(contentUri.split('/').slice(-1)[0]); 34 | } 35 | } else if (SINGLE_URI.test(contentUri)) { 36 | const [provider, providerId] = SINGLE_URI.exec(contentUri); 37 | docId = decodeURIComponent(contentUri); //DecodUri 38 | docId = docId.replace(provider, ''); //replace single to tree 39 | docId = path.normalize(docId); //normalize docid 40 | 41 | if (docId.startsWith('/')) docId = docId.slice(1); // remove leading '/' 42 | 43 | rootUri = 44 | `content://com.${providerId}.documents/tree/` + 45 | docId.split(':')[0] + 46 | '%3A'; 47 | } 48 | 49 | return { 50 | rootUri, 51 | docId, 52 | isFileUri: /^file:\/\/\//.test(rootUri), 53 | }; 54 | } else { 55 | throw new Error('Invalid uri format.'); 56 | } 57 | }, 58 | /** 59 | * Formats the five contentUri object to string 60 | * @param {{rootUri: string, docId: string} | String} contentUriObject or rootId 61 | * @param {string} [docId] 62 | * @returns {string} 63 | */ 64 | format(contentUriObject, docId) { 65 | let rootUri; 66 | 67 | if (typeof contentUriObject === 'string') { 68 | rootUri = contentUriObject; 69 | } else { 70 | rootUri = contentUriObject.rootUri; 71 | docId = contentUriObject.docId; 72 | } 73 | 74 | if (docId) return [rootUri, docId].join('::'); 75 | else return rootUri; 76 | }, 77 | /** 78 | * Gets virtual address by replacing root with name i.e. added in file explorer 79 | * @param {string} url 80 | */ 81 | getVirtualAddress(url) { 82 | try { 83 | const storageList = JSON.parse(localStorage.storageList || '[]'); 84 | 85 | const matches = []; 86 | for (let storage of storageList) { 87 | const regex = new RegExp('^' + (storage.uri ?? storage.url)); 88 | matches.push({ 89 | regex, 90 | charMatched: url.length - url.replace(regex, '').length, 91 | storage, 92 | }); 93 | } 94 | 95 | const matched = matches.sort((a, b) => { 96 | return b.charMatched - a.charMatched; 97 | })[0]; 98 | 99 | if (matched) { 100 | const { storage, regex } = matched; 101 | const { name } = storage; 102 | const [base, paths] = url.split('::') 103 | url = base + '/' + paths.split('/').slice(1).join('/'); 104 | return url.replace(regex, name).replace(/\/+/g, '/'); 105 | } 106 | 107 | return url; 108 | } catch (e) { 109 | return url; 110 | } 111 | }, 112 | /** 113 | * Gets primary address of a content url. 114 | * @param {string} url 115 | * @returns {string} 116 | */ 117 | getPrimaryAddress(url) { 118 | const [, primary] = url.split('::primary:'); 119 | return primary; 120 | }, 121 | }; -------------------------------------------------------------------------------- /src/utils/Path.js: -------------------------------------------------------------------------------- 1 | export default { 2 | /** 3 | * The path.dirname() method returns the directory name of a path, 4 | * similar to the Unix dirname command. 5 | * Trailing directory separators are ignored. 6 | * @param {string} path 7 | * @returns {string} 8 | */ 9 | dirname(path) { 10 | if (path.endsWith('/')) path = path.slice(0, -1); 11 | const parts = path.split('/').slice(0, -1); 12 | if (!/^(\.|\.\.|)$/.test(parts[0])) parts.unshift('.'); 13 | const res = parts.join('/'); 14 | 15 | if (!res) return '/'; 16 | else return res; 17 | }, 18 | 19 | /** 20 | * The path.basename() methods returns the last portion of a path, 21 | * similar to the Unix basename command. 22 | * Trailing directory separators are ignored, see path.sep. 23 | * @param {string} path 24 | * @returns {string} 25 | */ 26 | basename(path, ext = '') { 27 | ext = ext || ''; 28 | if (path === '' || path === '/') return path; 29 | const ar = path.split('/'); 30 | const last = ar.slice(-1)[0]; 31 | if (!last) return ar.slice(-2)[0]; 32 | let res = decodeURI(last.split('?')[0] || ''); 33 | if (this.extname(res) === ext) res = res.replace(new RegExp(ext + '$'), ''); 34 | return res; 35 | }, 36 | 37 | /** 38 | * returns the extension of the path, from the last occurrence of the . (period) 39 | * character to end of string in the last portion of the path. 40 | * If there is no . in the last portion of the path, or if there are no . characters 41 | * other than the first character of the basename of path (see path.basename()) , an 42 | * empty string is returned. 43 | * @param {string} path 44 | */ 45 | extname(path) { 46 | const filename = path.split('/').slice(-1)[0]; 47 | if (/.+\..*$/.test(filename)) { 48 | return /(?:\.([^.]*))?$/.exec(filename)[0] || ''; 49 | } 50 | 51 | return ''; 52 | }, 53 | 54 | /** 55 | * returns a path string from an object. 56 | * @param {PathObject} pathObject 57 | */ 58 | format(pathObject) { 59 | let { root, dir, ext, name, base } = pathObject; 60 | 61 | if (base || !ext.startsWith('.')) { 62 | ext = ''; 63 | if (base) name = ''; 64 | } 65 | 66 | dir = dir || root; 67 | 68 | if (!dir.endsWith('/')) dir += '/'; 69 | 70 | return dir + (base || name) + ext; 71 | }, 72 | 73 | /** 74 | * The path.isAbsolute() method determines if path is an absolute path. 75 | * @param {string} path 76 | */ 77 | isAbsolute(path) { 78 | return path.startsWith('/'); 79 | }, 80 | 81 | /** 82 | * Joins the given number of paths 83 | * @param {...string} paths 84 | */ 85 | join(...paths) { 86 | let res = paths.join('/'); 87 | return this.normalize(res); 88 | }, 89 | 90 | /** 91 | * Normalizes the given path, resolving '..' and '.' segments. 92 | * @param {string} path 93 | */ 94 | normalize(path) { 95 | path = path.replace(/\.\/+/g, './'); 96 | path = path.replace(/\/+/g, '/'); 97 | 98 | const resolved = []; 99 | const pathAr = path.split('/'); 100 | 101 | pathAr.forEach((dir) => { 102 | if (dir === '..') { 103 | if (resolved.length) resolved.pop(); 104 | } else if (dir === '.') { 105 | return; 106 | } else { 107 | resolved.push(dir); 108 | } 109 | }); 110 | 111 | return resolved.join('/'); 112 | }, 113 | 114 | /** 115 | * 116 | * @param {string} path 117 | * @returns {PathObject} 118 | */ 119 | parse(path) { 120 | const root = path.startsWith('/') ? '/' : ''; 121 | const dir = this.dirname(path); 122 | const ext = this.extname(path); 123 | const name = this.basename(path, ext); 124 | const base = this.basename(path); 125 | 126 | return { 127 | root, 128 | dir, 129 | base, 130 | ext, 131 | name, 132 | }; 133 | }, 134 | 135 | /** 136 | * Resolve the path eg. 137 | ```js 138 | resolvePath('path/to/some/dir/', '../../dir') //returns 'path/to/dir' 139 | ``` 140 | * @param {...string} paths 141 | */ 142 | resolve(...paths) { 143 | if (!paths.length) throw new Error('resolve(...path) : Arguments missing!'); 144 | 145 | let result = ''; 146 | 147 | paths.forEach((path) => { 148 | if (path.startsWith('/')) { 149 | result = path; 150 | return; 151 | } 152 | 153 | result = this.normalize(this.join(result, path)); 154 | }); 155 | 156 | if (result.startsWith('/')) return result; 157 | else return '/' + result; 158 | }, 159 | 160 | /** 161 | * Gets path for path2 relative to path1 162 | * @param {String} path1 163 | * @param {String} path2 164 | */ 165 | convertToRelative(path1, path2) { 166 | path1 = this.normalize(path1).split('/'); 167 | path2 = this.normalize(path2).split('/'); 168 | 169 | const p1len = path1.length; 170 | const p2len = path2.length; 171 | 172 | let flag = false; 173 | let path = []; 174 | 175 | path1.forEach((dir, i) => { 176 | if (dir === path2[i] && !flag) return; 177 | 178 | path.push(path2[i]); 179 | if (!flag) { 180 | flag = true; 181 | return; 182 | } 183 | 184 | if (flag) path.unshift('..'); 185 | }); 186 | 187 | if (p2len > p1len) path.push(...path2.slice(p1len)); 188 | 189 | return path.join('/'); 190 | }, 191 | }; 192 | -------------------------------------------------------------------------------- /src/utils/Url.js: -------------------------------------------------------------------------------- 1 | import URLParse from 'url-parse'; 2 | import path from './Path'; 3 | import Uri from './Uri'; 4 | 5 | export default { 6 | /** 7 | * Returns basename from a url eg. 'index.html' from 'ftp://localhost/foo/bar/index.html' 8 | * @param {string} url 9 | */ 10 | basename(url) { 11 | url = this.parse(url).url; 12 | const protocol = this.getProtocol(url); 13 | if (protocol === 'content:') { 14 | try { 15 | let { rootUri, docId, isFileUri } = Uri.parse(url); 16 | 17 | if (isFileUri) return this.basename(rootUri); 18 | 19 | if (docId.endsWith('/')) docId = docId.slice(0, -1); 20 | docId = docId.split(':').pop(); 21 | return this.pathname(docId).split('/').pop(); 22 | } catch (error) { 23 | return null; 24 | } 25 | } else { 26 | if (url.endsWith('/')) url = url.slice(0, -1); 27 | return this.pathname(url).split('/').pop(); 28 | } 29 | }, 30 | 31 | /** 32 | * Checks if given urls are same or not 33 | * @param {...String} urls 34 | */ 35 | areSame(...urls) { 36 | let firstUrl = urls[0]; 37 | if (firstUrl.endsWith('/')) firstUrl = firstUrl.slice(0, -1); 38 | return urls.every(url => { 39 | if (url.endsWith('/')) url = url.slice(0, -1); 40 | return firstUrl === url; 41 | }); 42 | }, 43 | 44 | /** 45 | * 46 | * @param {String} url 47 | * returns the extension of the path, from the last occurrence of the . (period) 48 | * character to end of string in the last portion of the path. 49 | * If there is no . in the last portion of the path, or if there are no . 50 | * characters other than the first character of the basename of path (see path.basename()), 51 | * an empty string is returned. 52 | */ 53 | extname(url) { 54 | const name = this.basename(url); 55 | if (name) return path.extname(name); 56 | else return null; 57 | }, 58 | /** 59 | * 60 | * @param {...string} pathnames 61 | * @returns {String} 62 | */ 63 | join(...pathnames) { 64 | if (pathnames.length < 2) 65 | throw new Error('Join(), requires atleast two parameters'); 66 | 67 | let { url, query } = this.parse(pathnames[0]); 68 | 69 | const protocol = (this.PROTOCOL_PATTERN.exec(url) || [])[0] || ''; 70 | 71 | if (protocol === 'content://') { 72 | try { 73 | if (pathnames[1].startsWith('/')) pathnames[1] = pathnames[1].slice(1); 74 | const contentUri = Uri.parse(url); 75 | let [root, pathname] = contentUri.docId.split(':'); 76 | const newDocId = path.join(pathname, ...pathnames.slice(1)); 77 | if (/^content:\/\/com.termux/.test(url)) { 78 | const rootCondition = root.endsWith('/'); 79 | const newDocIdCondition = newDocId.startsWith('/'); 80 | if (rootCondition === newDocIdCondition) { 81 | root = root.slice(0, -1); 82 | } else if (!rootCondition === !newDocIdCondition) { 83 | root += '/'; 84 | } 85 | return `${contentUri.rootUri}::${root}${newDocId}${query}`; 86 | } 87 | return `${contentUri.rootUri}::${root}:${newDocId}${query}`; 88 | } catch (error) { 89 | return null; 90 | } 91 | } else if (protocol) { 92 | url = url.replace(new RegExp('^' + protocol), ''); 93 | pathnames[0] = url; 94 | return protocol + path.join(...pathnames) + query; 95 | } else { 96 | return path.join(url, ...pathnames.slice(1)) + query; 97 | } 98 | }, 99 | /** 100 | * Make url safe by encoding url components 101 | * @param {string} url 102 | */ 103 | safe(url) { 104 | let { url: uri, query } = this.parse(url); 105 | url = uri; 106 | const protocol = (this.PROTOCOL_PATTERN.exec(url) || [])[0] || ''; 107 | if (protocol) url = url.replace(new RegExp('^' + protocol), ''); 108 | const parts = url.split('/').map((part, i) => { 109 | if (i === 0) return part; 110 | return fixedEncodeURIComponent(part); 111 | }); 112 | return protocol + parts.join('/') + query; 113 | 114 | function fixedEncodeURIComponent(str) { 115 | return encodeURIComponent(str).replace(/[!'()*]/g, function (c) { 116 | return '%' + c.charCodeAt(0).toString(16); 117 | }); 118 | } 119 | }, 120 | /** 121 | * Gets pathname from url eg. gets '/foo/bar' from 'ftp://myhost.com/foo/bar' 122 | * @param {string} url 123 | */ 124 | pathname(url) { 125 | if (typeof url !== 'string' || !this.PROTOCOL_PATTERN.test(url)) return url; 126 | 127 | url = url.split('?')[0]; 128 | const protocol = (this.PROTOCOL_PATTERN.exec(url) || [])[0] || ''; 129 | 130 | if (protocol === 'content://') { 131 | try { 132 | const { rootUri, docId, isFileUri } = Uri.parse(url); 133 | if (isFileUri) return this.pathname(rootUri); 134 | else return '/' + (docId.split(':')[1] || docId); 135 | } catch (error) { 136 | return null; 137 | } 138 | } else { 139 | if (protocol) url = url.replace(new RegExp('^' + protocol), ''); 140 | 141 | if (protocol !== 'file:///') 142 | return '/' + url.split('/').slice(1).join('/'); 143 | 144 | return '/' + url; 145 | } 146 | }, 147 | 148 | /** 149 | * Returns dirname from url eg. 'ftp://localhost/foo/' from 'ftp://localhost/foo/bar' 150 | * @param {string} url 151 | */ 152 | dirname(url) { 153 | if (typeof url !== 'string') throw new Error('URL must be string'); 154 | 155 | const urlObj = this.parse(url); 156 | url = urlObj.url; 157 | const protocol = this.getProtocol(url); 158 | 159 | if (protocol === 'content:') { 160 | try { 161 | let { rootUri, docId, isFileUri } = Uri.parse(url); 162 | 163 | if (isFileUri) return this.dirname(rootUri); 164 | else { 165 | if (docId.endsWith('/')) docId = docId.slice(0, -1); 166 | docId = [...docId.split('/').slice(0, -1), ''].join('/'); 167 | return Uri.format(rootUri, docId); 168 | } 169 | } catch (error) { 170 | return null; 171 | } 172 | } else { 173 | if (url.endsWith('/')) url = url.slice(0, -1); 174 | return [...url.split('/').slice(0, -1), ''].join('/') + urlObj.query; 175 | } 176 | }, 177 | 178 | /** 179 | * Parse given url into url and query 180 | * @param {string} url 181 | * @returns {URLObject} 182 | */ 183 | parse(url) { 184 | const [uri, query = ''] = url.split(/(?=\?)/); 185 | return { 186 | url: uri, 187 | query, 188 | }; 189 | }, 190 | 191 | /** 192 | * Formate Url object to string 193 | * @param {object} urlObj 194 | * @param {"ftp:"|"sftp:"|"http:"|"https:"} urlObj.protocol 195 | * @param {string|number} urlObj.hostname 196 | * @param {string} [urlObj.path] 197 | * @param {string} [urlObj.username] 198 | * @param {string} [urlObj.password] 199 | * @param {string|number} [urlObj.port] 200 | * @param {object} [urlObj.query] 201 | */ 202 | formate(urlObj) { 203 | let { protocol, hostname, username, password, path, port, query } = urlObj; 204 | 205 | const enc = (str) => encodeURIComponent(str); 206 | 207 | if (!protocol || !hostname) 208 | throw new Error("Cannot formate url. Missing 'protocol' and 'hostname'."); 209 | 210 | let string = `${protocol}//`; 211 | 212 | if (username && password) string += `${enc(username)}:${enc(password)}@`; 213 | else if (username) string += `${username}@`; 214 | 215 | string += hostname; 216 | 217 | if (port) string += `:${port}`; 218 | 219 | if (path) { 220 | if (!path.startsWith('/')) path = '/' + path; 221 | 222 | string += path; 223 | } 224 | 225 | if (query && typeof query === 'object') { 226 | string += '?'; 227 | 228 | for (let key in query) string += `${enc(key)}=${enc(query[key])}&`; 229 | 230 | string = string.slice(0, -1); 231 | } 232 | 233 | return string; 234 | }, 235 | /** 236 | * Returns protocol of a url e.g. 'ftp:' from 'ftp://localhost/foo/bar' 237 | * @param {string} url 238 | * @returns {"ftp:"|"sftp:"|"http:"|"https:"} 239 | */ 240 | getProtocol(url) { 241 | return (/^([a-z]+:)\/\/\/?/i.exec(url) || [])[1] || ''; 242 | }, 243 | /** 244 | * 245 | * @param {string} url 246 | * @returns {string} 247 | */ 248 | hidePassword(url) { 249 | const { protocol, username, hostname, pathname } = URLParse(url); 250 | if (protocol === 'file:') { 251 | return url; 252 | } else { 253 | return `${protocol}//${username}@${hostname}${pathname}`; 254 | } 255 | }, 256 | PROTOCOL_PATTERN: /^[a-z]+:\/\/\/?/i, 257 | }; 258 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Sass from 'sass.js/dist/sass'; 2 | import plugin from '../plugin.json'; 3 | import Path from './utils/Path'; 4 | import Url from './utils/Url'; 5 | 6 | const { acode } = window; 7 | const { fsOperation } = acode; 8 | const appSettings = acode.require('settings'); 9 | 10 | Sass.setWorkerUrl(new URL('sass.js/dist/sass.worker.js', import.meta.url)); 11 | class AcodePlugin { 12 | $page; 13 | #saveCount = 0; 14 | 15 | constructor() { 16 | let settingsChanged = false; 17 | // traverse all default settings and set if not set or type is different 18 | if (!this.settings) { 19 | appSettings.value[plugin.id] = {}; 20 | } 21 | Object.keys(this.defaultSettings).forEach((key) => { 22 | const type = typeof this.defaultSettings[key]; 23 | if (typeof this.settings[key] !== type) { 24 | settingsChanged = true; 25 | this.settings[key] = this.defaultSettings[key]; 26 | } 27 | }); 28 | if (settingsChanged) appSettings.update(); 29 | this.compile = this.compile.bind(this); 30 | } 31 | 32 | async init($page) { 33 | this.$page = $page; 34 | $page.settitle(plugin.name); 35 | editorManager.on('save-file', this.compile); 36 | editorManager.editor.commands.addCommand({ 37 | name: 'liveSassSettings', 38 | description: 'Live SASS plugin settings', 39 | exec: this.editSettings.bind(this), 40 | }); 41 | } 42 | 43 | async destroy() { 44 | editorManager.off('save-file', this.compile); 45 | editorManager.editor.commands.removeCommand('liveSassSettings'); 46 | } 47 | 48 | async compile(file) { 49 | const { location, name, session } = file; 50 | if (!location || !/\.(scss|sass)$/.test(name)) return; 51 | 52 | try { 53 | const text = session.getValue(); 54 | 55 | if (/^\/\/--ignore-compile/.test(text)) return; 56 | 57 | const sass = new Sass(); 58 | const settings = appSettings.value[plugin.id]; 59 | let indentedSyntax = false; 60 | if (/\.sass$/.test(name)) indentedSyntax = true; 61 | sass.options( 62 | { 63 | style: Sass.style[settings.style], 64 | precision: settings.precision, 65 | comments: settings.comments, 66 | indent: this.indent, 67 | linefeed: this.linefeed, 68 | sourceMapContents: settings.sourceMapContents, 69 | sourceMapEmbed: settings.sourceMapEmbed, 70 | sourceMapOmitUrl: settings.sourceMapOmitUrl, 71 | indentedSyntax, 72 | }, 73 | () => { 74 | sass.importer(async (req, res) => { 75 | if (!req.current) return; 76 | try { 77 | const file = Url.join(location, req.current); 78 | const text = await fsOperation(file).readFile('utf8'); 79 | res({ 80 | content: text, 81 | }); 82 | } catch (error) { 83 | res({ 84 | error: `${req.current} not found`, 85 | }); 86 | } 87 | }); 88 | 89 | sass.compile(text, async (res) => { 90 | const cssname = file.name.replace(/scss$/, 'css'); 91 | let css = Url.join(file.location, cssname); 92 | 93 | if (settings.outputDir) { 94 | css = Url.join(file.location, settings.outputDir, cssname); 95 | } 96 | 97 | const cssfs = fsOperation(css); 98 | 99 | if (res.status) { 100 | this.$page.content = ; 110 | window.toast('Error occured while compiling ' + name); 111 | this.$page.show(); 112 | } 113 | 114 | if (res.text) { 115 | if (!(await cssfs.exists())) { 116 | await this.createFileRecursive(file.location, Path.join(settings.outputDir, cssname)); 117 | } 118 | await cssfs.writeFile(res.text); 119 | } 120 | sass.destroy(); 121 | 122 | if (!IS_FREE_VERSION) { 123 | return; 124 | } 125 | 126 | ++this.#saveCount; 127 | if (this.#saveCount === 4) { 128 | window.toast('Ad coming up.'); 129 | this.#saveCount = 0; 130 | 131 | if (!(await window.iad?.isLoaded())) { 132 | await window.iad?.load(); 133 | } 134 | 135 | window.iad?.show(); 136 | } 137 | }); 138 | } 139 | ); 140 | } catch (error) { 141 | toast('Error occured while compiling ' + name); 142 | } 143 | } 144 | 145 | async createFileRecursive(parent, dir) { 146 | if (typeof dir === 'string') { 147 | dir = dir.split('/'); 148 | } 149 | dir = dir.filter((d) => d); 150 | const cd = dir.shift(); 151 | const newParent = Url.join(parent, cd); 152 | if (!(await fsOperation(newParent).exists())) { 153 | if (dir.length) { 154 | await fsOperation(parent).createDirectory(cd); 155 | } else { 156 | await fsOperation(parent).createFile(cd); 157 | } 158 | } 159 | if (dir.length) { 160 | await this.createFileRecursive(newParent, dir); 161 | } 162 | } 163 | 164 | async editSettings() { 165 | const file = fsOperation(appSettings.settingsFile); 166 | const { name } = await file.stat(); 167 | const text = await file.readFile('utf8'); 168 | const lines = text.split('\n'); 169 | let row = 0; 170 | let column = 0; 171 | 172 | const regex = new RegExp(`"${plugin.id}"`); 173 | lines.find((line) => { 174 | const res = regex.test(line); 175 | row += 1; 176 | column = line.length; 177 | return res; 178 | }); 179 | 180 | acode.newEditorFile(name, { 181 | text, 182 | uri: appSettings.settingsFile, 183 | render: true, 184 | }); 185 | 186 | editorManager.editor.moveCursorTo(row, column); 187 | editorManager.editor.scrollToRow(row); 188 | } 189 | 190 | get settingsJson() { 191 | const list = [ 192 | { 193 | key: 'outputDir', 194 | text: 'Output directory', 195 | value: this.settings.outputDir, 196 | prompt: 'Output directory', 197 | promptType: 'text', 198 | promptOptions: { 199 | placeholder: 'e.g. css', 200 | // should valid folder name 201 | match: /^[a-zA-Z0-9-_]+$/, 202 | } 203 | }, 204 | { 205 | key: 'watch', 206 | text: 'Compile on save', 207 | checkbox: this.settings.watch, 208 | }, 209 | { 210 | key: 'style', 211 | text: 'Output style', 212 | select: ['nested', 'expanded', 'compact', 'compressed'], 213 | value: this.settings.style, 214 | }, 215 | { 216 | key: 'precision', 217 | text: 'Precision', 218 | value: this.settings.precision, 219 | prompt: 'Precision', 220 | promptType: 'number', 221 | promptOptions: { 222 | placeholder: 'e.g. 5', 223 | // should be a number 224 | match: /^[0-9]+$/, 225 | }, 226 | }, 227 | { 228 | key: 'comments', 229 | text: 'Include comments', 230 | checkbox: this.settings.comments, 231 | }, 232 | { 233 | key: 'indent', 234 | text: 'Indent type', 235 | select: ['space', 'tab'], 236 | value: this.settings.indent, 237 | }, 238 | { 239 | key: 'indentWidth', 240 | text: 'Indent width', 241 | value: this.settings.indentWidth, 242 | prompt: 'Indent width', 243 | promptType: 'number', 244 | promptOptions: { 245 | placeholder: 'e.g. 2', 246 | // should be a number 247 | match: /^[0-9]+$/, 248 | }, 249 | }, 250 | { 251 | key: 'linefeed', 252 | text: 'Linefeed type', 253 | select: ['cr', 'crlf', 'lf', 'lfcr'], 254 | value: this.settings.linefeed, 255 | }, 256 | { 257 | key: 'sourceMapContents', 258 | text: 'Embed source map contents', 259 | checkbox: this.settings.sourceMapContents, 260 | }, 261 | { 262 | key: 'sourceMapEmbed', 263 | text: 'Embed source map', 264 | checkbox: this.settings.sourceMapEmbed, 265 | }, 266 | { 267 | key: 'sourceMapOmitUrl', 268 | text: 'Omit source map url', 269 | checkbox: this.settings.sourceMapOmitUrl, 270 | } 271 | ]; 272 | 273 | return { 274 | list, 275 | cb: (key, value) => { 276 | this.settings[key] = value; 277 | appSettings.update(); 278 | }, 279 | }; 280 | } 281 | 282 | get indent() { 283 | if (this.settings.indent === 'tab') { 284 | return '\t'; 285 | } 286 | return ' '.repeat( 287 | parseInt(this.settings.indentWidth, 10) || 2 288 | ); 289 | } 290 | 291 | get linefeed() { 292 | switch (this.settings.linefeed) { 293 | case 'cr': 294 | return '\r'; 295 | case 'crlf': 296 | return '\r\n'; 297 | case 'lfcr': 298 | return '\n\r'; 299 | default: 300 | return '\n'; 301 | } 302 | } 303 | 304 | get settings() { 305 | return appSettings.value[plugin.id]; 306 | } 307 | 308 | get defaultSettings() { 309 | return { 310 | outputDir: '', 311 | watch: true, 312 | style: 'expanded', 313 | precision: -1, 314 | indent: 'space', 315 | indentWidth: 2, 316 | comments: false, 317 | linefeed: 'lf', 318 | sourceMapContents: true, 319 | sourceMapEmbed: false, 320 | sourceMapOmitUrl: true, 321 | }; 322 | } 323 | } 324 | 325 | if (window.acode) { 326 | const acodePlugin = new AcodePlugin(); 327 | acode.setPluginInit(plugin.id, (baseUrl, $page, { cacheFileUrl, cacheFile }) => { 328 | if (!baseUrl.endsWith('/')) { 329 | baseUrl += '/'; 330 | } 331 | acodePlugin.baseUrl = baseUrl; 332 | acodePlugin.init($page, cacheFile, cacheFileUrl); 333 | }, acodePlugin.settingsJson); 334 | acode.setPluginUnmount(plugin.id, () => { 335 | acodePlugin.destroy(); 336 | }); 337 | } 338 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | type Formatter = { 2 | id: string; 3 | name: string; 4 | exts: Array; 5 | format(): Promise; 6 | }; 7 | 8 | type CancelOption = { 9 | timeout: number; 10 | callback(): void; 11 | }; 12 | 13 | type Loader = { 14 | setTitle(title: string): void; 15 | setMessage(message: string): void; 16 | hide(): void; 17 | show(): void; 18 | destroy(): void; 19 | }; 20 | 21 | type PromptOptions = { 22 | match: RegExp; 23 | required: boolean; 24 | placeholder: string; 25 | test(value: any): boolean; 26 | }; 27 | 28 | type SelectOptions = { 29 | onCancel(): void; 30 | hideOnSelect: boolean; 31 | textTransform: boolean; 32 | default: any; 33 | }; 34 | interface Acode { 35 | readonly exitAppMessage: string; 36 | readonly formatters: Array; 37 | exec(command: string, value?: any): boolean; 38 | setLoadingMessage(message: string): void; 39 | initPlugin(pluginId: string, baseUrl: string, $page: HTMLElement): void; 40 | unmountPlugin(pluginId: string): void; 41 | registerFormatter( 42 | id: string, 43 | extensions: Array, 44 | format: () => Promise, 45 | ): void; 46 | unregisterFormatter(id: string): void; 47 | fsOperation(file: string): FileSystem; 48 | newEditorFile(filename: string, options: object): object; 49 | alert(title: string, message: string, onhide: () => void): void; 50 | loader(title: string, message: string, options: CancelOption): Loader; 51 | prompt( 52 | message: string, 53 | defaultValue: string | number | boolean, 54 | type: string, 55 | options: PromptOptions, 56 | ): Promise; 57 | confirm(title: string, message: string): Promise; 58 | select( 59 | title: string, 60 | options: Array<[value: string, text: string, icon: string] | string>, 61 | config: SelectOptions, 62 | ): Promise; 63 | multiPrompt( 64 | title: string, 65 | inputs: Array, 66 | help: string, 67 | ): Promise>; 68 | fileBrowser( 69 | mode: 'file' | 'folder', 70 | info: string, 71 | openLast: boolean, 72 | ): Promise; 73 | toInternalUrl(url: string): Promise; 74 | $menuToggler: HTMLElement; 75 | $editMenuToggler: HTMLElement; 76 | pluginServer: Server; 77 | webServer: Server; 78 | $quickToolToggler: HTMLElement; 79 | $headerToggler: HTMLElement; 80 | } 81 | 82 | interface fileBrowserSettings { 83 | showHiddenFiles: 'on' | 'off'; 84 | sortByName: 'on' | 'off'; 85 | } 86 | 87 | interface searchSettings { 88 | wrap: boolean; 89 | caseSensitive: boolean; 90 | regExp: boolean; 91 | wholeWord: boolean; 92 | } 93 | 94 | interface Settings { 95 | animation: 'system' | boolean; 96 | autosave: number; 97 | fileBrowser: fileBrowserSettings; 98 | maxFileSize: number; 99 | filesNotAllowed: string[]; 100 | formatter: Map; 101 | search: searchSettings; 102 | lang: string; 103 | fontSize: string; 104 | editorTheme: string; 105 | appTheme: string; 106 | textWrap: boolean; 107 | softTab: boolean; 108 | tabSize: number; 109 | linenumbers: boolean; 110 | formatOnSave: boolean; 111 | linting: boolean; 112 | previewMode: 'browser' | 'inapp'; 113 | showSpaces: boolean; 114 | openFileListPos: 'sidebar' | 'header'; 115 | quickTools: boolean; 116 | editorFont: string; 117 | vibrateOnTap: boolean; 118 | fullscreen: boolean; 119 | smartCompletion: boolean; 120 | floatingButton: boolean; 121 | liveAutoCompletion: boolean; 122 | showPrintMargin: boolean; 123 | scrollbarSize: number; 124 | confirmOnExit: boolean; 125 | customTheme: Map; 126 | customThemeMode: 'light' | 'dark'; 127 | lineHeight: number; 128 | leftMargin: number; 129 | checkFiles: boolean; 130 | desktopMode: boolean; 131 | console: 'legacy' | 'eruda'; 132 | keyboardMode: 'CODE' | 'NORMAL'; 133 | keyboardMode: 'NO_SUGGESTIONS' | 'NO_SUGGESTIONS_AGGRESSIVE' | 'NORMAL'; 134 | showAd: boolean; 135 | disableCache: boolean; 136 | diagonalScrolling: boolean; 137 | reverseScrolling: boolean; 138 | teardropTimeout: number; 139 | teardropSize: 20 | 40 | 60; 140 | scrollSpeed: number; 141 | } 142 | 143 | interface AppSettings { 144 | value: Settings; 145 | update(settings?: Settings, showToast?: boolean): Promise; 146 | update(showToast?: boolean): Promise; 147 | defaultSettings: Settings; 148 | reset(): Promise; 149 | onload: () => void; 150 | onsave: () => void; 151 | loaded: boolean; 152 | isFileAllowed(ext: string): boolean; 153 | on( 154 | eventName: 'reset' | 'update', 155 | callback: (this: Settings, settings: Settings | string) => void, 156 | ): void; 157 | off( 158 | eventName: 'reset' | 'update', 159 | callback: (this: Settings, settings: Settings | string) => void, 160 | ): void; 161 | applyAutoSaveSetting(): void; 162 | applyAnimationSetting(): void; 163 | applyLangSetting(): void; 164 | } 165 | 166 | interface ActionStackOptions { 167 | id: string; 168 | action(): void; 169 | } 170 | 171 | interface ActionStack { 172 | push(options: ActionStackOptions): void; 173 | pop(): ActionStack; 174 | remove(id: string): void; 175 | has(id: string): boolean; 176 | length: number; 177 | /** 178 | * Sets a mark to recently pushed action 179 | */ 180 | setMark(): void; 181 | /** 182 | * Remove all actions that are pushed after marked positions (using `setMark()`) 183 | */ 184 | clearFromMark(): void; 185 | /** 186 | * Callback function when app is to close 187 | */ 188 | onCloseApp: () => void; 189 | } 190 | 191 | interface Fold { 192 | range: AceAjax.Range; 193 | ranges: Array; 194 | placeholder: string; 195 | } 196 | 197 | interface FileStatus { 198 | canRead: boolean; 199 | canWrite: boolean; 200 | exists: boolean; //indicates if file can be found on device storage 201 | isDirectory: boolean; 202 | isFile: boolean; 203 | isVirtual: boolean; 204 | lastModified: number; 205 | length: number; 206 | name: string; 207 | type: string; 208 | uri: string; 209 | } 210 | 211 | interface OriginObject { 212 | origin: string; 213 | query: string; 214 | } 215 | 216 | interface URLObject { 217 | url: string; 218 | query: string; 219 | } 220 | 221 | interface fileData { 222 | file: FileEntry; 223 | data: ArrayBuffer; 224 | } 225 | 226 | interface ExternalFs { 227 | readFile(): Promise; 228 | createFile( 229 | parent: string, 230 | filename: string, 231 | data: string, 232 | ): Promise<'SUCCESS'>; 233 | createDir(parent: string, path: string): Promise<'SUCCESS'>; 234 | delete(filename: string): Promise<'SUCCESS'>; 235 | writeFile(filename: string, content: string): Promise<'SUCCESS'>; 236 | renameFile(src: string, newname: string): Promise<'SUCCESS'>; 237 | copy(src: string, dest: string): Promise<'SUCCESS'>; 238 | move(src: string, dest: string): Promise<'SUCCESS'>; 239 | stats(src: string): Promise; 240 | uuid: string; 241 | } 242 | 243 | interface RemoteFs { 244 | listDir(path: string): Promise>; 245 | readFile(path: string): Promise; 246 | createFile(filename: string, data: string): Promise; 247 | createDir(path: string): Promise; 248 | delete(name: string): Promise; 249 | writeFile(filename: string, content: string): Promise; 250 | rename(src: string, newname: string): Promise; 251 | copyTo(src: string, dest: string): Promise; 252 | currentDirectory(): Promise; 253 | homeDirectory(): Promise; 254 | stats(src: string): Promise; 255 | /** 256 | * Resolve with true if file exists else resolve if false. Rejects if any error is generated. 257 | */ 258 | exists(): Promise; 259 | origin: string; 260 | originObjec: OriginObject; 261 | } 262 | 263 | interface InternalFs { 264 | copyTo(dest: string): Promise; 265 | moveTo(dest: string): Promise; 266 | listDir(path: string): Promise; 267 | createDir(parent: string, dirname: string): Promise; 268 | delete(filename: string): Promise; 269 | readFile(filename: string): Promise; 270 | writeFile( 271 | filename: string, 272 | content: string, 273 | create: boolean, 274 | exclusive: boolean, 275 | ): Promise; 276 | renameFile(src: string, newname: string): Promise; 277 | stats(src: string): Promise; 278 | exists(): Promise; 279 | } 280 | 281 | interface FsEntry { 282 | url: string; 283 | isDirectory: boolean; 284 | isFile: boolean; 285 | } 286 | 287 | interface FileSystem { 288 | lsDir(): Promise>; 289 | readFile(): Promise; 290 | readFile(encoding: string): Promise; 291 | writeFile(content: string): Promise; 292 | createFile(name: string, data: string): Promise; 293 | createDirectory(name: string): Promise; 294 | delete(): Promise; 295 | copyTo(dest: string): Promise; 296 | moveTo(dset: string): Promise; 297 | renameTo(newName: string): Promise; 298 | exists(): Promise; 299 | stat(): Promise; 300 | } 301 | 302 | interface externalStorageData { 303 | path: string; 304 | name: string; 305 | origin: string; 306 | } 307 | 308 | interface elementContainer { 309 | [key: string]: HTMLElement; 310 | } 311 | 312 | interface GistFile { 313 | filename: string; 314 | content: string; 315 | } 316 | 317 | interface GistFiles { 318 | [filename: string]: GistFile; 319 | } 320 | 321 | interface Repository {} 322 | 323 | interface Repo { 324 | readonly sha: string; 325 | name: string; 326 | data: string; 327 | repo: string; 328 | path: string; 329 | branch: 'master' | 'main' | string; 330 | commitMessage: string; 331 | setName(name: string): Promise; 332 | setData(data: string): Promise; 333 | repository: Repository; 334 | } 335 | 336 | interface Gist { 337 | readonly id: string; 338 | readonly isNew: boolean; 339 | files: GistFiles; 340 | setName(name: string, newName: string): Promise; 341 | setData(name: string, text: string): Promise; 342 | addFile(name: string): void; 343 | removeFile(name: string): Promise; 344 | } 345 | 346 | interface GitRecord { 347 | get(sha: string): Promise; 348 | add(gitFileRecord: Repo): void; 349 | remove(sha: string): Repo; 350 | update(sha: string, gitFileRecord: Repo): void; 351 | } 352 | 353 | interface GistRecord { 354 | get(id: string): Gist; 355 | add(gist: any, isNew?: boolean): void; 356 | remove(gist: Gist): Gist; 357 | update(gist: Gist): void; 358 | reset(): void; 359 | } 360 | 361 | interface Window { 362 | restoreTheme(): void; 363 | } 364 | 365 | interface Input { 366 | id: string; 367 | type: 368 | | 'text' 369 | | 'numberic' 370 | | 'tel' 371 | | 'search' 372 | | 'email' 373 | | 'url' 374 | | 'checkbox' 375 | | 'radio' 376 | | 'group' 377 | | 'button'; 378 | match: RegExp; 379 | value: string; 380 | name: string; 381 | required: boolean; 382 | hints(options: Array): void; 383 | placeholder: string; 384 | disabled: boolean; 385 | onclick(this: HTMLElement): void; 386 | } 387 | 388 | interface String { 389 | /** 390 | * Capitalize the string for e.g. converts "this is a string" to "This Is A string" 391 | */ 392 | capitalize(): string; 393 | /** 394 | * Capitalize a character at given index for e.g. 395 | * ```js 396 | * "this is a string".capitalize(0) //"This is a string" 397 | * ``` 398 | */ 399 | capitalize(index: number): string; 400 | /** 401 | * Returns hashcode of the string 402 | */ 403 | hashCode(): string; 404 | /** 405 | * Subtract the string passed in argument from the given string, 406 | * For e.g. ```"myname".subtract("my") //"name"``` 407 | */ 408 | subtract(str: string): string; 409 | } 410 | 411 | /** 412 | * Returns fully decoded url 413 | * @param url 414 | */ 415 | declare function decodeURL(url: string): string; 416 | 417 | /** 418 | * App settings 419 | */ 420 | declare var appSettings: AppSettings; 421 | /** 422 | * Predefined strings for language support 423 | */ 424 | declare var strings: Map; 425 | /** 426 | * Handles back button click 427 | */ 428 | declare var acode: Acode; 429 | 430 | declare var ASSETS_DIRECTORY: string; 431 | declare var CACHE_STORAGE: string; 432 | declare var DATA_STORAGE: string; 433 | declare var PLUGIN_DIR: string; 434 | declare var DOES_SUPPORT_THEME: boolean; 435 | declare var IS_FREE_VERSION: boolean; 436 | declare var KEYBINDING_FILE: string; 437 | declare var ANDROID_SDK_INT: number; 438 | 439 | declare var ace: AceAjax.Ace; 440 | declare var app: HTMLBodyElement; 441 | declare var root: HTMLElement; 442 | declare var freeze: boolean; 443 | declare var saveTimeout: number; 444 | declare var actionStack: ActionStack; 445 | declare var addedFolder: Array; 446 | declare var gitRecord: GitRecord; 447 | declare var gistRecord: GistRecord; 448 | declare var gitRecordFile: string; 449 | declare var gistRecordFile: string; 450 | declare var toastQueue: Array; 451 | declare var toast: (message: string) => void; 452 | -------------------------------------------------------------------------------- /dist/main.js: -------------------------------------------------------------------------------- 1 | /*! For license information please see main.js.LICENSE.txt */ 2 | !function(){var t={187:function(t){t.exports=function(){"use strict";var t={d:function(e,r){for(var n in r)t.o(r,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:r[n]})},o:function(t,e){return Object.prototype.hasOwnProperty.call(t,e)}},e={};function r(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,n=new Array(e);r1&&void 0!==arguments[1]?arguments[1]:{};return"string"==typeof e&&(e={innerHTML:e}),function(t){var e,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if("function"==typeof t)return t(r,arguments.length>2&&void 0!==arguments[2]?arguments[2]:[]);if(t instanceof Node)e=t;else{if("string"!=typeof t)throw new Error("Invalid tag, ",o(t));e=document.createElement(t)}return Object.keys(r).forEach((function(t){var n=r[t];if(void 0!==n)switch(t){case"child":i(e,n);break;case"children":if(!Array.isArray(n))throw new Error("children must be an array of Nodes");n.flat().forEach((function(t){i(e,t)}));break;case"attr":Object.keys(n).forEach((function(t){e.setAttribute(t,n[t])}));break;case"style":case"dataset":Object.keys(n).forEach((function(r){e[t][r]=n[r]}));break;case"ref":n.instanceOfRef&&(n.el=e);break;default:e[t]=n}})),e}(t,e,arguments.length>2&&void 0!==arguments[2]?arguments[2]:[])}return t.d(e,{default:function(){return a}}),Object.defineProperties(a,{get:{value:function(t){return document.querySelector(t)}},getAll:{value:function(t){return n(document.querySelectorAll(t))}},parse:{value:function(t){var e=document.createElement("div");return e.innerHTML=t,1===e.childElementCount?e.firstElementChild:n(e.children)}},text:{value:function(t){return document.createTextNode(t)}},use:{value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",e=t,r=!1,n=document.createTextNode(t),o=[n];return Object.defineProperty(n,"value",{set:function(t){e=t,o.forEach((function(e){e.textContent=t}))},get:function(){return e}}),Object.defineProperty(n,"clone",{value:function(){if(!r)return r=!0,n;var t=n.cloneNode();return o.push(t),t}}),n}}}),e.default}()},129:function(t,e){"use strict";var r=Object.prototype.hasOwnProperty;function n(t){try{return decodeURIComponent(t.replace(/\+/g," "))}catch(t){return null}}function o(t){try{return encodeURIComponent(t)}catch(t){return null}}e.stringify=function(t,e){e=e||"";var n,i,a=[];for(i in"string"!=typeof e&&(e="?"),t)if(r.call(t,i)){if((n=t[i])||null!=n&&!isNaN(n)||(n=""),i=o(i),n=o(n),null===i||null===n)continue;a.push(i+"="+n)}return a.length?e+a.join("&"):""},e.parse=function(t){for(var e,r=/([^=?#&]+)=?([^&]*)/g,o={};e=r.exec(t);){var i=n(e[1]),a=n(e[2]);null===i||null===a||i in o||(o[i]=a)}return o}},418:function(t){"use strict";t.exports=function(t,e){if(e=e.split(":")[0],!(t=+t))return!1;switch(e){case"http":case"ws":return 80!==t;case"https":case"wss":return 443!==t;case"ftp":return 21!==t;case"gopher":return 70!==t;case"file":return!1}return 0!==t}},829:function(t,e){var r,n;!function(o,i){"use strict";r=function(){var t,e=function(){},r=[].slice;function n(e){if(!e&&!t)throw new Error("Sass needs to be initialized with the URL of sass.worker.js - either via Sass.setWorkerUrl(url) or by new Sass(url)");for(var r in t||(t=e),this)"function"==typeof this[r]&&(this[r]=this[r].bind(this));this._callbacks={},this._worker=new Worker(e||t),this._worker.addEventListener("message",this._handleWorkerMessage,!1)}return n.setWorkerUrl=function(e){t=e},n.style={nested:0,expanded:1,compact:2,compressed:3},n.comments={none:0,default:1},n.prototype={style:n.style,comments:n.comments,destroy:function(){this._worker&&this._worker.terminate(),this._worker=null,this._callbacks={},this._importer=null},_handleWorkerMessage:function(t){t.data.command&&this[t.data.command](t.data.args),this._callbacks[t.data.id]&&this._callbacks[t.data.id](t.data.result),delete this._callbacks[t.data.id]},_dispatch:function(t,e){if(!this._worker)throw new Error("Sass worker has been terminated");t.id="cb"+Date.now()+Math.random(),this._callbacks[t.id]=e,this._worker.postMessage(t)},_importerInit:function(t){var e=function(t){this._worker.postMessage({command:"_importerFinish",args:[t]})}.bind(this);try{this._importer(t[0],e)}catch(t){throw e({error:t.message}),t}},importer:function(t,e){if("function"!=typeof t&&null!==t)throw new Error("importer callback must either be a function or null");this._importer=t,this._worker.postMessage({command:"importer",args:[Boolean(t)]}),e&&e()}},"writeFile readFile listFiles removeFile clearFiles lazyFiles preloadFiles options compile compileFile".split(" ").forEach((function(t){n.prototype[t]=function(){var n=r.call(arguments,-1)[0],o=r.call(arguments,0,-1);"function"!=typeof n&&(o.push(n),n=e),this._dispatch({command:t,args:o},n)}})),n.setWorkerUrl("//sass.worker.js"),n},void 0===(n=r.apply(e,[]))||(t.exports=n)}()},564:function(t,e,r){"use strict";var n=r(418),o=r(129),i=/^[\x00-\x20\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+/,a=/[\n\r\t]/g,s=/^[A-Za-z][A-Za-z0-9+-.]*:\/\//,c=/:\d+$/,u=/^([a-z][a-z0-9.+-]*:)?(\/\/)?([\\/]+)?([\S\s]*)/i,l=/^[a-zA-Z]:/;function f(t){return(t||"").toString().replace(i,"")}var p=[["#","hash"],["?","query"],function(t,e){return m(e.protocol)?t.replace(/\\/g,"/"):t},["/","pathname"],["@","auth",1],[NaN,"host",void 0,1,1],[/:(\d*)$/,"port",void 0,1],[NaN,"hostname",void 0,1,1]],h={hash:1,query:1};function d(t){var e,n=("undefined"!=typeof window?window:void 0!==r.g?r.g:"undefined"!=typeof self?self:{}).location||{},o={},i=typeof(t=t||n);if("blob:"===t.protocol)o=new v(unescape(t.pathname),{});else if("string"===i)for(e in o=new v(t,{}),h)delete o[e];else if("object"===i){for(e in t)e in h||(o[e]=t[e]);void 0===o.slashes&&(o.slashes=s.test(t.href))}return o}function m(t){return"file:"===t||"ftp:"===t||"http:"===t||"https:"===t||"ws:"===t||"wss:"===t}function y(t,e){t=(t=f(t)).replace(a,""),e=e||{};var r,n=u.exec(t),o=n[1]?n[1].toLowerCase():"",i=!!n[2],s=!!n[3],c=0;return i?s?(r=n[2]+n[3]+n[4],c=n[2].length+n[3].length):(r=n[2]+n[4],c=n[2].length):s?(r=n[3]+n[4],c=n[3].length):r=n[4],"file:"===o?c>=2&&(r=r.slice(2)):m(o)?r=n[4]:o?i&&(r=r.slice(2)):c>=2&&m(e.protocol)&&(r=n[4]),{protocol:o,slashes:i||m(o),slashesCount:c,rest:r}}function v(t,e,r){if(t=(t=f(t)).replace(a,""),!(this instanceof v))return new v(t,e,r);var i,s,c,u,h,g,b=p.slice(),w=typeof e,x=this,k=0;for("object"!==w&&"string"!==w&&(r=e,e=null),r&&"function"!=typeof r&&(r=o.parse),i=!(s=y(t||"",e=d(e))).protocol&&!s.slashes,x.slashes=s.slashes||i&&e.slashes,x.protocol=s.protocol||e.protocol||"",t=s.rest,("file:"===s.protocol&&(2!==s.slashesCount||l.test(t))||!s.slashes&&(s.protocol||s.slashesCount<2||!m(x.protocol)))&&(b[3]=[/(.*)/,"pathname"]);kt.length)&&(e=t.length);for(var r=0,n=new Array(e);r1&&void 0!==arguments[1]?arguments[1]:"";if(e=e||"",""===t||"/"===t)return t;var r=t.split("/"),n=r.slice(-1)[0];if(!n)return r.slice(-2)[0];var o=decodeURI(n.split("?")[0]||"");return this.extname(o)===e&&(o=o.replace(new RegExp(e+"$"),"")),o},extname:function(t){var e=t.split("/").slice(-1)[0];return/.+\..*$/.test(e)&&/(?:\.([^.]*))?$/.exec(e)[0]||""},format:function(t){var e=t.root,r=t.dir,n=t.ext,o=t.name,i=t.base;return!i&&n.startsWith(".")||(n="",i&&(o="")),(r=r||e).endsWith("/")||(r+="/"),r+(i||o)+n},isAbsolute:function(t){return t.startsWith("/")},join:function(){for(var t=arguments.length,e=new Array(t),r=0;rn&&s.push.apply(s,function(t){if(Array.isArray(t))return a(t)}(r=e.slice(n))||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(r)||function(t,e){if(t){if("string"==typeof t)return a(t,e);var r=Object.prototype.toString.call(t).slice(8,-1);return"Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r?Array.from(t):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?a(t,e):void 0}}(r)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()),s.join("/")}},c=r(564),u=r.n(c);function l(t,e){return function(t){if(Array.isArray(t))return t}(t)||function(t,e){var r=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null!=r){var n,o,i,a,s=[],c=!0,u=!1;try{if(i=(r=r.call(t)).next,0===e){if(Object(r)!==r)return;c=!1}else for(;!(c=(n=i.call(r)).done)&&(s.push(n.value),s.length!==e);c=!0);}catch(t){u=!0,o=t}finally{try{if(!c&&null!=r.return&&(a=r.return(),Object(a)!==a))return}finally{if(u)throw o}}return s}}(t,e)||f(t,e)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function f(t,e){if(t){if("string"==typeof t)return p(t,e);var r=Object.prototype.toString.call(t).slice(8,-1);return"Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r?Array.from(t):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?p(t,e):void 0}}function p(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,n=new Array(e);r"\/\\\|\?\*]).)*)\.documents\/document/;if(/^content:\/\/com\.((?![:<>"\/\\\|\?\*]).)*\.documents\//.test(t)){if(/^content:\/\/com\.((?![:<>"\/\\\|\?\*]).)*\.documents\/tree\//.test(t))if(/::/.test(t)){var o=l(t.split("::"),2);e=o[0],r=o[1]}else e=t,r=decodeURIComponent(t.split("/").slice(-1)[0]);else if(n.test(t)){var i=l(n.exec(t),2),a=i[0],c=i[1];r=(r=decodeURIComponent(t)).replace(a,""),(r=s.normalize(r)).startsWith("/")&&(r=r.slice(1)),e="content://com.".concat(c,".documents/tree/")+r.split(":")[0]+"%3A"}return{rootUri:e,docId:r,isFileUri:/^file:\/\/\//.test(e)}}throw new Error("Invalid uri format.")},format:function(t,e){var r;return"string"==typeof t?r=t:(r=t.rootUri,e=t.docId),e?[r,e].join("::"):r},getVirtualAddress:function(t){try{var e,r=JSON.parse(localStorage.storageList||"[]"),n=[],o=function(t,e){var r="undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(!r){if(Array.isArray(t)||(r=f(t))){r&&(t=r);var n=0,o=function(){};return{s:o,n:function(){return n>=t.length?{done:!0}:{done:!1,value:t[n++]}},e:function(t){throw t},f:o}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var i,a=!0,s=!1;return{s:function(){r=r.call(t)},n:function(){var t=r.next();return a=t.done,t},e:function(t){s=!0,i=t},f:function(){try{a||null==r.return||r.return()}finally{if(s)throw i}}}}(r);try{for(o.s();!(e=o.n()).done;){var i,a=e.value,s=new RegExp("^"+(null!==(i=a.uri)&&void 0!==i?i:a.url));n.push({regex:s,charMatched:t.length-t.replace(s,"").length,storage:a})}}catch(t){o.e(t)}finally{o.f()}var c=n.sort((function(t,e){return e.charMatched-t.charMatched}))[0];if(c){var u=c.storage,p=c.regex,h=u.name,d=l(t.split("::"),2);return(t=d[0]+"/"+d[1].split("/").slice(1).join("/")).replace(p,h).replace(/\/+/g,"/")}return t}catch(e){return t}},getPrimaryAddress:function(t){return l(t.split("::primary:"),2)[1]}};function d(t){return d="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},d(t)}function m(t){return function(t){if(Array.isArray(t))return g(t)}(t)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(t)||v(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function y(t,e){return function(t){if(Array.isArray(t))return t}(t)||function(t,e){var r=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null!=r){var n,o,i,a,s=[],c=!0,u=!1;try{if(i=(r=r.call(t)).next,0===e){if(Object(r)!==r)return;c=!1}else for(;!(c=(n=i.call(r)).done)&&(s.push(n.value),s.length!==e);c=!0);}catch(t){u=!0,o=t}finally{try{if(!c&&null!=r.return&&(a=r.return(),Object(a)!==a))return}finally{if(u)throw o}}return s}}(t,e)||v(t,e)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function v(t,e){if(t){if("string"==typeof t)return g(t,e);var r=Object.prototype.toString.call(t).slice(8,-1);return"Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r?Array.from(t):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?g(t,e):void 0}}function g(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,n=new Array(e);r=0;--o){var i=this.tryEntries[o],a=i.completion;if("root"===i.tryLoc)return n("end");if(i.tryLoc<=this.prev){var s=r.call(i,"catchLoc"),c=r.call(i,"finallyLoc");if(s&&c){if(this.prev=0;--n){var o=this.tryEntries[n];if(o.tryLoc<=this.prev&&r.call(o,"finallyLoc")&&this.prev=0;--e){var r=this.tryEntries[e];if(r.finallyLoc===t)return this.complete(r.completion,r.afterLoc),O(r),f}},catch:function(t){for(var e=this.tryEntries.length-1;e>=0;--e){var r=this.tryEntries[e];if(r.tryLoc===t){var n=r.completion;if("throw"===n.type){var o=n.arg;O(r)}return o}}throw new Error("illegal catch attempt")},delegateYield:function(t,e,r){return this.delegate={iterator:I(t),resultName:e,nextLoc:r},"next"===this.method&&(this.arg=void 0),f}},t}function x(t,e,r,n,o,i,a){try{var s=t[i](a),c=s.value}catch(t){return void r(t)}s.done?e(c):Promise.resolve(c).then(n,o)}function k(t){return function(){var e=this,r=arguments;return new Promise((function(n,o){var i=t.apply(e,r);function a(t){x(i,n,o,a,s,"next",t)}function s(t){x(i,n,o,a,s,"throw",t)}a(void 0)}))}}function S(t){return S="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},S(t)}function E(t,e){for(var r=0;r