Transition to eslint-config-airbnb-with-typescript

- Migrate to newer `eslint-config-airbnb-with-typescript` from
  `eslint-config-airbnb`.
- Add also `rushstack/eslint-patch` as per instructed by
  `eslint-config-airbnb-with-typescript` docs.
- Update codebase to align with new linting standards.
- Add script to configure VS Code for effective linting for project
  developers, move it to `scripts` directory along with clean npm
  install script for better organization.
This commit is contained in:
undergroundwires
2023-08-04 16:39:36 +02:00
parent 4d0ce12c96
commit ff84f5676e
39 changed files with 2394 additions and 696 deletions

View File

@@ -1,4 +1,4 @@
[*.{js,jsx,ts,tsx,vue}]
[*.{js,jsx,ts,tsx,vue,sh}]
indent_style = space
indent_size = 2
end_of_line = lf

View File

@@ -1,10 +1,6 @@
const { rules: baseBestPracticesRules } = require('eslint-config-airbnb-base/rules/best-practices');
const { rules: baseErrorsRules } = require('eslint-config-airbnb-base/rules/errors');
const { rules: baseES6Rules } = require('eslint-config-airbnb-base/rules/es6');
const { rules: baseImportsRules } = require('eslint-config-airbnb-base/rules/imports');
const { rules: baseStyleRules } = require('eslint-config-airbnb-base/rules/style');
const { rules: baseVariablesRules } = require('eslint-config-airbnb-base/rules/variables');
const tsconfigJson = require('./tsconfig.json');
require('@rushstack/eslint-patch/modern-module-resolution');
module.exports = {
root: true,
@@ -17,9 +13,7 @@ module.exports = {
'plugin:vue/essential',
// Extends eslint-config-airbnb
// Added by Vue CLI
// Here until https://github.com/vuejs/eslint-config-airbnb/issues/23 is done
'@vue/airbnb',
'@vue/eslint-config-airbnb-with-typescript',
// Extends @typescript-eslint/recommended
// Uses the recommended rules from the @typescript-eslint/eslint-plugin
@@ -52,18 +46,6 @@ module.exports = {
mocha: true,
},
},
{
files: ['**/*.ts?(x)', '**/*.d.ts'],
parserOptions: {
// Setting project is required for some rules such as @typescript-eslint/dot-notation,
// @typescript-eslint/return-await and @typescript-eslint/no-throw-literal.
// If this property is missing they fail due to missing parser.
project: ['./tsconfig.json'],
},
rules: {
...getTypeScriptOverrides(),
},
},
{
files: ['**/tests/**/*.{j,t}s?(x)'],
rules: {
@@ -115,6 +97,7 @@ function getOpinionatedRuleOverrides() {
return {
// https://erkinekici.com/articles/linting-trap#no-use-before-define
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': 'off',
// https://erkinekici.com/articles/linting-trap#arrow-body-style
'arrow-body-style': 'off',
// https://erkinekici.com/articles/linting-trap#no-plusplus
@@ -134,164 +117,6 @@ function getOpinionatedRuleOverrides() {
};
}
function getTypeScriptOverrides() {
/*
Here until Vue supports AirBnb Typescript overrides (vuejs/eslint-config-airbnb#23).
Based on `eslint-config-airbnb-typescript`.
Source: https://github.com/iamturns/eslint-config-airbnb-typescript/blob/v16.1.0/lib/shared.js
It cannot be used directly due to compilation errors.
*/
return {
'brace-style': 'off',
'@typescript-eslint/brace-style': baseStyleRules['brace-style'],
camelcase: 'off',
'@typescript-eslint/naming-convention': [
'error',
{ selector: 'variable', format: ['camelCase', 'PascalCase', 'UPPER_CASE'] },
{ selector: 'function', format: ['camelCase', 'PascalCase'] },
{ selector: 'typeLike', format: ['PascalCase'] },
],
'comma-dangle': 'off',
'@typescript-eslint/comma-dangle': [
baseStyleRules['comma-dangle'][0],
{
...baseStyleRules['comma-dangle'][1],
enums: baseStyleRules['comma-dangle'][1].arrays,
generics: baseStyleRules['comma-dangle'][1].arrays,
tuples: baseStyleRules['comma-dangle'][1].arrays,
},
],
'comma-spacing': 'off',
'@typescript-eslint/comma-spacing': baseStyleRules['comma-spacing'],
'default-param-last': 'off',
'@typescript-eslint/default-param-last': baseBestPracticesRules['default-param-last'],
'dot-notation': 'off',
'@typescript-eslint/dot-notation': baseBestPracticesRules['dot-notation'],
'func-call-spacing': 'off',
'@typescript-eslint/func-call-spacing': baseStyleRules['func-call-spacing'],
// ❌ Broken for some cases, but still useful.
// Here until Prettifier is used.
indent: 'off',
'@typescript-eslint/indent': baseStyleRules.indent,
'keyword-spacing': 'off',
'@typescript-eslint/keyword-spacing': baseStyleRules['keyword-spacing'],
'lines-between-class-members': 'off',
'@typescript-eslint/lines-between-class-members': baseStyleRules['lines-between-class-members'],
'no-array-constructor': 'off',
'@typescript-eslint/no-array-constructor': baseStyleRules['no-array-constructor'],
'no-dupe-class-members': 'off',
'@typescript-eslint/no-dupe-class-members': baseES6Rules['no-dupe-class-members'],
'no-empty-function': 'off',
'@typescript-eslint/no-empty-function': baseBestPracticesRules['no-empty-function'],
'no-extra-parens': 'off',
'@typescript-eslint/no-extra-parens': baseErrorsRules['no-extra-parens'],
'no-extra-semi': 'off',
'@typescript-eslint/no-extra-semi': baseErrorsRules['no-extra-semi'],
// ❌ Fails due to missing parser
// 'no-implied-eval': 'off',
// 'no-new-func': 'off',
// '@typescript-eslint/no-implied-eval': baseBestPracticesRules['no-implied-eval'],
'no-loss-of-precision': 'off',
'@typescript-eslint/no-loss-of-precision': baseErrorsRules['no-loss-of-precision'],
'no-loop-func': 'off',
'@typescript-eslint/no-loop-func': baseBestPracticesRules['no-loop-func'],
'no-magic-numbers': 'off',
'@typescript-eslint/no-magic-numbers': baseBestPracticesRules['no-magic-numbers'],
'no-redeclare': 'off',
'@typescript-eslint/no-redeclare': baseBestPracticesRules['no-redeclare'],
// ESLint variant does not work with TypeScript enums.
'no-shadow': 'off',
'@typescript-eslint/no-shadow': baseVariablesRules['no-shadow'],
'no-throw-literal': 'off',
'@typescript-eslint/no-throw-literal': baseBestPracticesRules['no-throw-literal'],
'no-unused-expressions': 'off',
'@typescript-eslint/no-unused-expressions': baseBestPracticesRules['no-unused-expressions'],
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': baseVariablesRules['no-unused-vars'],
// https://erkinekici.com/articles/linting-trap#no-use-before-define
// 'no-use-before-define': 'off',
// '@typescript-eslint/no-use-before-define': baseVariablesRules['no-use-before-define'],
// ESLint variant does not understand TypeScript constructors.
// eslint/eslint/#14118, typescript-eslint/typescript-eslint#873
'no-useless-constructor': 'off',
'@typescript-eslint/no-useless-constructor': baseES6Rules['no-useless-constructor'],
quotes: 'off',
'@typescript-eslint/quotes': baseStyleRules.quotes,
semi: 'off',
'@typescript-eslint/semi': baseStyleRules.semi,
'space-before-function-paren': 'off',
'@typescript-eslint/space-before-function-paren': baseStyleRules['space-before-function-paren'],
'require-await': 'off',
'@typescript-eslint/require-await': baseBestPracticesRules['require-await'],
'no-return-await': 'off',
'@typescript-eslint/return-await': baseBestPracticesRules['no-return-await'],
'space-infix-ops': 'off',
'@typescript-eslint/space-infix-ops': baseStyleRules['space-infix-ops'],
'object-curly-spacing': 'off',
'@typescript-eslint/object-curly-spacing': baseStyleRules['object-curly-spacing'],
'import/extensions': [
baseImportsRules['import/extensions'][0],
baseImportsRules['import/extensions'][1],
{
...baseImportsRules['import/extensions'][2],
ts: 'never',
tsx: 'never',
},
],
// Changes required is not yet implemented:
// 'import/no-extraneous-dependencies': [
// baseImportsRules['import/no-extraneous-dependencies'][0],
// {
// ...baseImportsRules['import/no-extraneous-dependencies'][1],
// devDependencies: baseImportsRules[
// 'import/no-extraneous-dependencies'
// ][1].devDependencies.reduce((result, devDep) => {
// const toAppend = [devDep];
// const devDepWithTs = devDep.replace(/\bjs(x?)\b/g, 'ts$1');
// if (devDepWithTs !== devDep) {
// toAppend.push(devDepWithTs);
// }
// return [...result, ...toAppend];
// }, []),
// },
// ],
};
}
function getAliasesFromTsConfig() {
return Object.keys(tsconfigJson.compilerOptions.paths)
.map((path) => `${path}*`);

View File

@@ -47,6 +47,13 @@ You could run other types of tests as well, but they may take longer time and ov
- Build desktop application: `npm run electron:build`
- (Re)create icons (see [documentation](../img/README.md)): `npm run create-icons`
### Utility Scripts
- Run fresh NPM install: [`./scripts/fresh-npm-install.sh`](../scripts/fresh-npm-install.sh)
- This script provides a clean NPM install, removing existing node modules and optionally the package-lock.json (when run with -n), then installs dependencies and runs unit tests.
- Configure VSCode: [`./scripts/configure-vscode.sh`](../scripts/configure-vscode.sh)
- This script checks and sets the necessary configurations for VSCode in `settings.json` file.
## Recommended extensions
You should use EditorConfig to follow project style.

2497
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -47,6 +47,7 @@
"vue-property-decorator": "^9.1.2"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.3.2",
"@types/ace": "^0.0.48",
"@types/chai": "^4.3.5",
"@types/file-saver": "^2.0.5",
@@ -59,7 +60,7 @@
"@vue/cli-plugin-typescript": "~5.0.8",
"@vue/cli-plugin-unit-mocha": "~5.0.8",
"@vue/cli-service": "~5.0.8",
"@vue/eslint-config-airbnb": "^6.0.0",
"@vue/eslint-config-airbnb-with-typescript": "^7.0.0",
"@vue/eslint-config-typescript": "^11.0.3",
"chai": "^4.3.7",
"cypress": "^12.17.2",
@@ -86,7 +87,8 @@
"ts-loader": "^9.4.4",
"typescript": "~4.6.2",
"vue-cli-plugin-electron-builder": "^3.0.0-alpha.4",
"yaml-lint": "^1.7.0"
"yaml-lint": "^1.7.0",
"tslib": "~2.4.0"
},
"overrides": {
"vue-cli-plugin-electron-builder": {

74
scripts/configure-vscode.sh Executable file
View File

@@ -0,0 +1,74 @@
#!/usr/bin/env bash
# This script ensures that the '.vscode/settings.json' file exists and is configured correctly for ESLint validation on Vue and JavaScript files.
# See https://web.archive.org/web/20230801024405/https://eslint.vuejs.org/user-guide/#visual-studio-code
declare -r SETTINGS_FILE='.vscode/settings.json'
declare -ra CONFIG_KEYS=('vue' 'javascript' 'typescript')
declare -r TEMP_FILE="tmp.$$.json"
main() {
ensure_vscode_directory_exists
create_or_update_settings
}
ensure_vscode_directory_exists() {
local dir_name
dir_name=$(dirname "${SETTINGS_FILE}")
if [[ ! -d ${dir_name} ]]; then
mkdir -p "${dir_name}"
echo "🎉 Created directory: ${dir_name}"
fi
}
create_or_update_settings() {
if [[ ! -f ${SETTINGS_FILE} ]]; then
create_default_settings
else
add_or_update_eslint_validate
fi
}
create_default_settings() {
local default_validate
default_validate=$(printf '%s' "${CONFIG_KEYS[*]}" | jq -R -s -c -M 'split(" ")')
echo "{ \"eslint.validate\": ${default_validate} }" | jq '.' > "${SETTINGS_FILE}"
echo "🎉 Created default ${SETTINGS_FILE}"
}
add_or_update_eslint_validate() {
if ! jq -e '.["eslint.validate"]' "${SETTINGS_FILE}" >/dev/null; then
add_default_eslint_validate
else
update_eslint_validate
fi
}
add_default_eslint_validate() {
jq --argjson keys "$(printf '%s' "${CONFIG_KEYS[*]}" \
| jq -R -s -c 'split(" ")')" '. += {"eslint.validate": $keys}' "${SETTINGS_FILE}" > "${TEMP_FILE}"
replace_and_confirm
echo "🎉 Added default 'eslint.validate' to ${SETTINGS_FILE}"
}
update_eslint_validate() {
local existing_keys
existing_keys=$(jq '.["eslint.validate"]' "${SETTINGS_FILE}")
for key in "${CONFIG_KEYS[@]}"; do
if ! echo "${existing_keys}" | jq 'index("'"${key}"'")' >/dev/null; then
jq '.["eslint.validate"] += ["'"${key}"'"]' "${SETTINGS_FILE}" > "${TEMP_FILE}"
mv "${TEMP_FILE}" "${SETTINGS_FILE}"
echo "🎉 Updated 'eslint.validate' in ${SETTINGS_FILE} for ${key}"
else
echo "⏩️ No updated needed for ${key} ${SETTINGS_FILE}."
fi
done
}
replace_and_confirm() {
if mv "${TEMP_FILE}" "${SETTINGS_FILE}"; then
echo "🎉 Updated ${SETTINGS_FILE}"
fi
}
main

View File

@@ -8,7 +8,7 @@ export class EscapeDoubleQuotes implements IPipe {
return raw;
}
return raw.replaceAll('"', '"^""');
/* eslint-disable max-len */
/* eslint-disable vue/max-len */
/*
"^"" is the most robust and stable choice.
Other options:
@@ -28,6 +28,6 @@ export class EscapeDoubleQuotes implements IPipe {
Works when using "^"": `PowerShell -Command ""^""a& c"^"".length"`
A good explanation: https://stackoverflow.com/a/31413730
*/
/* eslint-enable max-len */
/* eslint-enable vue/max-len */
}
}

View File

@@ -1,13 +1,13 @@
<template>
<div id="app">
<div class="app__wrapper">
<TheHeader class="app__row" />
<TheSearchBar class="app__row" />
<TheScriptArea class="app__row" />
<TheCodeButtons class="app__row app__code-buttons" />
<TheFooter />
</div>
</div>
<div class="app__wrapper">
<TheHeader class="app__row" />
<TheSearchBar class="app__row" />
<TheScriptArea class="app__row" />
<TheCodeButtons class="app__row app__code-buttons" />
<TheFooter />
</div>
</div>
</template>
<script lang="ts">

View File

@@ -1,8 +1,14 @@
<template>
<button class="button" @click="onClicked">
<button
class="button"
type="button"
@click="onClicked"
>
<font-awesome-icon
class="button__icon"
:icon="[iconPrefix, iconName]" size="2x" />
:icon="[iconPrefix, iconName]"
size="2x"
/>
<div class="button__text">{{text}}</div>
</button>
</template>

View File

@@ -1,7 +1,7 @@
<template>
<span class="code-wrapper">
<span class="dollar">$</span>
<code><slot></slot></code>
<code><slot /></code>
<font-awesome-icon
class="copy-button"
:icon="['fas', 'copy']"

View File

@@ -7,8 +7,8 @@
<p>
<strong>1. The easy alternative</strong>. Run your script without any manual steps by
<a :href="this.macOsDownloadUrl">downloading desktop version</a> of {{ this.appName }} on the
{{ this.osName }} system you wish to configure, and then click on the Run button. This is
recommended for most users.
{{ this.osName }} system you wish to configure, and then click on the Run button. This is
recommended for most users.
</p>
<hr />
<p>

View File

@@ -4,19 +4,21 @@
v-if="this.canRun"
text="Run"
v-on:click="executeCode"
icon-prefix="fas" icon-name="play">
</IconButton>
icon-prefix="fas"
icon-name="play"
/>
<IconButton
:text="this.isDesktopVersion ? 'Save' : 'Download'"
v-on:click="saveCode"
icon-prefix="fas"
:icon-name="this.isDesktopVersion ? 'save' : 'file-download'">
</IconButton>
:icon-name="this.isDesktopVersion ? 'save' : 'file-download'"
/>
<IconButton
text="Copy"
v-on:click="copyCode"
icon-prefix="fas" icon-name="copy">
</IconButton>
icon-prefix="fas"
icon-name="copy"
/>
<Dialog v-if="this.hasInstructions" ref="instructionsDialog">
<InstructionList :data="this.instructions" />
</Dialog>

View File

@@ -5,7 +5,7 @@
<div
:id="editorId"
class="code-area"
></div>
/>
</Responsive>
</template>

View File

@@ -1,7 +1,10 @@
<template>
<span> <!-- Parent wrapper allows adding content inside with CSS without making it clickable -->
<span
v-bind:class="{ 'disabled': !enabled, 'enabled': enabled}"
v-bind:class="{
disabled: !enabled,
enabled: enabled,
}"
v-non-collapsing
@click="enabled && onClicked()">{{label}}</span>
</span>

View File

@@ -4,32 +4,39 @@
label="None"
:enabled="this.currentSelection !== SelectionType.None"
@click="selectType(SelectionType.None)"
v-tooltip=" 'Deselect all selected scripts.<br/>' +
'💡 Good start to dive deeper into tweaks and select only what you want.'"
/>
v-tooltip="
'Deselect all selected scripts.<br/>'
+ '💡 Good start to dive deeper into tweaks and select only what you want.'
"
/>
<MenuOptionListItem
label="Standard"
:enabled="this.currentSelection !== SelectionType.Standard"
@click="selectType(SelectionType.Standard)"
v-tooltip=" '🛡️ Balanced for privacy and functionality.<br/>' +
'OS and applications will function normally.<br/>' +
'💡 Recommended for everyone'"
/>
v-tooltip="
'🛡️ Balanced for privacy and functionality.<br/>'
+ 'OS and applications will function normally.<br/>'
+ '💡 Recommended for everyone'"
/>
<MenuOptionListItem
label="Strict"
:enabled="this.currentSelection !== SelectionType.Strict"
@click="selectType(SelectionType.Strict)"
v-tooltip=" '🚫 Stronger privacy, disables risky functions that may leak your data.<br/>' +
+ '⚠️ Double check to remove scripts where you would trade functionality for privacy<br/>'
+ '💡 Recommended for daily users that prefers more privacy over non-essential functions'"
/>
v-tooltip="
'🚫 Stronger privacy, disables risky functions that may leak your data.<br/>'
+ '⚠️ Double check to remove scripts where you would trade functionality for privacy<br/>'
+ '💡 Recommended for daily users that prefers more privacy over non-essential functions'
"
/>
<MenuOptionListItem
label="All"
:enabled="this.currentSelection !== SelectionType.All"
@click="selectType(SelectionType.All)"
v-tooltip=" '🔒 Strongest privacy, disabling any functionality that may leak your data.<br/>'
label="All"
:enabled="this.currentSelection !== SelectionType.All"
@click="selectType(SelectionType.All)"
v-tooltip="
'🔒 Strongest privacy, disabling any functionality that may leak your data.<br/>'
+ '🛑 Not designed for daily users, it will break important functionalities.<br/>'
+ '💡 Only recommended for extreme use-cases like crime labs where no leak is acceptable'"
+ '💡 Only recommended for extreme use-cases like crime labs where no leak is acceptable'
"
/>
</MenuOptionList>
</template>

View File

@@ -1,11 +1,12 @@
<template>
<MenuOptionList>
<MenuOptionListItem
v-for="os in this.allOses" :key="os.name"
v-for="os in this.allOses"
:key="os.name"
:enabled="currentOs !== os.os"
@click="changeOs(os.os)"
:label="os.name"
/>
/>
</MenuOptionList>
</template>

View File

@@ -1,7 +1,7 @@
<template>
<div id="container">
<TheSelector class="item" />
<TheOsChanger class="item" />
<TheOsChanger class="item" />
<TheViewChanger
class="item"
v-on:viewChanged="$emit('viewChanged', $event)"

View File

@@ -3,7 +3,8 @@
label="View"
class="part">
<MenuOptionListItem
v-for="view in this.viewOptions" :key="view.type"
v-for="view in this.viewOptions"
:key="view.type"
:label="view.displayName"
:enabled="currentView !== view.type"
@click="setView(view.type)"

View File

@@ -5,8 +5,8 @@
@mousedown="startResize">
<div class="line" />
<font-awesome-icon
class="icon"
:icon="['fas', 'arrows-alt-h']"
class="icon"
:icon="['fas', 'arrows-alt-h']"
/>
<div class="line" />
</div>

View File

@@ -1,16 +1,19 @@
<template>
<div class="slider" v-bind:style="{
'--vertical-margin': this.verticalMargin,
'--first-min-width': this.firstMinWidth,
'--first-initial-width': this.firstInitialWidth,
'--second-min-width': this.secondMinWidth,
}">
<div
class="slider"
v-bind:style="{
'--vertical-margin': this.verticalMargin,
'--first-min-width': this.firstMinWidth,
'--first-initial-width': this.firstInitialWidth,
'--second-min-width': this.secondMinWidth,
}"
>
<div class="first" ref="firstElement">
<slot name="first"></slot>
<slot name="first" />
</div>
<Handle class="handle" @resized="onResize($event)" />
<div class="second">
<slot name="second"></slot>
<slot name="second" />
</div>
</div>
</template>

View File

@@ -1,9 +1,13 @@
<template>
<div class="scripts">
<TheScriptsMenu v-on:viewChanged="currentView = $event" />
<HorizontalResizeSlider class="row"
verticalMargin="15px" firstInitialWidth="55%"
firstMinWidth="20%" secondMinWidth="20%">
<HorizontalResizeSlider
class="row"
verticalMargin="15px"
firstInitialWidth="55%"
firstMinWidth="20%"
secondMinWidth="20%"
>
<template v-slot:first>
<TheScriptsView :currentView="currentView" />
</template>

View File

@@ -9,21 +9,24 @@
<span v-if="width >= 750">big</span>
</div>
-->
<div v-if="categoryIds != null && categoryIds.length > 0" class="cards">
<div
v-if="categoryIds != null && categoryIds.length > 0"
class="cards"
>
<CardListItem
class="card"
v-bind:class="{
'small-screen': width <= 500,
'medium-screen': width > 500 && width < 750,
'big-screen': width >= 750
'big-screen': width >= 750,
}"
v-for="categoryId of categoryIds"
:data-category="categoryId"
v-bind:key="categoryId"
:categoryId="categoryId"
:activeCategoryId="activeCategoryId"
v-on:selected="onSelected(categoryId, $event)">
</CardListItem>
v-on:selected="onSelected(categoryId, $event)"
/>
</div>
<div v-else class="error">Something went bad 😢</div>
</Responsive>

View File

@@ -1,44 +1,48 @@
<template>
<div class="card"
<div
class="card"
v-on:click="onSelected(!isExpanded)"
v-bind:class="{
'is-collapsed': !isExpanded,
'is-inactive': activeCategoryId && activeCategoryId != categoryId,
'is-expanded': isExpanded
'is-expanded': isExpanded,
}"
ref="cardElement">
<div class="card__inner">
<!-- Title -->
<span
class="card__inner__title"
v-if="cardTitle && cardTitle.length > 0">
<span>{{cardTitle}}</span>
</span>
<span v-else>Oh no 😢</span>
<!-- Expand icon -->
<div class="card__inner">
<!-- Title -->
<span
class="card__inner__title"
v-if="cardTitle && cardTitle.length > 0">
<span>{{cardTitle}}</span>
</span>
<span v-else>Oh no 😢</span>
<!-- Expand icon -->
<font-awesome-icon
class="card__inner__expand-icon"
:icon="['far', isExpanded ? 'folder-open' : 'folder']"
/>
<!-- Indeterminate and full states -->
<div class="card__inner__state-icons">
<font-awesome-icon
class="card__inner__expand-icon"
:icon="['far', isExpanded ? 'folder-open' : 'folder']"
:icon="['fa', 'battery-half']"
v-if="isAnyChildSelected && !areAllChildrenSelected"
/>
<font-awesome-icon
:icon="['fa', 'battery-full']"
v-if="areAllChildrenSelected"
/>
</div>
</div>
<div class="card__expander" v-on:click.stop>
<div class="card__expander__content">
<ScriptsTree :categoryId="categoryId" />
</div>
<div class="card__expander__close-button">
<font-awesome-icon
:icon="['fas', 'times']"
v-on:click="onSelected(false)"
/>
<!-- Indeterminate and full states -->
<div class="card__inner__state-icons">
<font-awesome-icon
:icon="['fa', 'battery-half']"
v-if="isAnyChildSelected && !areAllChildrenSelected"
/>
<font-awesome-icon
:icon="['fa', 'battery-full']"
v-if="areAllChildrenSelected"
/>
</div>
</div>
<div class="card__expander" v-on:click.stop>
<div class="card__expander__content">
<ScriptsTree :categoryId="categoryId"></ScriptsTree>
</div>
<div class="card__expander__close-button">
<font-awesome-icon :icon="['fas', 'times']" v-on:click="onSelected(false)"/>
</div>
</div>
</div>
</template>
@@ -82,7 +86,7 @@ export default class CardListItem extends StatefulVue {
}
@Watch('activeCategoryId')
public async onActiveCategoryChanged(value: |number) {
public async onActiveCategoryChanged(value?: number) {
this.isExpanded = value === this.categoryId;
}
@@ -96,7 +100,7 @@ export default class CardListItem extends StatefulVue {
}
@Watch('categoryId')
public async updateState(value: |number) {
public async updateState(value?: number) {
const context = await this.getCurrentContext();
const category = !value ? undefined : context.state.collection.findCategory(value);
this.cardTitle = category ? category.name : undefined;

View File

@@ -7,8 +7,7 @@
:filterPredicate="filterPredicate"
:filterText="filterText"
v-on:nodeSelected="toggleNodeSelection($event)"
>
</SelectableTree>
/>
</span>
<span v-else>Nooo 😢</span>
</span>

View File

@@ -13,10 +13,15 @@
<div
v-if="docs && docs.length > 0 && isExpanded"
class="docs"
v-bind:class="{ 'docs-expanded': isExpanded, 'docs-collapsed': !isExpanded }" >
v-bind:class="{ 'docs-expanded': isExpanded, 'docs-collapsed': !isExpanded }"
>
<DocumentationText
:docs="docs" class="text"
v-bind:class="{ 'expanded': isExpanded, 'collapsed': !isExpanded }" />
:docs="docs"
class="text"
v-bind:class="{
expanded: isExpanded,
collapsed: !isExpanded,
}" />
</div>
</div>
</template>

View File

@@ -3,8 +3,7 @@
class="documentation-text"
v-html="renderedText"
v-on:click.stop
>
</div>
/>
</template>
<script lang="ts">

View File

@@ -23,7 +23,7 @@ import Documentable from './Documentation/Documentable.vue';
},
})
export default class Node extends Vue {
@Prop() public data: INode;
@Prop() public data: INode;
}
</script>

View File

@@ -1,6 +1,8 @@
<template>
<div class="checkbox-switch" >
<input type="checkbox" class="input-checkbox"
<div class="checkbox-switch">
<input
type="checkbox"
class="input-checkbox"
v-model="isReverted"
@change="onRevertToggled()"
v-on:click.stop>

View File

@@ -1,13 +1,14 @@
<template>
<span>
<span v-if="initialLiquorTreeNodes != null && initialLiquorTreeNodes.length > 0">
<tree :options="liquorTreeOptions"
<tree
:options="liquorTreeOptions"
:data="initialLiquorTreeNodes"
v-on:node:checked="nodeSelected($event)"
v-on:node:unchecked="nodeSelected($event)"
ref="treeElement"
>
<span class="tree-text" slot-scope="{ node }" >
<span class="tree-text" slot-scope="{ node }">
<Node :data="convertExistingToNode(node)" />
</span>
</tree>
@@ -86,7 +87,7 @@ export default class SelectableTree extends Vue { // Stateless to make it easier
}
@Watch('filterText', { immediate: true })
public async updateFilterText(filterText: |string) {
public async updateFilterText(filterText?: string) {
const api = await this.getLiquorTreeApi();
if (!filterText) {
api.clearFilter();
@@ -111,7 +112,7 @@ export default class SelectableTree extends Vue { // Stateless to make it easier
private async getLiquorTreeApi(): Promise<ILiquorTree> {
const accessor = (): ILiquorTree => {
const uiElement = this.$refs.treeElement;
type TreeElement = typeof uiElement & {tree: ILiquorTree};
type TreeElement = typeof uiElement & { tree: ILiquorTree };
return uiElement ? (uiElement as TreeElement).tree : undefined;
};
const treeElement = await tryUntilDefined(accessor, 5, 20); // Wait for it to render

View File

@@ -1,7 +1,7 @@
<template>
<div class="scripts">
<div v-if="!isSearching">
<CardList v-if="currentView === ViewType.Cards"/>
<CardList v-if="currentView === ViewType.Cards" />
<div class="tree" v-else-if="currentView === ViewType.Tree">
<ScriptsTree />
</div>
@@ -9,18 +9,18 @@
<div v-else> <!-- Searching -->
<div class="search">
<div class="search__query">
<div>Searching for "{{this.searchQuery | threeDotsTrim}}"</div>
<div>Searching for "{{this.searchQuery | threeDotsTrim }}"</div>
<div class="search__query__close-button">
<font-awesome-icon
:icon="['fas', 'times']"
v-on:click="clearSearchQuery()"/>
v-on:click="clearSearchQuery()" />
</div>
</div>
<div v-if="!searchHasMatches" class="search-no-matches">
<div>Sorry, no matches for "{{this.searchQuery | threeDotsTrim}}" 😞</div>
<div>Sorry, no matches for "{{this.searchQuery | threeDotsTrim }}" 😞</div>
<div>
Feel free to extend the scripts
<a :href="repositoryUrl" target="_blank" class="child github" >here</a>
<a :href="repositoryUrl" target="_blank" class="child github">here</a>
</div>
</div>
</div>

View File

@@ -5,10 +5,13 @@
height="auto">
<div class="dialog">
<div class="dialog__content">
<slot></slot>
<slot />
</div>
<div class="dialog__close-button">
<font-awesome-icon :icon="['fas', 'times']" @click="$modal.hide(name)"/>
<font-awesome-icon
:icon="['fas', 'times']"
@click="$modal.hide(name)"
/>
</div>
</div>
</modal>

View File

@@ -1,6 +1,6 @@
<template>
<div ref="containerElement" class="container">
<slot ref="containerElement"></slot>
<slot ref="containerElement" />
</div>
</template>

View File

@@ -3,10 +3,10 @@
class="container"
v-bind:class="{
'container-unsupported': !hasCurrentOsDesktopVersion,
'container-supported': hasCurrentOsDesktopVersion
'container-supported': hasCurrentOsDesktopVersion,
}">
<span class="description">
<font-awesome-icon class="description__icon" :icon="['fas', 'desktop']" />
<font-awesome-icon class="description__icon" :icon="['fas', 'desktop']" />
<span class="description__text">For desktop:</span>
</span>
<span class="urls">

View File

@@ -1,9 +1,10 @@
<template>
<span class="url">
<a :href="downloadUrl"
v-bind:class="{
'url__active': hasCurrentOsDesktopVersion && isCurrentOs,
'url__inactive': hasCurrentOsDesktopVersion && !isCurrentOs,
<a
:href="downloadUrl"
v-bind:class="{
url__active: hasCurrentOsDesktopVersion && isCurrentOs,
url__inactive: hasCurrentOsDesktopVersion && !isCurrentOs,
}">{{ operatingSystemName }}</a>
</span>
</template>

View File

@@ -17,14 +17,18 @@
</div>
<div class="line">
<div class="line__emoji">🤖</div>
<div>All transparent: Deployed automatically from the master branch
of the <a :href="repositoryUrl" target="_blank">source code</a> with no changes.</div>
<div>
All transparent: Deployed automatically from the master branch
of the <a :href="repositoryUrl" target="_blank">source code</a> with no changes.
</div>
</div>
<div v-if="!isDesktop" class="line">
<div class="line__emoji">📈</div>
<div>Basic <a href="https://aws.amazon.com/cloudfront/reporting/" target="_blank">CDN statistics</a>
are collected by AWS but they cannot be traced to you or your behavior.
You can download the offline version if you don't want any CDN data collection.</div>
<div>
Basic <a href="https://aws.amazon.com/cloudfront/reporting/" target="_blank">CDN statistics</a>
are collected by AWS but they cannot be traced to you or your behavior.
You can download the offline version if you don't want any CDN data collection.
</div>
</div>
<div class="line">
<div class="line__emoji">🎉</div>

View File

@@ -3,7 +3,7 @@
<div class="footer">
<div class="footer__section">
<span v-if="isDesktop" class="footer__section__item">
<font-awesome-icon class="icon" :icon="['fas', 'globe']" />
<font-awesome-icon class="icon" :icon="['fas', 'globe']" />
<span>
Online version at <a :href="homepageUrl" target="_blank">{{ homepageUrl }}</a>
</span>
@@ -15,30 +15,30 @@
<div class="footer__section">
<div class="footer__section__item">
<a :href="feedbackUrl" target="_blank">
<font-awesome-icon class="icon" :icon="['far', 'smile']" />
<font-awesome-icon class="icon" :icon="['far', 'smile']" />
<span>Feedback</span>
</a>
</div>
<div class="footer__section__item">
<a :href="repositoryUrl" target="_blank">
<font-awesome-icon class="icon" :icon="['fab', 'github']" />
<font-awesome-icon class="icon" :icon="['fab', 'github']" />
<span>Source Code</span>
</a>
</div>
<div class="footer__section__item">
<a :href="releaseUrl" target="_blank">
<font-awesome-icon class="icon" :icon="['fas', 'tag']" />
<font-awesome-icon class="icon" :icon="['fas', 'tag']" />
<span>v{{ version }}</span>
</a>
</div>
<div class="footer__section__item">
<font-awesome-icon class="icon" :icon="['fas', 'user-secret']" />
<font-awesome-icon class="icon" :icon="['fas', 'user-secret']" />
<a @click="$refs.privacyDialog.show()">Privacy</a>
</div>
</div>
</div>
<Dialog ref="privacyDialog">
<PrivacyPolicy />
<PrivacyPolicy />
</Dialog>
</div>
</template>

View File

@@ -1,6 +1,6 @@
<template>
<div id="container">
<h1 class="child title" >{{ title }}</h1>
<h1 class="child title">{{ title }}</h1>
<h2 class="child subtitle">Now you have the choice</h2>
</div>
</template>

View File

@@ -1,8 +1,11 @@
<template>
<div class="search" v-non-collapsing>
<input type="search" class="search-term"
<input
type="search"
class="search-term"
:placeholder="searchPlaceHolder"
v-model="searchQuery" >
v-model="searchQuery"
>
<div class="icon-wrapper">
<font-awesome-icon :icon="['fas', 'search']" />
</div>
@@ -26,7 +29,7 @@ export default class TheSearchBar extends StatefulVue {
public searchQuery = '';
@Watch('searchQuery')
public async updateFilter(newFilter: |string) {
public async updateFilter(newFilter?: string) {
const context = await this.getCurrentContext();
const { filter } = context.state;
if (!newFilter) {