diff --git a/config/config.go b/config/config.go index cd75394..27b8f42 100644 --- a/config/config.go +++ b/config/config.go @@ -31,6 +31,11 @@ type PreFinishCommand struct { RejectOnNoneZeroExit bool } +type FileTypes struct { + Allowed []string + Disallowed []string +} + type Config struct { Server struct { ListenAddress string @@ -54,6 +59,7 @@ type Config struct { IdentifiedMaxAge duration CheckInterval duration } + FileTypes FileTypes PreFinishCommands []PreFinishCommand JwtSecretsByIssuer map[string]string Loggers []LoggerConfig @@ -135,7 +141,7 @@ func CreateMultiLogger(loggerConfigs []LoggerConfig) (*zerolog.Logger, error) { url := loggerCfg.Output.URL switch url.Scheme { case "file": - file, err := os.OpenFile(url.Path + url.Opaque, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0640) + file, err := os.OpenFile(url.Path+url.Opaque, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0640) if err != nil { return nil, err } diff --git a/config/default-config.go b/config/default-config.go index 1c36fac..e146660 100644 --- a/config/default-config.go +++ b/config/default-config.go @@ -66,6 +66,14 @@ CheckInterval = "5m" # "example.com" = "examplesecret" # "169.254.0.0" = "anothersecret" +[FileTypes] +Allowed = [ + # "image/*" +] +Disallowed = [ + # "image/*" +] + # PreFinishCommands allows system commands to be run based on minetype once the file is fully uploaded # but before it is hashed and moved from incomplete so the file can be rejected using RejectOnNoneZeroExit # %FILE% will be replace with the full path to the file within [Storage.Path]/incomplete/ diff --git a/db/db.go b/db/db.go index feef84e..a5e23ca 100644 --- a/db/db.go +++ b/db/db.go @@ -1,8 +1,10 @@ package db import ( + "database/sql" "fmt" "strings" + "sync" "github.com/jmoiron/sqlx" @@ -15,7 +17,8 @@ type DBConfig struct { } type DatabaseConnection struct { - DB *sqlx.DB + DB *sqlx.DB + DBLock sync.RWMutex DBConfig } @@ -24,7 +27,7 @@ func ConnectToDB(log *zerolog.Logger, dbConfig DBConfig) *DatabaseConnection { // Add the default connection options if none are given switch dbConfig.DriverName { case "sqlite3": - dbConfig.DSN += "?_busy_timeout=5000&cache=shared" + dbConfig.DSN += "?_busy_timeout=5000" case "mysql": dbConfig.DSN += "?parseTime=true" } @@ -35,6 +38,10 @@ func ConnectToDB(log *zerolog.Logger, dbConfig DBConfig) *DatabaseConnection { log.Fatal().Err(err).Msg("Could not open database") } + // if dbConfig.DriverName == "sqlite3" { + // db.SetMaxOpenConns(1) + // } + // note that we don't do db.SetMaxOpenConns(1), as we don't want to limit // read concurrency unnecessarily. sqlite will handle write locking on its // own, even across multiple processes accessing the same database file. @@ -44,14 +51,19 @@ func ConnectToDB(log *zerolog.Logger, dbConfig DBConfig) *DatabaseConnection { // networked filesystem return &DatabaseConnection{ - db, - dbConfig, + DB: db, + DBConfig: dbConfig, } } // UpdateRow wraps db.Exec and ensures that exactly one row was affected -func UpdateRow(db *sqlx.DB, query string, args ...interface{}) (err error) { - res, err := db.Exec(query, args...) +func UpdateRow(DBConn *DatabaseConnection, query string, args ...any) (err error) { + if DBConn.DBConfig.DriverName == "sqlite3" { + DBConn.DBLock.Lock() + defer DBConn.DBLock.Unlock() + } + + res, err := DBConn.DB.Exec(query, args...) if err != nil { return } @@ -65,3 +77,21 @@ func UpdateRow(db *sqlx.DB, query string, args ...interface{}) (err error) { } return } + +func QueryRow(DBConn *DatabaseConnection, query string, args ...any) *sql.Row { + if DBConn.DBConfig.DriverName == "sqlite3" { + DBConn.DBLock.RLock() + defer DBConn.DBLock.RUnlock() + } + + return DBConn.DB.QueryRow(query, args...) +} + +func Select(DBConn *DatabaseConnection, dest any, query string, args ...any) (err error) { + if DBConn.DBConfig.DriverName == "sqlite3" { + DBConn.DBLock.RLock() + defer DBConn.DBLock.RUnlock() + } + + return DBConn.DB.Select(dest, query, args...) +} diff --git a/events/tus-events.go b/events/tus-events.go index bb6fa6e..ccee16f 100644 --- a/events/tus-events.go +++ b/events/tus-events.go @@ -5,14 +5,13 @@ import ( "github.com/tus/tusd/cmd/tusd/cli/hooks" "github.com/tus/tusd/pkg/handler" - tusd "github.com/tus/tusd/pkg/handler" ) // how many events can be unread by a listener before everything starts to block const bufferSize = 16 type TusEvent struct { - Info tusd.FileInfo + Info handler.FileInfo Type hooks.HookType } @@ -22,7 +21,7 @@ type TusEventBroadcaster struct { quitChan chan struct{} // closes to signal quitting } -func NewTusEventBroadcaster(handler *tusd.UnroutedHandler) *TusEventBroadcaster { +func NewTusEventBroadcaster(handler *handler.UnroutedHandler) *TusEventBroadcaster { broadcaster := &TusEventBroadcaster{ quitChan: make(chan struct{}), } @@ -58,7 +57,7 @@ func (b *TusEventBroadcaster) Unlisten(listener chan *TusEvent) { b.listeners = b.listeners[:kept] } -func (b *TusEventBroadcaster) readLoop(handler *tusd.UnroutedHandler) { +func (b *TusEventBroadcaster) readLoop(handler *handler.UnroutedHandler) { for { select { case info := <-handler.CompleteUploads: diff --git a/expirer/expirer.go b/expirer/expirer.go index b4f9535..0fa16bf 100644 --- a/expirer/expirer.go +++ b/expirer/expirer.go @@ -3,6 +3,7 @@ package expirer import ( "time" + "github.com/kiwiirc/plugin-fileuploader/db" "github.com/kiwiirc/plugin-fileuploader/shardedfilestore" "github.com/rs/zerolog" ) @@ -55,7 +56,7 @@ func (expirer *Expirer) gc(t time.Time) { Msg("Filestore GC tick") var expiredIds []string - err := expirer.store.DBConn.DB.Select(&expiredIds, ` + err := db.Select(expirer.store.DBConn, &expiredIds, ` SELECT id FROM uploads WHERE deleted = 0 AND ( diff --git a/fileuploader-kiwiirc-plugin/.editorconfig b/fileuploader-kiwiirc-plugin/.editorconfig deleted file mode 100644 index c4cdee6..0000000 --- a/fileuploader-kiwiirc-plugin/.editorconfig +++ /dev/null @@ -1,36 +0,0 @@ -# http://editorconfig.org -root = true - -[*] -indent_style = space -indent_size = 4 -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -# Use 4 spaces for the Python files -[*.py] -indent_size = 4 -max_line_length = 80 - -# The JSON files contain newlines inconsistently -[*.json] -insert_final_newline = ignore - -# Minified JavaScript files shouldn't be changed -[**.min.js] -indent_style = ignore -insert_final_newline = ignore - -# Makefiles always use tabs for indentation -[Makefile] -indent_style = tab - -# Batch files use tabs for indentation -[*.bat] -indent_style = tab - -[*.md] -trim_trailing_whitespace = false - diff --git a/fileuploader-kiwiirc-plugin/.eslintignore b/fileuploader-kiwiirc-plugin/.eslintignore index c65ce31..b0a5c34 100644 --- a/fileuploader-kiwiirc-plugin/.eslintignore +++ b/fileuploader-kiwiirc-plugin/.eslintignore @@ -1,2 +1,2 @@ -dist/ -tests/unit/coverage/ +/node_modules/ +/dist/ diff --git a/fileuploader-kiwiirc-plugin/.eslintrc.js b/fileuploader-kiwiirc-plugin/.eslintrc.js index 222cff9..270d26f 100644 --- a/fileuploader-kiwiirc-plugin/.eslintrc.js +++ b/fileuploader-kiwiirc-plugin/.eslintrc.js @@ -1,46 +1,74 @@ -/* eslint-disable quote-props */ +const utils = require('./build/utils'); module.exports = { root: true, + + env: { + browser: true, + es6: true, + node: true, + }, + + parser: 'vue-eslint-parser', + parserOptions: { parser: '@babel/eslint-parser', - requireConfigFile: false, + ecmaVersion: 2020, + extraFileExtensions: ['.vue'], sourceType: 'module', }, + + plugins: ['@kiwiirc', 'jsdoc'], + extends: [ 'plugin:vue/recommended', + 'eslint:recommended', + '@vue/airbnb', 'standard', ], - env: { - 'browser': true, + + settings: { + 'import/resolver': { + alias: { + map: [ + ['@', utils.pathResolve('src')], + ], + extensions: ['.js', '.vue', '.json'], + }, + }, }, - // required to lint *.vue files - plugins: [ - 'vue', - ], - // add your custom rules here + rules: { - 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', - 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', + 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'warn', + 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'warn', + + '@kiwiirc/class-name-prefix': 'warn', + 'class-methods-use-this': 0, 'comma-dangle': ['error', { - 'arrays': 'always-multiline', - 'objects': 'always-multiline', - 'imports': 'never', - 'exports': 'never', - 'functions': 'ignore', + arrays: 'always-multiline', + objects: 'always-multiline', + imports: 'never', + exports: 'never', + functions: 'ignore', }], 'import/extensions': 0, 'import/no-cycle': 0, - 'import/no-extraneous-dependencies': 0, - 'import/no-unresolved': 0, + 'import/no-unresolved': [2, { + ignore: [ + // These files will not exist if lint is run before the first build + '/res/locales/available\\.json$', + '/static/locales/\\S+\\.json$', + ], + }], 'import/prefer-default-export': 0, 'indent': ['error', 4], + // 'max-len': ['error', { code: 120 }], 'max-classes-per-file': 0, 'no-continue': 0, 'no-else-return': 0, 'no-multi-assign': 0, - 'no-param-reassign': ['error', { 'props': false }], + 'no-param-reassign': ['error', { props: false }], 'no-plusplus': 0, 'no-prototype-builtins': 0, 'no-control-regex': 0, @@ -51,25 +79,72 @@ module.exports = { 'prefer-object-spread': 0, 'prefer-promise-reject-errors': 0, 'prefer-template': 0, + 'quote-props': ['error', 'consistent-as-needed'], 'semi': ['error', 'always'], - 'space-before-function-paren': ['error', 'never'], + 'space-before-function-paren': ['error', { + anonymous: 'always', + named: 'never', + asyncArrow: 'always', + }], 'vue/html-indent': ['error', 4], + 'vue/max-len': [ + 'error', + { + code: 120, + template: 120, + tabWidth: 4, + comments: 120, + ignoreComments: true, + }, + ], 'vue/max-attributes-per-line': 0, + 'vue/multi-word-component-names': 0, 'vue/multiline-html-element-content-newline': 0, - 'vue/no-unused-components': 0, + 'vue/no-mutating-props': ['error', { + shallowOnly: true, + }], 'vue/no-v-html': 0, - 'vue/require-prop-types': 0, 'vue/require-default-prop': 0, + 'vue/require-prop-types': 0, 'vue/singleline-html-element-content-newline': 0, - 'template-curly-spacing': 'off', + 'vuejs-accessibility/anchor-has-content': 0, + 'vuejs-accessibility/click-events-have-key-events': 0, + 'vuejs-accessibility/form-control-has-label': 0, + 'vuejs-accessibility/iframe-has-title': 0, + 'vuejs-accessibility/interactive-supports-focus': 0, + 'vuejs-accessibility/label-has-for': 0, + 'vuejs-accessibility/mouse-events-have-key-events': 0, + 'vuejs-accessibility/media-has-caption': 0, + + // TODO vue3 + 'multiline-ternary': 0, + 'vue/comma-dangle': 0, + 'vue/key-spacing': 0, + 'vue/no-template-target-blank': 0, + 'vue/no-unused-components': 0, + 'vue/object-curly-newline': 0, + 'vue/object-curly-spacing': 0, + 'vue/operator-linebreak': 0, + 'vue/quote-props': 0, + 'vue/space-infix-ops': 0, + 'vue/v-on-event-hyphenation': 0, }, - overrides: [{ - files: [ - '**/__tests__/*.{j,t}s?(x)', - '**/tests/unit/**/*.spec.{j,t}s?(x)', - ], - env: { - jest: true, + overrides: [ + { + files: [ + '**/__tests__/*.{j,t}s?(x)', + '**/tests/unit/**/*.spec.{j,t}s?(x)', + ], + env: { + jest: true, + }, + }, + { + files: ['webpack.config.js', 'build/**/*.js'], + rules: { + 'import/no-extraneous-dependencies': 0, + 'no-console': 0, + }, }, - }], + ], }; diff --git a/fileuploader-kiwiirc-plugin/.gitignore b/fileuploader-kiwiirc-plugin/.gitignore index ef73e2f..42c0c9b 100644 --- a/fileuploader-kiwiirc-plugin/.gitignore +++ b/fileuploader-kiwiirc-plugin/.gitignore @@ -1,5 +1,13 @@ -/node_modules/ -/dist/ -/.cache/ -/.vscode/ -/yarn-error.log +.DS_Store +dist/ +node_modules/ +tests/coverage/ + +# local env files +.env.local +.env.*.local + +# Log files +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/fileuploader-kiwiirc-plugin/.prettierrc.js b/fileuploader-kiwiirc-plugin/.prettierrc.js new file mode 100644 index 0000000..64d5827 --- /dev/null +++ b/fileuploader-kiwiirc-plugin/.prettierrc.js @@ -0,0 +1,9 @@ +module.exports = { + printWidth: 120, + quoteProps: 'consistent', + semi: true, + singleQuote: true, + trailingComma: 'es5', + tabWidth: 4, + jsdocVerticalAlignment: true, +}; diff --git a/fileuploader-kiwiirc-plugin/.stylelintignore b/fileuploader-kiwiirc-plugin/.stylelintignore new file mode 100644 index 0000000..3d2bc62 --- /dev/null +++ b/fileuploader-kiwiirc-plugin/.stylelintignore @@ -0,0 +1,2 @@ +/dist/ +/node_modules/ diff --git a/fileuploader-kiwiirc-plugin/.stylelintrc.js b/fileuploader-kiwiirc-plugin/.stylelintrc.js new file mode 100644 index 0000000..73d10e2 --- /dev/null +++ b/fileuploader-kiwiirc-plugin/.stylelintrc.js @@ -0,0 +1,46 @@ +module.exports = { + extends: ['stylelint-config-standard', 'stylelint-config-standard-scss', 'stylelint-config-recess-order'], + overrides: [ + { + files: ['**/*.vue', '**/*.html'], + customSyntax: 'postcss-html', + }, + ], + rules: { + 'alpha-value-notation': null, + 'color-function-notation': null, + 'declaration-block-no-redundant-longhand-properties': null, + 'declaration-no-important': true, + 'media-feature-range-notation': null, + 'no-descending-specificity': null, + 'no-empty-first-line': null, + 'number-max-precision': null, + 'property-no-vendor-prefix': null, + 'scss/at-rule-no-unknown': [ + true, + { + ignoreAtRules: [ + 'each', + 'else', + 'extends', + 'for', + 'function', + 'if', + 'ignores', + 'include', + 'media', + 'mixin', + 'return', + 'use', + + // Font Awesome 4 + 'fa-font-path', + ], + }, + ], + 'scss/double-slash-comment-empty-line-before': null, + 'scss/double-slash-comment-whitespace-inside': null, + 'selector-class-pattern': null, + 'shorthand-property-no-redundant-values': null, + }, +}; diff --git a/fileuploader-kiwiirc-plugin/.vscode/extensions.json b/fileuploader-kiwiirc-plugin/.vscode/extensions.json new file mode 100644 index 0000000..db7ae10 --- /dev/null +++ b/fileuploader-kiwiirc-plugin/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "vue.volar", + "dbaeumer.vscode-eslint", + "stylelint.vscode-stylelint", + "rvest.vs-code-prettier-eslint" + ] +} diff --git a/fileuploader-kiwiirc-plugin/.vscode/settings.json b/fileuploader-kiwiirc-plugin/.vscode/settings.json new file mode 100644 index 0000000..eb6a994 --- /dev/null +++ b/fileuploader-kiwiirc-plugin/.vscode/settings.json @@ -0,0 +1,23 @@ +{ + "files.eol": "\n", + "files.insertFinalNewline": true, + "files.trimFinalNewlines": true, + "files.trimTrailingWhitespace": true, + "stylelint.validate": [ + "vue", + "css", + "less", + "sass", + "scss", + "postcss" + ], + "[javascript]": { + "editor.defaultFormatter": "rvest.vs-code-prettier-eslint" + }, + "[vue]": { + "editor.defaultFormatter": "rvest.vs-code-prettier-eslint" + }, + "[css]": { + "editor.defaultFormatter": "rvest.vs-code-prettier-eslint" + }, +} diff --git a/fileuploader-kiwiirc-plugin/LICENCE b/fileuploader-kiwiirc-plugin/LICENCE new file mode 100644 index 0000000..0c70af3 --- /dev/null +++ b/fileuploader-kiwiirc-plugin/LICENCE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018 ItsOnlyBinary + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/fileuploader-kiwiirc-plugin/babel.config.js b/fileuploader-kiwiirc-plugin/babel.config.js new file mode 100644 index 0000000..45f7299 --- /dev/null +++ b/fileuploader-kiwiirc-plugin/babel.config.js @@ -0,0 +1,5 @@ +module.exports = { + presets: [ + '@babel/env', + ], +}; diff --git a/fileuploader-kiwiirc-plugin/build/commands/build.js b/fileuploader-kiwiirc-plugin/build/commands/build.js new file mode 100644 index 0000000..2f9eef3 --- /dev/null +++ b/fileuploader-kiwiirc-plugin/build/commands/build.js @@ -0,0 +1,135 @@ +const webpack = require('webpack'); +const minimist = require('minimist'); +const ora = require('ora'); +const chalk = require('chalk'); +const cliui = require('cliui'); +const { rimraf } = require('rimraf'); + +const utils = require('../utils'); +const webpackConfigFunc = require('../../webpack.config'); + +const argv = minimist(process.argv.slice(2)); +const spinner = ora(); + +(async () => { + const webpackConfig = await webpackConfigFunc({}, argv); + + console.log(); + spinner.text = `Building for ${webpackConfig.mode}...`; + spinner.start(); + + rimraf(utils.pathResolve('dist') + '/*', { glob: true }).then(() => { + webpack(webpackConfig, (wpErr, stats) => { + spinner.stop(); + console.log(); + + if (wpErr) { + console.error(wpErr); + console.log(); + process.exit(1); + } + + if (stats.hasErrors()) { + process.exit(1); + } + + const getCompressedAsset = (asset, type) => { + if (!Array.isArray(asset.related)) { + return undefined; + } + return asset.related.find((relAsset) => relAsset.type === type); + }; + + const isJS = (val) => /\.js$/.test(val); + const isCSS = (val) => /\.css$/.test(val); + const assetSorter = (a, b) => { + if (isJS(a.name) && isCSS(b.name)) { + return -1; + } + if (isCSS(a.name) && isJS(b.name)) { + return 1; + } + return b.size - a.size; + }; + + const data = stats.toJson(); + const files = Object.values(data.assetsByChunkName).flat(); + + const out = [ + // Column headers + ['File', 'Size', 'Gzip', 'Brotli'], + ]; + const totals = { + size: 0, + gzip: 0, + brotli: 0, + }; + + data.assets.sort(assetSorter).forEach((asset) => { + if (!asset.emitted) { + return; + } + + const gzipAsset = getCompressedAsset(asset, 'gzipped'); + const brotliAsset = getCompressedAsset(asset, 'brotliCompressed'); + + totals.size += asset.size; + totals.gzip += gzipAsset ? gzipAsset.size : asset.size; + totals.brotli += brotliAsset ? brotliAsset.size : asset.size; + + if (files.includes(asset.name)) { + out.push([ + asset.name, + utils.formatSize(asset.size), + gzipAsset ? utils.formatSize(gzipAsset.size) : '', + brotliAsset ? utils.formatSize(brotliAsset.size) : '', + ]); + } + }); + + out.push([ + 'Totals (including assets)', + utils.formatSize(totals.size), + utils.formatSize(totals.gzip), + utils.formatSize(totals.brotli), + ]); + + const colWidths = out.reduce((acc, row) => { + row.forEach((col, idx) => { + acc[idx] = Math.max(acc[idx] || 0, col.length + 4); + }); + return acc; + }, []); + + const table = cliui(); + out.forEach((row, rowIdx) => { + table.div( + ...row.map((col, colIdx) => ({ + text: (rowIdx === 0 || (rowIdx === out.length - 1 && colIdx === 0)) + ? chalk.cyan.bold(col) + : col, + width: colWidths[colIdx], + padding: (rowIdx === 0 || rowIdx === out.length - 2) + ? [0, 0, 1, 3] + : [0, 0, 0, 3], + })) + ); + }); + + console.log(table.toString()); + console.log(); + console.log(); + console.log( + chalk.bgGreen.black(' DONE '), + `Build Complete. The ${chalk.cyan('dist')} directory is ready to be deployed` + ); + console.log(); + }); + }).catch((rmErr) => { + spinner.stop(); + console.log(); + console.error(rmErr); + console.log(); + process.exit(1); + }); +})(); diff --git a/fileuploader-kiwiirc-plugin/build/commands/dev.js b/fileuploader-kiwiirc-plugin/build/commands/dev.js new file mode 100644 index 0000000..e119256 --- /dev/null +++ b/fileuploader-kiwiirc-plugin/build/commands/dev.js @@ -0,0 +1,63 @@ +const ora = require('ora'); +const chalk = require('chalk'); +const minimist = require('minimist'); +const portfinder = require('portfinder'); +const webpack = require('webpack'); +const WebpackDevServer = require('webpack-dev-server'); + +const utils = require('../utils'); +const webpackConfigFunc = require('../../webpack.config'); + +const argv = minimist(process.argv.slice(2)); +const spinner = ora(); + +(async () => { + const webpackConfig = await webpackConfigFunc({ WEBPACK_SERVE: true }, argv); + + console.log(); + spinner.text = 'Starting development server...'; + spinner.start(); + + const devServerOptions = webpackConfig.devServer; + + const protocol = devServerOptions.https ? 'https' : 'http'; + const host = devServerOptions.host || '0.0.0.0'; + const port = await portfinder.getPortPromise({ + port: devServerOptions.port || 8080, + host, + }); + + Object.assign(devServerOptions, { host, port }); + + const compiler = webpack(webpackConfig); + const server = new WebpackDevServer(devServerOptions, compiler); + + compiler.hooks.done.tap('dev', (stats) => { + spinner.stop(); + + if (stats.hasErrors()) { + return; + } + + console.log(' App running at:'); + if (host === 'localhost' || host.substring(0, 4) === '127.' || host === '0.0.0.0') { + const hostText = (host === '127.0.0.1') ? 'localhost' : host; + console.log(` - Local: ${chalk.cyan(`${protocol}://${hostText}:${port}`)}`); + + if (host !== '0.0.0.0') { + console.log(` - Network: ${chalk.grey('use "--host" to expose')}`); + } + } + if (host !== 'localhost' && host.substring(0, 4) !== '127.') { + const networkIPs = utils.getNetworkIPs(); + networkIPs.forEach((ip) => { + console.log(` - Network: ${chalk.cyan(`${protocol}://${ip}:${port}`)}`); + }); + } + console.log(); + console.log(' Note that the development build is not optimized.'); + console.log(` To create a production build, run ${chalk.cyan('yarn build')}.`); + }); + + await server.start(); +})(); diff --git a/fileuploader-kiwiirc-plugin/build/configs/base.js b/fileuploader-kiwiirc-plugin/build/configs/base.js new file mode 100644 index 0000000..20bc11f --- /dev/null +++ b/fileuploader-kiwiirc-plugin/build/configs/base.js @@ -0,0 +1,102 @@ +const { merge } = require('webpack-merge'); + +const ESLintPlugin = require('eslint-webpack-plugin'); +const ESLintFormatter = require('eslint-formatter-friendly'); +const { VueLoaderPlugin } = require('vue-loader'); +const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); +const FriendlyErrorsWebpackPlugin = require('@soda/friendly-errors-webpack-plugin'); + +const ConvertLocalesPlugin = require('../plugins/webpack/convert-locales'); + +const utils = require('../utils'); +const pkg = require('../../package.json'); + +const cssConfig = require('./css'); + +module.exports = (env, argv, config) => { + let sourceMap; + if (config.mode === 'development') { + sourceMap = env.WEBPACK_SERVE ? 'eval-source-map' : 'source-map'; + } else if (argv.srcmap) { + sourceMap = 'source-map'; + } + + const baseConfig = merge(config, { + context: process.cwd(), + + entry: { + app: './src/fileuploader-entry.js', + }, + + devtool: sourceMap, + + output: { + path: utils.pathResolve('dist'), + publicPath: 'auto', + filename: pkg.name.replace(/^kiwiirc-/, '') + '.js', + }, + + resolve: { + alias: { + '@': utils.pathResolve('src'), + }, + extensions: ['.js', '.jsx', '.vue', '.json'], + }, + + externals: { + vue: 'kiwi.Vue', + }, + + performance: { + maxEntrypointSize: 512 * utils.KiB, // 0.5MiB + maxAssetSize: 512 * utils.KiB, // 0.5MiB + }, + + plugins: [ + new ESLintPlugin({ + emitError: true, + emitWarning: true, + extensions: ['.ts', '.tsx', '.js', '.jsx', '.vue'], + formatter: ESLintFormatter, + }), + new VueLoaderPlugin(), + new CaseSensitivePathsPlugin(), + new ConvertLocalesPlugin(), + new FriendlyErrorsWebpackPlugin(), + ], + + module: { + rules: [ + { + test: /\.vue$/, + use: [ + { + loader: 'vue-loader', + options: { + transformAssetUrls: { + // Defaults + video: ['src', 'poster'], + source: 'src', + img: 'src', + image: ['xlink:href', 'href'], + use: ['xlink:href', 'href'], + + // Object can be used for svg files + object: 'data', + }, + }, + }, + ], + }, + + { + test: /\.js$/, + exclude: (file) => /node_modules/.test(file), + use: ['babel-loader'], + }, + ], + }, + }); + + return cssConfig(env, argv, baseConfig); +}; diff --git a/fileuploader-kiwiirc-plugin/build/configs/css.js b/fileuploader-kiwiirc-plugin/build/configs/css.js new file mode 100644 index 0000000..4ca6adb --- /dev/null +++ b/fileuploader-kiwiirc-plugin/build/configs/css.js @@ -0,0 +1,86 @@ +const Autoprefixer = require('autoprefixer'); +const { merge } = require('webpack-merge'); + +const cssRules = [ + { + test: /\.css$/, + use: [ + { + loader: 'vue-style-loader', + }, + { + loader: 'css-loader', + options: { + importLoaders: 2, + esModule: false, + }, + }, + { + loader: 'postcss-loader', + options: { + postcssOptions: { + plugins: [Autoprefixer], + }, + }, + }, + ], + }, + { + test: /\.less$/, + use: [ + { + loader: 'vue-style-loader', + }, + { + loader: 'css-loader', + options: { + importLoaders: 2, + esModule: false, + }, + }, + { + loader: 'postcss-loader', + options: { + postcssOptions: { + plugins: [Autoprefixer], + }, + }, + }, + { + loader: 'less-loader', + }, + ], + }, + { + test: /\.s[ac]ss$/, + use: [ + { + loader: 'vue-style-loader', + }, + { + loader: 'css-loader', + options: { + importLoaders: 2, + esModule: false, + }, + }, + { + loader: 'postcss-loader', + options: { + postcssOptions: { + plugins: [Autoprefixer], + }, + }, + }, + { + loader: 'sass-loader', + }, + ], + }, +]; + +module.exports = (env, argv, config) => merge(config, { + module: { + rules: cssRules, + }, +}); diff --git a/fileuploader-kiwiirc-plugin/build/configs/dev.js b/fileuploader-kiwiirc-plugin/build/configs/dev.js new file mode 100644 index 0000000..a273d0a --- /dev/null +++ b/fileuploader-kiwiirc-plugin/build/configs/dev.js @@ -0,0 +1,60 @@ +const { merge } = require('webpack-merge'); +const murmurhash3 = require('murmurhash3js'); +const utils = require('../utils'); +const pkg = require('../../package.json'); + +const baseConfig = require('./base'); + +module.exports = (env, argv, config) => { + const pluginNumber = Math.abs(murmurhash3.x86.hash32(pkg.name)) % 1000; + const portNumber = utils.mapRange(pluginNumber, 0, 999, 9000, 9999); + + const devConfig = { + plugins: [], + + devServer: { + devMiddleware: { + publicPath: 'auto', + }, + open: false, + host: '127.0.0.1', + port: portNumber, + headers: { + 'Access-Control-Allow-Origin': '*', + }, + static: [ + { + directory: utils.pathResolve('static'), + publicPath: 'static', + }, + ], + client: { + logging: 'info', + overlay: { + runtimeErrors: true, + errors: true, + warnings: false, + }, + }, + }, + + infrastructureLogging: { + level: 'warn', + }, + + stats: { + all: false, + loggingDebug: ['sass-loader'], + }, + }; + + if (argv.host) { + devConfig.devServer.host = argv.host === true ? '0.0.0.0' : argv.host; + } + + if (argv.port) { + devConfig.devServer.port = argv.port; + } + + return merge(baseConfig(env, argv, config), devConfig); +}; diff --git a/fileuploader-kiwiirc-plugin/build/configs/prod.js b/fileuploader-kiwiirc-plugin/build/configs/prod.js new file mode 100644 index 0000000..52de7e0 --- /dev/null +++ b/fileuploader-kiwiirc-plugin/build/configs/prod.js @@ -0,0 +1,44 @@ +const CompressionPlugin = require('compression-webpack-plugin'); +const TerserPlugin = require('terser-webpack-plugin'); +const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); +const { merge } = require('webpack-merge'); +const zlib = require('zlib'); + +const baseConfig = require('./base'); +const terserOptions = require('./terser'); + +module.exports = (env, argv, config) => { + const compressionTest = /\.(js|css|js.map|css.map|svg|json|ttf|eot|woff2?)(\?.*)?$/; + + const prodConfig = { + plugins: [ + new CompressionPlugin({ + filename: '[path][base].gz', + algorithm: 'gzip', + test: compressionTest, + compressionOptions: { + level: 9, + }, + threshold: 1024, + }), + new CompressionPlugin({ + filename: '[path][base].br', + algorithm: 'brotliCompress', + test: compressionTest, + compressionOptions: { + params: { + [zlib.constants.BROTLI_PARAM_QUALITY]: 8, + }, + }, + threshold: 1024, + }), + ], + + optimization: { + minimize: true, + minimizer: [new TerserPlugin(terserOptions), new CssMinimizerPlugin()], + }, + }; + + return merge(baseConfig(env, argv, config), prodConfig); +}; diff --git a/fileuploader-kiwiirc-plugin/build/configs/terser.js b/fileuploader-kiwiirc-plugin/build/configs/terser.js new file mode 100644 index 0000000..a6e64ab --- /dev/null +++ b/fileuploader-kiwiirc-plugin/build/configs/terser.js @@ -0,0 +1,16 @@ +module.exports = { + terserOptions: { + compress: { + booleans: true, + conditionals: true, + dead_code: true, + evaluate: true, + if_return: true, + sequences: true, + unused: true, + }, + }, + extractComments: { + condition: false, + }, +}; diff --git a/fileuploader-kiwiirc-plugin/build/convert-locales.js b/fileuploader-kiwiirc-plugin/build/convert-locales.js deleted file mode 100644 index e14ed86..0000000 --- a/fileuploader-kiwiirc-plugin/build/convert-locales.js +++ /dev/null @@ -1,26 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -const pluginName = 'ConvertLocalesPlugin'; - -class ConvertLocalesPlugin { - apply(compiler) { - compiler.hooks.emit.tap(pluginName, async(compilation) => { - const outputDir = path.resolve(__dirname, compilation.options.output.path, 'plugin-fileuploader/locales/uppy/'); - const nodeDir = path.resolve(__dirname, '../node_modules'); - const sourceDir = path.resolve(__dirname, nodeDir, '@uppy/locales/lib'); - - fs.mkdirSync(outputDir, { recursive: true }); - - const files = fs.readdirSync(sourceDir).filter(f => /\.js$/.test(f)); - for (let i = 0; i < files.length; i++) { - const srcFile = files[i]; - const outFile = path.resolve(__dirname, outputDir, srcFile.toLowerCase() + 'on'); - const locale = await import('file://' + path.resolve(__dirname, sourceDir, srcFile)); - fs.writeFileSync(outFile, JSON.stringify(locale.default.strings, null, 4)); - } - }); - } -} - -module.exports = ConvertLocalesPlugin; diff --git a/fileuploader-kiwiirc-plugin/build/plugins/eslint-rules/class-name-prefix.js b/fileuploader-kiwiirc-plugin/build/plugins/eslint-rules/class-name-prefix.js new file mode 100644 index 0000000..b3421c3 --- /dev/null +++ b/fileuploader-kiwiirc-plugin/build/plugins/eslint-rules/class-name-prefix.js @@ -0,0 +1,60 @@ +const pkg = require('../../../package.json'); + +const pkgClass = pkg.name.replace(/^kiwiirc-/, ''); +const pkgClassShort = pkgClass.replace(/^plugin-/, 'p-'); + +const allowedPrefixes = [ + 'kiwi-', + 'u-', + + `${pkgClass}-`, +]; + +const specialPrefixes = [ + // IRC colour classes + 'irc-fg-', + 'irc-bg-', + + // Special exception for google recaptcha - welcome screen. + 'g-', +]; + +if (pkgClass !== pkgClassShort) { + allowedPrefixes.push(`${pkgClassShort}-`); +} + +const prefixes = [...allowedPrefixes, ...specialPrefixes]; + +const reportMessage = `Expected class name to start with one of ['${allowedPrefixes.join('\', \'')}'] ({{ class }})`; + +module.exports = { + meta: { + docs: { + description: `html class names must start one of ['${allowedPrefixes.join('\', \'')}']`, + category: 'base', + url: null, + }, + fixable: null, + schema: [], + }, + create: (context) => context.parserServices.defineTemplateBodyVisitor({ + 'VAttribute[key.name=\'class\']': (node) => { + const classes = node.value.value.split(' '); + classes.forEach((c) => { + // Ignore empty and fontawesome classes + if (!c || c === 'fa' || c.startsWith('fa-')) { + return; + } + if (prefixes.every((p) => !c.startsWith(p))) { + context.report({ + node, + message: reportMessage, + data: { + class: c, + }, + }); + } + }); + }, + }), +}; diff --git a/fileuploader-kiwiirc-plugin/build/plugins/eslint-rules/index.js b/fileuploader-kiwiirc-plugin/build/plugins/eslint-rules/index.js new file mode 100644 index 0000000..e62c43b --- /dev/null +++ b/fileuploader-kiwiirc-plugin/build/plugins/eslint-rules/index.js @@ -0,0 +1,7 @@ +/* eslint-disable global-require */ + +module.exports = { + rules: { + 'class-name-prefix': require('./class-name-prefix'), + }, +}; diff --git a/fileuploader-kiwiirc-plugin/build/plugins/eslint-rules/package.json b/fileuploader-kiwiirc-plugin/build/plugins/eslint-rules/package.json new file mode 100644 index 0000000..5970afe --- /dev/null +++ b/fileuploader-kiwiirc-plugin/build/plugins/eslint-rules/package.json @@ -0,0 +1,9 @@ +{ + "name": "@kiwi/eslint-plugin", + "version": "1.0.0", + "private": true, + "main": "index.js", + "peerDependencies": { + "eslint": "^8.31.0" + } +} diff --git a/fileuploader-kiwiirc-plugin/build/plugins/webpack/convert-locales.js b/fileuploader-kiwiirc-plugin/build/plugins/webpack/convert-locales.js new file mode 100644 index 0000000..80cabe2 --- /dev/null +++ b/fileuploader-kiwiirc-plugin/build/plugins/webpack/convert-locales.js @@ -0,0 +1,88 @@ +const fs = require('fs'); + +const utils = require('../../utils'); + +// class ConvertLocalesPlugin { +// apply(compiler) { +// compiler.hooks.emit.tap(this.constructor.name, async (compilation) => { +// const compileOutput = compilation.options.output.path; +// const outputDir = utils.pathResolve(compileOutput, 'plugin-fileuploader/locales/uppy/'); +// const sourceDir = utils.pathResolve('node_modules/@uppy/locales/lib'); + +// fs.mkdirSync(outputDir, { recursive: true }); + +// const files = fs.readdirSync(sourceDir).filter((f) => /\.js$/.test(f)); +// files.forEach((srcFile) => { +// const outFile = utils.pathResolve(outputDir, srcFile.toLowerCase() + 'on'); +// console.log('out', outFile); +// import('file://' + utils.pathResolve(sourceDir, srcFile)).then((locale) => { +// fs.writeFileSync(outFile, JSON.stringify(locale.default.strings, null, 4)); +// }); +// }); +// }); +// } +// } + +class ConvertLocalesPlugin { + apply(compiler) { + const pluginName = this.constructor.name; + const fileDependencies = new Set(); + + compiler.hooks.beforeRun.tapAsync(pluginName, (compilation, callback) => { + // run in build mode + const compileOutput = compilation.options.output.path; + const outputDir = utils.pathResolve(compileOutput, 'plugin-fileuploader/locales/uppy/'); + convertLocales(fileDependencies, outputDir, callback); + }); + + compiler.hooks.watchRun.tapAsync(pluginName, (compilation, callback) => { + // run in dev mode + const compileOutput = compilation.options.output.path; + const outputDir = utils.pathResolve(compileOutput, 'plugin-fileuploader/locales/uppy/'); + convertLocales(fileDependencies, outputDir, callback); + }); + + compiler.hooks.afterEmit.tapAsync(pluginName, (compilation, callback) => { + // Add file dependencies + fileDependencies.forEach((dependency) => { + compilation.fileDependencies.add(dependency); + }); + + callback(); + }); + } +} + +async function convertLocales(fileDependencies, outputDir, callback) { + fileDependencies.clear(); + const sourceDir = utils.pathResolve('node_modules/@uppy/locales/lib'); + const awaitPromises = new Set(); + + fs.mkdirSync(outputDir, { recursive: true }); + + const files = fs.readdirSync(sourceDir).filter((f) => /\.js$/.test(f)); + files.forEach((srcFile) => { + const outFile = utils.pathResolve(outputDir, srcFile.toLowerCase() + 'on'); + const promise = import('file://' + utils.pathResolve(sourceDir, srcFile)).then((locale) => { + if (!locale?.default?.strings) { + return; + } + writeIfChanged(outFile, JSON.stringify(locale.default.strings, null, 4)); + }); + awaitPromises.add(promise); + }); + + await Promise.all(awaitPromises); + callback(); +} + +function writeIfChanged(file, _data) { + const data = Buffer.from(_data); + if (fs.existsSync(file) && data.equals(fs.readFileSync(file))) { + return; + } + + fs.writeFileSync(file, data); +} + +module.exports = ConvertLocalesPlugin; diff --git a/fileuploader-kiwiirc-plugin/build/utils.js b/fileuploader-kiwiirc-plugin/build/utils.js new file mode 100644 index 0000000..f0b3cc0 --- /dev/null +++ b/fileuploader-kiwiirc-plugin/build/utils.js @@ -0,0 +1,63 @@ +const os = require('os'); +const path = require('path'); +const { execSync } = require('child_process'); + +module.exports.pathResolve = (...args) => path.resolve(process.cwd(), ...args); + +module.exports.getCommitHash = () => { + let commitHash = 'unknown'; + try { + commitHash = execSync('git rev-parse --short HEAD').toString().trim(); + const modified = execSync('git diff --quiet HEAD -- || echo true').toString(); + if (modified.trim() === 'true') { + commitHash += '-modified'; + } + } catch { + console.error('Failed to get commit hash'); + } + return commitHash; +}; + +module.exports.getNetworkIPs = () => { + const interfaces = os.networkInterfaces(); + const ips = []; + /* eslint-disable no-restricted-syntax */ + for (const iface of Object.values(interfaces)) { + for (const alias of iface) { + if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) { + ips.push(alias.address); + } + } + } + + return ips; +}; + +module.exports.formatSize = (_size) => { + const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB']; + let size = _size; + let pos = 0; + + while (size >= 1024 && pos < units.length) { + size /= 1024; + pos++; + } + + return `${parseFloat(size.toFixed(2))} ${units[pos]}`; +}; + +module.exports.KiB = 1024; +module.exports.MiB = 1048576; +module.exports.GiB = 1073741824; + +/* + * Re-maps a number from one range to another + * http://processing.org/reference/map_.html + */ +module.exports.mapRange = (value, vMin, vMax, dMin, dMax) => { + let vValue = parseFloat(value); + let vRange = vMax - vMin; + let dRange = dMax - dMin; + + return (vValue - vMin) * dRange / vRange + dMin; +}; diff --git a/fileuploader-kiwiirc-plugin/jsconfig.json b/fileuploader-kiwiirc-plugin/jsconfig.json new file mode 100644 index 0000000..eac2f41 --- /dev/null +++ b/fileuploader-kiwiirc-plugin/jsconfig.json @@ -0,0 +1,5 @@ +{ + "vueCompilerOptions": { + "target": 2.7, + } +} diff --git a/fileuploader-kiwiirc-plugin/package.json b/fileuploader-kiwiirc-plugin/package.json index 8d3b234..b514e64 100644 --- a/fileuploader-kiwiirc-plugin/package.json +++ b/fileuploader-kiwiirc-plugin/package.json @@ -1,49 +1,93 @@ { - "name": "fileuploader-kiwiirc-plugin", + "name": "kiwiirc-plugin-fileuploader", "version": "0.3.0", "license": "Apache-2.0", "private": true, "scripts": { - "build": "webpack", - "watch": "webpack --watch", - "dev": "webpack serve", - "lint": "eslint --ext .js,.vue ./" + "dev": "node build/commands/dev.js", + "build": "node build/commands/build.js", + "stats": "node build/commands/build.js --stats", + "lint": "npm-run-all lint:*", + "lint:js": "eslint --ext .js,.vue ./", + "lint:style": "stylelint \"./src/**/*.{vue,html,css,less,scss,sass}\"" }, "dependencies": { - "@uppy/audio": "^1.1.1", - "@uppy/core": "^3.2.0", - "@uppy/dashboard": "^3.4.0", - "@uppy/image-editor": "^2.1.2", - "@uppy/tus": "^3.1.0", - "@uppy/webcam": "^3.3.1", - "core-js": "^3.30.2", + "@transloadit/prettier-bytes": "^0.3.4", + "@uppy/audio": "^2.0.1", + "@uppy/core": "^4.2.3", + "@uppy/dashboard": "^4.1.2", + "@uppy/image-editor": "^3.2.0", + "@uppy/locales": "^4.3.0", + "@uppy/tus": "^4.1.4", + "@uppy/utils": "^6.0.4", + "@uppy/webcam": "^4.0.2", + "bytes": "^3.1.2", + "core-js": "^3.39.0", "p-is-promise": "^4.0.0", - "vue": "^2.7.14" + "wildcard": "^2.0.1" }, "devDependencies": { - "@babel/core": "^7.22.5", - "@babel/eslint-parser": "^7.22.5", - "@babel/polyfill": "^7.12.1", - "@babel/preset-env": "^7.22.5", - "@uppy/locales": "^3.2.1", - "babel-loader": "^9.1.2", - "clean-webpack-plugin": "^4.0.0", + "@babel/core": "^7.26.0", + "@babel/eslint-parser": "^7.25.9", + "@babel/plugin-transform-runtime": "^7.25.9", + "@babel/preset-env": "^7.26.0", + "@kiwiirc/eslint-plugin": "file:./build/plugins/eslint-rules/", + "@soda/friendly-errors-webpack-plugin": "^1.8.1", + "@vue/eslint-config-airbnb": "^7.0.1", + "autoprefixer": "^10.4.20", + "babel-loader": "^9.2.1", + "case-sensitive-paths-webpack-plugin": "^2.4.0", + "chalk": "^4.1.2", + "cliui": "^8.0.1", "compression-webpack-plugin": "^10.0.0", - "css-loader": "^6.8.1", - "eslint": "^7.32.0", - "eslint-config-standard": "^14.1.1", - "eslint-plugin-import": "^2.27.5", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^4.3.1", - "eslint-plugin-standard": "^4.1.0", - "eslint-plugin-vue": "^8.7.1", - "eslint-plugin-vuejs-accessibility": "^1.2.0", - "mini-css-extract-plugin": "^2.7.6", - "style-loader": "^3.3.3", - "vue-loader": "^15.10.1", - "vue-template-compiler": "^2.7.14", - "webpack": "^5.86.0", + "copy-webpack-plugin": "^11.0.0", + "css-loader": "^6.11.0", + "css-minimizer-webpack-plugin": "^5.0.1", + "eslint": "^8.57.1", + "eslint-config-standard": "^17.1.0", + "eslint-formatter-friendly": "^7.0.0", + "eslint-import-resolver-alias": "^1.1.2", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jsdoc": "^46.10.1", + "eslint-plugin-n": "^16.6.2", + "eslint-plugin-promise": "^6.6.0", + "eslint-plugin-vue": "^9.30.0", + "eslint-plugin-vuejs-accessibility": "^2.4.1", + "eslint-webpack-plugin": "^4.2.0", + "html-loader": "^4.2.0", + "jsdoc": "^4.0.4", + "less": "^4.2.0", + "less-loader": "^11.1.4", + "mini-css-extract-plugin": "^2.9.2", + "murmurhash3js": "^3.0.1", + "npm-run-all": "^4.1.5", + "ora": "^5.4.1", + "portfinder": "^1.0.32", + "postcss": "^8.4.48", + "postcss-html": "^1.7.0", + "postcss-less": "^6.0.0", + "postcss-loader": "^7.3.4", + "postcss-scss": "^4.0.9", + "prettier": "2.8.8", + "prettier-plugin-jsdoc": "^0.4.2", + "rimraf": "^5.0.10", + "sass": "^1.80.6", + "sass-loader": "^13.3.3", + "style-loader": "^3.3.4", + "stylelint": "^15.11.0", + "stylelint-config-recess-order": "^4.6.0", + "stylelint-config-standard": "^34.0.0", + "stylelint-config-standard-scss": "^11.1.0", + "stylelint-order": "^6.0.4", + "vue-eslint-parser": "^9.4.3", + "vue-loader": "^15.11.1", + "vue-style-loader": "^4.1.3", + "vue-template-compiler": "^2.7.16", + "webpack": "^5.96.1", + "webpack-bundle-analyzer": "^4.10.2", "webpack-cli": "^5.1.4", - "webpack-dev-server": "^4.15.1" - } + "webpack-dev-server": "^4.15.2", + "webpack-merge": "^5.10.0" + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/fileuploader-kiwiirc-plugin/src/cache-loader.old.js.off b/fileuploader-kiwiirc-plugin/src/cache-loader.old.js.off new file mode 100644 index 0000000..e3a76cf --- /dev/null +++ b/fileuploader-kiwiirc-plugin/src/cache-loader.old.js.off @@ -0,0 +1,45 @@ +export default class CacheLoader { + // cache = new Map() + // loading = new Map() + // loadFn + // assertValid + + constructor(loadFn, assertValid) { + this.cache = new Map(); + this.loading = new Map(); + this.loadFn = loadFn; + this.assertValid = assertValid; + } + + get(key) { + if (!this.cache.has(key)) { + return this.load(key); + } + const val = this.cache.get(key); + try { + this.assertValid(val); + return val; + } catch (err) { + console.warn(`Cached value failed validation: ${err.message}`); + return this.load(key); + } + } + + async load(key) { + if (this.loading.has(key)) { + return this.loading.get(key); + } + + const valPromise = this.loadFn(key); + this.loading.set(key, valPromise); + + try { + const val = await valPromise; + this.assertValid(val); + this.cache.set(key, val); + return val; + } finally { + this.loading.delete(key); + } + } +} diff --git a/fileuploader-kiwiirc-plugin/src/components/AudioPlayer.vue b/fileuploader-kiwiirc-plugin/src/components/AudioPlayer.vue new file mode 100644 index 0000000..d8ac98d --- /dev/null +++ b/fileuploader-kiwiirc-plugin/src/components/AudioPlayer.vue @@ -0,0 +1,48 @@ + + + diff --git a/fileuploader-kiwiirc-plugin/src/components/PasteConfirm.vue b/fileuploader-kiwiirc-plugin/src/components/PasteConfirm.vue new file mode 100644 index 0000000..29c6094 --- /dev/null +++ b/fileuploader-kiwiirc-plugin/src/components/PasteConfirm.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/fileuploader-kiwiirc-plugin/src/components/SidebarFileList.vue b/fileuploader-kiwiirc-plugin/src/components/SidebarFileList.vue index 7c18fce..fd21d54 100644 --- a/fileuploader-kiwiirc-plugin/src/components/SidebarFileList.vue +++ b/fileuploader-kiwiirc-plugin/src/components/SidebarFileList.vue @@ -1,47 +1,47 @@