Compare commits

..

32 Commits

Author SHA1 Message Date
undergroundwires
bd41af466f update screenshot 2021-03-02 16:28:07 +01:00
undergroundwires
970221b996 remove "preview" disclaimer from macOS 2021-03-01 17:01:34 +01:00
undergroundwires
15004ff1f1 remove windows scripts for removing non-bloating system apps #55 2021-02-28 13:43:42 +01:00
undergroundwires
65226f3984 add better error messages to setting vscode settings 2021-02-27 14:40:14 +01:00
undergroundwires
b0a7d0b53b add more macos scripts for privacy cleanup 2021-02-26 14:27:12 +01:00
undergroundwires
ee43fd92a0 more scripts to disable speech recognition and Cortana 2021-02-25 16:37:57 +01:00
undergroundwires
cf39e6d254 move code area to right on bigger screens 2021-02-22 16:46:06 +01:00
undergroundwires
1260eea690 escape printed characters to prevent command injection #45 2021-02-21 12:34:33 +01:00
undergroundwires
45a3669443 refactor disabling application experience and document better 2021-02-20 12:13:08 +01:00
undergroundwires
c9b91f6d8f add script to automatically kill devicecensus process 2021-02-19 11:12:28 +01:00
undergroundwires
9a6b903b92 add option to run script directly in desktop app 2021-02-18 09:39:24 +01:00
undergroundwires
7661575573 allow functions to call other functions #53 2021-02-14 11:18:31 +01:00
undergroundwires-bot
f1abd7682f ⬆️ bump everywhere to 0.9.2 2021-02-13 11:08:41 +00:00
zy26
575636e6b7 correct the typo in application.md (#60) 2021-02-13 11:57:57 +01:00
undergroundwires
daa997b21b add GitHub issue templates 2021-02-10 21:16:56 +01:00
undergroundwires
5934b17283 refactor and add tests for NonCollapsingDirective 2021-02-09 08:53:29 +01:00
undergroundwires
d7de420d5c add test to ensure correct shared functions are being parsed 2021-02-08 07:10:41 +01:00
undergroundwires
df273f7f63 refactor state handling to make application available independent of the state 2021-02-07 12:32:05 +01:00
undergroundwires
67b2d1c11c refactor vscode configuration scripts using functions #41 2021-02-06 11:37:45 +01:00
undergroundwires
15353d0e25 make compiler throw if a function call includes an unexpected parameter 2021-02-05 13:27:40 +01:00
undergroundwires
f1e21babbf refactor event handling to consume base class for lifecycling 2021-02-04 19:58:09 +01:00
undergroundwires
34b8822ac8 fix wrong path for NvTelemtry file in NVIDIA script 2021-01-27 08:07:33 +01:00
undergroundwires
73e0520de7 do not compile with unused locals vuejs/vetur#1063 2021-01-26 06:00:19 +01:00
undergroundwires-bot
fbc3b109b9 ⬆️ bump everywhere to 0.9.1 2021-01-24 06:22:52 +00:00
undergroundwires
229c13a195 improve explanation for selections 2021-01-23 06:06:25 +01:00
undergroundwires
d7f9ef1cbe fix node APIs no longer working on desktop nklayman/vue-cli-plugin-electron-builder#610, nklayman/vue-cli-plugin-electron-builder#742 2021-01-22 06:07:30 +01:00
undergroundwires
7930bef48c transpile using babel for legacy browser support 2021-01-21 04:38:02 +01:00
undergroundwires
8b0e47da38 fix selection state indicator on cards not showing up 2021-01-20 05:30:19 +01:00
undergroundwires
2316e3fb68 specify desktop publish targets as defaults (may) change 2021-01-19 06:51:09 +01:00
undergroundwires
4015e2ccd8 in CI/CD, publish packages for other OSes if single one fails 2021-01-17 07:14:23 +01:00
undergroundwires
cf907d029a in CI/CD, allow publishing to github if release is more than 2 hours old electron-userland/electron-builder#2074 2021-01-16 08:14:38 +01:00
undergroundwires-bot
79a6c8b2ef ⬆️ bump everywhere to 0.9.0 2021-01-15 03:26:07 +00:00
123 changed files with 5656 additions and 1607 deletions

View File

@@ -0,0 +1,36 @@
---
name: Bug report (script bug or unexpected script behavior)
about: Create a bug report for generated scripts to help privacy.sexy improve
labels: bug
title: '[BUG]: '
---
<!--
Thank you for reporting an issue with generated script(s).
Please fill in as much of the template below as you're able.
As a small open source project with small community, it can sometimes take a long time for issues to be addressed so please be patient.
-->
### Describe the bug
<!-- A clear and concise description of what the bug is. -->
### OS
<!--
Which OS are you using? What version of OS you were using?
On Windows you can find it using "Start button" > "Settings" > "System" > "About".
On macOS you can find it using "Apple menu (top left corner)" > "About This Mac".
-->
### Screenshots
<!-- If applicable, add screenshots to help explain your problem. -->
### Scripts
<!-- Which scripts did you execute? If applicable, please paste the executed scripts or attach the generated privacy.sexy file . -->
### Additional information
<!-- Add any other context about the problem here. -->

View File

@@ -0,0 +1,52 @@
---
name: Bug report (unrelated to generated scripts)
about: Create a bug report that's not related to generated scripts to help privacy.sexy improve
labels: bug
title: '[BUG]: '
---
<!--
Thank you for reporting an issue.
Please fill in as much of the template below as you're able.
As a small open source project with small community, it can sometimes take a long time for issues to be addressed so please be patient.
-->
### Describe the bug
<!-- A clear and concise description of what the bug is. -->
### To Reproduce
<!--
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
-->
### Expected behavior
<!--
A clear and concise description of what you expected to happen.
-->
### Screenshots
<!--
If applicable, add screenshots to help explain your problem.
-->
### Distribution
<!--
If applicable, mention how you were using privacy.sexy when the bug was encountered:
- Web (on Desktop or mobile?)
- Or desktop (Windows, macOS or Linux?)
-->
### Additional context
<!--
Add any other context about the problem here.
-->

View File

@@ -0,0 +1,27 @@
---
name: Feature request
about: Suggest an idea for privacy.sexy
labels: enhancement
---
<!--
Thank you for suggesting an idea to make privacy better. 🤗
Please fill in as much of the template below as you're able.
-->
### Problem Description
<!-- Please add a clear and concise description of the problem you are seeking to solve with this feature request. Ex. I'm always frustrated when [...] -->
### Proposed solution
<!-- Describe the solution you'd like in a clear and concise manner. -->
### Alternatives considered
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
### Additional information
<!-- Add any other context or screenshots about the feature request here. -->

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1 @@
blank_issues_enabled: true

View File

@@ -10,6 +10,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [macos, ubuntu, windows] os: [macos, ubuntu, windows]
fail-fast: false # So publish runs for other OSes if one fails
runs-on: ${{ matrix.os }}-latest runs-on: ${{ matrix.os }}-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@@ -30,3 +31,4 @@ jobs:
run: npm run electron:build -- -p always # https://nklayman.github.io/vue-cli-plugin-electron-builder/guide/recipes.html#upload-release-to-github run: npm run electron:build -- -p always # https://nklayman.github.io/vue-cli-plugin-electron-builder/guide/recipes.html#upload-release-to-github
env: env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
EP_GH_IGNORE_TIME: true # Otherwise publishing fails if GitHub release is more than 2 hours old https://github.com/electron-userland/electron-builder/issues/2074

View File

@@ -1,230 +1,279 @@
# Changelog # Changelog
## 0.9.2 (2021-02-13)
* do not compile with unused locals vuejs/vetur#1063 | [73e0520](https://github.com/undergroundwires/privacy.sexy/commit/73e0520de70cdbaf0ecdc6e9be5e85f003fcfb79)
* fix wrong path for NvTelemtry file in NVIDIA script | [34b8822](https://github.com/undergroundwires/privacy.sexy/commit/34b8822ac821acb47e483e21b57e380551bcf455)
* refactor event handling to consume base class for lifecycling | [f1e21ba](https://github.com/undergroundwires/privacy.sexy/commit/f1e21babbfaac21903594a37e30163bfe3338279)
* make compiler throw if a function call includes an unexpected parameter | [15353d0](https://github.com/undergroundwires/privacy.sexy/commit/15353d0e2513c89ee4ffd9d9c5e9e83ef69b96b6)
* refactor vscode configuration scripts using functions #41 | [67b2d1c](https://github.com/undergroundwires/privacy.sexy/commit/67b2d1c11cd5b131dff93a4437db79d96ed8b3dc)
* refactor state handling to make application available independent of the state | [df273f7](https://github.com/undergroundwires/privacy.sexy/commit/df273f7f635ab156ac51a8dfb3fec66c4979f1c4)
* add test to ensure correct shared functions are being parsed | [d7de420](https://github.com/undergroundwires/privacy.sexy/commit/d7de420d5c91bd9ce64880cd4a4391ad3a0a5401)
* refactor and add tests for NonCollapsingDirective | [5934b17](https://github.com/undergroundwires/privacy.sexy/commit/5934b1728328c3b2ece1597b74dd87477d162175)
* add GitHub issue templates | [daa997b](https://github.com/undergroundwires/privacy.sexy/commit/daa997b21b624d133c6f5e4cd6b70214588f9144)
* correct the typo in application.md (#60) | [575636e](https://github.com/undergroundwires/privacy.sexy/commit/575636e6b728a2bdd1a9bd72c57bbf2752f10887)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.9.1...0.9.2)
## 0.9.1 (2021-01-23)
* in CI/CD, allow publishing to github if release is more than 2 hours old electron-userland/electron-builder#2074 | [cf907d0](https://github.com/undergroundwires/privacy.sexy/commit/cf907d029a6d80682ba78ec887a9c4fab639db51)
* in CI/CD, publish packages for other OSes if single one fails | [4015e2c](https://github.com/undergroundwires/privacy.sexy/commit/4015e2ccd8492e0693365b70fbfe3bd0ac7a6ea2)
* specify desktop publish targets as defaults (may) change | [2316e3f](https://github.com/undergroundwires/privacy.sexy/commit/2316e3fb6867e5d765eafcf675b77f88bd2a0f52)
* fix selection state indicator on cards not showing up | [8b0e47d](https://github.com/undergroundwires/privacy.sexy/commit/8b0e47da38c49cfe2645d7d25970c448ecd200f8)
* transpile using babel for legacy browser support | [7930bef](https://github.com/undergroundwires/privacy.sexy/commit/7930bef48c4e9a4fe0823673958ed8377f5ee533)
* fix node APIs no longer working on desktop nklayman/vue-cli-plugin-electron-builder#610, nklayman/vue-cli-plugin-electron-builder#742 | [d7f9ef1](https://github.com/undergroundwires/privacy.sexy/commit/d7f9ef1cbebe911aa19f29be8c5fa9360550793e)
* improve explanation for selections | [229c13a](https://github.com/undergroundwires/privacy.sexy/commit/229c13a195dee92e4a31731b7b41c319273a16f1)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.9.0...0.9.1)
## 0.9.0 (2021-01-15)
* refactor application.yaml to become an os definition #40 | [f7557bc](https://github.com/undergroundwires/privacy.sexy/commit/f7557bcc0faf44e8395b68c7eb14c5f715f07b92)
* refactor folders to move "/state" (IApplicationState) inside "/context" (IApplicationContext) | [3467241](https://github.com/undergroundwires/privacy.sexy/commit/34672414c3e0757173036e351df0a73c1708ded5)
* add scripts to prevent family safety monitoring | [e14bf2b](https://github.com/undergroundwires/privacy.sexy/commit/e14bf2bfa03efe28ff39942c9891fca605f13eed)
* rework Cortana scripts to remove duplicates, better document and support Windows version 2004/2009 #43 | [7cc161c](https://github.com/undergroundwires/privacy.sexy/commit/7cc161c828a3fa49f6f254e31834a95a502b7aa2)
* rename Application to CategoryCollection #40 | [6fe858d](https://github.com/undergroundwires/privacy.sexy/commit/6fe858d86aeb0f8b6d5ae5c2a5e3c25ff32e5f6f)
* add script to clean previous windows installation #35 | [3455a2c](https://github.com/undergroundwires/privacy.sexy/commit/3455a2ca6ce13f9b0e866d88532a5c3d6de30d4d)
* refactor to allow switching ICategoryCollection context #40 | [2e40605](https://github.com/undergroundwires/privacy.sexy/commit/2e40605d59eb764768457c6af561487e7ff09777)
* fix typo causing uninstalling capabilities to fail #51 | [c299e95](https://github.com/undergroundwires/privacy.sexy/commit/c299e95bc6d588317b69a9efcf5752ff5c9c3926)
* improve uninstalling apps to show errors and exit if taking ownership fails #51 | [72e925f](https://github.com/undergroundwires/privacy.sexy/commit/72e925fb6f908cd58fb50618f29726b3fb54a7f1)
* move application.yaml to collections/windows.yaml #40 | [6b83dcb](https://github.com/undergroundwires/privacy.sexy/commit/6b83dcbf8fa08b4efe9974c7d7a667458f7c595c)
* recommend onedrive removal on strict mode | [663d63b](https://github.com/undergroundwires/privacy.sexy/commit/663d63bde08dd1b0d43ec144c758399cec90ec70)
* document app connector removal and recommend on strict mode | [9d009c4](https://github.com/undergroundwires/privacy.sexy/commit/9d009c40dd411c73c7ae032a78ec51490ecce024)
* recommend removing cortana taskbar icon on standard mode | [7ec889e](https://github.com/undergroundwires/privacy.sexy/commit/7ec889e759df04bba99d3b6c4d0597809bd94058)
* fix unintended null file creation #52 | [2428de2](https://github.com/undergroundwires/privacy.sexy/commit/2428de23ee02de987e7e6ec80ebd67be369d9048)
* add initial macOS support #40 | [8a8b731](https://github.com/undergroundwires/privacy.sexy/commit/8a8b7319d539b31c1d8ad9eaf541762d64f02493)
* add scripts to manage chromium based edge | [86a2b2f](https://github.com/undergroundwires/privacy.sexy/commit/86a2b2fda0b6a2565c550758c7c175fa795926b7)
* update screenshot | [c318bd3](https://github.com/undergroundwires/privacy.sexy/commit/c318bd301a2cbebbf5cdba06c0f18ac291aa4788)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.8.2...0.9.0)
## 0.8.2 (2020-12-26) ## 0.8.2 (2020-12-26)
* replace ampersand in "Movies & TV app" with "and" to prevent batch file from misinterpreting it (#45) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/52d4313156d2dcbc508b7271e7d9dfd45723d7bc) * replace ampersand in "Movies & TV app" with "and" to prevent batch file from misinterpreting it (#45) | [52d4313](https://github.com/undergroundwires/privacy.sexy/commit/52d4313156d2dcbc508b7271e7d9dfd45723d7bc)
* update dependencies to latest #46 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/d9e44e25744e5d0aa01b8fc0f0af74c48027aea3) * update dependencies to latest #46 | [d9e44e2](https://github.com/undergroundwires/privacy.sexy/commit/d9e44e25744e5d0aa01b8fc0f0af74c48027aea3)
* fix type assignment error after typescript upgrade | [commit](https://github.com/undergroundwires/privacy.sexy/commit/55f936fee9f86757f63fa8952d89711feb247e5b) * fix type assignment error after typescript upgrade | [55f936f](https://github.com/undergroundwires/privacy.sexy/commit/55f936fee9f86757f63fa8952d89711feb247e5b)
* correct typos (#48) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/a744415eb2ab65ee4f519f863fdd6a43953377bb) * correct typos (#48) | [a744415](https://github.com/undergroundwires/privacy.sexy/commit/a744415eb2ab65ee4f519f863fdd6a43953377bb)
* in ci/cd, do not run security checks if PRs do not change dependencies #48 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/54ba4dbb0bf8f08f9479f8facb2e12c786c1bc51) * in ci/cd, do not run security checks if PRs do not change dependencies #48 | [54ba4db](https://github.com/undergroundwires/privacy.sexy/commit/54ba4dbb0bf8f08f9479f8facb2e12c786c1bc51)
* rename app launch tracking tweak to make it more clear #44 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/b3117c27f283c2d5a25fd94021a9f628a272cda6) * rename app launch tracking tweak to make it more clear #44 | [b3117c2](https://github.com/undergroundwires/privacy.sexy/commit/b3117c27f283c2d5a25fd94021a9f628a272cda6)
* refactor capabilities to use a shared function #41 #47 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/c4ec6a1445d2fd5eb923c97b54aee01e272e13a8) * refactor capabilities to use a shared function #41 #47 | [c4ec6a1](https://github.com/undergroundwires/privacy.sexy/commit/c4ec6a1445d2fd5eb923c97b54aee01e272e13a8)
* rename "disable" to "uninstall" for removing capabilities #47 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/8cd3352017f9dc85f8efcd7b450d90f555d3e92e) * rename "disable" to "uninstall" for removing capabilities #47 | [8cd3352](https://github.com/undergroundwires/privacy.sexy/commit/8cd3352017f9dc85f8efcd7b450d90f555d3e92e)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.8.1...0.8.2) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.8.1...0.8.2)
## 0.8.1 (2020-11-16) ## 0.8.1 (2020-11-16)
* refactor removing bloatware to use functions #41 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/ffa279f3dfe51db564f0a3859543eb212170e173) * refactor removing bloatware to use functions #41 | [ffa279f](https://github.com/undergroundwires/privacy.sexy/commit/ffa279f3dfe51db564f0a3859543eb212170e173)
* fix reinstalling store apps by searching appx for all users | [commit](https://github.com/undergroundwires/privacy.sexy/commit/2c5ab3ea7da159cfb9fbfbbb7cdd28afbee965ea) * fix reinstalling store apps by searching appx for all users | [2c5ab3e](https://github.com/undergroundwires/privacy.sexy/commit/2c5ab3ea7da159cfb9fbfbbb7cdd28afbee965ea)
* fix clearing jump lists causing os to break and user pin removal #37 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/92c3dd923257ac940eab6cbab858698ed55a09b7) * fix clearing jump lists causing os to break and user pin removal #37 | [92c3dd9](https://github.com/undergroundwires/privacy.sexy/commit/92c3dd923257ac940eab6cbab858698ed55a09b7)
* fix reinstalling store apps by searching appx for all users | [commit](https://github.com/undergroundwires/privacy.sexy/commit/4e7267337301fe4a0480ba0603218fca25c2d096) * fix reinstalling store apps by searching appx for all users | [4e72673](https://github.com/undergroundwires/privacy.sexy/commit/4e7267337301fe4a0480ba0603218fca25c2d096)
* refactor unused imports | [commit](https://github.com/undergroundwires/privacy.sexy/commit/45b8dd972b1edf9e263858c23b27e7a1d2e07077) * refactor unused imports | [45b8dd9](https://github.com/undergroundwires/privacy.sexy/commit/45b8dd972b1edf9e263858c23b27e7a1d2e07077)
* fix not being able to uninstall system apps | [commit](https://github.com/undergroundwires/privacy.sexy/commit/31e08d231d52e2a691400468b7c599c142a29448) * fix not being able to uninstall system apps | [31e08d2](https://github.com/undergroundwires/privacy.sexy/commit/31e08d231d52e2a691400468b7c599c142a29448)
* fix wrong app names caused by wrong Microsoft docs | [commit](https://github.com/undergroundwires/privacy.sexy/commit/e41e40c5bf01e2971d3054fcd3a48f8465a96622) * fix wrong app names caused by wrong Microsoft docs | [e41e40c](https://github.com/undergroundwires/privacy.sexy/commit/e41e40c5bf01e2971d3054fcd3a48f8465a96622)
* unrecommend some system apps and document more | [commit](https://github.com/undergroundwires/privacy.sexy/commit/29c7704e0bd38f6e9923cde84accb569b02d2dd6) * unrecommend some system apps and document more | [29c7704](https://github.com/undergroundwires/privacy.sexy/commit/29c7704e0bd38f6e9923cde84accb569b02d2dd6)
* fix not being able to rename paths including brackets | [commit](https://github.com/undergroundwires/privacy.sexy/commit/ad1872e7cd4ad7ef9facf33fadfa8c6a55065dd3) * fix not being able to rename paths including brackets | [ad1872e](https://github.com/undergroundwires/privacy.sexy/commit/ad1872e7cd4ad7ef9facf33fadfa8c6a55065dd3)
* fix errors when file already exists | [commit](https://github.com/undergroundwires/privacy.sexy/commit/c26bc209eb167aa71cad10b7f3ea02d0dedd97b0) * fix errors when file already exists | [c26bc20](https://github.com/undergroundwires/privacy.sexy/commit/c26bc209eb167aa71cad10b7f3ea02d0dedd97b0)
* move Microsoft.Appconnector to right category | [commit](https://github.com/undergroundwires/privacy.sexy/commit/b247b12c3f009aab4350e33f4779fd193e570050) * move Microsoft.Appconnector to right category | [b247b12](https://github.com/undergroundwires/privacy.sexy/commit/b247b12c3f009aab4350e33f4779fd193e570050)
* replace deprecated github ::set-env command | [commit](https://github.com/undergroundwires/privacy.sexy/commit/ab7d617886a65fe4f3c2daa929168e5678ccae60) * replace deprecated github ::set-env command | [ab7d617](https://github.com/undergroundwires/privacy.sexy/commit/ab7d617886a65fe4f3c2daa929168e5678ccae60)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.8.0...0.8.1) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.8.0...0.8.1)
## 0.8.0 (2020-11-01) ## 0.8.0 (2020-11-01)
* add support for different recommendation levels: strict and standard | [commit](https://github.com/undergroundwires/privacy.sexy/commit/14be3017c55ed5e0d9bdecb63fcc4e1131e79ab0) * add support for different recommendation levels: strict and standard | [14be301](https://github.com/undergroundwires/privacy.sexy/commit/14be3017c55ed5e0d9bdecb63fcc4e1131e79ab0)
* Add GroupMe and Spotify removal option (#34) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/3785c623f837b182d82fa383dfe7709722a67248) * Add GroupMe and Spotify removal option (#34) | [3785c62](https://github.com/undergroundwires/privacy.sexy/commit/3785c623f837b182d82fa383dfe7709722a67248)
* switch places of download and copy buttons | [commit](https://github.com/undergroundwires/privacy.sexy/commit/50fb29038ae19b17ec006093db02cf1e568d53c3) * switch places of download and copy buttons | [50fb290](https://github.com/undergroundwires/privacy.sexy/commit/50fb29038ae19b17ec006093db02cf1e568d53c3)
* change "download" button to "save" on desktop | [commit](https://github.com/undergroundwires/privacy.sexy/commit/07fc555324d8bf4fa3594a9701daaa124a873153) * change "download" button to "save" on desktop | [07fc555](https://github.com/undergroundwires/privacy.sexy/commit/07fc555324d8bf4fa3594a9701daaa124a873153)
* show icons on cards during indeterminate and fully selected states | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1072505219edc47d82a91f148d1f310f32869fea) * show icons on cards during indeterminate and fully selected states | [1072505](https://github.com/undergroundwires/privacy.sexy/commit/1072505219edc47d82a91f148d1f310f32869fea)
* add scripts to increase cryptography, enable camera notifications and remove todo app (#36) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/4c68408f1ec339dc8d39c7ab044f825a7f7185cb) * add scripts to increase cryptography, enable camera notifications and remove todo app (#36) | [4c68408](https://github.com/undergroundwires/privacy.sexy/commit/4c68408f1ec339dc8d39c7ab044f825a7f7185cb)
* update recommendations to be safer and consistent | [commit](https://github.com/undergroundwires/privacy.sexy/commit/d0019c2c9b1eea620e2e8e02b586903ce62b80e3) * update recommendations to be safer and consistent | [d0019c2](https://github.com/undergroundwires/privacy.sexy/commit/d0019c2c9b1eea620e2e8e02b586903ce62b80e3)
* rework disabling metadata retrieval | [commit](https://github.com/undergroundwires/privacy.sexy/commit/ac70b063b8a15bc528256185792939685be6b36f) * rework disabling metadata retrieval | [ac70b06](https://github.com/undergroundwires/privacy.sexy/commit/ac70b063b8a15bc528256185792939685be6b36f)
* add all dist folders in gitignore because of files auto-generated by vscode | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1a9db31c7778c3269a71c0bd9665827efda70a02) * add all dist folders in gitignore because of files auto-generated by vscode | [1a9db31](https://github.com/undergroundwires/privacy.sexy/commit/1a9db31c7778c3269a71c0bd9665827efda70a02)
* add support for shared functions #41 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/8ce06facbd54184402a4b1af3c7303e64db85b8a) * add support for shared functions #41 | [8ce06fa](https://github.com/undergroundwires/privacy.sexy/commit/8ce06facbd54184402a4b1af3c7303e64db85b8a)
* hide scrollbars on code area when not overflowing | [commit](https://github.com/undergroundwires/privacy.sexy/commit/fd28eaad061c75ea1aa7e0f0d60ea37a7e52f8c4) * hide scrollbars on code area when not overflowing | [fd28eaa](https://github.com/undergroundwires/privacy.sexy/commit/fd28eaad061c75ea1aa7e0f0d60ea37a7e52f8c4)
* update screenshot | [commit](https://github.com/undergroundwires/privacy.sexy/commit/cfedcd724cad7708b30c7390a7bca3b6313b6726) * update screenshot | [cfedcd7](https://github.com/undergroundwires/privacy.sexy/commit/cfedcd724cad7708b30c7390a7bca3b6313b6726)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.6...0.8.0) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.6...0.8.0)
## 0.7.6 (2020-10-18) ## 0.7.6 (2020-10-18)
* add docs for default0 pointing to github discussion (#30) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/a3fc3782efd346b4c99d2a0b40df2eb0229f5b36) * add docs for default0 pointing to github discussion (#30) | [a3fc378](https://github.com/undergroundwires/privacy.sexy/commit/a3fc3782efd346b4c99d2a0b40df2eb0229f5b36)
* add robots.txt to explicitly allow indexing | [commit](https://github.com/undergroundwires/privacy.sexy/commit/4c2f74949b0758d33049bdfa4f0124a28958f8ea) * add robots.txt to explicitly allow indexing | [4c2f749](https://github.com/undergroundwires/privacy.sexy/commit/4c2f74949b0758d33049bdfa4f0124a28958f8ea)
* add more reversibility | [commit](https://github.com/undergroundwires/privacy.sexy/commit/19a092dd31fb3588277f1ab3120b409d98506752) * add more reversibility | [19a092d](https://github.com/undergroundwires/privacy.sexy/commit/19a092dd31fb3588277f1ab3120b409d98506752)
* refactor to read more from package.json | [commit](https://github.com/undergroundwires/privacy.sexy/commit/784a67afff681bc19147d03c947de0e165d97e87) * refactor to read more from package.json | [784a67a](https://github.com/undergroundwires/privacy.sexy/commit/784a67afff681bc19147d03c947de0e165d97e87)
* simplify "why" section | [commit](https://github.com/undergroundwires/privacy.sexy/commit/77c3d2bbb8d13db86bb82ed0b5cbeaacfdea3db9) * simplify "why" section | [77c3d2b](https://github.com/undergroundwires/privacy.sexy/commit/77c3d2bbb8d13db86bb82ed0b5cbeaacfdea3db9)
* update dependencies to latest | [commit](https://github.com/undergroundwires/privacy.sexy/commit/11e06131655398db08faeeacff62062e46e0dddd) * update dependencies to latest | [11e0613](https://github.com/undergroundwires/privacy.sexy/commit/11e06131655398db08faeeacff62062e46e0dddd)
* run tests on all operating systems: macos, ubuntu, windows | [commit](https://github.com/undergroundwires/privacy.sexy/commit/d9d7f62d81d4d8f95104d33211e82641884d711f) * run tests on all operating systems: macos, ubuntu, windows | [d9d7f62](https://github.com/undergroundwires/privacy.sexy/commit/d9d7f62d81d4d8f95104d33211e82641884d711f)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.5...0.7.6) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.5...0.7.6)
## 0.7.5 (2020-09-14) ## 0.7.5 (2020-09-14)
* fix reverting (reinstalling) capabilities not working | [commit](https://github.com/undergroundwires/privacy.sexy/commit/939d838e3535bb1c9b00c8ea9dacb735ae41d700) * fix reverting (reinstalling) capabilities not working | [939d838](https://github.com/undergroundwires/privacy.sexy/commit/939d838e3535bb1c9b00c8ea9dacb735ae41d700)
* fix tests and checks are not running on PRs | [commit](https://github.com/undergroundwires/privacy.sexy/commit/82d509129b4e4a5df4b84786a0d6842a7d26e888) * fix tests and checks are not running on PRs | [82d5091](https://github.com/undergroundwires/privacy.sexy/commit/82d509129b4e4a5df4b84786a0d6842a7d26e888)
* fix the recycling bin option (#32) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/15db3118012a172a2191a2afad57084a65b34642) * fix the recycling bin option (#32) | [15db311](https://github.com/undergroundwires/privacy.sexy/commit/15db3118012a172a2191a2afad57084a65b34642)
* fix rendering issue in older edge/IE | [commit](https://github.com/undergroundwires/privacy.sexy/commit/6efed72bf25c2ddf0901caab7f22966ca13cd47a) * fix rendering issue in older edge/IE | [6efed72](https://github.com/undergroundwires/privacy.sexy/commit/6efed72bf25c2ddf0901caab7f22966ca13cd47a)
* fix pasting in search bar after page load showing no results | [commit](https://github.com/undergroundwires/privacy.sexy/commit/d1694341578288eeaf8b80caf9296a38d76789f0) * fix pasting in search bar after page load showing no results | [d169434](https://github.com/undergroundwires/privacy.sexy/commit/d1694341578288eeaf8b80caf9296a38d76789f0)
* fix typo | [commit](https://github.com/undergroundwires/privacy.sexy/commit/7dd15ed06433e0e6583ab0fa46a683ce6554bbea) * fix typo | [7dd15ed](https://github.com/undergroundwires/privacy.sexy/commit/7dd15ed06433e0e6583ab0fa46a683ce6554bbea)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.4...0.7.5) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.4...0.7.5)
## 0.7.4 (2020-09-12) ## 0.7.4 (2020-09-12)
* fix checked checkbox has blue border | [commit](https://github.com/undergroundwires/privacy.sexy/commit/4ae385b7fcea9014a68442714b7d99e2ee7df7d0) * fix checked checkbox has blue border | [4ae385b](https://github.com/undergroundwires/privacy.sexy/commit/4ae385b7fcea9014a68442714b7d99e2ee7df7d0)
* fix spectre protection getting single lined #31 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/22b23a9ece446c7f9abd4ede293051eb616ad50a) * fix spectre protection getting single lined #31 | [22b23a9](https://github.com/undergroundwires/privacy.sexy/commit/22b23a9ece446c7f9abd4ede293051eb616ad50a)
* fix missing reg value in denying app access to account | [commit](https://github.com/undergroundwires/privacy.sexy/commit/3c13a9e837e06e097450b31d7eb0c0e6bf20cefb) * fix missing reg value in denying app access to account | [3c13a9e](https://github.com/undergroundwires/privacy.sexy/commit/3c13a9e837e06e097450b31d7eb0c0e6bf20cefb)
* fix wrong path in clear all firefox user profile settings | [commit](https://github.com/undergroundwires/privacy.sexy/commit/ee66196d9a60f27d17ae7f62d02b4f119a47e6e0) * fix wrong path in clear all firefox user profile settings | [ee66196](https://github.com/undergroundwires/privacy.sexy/commit/ee66196d9a60f27d17ae7f62d02b4f119a47e6e0)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.3...0.7.4) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.3...0.7.4)
## 0.7.3 (2020-09-12) ## 0.7.3 (2020-09-12)
* fix vscode settings file override and add more configs | [commit](https://github.com/undergroundwires/privacy.sexy/commit/a0d61728ead04b4455437f85820121a848db9e00) * fix vscode settings file override and add more configs | [a0d6172](https://github.com/undergroundwires/privacy.sexy/commit/a0d61728ead04b4455437f85820121a848db9e00)
* fix nvidia tweak error message, categorize and add reversibility | [commit](https://github.com/undergroundwires/privacy.sexy/commit/99a2035fdb0766a4dfc2753133eab0d7666516cd) * fix nvidia tweak error message, categorize and add reversibility | [99a2035](https://github.com/undergroundwires/privacy.sexy/commit/99a2035fdb0766a4dfc2753133eab0d7666516cd)
* improve CPU specific tweaks by conditional platform checks and reversibility | [commit](https://github.com/undergroundwires/privacy.sexy/commit/8df5faf4ef05a49da63973bd0fbb5c5d07d5bd93) * improve CPU specific tweaks by conditional platform checks and reversibility | [8df5faf](https://github.com/undergroundwires/privacy.sexy/commit/8df5faf4ef05a49da63973bd0fbb5c5d07d5bd93)
* fix wrong path to the main telemetry file | [commit](https://github.com/undergroundwires/privacy.sexy/commit/de4ac978bdda79573b36d355697b8a028d2c0beb) * fix wrong path to the main telemetry file | [de4ac97](https://github.com/undergroundwires/privacy.sexy/commit/de4ac978bdda79573b36d355697b8a028d2c0beb)
* fix naming of firefox cleanup to mention profiles | [commit](https://github.com/undergroundwires/privacy.sexy/commit/3ab48b1cf5f7f934f07e468ef2318ccee07f530c) * fix naming of firefox cleanup to mention profiles | [3ab48b1](https://github.com/undergroundwires/privacy.sexy/commit/3ab48b1cf5f7f934f07e468ef2318ccee07f530c)
* add reversibility and more scripts to denying app access with better structure | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1d465ee3189d0e5a827453b3f0eb4361efe23770) * add reversibility and more scripts to denying app access with better structure | [1d465ee](https://github.com/undergroundwires/privacy.sexy/commit/1d465ee3189d0e5a827453b3f0eb4361efe23770)
* fix comment lines are being detected as duplicate in validation | [commit](https://github.com/undergroundwires/privacy.sexy/commit/b6ccb5927a20412976a54fd2215eb645092f98a8) * fix comment lines are being detected as duplicate in validation | [b6ccb59](https://github.com/undergroundwires/privacy.sexy/commit/b6ccb5927a20412976a54fd2215eb645092f98a8)
* add more detailed error message | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1f11c39773c12eccfb3efb898b58c2f6f37ab9ca) * add more detailed error message | [1f11c39](https://github.com/undergroundwires/privacy.sexy/commit/1f11c39773c12eccfb3efb898b58c2f6f37ab9ca)
* fix typo in a test | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1f19b2528a69383e63e579d2885f01cd804abf6c) * fix typo in a test | [1f19b25](https://github.com/undergroundwires/privacy.sexy/commit/1f19b2528a69383e63e579d2885f01cd804abf6c)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.2...0.7.3) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.2...0.7.3)
## 0.7.2 (2020-09-06) ## 0.7.2 (2020-09-06)
* update onesync documentation and do not recommend it as it breaks other apps | [commit](https://github.com/undergroundwires/privacy.sexy/commit/f36d8bfc7848bb65ac0c641e318a689bf3816ccf) * update onesync documentation and do not recommend it as it breaks other apps | [f36d8bf](https://github.com/undergroundwires/privacy.sexy/commit/f36d8bfc7848bb65ac0c641e318a689bf3816ccf)
* add reversibility for biometric disabling and do not recommend it | [commit](https://github.com/undergroundwires/privacy.sexy/commit/db74531cd4139615c6d595959217d3651f099019) * add reversibility for biometric disabling and do not recommend it | [db74531](https://github.com/undergroundwires/privacy.sexy/commit/db74531cd4139615c6d595959217d3651f099019)
* fix bad highlighting of selected nodes when using keyboard navigation | [commit](https://github.com/undergroundwires/privacy.sexy/commit/255133af4dfae40171406648a3e2920f16d71cb3) * fix bad highlighting of selected nodes when using keyboard navigation | [255133a](https://github.com/undergroundwires/privacy.sexy/commit/255133af4dfae40171406648a3e2920f16d71cb3)
* add reversibility to removing bloatware | [commit](https://github.com/undergroundwires/privacy.sexy/commit/c7b2a703128470a05f12c9c6e8002444def37ef8) * add reversibility to removing bloatware | [c7b2a70](https://github.com/undergroundwires/privacy.sexy/commit/c7b2a703128470a05f12c9c6e8002444def37ef8)
* fix indeterminate state being lost | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1f266c33535f72b69c65985bf2eff27cd2c5a104) * fix indeterminate state being lost | [1f266c3](https://github.com/undergroundwires/privacy.sexy/commit/1f266c33535f72b69c65985bf2eff27cd2c5a104)
* fix wording in default text in text area | [commit](https://github.com/undergroundwires/privacy.sexy/commit/ca63a0979ef55d07d09d9443e5cea9aa888870a5) * fix wording in default text in text area | [ca63a09](https://github.com/undergroundwires/privacy.sexy/commit/ca63a0979ef55d07d09d9443e5cea9aa888870a5)
* add best practice suggestion to come back | [commit](https://github.com/undergroundwires/privacy.sexy/commit/f4885b6f1c82752f2143934e336d6d1b1af03015) * add best practice suggestion to come back | [f4885b6](https://github.com/undergroundwires/privacy.sexy/commit/f4885b6f1c82752f2143934e336d6d1b1af03015)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.1...0.7.2) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.1...0.7.2)
## 0.7.1 (2020-09-04) ## 0.7.1 (2020-09-04)
* fix some browsers (including firefox) downloading the script as a text file | [commit](https://github.com/undergroundwires/privacy.sexy/commit/8c17929151f9c4fa5f48564492bbf400ced95eea) * fix some browsers (including firefox) downloading the script as a text file | [8c17929](https://github.com/undergroundwires/privacy.sexy/commit/8c17929151f9c4fa5f48564492bbf400ced95eea)
* rename screenshot image file | [commit](https://github.com/undergroundwires/privacy.sexy/commit/b8682a852a14ed6cf49986695d9510b840ac9d3d) * rename screenshot image file | [b8682a8](https://github.com/undergroundwires/privacy.sexy/commit/b8682a852a14ed6cf49986695d9510b840ac9d3d)
* fix new/changed script higlighting not working on production builds | [commit](https://github.com/undergroundwires/privacy.sexy/commit/8c38dd73d8c7b77d8d341c0389f4d7229f9b97fd) * fix new/changed script higlighting not working on production builds | [8c38dd7](https://github.com/undergroundwires/privacy.sexy/commit/8c38dd73d8c7b77d8d341c0389f4d7229f9b97fd)
* refactor unused imports | [commit](https://github.com/undergroundwires/privacy.sexy/commit/6badfef9daace0c5de3fd33652a82bfe22261b11) * refactor unused imports | [6badfef](https://github.com/undergroundwires/privacy.sexy/commit/6badfef9daace0c5de3fd33652a82bfe22261b11)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.0...0.7.1) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.0...0.7.1)
## 0.7.0 (2020-09-02) ## 0.7.0 (2020-09-02)
* [search] better (multilined) message when there are no results | [commit](https://github.com/undergroundwires/privacy.sexy/commit/ec15af01dd020b364c2174fe562fd66227c2320c) * [search] better (multilined) message when there are no results | [ec15af0](https://github.com/undergroundwires/privacy.sexy/commit/ec15af01dd020b364c2174fe562fd66227c2320c)
* [search] added clear/close button | [commit](https://github.com/undergroundwires/privacy.sexy/commit/d6fa9a2a03c0ebe68b94f0b80cc52b4e200c9213) * [search] added clear/close button | [d6fa9a2](https://github.com/undergroundwires/privacy.sexy/commit/d6fa9a2a03c0ebe68b94f0b80cc52b4e200c9213)
* move script generation to /generation | [commit](https://github.com/undergroundwires/privacy.sexy/commit/5df458739d076719e350ba194c4f3f772884fcdb) * move script generation to /generation | [5df4587](https://github.com/undergroundwires/privacy.sexy/commit/5df458739d076719e350ba194c4f3f772884fcdb)
* add auto-highlighting of selected/updated code | [commit](https://github.com/undergroundwires/privacy.sexy/commit/b789250cb89e2130b08e1a927df8181cf945dfeb) * add auto-highlighting of selected/updated code | [b789250](https://github.com/undergroundwires/privacy.sexy/commit/b789250cb89e2130b08e1a927df8181cf945dfeb)
* prompt admin priviliges automatically | [commit](https://github.com/undergroundwires/privacy.sexy/commit/f8ba5c46e4923d9c35f200f8a08aa6437f7c0ecc) * prompt admin priviliges automatically | [f8ba5c4](https://github.com/undergroundwires/privacy.sexy/commit/f8ba5c46e4923d9c35f200f8a08aa6437f7c0ecc)
* add removal of ghost (default0) telemetry user | [commit](https://github.com/undergroundwires/privacy.sexy/commit/c262681011f39b4412669b6cf233476f676ca550) * add removal of ghost (default0) telemetry user | [c262681](https://github.com/undergroundwires/privacy.sexy/commit/c262681011f39b4412669b6cf233476f676ca550)
* add more windows defender tweaks, categorization and reversibility | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1a34c7374ba56bafa0209bbb55c81b233bb419ed) * add more windows defender tweaks, categorization and reversibility | [1a34c73](https://github.com/undergroundwires/privacy.sexy/commit/1a34c7374ba56bafa0209bbb55c81b233bb419ed)
* fix NTP script documentation is on wrong place | [commit](https://github.com/undergroundwires/privacy.sexy/commit/3060ebf79cf242370433495cc3e1878b7581b202) * fix NTP script documentation is on wrong place | [3060ebf](https://github.com/undergroundwires/privacy.sexy/commit/3060ebf79cf242370433495cc3e1878b7581b202)
* updated dependencies to latest and audit fixes (#25) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/c628aa9aef8ab7c815661d3c1711e7fbc65c69a2) * updated dependencies to latest and audit fixes (#25) | [c628aa9](https://github.com/undergroundwires/privacy.sexy/commit/c628aa9aef8ab7c815661d3c1711e7fbc65c69a2)
* categorize, fix and extend windows log files cleanup | [commit](https://github.com/undergroundwires/privacy.sexy/commit/594a14d6ca76cbd27a21877b8c373c1930589ca6) * categorize, fix and extend windows log files cleanup | [594a14d](https://github.com/undergroundwires/privacy.sexy/commit/594a14d6ca76cbd27a21877b8c373c1930589ca6)
* add more OneDrive cleanup scripts and categorize them | [commit](https://github.com/undergroundwires/privacy.sexy/commit/978d7d08638dd161082f239ed088b12302f29458) * add more OneDrive cleanup scripts and categorize them | [978d7d0](https://github.com/undergroundwires/privacy.sexy/commit/978d7d08638dd161082f239ed088b12302f29458)
* add disabling firefox telemetry | [commit](https://github.com/undergroundwires/privacy.sexy/commit/f8b8b4c97ab734d5ba7370894b694993924388da) * add disabling firefox telemetry | [f8b8b4c](https://github.com/undergroundwires/privacy.sexy/commit/f8b8b4c97ab734d5ba7370894b694993924388da)
* add disabling ccleaner telemetry | [commit](https://github.com/undergroundwires/privacy.sexy/commit/018b7e270f207aac926cb12f8069ebfcdce193ce) * add disabling ccleaner telemetry | [018b7e2](https://github.com/undergroundwires/privacy.sexy/commit/018b7e270f207aac926cb12f8069ebfcdce193ce)
* Add disabling of PowerShell 7+ telemetry (#29) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/456e40bedf9afcc846f9b13f1ea144cef6115cf6) * Add disabling of PowerShell 7+ telemetry (#29) | [456e40b](https://github.com/undergroundwires/privacy.sexy/commit/456e40bedf9afcc846f9b13f1ea144cef6115cf6)
* categorize, fix, make scripts reversible in "UI for privacy", "security improvements" and "configure browsers" | [commit](https://github.com/undergroundwires/privacy.sexy/commit/532915b95da9fecd6b981d91bf489359e4e53caa) * categorize, fix, make scripts reversible in "UI for privacy", "security improvements" and "configure browsers" | [532915b](https://github.com/undergroundwires/privacy.sexy/commit/532915b95da9fecd6b981d91bf489359e4e53caa)
* fix "Configure Defender" being in wrong category #28 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/f709d6a566ed7846b677b383863deda9680a2a9c) * fix "Configure Defender" being in wrong category #28 | [f709d6a](https://github.com/undergroundwires/privacy.sexy/commit/f709d6a566ed7846b677b383863deda9680a2a9c)
* do not hardcode capability versions and make them reversible | [commit](https://github.com/undergroundwires/privacy.sexy/commit/2afef4ea3d0d3d09aa1fa1eedba8493680bd8f10) * do not hardcode capability versions and make them reversible | [2afef4e](https://github.com/undergroundwires/privacy.sexy/commit/2afef4ea3d0d3d09aa1fa1eedba8493680bd8f10)
* exclude paint, wordpad and notepad from bloatware removal | [commit](https://github.com/undergroundwires/privacy.sexy/commit/d235dee95514a01745aef9479d07f88ffb4b40b8) * exclude paint, wordpad and notepad from bloatware removal | [d235dee](https://github.com/undergroundwires/privacy.sexy/commit/d235dee95514a01745aef9479d07f88ffb4b40b8)
* add reversibility on category level | [commit](https://github.com/undergroundwires/privacy.sexy/commit/f51e8859eeb32c944126d692cfe03a0320c8b568) * add reversibility on category level | [f51e885](https://github.com/undergroundwires/privacy.sexy/commit/f51e8859eeb32c944126d692cfe03a0320c8b568)
* refactor unused imports & variables | [commit](https://github.com/undergroundwires/privacy.sexy/commit/a23d28f2cfa2d64d45460697cf5ee9d6b5920752) * refactor unused imports & variables | [a23d28f](https://github.com/undergroundwires/privacy.sexy/commit/a23d28f2cfa2d64d45460697cf5ee9d6b5920752)
* fix search (got broken in b789250) with tests and refactorings | [commit](https://github.com/undergroundwires/privacy.sexy/commit/8bbe6ebf750f1a1cbab493fb99b5ea91f4e21609) * fix search (got broken in b789250) with tests and refactorings | [8bbe6eb](https://github.com/undergroundwires/privacy.sexy/commit/8bbe6ebf750f1a1cbab493fb99b5ea91f4e21609)
* update the screenshot to show off highlighting | [commit](https://github.com/undergroundwires/privacy.sexy/commit/b4aacea2a3e0bbcf2d8a79ff67f51c0f19e888a6) * update the screenshot to show off highlighting | [b4aacea](https://github.com/undergroundwires/privacy.sexy/commit/b4aacea2a3e0bbcf2d8a79ff67f51c0f19e888a6)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.6.2...0.7.0) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.6.2...0.7.0)
## 0.6.2 (2020-08-16) ## 0.6.2 (2020-08-16)
* 🐛 fixed disabling error reporting for november 2019 update | [commit](https://github.com/undergroundwires/privacy.sexy/commit/5967347b80976a519f6f4eb1972a62f3e600df2b) * 🐛 fixed disabling error reporting for november 2019 update | [5967347](https://github.com/undergroundwires/privacy.sexy/commit/5967347b80976a519f6f4eb1972a62f3e600df2b)
* 🐛 fixed blank screen and icons on mac | [commit](https://github.com/undergroundwires/privacy.sexy/commit/7fac0fe79f252e8f9dda4f6f83cd6fa4ba2b539f) * 🐛 fixed blank screen and icons on mac | [7fac0fe](https://github.com/undergroundwires/privacy.sexy/commit/7fac0fe79f252e8f9dda4f6f83cd6fa4ba2b539f)
* 🐛 fixed removing onedrive does not delete scheduled tasks | [commit](https://github.com/undergroundwires/privacy.sexy/commit/b6bfc2572740c0cd46d3bc0058fa767dd5fa862e) * 🐛 fixed removing onedrive does not delete scheduled tasks | [b6bfc25](https://github.com/undergroundwires/privacy.sexy/commit/b6bfc2572740c0cd46d3bc0058fa767dd5fa862e)
* ⚙️ enhanced tweak to disable for office telemetry | [commit](https://github.com/undergroundwires/privacy.sexy/commit/afc3bfb3b8896f332c9a196973ded3dce8fd21e4) * ⚙️ enhanced tweak to disable for office telemetry | [afc3bfb](https://github.com/undergroundwires/privacy.sexy/commit/afc3bfb3b8896f332c9a196973ded3dce8fd21e4)
* ✨ added script to clear dotnet telemery | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1663bfeac7b6580b1335ca5fcf3587b69c080c72) * ✨ added script to clear dotnet telemery | [1663bfe](https://github.com/undergroundwires/privacy.sexy/commit/1663bfeac7b6580b1335ca5fcf3587b69c080c72)
* 🐛 fixed changing time server not working | [commit](https://github.com/undergroundwires/privacy.sexy/commit/c69998c7cb29ffcf40f0af03b73150736581da69) * 🐛 fixed changing time server not working | [c69998c](https://github.com/undergroundwires/privacy.sexy/commit/c69998c7cb29ffcf40f0af03b73150736581da69)
* 🔥 removed disabling ClickToRun as it breaks office | [commit](https://github.com/undergroundwires/privacy.sexy/commit/3d3380f27ebeea53f17f49974aaa89300ffaf2dd) * 🔥 removed disabling ClickToRun as it breaks office | [3d3380f](https://github.com/undergroundwires/privacy.sexy/commit/3d3380f27ebeea53f17f49974aaa89300ffaf2dd)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.6.1...0.6.2) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.6.1...0.6.2)
## 0.6.1 (2020-08-09) ## 0.6.1 (2020-08-09)
* updated documentation | [commit](https://github.com/undergroundwires/privacy.sexy/commit/5963d2bac551083f9d16cce6b851abf0e8b88ce7) * updated documentation | [5963d2b](https://github.com/undergroundwires/privacy.sexy/commit/5963d2bac551083f9d16cce6b851abf0e8b88ce7)
* fixed typo in footer | [commit](https://github.com/undergroundwires/privacy.sexy/commit/5c15a7a64aaf24578a32713dec491bf494216303) * fixed typo in footer | [5c15a7a](https://github.com/undergroundwires/privacy.sexy/commit/5c15a7a64aaf24578a32713dec491bf494216303)
* more scripts can be reverted | [commit](https://github.com/undergroundwires/privacy.sexy/commit/831c014f977515454ee6dc664d77a8c434495501) * more scripts can be reverted | [831c014](https://github.com/undergroundwires/privacy.sexy/commit/831c014f977515454ee6dc664d77a8c434495501)
* moved windows connect now to security & recommended | [commit](https://github.com/undergroundwires/privacy.sexy/commit/6049a2b834d8d17af741f8d8f8b07cd15153b001) * moved windows connect now to security & recommended | [6049a2b](https://github.com/undergroundwires/privacy.sexy/commit/6049a2b834d8d17af741f8d8f8b07cd15153b001)
* fixed mac / linux download links | [commit](https://github.com/undergroundwires/privacy.sexy/commit/4c8be45e287b5ea009d6f828f7f327f37850569e) * fixed mac / linux download links | [4c8be45](https://github.com/undergroundwires/privacy.sexy/commit/4c8be45e287b5ea009d6f828f7f327f37850569e)
* tweaks to disable webcam, speech and compatibility telemetry | [commit](https://github.com/undergroundwires/privacy.sexy/commit/a5dbe66fc175e39397f296ab2ff703e9b0ab4d7c) * tweaks to disable webcam, speech and compatibility telemetry | [a5dbe66](https://github.com/undergroundwires/privacy.sexy/commit/a5dbe66fc175e39397f296ab2ff703e9b0ab4d7c)
* refactorings | [commit](https://github.com/undergroundwires/privacy.sexy/commit/66d4d39d5bf3db305450514c6b6224654dafbfb2) * refactorings | [66d4d39](https://github.com/undergroundwires/privacy.sexy/commit/66d4d39d5bf3db305450514c6b6224654dafbfb2)
* fixed removing onedrive does not clean start menu / quick access | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1cc12195a3e9a11c590d3ed64d80299b50f74838) * fixed removing onedrive does not clean start menu / quick access | [1cc1219](https://github.com/undergroundwires/privacy.sexy/commit/1cc12195a3e9a11c590d3ed64d80299b50f74838)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.6.0...0.6.1) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.6.0...0.6.1)
## 0.6.0 (2020-07-26) ## 0.6.0 (2020-07-26)
* fixed dead links in documentation | [commit](https://github.com/undergroundwires/privacy.sexy/commit/25ce236a7737decaf2eb9b8c29a4c4f34d43f770) * fixed dead links in documentation | [25ce236](https://github.com/undergroundwires/privacy.sexy/commit/25ce236a7737decaf2eb9b8c29a4c4f34d43f770)
* runs tests on each push on the repository | [commit](https://github.com/undergroundwires/privacy.sexy/commit/73c426844a0330718a9ab7de12b61ca05e853323) * runs tests on each push on the repository | [73c4268](https://github.com/undergroundwires/privacy.sexy/commit/73c426844a0330718a9ab7de12b61ca05e853323)
* code area now shows "how" before "why" | [commit](https://github.com/undergroundwires/privacy.sexy/commit/4ff4b52202b1c5dbfe2b80580bbe7d93132ab05c) * code area now shows "how" before "why" | [4ff4b52](https://github.com/undergroundwires/privacy.sexy/commit/4ff4b52202b1c5dbfe2b80580bbe7d93132ab05c)
* support for desktop versions #20 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/04b9b59e14766ccd251474ad3710baf1f682fd49) * support for desktop versions #20 | [04b9b59](https://github.com/undergroundwires/privacy.sexy/commit/04b9b59e14766ccd251474ad3710baf1f682fd49)
* reworked on footer & removed github icon | [commit](https://github.com/undergroundwires/privacy.sexy/commit/60a5a2aa4026d384bef9e6a203f1b7514a269c33) * reworked on footer & removed github icon | [60a5a2a](https://github.com/undergroundwires/privacy.sexy/commit/60a5a2aa4026d384bef9e6a203f1b7514a269c33)
* updated dependencies to latest | [commit](https://github.com/undergroundwires/privacy.sexy/commit/45816a2bccb3d11a50e3f2bc19c0a6cc2587deaa) * updated dependencies to latest | [45816a2](https://github.com/undergroundwires/privacy.sexy/commit/45816a2bccb3d11a50e3f2bc19c0a6cc2587deaa)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.5.0...0.6.0) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.5.0...0.6.0)
## 0.5.0 (2020-07-19) ## 0.5.0 (2020-07-19)
* added ability to revert (#21) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/9c063d59defa6297c64f50b49403e8bd10620de9) * added ability to revert (#21) | [9c063d5](https://github.com/undergroundwires/privacy.sexy/commit/9c063d59defa6297c64f50b49403e8bd10620de9)
* search placeholder shows total scripts | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1d5225de07186f853f4cf7aedd4998f5d00c107a) * search placeholder shows total scripts | [1d5225d](https://github.com/undergroundwires/privacy.sexy/commit/1d5225de07186f853f4cf7aedd4998f5d00c107a)
* do not collapse card when on "Search" and "Select" | [commit](https://github.com/undergroundwires/privacy.sexy/commit/dd7e1416b4df54bf71b719d4654db88769dc0994) * do not collapse card when on "Search" and "Select" | [dd7e141](https://github.com/undergroundwires/privacy.sexy/commit/dd7e1416b4df54bf71b719d4654db88769dc0994)
* opening a card scrolls to its content div | [commit](https://github.com/undergroundwires/privacy.sexy/commit/31d2067f076c3159483baec49975617dddbd158d) * opening a card scrolls to its content div | [31d2067](https://github.com/undergroundwires/privacy.sexy/commit/31d2067f076c3159483baec49975617dddbd158d)
* all cards in same line now have same height | [commit](https://github.com/undergroundwires/privacy.sexy/commit/a9f9e9044385d9aed3b5551fc6c6823e813fd1e5) * all cards in same line now have same height | [a9f9e90](https://github.com/undergroundwires/privacy.sexy/commit/a9f9e9044385d9aed3b5551fc6c6823e813fd1e5)
* patched loadash vulnerability (#18) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/92a7118d1c5013312772e075b9ee5a79c93710b8) * patched loadash vulnerability (#18) | [92a7118](https://github.com/undergroundwires/privacy.sexy/commit/92a7118d1c5013312772e075b9ee5a79c93710b8)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.10...0.5.0) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.10...0.5.0)
## 0.4.10 (2020-07-15) ## 0.4.10 (2020-07-15)
* fixed script errors & added tests | [commit](https://github.com/undergroundwires/privacy.sexy/commit/9e722ddfb3825fb29d6298025baaaa033120d017) * fixed script errors & added tests | [9e722dd](https://github.com/undergroundwires/privacy.sexy/commit/9e722ddfb3825fb29d6298025baaaa033120d017)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.9...0.4.10) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.9...0.4.10)
## 0.4.9 (2020-07-14) ## 0.4.9 (2020-07-14)
* disable office telemetry Disassembler0/Win10-Initial-Setup-Script#288 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/53cf595e1726ee3de79137fd566978fd512d218f) * disable office telemetry Disassembler0/Win10-Initial-Setup-Script#288 | [53cf595](https://github.com/undergroundwires/privacy.sexy/commit/53cf595e1726ee3de79137fd566978fd512d218f)
* updated to may 2020 update | [commit](https://github.com/undergroundwires/privacy.sexy/commit/909c44d72a4a602ee8f27d06b6ec706c1e432ce1) * updated to may 2020 update | [909c44d](https://github.com/undergroundwires/privacy.sexy/commit/909c44d72a4a602ee8f27d06b6ec706c1e432ce1)
* simplified docker builds | [commit](https://github.com/undergroundwires/privacy.sexy/commit/f27a2871d74e5117fc029be82caef12246e10879) * simplified docker builds | [f27a287](https://github.com/undergroundwires/privacy.sexy/commit/f27a2871d74e5117fc029be82caef12246e10879)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.8...0.4.9) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.8...0.4.9)
## 0.4.8 (2020-07-11) ## 0.4.8 (2020-07-11)
* added more scripts #16 (#17) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/d8552c62ffea13ce62abce836c7dd4980eef6bb9) * added more scripts #16 (#17) | [d8552c6](https://github.com/undergroundwires/privacy.sexy/commit/d8552c62ffea13ce62abce836c7dd4980eef6bb9)
* stopping services before disabling #16 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/628c16eb952495f5b3f6d794161b355f4b08b819) * stopping services before disabling #16 | [628c16e](https://github.com/undergroundwires/privacy.sexy/commit/628c16eb952495f5b3f6d794161b355f4b08b819)
* can disable features, capabilities & remove onedrive #16 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/30efbcc621eb83dd5a9c1e66b8f1f5350eb95006) * can disable features, capabilities & remove onedrive #16 | [30efbcc](https://github.com/undergroundwires/privacy.sexy/commit/30efbcc621eb83dd5a9c1e66b8f1f5350eb95006)
* updated one more typo (#19) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/d7a1325c0b7665ce712dc411965d00fc1d6fa384) * updated one more typo (#19) | [d7a1325](https://github.com/undergroundwires/privacy.sexy/commit/d7a1325c0b7665ce712dc411965d00fc1d6fa384)
* more tweaks #16 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/2c4eb78c3f156cb0d033977cffbe7464697680f5) * more tweaks #16 | [2c4eb78](https://github.com/undergroundwires/privacy.sexy/commit/2c4eb78c3f156cb0d033977cffbe7464697680f5)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.7...0.4.8) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.7...0.4.8)
## 0.4.7 (2020-06-30) ## 0.4.7 (2020-06-30)
* removed HKU tweak as all HKU's are changed #10 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/c937af8ee7da9aa95131e56abf7bf24800390fe6) * removed HKU tweak as all HKU's are changed #10 | [c937af8](https://github.com/undergroundwires/privacy.sexy/commit/c937af8ee7da9aa95131e56abf7bf24800390fe6)
* Fixed types + script in "Clear Windows log files" (#15) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/461a4f122b342369db5cc08c5e30961c64e68cdd) * Fixed types + script in "Clear Windows log files" (#15) | [461a4f1](https://github.com/undergroundwires/privacy.sexy/commit/461a4f122b342369db5cc08c5e30961c64e68cdd)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.6...0.4.7) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.6...0.4.7)
## 0.4.6 (2020-06-16) ## 0.4.6 (2020-06-16)
* Fixed Some More Issues (#12) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/52d5713a99422cdf900aba819e49e998abac33cc) * Fixed Some More Issues (#12) | [52d5713](https://github.com/undergroundwires/privacy.sexy/commit/52d5713a99422cdf900aba819e49e998abac33cc)
* removed failing continuous deployment #14 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/583c5660d6ac934b845a044e013357aa91f61c15) * removed failing continuous deployment #14 | [583c566](https://github.com/undergroundwires/privacy.sexy/commit/583c5660d6ac934b845a044e013357aa91f61c15)
* Updated Some Tweaks (#11) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/0fc18459cde57684f00764815062f838f932aed5) * Updated Some Tweaks (#11) | [0fc1845](https://github.com/undergroundwires/privacy.sexy/commit/0fc18459cde57684f00764815062f838f932aed5)
* Updated Some More Tweaks (#13) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/019b838925e963b7ec052ac76c6faf5650b9eb67) * Updated Some More Tweaks (#13) | [019b838](https://github.com/undergroundwires/privacy.sexy/commit/019b838925e963b7ec052ac76c6faf5650b9eb67)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.5...0.4.6) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.5...0.4.6)
@@ -234,91 +283,91 @@
## 0.4.4 (2020-05-24) ## 0.4.4 (2020-05-24)
* fixed close card button not being visible & cleanup | [commit](https://github.com/undergroundwires/privacy.sexy/commit/0d2efe5b05aa965458b78b8fa43754ce2f4fe11b) * fixed close card button not being visible & cleanup | [0d2efe5](https://github.com/undergroundwires/privacy.sexy/commit/0d2efe5b05aa965458b78b8fa43754ce2f4fe11b)
* new footer with privacy policy | [commit](https://github.com/undergroundwires/privacy.sexy/commit/e2ab124fb799f56ada3570fdc911361cb803e889) * new footer with privacy policy | [e2ab124](https://github.com/undergroundwires/privacy.sexy/commit/e2ab124fb799f56ada3570fdc911361cb803e889)
* one command to lint everything "npm run lint" | [commit](https://github.com/undergroundwires/privacy.sexy/commit/bb98d20637cbf1d524ebb2973e308773006e3153) * one command to lint everything "npm run lint" | [bb98d20](https://github.com/undergroundwires/privacy.sexy/commit/bb98d20637cbf1d524ebb2973e308773006e3153)
* fix "group by" overflows on smaller screens | [commit](https://github.com/undergroundwires/privacy.sexy/commit/c668a97950a1cb7c8bf2a7fd8a72d1101e65e8ce) * fix "group by" overflows on smaller screens | [c668a97](https://github.com/undergroundwires/privacy.sexy/commit/c668a97950a1cb7c8bf2a7fd8a72d1101e65e8ce)
* clicking outside of a card closes it | [commit](https://github.com/undergroundwires/privacy.sexy/commit/aab8f21a8d8dbed54798af581e6e1ad9e86a4be1) * clicking outside of a card closes it | [aab8f21](https://github.com/undergroundwires/privacy.sexy/commit/aab8f21a8d8dbed54798af581e6e1ad9e86a4be1)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.3...0.4.4) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.3...0.4.4)
## 0.4.3 (2020-05-23) ## 0.4.3 (2020-05-23)
* removed redundant documentation | [commit](https://github.com/undergroundwires/privacy.sexy/commit/749a140eb8dba09cb67fec2f8dec937e66e3cff5) * removed redundant documentation | [749a140](https://github.com/undergroundwires/privacy.sexy/commit/749a140eb8dba09cb67fec2f8dec937e66e3cff5)
* fixed broke link | [commit](https://github.com/undergroundwires/privacy.sexy/commit/97b7e03233d9718a8df30cb01ce06ca9489a0295) * fixed broke link | [97b7e03](https://github.com/undergroundwires/privacy.sexy/commit/97b7e03233d9718a8df30cb01ce06ca9489a0295)
* simplified heading | [commit](https://github.com/undergroundwires/privacy.sexy/commit/226074c5342f7463c06fcff1457d352ca30295a3) * simplified heading | [226074c](https://github.com/undergroundwires/privacy.sexy/commit/226074c5342f7463c06fcff1457d352ca30295a3)
* reading version from package.json instead of version file #5 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/691f989682179016ddcbf55a05cded29155288c9) * reading version from package.json instead of version file #5 | [691f989](https://github.com/undergroundwires/privacy.sexy/commit/691f989682179016ddcbf55a05cded29155288c9)
* automatically increases patch number #5 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/3e3bc07576f7c7e74e3e11fc7d197cbb9a9fb8c0) * automatically increases patch number #5 | [3e3bc07](https://github.com/undergroundwires/privacy.sexy/commit/3e3bc07576f7c7e74e3e11fc7d197cbb9a9fb8c0)
* using deployment operations from aws-static-site-with-cd | [commit](https://github.com/undergroundwires/privacy.sexy/commit/997be7113f676888892ffa35566d9ebb58a3e9ea) * using deployment operations from aws-static-site-with-cd | [997be71](https://github.com/undergroundwires/privacy.sexy/commit/997be7113f676888892ffa35566d9ebb58a3e9ea)
* automated using bump-everywhere + more quality checks (#8) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/4a91e8ccd8a707bc6bea34ee28cff7fa4f66ee2f) * automated using bump-everywhere + more quality checks (#8) | [4a91e8c](https://github.com/undergroundwires/privacy.sexy/commit/4a91e8ccd8a707bc6bea34ee28cff7fa4f66ee2f)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.2...0.4.3) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.2...0.4.3)
## 0.4.2 (2020-02-29) ## 0.4.2 (2020-02-29)
* added missing semicolon for masking | [commit](https://github.com/undergroundwires/privacy.sexy/commit/e63ac4ae67da68243a525af149ff30e5d485b641) * added missing semicolon for masking | [e63ac4a](https://github.com/undergroundwires/privacy.sexy/commit/e63ac4ae67da68243a525af149ff30e5d485b641)
* set font on input | [commit](https://github.com/undergroundwires/privacy.sexy/commit/0c39a06be5e4b0a2031ad5e9f5220dd669afee53) * set font on input | [0c39a06](https://github.com/undergroundwires/privacy.sexy/commit/0c39a06be5e4b0a2031ad5e9f5220dd669afee53)
* shortened all HKEY paths | [commit](https://github.com/undergroundwires/privacy.sexy/commit/802b36bdd8dcc1f0a2853fe7da2ea2fccd69a88c) * shortened all HKEY paths | [802b36b](https://github.com/undergroundwires/privacy.sexy/commit/802b36bdd8dcc1f0a2853fe7da2ea2fccd69a88c)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.1...0.4.2) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.1...0.4.2)
## 0.4.1 (2020-01-11) ## 0.4.1 (2020-01-11)
* fixed search bug | [commit](https://github.com/undergroundwires/privacy.sexy/commit/31364bdfec503af09ffbb58044a17dfb833fc8d9) * fixed search bug | [31364bd](https://github.com/undergroundwires/privacy.sexy/commit/31364bdfec503af09ffbb58044a17dfb833fc8d9)
* hide grouping while searching | [commit](https://github.com/undergroundwires/privacy.sexy/commit/92f1a36bcb1e1fe7c90efe8ccd3ede55991e9d9c) * hide grouping while searching | [92f1a36](https://github.com/undergroundwires/privacy.sexy/commit/92f1a36bcb1e1fe7c90efe8ccd3ede55991e9d9c)
* 👀🔍 showing search queries | [commit](https://github.com/undergroundwires/privacy.sexy/commit/97a7747933d2b515cc03ab8243e6a8ae702ef16a) * 👀🔍 showing search queries | [97a7747](https://github.com/undergroundwires/privacy.sexy/commit/97a7747933d2b515cc03ab8243e6a8ae702ef16a)
* more efficient queries with single lowercase | [commit](https://github.com/undergroundwires/privacy.sexy/commit/19813b691746d98670823025c460480400e34b6e) * more efficient queries with single lowercase | [19813b6](https://github.com/undergroundwires/privacy.sexy/commit/19813b691746d98670823025c460480400e34b6e)
* using right 🔍 input type | [commit](https://github.com/undergroundwires/privacy.sexy/commit/0ce354ea0956391ad3f37b252daac1127bfc601a) * using right 🔍 input type | [0ce354e](https://github.com/undergroundwires/privacy.sexy/commit/0ce354ea0956391ad3f37b252daac1127bfc601a)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.0...0.4.1) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.0...0.4.1)
## 0.4.0 (2020-01-11) ## 0.4.0 (2020-01-11)
* 🔍 support for search | [commit](https://github.com/undergroundwires/privacy.sexy/commit/89862b2775703257b9dc2e19fbebde2c0d0fbda0) * 🔍 support for search | [89862b2](https://github.com/undergroundwires/privacy.sexy/commit/89862b2775703257b9dc2e19fbebde2c0d0fbda0)
* more scripts & better organized | [commit](https://github.com/undergroundwires/privacy.sexy/commit/95baf3175b0d2c7df516f7893a96346b94ac8eca) * more scripts & better organized | [95baf31](https://github.com/undergroundwires/privacy.sexy/commit/95baf3175b0d2c7df516f7893a96346b94ac8eca)
* refactorings | [commit](https://github.com/undergroundwires/privacy.sexy/commit/e3f82e069e305f6d94eab335470c8e7b44295dd6) * refactorings | [e3f82e0](https://github.com/undergroundwires/privacy.sexy/commit/e3f82e069e305f6d94eab335470c8e7b44295dd6)
* more margin for the scripts | [commit](https://github.com/undergroundwires/privacy.sexy/commit/5ea46ecbf52236953d19f09a8eade08b83e6cd34) * more margin for the scripts | [5ea46ec](https://github.com/undergroundwires/privacy.sexy/commit/5ea46ecbf52236953d19f09a8eade08b83e6cd34)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.3.0...0.4.0) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.3.0...0.4.0)
## 0.3.0 (2020-01-09) ## 0.3.0 (2020-01-09)
* added description & more descriptive title | [commit](https://github.com/undergroundwires/privacy.sexy/commit/99576340b648550149871e2c0fe0b0d8c2dd0d7c) * added description & more descriptive title | [9957634](https://github.com/undergroundwires/privacy.sexy/commit/99576340b648550149871e2c0fe0b0d8c2dd0d7c)
* allow robots | [commit](https://github.com/undergroundwires/privacy.sexy/commit/eee0e785ec2c5e6bed53d21b4126a57773e35dba) * allow robots | [eee0e78](https://github.com/undergroundwires/privacy.sexy/commit/eee0e785ec2c5e6bed53d21b4126a57773e35dba)
* removed unused references | [commit](https://github.com/undergroundwires/privacy.sexy/commit/cfd888f3afc5c260a0a4a73f2843b86b9f1df2cd) * removed unused references | [cfd888f](https://github.com/undergroundwires/privacy.sexy/commit/cfd888f3afc5c260a0a4a73f2843b86b9f1df2cd)
* 🚫 disable NVIDIA telemetry | [commit](https://github.com/undergroundwires/privacy.sexy/commit/ab28f4ed8538d51e1777c86302a63a0cd9c3cb2a) * 🚫 disable NVIDIA telemetry | [ab28f4e](https://github.com/undergroundwires/privacy.sexy/commit/ab28f4ed8538d51e1777c86302a63a0cd9c3cb2a)
* backwards compatibility for fonts | [commit](https://github.com/undergroundwires/privacy.sexy/commit/4bc13e11926a6df77079646499e799742153b4ab) * backwards compatibility for fonts | [4bc13e1](https://github.com/undergroundwires/privacy.sexy/commit/4bc13e11926a6df77079646499e799742153b4ab)
* added back meta needed for responsiveness | [commit](https://github.com/undergroundwires/privacy.sexy/commit/ed872ef3d9f6c92afc0ce0d06998c60463a8b4e8) * added back meta needed for responsiveness | [ed872ef](https://github.com/undergroundwires/privacy.sexy/commit/ed872ef3d9f6c92afc0ce0d06998c60463a8b4e8)
* fancy-font is renamed to main and now used | [commit](https://github.com/undergroundwires/privacy.sexy/commit/6825001c61426194dc363b96b57a321241f3ba57) * fancy-font is renamed to main and now used | [6825001](https://github.com/undergroundwires/privacy.sexy/commit/6825001c61426194dc363b96b57a321241f3ba57)
* added support for grouping | [commit](https://github.com/undergroundwires/privacy.sexy/commit/ec6b3c54072a77bb4305da1c234db6c649218b88) * added support for grouping | [ec6b3c5](https://github.com/undergroundwires/privacy.sexy/commit/ec6b3c54072a77bb4305da1c234db6c649218b88)
* less hyphens as it looks better on mobile | [commit](https://github.com/undergroundwires/privacy.sexy/commit/e0b080af69157f46ba12e2c25e794f5384671b51) * less hyphens as it looks better on mobile | [e0b080a](https://github.com/undergroundwires/privacy.sexy/commit/e0b080af69157f46ba12e2c25e794f5384671b51)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.2.0...0.3.0) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.2.0...0.3.0)
## 0.2.0 (2020-01-06) ## 0.2.0 (2020-01-06)
* added GitHub Actions badge for build & deploy | [commit](https://github.com/undergroundwires/privacy.sexy/commit/a229aca68a92bbcd8e8176ac1dd25ce03509e074) * added GitHub Actions badge for build & deploy | [a229aca](https://github.com/undergroundwires/privacy.sexy/commit/a229aca68a92bbcd8e8176ac1dd25ce03509e074)
* more badges 📛🏆📜 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/090e8319091044e53484ba8338510f6fb7c3cb80) * more badges 📛🏆📜 | [090e831](https://github.com/undergroundwires/privacy.sexy/commit/090e8319091044e53484ba8338510f6fb7c3cb80)
* typo fixes + whitespace refactorings | [commit](https://github.com/undergroundwires/privacy.sexy/commit/e99f210c9dcf61a21e445e2a331384b6066f2c98) * typo fixes + whitespace refactorings | [e99f210](https://github.com/undergroundwires/privacy.sexy/commit/e99f210c9dcf61a21e445e2a331384b6066f2c98)
* switched content information to "why" section | [commit](https://github.com/undergroundwires/privacy.sexy/commit/beb3c8339f83a224ca66ad8a911a9265ffe7c9c0) * switched content information to "why" section | [beb3c83](https://github.com/undergroundwires/privacy.sexy/commit/beb3c8339f83a224ca66ad8a911a9265ffe7c9c0)
* fixed contribution URL | [commit](https://github.com/undergroundwires/privacy.sexy/commit/7b4277d7706ccf6ba7e4b7b01aa46f8e3852cfc6) * fixed contribution URL | [7b4277d](https://github.com/undergroundwires/privacy.sexy/commit/7b4277d7706ccf6ba7e4b7b01aa46f8e3852cfc6)
* fixed wrong relation + lighter style | [commit](https://github.com/undergroundwires/privacy.sexy/commit/8d05b03c9f3c9fc015be6615da8c283809712065) * fixed wrong relation + lighter style | [8d05b03](https://github.com/undergroundwires/privacy.sexy/commit/8d05b03c9f3c9fc015be6615da8c283809712065)
* better URL validation | [commit](https://github.com/undergroundwires/privacy.sexy/commit/aff463dd64fecff92a786fcba88621dff6b1cf73) * better URL validation | [aff463d](https://github.com/undergroundwires/privacy.sexy/commit/aff463dd64fecff92a786fcba88621dff6b1cf73)
* refactoring to new function | [commit](https://github.com/undergroundwires/privacy.sexy/commit/c646c102730481c3f4648eb714dc0a84ce35b13c) * refactoring to new function | [c646c10](https://github.com/undergroundwires/privacy.sexy/commit/c646c102730481c3f4648eb714dc0a84ce35b13c)
* optimized find queries & refactorings | [commit](https://github.com/undergroundwires/privacy.sexy/commit/d38f6cd6a8b33e11df854c7abea05974dc04d4ce) * optimized find queries & refactorings | [d38f6cd](https://github.com/undergroundwires/privacy.sexy/commit/d38f6cd6a8b33e11df854c7abea05974dc04d4ce)
* 🎨 styled no JS error | [commit](https://github.com/undergroundwires/privacy.sexy/commit/c359f1d89c6874b3cc94154b993e33f58bd32268) * 🎨 styled no JS error | [c359f1d](https://github.com/undergroundwires/privacy.sexy/commit/c359f1d89c6874b3cc94154b993e33f58bd32268)
* simplified finding duplicates | [commit](https://github.com/undergroundwires/privacy.sexy/commit/57037aaefcc0e80f0f4719cea89568490a731028) * simplified finding duplicates | [57037aa](https://github.com/undergroundwires/privacy.sexy/commit/57037aaefcc0e80f0f4719cea89568490a731028)
* fixed maintainability badge URL | [commit](https://github.com/undergroundwires/privacy.sexy/commit/aaea47e7d15fe41dea26968db0107a0c53d108f3) * fixed maintainability badge URL | [aaea47e](https://github.com/undergroundwires/privacy.sexy/commit/aaea47e7d15fe41dea26968db0107a0c53d108f3)
* fixed wrong line dumps | [commit](https://github.com/undergroundwires/privacy.sexy/commit/5ccc7c59528885ae7729197df3dfa00f924a2b3f) * fixed wrong line dumps | [5ccc7c5](https://github.com/undergroundwires/privacy.sexy/commit/5ccc7c59528885ae7729197df3dfa00f924a2b3f)
* refactorings in parsing | [commit](https://github.com/undergroundwires/privacy.sexy/commit/2aa3742e30646bf1d1f3779419d161c3fb6c4808) * refactorings in parsing | [2aa3742](https://github.com/undergroundwires/privacy.sexy/commit/2aa3742e30646bf1d1f3779419d161c3fb6c4808)
* using free function | [commit](https://github.com/undergroundwires/privacy.sexy/commit/20020af7c1d8de13948d8761fd4e7f0affb2badb) * using free function | [20020af](https://github.com/undergroundwires/privacy.sexy/commit/20020af7c1d8de13948d8761fd4e7f0affb2badb)
* default selection is now none | [commit](https://github.com/undergroundwires/privacy.sexy/commit/3140cc663b86394d543de90228aa53e6a304d8d9) * default selection is now none | [3140cc6](https://github.com/undergroundwires/privacy.sexy/commit/3140cc663b86394d543de90228aa53e6a304d8d9)
* added hyphen lines for longer names | [commit](https://github.com/undergroundwires/privacy.sexy/commit/cced601d686d550f4225018e5311b7433efbb5ae) * added hyphen lines for longer names | [cced601](https://github.com/undergroundwires/privacy.sexy/commit/cced601d686d550f4225018e5311b7433efbb5ae)
* more descriptive subtitle | [commit](https://github.com/undergroundwires/privacy.sexy/commit/2cf9214b14d9720f747a71b3864ba7a28acf0ff4) * more descriptive subtitle | [2cf9214](https://github.com/undergroundwires/privacy.sexy/commit/2cf9214b14d9720f747a71b3864ba7a28acf0ff4)
* added footer with version | [commit](https://github.com/undergroundwires/privacy.sexy/commit/10a34fae2f1a219ec52db0c74edb39b46ebd8abc) * added footer with version | [10a34fa](https://github.com/undergroundwires/privacy.sexy/commit/10a34fae2f1a219ec52db0c74edb39b46ebd8abc)
* using font variables | [commit](https://github.com/undergroundwires/privacy.sexy/commit/60e6348dc8d53f1e81ebdb2ec0e1962aac1e9842) * using font variables | [60e6348](https://github.com/undergroundwires/privacy.sexy/commit/60e6348dc8d53f1e81ebdb2ec0e1962aac1e9842)
* code-gen refactorings | [commit](https://github.com/undergroundwires/privacy.sexy/commit/246e753ddc9dc8bf630e538663584bf3423cc749) * code-gen refactorings | [246e753](https://github.com/undergroundwires/privacy.sexy/commit/246e753ddc9dc8bf630e538663584bf3423cc749)
* added text when nothing is chosen | [commit](https://github.com/undergroundwires/privacy.sexy/commit/a7da75d4428090423b692ce45423f5bd300d8442) * added text when nothing is chosen | [a7da75d](https://github.com/undergroundwires/privacy.sexy/commit/a7da75d4428090423b692ce45423f5bd300d8442)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.1.0...0.2.0) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.1.0...0.2.0)

View File

@@ -23,17 +23,6 @@
- ❗ DON'T - ❗ DON'T
- Do not update the versions, current version is only [set by the maintainer](./img/architecture/gitops.png) and updated automatically by [bump-everywhere](https://github.com/undergroundwires/bump-everywhere) - Do not update the versions, current version is only [set by the maintainer](./img/architecture/gitops.png) and updated automatically by [bump-everywhere](https://github.com/undergroundwires/bump-everywhere)
## Guidelines
### Handle the state in presentation layer
- There are two types of components:
- **Stateless**, extends `Vue`
- **Stateful**, extends [`StatefulVue`](./src/presentation/StatefulVue.ts)
- The source of truth for the state lies in application layer ([`./src/application/`](src/application/)) and must be updated from the views if they're mutating the state
- They mutate or/and react to state changes in [ApplicationContext](src/application/Context/ApplicationContext.ts).
- You can react by getting the state and listening to it and update the view accordingly in [`mounted()`](https://vuejs.org/v2/api/#mounted) method.
## License ## License
By contributing, you agree that your contributions will be licensed under its GNU General Public License v3.0. By contributing, you agree that your contributions will be licensed under its GNU General Public License v3.0.

View File

@@ -14,11 +14,13 @@
## Get started ## Get started
- Online version: [https://privacy.sexy](https://privacy.sexy) - Online version at [https://privacy.sexy](https://privacy.sexy)
- or download latest desktop version for [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.8.2/privacy.sexy-Setup-0.8.2.exe), [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.8.2/privacy.sexy-0.8.2.AppImage), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.8.2/privacy.sexy-0.8.2.dmg) - 💡 No need to run any compiled software on your computer.
- 💡 Come back regularly to apply latest version for stronger privacy and security. - Alternatively download offline version for [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.9.2/privacy.sexy-Setup-0.9.2.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.9.2/privacy.sexy-0.9.2.dmg) or [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.9.2/privacy.sexy-0.9.2.AppImage).
- 💡 Single click to execute your script.
- ❗ Come back regularly to apply latest version for stronger privacy and security.
[![privacy.sexy application](img/screenshot.png)](https://privacy.sexy) [![privacy.sexy application](img/screenshot.png?raw=true)](https://privacy.sexy)
## Why ## Why
@@ -51,29 +53,17 @@
- Development: `npm run serve` to compile & hot-reload for development. - Development: `npm run serve` to compile & hot-reload for development.
- Production: `npm run build` to prepare files for distribution. - Production: `npm run build` to prepare files for distribution.
- Or run using Docker: - Or run using Docker:
1. Build: `docker build -t undergroundwires/privacy.sexy:0.8.2 .` 1. Build: `docker build -t undergroundwires/privacy.sexy:0.9.2 .`
2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy-0.8.2 undergroundwires/privacy.sexy:0.8.2` 2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy-0.9.2 undergroundwires/privacy.sexy:0.9.2`
## Architecture ## Architecture overview
### Application ### Application
- Powered by **TypeScript**, **Vue.js** and **Electron** 💪 - Powered by **TypeScript**, **Vue.js** and **Electron** 💪
- and driven by **Domain-driven design**, **Event-driven architecture**, **Data-driven programming** concepts. - and driven by **Domain-driven design**, **Event-driven architecture**, **Data-driven programming** concepts.
- Application uses highly decoupled models & services in different DDD layers. - Application uses highly decoupled models & services in different DDD layers.
- **Domain layer** is where the application is modelled with validation logic. - 📖 Read more on • [Presentation](./docs/presentation.md) • [Application](./docs/application.md)
- **Presentation Layer**
- Consists of Vue.js components and other UI-related code.
- Desktop application is created using [Electron](https://www.electronjs.org/).
- Event driven as in components simply listens to events from the state and act accordingly.
- **Application Layer**
- Keeps the application state using [state pattern](https://en.wikipedia.org/wiki/State_pattern)
- [ApplicationContext](src/application/Context/ApplicationContext.ts)
- Holds the [CategoryCollectionState](src/application/Context/State/CategoryCollectionState.ts)] for each OS
- Same instance is shared throughout the application
- The scripts are defined and controlled in [yaml files](src/application/collections/) per OS
- Uses [data-driven programming](https://en.wikipedia.org/wiki/Data-driven_programming)
- 📖 See [extend scripts](#extend-scripts) to read about how to extend them.
![DDD + vue.js](img/architecture/app-ddd.png) ![DDD + vue.js](img/architecture/app-ddd.png)

5
babel.config.js Normal file
View File

@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

22
docs/application.md Normal file
View File

@@ -0,0 +1,22 @@
# Application
- It's mainly responsible for
- creating and event based [application state](#application-state)
- parsing and compiling [application data](#application-data)
## Application state
- [ApplicationContext.ts](./../src/application/Context/ApplicationContext.ts) holds the [CategoryCollectionState](./../src/application/Context/State/CategoryCollectionState.ts) for each OS
- Uses [state pattern](https://en.wikipedia.org/wiki/State_pattern)
- Same instance is shared throughout the application to ensure consistent state
- 📖 See [Application State | Presentation layer](./presentation.md#application-state) to read more about how the state should be managed by the presentation layer.
- 📖 See [ApplicationContext.ts](./../src/application/Context/ApplicationContext.ts) to start diving into the state code.
## Application data
- Compiled to `Application` domain object.
- The scripts are defined and controlled in different data files per OS
- Enables [data-driven programming](https://en.wikipedia.org/wiki/Data-driven_programming) and easier contributions
- Application data is defined in collection files and
- 📖 See [Application data | Presentation layer](./presentation.md#application-data) to read how the application data is read by the presentation layer.
- 📖 See [collection files documentation](./collection-files.md) to read more about how the data files are structured/defined and see [collection yaml files](./../src/application/collections/) to directly check the code.

View File

@@ -101,11 +101,15 @@
### `Function` ### `Function`
- Functions allow re-usable code throughout the defined scripts. - Functions allow re-usable code throughout the defined scripts.
- Functions are templates compiled by privacy.sexy and uses special expressions. - Functions are templates compiled by privacy.sexy and uses special [expressions](#expressions).
- Expressions are defined inside mustaches (double brackets, `{{` and `}}`) - Functions can call other functions by defining `call` property instead of `code`
- 👀 See [parameter substitution](#parameter-substitution) for an example usage - 👀 See [parameter substitution](#parameter-substitution) for an example usage
#### Parameter substitution #### Expressions
- Expressions are defined inside mustaches (double brackets, `{{` and `}}`)
##### Parameter substitution
A simple function example A simple function example
@@ -125,6 +129,22 @@ It would print "Hello world" if it's called in a [script](#script) as following:
argument: World argument: World
``` ```
A function can call other functions such as:
```yaml
-
function: CallerFunction
parameters: [ 'value' ]
call:
function: EchoArgument
parameters:
argument: {{ $value }}
-
function: EchoArgument
parameters: [ 'argument' ]
code: Hello {{ $argument }} !
```
#### `Function` syntax #### `Function` syntax
- `name`: *`string`* (**required**) - `name`: *`string`* (**required**)
@@ -135,15 +155,20 @@ It would print "Hello world" if it's called in a [script](#script) as following:
- `parameters`: `[` *`string`* `, ... ]` - `parameters`: `[` *`string`* `, ... ]`
- Name of the parameters that the function has. - Name of the parameters that the function has.
- Parameter values are provided by a [Script](#script) through a [FunctionCall](#FunctionCall) - Parameter values are provided by a [Script](#script) through a [FunctionCall](#FunctionCall)
- Parameter names must be defined to be used in expressions such as [parameter substitution](#parameter-substitution) - Parameter names must be defined to be used in [expressions](#expressions)
- ❗ Parameter names must be unique - ❗ Parameter names must be unique
`code`: *`string`* (**required**) `code`: *`string`* (**required** if `call` is undefined)
- Batch file commands that will be executed - Batch file commands that will be executed
- 💡 If defined, best practice to also define `revertCode` - 💡 If defined, best practice to also define `revertCode`
- ❗ If not defined `call` must be defined
- `revertCode`: *`string`* - `revertCode`: *`string`*
- Code that'll undo the change done by `code` property. - Code that'll undo the change done by `code` property.
- E.g. let's say `code` sets an environment variable as `setx POWERSHELL_TELEMETRY_OPTOUT 1` - E.g. let's say `code` sets an environment variable as `setx POWERSHELL_TELEMETRY_OPTOUT 1`
- then `revertCode` should be doing `setx POWERSHELL_TELEMETRY_OPTOUT 0` - then `revertCode` should be doing `setx POWERSHELL_TELEMETRY_OPTOUT 0`
- `call`: ***[`FunctionCall`](#FunctionCall)*** | `[` ***[`FunctionCall`](#FunctionCall)*** `, ... ]` (may be **required**)
- A shared function or sequence of functions to call (called in order)
- The parameter values that are sent can use [expressions](#expressions)
- ❗ If not defined `code` must be defined
### `ScriptingDefinition` ### `ScriptingDefinition`

24
docs/presentation.md Normal file
View File

@@ -0,0 +1,24 @@
# Presentation layer
- Consists of Vue.js components and other UI-related code.
- Desktop application is created using [Electron](https://www.electronjs.org/).
- Event driven as in components simply listens to events from the state and act accordingly.
## Application data
- Components and should use [ApplicationFactory](./../src/application/ApplicationFactory.ts) singleton to reach the application domain.
- [Application.ts](../src/domain/Application.ts) domain model is the stateless application representation including
- available scripts, collections as defined in [collection files](./collection-files.md)
- package information as defined in [`package.json`](./../package.json)
- 📖 See [Application data | Application layer](./presentation.md#application-data) where application data is parsed and compiled.
## Application state
- Stateful components mutate or/and react to state changes in [ApplicationContext](./../src/application/Context/ApplicationContext.ts).
- Stateless components that does not handle state extends `Vue`
- Stateful components that depends on the collection state such as user selection, search queries and more extends [`StatefulVue`](./../src/presentation/StatefulVue.ts)
- The single source of truth is a singleton of the state created and made available to presentation layer by [`StatefulVue`](./../src/presentation/StatefulVue.ts)
- `StatefulVue` includes abstract `handleCollectionState` that is fired once the component is loaded and also each time [collection](./collection-files.md) is changed.
- Do not forget to subscribe from events when component is destroyed or if needed [collection](./collection-files.md) is changed.
- 💡 `events` in base class [`StatefulVue`](./../src/presentation/StatefulVue.ts) makes lifecycling easier
- 📖 See [Application state | Application layer](./presentation.md#application-state) where the state is implemented using using state pattern.

29
docs/tests.md Normal file
View File

@@ -0,0 +1,29 @@
# Unit tests
- Unit tests are defined in [`./tests`](./../tests)
- They follow same folder structure as [`./src`](./../src)
## Naming
- Each test suite first describe the system under test
- E.g. tests for class `Application` is categorized under `Application`
- Tests for specific methods are categorized under method name (if applicable)
- E.g. test for `run()` is categorized under `run`
## Act, arrange, assert
- Tests use act, arrange and assert (AAA) pattern when applicable
- **Arrange**
- Should set up the test case
- Starts with comment line `// arrange`
- **Act**
- Should cover the main thing to be tested
- Starts with comment line `// act`
- **Assert**
- Should elicit some sort of response
- Starts with comment line `// assert`
## Stubs
- Stubs are defined in [`./tests/stubs`](./../tests/unit/stubs)
- They implement dummy behavior to be functional

View File

@@ -1 +1 @@
<mxfile host="www.draw.io" modified="2019-12-27T03:04:27.829Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36" etag="O-1eaon4mqUmgvki0auB" version="12.4.3" pages="1"><diagram id="rhL8jzEM8kVVyiS98U7u" name="Page-1">3Zpdk6I4FIZ/jZdakPDlpa3tzlTNbnVVV83OzE1XhACZAUKF2Or8+k0wCBhau1f8GrpayUkC5HlzTg7IAE7T9V8M5fHfNMDJABjBegBnAwA8YIpPadhsDS50t4aIkWBrMmvDM/mNldFQ1iUJcNFqyClNOMnbRp9mGfZ5y4YYo6t2s5Am7bPmKMKa4dlHiW79lwQ8VsOyjdr+CZMors5sGqomRVVjZShiFNBVwwQfB3DKKOXbvXQ9xYlkV3HZ9pu/Ubu7MIYz/p4OD0/efJxMXmZfs6kd/vj2Txp+HlrVxfFNNWIcCACqSBmPaUQzlDzW1gdGl1mA5WENUarbfKE0F0ZTGH9izjdKTbTkVJhiniaqFq8J/ya7j2xV+t6oma3VkcvCpipknG0anWTxe7Ou7laWqn4FZ/QXntKEsnJ8cFxuomY7cjncN4kqU0GXzMcHMFYzE7EI8wPtwE534S+YplhcqejHcII4eW1fB1IzN9q1q8UVO0rfj2gNrqL1TreWarWI96AbPFG3suuEMbRpNMgpyXjROPKTNIgGKnZC5Zsqctp77n24NTDtvQmzPX09fXbjOGFGwatGj0bsMEbAfmf4uONpaF0zfKjjvqJkqc70xHAhxirOTTNtIrRlXsWE4+cclRhWIlNoSxqSJGkwDmzsBVYXfQ8soOPIHjTj1TyrrgwzjteHxdDhqQ6O23Yf5U2res2H1coeN9Z7xzgTbehc07XMlmu907PMtmeBm3AtcCHX+n8RftwO2iZsBe2j7YHrnj/IQ+9eUsSbmlCnpgwnRQ+gxepJnifE7yNUtwKvFrpDW/51ieGUmzpCw77ddiKdFMXtdhTf3Yw1wzjoCOPu2cK4fS/Oc9P3V/AeEiSoOd2Mpoj0mxotzCAIO7mbhgvH+Ayp0XjPqca6UwGrw6nsczmVpYH+nIUMCSJLny8Z7hV46PnY97uALzzbso0zAHf3clGnA7h3ySjmaMDxK1aZzRugzY+DDrHTDTpwxwvD6GeF8PbYujpbtwMtOBdaV0PLcE4Lwqk6+F3jhR0L8EXxenqoeMw44ffIdj8uXB3uWIPLY4ZRQLLoD8B79dBg6ll8Kn9e6TfsLpDvBbALLoCWZQf9wBXJSRuuYelw7ZHZgRecC6+er4ldEhy7R7pVxMA2R+Obg2xrkAuO+KEU7R18e6C15+xm9ZTjiqjOk2X1MbOc47DMrqej52Olp02o+YDDSGhE/NsDZ+pLymXBVWPoBjfaiNFfG9rtMdMXCg1SEaNc7pK0fJ+giUTCEHyTSUKiTNi4fIizs35BC5w8yYRfTlw4W1DOaSoaJLLiAfm/olKAVjokN9GkPNmkyLfvPcg0CFWFkKylZA/qemYx5/KFiYkkAeZ+kFkj4tMsJEJaNvLFGcFcrHxIfEm7iDlz4UK0GKIsGC6Y+JQmW6Ykc+i4L1+X+GfxIpuINMUb5VXG1+t9r60Jb1/yMQPQ16771x1+WPchCugC79QHnvfyjIqiR91NcGPC6yvx/QtvHhV+ka3Ep5B1Lf7FnlgYpFk+3Sp6Uhq4+zkqBCNdbQh1tStb/2rrucT9q20cVTukLMLZsMBU/rI/d8Stwjwn5fPTYkhzTlLyu0wKenR0WN0O7MTXpbc6pLc+LL0o1m/WbX8JrV9PhI//AQ==</diagram></mxfile> <mxfile host="Electron" modified="2021-01-31T12:32:01.751Z" agent="5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/14.1.8 Chrome/87.0.4280.88 Electron/11.1.1 Safari/537.36" etag="OTbSPW1ZOLwiPL6mt-j9" version="14.1.8" type="device"><diagram id="rhL8jzEM8kVVyiS98U7u" name="Page-1">3VtZd6JKF/01/dhZjA6PREjCXRbGFpM2L70QCYIoLsUI/PpvnwKcMD3cazrJl6wEqfHU3vvsKoz5Infm6e3KWU5ZPPGiL5IwSb/I+hdJUsUmflNBVhQoDaEo8FfBpCgS9wWDIPfKwqrZJph466OGSRxHSbA8LnTjxcJzk6MyZ7WKt8fNnuPoeNal43u1goHrRPXSx2CSTIvSlirsy++8wJ9WM4tCWTN3qsZlwXrqTOLtQZFsfJE7qzhOilfztONFhF2FS9Hv5pXaXWArb5H8Tofr+9ZNO9J+6A+Ljvr89N2aP5tflSq4JKtW7E0AQHkbr5Jp7McLJzL2pdereLOYeDSsgLt9m24cL1EoojD0kiQr2XQ2SYyiaTKPylovDZLv1P1KLe9GBzV6Wo7Mb7LqZpGssoNOdDs6rNt343dVv3WyimdeJ47iFV+f3OZfqKkDWGK6jjcr1/sJapUQnZXvJT9pJxXtCNGDCUp6br147iFSNFh5kZMEL8eSc0rl+rt2e3LxouT3T7iW3oXrHW9HrO1J/IC8yZfmjXfVVisnO2iwjINFsj4Y+Z4K0KD0TrnMzdI51ZP0/nlrSVRPBFNMv5fPbh3/QVHyu7rHgXcIV5L6m/bxeWSofCj7KMd9caJNOdP9yltj8Zg7XtSEcEzzdhok3mDpcFy2OCkcU/ocRNEBxhPVa02Uc+i3pLHcaFCPeJFUOjuzFVbBeqvES38KXlnbaB6nT5lN2/2eL1c7+/Rgv69OMxdHW268Z2qJR6n1m5klHmeW9B6pJb1Xav07h28fm7YoH5n2L9tLzebbm7zc+ixHxPcU1MWPDP/JPaSaV2vLZRS4l7DqI+OtWfezSt/nyGjwr3KEg/Li62ck/b6Lq8cuvnsYO7Rx6YyNN9/MxtXPkjwf6flK/pQHJLmWdHo8d4LLHo3G4mTyfBZ3UWjKbe8Njkbtk6Rq15NKUs4klfpWSaXUgDYXzysHiGzcZLPyLgr4c8v1XPcc4OOWqqjCGwDePDmLNs4A3vqbLtaoAe69eOXJ5hWgxT8H+tlrnAd60myPBeEyO0TrBNtmHdvmGWilt4K2WYN25S3jdZDE5eCfGl75zAb8V+Ft1a3CWCRB8hmxPfWFdwe3XQM3ma48ZxIs/P8DeN/dGsT6KX5Of165rO2OBU/2GufAFbyW0GpdBlwcTo7BFZQ6uOqVeAZe6a3grZ/X8DKY/OoZ6aNCLKniVfvDgazWQF4nTvKzI9qf42vo9H0ZFE9MQKze/XhHCP/K6euSGJ6+mXoORPHcu6lvh2H9mOUcviEiRLEfuJ8HULG+Nf1dQHdr2AOazamXG0eR5xKqH1efHw/N+lZUA289dZb0MpjzTywcQkUrh5QjLQr8BcoSeptoV9p1xl50T48UJHVZH8dJEs/RIKKKa8ed+ZyYowMXfaEJn0xbL4tPVtBBy6lunoOUqLwu49GnSUIfydAICenGnSyUq8CNF88BKF9duZhRusHe6uBC5VDHDZIuXn91FpOv4xV+U5FKh54budH88bDxwvUPaoKDUOtqWZ0pL/pk3aa/UpxyryhXap38fenl6a9vkp+ffvmP6f/qTOKxtxOB1Gr9GDjr9QXpl9TWVVP9PQlI5zUgvZkI6tv85xeB+EsRjBdb/AbFKX7wCtsyFdNbausLsS7LyhHlcuOqVT+QSO0zht9+K7Lr55HPT7bwS7Kf45XvLb6uvZg+TXDTAC83y4C/Z7v+Gi+TYB7k/ER2wZzfvUtSsS/XqK/0cUh9VfZfqH/Y3Hrj5vegr7fv2awhLwLh8cyf5n7J/CWIPS+Z8+Qe8V/RzamUteJWulm/gJzrlM58nfs7S3rKrpXxY7pxcyFw7r4Jrh6/dOWJPMlUmWXqizt3X1iobVmnnU/mbmDeTZZPd9/i+4GZWfYoMG+nkfM4iSe6ELBwKJnBteQ8Psj9eVtBm62paz4v103RCkzMfX/r+k/zaD1Gj/G8vXkamMV9R8wmj2l0P/gnmswfNmPp28wMldZIirKRlEbm7dNyfLttmwHL+uE/N0wwMLtFs6Qs9Om1unt9Z7bN0Mh6HfPlPkwXB33Vb7OH62/hLCjLEnfxsH6yi1i8+UM2zniUd9fTya3vPyFK2zawfkVkuulbubGxwijs2qbUBS7WQBC6oZtbgSBYgSJaobthoZl27SGVSyzQUqujCMweJqhPcfWZrW0se5Z17f6G5SMZ2KDtKLcGGq6mYOpMxb1gzY0MV8nKtOoKPA2fdajdKMOcVC4yiSVWpsiWbSbMZhjbxNgzxDFCHKZIYzEepylYmZAjnhx1PuJRmO6jD9aUz1LiqBsaQq8jpKjLMJZv6RjH9uWubaTd0M+tx3MxaRTTpmcPJawb8/Zlby4kWBvKZqqpu2jLtpbEUo4jxwBz2kOxWLshW4GWsUDJe3bfZ/kM8ZBmGOZkSq+j5bQ+FjJoiWL1wYO76el+jv4y4tiygaaijYR+GBu42po0sk1wxfEtr4hzoG27wIjpmkj4WzraZVgvjU9zow/TXQXrABY+8Qeeh4SJivi2TGcJHz830MYgvMCvoIBrzkcP2Fq8DeFPPDDU9WX0zxnVvcqpkHfDmQAN4DpUvYArdzv6/i02b/ttcyakPYo3NGl9W8xHvFPcWyg+QY5izQblGcadIXcFgc+n+xwP8CsWmhymLBBIu+grEB8CcEW8Ptqwck0zWgv0CDz1IepGGLt/Hs8O8BwoWyuc+Wifs9wKGdeARtio4C4j3Vvh0IfuVHALfg1gPEOsBtbaV0x9RPFluE8pZ4ABrn3oxkQe+FuKE30zWgt+gP1MfY1bU+8Tf9Ay8kDvS6RDi17nlAsjhWvtkdY7pHFIY4h1poInmluxOhrmdIUe8gXYiozj7UNrTCy4nAms0KqAssQKSQt96Jj0MiIfkICpwmwXGoWOwdPrsRJOrgz8ZCt/CLkubZYjZ0n3AmLJMJZq6W5i0Rj5CJgMEYNJmMiESY+0w/EeIUcpTkMs8sYAf8CNuAk06Arj5CbnGXy/ih/4Jx3JRR4b4hkdKtxrMi3t6S7laT7KMa+tCUUu+6RB4lXqwc8QP7h3eU739D7mIe0apPuUUUz6kDREeKo9e5T0qL3OuEYseFGXvIzy5/bVvCFNy/C3jPN2W3ohjYs1kkd0aT2hKUKLaW+gKD3ySfJpvQ//MEhjyG3kAvyD8XgE0qxI8XA/DYeUx7S2DJiCY7STXo0HOsB6uIZd1ZKXCTiBRkzu9z19BLwoj42kyEsT+qdYfLHUYIo4Ea+m0N6CeAXue1inRdjkM65dC+OAb8xtSow8PSM9ukmP+1c/ezVXqd4eCZQnaC+McoPvNSzTuIfhWuwPpF/eZkQ5grWT12jEPfj0aayUv87dpPBt4piwYrTPUR5R/iiv40T+oEHPtA/McuSpCK1k2DtoPInZ/4RYM+FAuSgSLvBZeBe45X7F84G4zfg+m1VtuYbLMu20n1/1IzzIO7G/kW/tvL+o2/Xf9ava0rqrGE777eYF5zx/4Iamfj0lbjAW4uvnZZ7Bi4wyV3gcx/WYAzGU9dcN2+7THqLynON7Vz8d5aSZ4UE8/dN4hAMcgAnXu0px4QwjHuC2679fB28LXWrJHhPe73DMPaaFXgn7Au+BdgbvPTZFjgAdySjXDB/RWbVu0jr5vQLfOq2vtFPUL+Kgmyst9/ZGcDrXM5wcLcxH5wHSqkjnLkv/Fv4Rd3WshDpW7BQr9V9j1flbWGEs7rczn2so9zOce6CxmVLstxo8XeBeWJwrNRWemvL9Jx8mHMP8aQo/ynCu86trNU9RT35J50BXIo/r2eRptNe78B/yf9pvyZ/orGls6ZxSxK3RtYxBK2OqYtCKmIr85vtOEdNTwwxadFLvtBf34fblCUroyniiyRV6hrrMnz8l+Uo9fuNJlurPoY0zz6GNP34Oxe3+v8qKTwHv/zVPNv4H</diagram></mxfile>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 89 KiB

1533
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,9 @@
{ {
"name": "privacy.sexy", "name": "privacy.sexy",
"version": "0.8.2", "version": "0.9.2",
"author": "undergroundwires",
"description": "Enforce privacy & security best-practices on Windows and macOS, because privacy is sexy 🍑🍆",
"homepage": "https://privacy.sexy",
"private": true, "private": true,
"repository": { "description": "Enforce privacy & security best-practices on Windows and macOS, because privacy is sexy 🍑🍆",
"type": "git", "author": "undergroundwires",
"url": "https://github.com/undergroundwires/privacy.sexy.git"
},
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",
"build": "vue-cli-service build", "build": "vue-cli-service build",
@@ -32,9 +27,11 @@
"@fortawesome/free-solid-svg-icons": "^5.15.1", "@fortawesome/free-solid-svg-icons": "^5.15.1",
"@fortawesome/vue-fontawesome": "^2.0.2", "@fortawesome/vue-fontawesome": "^2.0.2",
"ace-builds": "^1.4.12", "ace-builds": "^1.4.12",
"core-js": "^3.6.5",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"inversify": "^5.0.5", "inversify": "^5.0.5",
"liquor-tree": "^0.2.70", "liquor-tree": "^0.2.70",
"resize-observer-polyfill": "^1.5.1",
"v-tooltip": "2.0.2", "v-tooltip": "2.0.2",
"vue": "^2.6.12", "vue": "^2.6.12",
"vue-class-component": "^7.2.6", "vue-class-component": "^7.2.6",
@@ -46,6 +43,7 @@
"@types/chai": "^4.2.14", "@types/chai": "^4.2.14",
"@types/file-saver": "^2.0.1", "@types/file-saver": "^2.0.1",
"@types/mocha": "^8.2.0", "@types/mocha": "^8.2.0",
"@vue/cli-plugin-babel": "^4.5.10",
"@vue/cli-plugin-typescript": "^4.5.9", "@vue/cli-plugin-typescript": "^4.5.9",
"@vue/cli-plugin-unit-mocha": "^4.5.9", "@vue/cli-plugin-unit-mocha": "^4.5.9",
"@vue/cli-service": "^4.5.9", "@vue/cli-service": "^4.5.9",
@@ -67,5 +65,10 @@
"vue-cli-plugin-electron-builder": "^2.0.0-rc.5", "vue-cli-plugin-electron-builder": "^2.0.0-rc.5",
"vue-template-compiler": "^2.6.12", "vue-template-compiler": "^2.6.12",
"yaml-lint": "^1.2.4" "yaml-lint": "^1.2.4"
},
"homepage": "https://privacy.sexy",
"repository": {
"type": "git",
"url": "https://github.com/undergroundwires/privacy.sexy.git"
} }
} }

View File

@@ -1,12 +1,11 @@
<template> <template>
<div id="app"> <div id="app">
<div class="wrapper"> <div class="wrapper">
<TheHeader class="row" /> <TheHeader class="row" />
<TheSearchBar class="row" /> <TheSearchBar class="row" />
<TheScripts class="row"/> <TheScriptArea class="row" />
<TheCodeArea class="row" theme="xcode" /> <TheCodeButtons class="row code-buttons" />
<TheCodeButtons class="row code-buttons" /> <TheFooter />
<TheFooter />
</div> </div>
</div> </div>
</template> </template>
@@ -15,17 +14,15 @@
import { Component, Vue } from 'vue-property-decorator'; import { Component, Vue } from 'vue-property-decorator';
import TheHeader from '@/presentation/TheHeader.vue'; import TheHeader from '@/presentation/TheHeader.vue';
import TheFooter from '@/presentation/TheFooter/TheFooter.vue'; import TheFooter from '@/presentation/TheFooter/TheFooter.vue';
import TheCodeArea from '@/presentation/TheCodeArea.vue'; import TheCodeButtons from '@/presentation/Code/CodeButtons/TheCodeButtons.vue';
import TheCodeButtons from '@/presentation/CodeButtons/TheCodeButtons.vue'; import TheScriptArea from '@/presentation/Scripts/TheScriptArea.vue';
import TheSearchBar from '@/presentation/TheSearchBar.vue'; import TheSearchBar from '@/presentation/TheSearchBar.vue';
import TheScripts from '@/presentation/Scripts/TheScripts.vue';
@Component({ @Component({
components: { components: {
TheHeader, TheHeader,
TheCodeArea,
TheCodeButtons, TheCodeButtons,
TheScripts, TheScriptArea,
TheSearchBar, TheSearchBar,
TheFooter, TheFooter,
}, },
@@ -38,6 +35,7 @@ export default class App extends Vue {
<style lang="scss"> <style lang="scss">
@import "@/presentation/styles/colors.scss"; @import "@/presentation/styles/colors.scss";
@import "@/presentation/styles/fonts.scss"; @import "@/presentation/styles/fonts.scss";
@import "@/presentation/styles/media.scss";
* { * {
box-sizing: border-box; box-sizing: border-box;
@@ -49,12 +47,10 @@ body {
color: $slate; color: $slate;
} }
#app { #app {
margin-right: auto; margin-right: auto;
margin-left: auto; margin-left: auto;
max-width: 1500px; max-width: 1600px;
.wrapper { .wrapper {
margin: 0% 2% 0% 2%; margin: 0% 2% 0% 2%;
background-color: white; background-color: white;
@@ -62,18 +58,15 @@ body {
padding: 2%; padding: 2%;
display:flex; display:flex;
flex-direction: column; flex-direction: column;
.row { .row {
margin-bottom: 10px; margin-bottom: 10px;
} }
.code-buttons { .code-buttons {
padding-bottom: 10px; padding-bottom: 10px;
} }
} }
} }
@import "@/presentation/styles/tooltip.scss"; @import "@/presentation/styles/tooltip.scss";
@import "@/presentation/styles/tree.scss"; @import "@/presentation/styles/tree.scss";
</style> </style>

View File

@@ -0,0 +1,21 @@
import { IApplication } from '@/domain/IApplication';
import { AsyncLazy } from '@/infrastructure/Threading/AsyncLazy';
import { IApplicationFactory } from './IApplicationFactory';
import { parseApplication } from './Parser/ApplicationParser';
export type ApplicationGetter = () => IApplication;
const ApplicationGetter: ApplicationGetter = parseApplication;
export class ApplicationFactory implements IApplicationFactory {
public static readonly Current: IApplicationFactory = new ApplicationFactory(ApplicationGetter);
private readonly getter: AsyncLazy<IApplication>;
protected constructor(costlyGetter: ApplicationGetter) {
if (!costlyGetter) {
throw new Error('undefined getter');
}
this.getter = new AsyncLazy<IApplication>(() => Promise.resolve(costlyGetter()));
}
public getAppAsync(): Promise<IApplication> {
return this.getter.getValueAsync();
}
}

View File

@@ -4,12 +4,12 @@ import { CategoryCollectionState } from './State/CategoryCollectionState';
import { IApplication } from '@/domain/IApplication'; import { IApplication } from '@/domain/IApplication';
import { OperatingSystem } from '@/domain/OperatingSystem'; import { OperatingSystem } from '@/domain/OperatingSystem';
import { ICategoryCollection } from '@/domain/ICategoryCollection'; import { ICategoryCollection } from '@/domain/ICategoryCollection';
import { Signal } from '@/infrastructure/Events/Signal'; import { EventSource } from '@/infrastructure/Events/EventSource';
type StateMachine = Map<OperatingSystem, ICategoryCollectionState>; type StateMachine = Map<OperatingSystem, ICategoryCollectionState>;
export class ApplicationContext implements IApplicationContext { export class ApplicationContext implements IApplicationContext {
public readonly contextChanged = new Signal<IApplicationContextChangedEvent>(); public readonly contextChanged = new EventSource<IApplicationContextChangedEvent>();
public collection: ICategoryCollection; public collection: ICategoryCollection;
public currentOs: OperatingSystem; public currentOs: OperatingSystem;

View File

@@ -4,15 +4,15 @@ import { OperatingSystem } from '@/domain/OperatingSystem';
import { Environment } from '../Environment/Environment'; import { Environment } from '../Environment/Environment';
import { IApplication } from '@/domain/IApplication'; import { IApplication } from '@/domain/IApplication';
import { IEnvironment } from '../Environment/IEnvironment'; import { IEnvironment } from '../Environment/IEnvironment';
import { parseApplication } from '../Parser/ApplicationParser'; import { IApplicationFactory } from '../IApplicationFactory';
import { ApplicationFactory } from '../ApplicationFactory';
export type ApplicationParserType = () => IApplication; export async function buildContextAsync(
const ApplicationParser: ApplicationParserType = parseApplication; factory: IApplicationFactory = ApplicationFactory.Current,
environment = Environment.CurrentEnvironment): Promise<IApplicationContext> {
export function buildContext( if (!factory) { throw new Error('undefined factory'); }
parser = ApplicationParser, if (!environment) { throw new Error('undefined environment'); }
environment = Environment.CurrentEnvironment): IApplicationContext { const app = await factory.getAppAsync();
const app = parser();
const os = getInitialOs(app, environment); const os = getInitialOs(app, environment);
return new ApplicationContext(app, os); return new ApplicationContext(app, os);
} }

View File

@@ -1,12 +1,12 @@
import { ICategoryCollectionState } from './State/ICategoryCollectionState'; import { ICategoryCollectionState } from './State/ICategoryCollectionState';
import { OperatingSystem } from '@/domain/OperatingSystem'; import { OperatingSystem } from '@/domain/OperatingSystem';
import { ISignal } from '@/infrastructure/Events/ISignal'; import { IEventSource } from '@/infrastructure/Events/IEventSource';
import { IApplication } from '@/domain/IApplication'; import { IApplication } from '@/domain/IApplication';
export interface IApplicationContext { export interface IApplicationContext {
readonly app: IApplication; readonly app: IApplication;
readonly state: ICategoryCollectionState; readonly state: ICategoryCollectionState;
readonly contextChanged: ISignal<IApplicationContextChangedEvent>; readonly contextChanged: IEventSource<IApplicationContextChangedEvent>;
changeContext(os: OperatingSystem): void; changeContext(os: OperatingSystem): void;
} }

View File

@@ -4,13 +4,13 @@ import { ICodeChangedEvent } from './Event/ICodeChangedEvent';
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript'; import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { IUserSelection } from '@/application/Context/State/Selection/IUserSelection'; import { IUserSelection } from '@/application/Context/State/Selection/IUserSelection';
import { UserScriptGenerator } from './Generation/UserScriptGenerator'; import { UserScriptGenerator } from './Generation/UserScriptGenerator';
import { Signal } from '@/infrastructure/Events/Signal'; import { EventSource } from '@/infrastructure/Events/EventSource';
import { IApplicationCode } from './IApplicationCode'; import { IApplicationCode } from './IApplicationCode';
import { IUserScriptGenerator } from './Generation/IUserScriptGenerator'; import { IUserScriptGenerator } from './Generation/IUserScriptGenerator';
import { IScriptingDefinition } from '@/domain/IScriptingDefinition'; import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
export class ApplicationCode implements IApplicationCode { export class ApplicationCode implements IApplicationCode {
public readonly changed = new Signal<ICodeChangedEvent>(); public readonly changed = new EventSource<ICodeChangedEvent>();
public current: string; public current: string;
private scriptPositions = new Map<SelectedScript, CodePosition>(); private scriptPositions = new Map<SelectedScript, CodePosition>();

View File

@@ -5,6 +5,12 @@ export class BatchBuilder extends CodeBuilder {
return '::'; return '::';
} }
protected writeStandardOut(text: string): string { protected writeStandardOut(text: string): string {
return `echo ${text}`; return `echo ${escapeForEcho(text)}`;
} }
} }
function escapeForEcho(text: string) {
return text
.replace(/&/g, '^&')
.replace(/%/g, '%%');
}

View File

@@ -5,6 +5,11 @@ export class ShellBuilder extends CodeBuilder {
return '#'; return '#';
} }
protected writeStandardOut(text: string): string { protected writeStandardOut(text: string): string {
return `echo '${text}'`; return `echo '${escapeForEcho(text)}'`;
} }
} }
function escapeForEcho(text: string) {
return text
.replace(/'/g, '\'\\\'\'');
}

View File

@@ -1,7 +1,7 @@
import { ICodeChangedEvent } from './Event/ICodeChangedEvent'; import { ICodeChangedEvent } from './Event/ICodeChangedEvent';
import { ISignal } from '@/infrastructure/Events/ISignal'; import { IEventSource } from '@/infrastructure/Events/IEventSource';
export interface IApplicationCode { export interface IApplicationCode {
readonly changed: ISignal<ICodeChangedEvent>; readonly changed: IEventSource<ICodeChangedEvent>;
readonly current: string; readonly current: string;
} }

View File

@@ -1,10 +1,10 @@
import { ISignal } from '@/infrastructure/Events/ISignal'; import { IEventSource } from '@/infrastructure/Events/IEventSource';
import { IFilterResult } from './IFilterResult'; import { IFilterResult } from './IFilterResult';
export interface IUserFilter { export interface IUserFilter {
readonly currentFilter: IFilterResult | undefined; readonly currentFilter: IFilterResult | undefined;
readonly filtered: ISignal<IFilterResult>; readonly filtered: IEventSource<IFilterResult>;
readonly filterRemoved: ISignal<void>; readonly filterRemoved: IEventSource<void>;
setFilter(filter: string): void; setFilter(filter: string): void;
removeFilter(): void; removeFilter(): void;
} }

View File

@@ -2,12 +2,12 @@ import { IScript } from '@/domain/IScript';
import { FilterResult } from './FilterResult'; import { FilterResult } from './FilterResult';
import { IFilterResult } from './IFilterResult'; import { IFilterResult } from './IFilterResult';
import { IUserFilter } from './IUserFilter'; import { IUserFilter } from './IUserFilter';
import { Signal } from '@/infrastructure/Events/Signal'; import { EventSource } from '@/infrastructure/Events/EventSource';
import { ICategoryCollection } from '@/domain/ICategoryCollection'; import { ICategoryCollection } from '@/domain/ICategoryCollection';
export class UserFilter implements IUserFilter { export class UserFilter implements IUserFilter {
public readonly filtered = new Signal<IFilterResult>(); public readonly filtered = new EventSource<IFilterResult>();
public readonly filterRemoved = new Signal<void>(); public readonly filterRemoved = new EventSource<void>();
public currentFilter: IFilterResult | undefined; public currentFilter: IFilterResult | undefined;
constructor(private collection: ICategoryCollection) { constructor(private collection: ICategoryCollection) {

View File

@@ -1,10 +1,10 @@
import { SelectedScript } from './SelectedScript'; import { SelectedScript } from './SelectedScript';
import { IScript } from '@/domain/IScript'; import { IScript } from '@/domain/IScript';
import { ICategory } from '@/domain/ICategory'; import { ICategory } from '@/domain/ICategory';
import { ISignal } from '@/infrastructure/Events/ISignal'; import { IEventSource } from '@/infrastructure/Events/IEventSource';
export interface IUserSelection { export interface IUserSelection {
readonly changed: ISignal<ReadonlyArray<SelectedScript>>; readonly changed: IEventSource<ReadonlyArray<SelectedScript>>;
readonly selectedScripts: ReadonlyArray<SelectedScript>; readonly selectedScripts: ReadonlyArray<SelectedScript>;
readonly totalSelected: number; readonly totalSelected: number;
areAllSelected(category: ICategory): boolean; areAllSelected(category: ICategory): boolean;

View File

@@ -2,13 +2,13 @@ import { SelectedScript } from './SelectedScript';
import { IUserSelection } from './IUserSelection'; import { IUserSelection } from './IUserSelection';
import { InMemoryRepository } from '@/infrastructure/Repository/InMemoryRepository'; import { InMemoryRepository } from '@/infrastructure/Repository/InMemoryRepository';
import { IScript } from '@/domain/IScript'; import { IScript } from '@/domain/IScript';
import { Signal } from '@/infrastructure/Events/Signal'; import { EventSource } from '@/infrastructure/Events/EventSource';
import { IRepository } from '@/infrastructure/Repository/IRepository'; import { IRepository } from '@/infrastructure/Repository/IRepository';
import { ICategory } from '@/domain/ICategory'; import { ICategory } from '@/domain/ICategory';
import { ICategoryCollection } from '@/domain/ICategoryCollection'; import { ICategoryCollection } from '@/domain/ICategoryCollection';
export class UserSelection implements IUserSelection { export class UserSelection implements IUserSelection {
public readonly changed = new Signal<ReadonlyArray<SelectedScript>>(); public readonly changed = new EventSource<ReadonlyArray<SelectedScript>>();
private readonly scripts: IRepository<string, SelectedScript>; private readonly scripts: IRepository<string, SelectedScript>;
constructor( constructor(

View File

@@ -0,0 +1,5 @@
import { IApplication } from '@/domain/IApplication';
export interface IApplicationFactory {
getAppAsync(): Promise<IApplication>;
}

View File

@@ -0,0 +1,34 @@
import { IExpressionsCompiler, ParameterValueDictionary } from './IExpressionsCompiler';
import { generateIlCode, IILCode } from './ILCode';
export class ExpressionsCompiler implements IExpressionsCompiler {
public static readonly instance: IExpressionsCompiler = new ExpressionsCompiler();
protected constructor() { }
public compileExpressions(code: string, parameters?: ParameterValueDictionary): string {
let intermediateCode = generateIlCode(code);
intermediateCode = substituteParameters(intermediateCode, parameters);
return intermediateCode.compile();
}
}
function substituteParameters(intermediateCode: IILCode, parameters: ParameterValueDictionary): IILCode {
const parameterNames = intermediateCode.getUniqueParameterNames();
ensureValuesProvided(parameterNames, parameters);
for (const parameterName of parameterNames) {
const parameterValue = parameters[parameterName];
intermediateCode = intermediateCode.substituteParameter(parameterName, parameterValue);
}
return intermediateCode;
}
function ensureValuesProvided(names: string[], nameValues: ParameterValueDictionary) {
nameValues = nameValues || {};
const notProvidedNames = names.filter((name) => !Boolean(nameValues[name]));
if (notProvidedNames.length) {
throw new Error(`parameter value(s) not provided for: ${printList(notProvidedNames)}`);
}
}
function printList(list: readonly string[]): string {
return `"${list.join('", "')}"`;
}

View File

@@ -0,0 +1,5 @@
export interface ParameterValueDictionary { [parameterName: string]: string; }
export interface IExpressionsCompiler {
compileExpressions(code: string, parameters?: ParameterValueDictionary): string;
}

View File

@@ -0,0 +1,114 @@
import { FunctionData, InstructionHolder } from 'js-yaml-loader!*';
import { SharedFunction } from './SharedFunction';
import { SharedFunctionCollection } from './SharedFunctionCollection';
import { ISharedFunctionCollection } from './ISharedFunctionCollection';
import { IFunctionCompiler } from './IFunctionCompiler';
import { IFunctionCallCompiler } from '../FunctionCall/IFunctionCallCompiler';
import { FunctionCallCompiler } from '../FunctionCall/FunctionCallCompiler';
export class FunctionCompiler implements IFunctionCompiler {
public static readonly instance: IFunctionCompiler = new FunctionCompiler();
protected constructor(
private readonly functionCallCompiler: IFunctionCallCompiler = FunctionCallCompiler.instance) {
}
public compileFunctions(functions: readonly FunctionData[]): ISharedFunctionCollection {
const collection = new SharedFunctionCollection();
if (!functions || !functions.length) {
return collection;
}
ensureValidFunctions(functions);
functions
.filter((func) => hasCode(func))
.forEach((func) => {
const shared = new SharedFunction(func.name, func.parameters, func.code, func.revertCode);
collection.addFunction(shared);
});
functions
.filter((func) => hasCall(func))
.forEach((func) => {
const code = this.functionCallCompiler.compileCall(func.call, collection);
const shared = new SharedFunction(func.name, func.parameters, code.code, code.revertCode);
collection.addFunction(shared);
});
return collection;
}
}
function hasCode(data: FunctionData): boolean {
return Boolean(data.code);
}
function hasCall(data: FunctionData): boolean {
return Boolean(data.call);
}
function ensureValidFunctions(functions: readonly FunctionData[]) {
ensureNoUndefinedItem(functions);
ensureNoDuplicatesInFunctionNames(functions);
ensureNoDuplicatesInParameterNames(functions);
ensureNoDuplicateCode(functions);
ensureEitherCallOrCodeIsDefined(functions);
}
function printList(list: readonly string[]): string {
return `"${list.join('","')}"`;
}
function ensureEitherCallOrCodeIsDefined(holders: readonly InstructionHolder[]) {
// Ensure functions do not define both call and code
const withBothCallAndCode = holders.filter((holder) => hasCode(holder) && hasCall(holder));
if (withBothCallAndCode.length) {
throw new Error(`both "code" and "call" are defined in ${printNames(withBothCallAndCode)}`);
}
// Ensure functions have either code or call
const hasEitherCodeOrCall = holders.filter((holder) => !hasCode(holder) && !hasCall(holder));
if (hasEitherCodeOrCall.length) {
throw new Error(`neither "code" or "call" is defined in ${printNames(hasEitherCodeOrCall)}`);
}
}
function printNames(holders: readonly InstructionHolder[]) {
return printList(holders.map((holder) => holder.name));
}
function ensureNoDuplicatesInFunctionNames(functions: readonly FunctionData[]) {
const duplicateFunctionNames = getDuplicates(functions
.map((func) => func.name.toLowerCase()));
if (duplicateFunctionNames.length) {
throw new Error(`duplicate function name: ${printList(duplicateFunctionNames)}`);
}
}
function ensureNoUndefinedItem(functions: readonly FunctionData[]) {
if (functions.some((func) => !func)) {
throw new Error(`some functions are undefined`);
}
}
function ensureNoDuplicatesInParameterNames(functions: readonly FunctionData[]) {
const functionsWithParameters = functions
.filter((func) => func.parameters && func.parameters.length > 0);
for (const func of functionsWithParameters) {
const duplicateParameterNames = getDuplicates(func.parameters);
if (duplicateParameterNames.length) {
throw new Error(`"${func.name}": duplicate parameter name: ${printList(duplicateParameterNames)}`);
}
}
}
function ensureNoDuplicateCode(functions: readonly FunctionData[]) {
const duplicateCodes = getDuplicates(functions
.map((func) => func.code)
.filter((code) => code),
);
if (duplicateCodes.length > 0) {
throw new Error(`duplicate "code" in functions: ${printList(duplicateCodes)}`);
}
const duplicateRevertCodes = getDuplicates(functions
.filter((func) => func.revertCode)
.map((func) => func.revertCode));
if (duplicateRevertCodes.length > 0) {
throw new Error(`duplicate "revertCode" in functions: ${printList(duplicateRevertCodes)}`);
}
}
function getDuplicates(texts: readonly string[]): string[] {
return texts.filter((item, index) => texts.indexOf(item) !== index);
}

View File

@@ -0,0 +1,6 @@
import { FunctionData } from 'js-yaml-loader!*';
import { ISharedFunctionCollection } from './ISharedFunctionCollection';
export interface IFunctionCompiler {
compileFunctions(functions: readonly FunctionData[]): ISharedFunctionCollection;
}

View File

@@ -0,0 +1,6 @@
export interface ISharedFunction {
readonly name: string;
readonly parameters?: readonly string[];
readonly code: string;
readonly revertCode?: string;
}

View File

@@ -0,0 +1,5 @@
import { ISharedFunction } from './ISharedFunction';
export interface ISharedFunctionCollection {
getFunctionByName(name: string): ISharedFunction;
}

View File

@@ -0,0 +1,14 @@
import { ISharedFunction } from './ISharedFunction';
export class SharedFunction implements ISharedFunction {
constructor(
public readonly name: string,
public readonly parameters: readonly string[],
public readonly code: string,
public readonly revertCode: string,
) {
if (!name) { throw new Error('undefined function name'); }
if (!code) { throw new Error(`undefined function ("${name}") code`); }
this.parameters = parameters || [];
}
}

View File

@@ -0,0 +1,23 @@
import { ISharedFunction } from './ISharedFunction';
import { ISharedFunctionCollection } from './ISharedFunctionCollection';
export class SharedFunctionCollection implements ISharedFunctionCollection {
private readonly functionsByName = new Map<string, ISharedFunction>();
public addFunction(func: ISharedFunction): void {
if (!func) { throw new Error('undefined function'); }
if (this.functionsByName.has(func.name)) {
throw new Error(`function with name ${func.name} already exists`);
}
this.functionsByName.set(func.name, func);
}
public getFunctionByName(name: string): ISharedFunction {
if (!name) { throw Error('undefined function name'); }
const func = this.functionsByName.get(name);
if (!func) {
throw new Error(`called function is not defined "${name}"`);
}
return func;
}
}

View File

@@ -0,0 +1,88 @@
import { FunctionCallData, FunctionCallParametersData, FunctionData, ScriptFunctionCallData } from 'js-yaml-loader!*';
import { ICompiledCode } from './ICompiledCode';
import { ISharedFunctionCollection } from '../Function/ISharedFunctionCollection';
import { IFunctionCallCompiler } from './IFunctionCallCompiler';
import { IExpressionsCompiler } from '../Expressions/IExpressionsCompiler';
import { ExpressionsCompiler } from '../Expressions/ExpressionsCompiler';
export class FunctionCallCompiler implements IFunctionCallCompiler {
public static readonly instance: IFunctionCallCompiler = new FunctionCallCompiler();
protected constructor(
private readonly expressionsCompiler: IExpressionsCompiler = ExpressionsCompiler.instance) { }
public compileCall(
call: ScriptFunctionCallData,
functions: ISharedFunctionCollection): ICompiledCode {
if (!functions) { throw new Error('undefined functions'); }
if (!call) { throw new Error('undefined call'); }
const compiledCodes = new Array<ICompiledCode>();
const calls = getCallSequence(call);
calls.forEach((currentCall, currentCallIndex) => {
ensureValidCall(currentCall);
const commonFunction = functions.getFunctionByName(currentCall.function);
ensureExpectedParameters(commonFunction, currentCall);
let functionCode = compileCode(commonFunction, currentCall.parameters, this.expressionsCompiler);
if (currentCallIndex !== calls.length - 1) {
functionCode = appendLine(functionCode);
}
compiledCodes.push(functionCode);
});
const compiledCode = merge(compiledCodes);
return compiledCode;
}
}
function ensureExpectedParameters(func: FunctionData, call: FunctionCallData) {
if (!func.parameters && !call.parameters) {
return;
}
const unexpectedParameters = Object.keys(call.parameters || {})
.filter((callParam) => !func.parameters.includes(callParam));
if (unexpectedParameters.length) {
throw new Error(
`function "${func.name}" has unexpected parameter(s) provided: "${unexpectedParameters.join('", "')}"`);
}
}
function merge(codes: readonly ICompiledCode[]): ICompiledCode {
return {
code: codes.map((code) => code.code).join(''),
revertCode: codes.map((code) => code.revertCode).join(''),
};
}
function compileCode(
func: FunctionData,
parameters: FunctionCallParametersData,
compiler: IExpressionsCompiler): ICompiledCode {
return {
code: compiler.compileExpressions(func.code, parameters),
revertCode: compiler.compileExpressions(func.revertCode, parameters),
};
}
function getCallSequence(call: ScriptFunctionCallData): FunctionCallData[] {
if (typeof call !== 'object') {
throw new Error('called function(s) must be an object');
}
if (call instanceof Array) {
return call as FunctionCallData[];
}
return [ call as FunctionCallData ];
}
function ensureValidCall(call: FunctionCallData) {
if (!call) {
throw new Error(`undefined function call`);
}
if (!call.function) {
throw new Error(`empty function name called`);
}
}
function appendLine(code: ICompiledCode): ICompiledCode {
const appendLineIfNotEmpty = (str: string) => str ? `${str}\n` : str;
return {
code: appendLineIfNotEmpty(code.code),
revertCode: appendLineIfNotEmpty(code.revertCode),
};
}

View File

@@ -0,0 +1,4 @@
export interface ICompiledCode {
readonly code: string;
readonly revertCode?: string;
}

View File

@@ -0,0 +1,9 @@
import { ScriptFunctionCallData } from 'js-yaml-loader!*';
import { ICompiledCode } from './ICompiledCode';
import { ISharedFunctionCollection } from '../Function/ISharedFunctionCollection';
export interface IFunctionCallCompiler {
compileCall(
call: ScriptFunctionCallData,
functions: ISharedFunctionCollection): ICompiledCode;
}

View File

@@ -1,171 +1,42 @@
import { generateIlCode, IILCode } from './ILCode';
import { IScriptCode } from '@/domain/IScriptCode'; import { IScriptCode } from '@/domain/IScriptCode';
import { ScriptCode } from '@/domain/ScriptCode'; import { ScriptCode } from '@/domain/ScriptCode';
import { ScriptData, FunctionData, FunctionCallData, ScriptFunctionCallData, FunctionCallParametersData } from 'js-yaml-loader!@/*'; import { FunctionData, ScriptData } from 'js-yaml-loader!@/*';
import { IScriptCompiler } from './IScriptCompiler'; import { IScriptCompiler } from './IScriptCompiler';
import { ILanguageSyntax } from '@/domain/ScriptCode'; import { ILanguageSyntax } from '@/domain/ScriptCode';
import { ISharedFunctionCollection } from './Function/ISharedFunctionCollection';
interface ICompiledCode { import { IFunctionCallCompiler } from './FunctionCall/IFunctionCallCompiler';
readonly code: string; import { FunctionCallCompiler } from './FunctionCall/FunctionCallCompiler';
readonly revertCode: string; import { IFunctionCompiler } from './Function/IFunctionCompiler';
} import { FunctionCompiler } from './Function/FunctionCompiler';
export class ScriptCompiler implements IScriptCompiler { export class ScriptCompiler implements IScriptCompiler {
private readonly functions: ISharedFunctionCollection;
constructor( constructor(
private readonly functions: readonly FunctionData[] | undefined, functions: readonly FunctionData[] | undefined,
private syntax: ILanguageSyntax) { private readonly syntax: ILanguageSyntax,
ensureValidFunctions(functions); functionCompiler: IFunctionCompiler = FunctionCompiler.instance,
private readonly callCompiler: IFunctionCallCompiler = FunctionCallCompiler.instance,
) {
if (!syntax) { throw new Error('undefined syntax'); } if (!syntax) { throw new Error('undefined syntax'); }
this.functions = functionCompiler.compileFunctions(functions);
} }
public canCompile(script: ScriptData): boolean { public canCompile(script: ScriptData): boolean {
if (!script) { throw new Error('undefined script'); }
if (!script.call) { if (!script.call) {
return false; return false;
} }
return true; return true;
} }
public compile(script: ScriptData): IScriptCode { public compile(script: ScriptData): IScriptCode {
this.ensureCompilable(script.call); if (!script) { throw new Error('undefined script'); }
const compiledCodes = new Array<ICompiledCode>(); try {
const calls = getCallSequence(script.call); const compiledCode = this.callCompiler.compileCall(script.call, this.functions);
calls.forEach((currentCall, currentCallIndex) => { return new ScriptCode(
ensureValidCall(currentCall, script.name); compiledCode.code,
const commonFunction = this.getFunctionByName(currentCall.function); compiledCode.revertCode,
let functionCode = compileCode(commonFunction, currentCall.parameters); this.syntax);
if (currentCallIndex !== calls.length - 1) { } catch (error) {
functionCode = appendLine(functionCode); throw Error(`Script "${script.name}" ${error.message}`);
}
compiledCodes.push(functionCode);
});
const scriptCode = merge(compiledCodes);
return new ScriptCode(scriptCode.code, scriptCode.revertCode, script.name, this.syntax);
}
private getFunctionByName(name: string): FunctionData {
const func = this.functions.find((f) => f.name === name);
if (!func) {
throw new Error(`called function is not defined "${name}"`);
}
return func;
}
private ensureCompilable(call: ScriptFunctionCallData) {
if (!this.functions || this.functions.length === 0) {
throw new Error('cannot compile without shared functions');
}
if (typeof call !== 'object') {
throw new Error('called function(s) must be an object');
} }
} }
} }
function getDuplicates(texts: readonly string[]): string[] {
return texts.filter((item, index) => texts.indexOf(item) !== index);
}
function printList(list: readonly string[]): string {
return `"${list.join('","')}"`;
}
function ensureNoDuplicatesInFunctionNames(functions: readonly FunctionData[]) {
const duplicateFunctionNames = getDuplicates(functions
.map((func) => func.name.toLowerCase()));
if (duplicateFunctionNames.length) {
throw new Error(`duplicate function name: ${printList(duplicateFunctionNames)}`);
}
}
function ensureNoUndefinedItem(functions: readonly FunctionData[]) {
if (functions.some((func) => !func)) {
throw new Error(`some functions are undefined`);
}
}
function ensureNoDuplicatesInParameterNames(functions: readonly FunctionData[]) {
const functionsWithParameters = functions
.filter((func) => func.parameters && func.parameters.length > 0);
for (const func of functionsWithParameters) {
const duplicateParameterNames = getDuplicates(func.parameters);
if (duplicateParameterNames.length) {
throw new Error(`"${func.name}": duplicate parameter name: ${printList(duplicateParameterNames)}`);
}
}
}
function ensureNoDuplicateCode(functions: readonly FunctionData[]) {
const duplicateCodes = getDuplicates(functions.map((func) => func.code));
if (duplicateCodes.length > 0) {
throw new Error(`duplicate "code" in functions: ${printList(duplicateCodes)}`);
}
const duplicateRevertCodes = getDuplicates(functions
.filter((func) => func.revertCode)
.map((func) => func.revertCode));
if (duplicateRevertCodes.length > 0) {
throw new Error(`duplicate "revertCode" in functions: ${printList(duplicateRevertCodes)}`);
}
}
function ensureValidFunctions(functions: readonly FunctionData[]) {
if (!functions || functions.length === 0) {
return;
}
ensureNoUndefinedItem(functions);
ensureNoDuplicatesInFunctionNames(functions);
ensureNoDuplicatesInParameterNames(functions);
ensureNoDuplicateCode(functions);
}
function appendLine(code: ICompiledCode): ICompiledCode {
const appendLineIfNotEmpty = (str: string) => str ? `${str}\n` : str;
return {
code: appendLineIfNotEmpty(code.code),
revertCode: appendLineIfNotEmpty(code.revertCode),
};
}
function merge(codes: readonly ICompiledCode[]): ICompiledCode {
return {
code: codes.map((code) => code.code).join(''),
revertCode: codes.map((code) => code.revertCode).join(''),
};
}
function compileCode(func: FunctionData, parameters: FunctionCallParametersData): ICompiledCode {
return {
code: compileExpressions(func.code, parameters),
revertCode: compileExpressions(func.revertCode, parameters),
};
}
function compileExpressions(code: string, parameters: FunctionCallParametersData): string {
let intermediateCode = generateIlCode(code);
intermediateCode = substituteParameters(intermediateCode, parameters);
return intermediateCode.compile();
}
function substituteParameters(intermediateCode: IILCode, parameters: FunctionCallParametersData): IILCode {
const parameterNames = intermediateCode.getUniqueParameterNames();
if (parameterNames.length && !parameters) {
throw new Error(`no parameters defined, expected: ${printList(parameterNames)}`);
}
for (const parameterName of parameterNames) {
const parameterValue = parameters[parameterName];
if (!parameterValue) {
throw Error(`parameter value is not provided for "${parameterName}" in function call`);
}
intermediateCode = intermediateCode.substituteParameter(parameterName, parameterValue);
}
return intermediateCode;
}
function ensureValidCall(call: FunctionCallData, scriptName: string) {
if (!call) {
throw new Error(`undefined function call in script "${scriptName}"`);
}
if (!call.function) {
throw new Error(`empty function name called in script "${scriptName}"`);
}
}
function getCallSequence(call: ScriptFunctionCallData): FunctionCallData[] {
if (call instanceof Array) {
return call as FunctionCallData[];
}
return [ call as FunctionCallData ];
}

View File

@@ -31,7 +31,7 @@ function parseCode(script: ScriptData, context: ICategoryCollectionParseContext)
if (context.compiler.canCompile(script)) { if (context.compiler.canCompile(script)) {
return context.compiler.compile(script); return context.compiler.compile(script);
} }
return new ScriptCode(script.code, script.revertCode, script.name, context.syntax); return new ScriptCode(script.code, script.revertCode, context.syntax);
} }
function ensureNotBothCallAndCode(script: ScriptData) { function ensureNotBothCallAndCode(script: ScriptData) {

View File

@@ -4,7 +4,7 @@ import { ScriptingDefinition } from '@/domain/ScriptingDefinition';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage'; import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { IProjectInformation } from '@/domain/IProjectInformation'; import { IProjectInformation } from '@/domain/IProjectInformation';
import { createEnumParser } from '../Common/Enum'; import { createEnumParser } from '../Common/Enum';
import { generateIlCode } from './Script/Compiler/ILCode'; import { generateIlCode } from './Script/Compiler/Expressions/ILCode';
export function parseScriptingDefinition( export function parseScriptingDefinition(
definition: ScriptingDefinitionData, definition: ScriptingDefinitionData,

View File

@@ -18,30 +18,33 @@ declare module 'js-yaml-loader!*' {
readonly docs?: DocumentationUrlsData; readonly docs?: DocumentationUrlsData;
} }
export interface FunctionData { export interface InstructionHolder {
name: string; readonly name: string;
code: string;
revertCode?: string; readonly code?: string;
parameters?: readonly string[]; readonly revertCode?: string;
readonly call?: ScriptFunctionCallData;
}
export interface FunctionData extends InstructionHolder {
readonly parameters?: readonly string[];
} }
export interface FunctionCallParametersData { export interface FunctionCallParametersData {
[index: string]: string; readonly [index: string]: string;
} }
export interface FunctionCallData { export interface FunctionCallData {
function: string; readonly function: string;
parameters?: FunctionCallParametersData; readonly parameters?: FunctionCallParametersData;
} }
export type ScriptFunctionCallData = readonly FunctionCallData[] | FunctionCallData | undefined; export type ScriptFunctionCallData = readonly FunctionCallData[] | FunctionCallData | undefined;
export interface ScriptData extends DocumentableData { export interface ScriptData extends InstructionHolder, DocumentableData {
name: string; readonly name: string;
code?: string; readonly recommend?: string;
revertCode?: string;
call: ScriptFunctionCallData;
recommend?: string;
} }
export interface ScriptingDefinitionData { export interface ScriptingDefinitionData {

View File

@@ -40,17 +40,315 @@ actions:
sudo rm -rfv /var/spool/cups/tmp/* sudo rm -rfv /var/spool/cups/tmp/*
sudo rm -rfv /var/spool/cups/cache/job.cache* sudo rm -rfv /var/spool/cups/cache/job.cache*
- -
name: Clear the list of iOS devices connected name: Empty trash on all volumes
recommend: strict recommend: strict
code: |- code: |-
sudo defaults delete /Users/$USER/Library/Preferences/com.apple.iPod.plist "conn:128:Last Connect" # on all mounted volumes
sudo defaults delete /Users/$USER/Library/Preferences/com.apple.iPod.plist Devices sudo rm -rfv /Volumes/*/.Trashes/* &>/dev/null
sudo defaults delete /Library/Preferences/com.apple.iPod.plist "conn:128:Last Connect" # on main HDD
sudo defaults delete /Library/Preferences/com.apple.iPod.plist Devices sudo rm -rfv ~/.Trash/* &>/dev/null
sudo rm -rfv /var/db/lockdown/*
- -
name: Reset privacy database (remove all permissions) name: Clear system cache files
code: sudo tccutil reset All recommend: strict
code: |-
sudo rm -rfv /Library/Caches/* &>/dev/null
sudo rm -rfv /System/Library/Caches/* &>/dev/null
sudo rm -rfv ~/Library/Caches/* &>/dev/null
-
name: Clear system log files
recommend: strict
code: |-
sudo rm -rfv /private/var/log/asl/*.asl &>/dev/null
sudo rm -rfv /Library/Logs/DiagnosticReports/* &>/dev/null
sudo rm -rfv /Library/Logs/Adobe/* &>/dev/null
rm -rfv ~/Library/Containers/com.apple.mail/Data/Library/Logs/Mail/* &>/dev/null
rm -rfv ~/Library/Logs/CoreSimulator/* &>/dev/null
sudo rm -rfv /var/log/*
-
category: Clear browser history
children:
-
category: Clear Google Chrome history
children:
-
name: Clear Google Chrome browsing history
code: |-
rm -rfv ~/Library/Application\ Support/Google/Chrome/Default/History &>/dev/null
rm -rfv ~/Library/Application\ Support/Google/Chrome/Default/History-journal &>/dev/null
-
name: Google Chrome Cache Files
code: sudo rm -rfv ~/Library/Application\ Support/Google/Chrome/Default/Application\ Cache/* &>/dev/null
-
category: Clear Safari history
children:
-
name: Clear Safari browsing history
code: |-
rm -f ~/Library/Safari/History.plist
rm -f ~/Library/Safari/HistoryIndex.sk
-
name: Clear Safari downloads history
code: rm -f ~/Library/Safari/Downloads.plist
-
name: Clear Safari top sites
code: rm -f ~/Library/Safari/TopSites.plist
-
name: Clear Safari last session history
code: rm -f ~/Library/Safari/LastSession.plist
-
name: Clear Safari caches
code: |-
rm -f ~/Library/Caches/com.apple.Safari/Cache.db
rm -f ~/Library/Safari/WebpageIcons.db
rm -rf ~/Library/Caches/com.apple.Safari/Webpage Previews
-
name: Clear copy of the Safari history
code: rm -rf ~/Library/Caches/Metadata/Safari/History
-
name: Clear search history embedded in Safari preferences
code: defaults write ~/Library/Preferences/com.apple.Safari RecentSearchStrings '( )'
-
name: Clear Safari cookies
code: rm -f ~/Library/Cookies/Cookies.plists
-
name: Clear Safari zoom level preferences per site
code: rm -f ~/Library/Safari/PerSiteZoomPreferences.plists
-
name: Clear URLs that are allowed to display notifications in Safari
code: rm -f ~/Library/Safari/UserNotificationPreferences.plist
-
name: Clear Safari per-site preferences for Downloads, Geolocation, PopUps, and Autoplays
code: rm -f ~/Library/Safari/PerSitePreferences.db
-
category: Clear Firefox history
children:
-
name: Clear Firefox cache
code: |-
sudo rm -rf ~/Library/Caches/Mozilla/
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/netpredictions.sqlite
-
name: Delete Firefox form history
code: |-
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/formhistory.sqlite
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/formhistory.dat
-
name: Delete Firefox site preferences
code: rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/content-prefs.sqlite
-
name: Delete Firefox session restore data (loads after the browser closes or crashes)
code: |-
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionCheckpoints.json
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore*.js*
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore.bak*
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore-backups/previous.js*
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore-backups/recovery.js*
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore-backups/recovery.bak*
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore-backups/previous.bak*
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore-backups/upgrade.js*-20*
-
name: Delete Firefox passwords
docs: http://kb.mozillazine.org/Password_Manager
code: |-
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/signons.txt
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/signons2.txt
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/signons3.txt
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/signons.sqlite
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/logins.json
-
name: Delete Firefox HTML5 cookies
code: rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/webappsstore.sqlite
-
name: Delete Firefox crash reports
code: |-
rm -rfv ~/Library/Application\ Support/Firefox/Crash\ Reports/
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/minidumps/*.dmp
-
name: Delete Firefox backup files
code: |-
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/bookmarkbackups/*.json
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/bookmarkbackups/*.jsonlz4
-
name: Delete Firefox cookies
code: |-
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/cookies.txt
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/cookies.sqlite
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/cookies.sqlite-shm
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/cookies.sqlite-wal
rm -rfv ~/Library/Application\ Support/Firefox/Profiles/*/storage/default/http*
-
category: Clear third party application data
children:
-
name: Clear Adobe cache
recommend: standard
code: sudo rm -rfv ~/Library/Application\ Support/Adobe/Common/Media\ Cache\ Files/* &>/dev/null
-
name: Clear Gradle cache
recommend: strict
code: |-
if [ -d "/Users/${HOST}/.gradle/caches" ]; then
rm -rfv ~/.gradle/caches/ &> /dev/null
fi
-
name: Clear Dropbox cache
recommend: standard
code: |-
if [ -d "/Users/${HOST}/Dropbox" ]; then
sudo rm -rfv ~/Dropbox/.dropbox.cache/* &>/dev/null
fi
-
name: Clear Google Drive file stream cache
recommend: standard
code: |-
killall "Google Drive File Stream"
rm -rfv ~/Library/Application\ Support/Google/DriveFS/[0-9a-zA-Z]*/content_cache &>/dev/null
-
name: Clear Composer cache
recommend: strict
code: |-
if type "composer" &> /dev/null; then
composer clearcache &> /dev/null
fi
-
name: Clear Homebrew cache
recommend: strict
code: |-
if type "brew" &>/dev/null; then
brew cleanup -s &>/dev/null
rm -rfv $(brew --cache) &>/dev/null
brew tap --repair &>/dev/null
fi
-
name: Clear any old versions of Ruby gems
recommend: strict
code: |-
if type "gem" &> /dev/null; then
gem cleanup &>/dev/null
fi
-
name: Clear Docker
recommend: strict
code: |-
if type "docker" &> /dev/null; then
docker system prune -af
fi
-
name: Clear Pyenv-VirtualEnv cache
recommend: strict
code: |-
if [ "$PYENV_VIRTUALENV_CACHE_PATH" ]; then
rm -rfv $PYENV_VIRTUALENV_CACHE_PATH &>/dev/null
fi
-
name: Clear NPM cache
recommend: strict
code: |-
if type "npm" &> /dev/null; then
npm cache clean --force
fi
-
name: Clear Yarn cache
recommend: strict
code: |-
if type "yarn" &> /dev/null; then
echo 'Cleanup Yarn Cache...'
yarn cache clean --force
fi
-
category: iOS Cleanup
children:
-
name: Clear iOS applications
recommend: strict
code: rm -rfv ~/Music/iTunes/iTunes\ Media/Mobile\ Applications/* &>/dev/null
-
name: Clear iOS photo caches
recommend: standard
code: rm -rf ~/Pictures/iPhoto\ Library/iPod\ Photo\ Cache/*
-
name: Remove iOS Device Backups
recommend: strict
code: rm -rfv ~/Library/Application\ Support/MobileSync/Backup/* &>/dev/null
-
name: Clear iOS Simulators
recommend: strict
code: |-
if type "xcrun" &>/dev/null; then
osascript -e 'tell application "com.apple.CoreSimulator.CoreSimulatorService" to quit'
osascript -e 'tell application "iOS Simulator" to quit'
osascript -e 'tell application "Simulator" to quit'
xcrun simctl shutdown all
xcrun simctl erase all
fi
-
name: Clear the list of iOS devices connected
recommend: strict
code: |-
sudo defaults delete /Users/$USER/Library/Preferences/com.apple.iPod.plist "conn:128:Last Connect"
sudo defaults delete /Users/$USER/Library/Preferences/com.apple.iPod.plist Devices
sudo defaults delete /Library/Preferences/com.apple.iPod.plist "conn:128:Last Connect"
sudo defaults delete /Library/Preferences/com.apple.iPod.plist Devices
sudo rm -rfv /var/db/lockdown/*
-
name: Clear XCode Derived Data and Archives
recommend: strict
code: |-
rm -rfv ~/Library/Developer/Xcode/DerivedData/* &>/dev/null
rm -rfv ~/Library/Developer/Xcode/Archives/* &>/dev/null
rm -rfv ~/Library/Developer/Xcode/iOS Device Logs/* &>/dev/null
-
name: Clear DNS cache
recommend: standard
code: |-
sudo dscacheutil -flushcache
sudo killall -HUP mDNSResponder
-
name: Purge inactive memory
recommend: standard
code: sudo purge
-
category: Reset privacy permissions for all applications
children:
-
name: Reset camera permissions
code: tccutil reset Camera
-
name: Reset microphone permissions
code: tccutil reset Microphone
-
name: Reset accessibility permissions
code: tccutil reset Accessibility
-
name: Reset screen capture permissions
code: tccutil reset ScreenCapture
-
name: Reset reminders permissions
code: tccutil reset Reminders
-
name: Reset photos permissions
code: tccutil reset Photos
-
name: Reset calendar permissions
code: tccutil reset Calendar
-
name: Reset full disk access permissions
code: tccutil reset SystemPolicyAllFiles
-
name: Reset contacts permissions
code: tccutil reset SystemPolicyAllFiles
-
name: Reset desktop folder permissions
code: tccutil reset SystemPolicyDesktopFolder
-
name: Reset documents folder permissions
code: tccutil reset SystemPolicyDocumentsFolder
-
name: Reset downloads permissions
code: tccutil reset SystemPolicyDownloadsFolder
-
name: Reset all app permissions
code: tccutil reset All
- -
category: Configure programs category: Configure programs
children: children:

View File

@@ -476,26 +476,62 @@ actions:
schtasks /change /TN "\Microsoft\Windows\Customer Experience Improvement Program\KernelCeipTask" /ENABLE schtasks /change /TN "\Microsoft\Windows\Customer Experience Improvement Program\KernelCeipTask" /ENABLE
schtasks /change /TN "\Microsoft\Windows\Customer Experience Improvement Program\UsbCeip" /ENABLE schtasks /change /TN "\Microsoft\Windows\Customer Experience Improvement Program\UsbCeip" /ENABLE
- -
name: Disable Webcam Telemetry (devicecensus.exe) category: Disable Webcam Telemetry (devicecensus.exe)
recommend: standard docs:
docs: https://www.ghacks.net/2019/09/23/what-is-devicecensus-exe-on-windows-10-and-why-does-it-need-internet-connectivity/ - https://www.ghacks.net/2019/09/23/what-is-devicecensus-exe-on-windows-10-and-why-does-it-need-internet-connectivity/
code: schtasks /change /TN "Microsoft\Windows\Device Information\Device" /DISABLE - https://answers.microsoft.com/en-us/windows/forum/windows_10-security/devicecensusexe-and-host-process-for-windows-task/520d42a2-45c1-402a-81de-e1116ecf2538
revertCode: schtasks /change /TN "Microsoft\Windows\Device Information\Device" /ENABLE children:
-
name: Disable devicecensus.exe (telemetry) task
recommend: standard
code: schtasks /change /TN "Microsoft\Windows\Device Information\Device" /disable
revertCode: schtasks /change /TN "Microsoft\Windows\Device Information\Device" /enable
-
name: Disable devicecensus.exe (telemetry) process
recommend: standard
call:
function: KillProcessWhenItStarts
parameters:
processName: DeviceCensus.exe
- -
name: Disable Application Experience (Compatibility Telemetry) category: Disable Compatibility Telemetry (Application Experience)
recommend: standard children:
code: |- -
schtasks /change /TN "Microsoft\Windows\Application Experience\Microsoft Compatibility Appraiser" /DISABLE category: Disable Microsoft Compatibility Appraiser
schtasks /change /TN "Microsoft\Windows\Application Experience\ProgramDataUpdater" /DISABLE docs: https://www.ghacks.net/2016/10/26/turn-off-the-windows-customer-experience-program/
schtasks /change /TN "Microsoft\Windows\Application Experience\StartupAppTask" /DISABLE children:
schtasks /change /TN "Microsoft\Windows\Application Experience\AitAgent" /DISABLE -
reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\CompatTelRunner.exe" /v "Debugger" /t REG_SZ /d "%windir%\System32\taskkill.exe" /f name: Disable Microsoft Compatibility Appraiser task
revertCode: |- recommend: standard
schtasks /change /TN "Microsoft\Windows\Application Experience\Microsoft Compatibility Appraiser" /ENABLE code: schtasks /change /TN "Microsoft\Windows\Application Experience\Microsoft Compatibility Appraiser" /disable
schtasks /change /TN "Microsoft\Windows\Application Experience\ProgramDataUpdater" /ENABLE revertCode: schtasks /change /TN "Microsoft\Windows\Application Experience\Microsoft Compatibility Appraiser" /enable
schtasks /change /TN "Microsoft\Windows\Application Experience\StartupAppTask" /ENABLE -
schtasks /change /TN "Microsoft\Windows\Application Experience\AitAgent" /ENABLE name: Disable CompatTelRunner.exe (Microsoft Compatibility Appraiser) process
reg delete "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\CompatTelRunner.exe" /v "Debugger" /f recommend: standard
call:
function: KillProcessWhenItStarts
parameters:
processName: CompatTelRunner.exe
-
name: Disable sending information to Customer Experience Improvement Program
recommend: standard
docs:
- https://www.ghacks.net/2016/10/26/turn-off-the-windows-customer-experience-program/
- https://answers.microsoft.com/en-us/windows/forum/windows_10-performance/permanently-disabling-windows-compatibility/6bf71583-81b0-4a74-ae2e-8fd73305aad1
code: schtasks /change /TN "Microsoft\Windows\Application Experience\ProgramDataUpdater" /disable
revertCode: schtasks /change /TN "Microsoft\Windows\Application Experience\ProgramDataUpdater" /enable
-
name: Disable Application Impact Telemetry Agent task
recommend: standard
docs: https://www.shouldiblockit.com/aitagent.exe-6181.aspx
code: schtasks /change /TN "Microsoft\Windows\Application Experience\AitAgent" /disable
revertCode: schtasks /change /TN "Microsoft\Windows\Application Experience\AitAgent" /enable
-
name: Disable "Disable apps to improve performance" reminder
recommend: strict
docs: https://www.ghacks.net/2016/10/26/turn-off-the-windows-customer-experience-program/
code: schtasks /change /TN "Microsoft\Windows\Application Experience\StartupAppTask" /disable
revertCode: schtasks /change /TN "Microsoft\Windows\Application Experience\StartupAppTask" /enable
- -
name: Disable telemetry in data collection policy name: Disable telemetry in data collection policy
recommend: standard recommend: standard
@@ -1001,6 +1037,34 @@ actions:
recommend: standard recommend: standard
code: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\bluetoothSync" /v "Value" /d "Deny" /t REG_SZ /f code: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\bluetoothSync" /v "Value" /d "Deny" /t REG_SZ /f
revertCode: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\bluetoothSync" /v "Value" /d "Allow" /t REG_SZ /f revertCode: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\bluetoothSync" /v "Value" /d "Allow" /t REG_SZ /f
-
category: Disable app access to voice activation
docs: https://www.tenforums.com/tutorials/130122-allow-deny-apps-access-use-voice-activation-windows-10-a.html
children:
-
name: Disable apps and Cortana to activate with voice
recommend: standard
docs: https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.AppPrivacy::LetAppsActivateWithVoice
code: |-
reg add "HKCU\Software\Microsoft\Speech_OneCore\Settings\VoiceActivation\UserPreferenceForAllApps" /v "AgentActivationEnabled" /t REG_DWORD /d 0 /f
:: Using GPO (re-activation through GUI is not possible)
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\AppPrivacy" /v "LetAppsActivateWithVoice" /t REG_DWORD /d 2 /f
revertCode: |-
reg add "HKCU\Software\Microsoft\Speech_OneCore\Settings\VoiceActivation\UserPreferenceForAllApps" /v "AgentActivationEnabled" /t REG_DWORD /d 1 /f
:: Using GPO
reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\AppPrivacy" /v "LetAppsActivateWithVoice" /f
-
name: Disable apps and Cortana to activate with voice when sytem is locked
recommend: standard
docs: https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.AppPrivacy::LetAppsActivateWithVoiceAboveLock
code: |-
reg add "HKCU\Software\Microsoft\Speech_OneCore\Settings\VoiceActivation\UserPreferenceForAllApps" /v "AgentActivationOnLockScreenEnabled" /t REG_DWORD /d 0 /f
:: Using GPO (re-activation through GUI is not possible)
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\AppPrivacy" /v "LetAppsActivateWithVoiceAboveLock" /t REG_DWORD /d 2 /f
revertCode: |-
reg add "HKCU\Software\Microsoft\Speech_OneCore\Settings\VoiceActivation\UserPreferenceForAllApps" /v "AgentActivationOnLockScreenEnabled" /t REG_DWORD /d 1 /f
:: Using GPO
reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\AppPrivacy" /v "LetAppsActivateWithVoiceAboveLock" /f
- -
category: Disable location access category: Disable location access
children: children:
@@ -1081,11 +1145,61 @@ actions:
revertCode: |- revertCode: |-
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "CortanaEnabled" /t REG_DWORD /d 1 /f reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "CortanaEnabled" /t REG_DWORD /d 1 /f
reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "CortanaEnabled" /t REG_DWORD /d 1 /f reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "CortanaEnabled" /t REG_DWORD /d 1 /f
-
category: Disable Cortana history
children:
-
name: Prevent Cortana from displaying history
recommend: standard
code: reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "HistoryViewEnabled" /t REG_DWORD /d 0 /f
revertCode: reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "HistoryViewEnabled" /f
-
name: Prevent Cortana from using device history
recommend: standard
code: reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "DeviceHistoryEnabled" /t REG_DWORD /d 0 /f
revertCode: reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "DeviceHistoryEnabled" /f
- -
name: Remove the Cortana taskbar icon name: Remove the Cortana taskbar icon
recommend: standard recommend: standard
code: reg add HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced /v "ShowCortanaButton" /t REG_DWORD /d 0 /f code: reg add HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced /v "ShowCortanaButton" /t REG_DWORD /d 0 /f
revertCode: reg delete HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced /v "ShowCortanaButton" /f revertCode: reg delete HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced /v "ShowCortanaButton" /f
-
name: Disable Cortana in ambient mode
recommend: standard
code: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "CortanaInAmbientMode" /t REG_DWORD /d 0 /f
revertCode: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "CortanaInAmbientMode" /t REG_DWORD /d 1 /f
-
category: Disable Cortana voice listening
children:
-
name: Disable "Hey Cortana" voice activation
recommend: standard
code: |-
reg add "HKCU\Software\Microsoft\Speech_OneCore\Preferences" /v "VoiceActivationOn" /t REG_DWORD /d 0 /f
reg add "HKLM\Software\Microsoft\Speech_OneCore\Preferences" /v "VoiceActivationDefaultOn" /t REG_DWORD /d 0 /f
revertCode: |-
reg add "HKCU\Software\Microsoft\Speech_OneCore\Preferences" /v "VoiceActivationOn" /t REG_DWORD /d 1 /f
reg add "HKLM\Software\Microsoft\Speech_OneCore\Preferences" /v "VoiceActivationDefaultOn" /t REG_DWORD /d 1 /f
-
name: Disable Cortana listening to commands on Windows key + C
recommend: standard
code: reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Search" /v "VoiceShortcut" /t REG_DWORD /d 0 /f
revertCode: reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Search" /v "VoiceShortcut" /t REG_DWORD /d 1 /f
-
name: Disable using Cortana even when device is locked
recommend: standard
code: reg add "HKCU\Software\Microsoft\Speech_OneCore\Preferences" /v "VoiceActivationEnableAboveLockscreen" /t REG_DWORD /d 0 /f
revertCode: reg add "HKCU\Software\Microsoft\Speech_OneCore\Preferences" /v "VoiceActivationEnableAboveLockscreen" /t REG_DWORD /d 1 /f
-
name: Disable automatic update of Speech Data
recommend: standard
code: reg add "HKCU\Software\Microsoft\Speech_OneCore\Preferences" /v "ModelDownloadAllowed" /t REG_DWORD /d 0 /f
revertCode: reg delete "HKCU\Software\Microsoft\Speech_OneCore\Preferences" /v "ModelDownloadAllowed" /f
-
name: Disable Cortana voice support during Windows setup
recommend: standard
code: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE" /v "DisableVoice" /t REG_DWORD /d 1 /f
revertCode: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE" /v "DisableVoice" /f
- -
category: Configure Windows search indexing category: Configure Windows search indexing
children: children:
@@ -1467,7 +1581,7 @@ actions:
name: Delete NVIDIA residual telemetry files name: Delete NVIDIA residual telemetry files
recommend: standard recommend: standard
code: |- code: |-
del /s %systemdrive%\System32\DriverStore\FileRepository\NvTelemetry*.dll del /s %SystemRoot%\System32\DriverStore\FileRepository\NvTelemetry*.dll
rmdir /s /q "%ProgramFiles(x86)%\NVIDIA Corporation\NvTelemetry" 2>nul rmdir /s /q "%ProgramFiles(x86)%\NVIDIA Corporation\NvTelemetry" 2>nul
rmdir /s /q "%ProgramFiles%\NVIDIA Corporation\NvTelemetry" 2>nul rmdir /s /q "%ProgramFiles%\NVIDIA Corporation\NvTelemetry" 2>nul
- -
@@ -1508,45 +1622,72 @@ actions:
name: Disable Visual Studio Code telemetry name: Disable Visual Studio Code telemetry
docs: https://code.visualstudio.com/docs/getstarted/telemetry docs: https://code.visualstudio.com/docs/getstarted/telemetry
recommend: standard recommend: standard
code: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | Out-String | ConvertFrom-Json; $json | Add-Member -Type NoteProperty -Name 'telemetry.enableTelemetry' -Value $false -Force; $json | ConvertTo-Json | Set-Content $jsonfile;" call:
revertCode: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | ConvertFrom-Json; $json.PSObject.Properties.Remove('telemetry.enableTelemetry'); $json | ConvertTo-Json | Set-Content $jsonfile;" function: SetVsCodeSetting
parameters:
setting: telemetry.enableTelemetry
powerShellValue: $false
- -
name: Disable Visual Studio Code crash reporting name: Disable Visual Studio Code crash reporting
docs: https://code.visualstudio.com/docs/getstarted/telemetry docs: https://code.visualstudio.com/docs/getstarted/telemetry
recommend: standard recommend: standard
code: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | Out-String | ConvertFrom-Json; $json | Add-Member -Type NoteProperty -Name 'telemetry.enableCrashReporter' -Value $false -Force; $json | ConvertTo-Json | Set-Content $jsonfile;" call:
revertCode: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | ConvertFrom-Json;$json.PSObject.Properties.Remove('telemetry.enableCrashReporter'); $json | ConvertTo-Json | Set-Content $jsonfile;" function: SetVsCodeSetting
parameters:
setting: telemetry.enableCrashReporter
powerShellValue: $false
- -
name: Do not run Microsoft online experiments name: Do not run Microsoft online experiments
docs: https://github.com/Microsoft/vscode/blob/1aee0c194cff72d179b9f8ef324e47f34555a07d/src/vs/workbench/contrib/experiments/node/experimentService.ts#L173 docs: https://github.com/Microsoft/vscode/blob/1aee0c194cff72d179b9f8ef324e47f34555a07d/src/vs/workbench/contrib/experiments/node/experimentService.ts#L173
recommend: standard recommend: standard
code: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | Out-String | ConvertFrom-Json; $json | Add-Member -Type NoteProperty -Name 'workbench.enableExperiments' -Value $false -Force; $json | ConvertTo-Json | Set-Content $jsonfile;" call:
revertCode: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | ConvertFrom-Json;$json.PSObject.Properties.Remove('workbench.enableExperiments'); $json | ConvertTo-Json | Set-Content $jsonfile;" function: SetVsCodeSetting
parameters:
setting: workbench.enableExperiments
powerShellValue: $false
- -
name: Choose manual updates over automatic updates name: Choose manual updates over automatic updates
docs: https://github.com/Microsoft/vscode/blob/1aee0c194cff72d179b9f8ef324e47f34555a07d/src/vs/workbench/contrib/experiments/node/experimentService.ts#L173 call:
code: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | Out-String | ConvertFrom-Json; $json | Add-Member -Type NoteProperty -Name 'update.mode' -Value \"manual\" -Force; $json | ConvertTo-Json | Set-Content $jsonfile;" function: SetVsCodeSetting
revertCode: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | ConvertFrom-Json;$json.PSObject.Properties.Remove('update.mode'); $json | ConvertTo-Json | Set-Content $jsonfile;" parameters:
setting: update.mode
powerShellValue: >-
'manual'
- -
name: Show Release Notes from Microsoft online service after an update name: Show Release Notes from Microsoft online service after an update
code: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | Out-String | ConvertFrom-Json; $json | Add-Member -Type NoteProperty -Name 'update.showReleaseNotes' -Value $false -Force; $json | ConvertTo-Json | Set-Content $jsonfile;" call:
revertCode: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | ConvertFrom-Json;$json.PSObject.Properties.Remove('update.showReleaseNotes'); $json | ConvertTo-Json | Set-Content $jsonfile;" function: SetVsCodeSetting
parameters:
setting: update.showReleaseNotes
powerShellValue: $false
- -
name: Automatically check extensions from Microsoft online service name: Automatically check extensions from Microsoft online service
code: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | Out-String | ConvertFrom-Json; $json | Add-Member -Type NoteProperty -Name 'extensions.autoCheckUpdates' -Value $false -Force; $json | ConvertTo-Json | Set-Content $jsonfile;" call:
revertCode: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | ConvertFrom-Json;$json.PSObject.Properties.Remove('extensions.autoCheckUpdates'); $json | ConvertTo-Json | Set-Content $jsonfile;" function: SetVsCodeSetting
parameters:
setting: extensions.autoCheckUpdates
powerShellValue: $false
- -
name: Fetch recommendations from a Microsoft online service name: Fetch recommendations from Microsoft only on demand
code: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | Out-String | ConvertFrom-Json; $json | Add-Member -Type NoteProperty -Name 'extensions.showRecommendationsOnlyOnDemand' -Value $true -Force; $json | ConvertTo-Json | Set-Content $jsonfile;" call:
revertCode: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | ConvertFrom-Json;$json.PSObject.Properties.Remove('extensions.showRecommendationsOnlyOnDemand'); $json | ConvertTo-Json | Set-Content $jsonfile;" function: SetVsCodeSetting
parameters:
setting: extensions.showRecommendationsOnlyOnDemand
powerShellValue: $true
- -
name: Automatically fetch git commits from remote repository name: Automatically fetch git commits from remote repository
code: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | Out-String | ConvertFrom-Json; $json | Add-Member -Type NoteProperty -Name 'git.autofetch' -Value $false -Force; $json | ConvertTo-Json | Set-Content $jsonfile;" call:
revertCode: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | ConvertFrom-Json;$json.PSObject.Properties.Remove('git.autofetch'); $json | ConvertTo-Json | Set-Content $jsonfile;" function: SetVsCodeSetting
parameters:
setting: git.autofetch
powerShellValue: $false
- -
name: Fetch package information from NPM and Bower name: Fetch package information from NPM and Bower
code: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | Out-String | ConvertFrom-Json; $json | Add-Member -Type NoteProperty -Name 'npm.fetchOnlinePackageInfo' -Value $false -Force; $json | ConvertTo-Json | Set-Content $jsonfile;" call:
revertCode: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | ConvertFrom-Json;$json.PSObject.Properties.Remove('npm.fetchOnlinePackageInfo'); $json | ConvertTo-Json | Set-Content $jsonfile;" function: SetVsCodeSetting
parameters:
setting: npm.fetchOnlinePackageInfo
powerShellValue: $false
- -
category: Disable Microsoft Office telemetry category: Disable Microsoft Office telemetry
docs: https://docs.microsoft.com/en-us/deployoffice/compat/manage-the-privacy-of-data-monitored-by-telemetry-in-office docs: https://docs.microsoft.com/en-us/deployoffice/compat/manage-the-privacy-of-data-monitored-by-telemetry-in-office
@@ -2692,8 +2833,19 @@ actions:
- -
name: Disable NetBios for all interfaces name: Disable NetBios for all interfaces
docs: https://10dsecurity.com/saying-goodbye-netbios/ docs: https://10dsecurity.com/saying-goodbye-netbios/
code: Powershell -Command "$key = 'HKLM:SYSTEM\CurrentControlSet\services\NetBT\Parameters\Interfaces'; Get-ChildItem $key | foreach { Set-ItemProperty -Path \"$key\$($_.pschildname)\" -Name NetbiosOptions -Value 2 -Verbose}" call:
revertCode: Powershell -Command "$key = 'HKLM:SYSTEM\CurrentControlSet\services\NetBT\Parameters\Interfaces'; Get-ChildItem $key | foreach { Set-ItemProperty -Path \"$key\$($_.pschildname)\" -Name NetbiosOptions -Value 0 -Verbose}" function: RunPowerShell
parameters:
code:
$key = 'HKLM:SYSTEM\CurrentControlSet\services\NetBT\Parameters\Interfaces';
Get-ChildItem $key | foreach {
Set-ItemProperty -Path \"$key\$($_.pschildname)\" -Name NetbiosOptions -Value 2 -Verbose
}
revertCode:
$key = 'HKLM:SYSTEM\CurrentControlSet\services\NetBT\Parameters\Interfaces';
Get-ChildItem $key | foreach {
Set-ItemProperty -Path \"$key\$($_.pschildname)\" -Name NetbiosOptions -Value 0 -Verbose
}
- -
category: Remove bloatware category: Remove bloatware
children: children:
@@ -3459,12 +3611,13 @@ actions:
function: UninstallSystemApp function: UninstallSystemApp
parameters: parameters:
packageName: Microsoft.Windows.SecureAssessmentBrowser packageName: Microsoft.Windows.SecureAssessmentBrowser
- # -
name: Start app # # Not a bloatware, required for different setting windows such as WiFi and battery panes in action bar
call: # name: Start app
function: UninstallSystemApp # call:
parameters: # function: UninstallSystemApp
packageName: Microsoft.Windows.ShellExperienceHost # parameters:
# packageName: Microsoft.Windows.ShellExperienceHost
- -
category: Windows Feedback category: Windows Feedback
children: children:
@@ -3496,12 +3649,13 @@ actions:
function: UninstallSystemApp function: UninstallSystemApp
parameters: parameters:
packageName: Windows.ContactSupport packageName: Windows.ContactSupport
- # -
name: Settings app # # Not a bloatware, required for core OS functinoality
call: # name: Settings app
function: UninstallSystemApp # call:
parameters: # function: UninstallSystemApp
packageName: Windows.immersivecontrolpanel # parameters:
# packageName: Windows.immersivecontrolpanel
- -
name: Windows Print 3D app name: Windows Print 3D app
call: call:
@@ -4138,67 +4292,81 @@ actions:
copy "%~dpnx0" "%AppData%\Microsoft\Windows\Start Menu\Programs\Startup\privacy-cleanup.bat" copy "%~dpnx0" "%AppData%\Microsoft\Windows\Start Menu\Programs\Startup\privacy-cleanup.bat"
revertCode: del /f /q %AppData%\Microsoft\Windows\Start Menu\Programs\Startup\privacy-cleanup.bat revertCode: del /f /q %AppData%\Microsoft\Windows\Start Menu\Programs\Startup\privacy-cleanup.bat
functions: functions:
-
name: KillProcessWhenItStarts
parameters: [ processName ]
# https://docs.microsoft.com/en-us/previous-versions/windows/desktop/xperf/image-file-execution-options
code: reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\'{{ $processName }}'" /v "Debugger" /t REG_SZ /d "%windir%\System32\taskkill.exe" /f
revertCode: reg delete "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\'{{ $processName }}'" /v "Debugger" /f
- -
name: UninstallStoreApp name: UninstallStoreApp
parameters: [ packageName ] parameters: [ packageName ]
code: PowerShell -Command "Get-AppxPackage '{{ $packageName }}' | Remove-AppxPackage" call:
revertCode: function: RunPowerShell
PowerShell -ExecutionPolicy Unrestricted -Command " parameters:
$package = Get-AppxPackage -AllUsers '{{ $packageName }}'; code: Get-AppxPackage '{{ $packageName }}' | Remove-AppxPackage
if (!$package) { revertCode:
Write-Error \"Cannot reinstall '{{ $packageName }}'\" -ErrorAction Stop $package = Get-AppxPackage -AllUsers '{{ $packageName }}';
} if (!$package) {
$manifest = $package.InstallLocation + '\AppxManifest.xml'; Write-Error \"Cannot reinstall '{{ $packageName }}'\" -ErrorAction Stop
Add-AppxPackage -DisableDevelopmentMode -Register \"$manifest\" " }
$manifest = $package.InstallLocation + '\AppxManifest.xml';
Add-AppxPackage -DisableDevelopmentMode -Register \"$manifest\"
- -
name: UninstallSystemApp name: UninstallSystemApp
parameters: [ packageName ] parameters: [ packageName ]
# It simply renames files # It simply renames files
# Because system apps are non removable (check: (Get-AppxPackage -AllUsers 'Windows.CBSPreview').NonRemovable) # Because system apps are non removable (check: (Get-AppxPackage -AllUsers 'Windows.CBSPreview').NonRemovable)
# Otherwise they throw 0x80070032 when trying to uninstall them # Otherwise they throw 0x80070032 when trying to uninstall them
code: call:
PowerShell -Command " function: RunPowerShell
$package = (Get-AppxPackage -AllUsers '{{ $packageName }}'); parameters:
if (!$package) { code:
Write-Host 'Not installed'; $package = (Get-AppxPackage -AllUsers '{{ $packageName }}');
exit 0; if (!$package) {
} Write-Host 'Not installed';
$directories = @($package.InstallLocation, \"$env:LOCALAPPDATA\Packages\$($package.PackageFamilyName)\"); exit 0;
foreach($dir in $directories) { }
if ( !$dir -Or !(Test-Path \"$dir\") ) { continue; } $directories = @($package.InstallLocation, \"$env:LOCALAPPDATA\Packages\$($package.PackageFamilyName)\");
cmd /c ('takeown /f \"' + $dir + '\" /r /d y 1> nul'); if($LASTEXITCODE) { throw 'Failed to take ownership'; } foreach($dir in $directories) {
cmd /c ('icacls \"' + $dir + '\" /grant administrators:F /t 1> nul'); if($LASTEXITCODE) { throw 'Failed to take ownership'; } if ( !$dir -Or !(Test-Path \"$dir\") ) { continue; }
$files = Get-ChildItem -File -Path $dir -Recurse -Force; cmd /c ('takeown /f \"' + $dir + '\" /r /d y 1> nul'); if($LASTEXITCODE) { throw 'Failed to take ownership'; }
foreach($file in $files) { cmd /c ('icacls \"' + $dir + '\" /grant administrators:F /t 1> nul'); if($LASTEXITCODE) { throw 'Failed to take ownership'; }
if($file.Name.EndsWith('.OLD')) { continue; } $files = Get-ChildItem -File -Path $dir -Recurse -Force;
$newName = $file.FullName + '.OLD'; foreach($file in $files) {
Write-Host \"Rename '$($file.FullName)' to '$newName'\"; if($file.Name.EndsWith('.OLD')) { continue; }
Move-Item -LiteralPath \"$($file.FullName)\" -Destination \"$newName\" -Force; $newName = $file.FullName + '.OLD';
} Write-Host \"Rename '$($file.FullName)' to '$newName'\";
};" Move-Item -LiteralPath \"$($file.FullName)\" -Destination \"$newName\" -Force;
revertCode: }
PowerShell -Command " }
$package = (Get-AppxPackage -AllUsers '{{ $packageName }}'); revertCode:
if (!$package) { $package = (Get-AppxPackage -AllUsers '{{ $packageName }}');
Write-Error 'App could not be found' -ErrorAction Stop; if (!$package) {
} Write-Error 'App could not be found' -ErrorAction Stop;
$directories = @($package.InstallLocation, \"$env:LOCALAPPDATA\Packages\$($package.PackageFamilyName)\"); }
foreach($dir in $directories) { $directories = @($package.InstallLocation, \"$env:LOCALAPPDATA\Packages\$($package.PackageFamilyName)\");
if ( !$dir -Or !(Test-Path \"$dir\") ) { continue; } foreach($dir in $directories) {
cmd /c ('takeown /f \"' + $dir + '\" /r /d y 1> nul'); if($LASTEXITCODE) { throw 'Failed to take ownership'; } if ( !$dir -Or !(Test-Path \"$dir\") ) { continue; }
cmd /c ('icacls \"' + $dir + '\" /grant administrators:F /t 1> nul'); if($LASTEXITCODE) { throw 'Failed to take ownership'; } cmd /c ('takeown /f \"' + $dir + '\" /r /d y 1> nul'); if($LASTEXITCODE) { throw 'Failed to take ownership'; }
$files = Get-ChildItem -File -Path \"$dir\*.OLD\" -Recurse -Force; cmd /c ('icacls \"' + $dir + '\" /grant administrators:F /t 1> nul'); if($LASTEXITCODE) { throw 'Failed to take ownership'; }
foreach($file in $files) { $files = Get-ChildItem -File -Path \"$dir\*.OLD\" -Recurse -Force;
$newName = $file.FullName.Substring(0, $file.FullName.Length - 4); foreach($file in $files) {
Write-Host \"Rename '$($file.FullName)' to '$newName'\"; $newName = $file.FullName.Substring(0, $file.FullName.Length - 4);
Move-Item -LiteralPath \"$($file.FullName)\" -Destination \"$newName\" -Force; Write-Host \"Rename '$($file.FullName)' to '$newName'\";
} Move-Item -LiteralPath \"$($file.FullName)\" -Destination \"$newName\" -Force;
};" }
}
- -
name: UninstallCapability name: UninstallCapability
parameters: [ capabilityName ] parameters: [ capabilityName ]
code: PowerShell -Command "Get-WindowsCapability -Online -Name '{{ $capabilityName }}*' | Remove-WindowsCapability -Online" call:
revertCode: PowerShell -Command "$capability = Get-WindowsCapability -Online -Name '{{ $capabilityName }}*'; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: RunPowerShell
parameters:
code: Get-WindowsCapability -Online -Name '{{ $capabilityName }}*' | Remove-WindowsCapability -Online
revertCode:
$capability = Get-WindowsCapability -Online -Name '{{ $capabilityName }}*';
Add-WindowsCapability -Name \"$capability.Name\" -Online
- -
name: RenameSystemFile name: RenameSystemFile
parameters: [ filePath ] parameters: [ filePath ]
@@ -4220,3 +4388,31 @@ functions:
) else ( ) else (
echo Could not find backup file "{{ $filePath }}.OLD" 1>&2 echo Could not find backup file "{{ $filePath }}.OLD" 1>&2
) )
-
name: SetVsCodeSetting
parameters: [ setting, powerShellValue ]
call:
function: RunPowerShell
parameters:
code:
$jsonfile = \"$env:APPDATA\Code\User\settings.json\";
if (!(Test-Path $jsonfile -PathType Leaf)) {
Write-Host \"No updates. Settings file was not at $jsonfile\";
exit 0;
}
$json = Get-Content $jsonfile | Out-String | ConvertFrom-Json;
$json | Add-Member -Type NoteProperty -Name '{{ $setting }}' -Value {{ $powerShellValue }} -Force;
$json | ConvertTo-Json | Set-Content $jsonfile;
revertCode:
$jsonfile = \"$env:APPDATA\Code\User\settings.json\";
if (!(Test-Path $jsonfile -PathType Leaf)) {
Write-Error \"Settings file could not be found at $jsonfile\" -ErrorAction Stop;
}
$json = Get-Content $jsonfile | ConvertFrom-Json;
$json.PSObject.Properties.Remove('{{ $setting }}');
$json | ConvertTo-Json | Set-Content $jsonfile;
-
name: RunPowerShell
parameters: [ code, revertCode ]
code: PowerShell -ExecutionPolicy Unrestricted -Command "{{ $code }}"
revertCode: PowerShell -ExecutionPolicy Unrestricted -Command "{{ $revertCode }}"

View File

@@ -4,16 +4,17 @@ export class ScriptCode implements IScriptCode {
constructor( constructor(
public readonly execute: string, public readonly execute: string,
public readonly revert: string, public readonly revert: string,
scriptName: string,
syntax: ILanguageSyntax) { syntax: ILanguageSyntax) {
if (!scriptName) { throw new Error('script name is undefined'); } if (!syntax) { throw new Error('undefined syntax'); }
if (!syntax) { throw new Error('syntax is undefined'); } validateCode(execute, syntax);
validateCode(scriptName, execute, syntax);
if (revert) { if (revert) {
scriptName = `${scriptName} (revert)`; try {
validateCode(scriptName, revert, syntax); validateCode(revert, syntax);
if (execute === revert) { if (execute === revert) {
throw new Error(`${scriptName}: Code itself and its reverting code cannot be the same`); throw new Error(`Code itself and its reverting code cannot be the same`);
}
} catch (err) {
throw Error(`(revert): ${err.message}`);
} }
} }
} }
@@ -24,21 +25,21 @@ export interface ILanguageSyntax {
readonly commonCodeParts: string[]; readonly commonCodeParts: string[];
} }
function validateCode(name: string, code: string, syntax: ILanguageSyntax): void { function validateCode(code: string, syntax: ILanguageSyntax): void {
if (!code || code.length === 0) { if (!code || code.length === 0) {
throw new Error(`code of ${name} is empty or undefined`); throw new Error(`code is empty or undefined`);
} }
ensureNoEmptyLines(name, code); ensureNoEmptyLines(code);
ensureCodeHasUniqueLines(name, code, syntax); ensureCodeHasUniqueLines(code, syntax);
} }
function ensureNoEmptyLines(name: string, code: string): void { function ensureNoEmptyLines(code: string): void {
if (code.split('\n').some((line) => line.trim().length === 0)) { if (code.split('\n').some((line) => line.trim().length === 0)) {
throw Error(`script has empty lines "${name}"`); throw Error(`script has empty lines`);
} }
} }
function ensureCodeHasUniqueLines(name: string, code: string, syntax: ILanguageSyntax): void { function ensureCodeHasUniqueLines(code: string, syntax: ILanguageSyntax): void {
const lines = code.split('\n') const lines = code.split('\n')
.filter((line) => !shouldIgnoreLine(line, syntax)); .filter((line) => !shouldIgnoreLine(line, syntax));
if (lines.length === 0) { if (lines.length === 0) {
@@ -46,7 +47,7 @@ function ensureCodeHasUniqueLines(name: string, code: string, syntax: ILanguageS
} }
const duplicateLines = lines.filter((e, i, a) => a.indexOf(e) !== i); const duplicateLines = lines.filter((e, i, a) => a.indexOf(e) !== i);
if (duplicateLines.length !== 0) { if (duplicateLines.length !== 0) {
throw Error(`Duplicates detected in script "${name}":\n ${duplicateLines.join('\n')}`); throw Error(`Duplicates detected in script :\n ${duplicateLines.join('\n')}`);
} }
} }

View File

@@ -1,4 +1,3 @@
export class Clipboard { export class Clipboard {
public static copyText(text: string): void { public static copyText(text: string): void {
const el = document.createElement('textarea'); const el = document.createElement('textarea');

View File

@@ -0,0 +1,69 @@
import { Environment } from '@/application/Environment/Environment';
import os from 'os';
import path from 'path';
import fs from 'fs';
import child_process from 'child_process';
import { OperatingSystem } from '@/domain/OperatingSystem';
export async function runCodeAsync(
code: string, folderName: string, fileExtension: string,
node = getNodeJs(), environment = Environment.CurrentEnvironment): Promise<void> {
const dir = node.path.join(node.os.tmpdir(), folderName);
await node.fs.promises.mkdir(dir, {recursive: true});
const filePath = node.path.join(dir, `run.${fileExtension}`);
await node.fs.promises.writeFile(filePath, code);
await node.fs.promises.chmod(filePath, '755');
const command = getExecuteCommand(filePath, environment);
node.child_process.exec(command);
}
function getExecuteCommand(scriptPath: string, environment: Environment): string {
switch (environment.os) {
case OperatingSystem.macOS:
return `open -a Terminal.app ${scriptPath}`;
// Another option with graphical sudo would be
// `osascript -e "do shell script \\"${scriptPath}\\" with administrator privileges"`
// However it runs in background
case OperatingSystem.Windows:
return scriptPath;
default:
throw Error('undefined os');
}
}
function getNodeJs(): INodeJs {
return { os, path, fs, child_process };
}
export interface INodeJs {
os: INodeOs;
path: INodePath;
fs: INodeFs;
child_process: INodeChildProcess;
}
export interface INodeOs {
tmpdir(): string;
}
export interface INodePath {
join(...paths: string[]): string;
}
export interface INodeChildProcess {
exec(command: string): void;
}
export interface INodeFs {
readonly promises: INodeFsPromises;
}
interface INodeFsPromisesMakeDirectoryOptions {
recursive?: boolean;
}
interface INodeFsPromises { // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/node/v13/fs.d.ts
chmod(path: string, mode: string | number): Promise<void>;
mkdir(path: string, options: INodeFsPromisesMakeDirectoryOptions): Promise<string>;
writeFile(path: string, data: string): Promise<void>;
}

View File

@@ -1,7 +1,6 @@
import { EventHandler, ISignal } from './ISignal'; import { EventHandler, IEventSource, IEventSubscription } from './IEventSource';
import { IEventSubscription } from './ISubscription';
export class Signal<T> implements ISignal<T> { export class EventSource<T> implements IEventSource<T> {
private handlers = new Map<number, EventHandler<T>>(); private handlers = new Map<number, EventHandler<T>>();
public on(handler: EventHandler<T>): IEventSubscription { public on(handler: EventHandler<T>): IEventSubscription {

View File

@@ -0,0 +1,12 @@
import { IEventSubscription } from './IEventSource';
export class EventSubscriptionCollection {
private readonly subscriptions = new Array<IEventSubscription>();
public register(...subscriptions: IEventSubscription[]) {
this.subscriptions.push(...subscriptions);
}
public unsubscribeAll() {
this.subscriptions.forEach((listener) => listener.unsubscribe());
this.subscriptions.splice(0, this.subscriptions.length);
}
}

View File

@@ -1,6 +1,11 @@
import { IEventSubscription } from './ISubscription'; export interface IEventSource<T> {
export interface ISignal<T> {
on(handler: EventHandler<T>): IEventSubscription; on(handler: EventHandler<T>): IEventSubscription;
} }
export interface IEventSubscription {
unsubscribe(): void;
}
export type EventHandler<T> = (data: T) => void; export type EventHandler<T> = (data: T) => void;

View File

@@ -1,3 +0,0 @@
export interface IEventSubscription {
unsubscribe(): void;
}

View File

@@ -1,7 +1,7 @@
import { Signal } from '../Events/Signal'; import { EventSource } from '../Events/EventSource';
export class AsyncLazy<T> { export class AsyncLazy<T> {
private valueCreated = new Signal(); private valueCreated = new EventSource();
private isValueCreated = false; private isValueCreated = false;
private isCreatingValue = false; private isCreatingValue = false;
private value: T | undefined; private value: T | undefined;
@@ -15,7 +15,7 @@ export class AsyncLazy<T> {
public async getValueAsync(): Promise<T> { public async getValueAsync(): Promise<T> {
// If value is already created, return the value directly // If value is already created, return the value directly
if (this.isValueCreated) { if (this.isValueCreated) {
return Promise.resolve(this.value as T); return Promise.resolve(this.value);
} }
// If value is being created, wait until the value is created and then return it. // If value is being created, wait until the value is created and then return it.
if (this.isCreatingValue) { if (this.isCreatingValue) {

View File

@@ -1,7 +1,6 @@
import Vue from 'vue'; import Vue from 'vue';
import App from './App.vue'; import App from './App.vue';
import { ApplicationBootstrapper } from './presentation/Bootstrapping/ApplicationBootstrapper'; import { ApplicationBootstrapper } from './presentation/Bootstrapping/ApplicationBootstrapper';
import 'core-js/fn/array/flat-map'; // Here until Vue 3 & CLI v4 https://github.com/vuejs/vue-cli/issues/3834
new ApplicationBootstrapper() new ApplicationBootstrapper()
.bootstrap(Vue); .bootstrap(Vue);

View File

@@ -7,8 +7,7 @@ import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import { faFolderOpen, faFolder, faSmile } from '@fortawesome/free-regular-svg-icons'; import { faFolderOpen, faFolder, faSmile } from '@fortawesome/free-regular-svg-icons';
/** SOLID ICONS (PREFIX: fas (default)) */ /** SOLID ICONS (PREFIX: fas (default)) */
import { faTimes, faFileDownload, faCopy, faSearch, faInfoCircle, faUserSecret, faDesktop, import { faTimes, faFileDownload, faCopy, faSearch, faInfoCircle, faUserSecret, faDesktop,
faTag, faGlobe, faSave, faBatteryFull, faBatteryHalf } from '@fortawesome/free-solid-svg-icons'; faTag, faGlobe, faSave, faBatteryFull, faBatteryHalf, faPlay, faArrowsAltH } from '@fortawesome/free-solid-svg-icons';
export class IconBootstrapper implements IVueBootstrapper { export class IconBootstrapper implements IVueBootstrapper {
public bootstrap(vue: VueConstructor): void { public bootstrap(vue: VueConstructor): void {
@@ -24,9 +23,12 @@ export class IconBootstrapper implements IVueBootstrapper {
faTimes, faTimes,
faFileDownload, faSave, faFileDownload, faSave,
faCopy, faCopy,
faPlay,
faSearch, faSearch,
faBatteryFull, faBatteryHalf, faBatteryFull, faBatteryHalf,
faInfoCircle); faInfoCircle,
faArrowsAltH,
);
vue.component('font-awesome-icon', FontAwesomeIcon); vue.component('font-awesome-icon', FontAwesomeIcon);
} }
} }

View File

@@ -1,8 +1,8 @@
<template> <template>
<div class="instructions"> <div class="instructions">
<!-- <p> <p>
Since you're using online version of {{ this.appName }}, you will need to do additional steps after downloading the file to execute your script on macOS: Since you're using online version of {{ this.appName }}, you will need to do additional steps after downloading the file to execute your script on macOS:
</p> --> </p>
<p> <p>
<ol> <ol>
<li> <li>
@@ -73,36 +73,33 @@
</li> </li>
</ol> </ol>
</p> </p>
<!-- <p> <p>
Or download the <a :href="this.macOsDownloadUrl">offline version</a> to run your scripts directly to skip these steps. Or download the <a :href="this.macOsDownloadUrl">offline version</a> to run your scripts directly to skip these steps.
</p> --> </p>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Prop } from 'vue-property-decorator'; import { Component, Prop, Vue } from 'vue-property-decorator';
import { StatefulVue } from '@/presentation/StatefulVue';
import Code from './Code.vue'; import Code from './Code.vue';
import { IApplication } from '@/domain/IApplication';
import { OperatingSystem } from '@/domain/OperatingSystem'; import { OperatingSystem } from '@/domain/OperatingSystem';
import { ApplicationFactory } from '@/application/ApplicationFactory';
@Component({ @Component({
components: { components: {
Code, Code,
}, },
}) })
export default class MacOsInstructions extends StatefulVue { export default class MacOsInstructions extends Vue {
@Prop() public fileName: string; @Prop() public fileName: string;
public appName = ''; public appName = '';
public macOsDownloadUrl = ''; public macOsDownloadUrl = '';
protected initialize(app: IApplication): void { public async created() {
const app = await ApplicationFactory.Current.getAppAsync();
this.appName = app.info.name; this.appName = app.info.name;
this.macOsDownloadUrl = app.info.getDownloadUrl(OperatingSystem.macOS); this.macOsDownloadUrl = app.info.getDownloadUrl(OperatingSystem.macOS);
} }
protected handleCollectionState(): void {
return;
}
} }
</script> </script>

View File

@@ -1,5 +1,11 @@
<template> <template>
<div class="container" v-if="hasCode"> <div class="container" v-if="hasCode">
<IconButton
v-if="this.canRun"
text="Run"
v-on:click="executeCodeAsync"
icon-prefix="fas" icon-name="play">
</IconButton>
<IconButton <IconButton
:text="this.isDesktopVersion ? 'Save' : 'Download'" :text="this.isDesktopVersion ? 'Save' : 'Download'"
v-on:click="saveCodeAsync" v-on:click="saveCodeAsync"
@@ -35,11 +41,11 @@ import MacOsInstructions from './MacOsInstructions.vue';
import { Environment } from '@/application/Environment/Environment'; import { Environment } from '@/application/Environment/Environment';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState'; import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage'; import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { IApplication } from '@/domain/IApplication';
import { IApplicationCode } from '@/application/Context/State/Code/IApplicationCode'; import { IApplicationCode } from '@/application/Context/State/Code/IApplicationCode';
import { IEventSubscription } from '@/infrastructure/Events/ISubscription';
import { IScriptingDefinition } from '@/domain/IScriptingDefinition'; import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import { OperatingSystem } from '@/domain/OperatingSystem'; import { OperatingSystem } from '@/domain/OperatingSystem';
import { runCodeAsync } from '@/infrastructure/CodeRunner';
import { IApplicationContext } from '@/application/Context/IApplicationContext';
@Component({ @Component({
components: { components: {
@@ -50,13 +56,12 @@ import { OperatingSystem } from '@/domain/OperatingSystem';
export default class TheCodeButtons extends StatefulVue { export default class TheCodeButtons extends StatefulVue {
public readonly macOsModalName = 'macos-instructions'; public readonly macOsModalName = 'macos-instructions';
public readonly isDesktopVersion = Environment.CurrentEnvironment.isDesktop;
public canRun = false;
public hasCode = false; public hasCode = false;
public isDesktopVersion = Environment.CurrentEnvironment.isDesktop;
public isMacOsCollection = false; public isMacOsCollection = false;
public fileName = ''; public fileName = '';
private codeListener: IEventSubscription;
public async copyCodeAsync() { public async copyCodeAsync() {
const code = await this.getCurrentCodeAsync(); const code = await this.getCurrentCodeAsync();
Clipboard.copyText(code.current); Clipboard.copyText(code.current);
@@ -68,16 +73,13 @@ export default class TheCodeButtons extends StatefulVue {
this.$modal.show(this.macOsModalName); this.$modal.show(this.macOsModalName);
} }
} }
public destroyed() { public async executeCodeAsync() {
if (this.codeListener) { const context = await this.getCurrentContextAsync();
this.codeListener.unsubscribe(); await executeCodeAsync(context);
}
} }
protected initialize(app: IApplication): void {
return;
}
protected handleCollectionState(newState: ICategoryCollectionState): void { protected handleCollectionState(newState: ICategoryCollectionState): void {
this.canRun = this.isDesktopVersion && newState.collection.os === Environment.CurrentEnvironment.os;
this.isMacOsCollection = newState.collection.os === OperatingSystem.macOS; this.isMacOsCollection = newState.collection.os === OperatingSystem.macOS;
this.fileName = buildFileName(newState.collection.scripting); this.fileName = buildFileName(newState.collection.scripting);
this.react(newState.code); this.react(newState.code);
@@ -90,12 +92,10 @@ export default class TheCodeButtons extends StatefulVue {
} }
private async react(code: IApplicationCode) { private async react(code: IApplicationCode) {
this.hasCode = code.current && code.current.length > 0; this.hasCode = code.current && code.current.length > 0;
if (this.codeListener) { this.events.unsubscribeAll();
this.codeListener.unsubscribe(); this.events.register(code.changed.on((newCode) => {
}
this.codeListener = code.changed.on((newCode) => {
this.hasCode = newCode && newCode.code.length > 0; this.hasCode = newCode && newCode.code.length > 0;
}); }));
} }
} }
@@ -122,6 +122,15 @@ function buildFileName(scripting: IScriptingDefinition) {
} }
return fileName; return fileName;
} }
async function executeCodeAsync(context: IApplicationContext) {
await runCodeAsync(
/*code*/ context.state.code.current,
/*appName*/ context.app.info.name,
/*fileExtension*/ context.state.collection.scripting.fileExtension,
);
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@@ -1,56 +1,55 @@
<template> <template>
<div :id="editorId" class="code-area" ></div> <Responsive v-on:sizeChanged="sizeChanged()">
<div
:id="editorId"
class="code-area"
></div>
</Responsive>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Prop } from 'vue-property-decorator'; import { Component, Prop } from 'vue-property-decorator';
import { StatefulVue } from './StatefulVue'; import { StatefulVue } from '@/presentation/StatefulVue';
import ace from 'ace-builds'; import ace from 'ace-builds';
import 'ace-builds/webpack-resolver'; import 'ace-builds/webpack-resolver';
import { ICodeChangedEvent } from '@/application/Context/State/Code/Event/ICodeChangedEvent'; import { ICodeChangedEvent } from '@/application/Context/State/Code/Event/ICodeChangedEvent';
import { IScript } from '@/domain/IScript'; import { IScript } from '@/domain/IScript';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage'; import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState'; import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { IApplication } from '@/domain/IApplication';
import { IEventSubscription } from '@/infrastructure/Events/ISubscription';
import { IApplicationCode } from '@/application/Context/State/Code/IApplicationCode';
import { CodeBuilderFactory } from '@/application/Context/State/Code/Generation/CodeBuilderFactory'; import { CodeBuilderFactory } from '@/application/Context/State/Code/Generation/CodeBuilderFactory';
import Responsive from '@/presentation/Responsive.vue';
@Component @Component({
components: {
Responsive,
},
})
export default class TheCodeArea extends StatefulVue { export default class TheCodeArea extends StatefulVue {
public readonly editorId = 'codeEditor'; public readonly editorId = 'codeEditor';
private editor!: ace.Ace.Editor; private editor!: ace.Ace.Editor;
private currentMarkerId?: number; private currentMarkerId?: number;
private codeListener: IEventSubscription;
@Prop() private theme!: string; @Prop() private theme!: string;
public destroyed() { public destroyed() {
this.unsubscribeCodeListening();
this.destroyEditor(); this.destroyEditor();
} }
public sizeChanged() {
protected initialize(app: IApplication): void { if (this.editor) {
return; this.editor.resize();
}
} }
protected handleCollectionState(newState: ICategoryCollectionState): void { protected handleCollectionState(newState: ICategoryCollectionState): void {
this.destroyEditor(); this.destroyEditor();
this.editor = initializeEditor(this.theme, this.editorId, newState.collection.scripting.language); this.editor = initializeEditor(this.theme, this.editorId, newState.collection.scripting.language);
const appCode = newState.code; const appCode = newState.code;
this.editor.setValue(appCode.current || getDefaultCode(newState.collection.scripting.language), 1); this.editor.setValue(appCode.current || getDefaultCode(newState.collection.scripting.language), 1);
this.unsubscribeCodeListening(); this.events.unsubscribeAll();
this.subscribe(appCode); this.events.register(appCode.changed.on((code) => this.updateCodeAsync(code)));
} }
private subscribe(appCode: IApplicationCode) {
this.codeListener = appCode.changed.on((code) => this.updateCodeAsync(code));
}
private unsubscribeCodeListening() {
if (this.codeListener) {
this.codeListener.unsubscribe();
}
}
private async updateCodeAsync(event: ICodeChangedEvent) { private async updateCodeAsync(event: ICodeChangedEvent) {
this.removeCurrentHighlighting(); this.removeCurrentHighlighting();
if (event.isEmpty()) { if (event.isEmpty()) {
@@ -60,7 +59,6 @@ export default class TheCodeArea extends StatefulVue {
return; return;
} }
this.editor.setValue(event.code, 1); this.editor.setValue(event.code, 1);
if (event.addedScripts && event.addedScripts.length) { if (event.addedScripts && event.addedScripts.length) {
this.reactToChanges(event, event.addedScripts); this.reactToChanges(event, event.addedScripts);
} else if (event.changedScripts && event.changedScripts.length) { } else if (event.changedScripts && event.changedScripts.length) {
@@ -99,6 +97,7 @@ export default class TheCodeArea extends StatefulVue {
private destroyEditor() { private destroyEditor() {
if (this.editor) { if (this.editor) {
this.editor.destroy(); this.editor.destroy();
this.editor = undefined;
} }
} }
} }
@@ -111,6 +110,7 @@ function initializeEditor(theme: string, editorId: string, language: ScriptingLa
editor.setTheme(`ace/theme/${theme}`); editor.setTheme(`ace/theme/${theme}`);
editor.setReadOnly(true); editor.setReadOnly(true);
editor.setAutoScrollEditorIntoView(true); editor.setAutoScrollEditorIntoView(true);
editor.setShowPrintMargin(false); // hides vertical line
editor.getSession().setUseWrapMode(true); // So code is readable on mobile editor.getSession().setUseWrapMode(true); // So code is readable on mobile
return editor; return editor;
} }
@@ -145,17 +145,17 @@ function getDefaultCode(language: ScriptingLanguage): string {
</script> </script>
<style lang="scss"> <style scoped lang="scss">
@import "@/presentation/styles/colors.scss"; @import "@/presentation/styles/colors.scss";
.code-area { ::v-deep .code-area {
width: 100%; min-height: 200px;
max-height: 1000px; width: 100%;
min-height: 200px; height: 100%;
overflow: auto; overflow: auto;
&__highlight { &__highlight {
background-color:$accent; background-color: $accent;
opacity: 0.2; // having procent fails in production (minified) build opacity: 0.2; // having procent fails in production (minified) build
position:absolute; position: absolute;
} }
} }
</style> </style>

View File

@@ -0,0 +1,75 @@
<template>
<div ref="containerElement" class="container">
<slot ref="containerElement"></slot>
</div>
</template>
<script lang="ts">
import { Component, Vue, Emit } from 'vue-property-decorator';
import ResizeObserver from 'resize-observer-polyfill';
import { throttle } from './Throttle';
@Component
export default class Responsive extends Vue {
private width: number;
private height: number;
private observer: ResizeObserver;
private get container(): HTMLElement { return this.$refs.containerElement as HTMLElement; }
public mounted() {
this.width = this.container.offsetWidth;
this.height = this.container.offsetHeight;
const resizeCallback = throttle(() => this.updateSize(), 200);
this.observer = new ResizeObserver(resizeCallback);
this.observer.observe(this.container);
this.fireChangeEvents();
}
public updateSize() {
let sizeChanged = false;
if (this.isWidthChanged()) {
this.updateWidth(this.container.offsetWidth);
sizeChanged = true;
}
if (this.isHeightChanged()) {
this.updateHeight(this.container.offsetHeight);
sizeChanged = true;
}
if (sizeChanged) {
this.$emit('sizeChanged');
}
}
@Emit('widthChanged') public updateWidth(width: number) {
this.width = width;
}
@Emit('heightChanged') public updateHeight(height: number) {
this.height = height;
}
public destroyed() {
if (this.observer) {
this.observer.disconnect();
}
}
private fireChangeEvents() {
this.updateWidth(this.container.offsetWidth);
this.updateHeight(this.container.offsetHeight);
this.$emit('sizeChanged');
}
private isWidthChanged(): boolean {
return this.width !== this.container.offsetWidth;
}
private isHeightChanged(): boolean {
return this.height !== this.container.offsetHeight;
}
}
</script>
<style scoped lang="scss">
.container {
width: 100%;
height: 100%;
display: inline-block; // if inline then it has no height or weight
}
</style>

View File

@@ -1,8 +1,17 @@
<template> <template>
<div> <Responsive v-on:widthChanged="width = $event">
<div v-if="categoryIds != null && categoryIds.length > 0" class="cards"> <!-- <div id="responsivity-debug">
Width: {{ width || 'undefined' }}
Size: <span v-if="width <= 500">small</span><span v-if="width > 500 && width < 750">medium</span><span v-if="width >= 750">big</span>
</div> -->
<div v-if="categoryIds != null && categoryIds.length > 0" class="cards">
<CardListItem <CardListItem
class="card" class="card"
v-bind:class="{
'small-screen': width <= 500,
'medium-screen': width > 500 && width < 750,
'big-screen': width >= 750
}"
v-for="categoryId of categoryIds" v-for="categoryId of categoryIds"
:data-category="categoryId" :data-category="categoryId"
v-bind:key="categoryId" v-bind:key="categoryId"
@@ -12,24 +21,26 @@
</CardListItem> </CardListItem>
</div> </div>
<div v-else class="error">Something went bad 😢</div> <div v-else class="error">Something went bad 😢</div>
</div> </Responsive>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component } from 'vue-property-decorator';
import CardListItem from './CardListItem.vue'; import CardListItem from './CardListItem.vue';
import Responsive from '@/presentation/Responsive.vue';
import { Component } from 'vue-property-decorator';
import { StatefulVue } from '@/presentation/StatefulVue'; import { StatefulVue } from '@/presentation/StatefulVue';
import { ICategory } from '@/domain/ICategory'; import { ICategory } from '@/domain/ICategory';
import { hasDirective } from './NonCollapsingDirective'; import { hasDirective } from './NonCollapsingDirective';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState'; import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { IApplication } from '@/domain/IApplication';
@Component({ @Component({
components: { components: {
CardListItem, CardListItem,
Responsive,
}, },
}) })
export default class CardList extends StatefulVue { export default class CardList extends StatefulVue {
public width: number = 0;
public categoryIds: number[] = []; public categoryIds: number[] = [];
public activeCategoryId?: number = null; public activeCategoryId?: number = null;
@@ -45,9 +56,6 @@ export default class CardList extends StatefulVue {
this.activeCategoryId = isExpanded ? categoryId : undefined; this.activeCategoryId = isExpanded ? categoryId : undefined;
} }
protected initialize(app: IApplication): void {
return;
}
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void { protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
this.setCategories(newState.collection.actions); this.setCategories(newState.collection.actions);
this.activeCategoryId = undefined; this.activeCategoryId = undefined;
@@ -79,6 +87,7 @@ export default class CardList extends StatefulVue {
flex-flow: row wrap; flex-flow: row wrap;
font-family: $main-font; font-family: $main-font;
} }
.error { .error {
width: 100%; width: 100%;
text-align: center; text-align: center;

View File

@@ -50,7 +50,10 @@ export default class CardListItem extends StatefulVue {
public areAllChildrenSelected = false; public areAllChildrenSelected = false;
public async mounted() { public async mounted() {
this.updateStateAsync(this.categoryId); const context = await this.getCurrentContextAsync();
this.events.register(context.state.selection.changed.on(
() => this.updateSelectionIndicatorsAsync(this.categoryId)));
await this.updateStateAsync(this.categoryId);
} }
@Emit('selected') @Emit('selected')
public onSelected(isExpanded: boolean) { public onSelected(isExpanded: boolean) {
@@ -71,19 +74,22 @@ export default class CardListItem extends StatefulVue {
@Watch('categoryId') @Watch('categoryId')
public async updateStateAsync(value: |number) { public async updateStateAsync(value: |number) {
const context = await this.getCurrentContextAsync(); const context = await this.getCurrentContextAsync();
const category = !value ? undefined : context.state.collection.findCategory(this.categoryId); const category = !value ? undefined : context.state.collection.findCategory(value);
this.cardTitle = category ? category.name : undefined; this.cardTitle = category ? category.name : undefined;
const currentSelection = context.state.selection; await this.updateSelectionIndicatorsAsync(value);
this.isAnyChildSelected = category ? currentSelection.isAnySelected(category) : false;
this.areAllChildrenSelected = category ? currentSelection.areAllSelected(category) : false;
}
protected initialize(): void {
return;
} }
protected handleCollectionState(): void { protected handleCollectionState(): void {
// No need, as categoryId will be updated instead
return; return;
} }
private async updateSelectionIndicatorsAsync(categoryId: number) {
const context = await this.getCurrentContextAsync();
const selection = context.state.selection;
const category = context.state.collection.findCategory(categoryId);
this.isAnyChildSelected = category ? selection.isAnySelected(category) : false;
this.areAllChildrenSelected = category ? selection.areAllSelected(category) : false;
}
} }
</script> </script>
@@ -100,12 +106,7 @@ $expanded-margin-top: 30px;
.card { .card {
margin: 15px; margin: 15px;
width: calc((100% / 3) - #{$card-line-break-width});
transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out;
// Media queries for stacking cards
@media screen and (max-width: $big-screen-width) { width: calc((100% / 2) - #{$card-line-break-width}); }
@media screen and (max-width: $medium-screen-width) { width: 100%; }
@media screen and (max-width: $small-screen-width) { width: 90%; }
&__inner { &__inner {
padding: $card-padding $card-padding 0 $card-padding; padding: $card-padding $card-padding 0 $card-padding;
@@ -235,31 +236,32 @@ $expanded-margin-top: 30px;
} }
} }
} }
@mixin adaptive-card($cards-in-row) {
@media screen and (min-width: $big-screen-width) { // when 3 cards in a row &.card {
.card:nth-of-type(3n+2) .card__expander { width: calc((100% / #{$cards-in-row}) - #{$card-line-break-width});
margin-left: calc(-100% - #{$card-line-break-width}); @for $nth-card from 2 through $cards-in-row {
} &:nth-of-type(#{$cards-in-row}n+#{$nth-card}) {
.card:nth-of-type(3n+3) .card__expander { .card__expander {
margin-left: calc(-200% - (#{$card-line-break-width} * 2)); $card-left: -100% * ($nth-card - 1);
} $additional-space: $card-line-break-width * ($nth-card - 1);
.card:nth-of-type(3n+4) { margin-left: calc(#{$card-left} - #{$additional-space});
clear: left; }
}
}
// Ensure new line after last row
$card-after-last: $cards-in-row + 1;
&:nth-of-type(#{$cards-in-row}n+#{$card-after-last}) {
clear: left;
}
} }
.card__expander { .card__expander {
width: calc(300% + (#{$card-line-break-width} * 2)); $all-cards-width: 100% * $cards-in-row;
$card-padding: $card-line-break-width * ($cards-in-row - 1);
width: calc(#{$all-cards-width} + #{$card-padding});
} }
} }
@media screen and (min-width: $medium-screen-width) and (max-width: $big-screen-width) { // when 2 cards in a row .big-screen { @include adaptive-card(3); }
.card:nth-of-type(2n+2) .card__expander { .medium-screen { @include adaptive-card(2); }
margin-left: calc(-100% - #{$card-line-break-width}); .small-screen { @include adaptive-card(1); }
}
.card:nth-of-type(2n+3) {
clear: left;
}
.card__expander {
width: calc(200% + #{$card-line-break-width});
}
}
</style> </style>

View File

@@ -1,6 +1,6 @@
import { DirectiveOptions } from 'vue'; import { DirectiveOptions } from 'vue';
const attributeName = 'data-interactionDoesNotCollapse'; const attributeName = 'data-interaction-does-not-collapse';
export function hasDirective(el: Element): boolean { export function hasDirective(el: Element): boolean {
if (el.hasAttribute(attributeName)) { if (el.hasAttribute(attributeName)) {

View File

@@ -1,13 +1,14 @@
<template> <template>
<div class="container"> <div class="container">
<div class="part select">Select:</div> <div class="part">Select:</div>
<div class="part"> <div class="part">
<div class="part"> <div class="part">
<SelectableOption <SelectableOption
label="None" label="None"
:enabled="this.currentSelection == SelectionState.None" :enabled="this.currentSelection == SelectionState.None"
@click="selectAsync(SelectionState.None)" @click="selectAsync(SelectionState.None)"
v-tooltip="'Deselect all selected scripts. 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.'"
/> />
</div> </div>
<div class="part"> | </div> <div class="part"> | </div>
@@ -16,7 +17,9 @@
label="Standard" label="Standard"
:enabled="this.currentSelection == SelectionState.Standard" :enabled="this.currentSelection == SelectionState.Standard"
@click="selectAsync(SelectionState.Standard)" @click="selectAsync(SelectionState.Standard)"
v-tooltip="'🛡️ Balanced for privacy and functionality. OS and applications will function normally.'" v-tooltip=" '🛡️ Balanced for privacy and functionality.<br/>' +
'OS and applications will function normally.<br/>' +
'💡 Recommended for everyone'"
/> />
</div> </div>
<div class="part"> | </div> <div class="part"> | </div>
@@ -25,7 +28,9 @@
label="Strict" label="Strict"
:enabled="this.currentSelection == SelectionState.Strict" :enabled="this.currentSelection == SelectionState.Strict"
@click="selectAsync(SelectionState.Strict)" @click="selectAsync(SelectionState.Strict)"
v-tooltip="'🚫 Stronger privacy, disables risky functions that may leak your data. Double check selected tweaks!'" v-tooltip=" '🚫 Stronger privacy, disables risky functions that may leak your data.<br/>' +
'⚠️ Double check to remove sripts where you would trade functionality for privacy<br/>' +
'💡 Recommended for daily users that prefers more privacy over non-essential functions'"
/> />
</div> </div>
<div class="part"> | </div> <div class="part"> | </div>
@@ -34,7 +39,9 @@
label="All" label="All"
:enabled="this.currentSelection == SelectionState.All" :enabled="this.currentSelection == SelectionState.All"
@click="selectAsync(SelectionState.All)" @click="selectAsync(SelectionState.All)"
v-tooltip="'🔒 Strongest privacy. Disables any functionality that may leak your data. ⚠️ Not recommended for inexperienced users'" 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'"
/> />
</div> </div>
</div> </div>
@@ -49,7 +56,6 @@ import { IScript } from '@/domain/IScript';
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript'; import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { RecommendationLevel } from '@/domain/RecommendationLevel'; import { RecommendationLevel } from '@/domain/RecommendationLevel';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState'; import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { IApplication } from '@/domain/IApplication';
enum SelectionState { enum SelectionState {
Standard, Standard,
@@ -75,9 +81,6 @@ export default class TheSelector extends StatefulVue {
selectType(context.state, type); selectType(context.state, type);
} }
protected initialize(app: IApplication): void {
return;
}
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void { protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
this.updateSelections(newState); this.updateSelections(newState);
newState.selection.changed.on(() => this.updateSelections(newState)); newState.selection.changed.on(() => this.updateSelections(newState));
@@ -170,5 +173,4 @@ function areAllSelected(
} }
font-family: $normal-font; font-family: $normal-font;
} }
</style> </style>

View File

@@ -1,12 +1,15 @@
<template> <template>
<div class="container"> <div class="container">
<div v-for="os in this.allOses" :key="os.name"> <!-- <div>OS:</div> -->
<span <div class="os-list">
class="name" <div v-for="os in this.allOses" :key="os.name">
v-bind:class="{ 'current': currentOs === os.os }" <span
v-on:click="changeOsAsync(os.os)"> class="os-name"
{{ os.name }} v-bind:class="{ 'current': currentOs === os.os }"
</span> v-on:click="changeOsAsync(os.os)">
{{ os.name }}
</span>
</div>
</div> </div>
</div> </div>
</template> </template>
@@ -16,23 +19,24 @@ import { Component } from 'vue-property-decorator';
import { OperatingSystem } from '@/domain/OperatingSystem'; import { OperatingSystem } from '@/domain/OperatingSystem';
import { StatefulVue } from '@/presentation/StatefulVue'; import { StatefulVue } from '@/presentation/StatefulVue';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState'; import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { IApplication } from '@/domain/IApplication'; import { ApplicationFactory } from '@/application/ApplicationFactory';
@Component @Component
export default class TheOsChanger extends StatefulVue { export default class TheOsChanger extends StatefulVue {
public allOses: Array<{ name: string, os: OperatingSystem }> = []; public allOses: Array<{ name: string, os: OperatingSystem }> = [];
public currentOs: OperatingSystem = undefined; public currentOs: OperatingSystem = OperatingSystem.Unknown;
public async created() {
const app = await ApplicationFactory.Current.getAppAsync();
this.allOses = app.getSupportedOsList()
.map((os) => ({ os, name: renderOsName(os) }));
}
public async changeOsAsync(newOs: OperatingSystem) { public async changeOsAsync(newOs: OperatingSystem) {
const context = await this.getCurrentContextAsync(); const context = await this.getCurrentContextAsync();
context.changeContext(newOs); context.changeContext(newOs);
} }
protected initialize(app: IApplication): void { protected handleCollectionState(newState: ICategoryCollectionState): void {
this.allOses = app.getSupportedOsList()
.map((os) => ({ os, name: renderOsName(os) }));
}
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
this.currentOs = newState.os; this.currentOs = newState.os;
this.$forceUpdate(); // v-bind:class is not updated otherwise this.$forceUpdate(); // v-bind:class is not updated otherwise
} }
@@ -41,7 +45,7 @@ export default class TheOsChanger extends StatefulVue {
function renderOsName(os: OperatingSystem): string { function renderOsName(os: OperatingSystem): string {
switch (os) { switch (os) {
case OperatingSystem.Windows: return 'Windows'; case OperatingSystem.Windows: return 'Windows';
case OperatingSystem.macOS: return 'macOS (preview)'; case OperatingSystem.macOS: return 'macOS';
default: throw new RangeError(`Cannot render os name: ${OperatingSystem[os]}`); default: throw new RangeError(`Cannot render os name: ${OperatingSystem[os]}`);
} }
} }
@@ -54,20 +58,24 @@ function renderOsName(os: OperatingSystem): string {
font-family: $normal-font; font-family: $normal-font;
display: flex; display: flex;
align-items: center; align-items: center;
div + div::before { .os-list {
content: "|"; display: flex;
margin-left: 0.5rem; margin-left: 0.25rem;
} div + div::before {
.name { content: "|";
&:not(.current) { margin-left: 0.5rem;
cursor: pointer;
&:hover {
font-weight: bold;
text-decoration: underline;
}
} }
&.current { .os-name {
color: $gray; &:not(.current) {
cursor: pointer;
&:hover {
font-weight: bold;
text-decoration: underline;
}
}
&.current {
color: $gray;
}
} }
} }
} }

View File

@@ -0,0 +1,77 @@
<template>
<div id="container">
<TheSelector class="item" />
<TheOsChanger class="item" />
<TheGrouper
class="item"
v-on:groupingChanged="$emit('groupingChanged', $event)"
v-if="!this.isSearching" />
</div>
</template>
<script lang="ts">
import { Component } from 'vue-property-decorator';
import TheOsChanger from './TheOsChanger.vue';
import TheSelector from './Selector/TheSelector.vue';
import TheGrouper from './Grouping/TheGrouper.vue';
import { StatefulVue } from '@/presentation/StatefulVue';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { IEventSubscription } from '@/infrastructure/Events/IEventSource';
@Component({
components: {
TheSelector,
TheOsChanger,
TheGrouper,
},
})
export default class TheScriptsMenu extends StatefulVue {
public isSearching = false;
private listeners = new Array<IEventSubscription>();
public destroyed() {
this.unsubscribeAll();
}
protected initialize(): void {
return;
}
protected handleCollectionState(newState: ICategoryCollectionState): void {
this.subscribe(newState);
}
private subscribe(state: ICategoryCollectionState) {
this.listeners.push(state.filter.filterRemoved.on(() => {
this.isSearching = false;
}));
state.filter.filtered.on(() => {
this.isSearching = true;
});
}
private unsubscribeAll() {
this.listeners.forEach((listener) => listener.unsubscribe());
this.listeners.splice(0, this.listeners.length);
}
}
</script>
<style scoped lang="scss">
#container {
display: flex;
flex-wrap: wrap;
.item {
flex: 1;
white-space: nowrap;
display: flex;
justify-content: center;
margin: 0 5px 0 5px;
&:first-child {
justify-content: flex-start;
}
&:last-child {
justify-content: flex-end;
}
}
}
</style>

View File

@@ -27,8 +27,6 @@ import SelectableTree from './SelectableTree/SelectableTree.vue';
import { INode, NodeType } from './SelectableTree/Node/INode'; import { INode, NodeType } from './SelectableTree/Node/INode';
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript'; import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { INodeSelectedEvent } from './SelectableTree/INodeSelectedEvent'; import { INodeSelectedEvent } from './SelectableTree/INodeSelectedEvent';
import { IApplication } from '@/domain/IApplication';
import { IEventSubscription } from '@/infrastructure/Events/ISubscription';
@Component({ @Component({
components: { components: {
@@ -43,7 +41,6 @@ export default class ScriptsTree extends StatefulVue {
public filterText?: string = null; public filterText?: string = null;
private filtered?: IFilterResult; private filtered?: IFilterResult;
private listeners = new Array<IEventSubscription>();
public async toggleNodeSelectionAsync(event: INodeSelectedEvent) { public async toggleNodeSelectionAsync(event: INodeSelectedEvent) {
const context = await this.getCurrentContextAsync(); const context = await this.getCurrentContextAsync();
@@ -75,31 +72,24 @@ export default class ScriptsTree extends StatefulVue {
|| this.filtered.categoryMatches.some( || this.filtered.categoryMatches.some(
(category: ICategory) => node.id === getCategoryNodeId(category)); (category: ICategory) => node.id === getCategoryNodeId(category));
} }
public destroyed() {
this.unsubscribeAll();
}
protected initialize(app: IApplication): void {
return;
}
protected async handleCollectionState(newState: ICategoryCollectionState) { protected async handleCollectionState(newState: ICategoryCollectionState) {
this.setCurrentFilter(newState.filter.currentFilter); this.setCurrentFilter(newState.filter.currentFilter);
if (!this.categoryId) { if (!this.categoryId) {
this.nodes = parseAllCategories(newState.collection); this.nodes = parseAllCategories(newState.collection);
} }
this.unsubscribeAll(); this.events.unsubscribeAll();
this.subscribe(newState); this.subscribeState(newState);
} }
private subscribe(state: ICategoryCollectionState) { private subscribeState(state: ICategoryCollectionState) {
this.listeners.push(state.selection.changed.on(this.handleSelectionChanged)); this.events.register(
this.listeners.push(state.filter.filterRemoved.on(this.handleFilterRemoved)); state.selection.changed.on(this.handleSelectionChanged),
this.listeners.push(state.filter.filtered.on(this.handleFiltered)); state.filter.filterRemoved.on(this.handleFilterRemoved),
} state.filter.filtered.on(this.handleFiltered),
private unsubscribeAll() { );
this.listeners.forEach((listener) => listener.unsubscribe());
this.listeners.splice(0, this.listeners.length);
} }
private setCurrentFilter(currentFilter: IFilterResult | undefined) { private setCurrentFilter(currentFilter: IFilterResult | undefined) {
if (!currentFilter) { if (!currentFilter) {
this.handleFilterRemoved(); this.handleFilterRemoved();

View File

@@ -13,49 +13,40 @@
<script lang="ts"> <script lang="ts">
import { Component, Prop, Watch } from 'vue-property-decorator'; import { Component, Prop, Watch } from 'vue-property-decorator';
import { IReverter } from './Reverter/IReverter'; import { IReverter } from './Reverter/IReverter';
import { StatefulVue } from '@/presentation/StatefulVue'; import { StatefulVue } from '@/presentation/StatefulVue';
import { INode } from './INode'; import { INode } from './INode';
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript'; import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { getReverter } from './Reverter/ReverterFactory'; import { getReverter } from './Reverter/ReverterFactory';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState'; import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { IApplication } from '@/domain/IApplication';
import { IEventSubscription } from '@/infrastructure/Events/ISubscription';
@Component @Component
export default class RevertToggle extends StatefulVue { export default class RevertToggle extends StatefulVue {
@Prop() public node: INode; @Prop() public node: INode;
public isReverted = false; public isReverted = false;
private handler: IReverter; private handler: IReverter;
private selectionChangeListener: IEventSubscription;
@Watch('node', {immediate: true}) public async onNodeChangedAsync(node: INode) { @Watch('node', {immediate: true}) public async onNodeChangedAsync(node: INode) {
const context = await this.getCurrentContextAsync(); const context = await this.getCurrentContextAsync();
this.handler = getReverter(node, context.state.collection); this.handler = getReverter(node, context.state.collection);
}
public async onRevertToggledAsync() {
const context = await this.getCurrentContextAsync();
this.handler.selectWithRevertState(this.isReverted, context.state.selection);
}
protected initialize(app: IApplication): void {
return;
}
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
this.updateStatus(newState.selection.selectedScripts);
if (this.selectionChangeListener) {
this.selectionChangeListener.unsubscribe();
}
this.selectionChangeListener = newState.selection.changed.on(
(scripts) => this.updateStatus(scripts));
}
private updateStatus(scripts: ReadonlyArray<SelectedScript>) {
this.isReverted = this.handler.getState(scripts);
}
} }
public async onRevertToggledAsync() {
const context = await this.getCurrentContextAsync();
this.handler.selectWithRevertState(this.isReverted, context.state.selection);
}
protected handleCollectionState(newState: ICategoryCollectionState): void {
this.updateStatus(newState.selection.selectedScripts);
this.events.unsubscribeAll();
this.events.register(newState.selection.changed.on((scripts) => this.updateStatus(scripts)));
}
private updateStatus(scripts: ReadonlyArray<SelectedScript>) {
this.isReverted = this.handler.getState(scripts);
}
}
</script> </script>

View File

@@ -0,0 +1,71 @@
<template>
<div
class="handle"
:style="{ cursor: cursorCssValue }"
@mousedown="startResize">
<div class="line"></div>
<font-awesome-icon
class="image"
:icon="['fas', 'arrows-alt-h']"
/> <!-- exchange-alt arrows-alt-h-->
<div class="line"></div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
@Component
export default class Handle extends Vue {
public readonly cursorCssValue = 'ew-resize';
private initialX: number = undefined;
public startResize(event: MouseEvent): void {
this.initialX = event.clientX;
document.body.style.setProperty('cursor', this.cursorCssValue);
document.addEventListener('mousemove', this.resize);
window.addEventListener('mouseup', this.stopResize);
event.stopPropagation();
event.preventDefault();
}
public resize(event: MouseEvent): void {
const displacementX = event.clientX - this.initialX;
this.$emit('resized', displacementX);
this.initialX = event.clientX;
}
public stopResize(): void {
document.body.style.removeProperty('cursor');
document.removeEventListener('mousemove', this.resize);
window.removeEventListener('mouseup', this.stopResize);
}
}
</script>
<style lang="scss" scoped>
@import "@/presentation/styles/colors.scss";
.handle {
user-select: none;
display: flex;
flex-direction: column;
align-items: center;
&:hover {
.line {
background: $gray;
}
.image {
color: $gray;
}
}
.line {
flex: 1;
background: $dark-gray;
width: 3px;
}
.image {
color: $dark-gray;
}
margin-right: 10px;
}
</style>

View File

@@ -0,0 +1,53 @@
<template>
<div class="slider">
<div class="left" ref="leftElement">
<slot name="left"></slot>
</div>
<Handle class="handle" @resized="onResize($event)" />
<div class="right">
<slot name="right"></slot>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Handle from './Handle.vue';
@Component({
components: {
Handle,
},
})
export default class HorizontalResizeSlider extends Vue {
private get left(): HTMLElement { return this.$refs.leftElement as HTMLElement; }
public onResize(displacementX: number): void {
const leftWidth = this.left.offsetWidth + displacementX;
this.left.style.width = `${leftWidth}px`;
}
}
</script>
<style lang="scss" scoped>
@import "@/presentation/styles/media.scss";
.slider {
display: flex;
flex-direction: row;
.right {
flex: 1;
}
}
@media screen and (max-width: $vertical-view-breakpoint) {
.slider {
flex-direction: column;
.left {
width: auto !important;
}
.handle {
display: none;
}
}
}
</style>

View File

@@ -0,0 +1,49 @@
<template>
<div class="scripts">
<TheScriptsMenu v-on:groupingChanged="grouping = $event" />
<HorizontalResizeSlider class="row">
<template v-slot:left>
<TheScriptsList :grouping="grouping" />
</template>
<template v-slot:right>
<TheCodeArea theme="xcode" />
</template>
</HorizontalResizeSlider>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import TheCodeArea from '@/presentation/Code/TheCodeArea.vue';
import TheScriptsList from '@/presentation/Scripts/TheScriptsList.vue';
import TheScriptsMenu from '@/presentation/Scripts/Menu/TheScriptsMenu.vue';
import HorizontalResizeSlider from '@/presentation/Scripts/Slider/HorizontalResizeSlider.vue';
import { Grouping } from '@/presentation/Scripts/Menu/Grouping/Grouping';
@Component({
components: {
TheCodeArea,
TheScriptsList,
TheScriptsMenu,
HorizontalResizeSlider,
},
})
export default class TheScriptArea extends Vue {
public grouping = Grouping.Cards;
}
</script>
<style scoped lang="scss">
.scripts {
> * + * {
margin-top: 15px;
}
}
::v-deep .left {
width: 55%; // initial width
min-width: 20%;
}
::v-deep .right {
min-width: 20%;
}
</style>

View File

@@ -1,195 +0,0 @@
<template>
<div>
<div class="heading">
<TheSelector class="item"/>
<TheOsChanger class="item"/>
<TheGrouper
class="item"
v-on:groupingChanged="onGroupingChanged($event)"
v-if="!this.isSearching" />
</div>
<div class="scripts">
<div v-if="!isSearching">
<CardList v-if="currentGrouping === Grouping.Cards"/>
<div class="tree" v-if="currentGrouping === Grouping.None">
<ScriptsTree />
</div>
</div>
<div v-else> <!-- Searching -->
<div class="search">
<div class="search__query">
<div>Searching for "{{this.searchQuery | threeDotsTrim}}"</div>
<div class="search__query__close-button">
<font-awesome-icon
:icon="['fas', 'times']"
v-on:click="clearSearchQueryAsync()"/>
</div>
</div>
<div v-if="!searchHasMatches" class="search-no-matches">
<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> </div>
</div>
</div>
<div v-if="searchHasMatches" class="tree tree--searching">
<ScriptsTree />
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import TheGrouper from '@/presentation/Scripts/Grouping/TheGrouper.vue';
import TheOsChanger from '@/presentation/Scripts/TheOsChanger.vue';
import TheSelector from '@/presentation/Scripts/Selector/TheSelector.vue';
import ScriptsTree from '@/presentation/Scripts/ScriptsTree/ScriptsTree.vue';
import CardList from '@/presentation/Scripts/Cards/CardList.vue';
import { Component } from 'vue-property-decorator';
import { StatefulVue } from '@/presentation/StatefulVue';
import { Grouping } from './Grouping/Grouping';
import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { IApplication } from '@/domain/IApplication';
import { IEventSubscription } from '@/infrastructure/Events/ISubscription';
/** Shows content of single category or many categories */
@Component({
components: {
TheGrouper,
TheSelector,
ScriptsTree,
CardList,
TheOsChanger,
},
filters: {
threeDotsTrim(query: string) {
const threshold = 30;
if (query.length <= threshold - 3) {
return query;
}
return `${query.substr(0, threshold)}...`;
},
},
})
export default class TheScripts extends StatefulVue {
public repositoryUrl = '';
public Grouping = Grouping; // Make it accessible from view
public currentGrouping = Grouping.Cards;
public searchQuery = '';
public isSearching = false;
public searchHasMatches = false;
private listeners = new Array<IEventSubscription>();
public destroyed() {
this.unsubscribeAll();
}
public async clearSearchQueryAsync() {
const context = await this.getCurrentContextAsync();
const filter = context.state.filter;
filter.removeFilter();
}
public onGroupingChanged(group: Grouping) {
this.currentGrouping = group;
}
protected initialize(app: IApplication): void {
this.repositoryUrl = app.info.repositoryWebUrl;
}
protected handleCollectionState(newState: ICategoryCollectionState): void {
this.unsubscribeAll();
this.subscribe(newState);
}
private subscribe(state: ICategoryCollectionState) {
this.listeners.push(state.filter.filterRemoved.on(() => {
this.isSearching = false;
}));
state.filter.filtered.on((result: IFilterResult) => {
this.searchQuery = result.query;
this.isSearching = true;
this.searchHasMatches = result.hasAnyMatches();
});
}
private unsubscribeAll() {
this.listeners.forEach((listener) => listener.unsubscribe());
this.listeners.splice(0, this.listeners.length);
}
}
</script>
<style scoped lang="scss">
@import "@/presentation/styles/colors.scss";
@import "@/presentation/styles/fonts.scss";
$inner-margin: 4px;
.scripts {
margin-top: $inner-margin;
.tree {
padding-left: 3%;
padding-top: 15px;
padding-bottom: 15px;
&--searching {
padding-top: 0px;
}
}
}
.search {
display: flex;
flex-direction: column;
background-color: $slate;
&__query {
display: flex;
justify-content: center;
flex-direction: row;
align-items: center;
margin-top: 1em;
color: $gray;
&__close-button {
cursor: pointer;
font-size: 1.25em;
margin-left: 0.25rem;
&:hover {
opacity: 0.9;
}
}
}
&-no-matches {
display:flex;
flex-direction: column;
word-break:break-word;
text-transform: uppercase;
color: $light-gray;
font-size: 1.5em;
padding:10px;
text-align:center;
> div {
padding-bottom:13px;
}
a {
color: $gray;
}
}
}
.heading {
margin-top: $inner-margin;
display: flex;
flex-wrap: wrap;
.item {
flex: 1;
white-space: nowrap;
display: flex;
justify-content: center;
margin: 0 5px 0 5px;
&:first-child {
justify-content: flex-start;
}
&:last-child {
justify-content: flex-end;
}
}
}
</style>

View File

@@ -0,0 +1,159 @@
<template>
<div class="scripts">
<div v-if="!isSearching">
<CardList v-if="grouping === Grouping.Cards"/>
<div class="tree" v-if="grouping === Grouping.None">
<ScriptsTree />
</div>
</div>
<div v-else> <!-- Searching -->
<div class="search">
<div class="search__query">
<div>Searching for "{{this.searchQuery | threeDotsTrim}}"</div>
<div class="search__query__close-button">
<font-awesome-icon
:icon="['fas', 'times']"
v-on:click="clearSearchQueryAsync()"/>
</div>
</div>
<div v-if="!searchHasMatches" class="search-no-matches">
<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> </div>
</div>
</div>
<div v-if="searchHasMatches" class="tree tree--searching">
<ScriptsTree />
</div>
</div>
</div>
</template>
<script lang="ts">
import TheGrouper from '@/presentation/Scripts/Menu/Grouping/TheGrouper.vue';
import ScriptsTree from '@/presentation/Scripts/ScriptsTree/ScriptsTree.vue';
import CardList from '@/presentation/Scripts/Cards/CardList.vue';
import { Component, Prop } from 'vue-property-decorator';
import { StatefulVue } from '@/presentation/StatefulVue';
import { Grouping } from '@/presentation/Scripts/Menu/Grouping/Grouping';
import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { ApplicationFactory } from '@/application/ApplicationFactory';
/** Shows content of single category or many categories */
@Component({
components: {
TheGrouper,
ScriptsTree,
CardList,
},
filters: {
threeDotsTrim(query: string) {
const threshold = 30;
if (query.length <= threshold - 3) {
return query;
}
return `${query.substr(0, threshold)}...`;
},
},
})
export default class TheScriptsList extends StatefulVue {
@Prop() public grouping: Grouping;
public repositoryUrl = '';
public Grouping = Grouping; // Make it accessible from the view
public searchQuery = '';
public isSearching = false;
public searchHasMatches = false;
public async created() {
const app = await ApplicationFactory.Current.getAppAsync();
this.repositoryUrl = app.info.repositoryWebUrl;
}
public async clearSearchQueryAsync() {
const context = await this.getCurrentContextAsync();
const filter = context.state.filter;
filter.removeFilter();
}
protected handleCollectionState(newState: ICategoryCollectionState): void {
this.events.unsubscribeAll();
this.subscribeState(newState);
}
private subscribeState(state: ICategoryCollectionState) {
this.events.register(
state.filter.filterRemoved.on(() => {
this.isSearching = false;
}),
state.filter.filtered.on((result: IFilterResult) => {
this.searchQuery = result.query;
this.isSearching = true;
this.searchHasMatches = result.hasAnyMatches();
}),
);
}
}
</script>
<style scoped lang="scss">
@import "@/presentation/styles/colors.scss";
@import "@/presentation/styles/fonts.scss";
@import "@/presentation/styles/media.scss";
$inner-margin: 4px;
.scripts {
margin-top: $inner-margin;
@media screen and (min-width: $vertical-view-breakpoint) { // so the current code is always visible
overflow: auto;
max-height: 70vh;
}
.tree {
padding-left: 3%;
padding-top: 15px;
padding-bottom: 15px;
&--searching {
padding-top: 0px;
}
}
}
.search {
display: flex;
flex-direction: column;
background-color: $slate;
&__query {
display: flex;
justify-content: center;
flex-direction: row;
align-items: center;
margin-top: 1em;
color: $gray;
&__close-button {
cursor: pointer;
font-size: 1.25em;
margin-left: 0.25rem;
&:hover {
opacity: 0.9;
}
}
}
&-no-matches {
display:flex;
flex-direction: column;
word-break:break-word;
text-transform: uppercase;
color: $light-gray;
font-size: 1.5em;
padding:10px;
text-align:center;
> div {
padding-bottom:13px;
}
a {
color: $gray;
}
}
}
</style>

View File

@@ -1,33 +1,30 @@
import { Component, Vue } from 'vue-property-decorator'; import { Component, Vue } from 'vue-property-decorator';
import { AsyncLazy } from '@/infrastructure/Threading/AsyncLazy'; import { AsyncLazy } from '@/infrastructure/Threading/AsyncLazy';
import { IApplicationContext } from '@/application/Context/IApplicationContext'; import { IApplicationContext } from '@/application/Context/IApplicationContext';
import { buildContext } from '@/application/Context/ApplicationContextProvider'; import { buildContextAsync } from '@/application/Context/ApplicationContextFactory';
import { IApplicationContextChangedEvent } from '../application/Context/IApplicationContext'; import { IApplicationContextChangedEvent } from '@/application/Context/IApplicationContext';
import { IApplication } from '@/domain/IApplication'; import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { ICategoryCollectionState } from '../application/Context/State/ICategoryCollectionState'; import { EventSubscriptionCollection } from '../infrastructure/Events/EventSubscriptionCollection';
import { IEventSubscription } from '../infrastructure/Events/ISubscription';
// @ts-ignore because https://github.com/vuejs/vue-class-component/issues/91 // @ts-ignore because https://github.com/vuejs/vue-class-component/issues/91
@Component @Component
export abstract class StatefulVue extends Vue { export abstract class StatefulVue extends Vue {
public static instance = new AsyncLazy<IApplicationContext>( private static readonly instance = new AsyncLazy<IApplicationContext>(() => buildContextAsync());
() => Promise.resolve(buildContext()));
private listener: IEventSubscription; protected readonly events = new EventSubscriptionCollection();
private readonly ownEvents = new EventSubscriptionCollection();
public async mounted() { public async mounted() {
const context = await this.getCurrentContextAsync(); const context = await this.getCurrentContextAsync();
this.listener = context.contextChanged.on((event) => this.handleStateChangedEvent(event)); this.ownEvents.register(context.contextChanged.on((event) => this.handleStateChangedEvent(event)));
this.initialize(context.app);
this.handleCollectionState(context.state, undefined); this.handleCollectionState(context.state, undefined);
} }
public destroyed() { public destroyed() {
if (this.listener) { this.ownEvents.unsubscribeAll();
this.listener.unsubscribe(); this.events.unsubscribeAll();
}
} }
protected abstract initialize(app: IApplication): void;
protected abstract handleCollectionState( protected abstract handleCollectionState(
newState: ICategoryCollectionState, oldState: ICategoryCollectionState | undefined): void; newState: ICategoryCollectionState, oldState: ICategoryCollectionState | undefined): void;
protected getCurrentContextAsync(): Promise<IApplicationContext> { protected getCurrentContextAsync(): Promise<IApplicationContext> {

View File

@@ -9,15 +9,13 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Prop, Watch } from 'vue-property-decorator'; import { Component, Prop, Watch, Vue } from 'vue-property-decorator';
import { StatefulVue } from '@/presentation/StatefulVue';
import { Environment } from '@/application/Environment/Environment'; import { Environment } from '@/application/Environment/Environment';
import { OperatingSystem } from '@/domain/OperatingSystem'; import { OperatingSystem } from '@/domain/OperatingSystem';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState'; import { ApplicationFactory } from '@/application/ApplicationFactory';
import { IApplication } from '@/domain/IApplication';
@Component @Component
export default class DownloadUrlListItem extends StatefulVue { export default class DownloadUrlListItem extends Vue {
@Prop() public operatingSystem!: OperatingSystem; @Prop() public operatingSystem!: OperatingSystem;
public downloadUrl: string = ''; public downloadUrl: string = '';
@@ -38,16 +36,9 @@ export default class DownloadUrlListItem extends StatefulVue {
this.hasCurrentOsDesktopVersion = hasDesktopVersion(currentOs); this.hasCurrentOsDesktopVersion = hasDesktopVersion(currentOs);
} }
protected initialize(app: IApplication): void {
return;
}
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
return;
}
private async getDownloadUrlAsync(os: OperatingSystem): Promise<string> { private async getDownloadUrlAsync(os: OperatingSystem): Promise<string> {
const context = await this.getCurrentContextAsync(); const context = await ApplicationFactory.Current.getAppAsync();
return context.app.info.getDownloadUrl(os); return context.info.getDownloadUrl(os);
} }
} }

View File

@@ -31,25 +31,27 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component } from 'vue-property-decorator'; import { Component, Vue } from 'vue-property-decorator';
import { StatefulVue } from '@/presentation/StatefulVue';
import { Environment } from '@/application/Environment/Environment'; import { Environment } from '@/application/Environment/Environment';
import { ApplicationFactory } from '@/application/ApplicationFactory';
import { IApplication } from '@/domain/IApplication'; import { IApplication } from '@/domain/IApplication';
@Component @Component
export default class PrivacyPolicy extends StatefulVue { export default class PrivacyPolicy extends Vue {
public repositoryUrl: string = ''; public repositoryUrl: string = '';
public feedbackUrl: string = ''; public feedbackUrl: string = '';
public isDesktop = Environment.CurrentEnvironment.isDesktop; public isDesktop = Environment.CurrentEnvironment.isDesktop;
protected initialize(app: IApplication): void { public async created() {
const app = await ApplicationFactory.Current.getAppAsync();
this.initialize(app);
}
private initialize(app: IApplication) {
const info = app.info; const info = app.info;
this.repositoryUrl = info.repositoryWebUrl; this.repositoryUrl = info.repositoryWebUrl;
this.feedbackUrl = info.feedbackUrl; this.feedbackUrl = info.feedbackUrl;
} }
protected handleCollectionState(): void {
return;
}
} }
</script> </script>

View File

@@ -47,22 +47,21 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component } from 'vue-property-decorator'; import { Component, Vue } from 'vue-property-decorator';
import { StatefulVue } from '@/presentation/StatefulVue';
import { Environment } from '@/application/Environment/Environment'; import { Environment } from '@/application/Environment/Environment';
import PrivacyPolicy from './PrivacyPolicy.vue'; import PrivacyPolicy from './PrivacyPolicy.vue';
import DownloadUrlList from './DownloadUrlList.vue'; import DownloadUrlList from './DownloadUrlList.vue';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { IApplication } from '@/domain/IApplication'; import { IApplication } from '@/domain/IApplication';
import { ApplicationFactory } from '@/application/ApplicationFactory';
@Component({ @Component({
components: { components: {
PrivacyPolicy, DownloadUrlList, PrivacyPolicy, DownloadUrlList,
}, },
}) })
export default class TheFooter extends StatefulVue { export default class TheFooter extends Vue {
public readonly modalName = 'privacy-policy'; public readonly modalName = 'privacy-policy';
public readonly isDesktop: boolean; public readonly isDesktop = Environment.CurrentEnvironment.isDesktop;
public version: string = ''; public version: string = '';
public repositoryUrl: string = ''; public repositoryUrl: string = '';
@@ -70,12 +69,12 @@ export default class TheFooter extends StatefulVue {
public feedbackUrl: string = ''; public feedbackUrl: string = '';
public homepageUrl: string = ''; public homepageUrl: string = '';
constructor() { public async created() {
super(); const app = await ApplicationFactory.Current.getAppAsync();
this.isDesktop = Environment.CurrentEnvironment.isDesktop; this.initialize(app);
} }
protected initialize(app: IApplication): void { private initialize(app: IApplication) {
const info = app.info; const info = app.info;
this.version = info.version; this.version = info.version;
this.homepageUrl = info.homepage; this.homepageUrl = info.homepage;
@@ -83,10 +82,6 @@ export default class TheFooter extends StatefulVue {
this.releaseUrl = info.releaseUrl; this.releaseUrl = info.releaseUrl;
this.feedbackUrl = info.feedbackUrl; this.feedbackUrl = info.feedbackUrl;
} }
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
return;
}
} }
</script> </script>
@@ -104,13 +99,13 @@ export default class TheFooter extends StatefulVue {
.footer { .footer {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@media (max-width: $big-screen-width) { @media screen and (max-width: $big-screen-width) {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
} }
&__section { &__section {
display: flex; display: flex;
@media (max-width: $big-screen-width) { @media screen and (max-width: $big-screen-width) {
justify-content: space-around; justify-content: space-around;
width:100%; width:100%;
&:not(:first-child) { &:not(:first-child) {
@@ -134,7 +129,7 @@ export default class TheFooter extends StatefulVue {
content: "|"; content: "|";
padding: 0 5px; padding: 0 5px;
} }
@media (max-width: $big-screen-width) { @media screen and (max-width: $big-screen-width) {
margin-top: 3px; margin-top: 3px;
&::before { &::before {
content: ""; content: "";

View File

@@ -1,27 +1,23 @@
<template> <template>
<div id="container"> <div id="container">
<h1 class="child title" >{{ title }}</h1> <h1 class="child title" >{{ title }}</h1>
<h2 class="child subtitle">Enforce privacy & security on Windows and macOS</h2> <h2 class="child subtitle">Enforce privacy &amp; security on Windows and macOS</h2>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState'; import { ApplicationFactory } from '@/application/ApplicationFactory';
import { IApplication } from '@/domain/IApplication'; import { Component, Vue } from 'vue-property-decorator';
import { Component } from 'vue-property-decorator';
import { StatefulVue } from './StatefulVue';
@Component @Component
export default class TheHeader extends StatefulVue { export default class TheHeader extends Vue {
public title = ''; public title = '';
public subtitle = ''; public subtitle = '';
protected initialize(app: IApplication): void { public async created() {
const app = await ApplicationFactory.Current.getAppAsync();
this.title = app.info.name; this.title = app.info.name;
} }
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
return;
}
} }
</script> </script>

View File

@@ -15,9 +15,7 @@ import { StatefulVue } from './StatefulVue';
import { NonCollapsing } from '@/presentation/Scripts/Cards/NonCollapsingDirective'; import { NonCollapsing } from '@/presentation/Scripts/Cards/NonCollapsingDirective';
import { IUserFilter } from '@/application/Context/State/Filter/IUserFilter'; import { IUserFilter } from '@/application/Context/State/Filter/IUserFilter';
import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult'; import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
import { IApplication } from '@/domain/IApplication';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState'; import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { IEventSubscription } from '@/infrastructure/Events/ISubscription';
@Component( { @Component( {
directives: { NonCollapsing }, directives: { NonCollapsing },
@@ -27,8 +25,6 @@ export default class TheSearchBar extends StatefulVue {
public searchPlaceHolder = 'Search'; public searchPlaceHolder = 'Search';
public searchQuery = ''; public searchQuery = '';
private readonly listeners = new Array<IEventSubscription>();
@Watch('searchQuery') @Watch('searchQuery')
public async updateFilterAsync(newFilter: |string) { public async updateFilterAsync(newFilter: |string) {
const context = await this.getCurrentContextAsync(); const context = await this.getCurrentContextAsync();
@@ -39,28 +35,18 @@ export default class TheSearchBar extends StatefulVue {
filter.setFilter(newFilter); filter.setFilter(newFilter);
} }
} }
public destroyed() {
this.unsubscribeAll();
}
protected initialize(app: IApplication): void {
return;
}
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState | undefined) { protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState | undefined) {
const totalScripts = newState.collection.totalScripts; const totalScripts = newState.collection.totalScripts;
this.searchPlaceHolder = `Search in ${totalScripts} scripts`; this.searchPlaceHolder = `Search in ${totalScripts} scripts`;
this.searchQuery = newState.filter.currentFilter ? newState.filter.currentFilter.query : ''; this.searchQuery = newState.filter.currentFilter ? newState.filter.currentFilter.query : '';
this.unsubscribeAll(); this.events.unsubscribeAll();
this.subscribe(newState.filter); this.subscribeFilter(newState.filter);
} }
private subscribe(filter: IUserFilter) { private subscribeFilter(filter: IUserFilter) {
this.listeners.push(filter.filtered.on((result) => this.handleFiltered(result))); this.events.register(filter.filtered.on((result) => this.handleFiltered(result)));
this.listeners.push(filter.filterRemoved.on(() => this.handleFilterRemoved())); this.events.register(filter.filterRemoved.on(() => this.handleFilterRemoved()));
}
private unsubscribeAll() {
this.listeners.forEach((listener) => listener.unsubscribe());
this.listeners.splice(0, this.listeners.length);
} }
private handleFiltered(result: IFilterResult) { private handleFiltered(result: IFilterResult) {
this.searchQuery = result.query; this.searchQuery = result.query;

View File

@@ -0,0 +1,30 @@
export function throttle<T extends []>(
callback: (..._: T) => void, wait: number,
timer: ITimer = NodeTimer): (..._: T) => void {
let queuedToRun: ReturnType<typeof setTimeout>;
let previouslyRun: number;
return function invokeFn(...args: T) {
const now = timer.dateNow();
if (queuedToRun) {
queuedToRun = timer.clearTimeout(queuedToRun) as undefined;
}
if (!previouslyRun || (now - previouslyRun >= wait)) {
callback(...args);
previouslyRun = now;
} else {
queuedToRun = timer.setTimeout(invokeFn.bind(null, ...args), wait - (now - previouslyRun));
}
};
}
export interface ITimer {
setTimeout: (callback: () => void, ms: number) => ReturnType<typeof setTimeout>;
clearTimeout: (timeoutId: ReturnType<typeof setTimeout>) => void;
dateNow(): number;
}
const NodeTimer: ITimer = {
setTimeout: (callback, ms) => setTimeout(callback, ms),
clearTimeout: (timeoutId) => clearTimeout(timeoutId),
dateNow: () => Date.now(),
};

View File

@@ -1,3 +1,5 @@
$big-screen-width: 992px; $big-screen-width: 992px;
$medium-screen-width: 768px; $medium-screen-width: 768px;
$small-screen-width: 380px; $small-screen-width: 380px;
$vertical-view-breakpoint: 992px;

View File

@@ -0,0 +1,60 @@
import 'mocha';
import { expect } from 'chai';
import { ApplicationFactory, ApplicationGetter } from '@/application/ApplicationFactory';
import { ApplicationStub } from '../stubs/ApplicationStub';
describe('ApplicationFactory', () => {
describe('ctor', () => {
it('throws if getter is undefined', () => {
// arrange
const expectedError = 'undefined getter';
const getter = undefined;
// act
const act = () => new SystemUnderTest(getter);
// assert
expect(act).to.throw(expectedError);
});
});
describe('getAppAsync', () => {
it('returns result from the getter', async () => {
// arrange
const expected = new ApplicationStub();
const getter: ApplicationGetter = () => expected;
const sut = new SystemUnderTest(getter);
// act
const actual = await Promise.all( [
sut.getAppAsync(),
sut.getAppAsync(),
sut.getAppAsync(),
sut.getAppAsync(),
]);
// assert
expect(actual.every((value) => value === expected));
});
it('only executes getter once', async () => {
// arrange
let totalExecution = 0;
const expected = new ApplicationStub();
const getter: ApplicationGetter = () => {
totalExecution++;
return expected;
};
const sut = new SystemUnderTest(getter);
// act
await Promise.all( [
sut.getAppAsync(),
sut.getAppAsync(),
sut.getAppAsync(),
sut.getAppAsync(),
]);
// assert
expect(totalExecution).to.equal(1);
});
});
});
class SystemUnderTest extends ApplicationFactory {
public constructor(costlyGetter: ApplicationGetter) {
super(costlyGetter);
}
}

View File

@@ -0,0 +1,83 @@
import 'mocha';
import { expect } from 'chai';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { ICategoryCollection } from '@/domain/ICategoryCollection';
import { CategoryCollectionStub } from '../../stubs/CategoryCollectionStub';
import { EnvironmentStub } from '../../stubs/EnvironmentStub';
import { ApplicationStub } from '../../stubs/ApplicationStub';
import { buildContextAsync } from '@/application/Context/ApplicationContextFactory';
import { IApplicationFactory } from '@/application/IApplicationFactory';
import { IApplication } from '@/domain/IApplication';
describe('ApplicationContextFactory', () => {
describe('buildContextAsync', () => {
describe('factory', () => {
it('sets application from factory', async () => {
// arrange
const expected = new ApplicationStub().withCollection(
new CategoryCollectionStub().withOs(OperatingSystem.macOS));
const factoryMock = mockFactoryWithApp(expected);
// act
const context = await buildContextAsync(factoryMock);
// assert
expect(expected).to.equal(context.app);
});
});
describe('environment', () => {
describe('sets initial OS as expected', () => {
it('returns currentOs if it is supported', async () => {
// arrange
const expected = OperatingSystem.Windows;
const environment = new EnvironmentStub().withOs(expected);
const collection = new CategoryCollectionStub().withOs(expected);
const factoryMock = mockFactoryWithCollection(collection);
// act
const context = await buildContextAsync(factoryMock, environment);
// assert
const actual = context.state.os;
expect(expected).to.equal(actual);
});
it('fallbacks to other os if OS in environment is not supported', async () => {
// arrange
const expected = OperatingSystem.Windows;
const currentOs = OperatingSystem.macOS;
const environment = new EnvironmentStub().withOs(currentOs);
const collection = new CategoryCollectionStub().withOs(expected);
const factoryMock = mockFactoryWithCollection(collection);
// act
const context = await buildContextAsync(factoryMock, environment);
// assert
const actual = context.state.os;
expect(expected).to.equal(actual);
});
it('fallbacks to most supported os if current os is not supported', async () => {
// arrange
const expectedOs = OperatingSystem.Android;
const allCollections = [
new CategoryCollectionStub().withOs(OperatingSystem.Linux).withTotalScripts(3),
new CategoryCollectionStub().withOs(expectedOs).withTotalScripts(5),
new CategoryCollectionStub().withOs(OperatingSystem.Windows).withTotalScripts(4),
];
const environment = new EnvironmentStub().withOs(OperatingSystem.macOS);
const app = new ApplicationStub().withCollections(...allCollections);
const factoryMock = mockFactoryWithApp(app);
// act
const context = await buildContextAsync(factoryMock, environment);
// assert
const actual = context.state.os;
expect(expectedOs).to.equal(actual, `Expected: ${OperatingSystem[expectedOs]}, actual: ${OperatingSystem[actual]}`);
});
});
});
});
});
function mockFactoryWithCollection(result: ICategoryCollection): IApplicationFactory {
return mockFactoryWithApp(new ApplicationStub().withCollection(result));
}
function mockFactoryWithApp(app: IApplication): IApplicationFactory {
return {
getAppAsync: () => Promise.resolve(app),
};
}

View File

@@ -1,69 +0,0 @@
import 'mocha';
import { expect } from 'chai';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { ICategoryCollection } from '@/domain/ICategoryCollection';
import { ApplicationParserType, buildContext } from '@/application/Context/ApplicationContextProvider';
import { CategoryCollectionStub } from '../../stubs/CategoryCollectionStub';
import { EnvironmentStub } from '../../stubs/EnvironmentStub';
import { ApplicationStub } from '../../stubs/ApplicationStub';
describe('ApplicationContextProvider', () => {
describe('buildContext', () => {
it('sets application from parser', () => {
// arrange
const expected = new ApplicationStub().withCollection(
new CategoryCollectionStub().withOs(OperatingSystem.macOS));
const parserMock: ApplicationParserType = () => expected;
// act
const context = buildContext(parserMock);
// assert
expect(expected).to.equal(context.app);
});
describe('sets initial OS as expected', () => {
it('returns currentOs if it is supported', () => {
// arrange
const expected = OperatingSystem.Windows;
const environment = new EnvironmentStub().withOs(expected);
const parser = mockParser(new CategoryCollectionStub().withOs(expected));
// act
const context = buildContext(parser, environment);
// assert
const actual = context.state.os;
expect(expected).to.equal(actual);
});
it('fallbacks to other os if OS in environment is not supported', () => {
// arrange
const expected = OperatingSystem.Windows;
const currentOs = OperatingSystem.macOS;
const environment = new EnvironmentStub().withOs(currentOs);
const parser = mockParser(new CategoryCollectionStub().withOs(expected));
// act
const context = buildContext(parser, environment);
// assert
const actual = context.state.os;
expect(expected).to.equal(actual);
});
it('fallbacks to most supported os if current os is not supported', () => {
// arrange
const expectedOs = OperatingSystem.Android;
const allCollections = [
new CategoryCollectionStub().withOs(OperatingSystem.Linux).withTotalScripts(3),
new CategoryCollectionStub().withOs(expectedOs).withTotalScripts(5),
new CategoryCollectionStub().withOs(OperatingSystem.Windows).withTotalScripts(4),
];
const environment = new EnvironmentStub().withOs(OperatingSystem.macOS);
const app = new ApplicationStub().withCollections(...allCollections);
const parser: ApplicationParserType = () => app;
// act
const context = buildContext(parser, environment);
// assert
const actual = context.state.os;
expect(expectedOs).to.equal(actual, `Expected: ${OperatingSystem[expectedOs]}, actual: ${OperatingSystem[actual]}`);
});
});
});
});
function mockParser(result: ICategoryCollection): ApplicationParserType {
return () => new ApplicationStub().withCollection(result);
}

View File

@@ -23,15 +23,37 @@ describe('BatchBuilder', () => {
}); });
}); });
describe('writeStandardOut', () => { describe('writeStandardOut', () => {
it('prepends expected', () => { const testData = [
// arrange {
const text = 'test'; name: 'plain text',
const expected = `echo ${text}`; text: 'test',
const sut = new BatchBuilderRevealer(); expected: 'echo test',
// act },
const actual = sut.writeStandardOut(text); {
// assert name: 'text with ampersand',
expect(expected).to.equal(actual); text: 'a & b',
}); expected: 'echo a ^& b',
},
{
name: 'text with percent sign',
text: '90%',
expected: 'echo 90%%',
},
{
name: 'text with multiple ampersands and percent signs',
text: 'Me&you in % ? You & me = 0%',
expected: 'echo Me^&you in %% ? You ^& me = 0%%',
},
];
for (const test of testData) {
it(test.name, () => {
// arrange
const sut = new BatchBuilderRevealer();
// act
const actual = sut.writeStandardOut(test.text);
// assert
expect(test.expected).to.equal(actual);
});
}
}); });
}); });

View File

@@ -23,15 +23,32 @@ describe('ShellBuilder', () => {
}); });
}); });
describe('writeStandardOut', () => { describe('writeStandardOut', () => {
it('prepends expected', () => { const testData = [
// arrange {
const text = 'test'; name: 'plain text',
const expected = `echo '${text}'`; text: 'test',
const sut = new ShellBuilderRevealer(); expected: 'echo \'test\'',
// act },
const actual = sut.writeStandardOut(text); {
// assert name: 'text with single quote',
expect(expected).to.equal(actual); text: 'I\'m not who you think I am',
}); expected: 'echo \'I\'\\\'\'m not who you think I am\'',
},
{
name: 'text with multiple single quotes',
text: 'I\'m what you\'re',
expected: 'echo \'I\'\\\'\'m what you\'\\\'\'re\'',
},
];
for (const test of testData) {
it(test.name, () => {
// arrange
const sut = new ShellBuilderRevealer();
// act
const actual = sut.writeStandardOut(test.text);
// assert
expect(test.expected).to.equal(actual);
});
}
}); });
}); });

View File

@@ -10,6 +10,10 @@ import { mockEnumParser } from '../../stubs/EnumParserStub';
import { ProjectInformationStub } from '../../stubs/ProjectInformationStub'; import { ProjectInformationStub } from '../../stubs/ProjectInformationStub';
import { getCategoryStub, CollectionDataStub } from '../../stubs/CollectionDataStub'; import { getCategoryStub, CollectionDataStub } from '../../stubs/CollectionDataStub';
import { CategoryCollectionParseContextStub } from '../../stubs/CategoryCollectionParseContextStub'; import { CategoryCollectionParseContextStub } from '../../stubs/CategoryCollectionParseContextStub';
import { CategoryDataStub } from '../../stubs/CategoryDataStub';
import { ScriptDataStub } from '../../stubs/ScriptDataStub';
import { FunctionDataStub } from '../../stubs/FunctionDataStub';
import { RecommendationLevel } from '../../../../src/domain/RecommendationLevel';
describe('CategoryCollectionParser', () => { describe('CategoryCollectionParser', () => {
describe('parseCategoryCollection', () => { describe('parseCategoryCollection', () => {
@@ -93,5 +97,35 @@ describe('CategoryCollectionParser', () => {
expect(actual.os).to.equal(expectedOs); expect(actual.os).to.equal(expectedOs);
}); });
}); });
describe('functions', () => {
it('compiles script call with given function', () => {
// arrange
const expectedCode = 'code-from-the-function';
const functionName = 'function-name';
const scriptName = 'script-name';
const script = ScriptDataStub.createWithCall({ function: functionName })
.withName(scriptName);
const func = FunctionDataStub.createWithCode()
.withName(functionName)
.withCode(expectedCode);
const category = new CategoryDataStub()
.withChildren([ script,
ScriptDataStub.createWithCode().withName('2')
.withRecommendationLevel(RecommendationLevel.Standard),
ScriptDataStub.createWithCode()
.withName('3').withRecommendationLevel(RecommendationLevel.Strict),
]);
const collection = new CollectionDataStub()
.withActions([ category ])
.withFunctions([ func ]);
const info = new ProjectInformationStub();
// act
const actual = parseCategoryCollection(collection, info);
// assert
const actualScript = actual.findScript(scriptName);
const actualCode = actualScript.code.execute;
expect(actualCode).to.equal(expectedCode);
});
});
}); });
}); });

View File

@@ -1,13 +1,13 @@
import 'mocha'; import 'mocha';
import { expect } from 'chai'; import { expect } from 'chai';
import { parseCategory } from '@/application/Parser/CategoryParser'; import { parseCategory } from '@/application/Parser/CategoryParser';
import { CategoryData, CategoryOrScriptData } from 'js-yaml-loader!@/*';
import { parseScript } from '@/application/Parser/Script/ScriptParser'; import { parseScript } from '@/application/Parser/Script/ScriptParser';
import { parseDocUrls } from '@/application/Parser/DocumentationParser'; import { parseDocUrls } from '@/application/Parser/DocumentationParser';
import { ScriptCompilerStub } from '../../stubs/ScriptCompilerStub'; import { ScriptCompilerStub } from '../../stubs/ScriptCompilerStub';
import { ScriptDataStub } from '../../stubs/ScriptDataStub'; import { ScriptDataStub } from '../../stubs/ScriptDataStub';
import { CategoryCollectionParseContextStub } from '../../stubs/CategoryCollectionParseContextStub'; import { CategoryCollectionParseContextStub } from '../../stubs/CategoryCollectionParseContextStub';
import { LanguageSyntaxStub } from '../../stubs/LanguageSyntaxStub'; import { LanguageSyntaxStub } from '../../stubs/LanguageSyntaxStub';
import { CategoryDataStub } from '../../stubs/CategoryDataStub';
describe('CategoryParser', () => { describe('CategoryParser', () => {
describe('parseCategory', () => { describe('parseCategory', () => {
@@ -26,10 +26,9 @@ describe('CategoryParser', () => {
// arrange // arrange
const categoryName = 'test'; const categoryName = 'test';
const expectedMessage = `category has no children: "${categoryName}"`; const expectedMessage = `category has no children: "${categoryName}"`;
const category: CategoryData = { const category = new CategoryDataStub()
category: categoryName, .withName(categoryName)
children: [], .withChildren([]);
};
const context = new CategoryCollectionParseContextStub(); const context = new CategoryCollectionParseContextStub();
// act // act
const act = () => parseCategory(category, context); const act = () => parseCategory(category, context);
@@ -40,10 +39,9 @@ describe('CategoryParser', () => {
// arrange // arrange
const categoryName = 'test'; const categoryName = 'test';
const expectedMessage = `category has no children: "${categoryName}"`; const expectedMessage = `category has no children: "${categoryName}"`;
const category: CategoryData = { const category = new CategoryDataStub()
category: categoryName, .withName(categoryName)
children: undefined, .withChildren(undefined);
};
const context = new CategoryCollectionParseContextStub(); const context = new CategoryCollectionParseContextStub();
// act // act
const act = () => parseCategory(category, context); const act = () => parseCategory(category, context);
@@ -55,10 +53,8 @@ describe('CategoryParser', () => {
const expectedMessage = 'category has no name'; const expectedMessage = 'category has no name';
const invalidNames = ['', undefined]; const invalidNames = ['', undefined];
invalidNames.forEach((invalidName) => { invalidNames.forEach((invalidName) => {
const category: CategoryData = { const category = new CategoryDataStub()
category: invalidName, .withName(invalidName);
children: getTestChildren(),
};
const context = new CategoryCollectionParseContextStub(); const context = new CategoryCollectionParseContextStub();
// act // act
const act = () => parseCategory(category, context); const act = () => parseCategory(category, context);
@@ -71,7 +67,7 @@ describe('CategoryParser', () => {
// arrange // arrange
const expectedError = 'undefined context'; const expectedError = 'undefined context';
const context = undefined; const context = undefined;
const category = getValidCategory(); const category = new CategoryDataStub();
// act // act
const act = () => parseCategory(category, context); const act = () => parseCategory(category, context);
// assert // assert
@@ -81,11 +77,8 @@ describe('CategoryParser', () => {
// arrange // arrange
const url = 'https://privacy.sexy'; const url = 'https://privacy.sexy';
const expected = parseDocUrls({ docs: url }); const expected = parseDocUrls({ docs: url });
const category: CategoryData = { const category = new CategoryDataStub()
category: 'category name', .withDocs(url);
children: getTestChildren(),
docs: url,
};
const context = new CategoryCollectionParseContextStub(); const context = new CategoryCollectionParseContextStub();
// act // act
const actual = parseCategory(category, context).documentationUrls; const actual = parseCategory(category, context).documentationUrls;
@@ -98,10 +91,8 @@ describe('CategoryParser', () => {
const script = ScriptDataStub.createWithCode(); const script = ScriptDataStub.createWithCode();
const context = new CategoryCollectionParseContextStub(); const context = new CategoryCollectionParseContextStub();
const expected = [ parseScript(script, context) ]; const expected = [ parseScript(script, context) ];
const category: CategoryData = { const category = new CategoryDataStub()
category: 'category name', .withChildren([ script ]);
children: [ script ],
};
// act // act
const actual = parseCategory(category, context).scripts; const actual = parseCategory(category, context).scripts;
// assert // assert
@@ -115,10 +106,8 @@ describe('CategoryParser', () => {
const context = new CategoryCollectionParseContextStub() const context = new CategoryCollectionParseContextStub()
.withCompiler(compiler); .withCompiler(compiler);
const expected = [ parseScript(script, context) ]; const expected = [ parseScript(script, context) ];
const category: CategoryData = { const category = new CategoryDataStub()
category: 'category name', .withChildren([ script ]);
children: [ script ],
};
// act // act
const actual = parseCategory(category, context).scripts; const actual = parseCategory(category, context).scripts;
// assert // assert
@@ -128,10 +117,8 @@ describe('CategoryParser', () => {
// arrange // arrange
const callableScript = ScriptDataStub.createWithCall(); const callableScript = ScriptDataStub.createWithCall();
const scripts = [ callableScript, ScriptDataStub.createWithCode() ]; const scripts = [ callableScript, ScriptDataStub.createWithCode() ];
const category: CategoryData = { const category = new CategoryDataStub()
category: 'category name', .withChildren(scripts);
children: scripts,
};
const compiler = new ScriptCompilerStub() const compiler = new ScriptCompilerStub()
.withCompileAbility(callableScript); .withCompileAbility(callableScript);
const context = new CategoryCollectionParseContextStub() const context = new CategoryCollectionParseContextStub()
@@ -148,19 +135,16 @@ describe('CategoryParser', () => {
const duplicatedCode = `${commentDelimiter} duplicate-line\n${commentDelimiter} duplicate-line`; const duplicatedCode = `${commentDelimiter} duplicate-line\n${commentDelimiter} duplicate-line`;
const parseContext = new CategoryCollectionParseContextStub() const parseContext = new CategoryCollectionParseContextStub()
.withSyntax(new LanguageSyntaxStub().withCommentDelimiters(commentDelimiter)); .withSyntax(new LanguageSyntaxStub().withCommentDelimiters(commentDelimiter));
const category: CategoryData = { const category = new CategoryDataStub()
category: 'category name', .withChildren([
children: [ new CategoryDataStub()
{ .withName('sub-category')
category: 'sub-category', .withChildren([
children: [
ScriptDataStub ScriptDataStub
.createWithoutCallOrCodes() .createWithoutCallOrCodes()
.withCode(duplicatedCode), .withCode(duplicatedCode),
], ]),
}, ]);
],
};
// act // act
const act = () => parseCategory(category, parseContext).scripts; const act = () => parseCategory(category, parseContext).scripts;
// assert // assert
@@ -169,14 +153,13 @@ describe('CategoryParser', () => {
}); });
it('returns expected subcategories', () => { it('returns expected subcategories', () => {
// arrange // arrange
const expected: CategoryData[] = [ { const expected = [ new CategoryDataStub()
category: 'test category', .withName('test category')
children: [ ScriptDataStub.createWithCode() ], .withChildren([ ScriptDataStub.createWithCode() ]),
}]; ];
const category: CategoryData = { const category = new CategoryDataStub()
category: 'category name', .withName('category name')
children: expected, .withChildren(expected);
};
const context = new CategoryCollectionParseContextStub(); const context = new CategoryCollectionParseContextStub();
// act // act
const actual = parseCategory(category, context).subCategories; const actual = parseCategory(category, context).subCategories;
@@ -187,17 +170,3 @@ describe('CategoryParser', () => {
}); });
}); });
}); });
function getValidCategory(): CategoryData {
return {
category: 'category name',
children: getTestChildren(),
docs: undefined,
};
}
function getTestChildren(): ReadonlyArray<CategoryOrScriptData> {
return [
ScriptDataStub.createWithCode(),
];
}

View File

@@ -29,7 +29,7 @@ describe('CategoryCollectionParseContext', () => {
// arrange // arrange
const expectedError = 'undefined scripting'; const expectedError = 'undefined scripting';
const scripting = undefined; const scripting = undefined;
const functionsData = [ new FunctionDataStub() ]; const functionsData = [ FunctionDataStub.createWithCode() ];
// act // act
const act = () => new CategoryCollectionParseContext(functionsData, scripting); const act = () => new CategoryCollectionParseContext(functionsData, scripting);
// assert // assert
@@ -39,7 +39,7 @@ describe('CategoryCollectionParseContext', () => {
describe('compiler', () => { describe('compiler', () => {
it('constructed as expected', () => { it('constructed as expected', () => {
// arrange // arrange
const functionsData = [ new FunctionDataStub() ]; const functionsData = [ FunctionDataStub.createWithCode() ];
const syntax = new LanguageSyntaxStub(); const syntax = new LanguageSyntaxStub();
const expected = new ScriptCompiler(functionsData, syntax); const expected = new ScriptCompiler(functionsData, syntax);
const language = ScriptingLanguage.shellscript; const language = ScriptingLanguage.shellscript;

View File

@@ -0,0 +1,99 @@
import 'mocha';
import { expect } from 'chai';
import { ExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/ExpressionsCompiler';
describe('ExpressionsCompiler', () => {
describe('parameter substitution', () => {
describe('substitutes as expected', () => {
// arrange
const testCases = [ {
name: 'with different parameters',
code: 'He{{ $firstParameter }} {{ $secondParameter }}!',
parameters: {
firstParameter: 'llo',
secondParameter: 'world',
},
expected: 'Hello world!',
}, {
name: 'with single parameter',
code: '{{ $parameter }}!',
parameters: {
parameter: 'Hodor',
},
expected: 'Hodor!',
}];
for (const testCase of testCases) {
it(testCase.name, () => {
const sut = new MockableExpressionsCompiler();
// act
const actual = sut.compileExpressions(testCase.code, testCase.parameters);
// assert
expect(actual).to.equal(testCase.expected);
});
}
});
describe('throws when expected value is not provided', () => {
// arrange
const noParameterTestCases = [
{
name: 'empty parameters',
code: '{{ $parameter }}!',
parameters: {},
expectedError: 'parameter value(s) not provided for: "parameter"',
},
{
name: 'undefined parameters',
code: '{{ $parameter }}!',
parameters: undefined,
expectedError: 'parameter value(s) not provided for: "parameter"',
},
{
name: 'unnecessary parameter provided',
code: '{{ $parameter }}!',
parameters: {
unnecessaryParameter: 'unnecessaryValue',
},
expectedError: 'parameter value(s) not provided for: "parameter"',
},
{
name: 'undefined value',
code: '{{ $parameter }}!',
parameters: {
parameter: undefined,
},
expectedError: 'parameter value(s) not provided for: "parameter"',
},
{
name: 'multiple values are not',
code: '{{ $parameter1 }}, {{ $parameter2 }}, {{ $parameter3 }}',
parameters: {},
expectedError: 'parameter value(s) not provided for: "parameter1", "parameter2", "parameter3"',
},
{
name: 'some values are provided',
code: '{{ $parameter1 }}, {{ $parameter2 }}, {{ $parameter3 }}',
parameters: {
parameter2: 'value',
},
expectedError: 'parameter value(s) not provided for: "parameter1", "parameter3"',
},
];
for (const testCase of noParameterTestCases) {
it(testCase.name, () => {
const sut = new MockableExpressionsCompiler();
// act
const act = () => sut.compileExpressions(testCase.code, testCase.parameters);
// assert
expect(act).to.throw(testCase.expectedError);
});
}
});
});
});
class MockableExpressionsCompiler extends ExpressionsCompiler {
constructor() {
super();
}
}

View File

@@ -1,6 +1,6 @@
import 'mocha'; import 'mocha';
import { expect } from 'chai'; import { expect } from 'chai';
import { generateIlCode } from '@/application/Parser/Script/Compiler/ILCode'; import { generateIlCode } from '@/application/Parser/Script/Compiler/Expressions/ILCode';
describe('ILCode', () => { describe('ILCode', () => {
describe('getUniqueParameterNames', () => { describe('getUniqueParameterNames', () => {

Some files were not shown because too many files have changed in this diff Show More