├── src ├── boot │ └── .gitkeep ├── assets │ ├── Logo.afdesign │ └── Logo.svg ├── css │ ├── app.scss │ ├── _color.sass │ ├── _message.sass │ ├── _mixin.sass │ └── quasar.variables.scss ├── App.vue ├── env.d.ts ├── shims-vue.d.ts ├── layouts │ └── MainLayout.vue ├── quasar.d.ts ├── router │ ├── routes.ts │ └── index.ts ├── pages │ ├── ErrorNotFound.vue │ └── IndexPage.vue ├── models │ ├── CategoryModel.ts │ └── ProfileModel.ts ├── controller │ ├── contentFileReader.ts │ ├── helper.ts │ ├── session.ts │ └── string.ts ├── index.template.html └── components │ ├── HeaderComponent.vue │ ├── FooterComponent.vue │ ├── ImportExportComponent.vue │ ├── CategorySubComponent.vue │ ├── TextField.vue │ └── CategoriesComponent.vue ├── .prettierrc ├── public ├── favicon.ico ├── img │ └── icons │ │ └── Logo.png └── icons │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon-96x96.png │ └── favicon-128x128.png ├── .eslintignore ├── tsconfig.json ├── .postcssrc.js ├── .editorconfig ├── babel.config.js ├── .vscode ├── extensions.json └── settings.json ├── .gitignore ├── CHANGELOG.md ├── package.json ├── README.md ├── .github └── workflows │ └── codeql-analysis.yml ├── .eslintrc.js ├── quasar.config.js └── LICENSE /src/boot/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": true, 4 | "trailingComma": "none" 5 | } 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragonsea0927/KeywordFinder/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/img/icons/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragonsea0927/KeywordFinder/HEAD/public/img/icons/Logo.png -------------------------------------------------------------------------------- /src/assets/Logo.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragonsea0927/KeywordFinder/HEAD/src/assets/Logo.afdesign -------------------------------------------------------------------------------- /public/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragonsea0927/KeywordFinder/HEAD/public/icons/favicon-16x16.png -------------------------------------------------------------------------------- /public/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragonsea0927/KeywordFinder/HEAD/public/icons/favicon-32x32.png -------------------------------------------------------------------------------- /public/icons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragonsea0927/KeywordFinder/HEAD/public/icons/favicon-96x96.png -------------------------------------------------------------------------------- /src/css/app.scss: -------------------------------------------------------------------------------- 1 | // app global css in SCSS form 2 | 3 | body { 4 | min-width: 50rem; 5 | min-height: 30rem; 6 | } 7 | -------------------------------------------------------------------------------- /public/icons/favicon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dragonsea0927/KeywordFinder/HEAD/public/icons/favicon-128x128.png -------------------------------------------------------------------------------- /src/css/_color.sass: -------------------------------------------------------------------------------- 1 | $main-color: #51B7D0 !important 2 | $seccond-color: #0085A6 !important 3 | $third-color: #1976d2 !important 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /src-bex/www 3 | /src-capacitor 4 | /src-cordova 5 | /.quasar 6 | /node_modules 7 | .eslintrc.js 8 | babel.config.js 9 | /src-ssr 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@quasar/app-webpack/tsconfig-preset", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "target": "ESNEXT", 6 | "lib": ["esnext", "dom"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | declare namespace NodeJS { 4 | interface ProcessEnv { 5 | NODE_ENV: string; 6 | VUE_ROUTER_MODE: 'hash' | 'history' | 'abstract' | undefined; 7 | VUE_ROUTER_BASE: string | undefined; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // https://github.com/michael-ciniawsky/postcss-load-config 3 | 4 | module.exports = { 5 | plugins: [ 6 | // to edit target browsers: use "browserslist" field in package.json 7 | require('autoprefixer') 8 | ] 9 | }; 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = tab 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | indent_style = space 14 | -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | // Mocks all files ending in `.vue` showing them as plain Vue instances 4 | declare module '*.vue' { 5 | import type { DefineComponent } from 'vue'; 6 | const component: DefineComponent<{}, {}, any>; 7 | export default component; 8 | } 9 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | module.exports = (api) => { 4 | return { 5 | presets: [ 6 | [ 7 | '@quasar/babel-preset-app', 8 | api.caller((caller) => caller && caller.target === 'node') 9 | ? { targets: { node: 'current' } } 10 | : {} 11 | ] 12 | ] 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /src/layouts/MainLayout.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 16 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "esbenp.prettier-vscode", 5 | "editorconfig.editorconfig", 6 | "wayou.vscode-todo-highlight" 7 | ], 8 | "unwantedRecommendations": [ 9 | "octref.vetur", 10 | "hookyqr.beautify", 11 | "dbaeumer.jshint", 12 | "ms-vscode.vscode-typescript-tslint-plugin" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.bracketPairColorization.enabled": true, 3 | "editor.guides.bracketPairs": true, 4 | "editor.formatOnSave": true, 5 | "editor.defaultFormatter": "esbenp.prettier-vscode", 6 | "editor.codeActionsOnSave": ["source.fixAll.eslint"], 7 | "eslint.validate": ["javascript", "javascriptreact", "typescript", "vue"], 8 | "typescript.tsdk": "node_modules/typescript/lib" 9 | } 10 | -------------------------------------------------------------------------------- /src/quasar.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | // Forces TS to apply `@quasar/app-webpack` augmentations of `quasar` package 4 | // Removing this would break `quasar/wrappers` imports as those typings are declared 5 | // into `@quasar/app-webpack` 6 | // As a side effect, since `@quasar/app-webpack` reference `quasar` to augment it, 7 | // this declaration also apply `quasar` own 8 | // augmentations (eg. adds `$q` into Vue component context) 9 | /// 10 | -------------------------------------------------------------------------------- /src/router/routes.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw } from 'vue-router'; 2 | 3 | const routes: RouteRecordRaw[] = [ 4 | { 5 | path: '/', 6 | component: () => import('layouts/MainLayout.vue'), 7 | children: [{ path: '', component: () => import('pages/IndexPage.vue') }] 8 | }, 9 | 10 | // Always leave this as last one, 11 | // but you can also remove it 12 | { 13 | path: '/:catchAll(.*)*', 14 | component: () => import('pages/ErrorNotFound.vue') 15 | } 16 | ]; 17 | 18 | export default routes; 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .thumbs.db 3 | node_modules 4 | 5 | # Quasar core related directories 6 | .quasar 7 | /dist 8 | 9 | # Cordova related directories and files 10 | /src-cordova/node_modules 11 | /src-cordova/platforms 12 | /src-cordova/plugins 13 | /src-cordova/www 14 | 15 | # Capacitor related directories and files 16 | /src-capacitor/www 17 | /src-capacitor/node_modules 18 | 19 | # BEX related directories and files 20 | /src-bex/www 21 | /src-bex/js/core 22 | 23 | # Log files 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # Editor directories and files 29 | .idea 30 | *.suo 31 | *.ntvs* 32 | *.sln 33 | 34 | **/dist 35 | -------------------------------------------------------------------------------- /src/css/_message.sass: -------------------------------------------------------------------------------- 1 | @import "mixin" 2 | 3 | #message_container 4 | padding: 1rem 5 | margin-bottom: 1rem 6 | width: 30em 7 | position: fixed 8 | z-index: 1000 9 | right: 0 10 | top: 0 11 | 12 | .material-icons 13 | font-size: 3rem 14 | 15 | .success_message 16 | color: #4caf50 17 | background-color: #e8f5e9 18 | @include message 19 | 20 | .error_message 21 | color: #f44336 22 | background-color: #ffebee 23 | @include message 24 | 25 | .warning_message 26 | color: #ff9800 27 | background-color: #fffde7 28 | @include message 29 | 30 | .info_message 31 | color: #2196f3 32 | background-color: #e3f2fd 33 | @include message 34 | -------------------------------------------------------------------------------- /src/pages/ErrorNotFound.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 30 | -------------------------------------------------------------------------------- /src/css/_mixin.sass: -------------------------------------------------------------------------------- 1 | @import "color" 2 | 3 | @mixin noselect 4 | -webkit-touch-callout: none 5 | -webkit-user-select: none 6 | -khtml-user-select: none 7 | -moz-user-select: none 8 | -ms-user-select: none 9 | user-select: none 10 | pointer-events: none 11 | 12 | @mixin message 13 | font-weight: bold 14 | margin-bottom: 1rem 15 | padding: 0.5rem 16 | border-radius: 0.25rem 17 | width: 100% 18 | right: 0 19 | top: 0 20 | box-shadow: 0 0.25rem 0.25rem rgba(0, 0, 0, 0.1) 21 | 22 | @include noselect 23 | 24 | @mixin basicButton 25 | margin: 0.5rem 26 | padding: 0.7rem 27 | background-color: $third-color 28 | color: white 29 | height: 3rem 30 | border-radius: 1rem 31 | cursor: pointer 32 | -------------------------------------------------------------------------------- /src/models/CategoryModel.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Categorie model 3 | */ 4 | export class Category { 5 | /** 6 | * Id of categorie 7 | */ 8 | public id: string; 9 | 10 | /** 11 | * Name of categorie 12 | */ 13 | public name: string; 14 | 15 | /** 16 | * Color of categorie 17 | */ 18 | public color: string; 19 | 20 | /** 21 | * Flags of categorie 22 | */ 23 | public tags: Array; 24 | 25 | /** 26 | * Creates an instance of categorie. 27 | * @param id 28 | * @param name 29 | * @param color 30 | * @param tags 31 | */ 32 | constructor(id: string, name: string, color: string, tags: Array) { 33 | this.id = id; 34 | this.name = name; 35 | this.color = color; 36 | this.tags = tags; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/controller/contentFileReader.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Content file reader 3 | */ 4 | export default class ContentFileReader { 5 | /** 6 | * File of content file reader 7 | */ 8 | private file: File; 9 | 10 | /** 11 | * Creates an instance of content file reader. 12 | * @param file 13 | */ 14 | constructor(file: File) { 15 | this.file = file; 16 | } 17 | 18 | /** 19 | * Gets content 20 | * @returns content 21 | */ 22 | public async getContent(): Promise { 23 | return new Promise((resolve) => { 24 | const reader = new FileReader(); 25 | reader.onload = () => { 26 | resolve(reader.result as string); 27 | }; 28 | try { 29 | reader.readAsText(this.file); 30 | } catch (e) { 31 | resolve(''); 32 | } 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/pages/IndexPage.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 27 | -------------------------------------------------------------------------------- /src/css/quasar.variables.scss: -------------------------------------------------------------------------------- 1 | // Quasar SCSS (& Sass) Variables 2 | // -------------------------------------------------- 3 | // To customize the look and feel of this app, you can override 4 | // the Sass/SCSS variables found in Quasar's source Sass/SCSS files. 5 | 6 | // Check documentation for full list of Quasar variables 7 | 8 | // Your own variables (that are declared here) and Quasar's own 9 | // ones will be available out of the box in your .vue/.scss/.sass files 10 | 11 | // It's highly recommended to change the default colors 12 | // to match your app's branding. 13 | // Tip: Use the "Theme Builder" on Quasar's documentation website. 14 | 15 | $primary: #1976d2; 16 | $secondary: #26a69a; 17 | $accent: #9c27b0; 18 | 19 | $dark: #1d1d1d; 20 | 21 | $positive: #21ba45; 22 | $negative: #c10015; 23 | $info: #31ccec; 24 | $warning: #f2c037; 25 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { route } from 'quasar/wrappers'; 2 | import { 3 | createMemoryHistory, 4 | createRouter, 5 | createWebHashHistory, 6 | createWebHistory 7 | } from 'vue-router'; 8 | import routes from './routes'; 9 | 10 | /* 11 | * If not building with SSR mode, you can 12 | * directly export the Router instantiation; 13 | * 14 | * The function below can be async too; either use 15 | * async/await or return a Promise which resolves 16 | * with the Router instance. 17 | */ 18 | 19 | export default route(function (/* { store, ssrContext } */) { 20 | const createHistory = process.env.SERVER 21 | ? createMemoryHistory 22 | : process.env.VUE_ROUTER_MODE === 'history' 23 | ? createWebHistory 24 | : createWebHashHistory; 25 | 26 | const Router = createRouter({ 27 | scrollBehavior: () => ({ left: 0, top: 0 }), 28 | routes, 29 | 30 | // Leave this as is and make changes in quasar.conf.js instead! 31 | // quasar.conf.js -> build -> vueRouterMode 32 | // quasar.conf.js -> build -> publicPath 33 | history: createHistory( 34 | process.env.MODE === 'ssr' ? void 0 : process.env.VUE_ROUTER_BASE 35 | ) 36 | }); 37 | 38 | return Router; 39 | }); 40 | -------------------------------------------------------------------------------- /src/index.template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= productName %> 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 21 | 27 | 33 | 39 | 40 | 41 | 42 | 43 |
44 | 45 | 46 | -------------------------------------------------------------------------------- /src/models/ProfileModel.ts: -------------------------------------------------------------------------------- 1 | import { Category } from './CategoryModel'; 2 | 3 | /** 4 | * Profile model 5 | */ 6 | export class Profile { 7 | /** 8 | * Id of profile 9 | */ 10 | public id: string; 11 | 12 | /** 13 | * Name of profile 14 | */ 15 | public name: string; 16 | 17 | /** 18 | * Profile of categories 19 | */ 20 | public categories: Array; 21 | 22 | /** 23 | * Creates an instance of Profile. 24 | */ 25 | constructor(name: string) { 26 | this.categories = []; 27 | this.id = crypto.randomUUID(); 28 | this.name = name; 29 | } 30 | 31 | /** 32 | * Adds category 33 | * @param category 34 | */ 35 | addCategory(category: Category) { 36 | this.categories.push(category); 37 | } 38 | 39 | /** 40 | * Adds multiple categories 41 | * @param categories 42 | */ 43 | addMultipleCategories(categories: Array) { 44 | this.categories = this.categories.concat(categories); 45 | } 46 | 47 | /** 48 | * Deletes category 49 | * @param category 50 | */ 51 | deleteCategorie(categorie: Category) { 52 | this.categories = this.categories.filter((cat) => cat.id !== categorie.id); 53 | } 54 | 55 | /** 56 | * Deletes multiple categories 57 | * @param categories 58 | */ 59 | deleteMultipleCategories(categories: Array) { 60 | this.categories = this.categories.filter( 61 | (cat) => !categories.includes(cat) 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project are documented in this file. 4 | 5 | ## Menu 6 | 7 | --- 8 | 9 | - [Changelog](#changelog) 10 | - [Menu](#menu) 11 | - [\[1.3.1\]](#131) 12 | - [Updates](#updates) 13 | - [\[1.2.0\]](#120) 14 | - [License](#license) 15 | - [\[1.1.1\]](#111) 16 | - [Bug fix Category loading](#bug-fix-category-loading) 17 | - [\[1.1.0\]](#110) 18 | - [Optic overhaul](#optic-overhaul) 19 | - [\[1.0.0\]](#100) 20 | - [First working version](#first-working-version) 21 | - [\[0.3.1\]](#031) 22 | - [ResetupCode](#resetupcode) 23 | - [\[0.0.1\]](#001) 24 | - [ProjectStartup](#projectstartup) 25 | 26 | --- 27 | 28 | ## [1.3.1] 29 | 30 | ### Updates 31 | 32 | - Dependency updates 33 | 34 | ## [1.2.0] 35 | 36 | ### License 37 | 38 | - This Project is licensed under the MPL 2.0 license. 39 | - Dependency updates 40 | 41 | ## [1.1.1] 42 | 43 | ### Bug fix Category loading 44 | 45 | - A bug was fixed, that prevented the category loading when one or more Profiles were uploaded. 46 | 47 | ## [1.1.0] 48 | 49 | ### Optic overhaul 50 | 51 | - A complete overhaul of all assets 52 | - Optimizing the look and feel 53 | 54 | ## [1.0.0] 55 | 56 | ### First working version 57 | 58 | - The first working Version 59 | 60 | ## [0.3.1] 61 | 62 | ### ResetupCode 63 | 64 | - Base Code 65 | 66 | ## [0.0.1] 67 | 68 | ### ProjectStartup 69 | 70 | - ReadMe with project description 71 | - ChangeLog 72 | - Front-end project 73 | - Versioning strategy 74 | -------------------------------------------------------------------------------- /src/assets/Logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "keyword-finder", 3 | "version": "1.3.1", 4 | "description": "A Quasar Project", 5 | "productName": "KeywordFinder", 6 | "author": { 7 | "name": "Jonas Pfalzgraf", 8 | "email": "info@josunlp.de" 9 | }, 10 | "private": true, 11 | "repository": { 12 | "type": "git", 13 | "url": "git@github.com:JosunLP/KeywordFinder.git" 14 | }, 15 | "homepage": "https://github.com/JosunLP/KeywordFinder", 16 | "scripts": { 17 | "dev": "quasar dev", 18 | "build": "quasar build", 19 | "lint": "eslint --ext .js,.ts,.vue ./", 20 | "format": "prettier --write \"**/*.{js,ts,vue,sass,html,md,json}\" --ignore-path .gitignore", 21 | "test": "echo \"No test specified\" && exit 0", 22 | "clean": "npm ci" 23 | }, 24 | "dependencies": { 25 | "@quasar/extras": "^1.0.0", 26 | "core-js": "^3.6.5", 27 | "quasar": "^2.6.0", 28 | "simple-toast-messages": "^2.0.0", 29 | "vue": "^3.0.0", 30 | "vue-router": "^4.0.0" 31 | }, 32 | "devDependencies": { 33 | "@quasar/app-webpack": "^3.0.0", 34 | "@types/node": "^12.20.21", 35 | "@typescript-eslint/eslint-plugin": "^5.10.0", 36 | "@typescript-eslint/parser": "^5.10.0", 37 | "eslint": "^8.10.0", 38 | "eslint-config-prettier": "^8.1.0", 39 | "eslint-plugin-vue": "^8.5.0", 40 | "prettier": "^2.5.1", 41 | "vue-tsc": "^0.29.8" 42 | }, 43 | "browserslist": [ 44 | "last 10 Chrome versions", 45 | "last 10 Firefox versions", 46 | "last 4 Edge versions", 47 | "last 7 Safari versions", 48 | "last 8 Android versions", 49 | "last 8 ChromeAndroid versions", 50 | "last 8 FirefoxAndroid versions", 51 | "last 10 iOS versions", 52 | "last 5 Opera versions" 53 | ], 54 | "engines": { 55 | "node": ">= 12.22.1", 56 | "npm": ">= 6.13.4", 57 | "yarn": ">= 1.21.1" 58 | } 59 | } -------------------------------------------------------------------------------- /src/components/HeaderComponent.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 32 | 33 | 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KeywordFinder 2 | 3 | [![License: MPL 2.0](https://img.shields.io/badge/License-MPL%202.0-brightgreen.svg)](https://opensource.org/licenses/MPL-2.0) 4 | [![GitHub issues](https://img.shields.io/github/issues/JosunLP/KeywordFinder)](https://github.com/JosunLP/KeywordFinder/issues) 5 | [![CodeFactor](https://www.codefactor.io/repository/github/josunlp/KeywordFinder/badge)](https://www.codefactor.io/repository/github/josunlp/KeywordFinder) 6 | ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/josunlp/KeywordFinder) 7 | ![GitHub top language](https://img.shields.io/github/languages/top/josunlp/KeywordFinder) 8 | ![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/josunlp/KeywordFinder) 9 | 10 | ## Menü 11 | 12 | --- 13 | 14 | - [KeywordFinder](#keywordfinder) 15 | - [Menü](#menü) 16 | - [English](#english) 17 | - [Description](#description) 18 | - [Installation](#installation) 19 | - [Development](#development) 20 | - [Deutsch](#deutsch) 21 | - [Beschreibung](#beschreibung) 22 | - [Aufsetzen](#aufsetzen) 23 | - [Entwicklung](#entwicklung) 24 | 25 | --- 26 | 27 | ## English 28 | 29 | ### Description 30 | 31 | The KeywordFinder is a tool that supports the search for keywords in a text. Its intended use case is to search for keywords in tender texts in order to speed up the evaluation process. 32 | 33 | ### Installation 34 | 35 | npm install 36 | 37 | ### Development 38 | 39 | npm run dev 40 | 41 | --- 42 | 43 | ## Deutsch 44 | 45 | ### Beschreibung 46 | 47 | Der KeywordFinder ist ein Tool, das die Suche nach Schlüsselwörtern in einem Text unterstützt. Sein beabsichtigter Anwendungsfall ist die Suche nach Schlüsselwörtern in Ausschreibungstexten, um den Evaluierungsprozess zu beschleunigen. 48 | 49 | ### Aufsetzen 50 | 51 | npm install 52 | 53 | ### Entwicklung 54 | 55 | npm run dev 56 | -------------------------------------------------------------------------------- /src/controller/helper.ts: -------------------------------------------------------------------------------- 1 | export class Helper { 2 | public static async sleep(timeout: number) { 3 | return new Promise((resolve) => { 4 | setTimeout(() => { 5 | resolve(); 6 | }, timeout); 7 | }); 8 | } 9 | public static generateRandomHex(): string { 10 | return '#' + Math.floor(Math.random() * 16777215).toString(16); 11 | } 12 | 13 | public static generateRandomNumber(min: number, max: number): number { 14 | return Math.floor(Math.random() * (max - min + 1) + min); 15 | } 16 | 17 | public static generateWordArray(length: number): string[] { 18 | const result = []; 19 | for (let i = 0; i < length; i++) { 20 | result.push(this.generateWord()); 21 | } 22 | return result; 23 | } 24 | 25 | public static generateWord(): string { 26 | const length = this.generateRandomNumber(3, 10); 27 | let result = ''; 28 | for (let i = 0; i < length; i++) { 29 | result += this.generateRandomChar(); 30 | } 31 | return result; 32 | } 33 | 34 | public static generateRandomChar(): string { 35 | const chars = 'abcdefghijklmnopqrstuvwxyz'; 36 | return chars[this.generateRandomNumber(0, chars.length - 1)]; 37 | } 38 | 39 | public static getHighestNumberFromStringArray(array: string[]): number { 40 | const numbers = array.map((a) => parseInt(a, 10)); 41 | return Math.max(...numbers); 42 | } 43 | 44 | public static isDark(hex: string): boolean { 45 | return this.getLuminance(hex) < 0.5; 46 | } 47 | 48 | public static getLuminance(hex: string): number { 49 | const rgb = this.hexToRgb(hex); 50 | return 0.2126 * rgb.r + 0.7152 * rgb.g + 0.0722 * rgb.b; 51 | } 52 | 53 | public static hexToRgb(hex: string): { r: number; g: number; b: number } { 54 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 55 | const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)!; 56 | return { 57 | r: parseInt(result[1], 16), 58 | g: parseInt(result[2], 16), 59 | b: parseInt(result[3], 16) 60 | }; 61 | } 62 | 63 | public static rgbToHex(rgb: { r: number; g: number; b: number }): string { 64 | return ( 65 | '#' + 66 | this.componentToHex(rgb.r) + 67 | this.componentToHex(rgb.g) + 68 | this.componentToHex(rgb.b) 69 | ); 70 | } 71 | 72 | public static componentToHex(c: number): string { 73 | const hex = c.toString(16); 74 | return hex.length == 1 ? '0' + hex : hex; 75 | } 76 | 77 | public static getContrastYIQ(hexcolor: string): string { 78 | const rgb = this.hexToRgb(hexcolor); 79 | const yiq = (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000; 80 | return yiq >= 128 ? '#000000' : '#ffffff'; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: 'CodeQL' 13 | 14 | on: 15 | push: 16 | branches: ['master'] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: ['master'] 20 | schedule: 21 | - cron: '32 19 * * 6' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: ['javascript'] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 56 | # If this step fails, then you should remove it and run the build manually (see below) 57 | - name: Autobuild 58 | uses: github/codeql-action/autobuild@v2 59 | 60 | # ℹ️ Command-line programs to run using the OS shell. 61 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 62 | 63 | # If the Autobuild fails above, remove it and uncomment the following three lines. 64 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 65 | 66 | # - run: | 67 | # echo "Run, Build Application using script" 68 | # ./location_of_script_within_repo/buildscript.sh 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v2 72 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy 3 | // This option interrupts the configuration hierarchy at this file 4 | // Remove this if you have an higher level ESLint config file (it usually happens into a monorepos) 5 | root: true, 6 | 7 | // https://eslint.vuejs.org/user-guide/#how-to-use-a-custom-parser 8 | // Must use parserOptions instead of "parser" to allow vue-eslint-parser to keep working 9 | // `parser: 'vue-eslint-parser'` is already included with any 'plugin:vue/**' config and should be omitted 10 | parserOptions: { 11 | parser: require.resolve('@typescript-eslint/parser'), 12 | extraFileExtensions: ['.vue'] 13 | }, 14 | 15 | env: { 16 | browser: true, 17 | es2021: true, 18 | node: true 19 | }, 20 | 21 | // Rules order is important, please avoid shuffling them 22 | extends: [ 23 | // Base ESLint recommended rules 24 | // 'eslint:recommended', 25 | 26 | // https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#usage 27 | // ESLint typescript rules 28 | 'plugin:@typescript-eslint/recommended', 29 | 30 | // Uncomment any of the lines below to choose desired strictness, 31 | // but leave only one uncommented! 32 | // See https://eslint.vuejs.org/rules/#available-rules 33 | 'plugin:vue/vue3-essential', // Priority A: Essential (Error Prevention) 34 | // 'plugin:vue/vue3-strongly-recommended', // Priority B: Strongly Recommended (Improving Readability) 35 | // 'plugin:vue/vue3-recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead) 36 | 37 | // https://github.com/prettier/eslint-config-prettier#installation 38 | // usage with Prettier, provided by 'eslint-config-prettier'. 39 | 'prettier' 40 | ], 41 | 42 | plugins: [ 43 | // required to apply rules which need type information 44 | '@typescript-eslint', 45 | 46 | // https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-files 47 | // required to lint *.vue files 48 | 'vue' 49 | 50 | // https://github.com/typescript-eslint/typescript-eslint/issues/389#issuecomment-509292674 51 | // Prettier has not been included as plugin to avoid performance impact 52 | // add it as an extension for your IDE 53 | ], 54 | 55 | globals: { 56 | ga: 'readonly', // Google Analytics 57 | cordova: 'readonly', 58 | __statics: 'readonly', 59 | __QUASAR_SSR__: 'readonly', 60 | __QUASAR_SSR_SERVER__: 'readonly', 61 | __QUASAR_SSR_CLIENT__: 'readonly', 62 | __QUASAR_SSR_PWA__: 'readonly', 63 | process: 'readonly', 64 | Capacitor: 'readonly', 65 | chrome: 'readonly', 66 | defineProps: 'readonly', // Vue SFC setup compiler macro 67 | defineEmits: 'readonly', // Vue SFC setup compiler macro 68 | defineExpose: 'readonly' // Vue SFC setup compiler macro 69 | }, 70 | 71 | // add your custom rules here 72 | rules: { 73 | 'prefer-promise-reject-errors': 'off', 74 | 75 | quotes: ['warn', 'single', { avoidEscape: true }], 76 | 77 | // this rule, if on, would require explicit return type on the `render` function 78 | '@typescript-eslint/explicit-function-return-type': 'off', 79 | 80 | // in plain CommonJS modules, you can't use `import foo = require('foo')` to pass this rule, so it has to be disabled 81 | '@typescript-eslint/no-var-requires': 'off', 82 | 83 | // The core 'no-unused-vars' rules (in the eslint:recommeded ruleset) 84 | // does not work with type definitions 85 | 'no-unused-vars': 'off', 86 | 87 | // allow debugger during development only 88 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 89 | } 90 | }; 91 | -------------------------------------------------------------------------------- /src/components/FooterComponent.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 79 | 80 | 147 | -------------------------------------------------------------------------------- /src/components/ImportExportComponent.vue: -------------------------------------------------------------------------------- 1 | 42 | 107 | 158 | -------------------------------------------------------------------------------- /src/components/CategorySubComponent.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 138 | 139 | 156 | -------------------------------------------------------------------------------- /src/components/TextField.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 146 | 147 | 172 | -------------------------------------------------------------------------------- /quasar.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | /* 4 | * This file runs in a Node context (it's NOT transpiled by Babel), so use only 5 | * the ES6 features that are supported by your Node version. https://node.green/ 6 | */ 7 | 8 | // Configuration for your app 9 | // https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js 10 | 11 | /* eslint-disable @typescript-eslint/no-var-requires */ 12 | 13 | const { configure } = require('quasar/wrappers'); 14 | 15 | module.exports = configure(function (ctx) { 16 | return { 17 | // https://v2.quasar.dev/quasar-cli-webpack/supporting-ts 18 | supportTS: { 19 | tsCheckerConfig: { 20 | eslint: { 21 | enabled: true, 22 | files: './src/**/*.{ts,tsx,js,jsx,vue}' 23 | } 24 | } 25 | }, 26 | 27 | // https://v2.quasar.dev/quasar-cli-webpack/prefetch-feature 28 | // preFetch: true, 29 | 30 | // app boot file (/src/boot) 31 | // --> boot files are part of "main.js" 32 | // https://v2.quasar.dev/quasar-cli-webpack/boot-files 33 | boot: [], 34 | 35 | // https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-css 36 | css: ['app.scss'], 37 | 38 | // https://github.com/quasarframework/quasar/tree/dev/extras 39 | extras: [ 40 | // 'ionicons-v4', 41 | // 'mdi-v5', 42 | // 'fontawesome-v6', 43 | // 'eva-icons', 44 | // 'themify', 45 | // 'line-awesome', 46 | // 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both! 47 | 48 | 'roboto-font', // optional, you are not bound to it 49 | 'material-icons' // optional, you are not bound to it 50 | ], 51 | 52 | // Full list of options: https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-build 53 | build: { 54 | vueRouterMode: 'hash', // available values: 'hash', 'history' 55 | 56 | // transpile: false, 57 | // publicPath: '/', 58 | 59 | // Add dependencies for transpiling with Babel (Array of string/regex) 60 | // (from node_modules, which are by default not transpiled). 61 | // Applies only if "transpile" is set to true. 62 | // transpileDependencies: [], 63 | 64 | // rtl: true, // https://quasar.dev/options/rtl-support 65 | // preloadChunks: true, 66 | // showProgress: false, 67 | // gzip: true, 68 | // analyze: true, 69 | 70 | // Options below are automatically set depending on the env, set them if you want to override 71 | // extractCSS: false, 72 | 73 | // https://v2.quasar.dev/quasar-cli-webpack/handling-webpack 74 | // "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain 75 | chainWebpack(/* chain */) {} 76 | }, 77 | 78 | // Full list of options: https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-devServer 79 | devServer: { 80 | server: { 81 | type: 'http' 82 | }, 83 | port: 8080, 84 | open: true // opens browser window automatically 85 | }, 86 | 87 | // https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-framework 88 | framework: { 89 | config: {}, 90 | 91 | // iconSet: 'material-icons', // Quasar icon set 92 | // lang: 'en-US', // Quasar language pack 93 | 94 | // For special cases outside of where the auto-import strategy can have an impact 95 | // (like functional components as one of the examples), 96 | // you can manually specify Quasar components/directives to be available everywhere: 97 | // 98 | // components: [], 99 | // directives: [], 100 | 101 | // Quasar plugins 102 | plugins: ['Notify'] 103 | }, 104 | 105 | // animations: 'all', // --- includes all animations 106 | // https://quasar.dev/options/animations 107 | animations: [], 108 | 109 | // https://v2.quasar.dev/quasar-cli-webpack/developing-ssr/configuring-ssr 110 | ssr: { 111 | pwa: false, 112 | 113 | // manualStoreHydration: true, 114 | // manualPostHydrationTrigger: true, 115 | 116 | prodPort: 3000, // The default port that the production server should use 117 | // (gets superseded if process.env.PORT is specified at runtime) 118 | 119 | maxAge: 1000 * 60 * 60 * 24 * 30, 120 | // Tell browser when a file from the server should expire from cache (in ms) 121 | 122 | chainWebpackWebserver(/* chain */) {}, 123 | 124 | middlewares: [ 125 | ctx.prod ? 'compression' : '', 126 | 'render' // keep this as last one 127 | ] 128 | }, 129 | 130 | // https://v2.quasar.dev/quasar-cli-webpack/developing-pwa/configuring-pwa 131 | pwa: { 132 | workboxPluginMode: 'GenerateSW', // 'GenerateSW' or 'InjectManifest' 133 | workboxOptions: {}, // only for GenerateSW 134 | 135 | // for the custom service worker ONLY (/src-pwa/custom-service-worker.[js|ts]) 136 | // if using workbox in InjectManifest mode 137 | chainWebpackCustomSW(/* chain */) {}, 138 | 139 | manifest: { 140 | name: 'KeywordFinder', 141 | short_name: 'KeywordFinder', 142 | description: '', 143 | display: 'standalone', 144 | orientation: 'portrait', 145 | background_color: '#ffffff', 146 | theme_color: '#027be3', 147 | icons: [ 148 | { 149 | src: 'icons/icon-128x128.png', 150 | sizes: '128x128', 151 | type: 'image/png' 152 | }, 153 | { 154 | src: 'icons/icon-192x192.png', 155 | sizes: '192x192', 156 | type: 'image/png' 157 | }, 158 | { 159 | src: 'icons/icon-256x256.png', 160 | sizes: '256x256', 161 | type: 'image/png' 162 | }, 163 | { 164 | src: 'icons/icon-384x384.png', 165 | sizes: '384x384', 166 | type: 'image/png' 167 | }, 168 | { 169 | src: 'icons/icon-512x512.png', 170 | sizes: '512x512', 171 | type: 'image/png' 172 | } 173 | ] 174 | } 175 | }, 176 | 177 | // Full list of options: https://v2.quasar.dev/quasar-cli-webpack/developing-cordova-apps/configuring-cordova 178 | cordova: { 179 | // noIosLegacyBuildFlag: true, // uncomment only if you know what you are doing 180 | }, 181 | 182 | // Full list of options: https://v2.quasar.dev/quasar-cli-webpack/developing-capacitor-apps/configuring-capacitor 183 | capacitor: { 184 | hideSplashscreen: true 185 | }, 186 | 187 | // Full list of options: https://v2.quasar.dev/quasar-cli-webpack/developing-electron-apps/configuring-electron 188 | electron: { 189 | bundler: 'packager', // 'packager' or 'builder' 190 | 191 | packager: { 192 | // https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options 193 | // OS X / Mac App Store 194 | // appBundleId: '', 195 | // appCategoryType: '', 196 | // osxSign: '', 197 | // protocol: 'myapp://path', 198 | // Windows only 199 | // win32metadata: { ... } 200 | }, 201 | 202 | builder: { 203 | // https://www.electron.build/configuration/configuration 204 | 205 | appId: '{{ name }}' 206 | }, 207 | 208 | // "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain 209 | chainWebpackMain(/* chain */) { 210 | // do something with the Electron main process Webpack cfg 211 | // extendWebpackMain also available besides this chainWebpackMain 212 | }, 213 | 214 | // "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain 215 | chainWebpackPreload(/* chain */) { 216 | // do something with the Electron main process Webpack cfg 217 | // extendWebpackPreload also available besides this chainWebpackPreload 218 | } 219 | } 220 | }; 221 | }); 222 | -------------------------------------------------------------------------------- /src/controller/session.ts: -------------------------------------------------------------------------------- 1 | import { Category } from 'src/models/CategoryModel'; 2 | import { Profile } from 'src/models/ProfileModel'; 3 | import { PositionEnum, SimpleToastMessages } from 'simple-toast-messages'; 4 | import { Helper } from './helper'; 5 | 6 | /** 7 | * Session 8 | */ 9 | export class Session { 10 | /** 11 | * Instance of session 12 | */ 13 | private static instance: Session; 14 | 15 | private static msg = SimpleToastMessages.getInstance(); 16 | 17 | /** 18 | * Gets instance 19 | * @returns 20 | */ 21 | static getInstance() { 22 | if (!Session.instance && !Session.load()) { 23 | Session.instance = new Session(); 24 | } 25 | if (!Session.instance && Session.load()) { 26 | Session.instance = Session.load(); 27 | } 28 | Session.save(); 29 | return Session.instance; 30 | } 31 | 32 | /** 33 | * Creates an instance of session. 34 | */ 35 | private constructor() { 36 | this.profiles = new Array(); 37 | 38 | const profile = new Profile('Default'); 39 | 40 | let words = Helper.generateWordArray(Helper.generateRandomNumber(1, 10)); 41 | 42 | words = words.filter((w) => !!w); 43 | 44 | profile.addCategory( 45 | new Category( 46 | crypto.randomUUID(), 47 | 'Kategorie 1', 48 | Helper.generateRandomHex(), 49 | words 50 | ) 51 | ); 52 | 53 | this.profiles.push(profile); 54 | this.currentProfile = this.profiles[0]; 55 | } 56 | 57 | /** 58 | * Sets instance to local storage 59 | */ 60 | public static save() { 61 | localStorage.setItem('session', JSON.stringify(this.instance)); 62 | } 63 | 64 | /** 65 | * Gets instance from local storage 66 | * @returns 67 | */ 68 | public static load(): Session | null { 69 | const session = localStorage.getItem('session'); 70 | if (session) { 71 | const obj = JSON.parse(session); 72 | const result = new Session(); 73 | result.profiles = Session.reBuildProfiles(obj.profiles); 74 | result.currentProfile = Session.reBuildProfile(obj.currentProfile); 75 | return result; 76 | } 77 | return null; 78 | } 79 | 80 | /** 81 | * Reloads session 82 | */ 83 | public static reloadSession() { 84 | const session = localStorage.getItem('session'); 85 | if (session) { 86 | const obj = JSON.parse(session); 87 | const result = new Session(); 88 | result.profiles = Session.reBuildProfiles(obj.profiles); 89 | result.currentProfile = Session.reBuildProfile(obj.currentProfile); 90 | Session.instance = result; 91 | } 92 | } 93 | 94 | /** 95 | * Gets profile export url 96 | * @returns profile export url 97 | */ 98 | public static getProfileExportUrl(): string { 99 | let content = JSON.stringify(this.instance.currentProfile); 100 | content = encodeURIComponent(content); 101 | return 'data:application/json;charset=utf-8,' + content; 102 | } 103 | 104 | /** 105 | * Gets profiles export url 106 | * @returns profiles export url 107 | */ 108 | public static getProfilesExportUrl(): string { 109 | let content = JSON.stringify(this.instance.profiles); 110 | content = encodeURIComponent(content); 111 | return 'data:application/json;charset=utf-8,' + content; 112 | } 113 | 114 | /** 115 | * Re build profile 116 | * @param data 117 | * @returns 118 | */ 119 | private static reBuildProfile(data: Profile) { 120 | const profile = new Profile(data.name); 121 | profile.id = data.id; 122 | profile.categories = data.categories; 123 | return profile; 124 | } 125 | 126 | /** 127 | * Re build profiles 128 | * @param data 129 | * @returns 130 | */ 131 | private static reBuildProfiles(data: Array) { 132 | const profiles = new Array(); 133 | for (let i = 0; i < data.length; i++) { 134 | profiles.push(Session.reBuildProfile(data[i])); 135 | } 136 | return profiles; 137 | } 138 | 139 | /** 140 | * Imports profile from json 141 | * @param json 142 | */ 143 | public static importProfileFromJson(json: string) { 144 | const data = JSON.parse(json); 145 | 146 | if (!data.id || !data.name || !data.categories) { 147 | this.msg.error('Import fehlgeschlagen'); 148 | return; 149 | } 150 | 151 | const profile = Session.reBuildProfile(data); 152 | 153 | if (this.instance.profiles.find((p) => p.name === profile.name)) { 154 | this.msg.error('Profile with name ' + profile.name + ' already exists'); 155 | return; 156 | } 157 | 158 | if (this.instance.profiles.find((p) => p.id === profile.id)) { 159 | this.msg.error('Profile with id ' + profile.id + ' already exists'); 160 | return; 161 | } 162 | 163 | this.instance.profiles.push(profile); 164 | this.instance.currentProfile = profile; 165 | Session.save(); 166 | location.reload(); 167 | this.msg.success('Profile wurde importiert', { 168 | timeOut: 3000, 169 | position: PositionEnum.TOP_RIGHT 170 | }); 171 | } 172 | 173 | /** 174 | * Imports profiles from json 175 | * @param json 176 | */ 177 | public static importProfilesFromJson(json: string) { 178 | const profiles = >JSON.parse(json); 179 | 180 | try { 181 | profiles.forEach((data) => { 182 | if (!data.id || !data.name || !data.categories) { 183 | this.msg.error('Import fehlgeschlagen'); 184 | return; 185 | } 186 | 187 | const profile = Session.reBuildProfile(data); 188 | if ( 189 | !this.instance.profiles.find((p) => p.name === profile.name) && 190 | !this.instance.profiles.find((p) => p.id === profile.id) 191 | ) { 192 | this.instance.profiles.push(profile); 193 | this.instance.currentProfile = profile; 194 | } 195 | }); 196 | } catch { 197 | this.msg.error('Import fehlgeschlagen'); 198 | } 199 | Session.save(); 200 | location.reload(); 201 | this.msg.success('Profile wurde importiert', { 202 | timeOut: 3000, 203 | position: PositionEnum.TOP_RIGHT 204 | }); 205 | } 206 | 207 | /** 208 | * Session id of session 209 | */ 210 | public readonly sessionId: string = crypto.randomUUID(); 211 | 212 | /** 213 | * Profiles of session 214 | */ 215 | public profiles: Array; 216 | 217 | /** 218 | * Active profile of session 219 | */ 220 | public currentProfile: Profile; 221 | 222 | /** 223 | * Sets current profile 224 | * @param profile 225 | */ 226 | public setCurrentProfile(profile: Profile) { 227 | if (this.profiles.find((p) => p.name === profile.name)) { 228 | this.currentProfile = ( 229 | this.profiles.find((p) => p.name === profile.name) 230 | ); 231 | } else { 232 | this.currentProfile = this.profiles[0]; 233 | } 234 | Session.save(); 235 | } 236 | 237 | /** 238 | * Resets session 239 | */ 240 | public static resetSession() { 241 | localStorage.removeItem('session'); 242 | sessionStorage.removeItem('session'); 243 | this.instance = new Session(); 244 | Session.save(); 245 | location.reload(); 246 | this.msg.info('Session wurde zurückgesetzt', { 247 | timeOut: 3000, 248 | position: PositionEnum.TOP_RIGHT 249 | }); 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/controller/string.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * String controller 3 | */ 4 | export default class StringController { 5 | private static _instance: StringController; 6 | 7 | /** 8 | * Gets instance 9 | */ 10 | public static getInstance(): StringController { 11 | if (!this._instance) { 12 | this._instance = new StringController(); 13 | } 14 | return this._instance; 15 | } 16 | 17 | private constructor() { 18 | console.log('StringController created'); 19 | } 20 | 21 | /** 22 | * Purges xmltags 23 | * @param str 24 | * @returns xmltags 25 | * 26 | * @example 27 | * ``` 28 | * const str = '

Hello

'; 29 | * const result = purgeXmltags(str); 30 | * console.log(result); 31 | * // Hello 32 | */ 33 | public purgeXmltags(str: string): string { 34 | str = str.replace(/['"]+/g, ''); 35 | str = this.xmlRemoveScript(str); 36 | return str.replace(/<[^>]*>/g, ''); 37 | } 38 | 39 | /** 40 | * Xmls remove coments 41 | * @param str 42 | * @returns remove coments 43 | */ 44 | public xmlRemoveComents(str: string): string { 45 | str = str.replace(/['"]+/g, ''); 46 | str = str.replace(/\[/g, ''); 47 | return str.replace(//g, ''); 48 | } 49 | 50 | /** 51 | * Xmls remove script 52 | * @param str 53 | * @returns remove script 54 | */ 55 | public xmlRemoveScript(str: string): string { 56 | str = str.replace(/['"]+/g, ''); 57 | return this.htmlRemoveScript(this.xmlRemoveComents(str)); 58 | } 59 | 60 | /** 61 | * Xmls remove cdata 62 | * @param str 63 | * @returns remove cdata 64 | */ 65 | public xmlRemoveCdata(str: string): string { 66 | str = str.replace(/['"]+/g, ''); 67 | return str.replace(//g, ''); 68 | } 69 | 70 | /** 71 | * Purges html 72 | * @param str 73 | * @returns string 74 | * 75 | * @example 76 | * ``` 77 | * const str = '

Hello

'; 78 | * const result = purgeHtml(str); 79 | * console.log(result); 80 | * // Hello 81 | */ 82 | public purgeHtml(str: string): string { 83 | str = str.replace(/['"]+/g, ''); 84 | str = this.xmlRemoveScript(str); 85 | str = this.xmlRemoveCdata(str); 86 | str = this.xmlRemoveComents(str); 87 | str = this.purgeXmltags(str); 88 | return this.htmlSanitize(str); 89 | } 90 | 91 | /** 92 | * Htmls sanitize 93 | * @param str 94 | * @returns sanitized string 95 | */ 96 | public htmlSanitize(str: string): string { 97 | return str 98 | .replace(/&/g, '&') 99 | .replace(//g, '>') 101 | .replace(/"/g, '"') 102 | .replace(/'/g, '''); 103 | } 104 | 105 | /** 106 | * Htmls remove coments 107 | * @param str 108 | * @returns remove coments 109 | */ 110 | public htmlRemoveComents(str: string): string { 111 | str = str.replace(/['"]+/g, ''); 112 | return str.replace(//g, ''); 113 | } 114 | 115 | /** 116 | * Htmls remove script 117 | * @param str 118 | * @returns remove script 119 | */ 120 | public htmlRemoveScript(str: string): string { 121 | str = str.replace(/['"]+/g, ''); 122 | str = str.replace(/\[/g, ''); 123 | str = str.replace(/.*?<\/script>/gi, ''); 124 | str = str.replace(/]*>[\s\S]*?<\/script>/gi, ''); 125 | return str.replace( 126 | //gi, 127 | '' 128 | ); 129 | } 130 | 131 | /** 132 | * To camel case 133 | * @param str 134 | * @returns camel case 135 | */ 136 | public toCamelCase(str: string): string { 137 | return str 138 | .replace(/\s(.)/g, function ($1) { 139 | return $1.toUpperCase(); 140 | }) 141 | .replace(/\s/g, ''); 142 | } 143 | 144 | /** 145 | * To snake case 146 | * @param str 147 | * @returns snake case 148 | */ 149 | public toSnakeCase(str: string): string { 150 | return str.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase(); 151 | } 152 | 153 | /** 154 | * To kebab case 155 | * @param str 156 | * @returns kebab case 157 | */ 158 | public toKebabCase(str: string): string { 159 | return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); 160 | } 161 | 162 | /** 163 | * Purges sql 164 | * @param str 165 | * @returns sql 166 | */ 167 | public purgeSql(str: string): string { 168 | return str.replace(/'/g, "''"); 169 | } 170 | 171 | /** 172 | * Purges json 173 | * @param str 174 | * @returns json 175 | */ 176 | public purgeJson(str: string): string { 177 | str = str.replace(/\\/g, '\\\\'); 178 | return str.replace(/'/g, "\\'"); 179 | } 180 | 181 | /** 182 | * Purges dangerous characters 183 | * @param str 184 | * @returns dangerous characters 185 | */ 186 | public purgeDangerousCharacters(str: string): string { 187 | // eslint-disable-next-line no-control-regex 188 | return str.replace(/[\u0000-\u001F]/g, ''); 189 | } 190 | 191 | /** 192 | * Cleans string controller 193 | * @param str 194 | * @returns clean 195 | */ 196 | public clean(str: string): string { 197 | return str.replace(/\s+/g, ' ').trim(); 198 | } 199 | 200 | /** 201 | * Purges markdown 202 | * @param str 203 | * @returns markdown 204 | */ 205 | public purgeMarkdown(str: string): string { 206 | // remove markdown header 207 | str = str.replace(/^#+\s+/, ''); 208 | 209 | // remove markdown list 210 | str = str.replace(/^\s*\*\s*/gm, ''); 211 | 212 | // remove markdown link 213 | str = str.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); 214 | 215 | // remove markdown image 216 | str = str.replace(/!\[([^\]]+)\]\(([^)]+)\)/g, '$1'); 217 | 218 | // remove markdown code 219 | str = str.replace(/`([^`]+)`/g, '$1'); 220 | 221 | // remove markdown italic 222 | str = str.replace(/\*([^*]+)\*/g, '$1'); 223 | 224 | // remove markdown bold 225 | str = str.replace(/\*\*([^*]+)\*\*/g, '$1'); 226 | 227 | // remove markdown bold italic 228 | str = str.replace(/\*\*\*([^*]+)\*\*/g, '$1'); 229 | 230 | return str; 231 | } 232 | 233 | /** 234 | * Purges all 235 | * @param str 236 | * @returns all 237 | */ 238 | public purgeAll(str: string): string { 239 | return this.purgeXmltags( 240 | this.purgeHtml( 241 | this.purgeSql( 242 | this.purgeJson( 243 | this.purgeDangerousCharacters(this.clean(this.purgeMarkdown(str))) 244 | ) 245 | ) 246 | ) 247 | ); 248 | } 249 | 250 | /** 251 | * Gets string from array 252 | * @param array 253 | * @returns string from array 254 | */ 255 | public getStringFromArray(array: string[]): string { 256 | return array.join(', '); 257 | } 258 | 259 | /** 260 | * Gets array from string 261 | * @param str 262 | * @returns array from string 263 | */ 264 | public getArrayFromString(str: string): string[] { 265 | return str.split(', '); 266 | } 267 | 268 | /** 269 | * Capitalise string controller 270 | * @param str 271 | * @returns Capitalise 272 | */ 273 | public capitalise(str: string): string { 274 | return str.charAt(0).toUpperCase() + str.slice(1); 275 | } 276 | 277 | /** 278 | * Decapitalise string controller 279 | * @param str 280 | * @returns decapitalise 281 | */ 282 | public decapitalise(str: string): string { 283 | return str.charAt(0).toLowerCase() + str.slice(1); 284 | } 285 | 286 | /** 287 | * To uppercase 288 | * @param str 289 | * @returns uppercase 290 | */ 291 | public toUppercase(str: string): string { 292 | return str.toUpperCase(); 293 | } 294 | 295 | /** 296 | * To lowercase 297 | * @param str 298 | * @returns lowercase 299 | */ 300 | public toLowercase(str: string): string { 301 | return str.toLowerCase(); 302 | } 303 | 304 | /** 305 | * String to buffer 306 | * @param str 307 | * @returns to buffer 308 | */ 309 | public stringToBuffer(str: string): Buffer { 310 | return Buffer.from(str); 311 | } 312 | 313 | /** 314 | * Replaces all 315 | * @param str 316 | * @param find 317 | * @param replace 318 | * @returns all 319 | */ 320 | public replaceAll(str: string, find: string, replace: string): string { 321 | return str.replace(new RegExp(find, 'g'), replace); 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /src/components/CategoriesComponent.vue: -------------------------------------------------------------------------------- 1 | 156 | 157 | 342 | 343 | 430 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | --------------------------------------------------------------------------------