Compare commits

..

32 Commits

Author SHA1 Message Date
undergroundwires
67c3677621 win, linux, mac: fix typos and improve naming
- Use instruction format such as "do this, do that" to provide clear,
  direct instructions. This format minimize confusion and is easy to
  follow. They are specific and leave no room for interpretation,
  stating precisely what needs to be done without ambiguity.
- Fix typos and grammar issues.
- Improve consistency in script and category names.
- Revise sentences for more natural English language flow.
- Change brand name casing to match official branding.
- Change title case (all words start capitalized) to sentence case.
- Prioritize consistency over variations.
- Add minor documentation to explain scripts where the names are not
  clear.
- Add naming guidelines.
2023-10-13 20:14:33 +02:00
undergroundwires
bab6316e76 win: fix and improve AppCompat disabling #255
- Introduce a new parent category: 'Disable Application Compatibility
  framework" for better categorization.
- Move following existing scripts under the new category:
  - Disable Application Impact Telemetry (AIT)
  - Disable steps recorder
  - Disable Inventory Collector
  - Program Compatibility Assistant Service
- Add new scripts new scripts within the same category:
  - Disable Application Compatibility Engine
  - Disable "Program Compatibility Assistant (PCA)" feature
  - Disable "Program Compatibility Assistant Service" (`PcaSvc`)
- Add missing revert codes for:
  - 'Disable steps recorder'
- Fix revert codes for scripts:
  - 'Disable Inventory Collector'
  - 'Disable Application Impact Telemetry (AIT)' (as pointed in #255).
- Add extensive documentation for all related scripts.
- Rename scripts for clarity:
  - 'Disable Inventory Collector' > 'Disable "Inventory Collector"
    task'.
  - 'Program Compatibility Assistant Service' > 'Disable "Program
    Compatibility Assistant Service" (`PcaSvc`) service'.
  - 'Disable steps recorder' > 'Disable Steps Recorder (collects
    screenshots, mouse/keyboard input and UI data)'.
2023-10-12 14:49:35 +02:00
undergroundwires
48730bca05 Implement new UI component for icons #230
- Introduce `AppIcon.vue`, offering improved performance over the
  previous `fort-awesome` dependency. This implementation reduces bundle
  size by 67.31KB (tested for web using `npm run build -- --mode prod`).
- Migrate Font Awesome 5 icons to Font Awesome 6.

This commit facilitates migration to Vue 3.0 (#230) and ensures no Vue
component remains tightly bound to a specific Vue version, enhancing
code portability.

Font Awesome license is not included because Font Awesome revokes its
right:

> "Attribution is no longer required as of Font Awesome 3.0"
>
> Sources:
>
> - https://fontawesome.com/v4/license/ (archived: https://web.archive.org/web/20231003213441/https://fontawesome.com/v4/license/, https://archive.ph/Yy9j5)
> - https://github.com/FortAwesome/Font-Awesome/wiki (archived: https://web.archive.org/web/20231003214646/https://github.com/FortAwesome/Font-Awesome/wiki, https://archive.ph/C6sXv)

This commit removes following third-party production dependencies:

- `@fortawesome/vue-fontawesome`
- `@fortawesome/free-solid-svg-icons`
- `@fortawesome/free-regular-svg-icons`
- `@fortawesome/free-brands-svg-icons`
- `@fortawesome/fontawesome-svg-core`
2023-10-11 18:38:19 +02:00
undergroundwires
698b570ee6 Fix working directory in CI/CD web release
This commit fixes the CI/CD website release process which was failing
due to an incorrect working directory setting. The `working-directory`
is now correctly set within the action workflow, ensuring the `npm run
install-deps` command runs in project root directory where
`package.json` exists.
2023-10-10 15:37:59 +02:00
undergroundwires
a3f11dff18 win: improve app reversion and docs #260
This commit prepares for #260, aiming for a hard delete of system apps,
and necessitating a more reliable app reversion method.

- Improve documentation:
  - Add existence status for latest OS versions.
  - Add command for quick future testing.
  - Use archive links.
  - Document categories.
  - Add documentation to list of default apps to give context about why
    the package is here.
  - Fix wrong store URL for Cortana app.
  - Unify documentation of excluded apps.
- Fix categorization:
  - Categorize uninstallation of Windows store apps.
  - Remove "Zune" category (flatten children apps) to be able to align
    with latest branding.
  - Categorize uninstallation of Candy Crush apps.
  - Categorize uninstallation of OOBE apps.
- Rename:
  - "Uninstall Windows store apps" to "Uninstall Windows apps" as these
    apps are not necessarily store apps.
  - "Xbox Game Bar Plugin appcache" to "Xbox Game Bar Plugin".
  - "Groove Music" to "Windows Media Player".
  - "Movies and TV" to "Movies & TV".
  - "Your Phone" to "Phone Link".
  - "Cred Dialog Host" to "Credentials Dialog Host".
  - "Windows Voice Recorder" to "Windows Sound Recorder".
  - "Remote Desktop" to "Microsoft Remote Desktop"
  - "Microsoft To Do" to "Microsoft To Do: Lists, Tasks & Reminders".
  - "People Hub app (People Experience Host)" to "People Hub app".
  - "My Office" to "Microsoft 365 (Office)".
  - "iHeartRadio" to "iHeart: Radio, Music, Podcasts".
  - "Duolingo" to "Duolingo - Language Lessons".
  - "Photoshop Express" to "Adobe Photoshop Express".
  - "Spotify" to "Spotify - Music and Podcasts".
  - "Windows Alarms and Clock" to "Windows Clock".
  - "OOBE Network Captive Port" to "OOBE Network Captive Portal".
  - "Secure Assessment Browser app (breaks Microsoft Intune/Graph)" to
    "Take a Test app".
  - "Windows 10 Family Safety / Parental Controls" > "Microsoft Family
    Safety / Parental control".
  - "People / People Bar App on taskbar (People Experience Host)" > "My
    People"
  - "MSN News" > "Microsoft News"
  - "Minecraft for Windows 10" > "Minecraft for Windows"
  - "Snip & Sketch" > "Snipping Tool"
  - "Bio enrollment" > "Hello setup UI"
- Fix package names for:
  - `AdobeSystemIncorporated.AdobePhotoshop` >
    `AdobeSystemsIncorporated.AdobePhotoshopExpress`
2023-10-09 16:21:26 +02:00
undergroundwires
5e359c2fb8 win: fix and improve network data usage reset #265
Fix `Clear (Reset) Network Data Usage` trying to delete other files from
Windows system directory.

Changes:

- Precisely target the deletion of `C:\System32\sru\SRUDB.dat`.
- Improve documentation.
- Handle explicitly and better if `DPS` service is missing.
- Rename script from `Clear (Reset) Network Data Usage` to `Clear System
  Resource Usage Monitor (SRUM) data` for clearer representation.
- Migrate script from batchfile to PowerShell for better
  maintainability and readability.
- Add user-friendly output messages.
- Improve script logic to avoid unnecessary service start/stop when the
  file doesn't exist.
2023-10-08 15:55:06 +02:00
undergroundwires
2147eae687 Add developer toolkit UI component
The commit adds a new a UI component that's enabled in development mode.
This component, initially, provides a button that wen clicked, logs all
the script and category names to the console. It helps revising names
used throughout the application.

By having this component in a conditionally rendered component, it's
excluded from the production builds.
2023-10-07 15:14:53 +02:00
undergroundwires
286295128d win: relocate and document SecHealthUI #190
- Move removal of `SecHealthUI` app to "Privacy over security" category.
- Emphasize disruptive behavior in the script name.
- Add comprehensive documentation
2023-10-06 14:02:11 +02:00
undergroundwires
8501495c17 win: improve Edge & OneDrive shortcut removal #73
- Add script to remove Edge shortcuts upon uninstallation.
- Unify OneDrive shortcut removal logic with Edge's, introducing revert
  feature to the OneDrive removal script.
- Add more extensive documentation.
- Rename "Delete OneDrive shortcuts" to "Remove OneDrive shortcuts" to
  have consistent naming.
2023-10-05 11:50:21 +02:00
undergroundwires
888c9166fc win: add removal of Edge assocations #64
This commit introduces scripts for cleaning up file and URL associations
related to Microsoft Edge, enhancing the uninstallation process. The
changes adress the issues detailed in #64, improving system reliability,
integrity and security by preventing lingering associations.

Changes include:

- Introduce scripts to clear Edge browser file and URL associations.
- Provide extensive documentation for related scripts.
- Ensure thorough cleanup of URL, file, OpenWith menu, and toast
  associations.
- Recommend removing Microsoft Edge (Legacy) Dev Tools Client app on
  Strict to align with other Edge legacy removal recommendations.
2023-10-04 11:22:47 +02:00
undergroundwires
e5f6edf405 linux: fix obsolete Firefox DPI script #239
- Replace obsolete "Firefox First party isolation" with "Firefox state
  partitioning".
- Add comprehensive documentation for the new scripts.
- Introduce enabling dynamic First-Party Isolation (dFPI)
- Disable deprecated First-Party Isolation (FPI) to avoid conflicts with
  dFPI.
- Add script to enable Firefox network partitioning to cover
  functionality of older FPI script.
2023-10-03 12:36:06 +02:00
undergroundwires
e8a52f717d win, linux: improve VSCode setting robustness #196
This commit enhances the robustness of setting VSCode configurations,
ensuring consistent and reliable operation even in edge cases, such as
when the settings file is empty. This commit also uniforms behavior of
Linux and Windows modification of VSCode settings.

On Windows:

- Move parameters to on top of scripts to be able to easily test the
  scripts using PowerShell without compiling.
- Add a check to exit the script with an error message if the attempt to
  parse the JSON content fails.
- Omit the `OutString` cmdlet from the pipeline in the script for
  converting JSON file content to a PowerShell object. `Out-String` is
  unnecessary in this context because `Get-Content` already outputs the
  file content as a string array, which `ConvertFrom-Json` effectively.
  Additionally, using `Out-String` could potentially introduce issues by
  concatenating file content into a single string, causing
  `ConvertFrom-Json` to fail when processing pretty-printed JSON. By
  removing `Out-String`, the script is streamlined and potential errors
  are avoided.
- Add logic to handle empty settings file. Add an additional check for
  empty settings file, if the file is empty, the script writes a default
  empty JSON object (`{}`) to the file. The operation is logged to
  ensure transparency, notifying the user of the action taken. This
  change removes fails due to empty setting files.
- When reverting, do not fail if the setting file is missing because it
  means that default settings are already in-place.
- When reverting, show informative message if the key does not exist or
  does not have the value set by privacy.sexy and do not take any
  further action.
- If the desired value is already set, show a message for it and skip
  updating the setting file.

On Linux:

- Handles empty `settings.json` similarly to Windows.
- Add more user friendly error if JSON file cannot be parsed.
2023-10-02 14:33:55 +02:00
undergroundwires
d45750428c win: fix and improve temp dir cleanup #176, #89
This commit improve cleanup of temporary directories on Windows,
addressing issues #176 and #89.

Changes include:

- Fix side-effects caused by this script by clearing the contents of
  directories rather than deleting the directories themselves.
- Add the removal of Prefetch directory contents, which stores temporary
  files and can enhance privacy and free up disk space when cleared.
- Remove the command `del /f /q %localappdata%\Temp\*` due to its
  redundancy.
- Improve the granularity and documentation of cleanup scripts, and
  moving the `Clear temporary Windows files` category up in the hierarchy
  for better structure and clarity.

Co-authored-by: iam-py-test <84232764+iam-py-test@users.noreply.github.com>
2023-10-01 17:42:25 +02:00
undergroundwires
cf55ca9e28 Add Scoop download instructions #174
- Add "Further Installation Options" section.
- Move releases page reference to the new section to keep Get Started
  simple.

Co-authored-by: MrEddX <101912712+Zliced13@users.noreply.github.com>
2023-09-29 14:03:07 +02:00
undergroundwires
3e5239f7d3 Add SAST security checks with SECURITY.md #178
This commit incorporates Static Analysis Security Testing (SAST) using
CodeQL. This integration will enforce consistent security assessments
with every change and on a predetermined schedule.

This commit also involves a restructure of security checks. The existing
security-checks workflow is renamed to better reflect its functionality
related to dependency audits.

These changes will enhance the project's resilience against potential
vulnerabilities in both the codebase and third-party dependencies.

Changes include:

- Remove older LGTM badge that's replaced by SAST checks.
- Rename `checks.security.yaml` to `checks.security.dependencies.yaml`,
  reinforcing the focus on dependency audits.
- Update `README.md`, ensuring the clear representation of security
  check statuses, including new SAST integration.
- Add new `SECURITY.md`, establishing the protocol for reporting
  vulnerabilities and outlining the project's commitment to robust
  security testing.
- Enhance `docs/tests.md` with detailed information on the newly
  integrated security checks.
- Add reference to SECURITY.md in README.md.
2023-09-28 15:19:09 +02:00
undergroundwires
7669985f8e Fix Docker build and improve checks #220
This commit improves multiple aspects of Docker builds:

- Enable artifact output validation for Dockerfile.
- Correct the path references in Dockerfile for the distribution
  directory.
- Add Dockerfile specific indentation rules to `.editorconfig`.
- Use `npm run install-deps` for dependency installation, enhancing
  build reliability.
- Add automation script `verify-web-server-status.js` to verify running
  web server on given URL.
- Introduce automated build verification for Dockerfile:
  - On macOS, install Docker with colima as the container runtime
    because default agents do not include Docker and Docker runtime is
    not installed due to licensing issues (see actions/runner-images#17).
  - On Windows, there's no Linux container support (actions/runner#904,
    actions/runner-images#1143), so keep the checks for macOS and Ubuntu
    only.
2023-09-27 19:53:40 +02:00
undergroundwires-bot
5047c9b6e7 ⬆️ bump everywhere to 0.12.4 2023-09-26 11:45:04 +00:00
undergroundwires
bd2082e8c5 Fix slow appearance of nodes on tree view
The tree view rendering performance is optimized by improving the node
render queue ordering. The node rendering order is modified based on the
expansion state and the depth in the hierarchy, leading to faster
rendering of visible nodes. This optimization is applied when the tree
nodes are not expanded to improve the rendering speed.

This new ordering ensures that nodes are rendered more efficiently,
prioritizing nodes that are collapsed and are at a higher level in the
hierarchy.
2023-09-25 14:21:29 +02:00
undergroundwires
8f188acd3c Fix loss of tree node state when switching views
This commit fixes an issue where the check state of categories was lost
when toggling between card and tree views. This is solved by immediately
emitting node state changes for all nodes. This ensures consistent view
transitions without any loss of node state information.

Furthermore, this commit includes added unit tests for the modified code
sections.
2023-09-24 20:34:47 +02:00
undergroundwires
0303ef2fd9 Fix outdated and broken links in README #161
This commit fixes issues with download URLs of desktop application
artifacts on README.md

- Corrected typo in Linux AppImage link
- Updated older version links to the newest release

Co-authored-by: MrEddX <66828538+MrEddX@users.noreply.github.com>
2023-09-23 10:33:46 +02:00
undergroundwires
cb21a970b6 win: fix Defender scan artifacts removal #246
- Modify script to run as `TrustedInstaller`, resolving access right
  problems discussed in #246.
- Change script name for better alignment with its functionality.
- Improve script description for clarity and detailed documentation.
2023-09-22 14:11:52 +02:00
undergroundwires
203daeb4a2 win: fix delivery optimization side-effects #173
- Add non-intrusive way to disable delivery optimization. This new
  script do not introduce side-effects caused by disabling Delivery
  Optimization service.
- Recomend delivery optimization service (`DoSvc`) only on Strict
  mode, removing it from Standard recommendation.
- Categorize delivery optimization disabling under one category.
- Move disabling delivery optimization to "Disable OS collection" >
  "Disable Windows Update data collection".
- Add more documentation.
2023-09-21 11:40:15 +02:00
undergroundwires
60dde11311 win: fix uninstallation of newer Edge #236
- Fix script failing when multiple installations of Edge is found.
- Fix Edge not being able to be uninstalled due in newer Edge versions.
- Add documentation
- Add missing revert script
2023-09-20 07:48:50 +02:00
undergroundwires
8b930fc57c Rewrite tooltip UI for efficiency and Vue 3.0 #230
- Introduce a new UI component for tooltips.
- Fix tooltip arrow misalignment issues in code download/execution
  instructions dialogs.

Reasons for dropping `v-tooltip` dependency:

- Lack of support for Vue 3.0, which blocks migration to Vue 3.0 (see
  #230).
- Inability to render HTML content that's required for privacy.sexy.
- Inefficient, adding an extra 162.48 KB to the production bundle for
  web distribution (tested using `npm run build -- --mode production`).

Advantages of adopting `floating-ui` (Floating UI):

- Compatibility across multiple Vue versions including 2.0, 2.7, and 3.0.
- Reduced boilerplate resulting in cleaner, more maintainable code.
- Efficient position recalculations without reinventing the wheel.
2023-09-18 17:57:50 +02:00
undergroundwires
f810ed0c14 Fix no spacing after lists in documentation text
This commit adds missing vertical margin paragraphs that appear after
lists. It also changes vertical margin gap to match the font size along
with refactoring that makes paragraph gap modification easier to
understand.
2023-09-17 13:38:40 +02:00
undergroundwires
53222fd83c Fix compiler bug with nested optional arguments
This commit fixes compiler bug where it fails when optional values are
compiled into absent values in nested calls.

- Throw exception with more context for easier future debugging.
- Add better validation of argument values for nested calls.
- Refactor `FunctionCallCompiler` for better clarity and modularize it
  to make it more maintainable and testable.
- Refactor related interface to not have `I` prefix, and
  function/variable names for better clarity.

Context:

Discovered this issue while attempting to call
`RunInlineCodeAsTrustedInstaller` which in turn invokes `RunPowerShell`
for issue #246. This led to the realization that despite parameters
flagged as optional, the nested argument compilation didn't support
them.
2023-09-16 16:11:41 +02:00
undergroundwires
a1f2497381 Fix wrong action path in website CI deployment 2023-09-15 13:36:05 +02:00
Couleur
c27172c32e win: refactor update.mode key for VSCode #215
Removed unnecessary single quotes wrapping the value `manual` in yaml.
2023-09-14 12:47:33 +02:00
undergroundwires
6e9b65d8b1 win: fix, improve disabling automatic updates #252
- Add script to disable `WaaSMedicSvc` service (#252)
- Refine script granularity for more precise control.
- Introduce detailed documentation for the category and associated
  scripts.
- Fix `ScheduledInstallTime` being set to `3` which schedules updates to
  install at 3 AM.
- Fix `ScheduledInstallDay` is being set to `0` which schedules daily
  update installation.
- Fix `NoAutoUpdate` being set to `0` (enable) instead of `1` (disable).
- Add disabling of missing `wuauserv` service.
- Add parent category for disabling Windows update services for better
  organization.
2023-09-13 13:18:14 +02:00
billy
6d301f9961 win: fix Edge telemetry disabling for v116+ #242 2023-09-12 13:28:22 +02:00
undergroundwires
659fea7afc win: fix Windows spotlight revert, docs, recommend
- Move disabling Windows Spotlight from Standard to Strict
  recommendation due to unexpected behavior for some users (#65).
- Enhance documentation.
- Correct revert code to ensure return to the default OS state.
2023-09-11 14:08:33 +02:00
undergroundwires-bot
e0303058a3 ⬆️ bump everywhere to 0.12.3 2023-09-10 11:21:25 +00:00
159 changed files with 9520 additions and 2962 deletions

View File

@@ -5,3 +5,7 @@ end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 100
[{Dockerfile}]
indent_style = space
indent_size = 4

View File

@@ -8,4 +8,5 @@ runs:
-
name: Run `npm ci` with retries
shell: bash
run: npm run install-deps -- --ci --root-directory "${{ inputs.working-directory }}"
run: npm run install-deps -- --ci
working-directory: ${{ inputs.working-directory }}

View File

@@ -1,4 +1,4 @@
name: build-checks
name: checks.build
on:
push:
@@ -68,3 +68,33 @@ jobs:
-
name: Verify bundled desktop build artifacts
run: npm run check:verify-build-artifacts -- --electron-bundled
build-docker:
strategy:
matrix:
os: [ macos, ubuntu ] # Windows runners do not support Linux containers
fail-fast: false # Allows to see results from other combinations
runs-on: ${{ matrix.os }}-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Install Docker on macOS
if: matrix.os == 'macos' # macOS runner is missing Docker
run: |-
# Install Docker
brew install docker
# Docker on macOS misses daemon due to licensing, so install colima as runtime
brew install colima
# Start the daemon
colima start
-
name: Build Docker image
run: docker build -t undergroundwires/privacy.sexy:latest .
-
name: Run Docker image on port 8080
run: docker run -d -p 8080:80 --rm --name privacy.sexy undergroundwires/privacy.sexy:latest
-
name: Check server is up and returns HTTP 200
run: node ./scripts/verify-web-server-status.js --url http://localhost:8080

View File

@@ -1,4 +1,4 @@
name: security-checks
name: checks.security.dependencies
on:
push:

View File

@@ -0,0 +1,42 @@
name: checks.security.sast
on:
push:
pull_request:
schedule:
- cron: '0 0 * * 0' # at 00:00 on every Sunday
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [
javascript # analyzes code written in JavaScript, TypeScript and both.
]
steps:
-
name: Checkout
uses: actions/checkout@v3
-
name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
queries: +security-and-quality
-
name: Autobuild
uses: github/codeql-action/autobuild@v2
-
name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{ matrix.language }}"

View File

@@ -84,7 +84,7 @@ jobs:
uses: ./app/.github/actions/setup-node
-
name: "App: Install dependencies"
uses: ./.github/actions/npm-install-dependencies
uses: ./app/.github/actions/npm-install-dependencies
with:
working-directory: app
-

View File

@@ -1,5 +1,40 @@
# Changelog
## 0.12.4 (2023-09-25)
* win: fix Windows spotlight revert, docs, recommend | [659fea7](https://github.com/undergroundwires/privacy.sexy/commit/659fea7afcabcd0ea273cfdcc8c4bae190c126f3)
* win: fix Edge telemetry disabling for v116+ #242 | [6d301f9](https://github.com/undergroundwires/privacy.sexy/commit/6d301f99616ed49975876803d0098eafe4d3cb2e)
* win: fix, improve disabling automatic updates #252 | [6e9b65d](https://github.com/undergroundwires/privacy.sexy/commit/6e9b65d8b1b481c1471dde90876c37838b4ac4e5)
* win: refactor `update.mode` key for VSCode #215 | [c27172c](https://github.com/undergroundwires/privacy.sexy/commit/c27172c32e7c316b7cb0f44cab611eed89ca034e)
* Fix wrong action path in website CI deployment | [a1f2497](https://github.com/undergroundwires/privacy.sexy/commit/a1f24973813ccbdd7e1f06c64e1912a991a6bb64)
* Fix compiler bug with nested optional arguments | [53222fd](https://github.com/undergroundwires/privacy.sexy/commit/53222fd83c2846089746a217482195806f960d18)
* Fix no spacing after lists in documentation text | [f810ed0](https://github.com/undergroundwires/privacy.sexy/commit/f810ed0c147c2a46cae3b70b635ed81128646fff)
* Rewrite tooltip UI for efficiency and Vue 3.0 #230 | [8b930fc](https://github.com/undergroundwires/privacy.sexy/commit/8b930fc57c8ee6691ed6165bcb27d97e64a1a0c0)
* win: fix uninstallation of newer Edge #236 | [60dde11](https://github.com/undergroundwires/privacy.sexy/commit/60dde11311a2409537f5965f370b0daaaec53339)
* win: fix delivery optimization side-effects #173 | [203daeb](https://github.com/undergroundwires/privacy.sexy/commit/203daeb4a2fca0a0295cbc2a736394f9f87725e6)
* win: fix Defender scan artifacts removal #246 | [cb21a97](https://github.com/undergroundwires/privacy.sexy/commit/cb21a970b6b867e1476a5eb8a72b9a7fdd53a744)
* Fix outdated and broken links in README #161 | [0303ef2](https://github.com/undergroundwires/privacy.sexy/commit/0303ef2fd98b36306523e2a0c5f5ae812a4c6c99)
* Fix loss of tree node state when switching views | [8f188ac](https://github.com/undergroundwires/privacy.sexy/commit/8f188acd3c2d93e40c89569c74bc5cff992f0052)
* Fix slow appearance of nodes on tree view | [bd2082e](https://github.com/undergroundwires/privacy.sexy/commit/bd2082e8c574db065bb4462f30ea3ace2cb028cb)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.12.3...0.12.4)
## 0.12.3 (2023-09-09)
* linux: use user.js over prefs.js for Firefox #232 | [dae6d11](https://github.com/undergroundwires/privacy.sexy/commit/dae6d114daab6857d773071211eb57619b136281)
* win: fix typo in Defender retention script #213 | [35be05d](https://github.com/undergroundwires/privacy.sexy/commit/35be05df2094ea8bba4ee4725e6fa4956a79493d)
* Improve desktop runtime execution tests | [ad0576a](https://github.com/undergroundwires/privacy.sexy/commit/ad0576a752f8fd6ea2f917a59173fe61f9951246)
* Fix Windows artifact naming in desktop packaging | [f4d86fc](https://github.com/undergroundwires/privacy.sexy/commit/f4d86fccfd0e73e94c8c6e400a33514900bc5abe)
* Refactor and improve external URL checks | [19e42c9](https://github.com/undergroundwires/privacy.sexy/commit/19e42c9c52a18c813ded4265e687e01032cdd4c8)
* Fix memory leaks via auto-unsubscribing and DI | [eb096d0](https://github.com/undergroundwires/privacy.sexy/commit/eb096d07e276e1b4c8040220c47f186d02841e14)
* Refactor build configs and improve CI/CD checks | [0a2a1a0](https://github.com/undergroundwires/privacy.sexy/commit/0a2a1a026b0efb29624be82b06536c518c1ea439)
* Introduce retry mechanism for npm install in CI/CD | [4beb1bb](https://github.com/undergroundwires/privacy.sexy/commit/4beb1bb5748a60886210187ca3cdc7f4b41067c0)
* win: fix disable recent apps revert #211, #248 | [4ce327e](https://github.com/undergroundwires/privacy.sexy/commit/4ce327eb6af542ed2916d649553e5e1ba5833882)
* Change license to AGPLv3 | [821cc62](https://github.com/undergroundwires/privacy.sexy/commit/821cc62c4c8347cb76d041f82f574754e4d948c5)
* Introduce new TreeView UI component | [65f121c](https://github.com/undergroundwires/privacy.sexy/commit/65f121c451af87315e1c91df4198562e0445b2c2)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.12.2...0.12.3)
## 0.12.2 (2023-08-25)
* Add automated checks for desktop app runtime #233 | [04b3133](https://github.com/undergroundwires/privacy.sexy/commit/04b3133500485d0d278a81a177a1677134131405)

View File

@@ -1,13 +1,16 @@
# Build
FROM node:lts-alpine as build-stage
FROM node:lts-alpine AS build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
RUN npm run install-deps
RUN npm run build \
&& npm run check:verify-build-artifacts -- --web
RUN mkdir /dist \
&& dist_directory=$(node 'scripts/print-dist-dir.js' --web) \
&& cp -a "${dist_directory}/." '/dist'
# Production stage
FROM nginx:stable-alpine as production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
FROM nginx:stable-alpine AS production-stage
COPY --from=build-stage /dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -16,14 +16,6 @@
src="https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat"
/>
</a>
<!-- Code quality -->
<br />
<a href="https://lgtm.com/projects/g/undergroundwires/privacy.sexy/context:javascript" target="_blank" rel="noopener noreferrer">
<img
alt="Language grade: JavaScript/TypeScript"
src="https://img.shields.io/lgtm/grade/javascript/g/undergroundwires/privacy.sexy.svg?logo=lgtm&logoWidth=18"
/>
</a>
<a href="https://codeclimate.com/github/undergroundwires/privacy.sexy/maintainability" target="_blank" rel="noopener noreferrer">
<img
alt="Maintainability"
@@ -50,6 +42,20 @@
src="https://github.com/undergroundwires/privacy.sexy/workflows/e2e-tests/badge.svg"
/>
</a>
<!-- Security checks -->
<br />
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.security.sast.yaml" target="_blank" rel="noopener noreferrer">
<img
alt="Status of dependency security checks"
src="https://github.com/undergroundwires/privacy.sexy/workflows/checks.security.sast/badge.svg"
/>
</a>
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.security.dependencies.yaml" target="_blank" rel="noopener noreferrer">
<img
alt="Status of Static Analysis Security Testing (SAST)"
src="https://github.com/undergroundwires/privacy.sexy/workflows/checks.security.dependencies/badge.svg"
/>
</a>
<!-- Checks -->
<br />
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.quality.yaml" target="_blank" rel="noopener noreferrer">
@@ -58,16 +64,10 @@
src="https://github.com/undergroundwires/privacy.sexy/workflows/quality-checks/badge.svg"
/>
</a>
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.security.yaml" target="_blank" rel="noopener noreferrer">
<img
alt="Security checks status"
src="https://github.com/undergroundwires/privacy.sexy/workflows/security-checks/badge.svg"
/>
</a>
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.build.yaml" target="_blank" rel="noopener noreferrer">
<img
alt="Build checks status"
src="https://github.com/undergroundwires/privacy.sexy/workflows/build-checks/badge.svg"
alt="Status of build checks"
src="https://github.com/undergroundwires/privacy.sexy/workflows/checks.build/badge.svg"
/>
</a>
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.desktop-runtime-errors.yaml" target="_blank" rel="noopener noreferrer">
@@ -122,7 +122,7 @@
## Get started
- 🌍️ **Online**: [https://privacy.sexy](https://privacy.sexy).
- 🖥️ **Offline**: Check [releases page](https://github.com/undergroundwires/privacy.sexy/releases), or download directly for: [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.11.2/privacy.sexy-Setup-0.11.2.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.11.2/privacy.sexy-0.11.2.dmg), [Linux](https://github.com/undergroundwires/pr.vacy.sexy/releases/download/0.11.2/privacy.sexy-0.11.2.AppImage).
- 🖥️ **Offline**: Download directly for: [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.4/privacy.sexy-Setup-0.12.4.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.4/privacy.sexy-0.12.4.dmg), [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.4/privacy.sexy-0.12.4.AppImage). For more options, see [here](#additional-install-options).
Online version does not require to run any software on your computer. Offline version has more functions such as running the scripts directly.
@@ -150,6 +150,16 @@ Online version does not require to run any software on your computer. Offline ve
**Contribute 👷**. Contributions of any type are welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) as the starting point. It includes useful information like [how to add new scripts](./CONTRIBUTING.md#extend-scripts).
## Additional Install Options
- Check the [releases page](https://github.com/undergroundwires/privacy.sexy/releases) for all available versions.
- Using [Scoop](https://scoop.sh/#/apps?q=privacy.sexy&s=2&d=1&o=true) package manager on Windows:
```powershell
scoop bucket add extras
scoop install privacy.sexy
```
## Development
Refer to [development.md](./docs/development.md) for Docker usage and reading more about setting up your development environment.
@@ -157,3 +167,7 @@ Refer to [development.md](./docs/development.md) for Docker usage and reading mo
Check [architecture.md](./docs/architecture.md) for an overview of design and how different parts and layers work together. You can refer to [application.md](./docs/application.md) for a closer look at application layer codebase and [presentation.md](./docs/presentation.md) for code related to GUI layer. [collection-files.md](./docs/collection-files.md) explains the YAML files that are the core of the application and [templating.md](./docs/templating.md) documents how to use templating language in those files. In [ci-cd.md](./docs/ci-cd.md), you can read more about the pipelines that automates maintenance tasks and ensures you get what see.
[docs/](./docs/) folder includes all other documentation.
## Security
Security is a top priority at privacy.sexy. An extensive commitment to security verification ensures this priority. For any security concerns or vulnerabilities, please consult the [Security Policy](./SECURITY.md).

31
SECURITY.md Normal file
View File

@@ -0,0 +1,31 @@
# Security Policy
privacy.sexy takes security seriously. Commitment is made to address all security issues with urgency. Responsible reporting of any discovered vulnerabilities in the project is highly encouraged.
## Reporting a Vulnerability
Efforts to responsibly disclose findings are greatly appreciated. To report a security vulnerability, follow these steps:
- For general vulnerabilities, [open an issue](https://github.com/undergroundwires/privacy.sexy/issues/new/choose) using the bug report template.
- For sensitive matters, [contact the developer directly](https://undergroundwires.dev).
## Security Report Handling
Upon receipt of a security report, the following actions will be taken:
- The report will be confirmed, identifying the affected components.
- The impact and severity of the issue will be assessed.
- Work on a fix and plan a release to address the vulnerability will be initiated.
- The reporter will be kept updated about the progress.
## Testing
Regular and extensive testing is conducted to ensure robust security in the project. Information about testing practices can be found in the [Testing Documentation](./docs/tests.md).
## Support
For additional assistance or any unanswered questions, [submit a GitHub issue](https://github.com/undergroundwires/privacy.sexy/issues/new/choose). Security concerns are a priority, and necessary support to address them is assured.
---
Active contribution to the safety and security of privacy.sexy is thanked. This collaborative effort keeps the project resilient and trustworthy for all.

View File

@@ -174,3 +174,19 @@
- `endCode:` *`string`* (**required**)
- Code that'll be inserted at the end of user created script.
- Global variables such as `$homepage`, `$version`, `$date` can be used using [parameter substitution](./templating.md#parameter-substitution) code syntax such as `Welcome to {{ $homepage }}!`
## Naming guidelines
- Prioritize consistency throughout all names.
- Use an instruction format like "do this, do that" for clear, direct guidance. This approach reduces potential confusion and offers easy-to-follow steps. It provides specific, unambiguous instructions.
- Ensure brand names adhere to their official casing.
- Choose clear and uncomplicated language.
- Favor the terms:
- "Disable" over "Turn off"
- "Configure" over "Set up"
- "Clear" over "Erase" or "Clean"
- "Minimize" over "Limit" or "Reduce" (when it enhances clarity)
- "Remove" over "Uninstall"
- Structure your phrases for clarity.
- For instance, "Disable XX telemetry" or "Clear XX data" are preferred over "Clear data from XX", "Disable telemetry in XX", or "Clear data of XX".
- Use sentence case rather than Title Case.

View File

@@ -60,6 +60,7 @@ See [ci-cd.md](./ci-cd.md) for more information.
1. Build: `docker build -t undergroundwires/privacy.sexy:latest .`
2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy undergroundwires/privacy.sexy:latest`
3. Application should be available at [`http://localhost:8080`](http://localhost:8080)
### Building
@@ -81,11 +82,12 @@ See [ci-cd.md](./ci-cd.md) for more information.
#### Automation scripts
- [**`node scripts/print-dist-dir.js [-- <options>]`**](../scripts/print-dist-dir.js):
- [**`node scripts/print-dist-dir.js [<options>]`**](../scripts/print-dist-dir.js):
- Determines the absolute path of a distribution directory based on CLI arguments and outputs its absolute path.
- Primarily used by automation scripts.
- [**`npm run check:verify-build-artifacts [-- <options>]`**](../scripts/verify-build-artifacts.js):
- Verifies the existence and content of build artifacts. Useful for ensuring that the build process is generating the expected output.
- [**`node scripts/verify-web-server-status.js --url [URL]`**](../scripts/verify-web-server-status.js):
- Checks if a specified server is up with retries and returns an HTTP 200 status code.
## Recommended extensions

View File

@@ -20,7 +20,6 @@ The presentation layer uses an event-driven architecture for bidirectional react
- [**`fonts/`**](./../src/presentation/assets/fonts/): Contains fonts.
- [**`styles/`**](./../src/presentation/assets/styles/): Contains shared styles.
- [**`components/`**](./../src/presentation/assets/styles/components): Contains styles coupled to Vue components.
- [**`vendors-extensions/`**](./../src/presentation/assets/styles/third-party-extensions): Contains styles for third-party components.
- [**`main.scss`**](./../src/presentation/assets/styles/main.scss): Main Sass file, imported by other components as single entrypoint.
- [**`main.ts`**](./../src/presentation/main.ts): Starts Vue app.
- [**`electron/`**](./../src/presentation/electron/): Contains Electron code.

View File

@@ -56,6 +56,11 @@ These checks validate various qualities like runtime execution, building process
- Use [various tools](./../package.json) and [scripts](./../scripts).
- Are automatically executed as [GitHub workflows](./../.github/workflows).
### Security checks
- [`checks.security.sast`](./../.github/workflows/checks.security.sast.yaml): Utilizes CodeQL to conduct Static Analysis Security Testing (SAST) to ensure the secure integrity of the codebase.
- [`checks.security.dependencies`](./../.github/workflows/checks.security.dependencies.yaml): Performs audits on third-party dependencies to identify and mitigate potential vulnerabilities, safeguarding the project from exploitable weaknesses.
## Tests structure
- [`package.json`](./../package.json): Defines test commands and includes tools used in tests.

245
package-lock.json generated
View File

@@ -1,19 +1,15 @@
{
"name": "privacy.sexy",
"version": "0.12.2",
"version": "0.12.4",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "privacy.sexy",
"version": "0.12.2",
"version": "0.12.4",
"hasInstallScript": true,
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.4.0",
"@fortawesome/free-brands-svg-icons": "^6.4.0",
"@fortawesome/free-regular-svg-icons": "^6.4.0",
"@fortawesome/free-solid-svg-icons": "^6.4.0",
"@fortawesome/vue-fontawesome": "^2.0.9",
"@floating-ui/vue": "^1.0.2",
"@juggle/resize-observer": "^3.4.0",
"ace-builds": "^1.23.4",
"cross-fetch": "^4.0.0",
@@ -21,7 +17,6 @@
"file-saver": "^2.0.5",
"markdown-it": "^13.0.1",
"npm": "^9.8.1",
"v-tooltip": "2.1.3",
"vue": "^2.7.14"
},
"devDependencies": {
@@ -1716,6 +1711,7 @@
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz",
"integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==",
"dev": true,
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
@@ -1726,7 +1722,8 @@
"node_modules/@babel/runtime/node_modules/regenerator-runtime": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
"integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
"integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==",
"dev": true
},
"node_modules/@babel/template": {
"version": "7.22.5",
@@ -2520,70 +2517,60 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@fortawesome/fontawesome-common-types": {
"version": "6.4.2",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.2.tgz",
"integrity": "sha512-1DgP7f+XQIJbLFCTX1V2QnxVmpLdKdzzo2k8EmvDOePfchaIGQ9eCHj2up3/jNEbZuBqel5OxiaOJf37TWauRA==",
"hasInstallScript": true,
"engines": {
"node": ">=6"
"node_modules/@floating-ui/core": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz",
"integrity": "sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==",
"dependencies": {
"@floating-ui/utils": "^0.1.3"
}
},
"node_modules/@fortawesome/fontawesome-svg-core": {
"version": "6.4.2",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.2.tgz",
"integrity": "sha512-gjYDSKv3TrM2sLTOKBc5rH9ckje8Wrwgx1CxAPbN5N3Fm4prfi7NsJVWd1jklp7i5uSCVwhZS5qlhMXqLrpAIg==",
"hasInstallScript": true,
"node_modules/@floating-ui/dom": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz",
"integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==",
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.4.2"
"@floating-ui/core": "^1.4.2",
"@floating-ui/utils": "^0.1.3"
}
},
"node_modules/@floating-ui/utils": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.3.tgz",
"integrity": "sha512-uvnFKtPgzLnpzzTRfhDlvXX0kLYi9lDRQbcDmT8iXl71Rx+uwSuaUIQl3DNC7w5OweAQ7XQMDObML+KaYDQfng=="
},
"node_modules/@floating-ui/vue": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@floating-ui/vue/-/vue-1.0.2.tgz",
"integrity": "sha512-sImlAl9mAoCKZLNlwWz2P2ZMJIDlOEDXrRD6aD2sIHAka1LPC+nWtB+D3lPe7IE7FGWSbwBPTnlSdlABa3Fr0A==",
"dependencies": {
"@floating-ui/dom": "^1.4.5",
"vue-demi": ">=0.13.0"
}
},
"node_modules/@floating-ui/vue/node_modules/vue-demi": {
"version": "0.14.6",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz",
"integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-brands-svg-icons": {
"version": "6.4.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.4.2.tgz",
"integrity": "sha512-LKOwJX0I7+mR/cvvf6qIiqcERbdnY+24zgpUSouySml+5w8B4BJOx8EhDR/FTKAu06W12fmUIcv6lzPSwYKGGg==",
"hasInstallScript": true,
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.4.2"
"node": ">=12"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-regular-svg-icons": {
"version": "6.4.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.4.2.tgz",
"integrity": "sha512-0+sIUWnkgTVVXVAPQmW4vxb9ZTHv0WstOa3rBx9iPxrrrDH6bNLsDYuwXF9b6fGm+iR7DKQvQshUH/FJm3ed9Q==",
"hasInstallScript": true,
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.4.2"
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-solid-svg-icons": {
"version": "6.4.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.4.2.tgz",
"integrity": "sha512-sYwXurXUEQS32fZz9hVCUUv/xu49PEJEyUOsA51l6PU/qVgfbTb2glsTEaJngVVT8VqBATRIdh7XVgV1JF1LkA==",
"hasInstallScript": true,
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.4.2"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/vue-fontawesome": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-2.0.10.tgz",
"integrity": "sha512-OTETSXz+3ygD2OK2/vy82cmUBpuJqeOAg4gfnnv+f2Rir1tDIhQg026Q3NQxznq83ZLz8iNqGG9XJm26inpDeg==",
"peerDependencies": {
"@fortawesome/fontawesome-svg-core": "~1 || ~6",
"vue": "~2"
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/@hapi/hoek": {
@@ -10909,7 +10896,8 @@
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"node_modules/lodash.debounce": {
"version": "4.0.8",
@@ -16098,16 +16086,6 @@
"node": ">=12.13.0"
}
},
"node_modules/popper.js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
"deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/postcss": {
"version": "8.4.28",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz",
@@ -20339,17 +20317,6 @@
"node": ">=8"
}
},
"node_modules/v-tooltip": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/v-tooltip/-/v-tooltip-2.1.3.tgz",
"integrity": "sha512-xXngyxLQTOx/yUEy50thb8te7Qo4XU6h4LZB6cvEfVd9mnysUxLEoYwGWDdqR+l69liKsy3IPkdYff3J1gAJ5w==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"lodash": "^4.17.21",
"popper.js": "^1.16.1",
"vue-resize": "^1.0.1"
}
},
"node_modules/validate-npm-package-license": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
@@ -20750,17 +20717,6 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/vue-resize": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/vue-resize/-/vue-resize-1.0.1.tgz",
"integrity": "sha512-z5M7lJs0QluJnaoMFTIeGx6dIkYxOwHThlZDeQnWZBizKblb99GSejPnK37ZbNE/rVwDcYcHY+Io+AxdpY952w==",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"vue": "^2.6.0"
}
},
"node_modules/vue-template-compiler": {
"version": "2.7.14",
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz",
@@ -22436,6 +22392,7 @@
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz",
"integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==",
"dev": true,
"requires": {
"regenerator-runtime": "^0.14.0"
},
@@ -22443,7 +22400,8 @@
"regenerator-runtime": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
"integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
"integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==",
"dev": true
}
}
},
@@ -22947,49 +22905,45 @@
"integrity": "sha512-P6omY1zv5MItm93kLM8s2vr1HICJH8v0dvddDhysbIuZ+vcjOHg5Zbkf1mTkcmi2JA9oBG2anOkRnW8WJTS8Og==",
"dev": true
},
"@fortawesome/fontawesome-common-types": {
"version": "6.4.2",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.2.tgz",
"integrity": "sha512-1DgP7f+XQIJbLFCTX1V2QnxVmpLdKdzzo2k8EmvDOePfchaIGQ9eCHj2up3/jNEbZuBqel5OxiaOJf37TWauRA=="
},
"@fortawesome/fontawesome-svg-core": {
"version": "6.4.2",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.2.tgz",
"integrity": "sha512-gjYDSKv3TrM2sLTOKBc5rH9ckje8Wrwgx1CxAPbN5N3Fm4prfi7NsJVWd1jklp7i5uSCVwhZS5qlhMXqLrpAIg==",
"@floating-ui/core": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz",
"integrity": "sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==",
"requires": {
"@fortawesome/fontawesome-common-types": "6.4.2"
"@floating-ui/utils": "^0.1.3"
}
},
"@fortawesome/free-brands-svg-icons": {
"version": "6.4.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.4.2.tgz",
"integrity": "sha512-LKOwJX0I7+mR/cvvf6qIiqcERbdnY+24zgpUSouySml+5w8B4BJOx8EhDR/FTKAu06W12fmUIcv6lzPSwYKGGg==",
"@floating-ui/dom": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz",
"integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==",
"requires": {
"@fortawesome/fontawesome-common-types": "6.4.2"
"@floating-ui/core": "^1.4.2",
"@floating-ui/utils": "^0.1.3"
}
},
"@fortawesome/free-regular-svg-icons": {
"version": "6.4.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.4.2.tgz",
"integrity": "sha512-0+sIUWnkgTVVXVAPQmW4vxb9ZTHv0WstOa3rBx9iPxrrrDH6bNLsDYuwXF9b6fGm+iR7DKQvQshUH/FJm3ed9Q==",
"requires": {
"@fortawesome/fontawesome-common-types": "6.4.2"
}
"@floating-ui/utils": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.3.tgz",
"integrity": "sha512-uvnFKtPgzLnpzzTRfhDlvXX0kLYi9lDRQbcDmT8iXl71Rx+uwSuaUIQl3DNC7w5OweAQ7XQMDObML+KaYDQfng=="
},
"@fortawesome/free-solid-svg-icons": {
"version": "6.4.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.4.2.tgz",
"integrity": "sha512-sYwXurXUEQS32fZz9hVCUUv/xu49PEJEyUOsA51l6PU/qVgfbTb2glsTEaJngVVT8VqBATRIdh7XVgV1JF1LkA==",
"@floating-ui/vue": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@floating-ui/vue/-/vue-1.0.2.tgz",
"integrity": "sha512-sImlAl9mAoCKZLNlwWz2P2ZMJIDlOEDXrRD6aD2sIHAka1LPC+nWtB+D3lPe7IE7FGWSbwBPTnlSdlABa3Fr0A==",
"requires": {
"@fortawesome/fontawesome-common-types": "6.4.2"
"@floating-ui/dom": "^1.4.5",
"vue-demi": ">=0.13.0"
},
"dependencies": {
"vue-demi": {
"version": "0.14.6",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz",
"integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
"requires": {}
}
}
},
"@fortawesome/vue-fontawesome": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-2.0.10.tgz",
"integrity": "sha512-OTETSXz+3ygD2OK2/vy82cmUBpuJqeOAg4gfnnv+f2Rir1tDIhQg026Q3NQxznq83ZLz8iNqGG9XJm26inpDeg==",
"requires": {}
},
"@hapi/hoek": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
@@ -29343,7 +29297,8 @@
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"lodash.debounce": {
"version": "4.0.8",
@@ -32853,11 +32808,6 @@
"integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==",
"dev": true
},
"popper.js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ=="
},
"postcss": {
"version": "8.4.28",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz",
@@ -36079,17 +36029,6 @@
"sade": "^1.7.3"
}
},
"v-tooltip": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/v-tooltip/-/v-tooltip-2.1.3.tgz",
"integrity": "sha512-xXngyxLQTOx/yUEy50thb8te7Qo4XU6h4LZB6cvEfVd9mnysUxLEoYwGWDdqR+l69liKsy3IPkdYff3J1gAJ5w==",
"requires": {
"@babel/runtime": "^7.13.10",
"lodash": "^4.17.21",
"popper.js": "^1.16.1",
"vue-resize": "^1.0.1"
}
},
"validate-npm-package-license": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
@@ -36316,14 +36255,6 @@
}
}
},
"vue-resize": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/vue-resize/-/vue-resize-1.0.1.tgz",
"integrity": "sha512-z5M7lJs0QluJnaoMFTIeGx6dIkYxOwHThlZDeQnWZBizKblb99GSejPnK37ZbNE/rVwDcYcHY+Io+AxdpY952w==",
"requires": {
"@babel/runtime": "^7.13.10"
}
},
"vue-template-compiler": {
"version": "2.7.14",
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "privacy.sexy",
"version": "0.12.2",
"version": "0.12.4",
"private": true,
"slogan": "Now you have the choice",
"description": "Enforce privacy & security best-practices on Windows, macOS and Linux, because privacy is sexy 🍑🍆",
@@ -34,11 +34,7 @@
"postuninstall": "electron-builder install-app-deps"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.4.0",
"@fortawesome/free-brands-svg-icons": "^6.4.0",
"@fortawesome/free-regular-svg-icons": "^6.4.0",
"@fortawesome/free-solid-svg-icons": "^6.4.0",
"@fortawesome/vue-fontawesome": "^2.0.9",
"@floating-ui/vue": "^1.0.2",
"@juggle/resize-observer": "^3.4.0",
"ace-builds": "^1.23.4",
"cross-fetch": "^4.0.0",
@@ -46,7 +42,6 @@
"file-saver": "^2.0.5",
"markdown-it": "^13.0.1",
"npm": "^9.8.1",
"v-tooltip": "2.1.3",
"vue": "^2.7.14"
},
"devDependencies": {

View File

@@ -0,0 +1,62 @@
/**
* Description:
* This script checks if a server, provided as a CLI argument, is up
* and returns an HTTP 200 status code.
* It is designed to provide easy verification of server availability
* and will retry a specified number of times.
*
* Usage:
* node ./scripts/verify-web-server-status.js --url [URL]
*
* Options:
* --url URL of the server to check
*/
import { get } from 'http';
const MAX_RETRIES = 30;
const RETRY_DELAY_IN_SECONDS = 3;
const URL_PARAMETER_NAME = '--url';
function checkServer(currentRetryCount = 1) {
const serverUrl = getServerUrl();
console.log(`Requesting ${serverUrl}...`);
get(serverUrl, (res) => {
if (res.statusCode === 200) {
console.log('🎊 Success: The server is up and returned HTTP 200.');
process.exit(0);
} else {
console.log(`Server returned HTTP status code ${res.statusCode}.`);
retry(currentRetryCount);
}
}).on('error', (err) => {
console.error('Error making the request:', err);
retry(currentRetryCount);
});
}
function retry(currentRetryCount) {
console.log(`Attempt ${currentRetryCount}/${MAX_RETRIES}:`);
console.log(`Retrying in ${RETRY_DELAY_IN_SECONDS} seconds.`);
const remainingTime = (MAX_RETRIES - currentRetryCount) * RETRY_DELAY_IN_SECONDS;
console.log(`Time remaining before timeout: ${remainingTime}s`);
if (currentRetryCount < MAX_RETRIES) {
setTimeout(() => checkServer(currentRetryCount + 1), RETRY_DELAY_IN_SECONDS * 1000);
} else {
console.log('Failure: The server at did not return HTTP 200 within the allocated time. Exiting.');
process.exit(1);
}
}
function getServerUrl() {
const urlIndex = process.argv.indexOf(URL_PARAMETER_NAME);
if (urlIndex === -1 || urlIndex === process.argv.length - 1) {
console.error(`Parameter "${URL_PARAMETER_NAME}" is not provided.`);
process.exit(1);
}
return process.argv[urlIndex + 1];
}
checkServer();

View File

@@ -0,0 +1,5 @@
import { CompiledCode } from '../CompiledCode';
export interface CodeSegmentMerger {
mergeCodeParts(codeSegments: readonly CompiledCode[]): CompiledCode;
}

View File

@@ -0,0 +1,20 @@
import { CompiledCode } from '../CompiledCode';
import { CodeSegmentMerger } from './CodeSegmentMerger';
export class NewlineCodeSegmentMerger implements CodeSegmentMerger {
public mergeCodeParts(codeSegments: readonly CompiledCode[]): CompiledCode {
if (!codeSegments?.length) {
throw new Error('missing segments');
}
return {
code: joinCodeParts(codeSegments.map((f) => f.code)),
revertCode: joinCodeParts(codeSegments.map((f) => f.revertCode)),
};
}
}
function joinCodeParts(codeSegments: readonly string[]): string {
return codeSegments
.filter((segment) => segment?.length > 0)
.join('\n');
}

View File

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

View File

@@ -0,0 +1,9 @@
import { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
import { FunctionCall } from '../FunctionCall';
import type { SingleCallCompiler } from './SingleCall/SingleCallCompiler';
export interface FunctionCallCompilationContext {
readonly allFunctions: ISharedFunctionCollection;
readonly rootCallSequence: readonly FunctionCall[];
readonly singleCallCompiler: SingleCallCompiler;
}

View File

@@ -1,149 +1,10 @@
import { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/IFunctionCallArgumentCollection';
import { IFunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/IFunctionCall';
import { FunctionCallArgument } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
import { ISharedFunctionCollection } from '../../ISharedFunctionCollection';
import { IExpressionsCompiler } from '../../../Expressions/IExpressionsCompiler';
import { ExpressionsCompiler } from '../../../Expressions/ExpressionsCompiler';
import { ISharedFunction, IFunctionCode } from '../../ISharedFunction';
import { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
import { FunctionCall } from '../FunctionCall';
import { FunctionCallArgumentCollection } from '../Argument/FunctionCallArgumentCollection';
import { IFunctionCallCompiler } from './IFunctionCallCompiler';
import { ICompiledCode } from './ICompiledCode';
import { CompiledCode } from './CompiledCode';
export class FunctionCallCompiler implements IFunctionCallCompiler {
public static readonly instance: IFunctionCallCompiler = new FunctionCallCompiler();
protected constructor(
private readonly expressionsCompiler: IExpressionsCompiler = new ExpressionsCompiler(),
) {
}
public compileCall(
calls: IFunctionCall[],
export interface FunctionCallCompiler {
compileFunctionCalls(
calls: readonly FunctionCall[],
functions: ISharedFunctionCollection,
): ICompiledCode {
if (!functions) { throw new Error('missing functions'); }
if (!calls) { throw new Error('missing calls'); }
if (calls.some((f) => !f)) { throw new Error('missing function call'); }
const context: ICompilationContext = {
allFunctions: functions,
callSequence: calls,
expressionsCompiler: this.expressionsCompiler,
};
const code = compileCallSequence(context);
return code;
}
}
interface ICompilationContext {
allFunctions: ISharedFunctionCollection;
callSequence: readonly IFunctionCall[];
expressionsCompiler: IExpressionsCompiler;
}
interface ICompiledFunctionCall {
readonly code: string;
readonly revertCode: string;
}
function compileCallSequence(context: ICompilationContext): ICompiledFunctionCall {
const compiledFunctions = context.callSequence
.flatMap((call) => compileSingleCall(call, context));
return {
code: merge(compiledFunctions.map((f) => f.code)),
revertCode: merge(compiledFunctions.map((f) => f.revertCode)),
};
}
function compileSingleCall(
call: IFunctionCall,
context: ICompilationContext,
): ICompiledFunctionCall[] {
const func = context.allFunctions.getFunctionByName(call.functionName);
ensureThatCallArgumentsExistInParameterDefinition(func, call.args);
if (func.body.code) { // Function with inline code
const compiledCode = compileCode(func.body.code, call.args, context.expressionsCompiler);
return [compiledCode];
}
// Function with inner calls
return func.body.calls
.map((innerCall) => {
const compiledArgs = compileArgs(innerCall.args, call.args, context.expressionsCompiler);
const compiledCall = new FunctionCall(innerCall.functionName, compiledArgs);
return compileSingleCall(compiledCall, context);
})
.flat();
}
function compileCode(
code: IFunctionCode,
args: IReadOnlyFunctionCallArgumentCollection,
compiler: IExpressionsCompiler,
): ICompiledFunctionCall {
return {
code: compiler.compileExpressions(code.execute, args),
revertCode: compiler.compileExpressions(code.revert, args),
};
}
function compileArgs(
argsToCompile: IReadOnlyFunctionCallArgumentCollection,
args: IReadOnlyFunctionCallArgumentCollection,
compiler: IExpressionsCompiler,
): IReadOnlyFunctionCallArgumentCollection {
return argsToCompile
.getAllParameterNames()
.map((parameterName) => {
const { argumentValue } = argsToCompile.getArgument(parameterName);
const compiledValue = compiler.compileExpressions(argumentValue, args);
return new FunctionCallArgument(parameterName, compiledValue);
})
.reduce((compiledArgs, arg) => {
compiledArgs.addArgument(arg);
return compiledArgs;
}, new FunctionCallArgumentCollection());
}
function merge(codeParts: readonly string[]): string {
return codeParts
.filter((part) => part?.length > 0)
.join('\n');
}
function ensureThatCallArgumentsExistInParameterDefinition(
func: ISharedFunction,
args: IReadOnlyFunctionCallArgumentCollection,
): void {
const callArgumentNames = args.getAllParameterNames();
const functionParameterNames = func.parameters.all.map((param) => param.name) || [];
const unexpectedParameters = findUnexpectedParameters(callArgumentNames, functionParameterNames);
throwIfNotEmpty(func.name, unexpectedParameters, functionParameterNames);
}
function findUnexpectedParameters(
callArgumentNames: string[],
functionParameterNames: string[],
): string[] {
if (!callArgumentNames.length && !functionParameterNames.length) {
return [];
}
return callArgumentNames
.filter((callParam) => !functionParameterNames.includes(callParam));
}
function throwIfNotEmpty(
functionName: string,
unexpectedParameters: string[],
expectedParameters: string[],
) {
if (!unexpectedParameters.length) {
return;
}
throw new Error(
// eslint-disable-next-line prefer-template
`Function "${functionName}" has unexpected parameter(s) provided: `
+ `"${unexpectedParameters.join('", "')}"`
+ '. Expected parameter(s): '
+ (expectedParameters.length ? `"${expectedParameters.join('", "')}"` : 'none'),
);
): CompiledCode;
}

View File

@@ -0,0 +1,36 @@
import { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
import { ISharedFunctionCollection } from '../../ISharedFunctionCollection';
import { FunctionCallCompiler } from './FunctionCallCompiler';
import { CompiledCode } from './CompiledCode';
import { FunctionCallCompilationContext } from './FunctionCallCompilationContext';
import { SingleCallCompiler } from './SingleCall/SingleCallCompiler';
import { AdaptiveFunctionCallCompiler } from './SingleCall/AdaptiveFunctionCallCompiler';
import { CodeSegmentMerger } from './CodeSegmentJoin/CodeSegmentMerger';
import { NewlineCodeSegmentMerger } from './CodeSegmentJoin/NewlineCodeSegmentMerger';
export class FunctionCallSequenceCompiler implements FunctionCallCompiler {
public static readonly instance: FunctionCallCompiler = new FunctionCallSequenceCompiler();
/* The constructor is protected to enforce the singleton pattern. */
protected constructor(
private readonly singleCallCompiler: SingleCallCompiler = new AdaptiveFunctionCallCompiler(),
private readonly codeSegmentMerger: CodeSegmentMerger = new NewlineCodeSegmentMerger(),
) { }
public compileFunctionCalls(
calls: readonly FunctionCall[],
functions: ISharedFunctionCollection,
): CompiledCode {
if (!functions) { throw new Error('missing functions'); }
if (!calls?.length) { throw new Error('missing calls'); }
if (calls.some((f) => !f)) { throw new Error('missing function call'); }
const context: FunctionCallCompilationContext = {
allFunctions: functions,
rootCallSequence: calls,
singleCallCompiler: this.singleCallCompiler,
};
const codeSegments = context.rootCallSequence
.flatMap((call) => this.singleCallCompiler.compileSingleCall(call, context));
return this.codeSegmentMerger.mergeCodeParts(codeSegments);
}
}

View File

@@ -1,9 +0,0 @@
import { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
import { IFunctionCall } from '../IFunctionCall';
import { ICompiledCode } from './ICompiledCode';
export interface IFunctionCallCompiler {
compileCall(
calls: IFunctionCall[],
functions: ISharedFunctionCollection): ICompiledCode;
}

View File

@@ -0,0 +1,78 @@
import { FunctionCall } from '../../FunctionCall';
import { CompiledCode } from '../CompiledCode';
import { FunctionCallCompilationContext } from '../FunctionCallCompilationContext';
import { IReadOnlyFunctionCallArgumentCollection } from '../../Argument/IFunctionCallArgumentCollection';
import { ISharedFunction } from '../../../ISharedFunction';
import { SingleCallCompiler } from './SingleCallCompiler';
import { SingleCallCompilerStrategy } from './SingleCallCompilerStrategy';
import { InlineFunctionCallCompiler } from './Strategies/InlineFunctionCallCompiler';
import { NestedFunctionCallCompiler } from './Strategies/NestedFunctionCallCompiler';
export class AdaptiveFunctionCallCompiler implements SingleCallCompiler {
public constructor(
private readonly strategies: SingleCallCompilerStrategy[] = [
new InlineFunctionCallCompiler(),
new NestedFunctionCallCompiler(),
],
) {
}
public compileSingleCall(
call: FunctionCall,
context: FunctionCallCompilationContext,
): CompiledCode[] {
const func = context.allFunctions.getFunctionByName(call.functionName);
ensureThatCallArgumentsExistInParameterDefinition(func, call.args);
const strategy = this.findStrategy(func);
return strategy.compileFunction(func, call, context);
}
private findStrategy(func: ISharedFunction): SingleCallCompilerStrategy {
const strategies = this.strategies.filter((strategy) => strategy.canCompile(func));
if (strategies.length > 1) {
throw new Error('Multiple strategies found to compile the function call.');
}
if (strategies.length === 0) {
throw new Error('No strategies found to compile the function call.');
}
return strategies[0];
}
}
function ensureThatCallArgumentsExistInParameterDefinition(
func: ISharedFunction,
callArguments: IReadOnlyFunctionCallArgumentCollection,
): void {
const callArgumentNames = callArguments.getAllParameterNames();
const functionParameterNames = func.parameters.all.map((param) => param.name) || [];
const unexpectedParameters = findUnexpectedParameters(callArgumentNames, functionParameterNames);
throwIfUnexpectedParametersExist(func.name, unexpectedParameters, functionParameterNames);
}
function findUnexpectedParameters(
callArgumentNames: string[],
functionParameterNames: string[],
): string[] {
if (!callArgumentNames.length && !functionParameterNames.length) {
return [];
}
return callArgumentNames
.filter((callParam) => !functionParameterNames.includes(callParam));
}
function throwIfUnexpectedParametersExist(
functionName: string,
unexpectedParameters: string[],
expectedParameters: string[],
) {
if (!unexpectedParameters.length) {
return;
}
throw new Error(
// eslint-disable-next-line prefer-template
`Function "${functionName}" has unexpected parameter(s) provided: `
+ `"${unexpectedParameters.join('", "')}"`
+ '. Expected parameter(s): '
+ (expectedParameters.length ? `"${expectedParameters.join('", "')}"` : 'none'),
);
}

View File

@@ -0,0 +1,10 @@
import { FunctionCall } from '../../FunctionCall';
import { CompiledCode } from '../CompiledCode';
import { FunctionCallCompilationContext } from '../FunctionCallCompilationContext';
export interface SingleCallCompiler {
compileSingleCall(
call: FunctionCall,
context: FunctionCallCompilationContext,
): CompiledCode[];
}

View File

@@ -0,0 +1,13 @@
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
import { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
import { CompiledCode } from '../CompiledCode';
import { FunctionCallCompilationContext } from '../FunctionCallCompilationContext';
export interface SingleCallCompilerStrategy {
canCompile(func: ISharedFunction): boolean;
compileFunction(
calledFunction: ISharedFunction,
callToFunction: FunctionCall,
context: FunctionCallCompilationContext,
): CompiledCode[],
}

View File

@@ -0,0 +1,10 @@
import { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
import { FunctionCallCompilationContext } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompilationContext';
export interface ArgumentCompiler {
createCompiledNestedCall(
nestedFunctionCall: FunctionCall,
parentFunctionCall: FunctionCall,
context: FunctionCallCompilationContext,
): FunctionCall;
}

View File

@@ -0,0 +1,109 @@
import { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/IFunctionCallArgumentCollection';
import { FunctionCallArgument } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
import { FunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection';
import { ExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/ExpressionsCompiler';
import { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/IExpressionsCompiler';
import { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
import { FunctionCallCompilationContext } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompilationContext';
import { ParsedFunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/ParsedFunctionCall';
import { ArgumentCompiler } from './ArgumentCompiler';
export class NestedFunctionArgumentCompiler implements ArgumentCompiler {
constructor(
private readonly expressionsCompiler: IExpressionsCompiler = new ExpressionsCompiler(),
) { }
public createCompiledNestedCall(
nestedFunction: FunctionCall,
parentFunction: FunctionCall,
context: FunctionCallCompilationContext,
): FunctionCall {
const compiledArgs = compileNestedFunctionArguments(
nestedFunction,
parentFunction.args,
context,
this.expressionsCompiler,
);
const compiledCall = new ParsedFunctionCall(nestedFunction.functionName, compiledArgs);
return compiledCall;
}
}
function compileNestedFunctionArguments(
nestedFunction: FunctionCall,
parentFunctionArgs: IReadOnlyFunctionCallArgumentCollection,
context: FunctionCallCompilationContext,
expressionsCompiler: IExpressionsCompiler,
): IReadOnlyFunctionCallArgumentCollection {
const requiredParameterNames = context
.allFunctions
.getRequiredParameterNames(nestedFunction.functionName);
const compiledArguments = nestedFunction.args
.getAllParameterNames()
// Compile each argument value
.map((paramName) => ({
parameterName: paramName,
compiledArgumentValue: compileArgument(
paramName,
nestedFunction,
parentFunctionArgs,
expressionsCompiler,
),
}))
// Filter out arguments with absent values
.filter(({
parameterName,
compiledArgumentValue,
}) => isValidNonAbsentArgumentValue(
parameterName,
compiledArgumentValue,
requiredParameterNames,
))
/*
Create argument object with non-absent values.
This is done after eliminating absent values because otherwise creating argument object
with absent values throws error.
*/
.map(({
parameterName,
compiledArgumentValue,
}) => new FunctionCallArgument(parameterName, compiledArgumentValue));
return buildArgumentCollectionFromArguments(compiledArguments);
}
function isValidNonAbsentArgumentValue(
parameterName: string,
argumentValue: string | undefined,
requiredParameterNames: string[],
): boolean {
if (argumentValue) {
return true;
}
if (!requiredParameterNames.includes(parameterName)) {
return false;
}
throw new Error(`Compilation resulted in empty value for required parameter: "${parameterName}"`);
}
function compileArgument(
parameterName: string,
nestedFunction: FunctionCall,
parentFunctionArgs: IReadOnlyFunctionCallArgumentCollection,
expressionsCompiler: IExpressionsCompiler,
): string {
try {
const { argumentValue: codeInArgument } = nestedFunction.args.getArgument(parameterName);
return expressionsCompiler.compileExpressions(codeInArgument, parentFunctionArgs);
} catch (err) {
throw new AggregateError([err], `Error when compiling argument for "${parameterName}"`);
}
}
function buildArgumentCollectionFromArguments(
args: FunctionCallArgument[],
): FunctionCallArgumentCollection {
return args.reduce((compiledArgs, arg) => {
compiledArgs.addArgument(arg);
return compiledArgs;
}, new FunctionCallArgumentCollection());
}

View File

@@ -0,0 +1,31 @@
import { ExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/ExpressionsCompiler';
import { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/IExpressionsCompiler';
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
import { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
import { CompiledCode } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/CompiledCode';
import { SingleCallCompilerStrategy } from '../SingleCallCompilerStrategy';
export class InlineFunctionCallCompiler implements SingleCallCompilerStrategy {
public constructor(
private readonly expressionsCompiler: IExpressionsCompiler = new ExpressionsCompiler(),
) {
}
public canCompile(func: ISharedFunction): boolean {
return func.body.code !== undefined;
}
public compileFunction(
calledFunction: ISharedFunction,
callToFunction: FunctionCall,
): CompiledCode[] {
const { code } = calledFunction.body;
const { args } = callToFunction;
return [
{
code: this.expressionsCompiler.compileExpressions(code.execute, args),
revertCode: this.expressionsCompiler.compileExpressions(code.revert, args),
},
];
}
}

View File

@@ -0,0 +1,37 @@
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
import { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
import { FunctionCallCompilationContext } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompilationContext';
import { CompiledCode } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/CompiledCode';
import { SingleCallCompilerStrategy } from '../SingleCallCompilerStrategy';
import { ArgumentCompiler } from './Argument/ArgumentCompiler';
import { NestedFunctionArgumentCompiler } from './Argument/NestedFunctionArgumentCompiler';
export class NestedFunctionCallCompiler implements SingleCallCompilerStrategy {
public constructor(
private readonly argumentCompiler: ArgumentCompiler = new NestedFunctionArgumentCompiler(),
) {
}
public canCompile(func: ISharedFunction): boolean {
return func.body.calls !== undefined;
}
public compileFunction(
calledFunction: ISharedFunction,
callToFunction: FunctionCall,
context: FunctionCallCompilationContext,
): CompiledCode[] {
const nestedCalls = calledFunction.body.calls;
return nestedCalls.map((nestedCall) => {
try {
const compiledParentCall = this.argumentCompiler
.createCompiledNestedCall(nestedCall, callToFunction, context);
const compiledNestedCall = context.singleCallCompiler
.compileSingleCall(compiledParentCall, context);
return compiledNestedCall;
} catch (err) {
throw new AggregateError([err], `Error with call to "${nestedCall.functionName}" function from "${callToFunction.functionName}" function`);
}
}).flat();
}
}

View File

@@ -1,16 +1,6 @@
import { IReadOnlyFunctionCallArgumentCollection } from './Argument/IFunctionCallArgumentCollection';
import { IFunctionCall } from './IFunctionCall';
export class FunctionCall implements IFunctionCall {
constructor(
public readonly functionName: string,
public readonly args: IReadOnlyFunctionCallArgumentCollection,
) {
if (!functionName) {
throw new Error('missing function name in function call');
}
if (!args) {
throw new Error('missing args');
}
}
export interface FunctionCall {
readonly functionName: string;
readonly args: IReadOnlyFunctionCallArgumentCollection;
}

View File

@@ -1,10 +1,10 @@
import type { FunctionCallData, FunctionCallsData, FunctionCallParametersData } from '@/application/collections/';
import { IFunctionCall } from './IFunctionCall';
import { FunctionCall } from './FunctionCall';
import { FunctionCallArgumentCollection } from './Argument/FunctionCallArgumentCollection';
import { FunctionCallArgument } from './Argument/FunctionCallArgument';
import { FunctionCall } from './FunctionCall';
import { ParsedFunctionCall } from './ParsedFunctionCall';
export function parseFunctionCalls(calls: FunctionCallsData): IFunctionCall[] {
export function parseFunctionCalls(calls: FunctionCallsData): FunctionCall[] {
if (calls === undefined) {
throw new Error('missing call data');
}
@@ -22,12 +22,12 @@ function getCallSequence(calls: FunctionCallsData): FunctionCallData[] {
return [calls as FunctionCallData];
}
function parseFunctionCall(call: FunctionCallData): IFunctionCall {
function parseFunctionCall(call: FunctionCallData): FunctionCall {
if (!call) {
throw new Error('missing call data');
}
const callArgs = parseArgs(call.parameters);
return new FunctionCall(call.function, callArgs);
return new ParsedFunctionCall(call.function, callArgs);
}
function parseArgs(

View File

@@ -1,6 +0,0 @@
import { IReadOnlyFunctionCallArgumentCollection } from './Argument/IFunctionCallArgumentCollection';
export interface IFunctionCall {
readonly functionName: string;
readonly args: IReadOnlyFunctionCallArgumentCollection;
}

View File

@@ -0,0 +1,16 @@
import { IReadOnlyFunctionCallArgumentCollection } from './Argument/IFunctionCallArgumentCollection';
import { FunctionCall } from './FunctionCall';
export class ParsedFunctionCall implements FunctionCall {
constructor(
public readonly functionName: string,
public readonly args: IReadOnlyFunctionCallArgumentCollection,
) {
if (!functionName) {
throw new Error('missing function name in function call');
}
if (!args) {
throw new Error('missing args');
}
}
}

View File

@@ -1,5 +1,5 @@
import { IReadOnlyFunctionParameterCollection } from './Parameter/IFunctionParameterCollection';
import { IFunctionCall } from './Call/IFunctionCall';
import { FunctionCall } from './Call/FunctionCall';
export interface ISharedFunction {
readonly name: string;
@@ -9,8 +9,8 @@ export interface ISharedFunction {
export interface ISharedFunctionBody {
readonly type: FunctionBodyType;
readonly code: IFunctionCode;
readonly calls: readonly IFunctionCall[];
readonly code: IFunctionCode | undefined;
readonly calls: readonly FunctionCall[] | undefined;
}
export enum FunctionBodyType {

View File

@@ -2,4 +2,5 @@ import { ISharedFunction } from './ISharedFunction';
export interface ISharedFunctionCollection {
getFunctionByName(name: string): ISharedFunction;
getRequiredParameterNames(functionName: string): string[];
}

View File

@@ -1,4 +1,4 @@
import { IFunctionCall } from './Call/IFunctionCall';
import { FunctionCall } from './Call/FunctionCall';
import {
FunctionBodyType, IFunctionCode, ISharedFunction, ISharedFunctionBody,
@@ -8,7 +8,7 @@ import { IReadOnlyFunctionParameterCollection } from './Parameter/IFunctionParam
export function createCallerFunction(
name: string,
parameters: IReadOnlyFunctionParameterCollection,
callSequence: readonly IFunctionCall[],
callSequence: readonly FunctionCall[],
): ISharedFunction {
if (!callSequence || !callSequence.length) {
throw new Error(`missing call sequence in function "${name}"`);
@@ -38,7 +38,7 @@ class SharedFunction implements ISharedFunction {
constructor(
public readonly name: string,
public readonly parameters: IReadOnlyFunctionParameterCollection,
content: IFunctionCode | readonly IFunctionCall[],
content: IFunctionCode | readonly FunctionCall[],
bodyType: FunctionBodyType,
) {
if (!name) { throw new Error('missing function name'); }
@@ -46,7 +46,7 @@ class SharedFunction implements ISharedFunction {
this.body = {
type: bodyType,
code: bodyType === FunctionBodyType.Code ? content as IFunctionCode : undefined,
calls: bodyType === FunctionBodyType.Calls ? content as readonly IFunctionCall[] : undefined,
calls: bodyType === FunctionBodyType.Calls ? content as readonly FunctionCall[] : undefined,
};
}
}

View File

@@ -21,6 +21,15 @@ export class SharedFunctionCollection implements ISharedFunctionCollection {
return func;
}
public getRequiredParameterNames(functionName: string): string[] {
return this
.getFunctionByName(functionName)
.parameters
.all
.filter((parameter) => !parameter.isOptional)
.map((parameter) => parameter.name);
}
private has(functionName: string) {
return this.functionsByName.has(functionName);
}

View File

@@ -7,12 +7,12 @@ import { NoEmptyLines } from '@/application/Parser/Script/Validation/Rules/NoEmp
import { ICodeValidator } from '@/application/Parser/Script/Validation/ICodeValidator';
import { IScriptCompiler } from './IScriptCompiler';
import { ISharedFunctionCollection } from './Function/ISharedFunctionCollection';
import { IFunctionCallCompiler } from './Function/Call/Compiler/IFunctionCallCompiler';
import { FunctionCallSequenceCompiler } from './Function/Call/Compiler/FunctionCallSequenceCompiler';
import { FunctionCallCompiler } from './Function/Call/Compiler/FunctionCallCompiler';
import { ISharedFunctionsParser } from './Function/ISharedFunctionsParser';
import { SharedFunctionsParser } from './Function/SharedFunctionsParser';
import { parseFunctionCalls } from './Function/Call/FunctionCallParser';
import { ICompiledCode } from './Function/Call/Compiler/ICompiledCode';
import { CompiledCode } from './Function/Call/Compiler/CompiledCode';
export class ScriptCompiler implements IScriptCompiler {
private readonly functions: ISharedFunctionCollection;
@@ -21,7 +21,7 @@ export class ScriptCompiler implements IScriptCompiler {
functions: readonly FunctionData[] | undefined,
syntax: ILanguageSyntax,
sharedFunctionsParser: ISharedFunctionsParser = SharedFunctionsParser.instance,
private readonly callCompiler: IFunctionCallCompiler = FunctionCallCompiler.instance,
private readonly callCompiler: FunctionCallCompiler = FunctionCallSequenceCompiler.instance,
private readonly codeValidator: ICodeValidator = CodeValidator.instance,
) {
if (!syntax) { throw new Error('missing syntax'); }
@@ -40,7 +40,7 @@ export class ScriptCompiler implements IScriptCompiler {
if (!script) { throw new Error('missing script'); }
try {
const calls = parseFunctionCalls(script.call);
const compiledCode = this.callCompiler.compileCall(calls, this.functions);
const compiledCode = this.callCompiler.compileFunctionCalls(calls, this.functions);
validateCompiledCode(compiledCode, this.codeValidator);
return new ScriptCode(
compiledCode.code,
@@ -52,7 +52,7 @@ export class ScriptCompiler implements IScriptCompiler {
}
}
function validateCompiledCode(compiledCode: ICompiledCode, validator: ICodeValidator): void {
function validateCompiledCode(compiledCode: CompiledCode, validator: ICodeValidator): void {
[compiledCode.code, compiledCode.revertCode].forEach(
(code) => validator.throwIfInvalid(code, [new NoEmptyLines()]),
);

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
# Structure documented in "docs/collection-files.md"
# Structure is documented in "docs/collection-files.md"
os: macos
scripting:
language: shellscript
@@ -21,7 +21,7 @@ actions:
-
category: Privacy cleanup
children:
-
-
category: Clear terminal history
children:
-
@@ -48,18 +48,18 @@ actions:
# on main HDD
sudo rm -rfv ~/.Trash/* &>/dev/null
-
name: Clear system cache files
name: Clear system cache
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
-
category: Clear OS logs
category: Clear operating system logs
recommend: strict
children:
-
category: Clear unified logs (diagnostics)
category: Clear unified diagnostic logs
docs: https://developer.apple.com/documentation/os/logging
children:
-
@@ -69,15 +69,15 @@ actions:
sudo rm -rfv /private/var/db/diagnostics/*
sudo rm -rfv /var/db/diagnostics/*
-
name: Clear shared-cache strings data
docs:
name: Clear shared cache strings data
docs:
- https://eclecticlight.co/2017/09/23/sierras-unified-log-evolves-more-persistent-and-a-valuable-log-log/
- https://github.com/privacysexy-forks/dtformats/blob/main/documentation/Apple%20Unified%20Logging%20and%20Activity%20Tracing%20formats.asciidoc
code: |-
sudo rm -rfv /private/var/db/uuidtext/
sudo rm -rfv /var/db/uuidtext/
-
category: Clear system logs (/var/log/)
category: Clear system logs
children:
-
name: Clear Apple System Logs (ASL)
@@ -94,7 +94,7 @@ actions:
docs: https://discussions.apple.com/thread/1829842
code: sudo rm -fv /var/log/install.log
-
name: Clear all system logs
name: Clear all system logs in `/var/log/` directory
docs: https://www.howtogeek.com/356942/how-to-view-the-system-log-on-a-mac/
code: sudo rm -rfv /var/log/* # Clears including /var/log/system.log
-
@@ -105,7 +105,7 @@ actions:
name: Clear Mail logs
code: rm -rfv ~/Library/Containers/com.apple.mail/Data/Library/Logs/Mail/*
-
name: Clear audit logs (login, logout, authentication and other user activity)
name: Clear user activity audit logs (login, logout, authentication, etc.)
docs:
- https://papers.put.as/papers/macosx/2012/Mac_Log_Analysis_Sarah_Edwards_DFIRSummit2012.pdf
- http://macadmins.psu.edu/wp-content/uploads/sites/24696/2016/06/psumac2016-19-osxlogs_macadmins_2016.pdf
@@ -113,7 +113,7 @@ actions:
sudo rm -rfv /var/audit/*
sudo rm -rfv /private/var/audit/*
-
name: Clear user logs (user reports)
name: Clear user report logs
docs:
- https://www.howtogeek.com/356942/how-to-view-the-system-log-on-a-mac/
- https://apple.stackexchange.com/questions/272929/is-it-safe-to-delete-the-content-of-library-logs
@@ -134,15 +134,15 @@ actions:
category: Clear browser history
children:
-
category: Clear Google Chrome history
category: Clear Chrome history
children:
-
name: Clear Google Chrome browsing history
name: Clear 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
name: Clear Chrome cache
code: sudo rm -rfv ~/Library/Application\ Support/Google/Chrome/Default/Application\ Cache/* &>/dev/null
-
category: Clear Safari history
@@ -165,7 +165,7 @@ actions:
docs: https://blog.d204n6.com/2020/09/macos-safari-preferences-and-privacy.html
code: rm -f ~/Library/Safari/Downloads.plist
-
name: Clear Safari top sites
name: Clear Safari frequently visited sites
docs: https://davidkoepi.wordpress.com/2013/04/20/safariforensic/
code: rm -f ~/Library/Safari/TopSites.plist
-
@@ -182,7 +182,7 @@ actions:
docs: https://davidkoepi.wordpress.com/2013/04/20/safariforensic/
code: rm -f ~/Library/Caches/com.apple.Safari/Cache.db
-
name: Clear Safari web page icons displayed on URL bar
name: Clear Safari URL bar web page icons
docs:
- https://davidkoepi.wordpress.com/2013/04/20/safariforensic/
- https://lifehacker.com/safaris-private-browsing-mode-saves-urls-in-an-easily-a-1691944343
@@ -194,11 +194,11 @@ actions:
- https://www.reddit.com/r/apple/comments/18lp92/your_apple_computer_keeps_a_screen_shot_of_nearly/
code: rm -rfv ~/Library/Caches/com.apple.Safari/Webpage\ Previews
-
name: Clear copy of the Safari history
name: Clear Safari history copy
docs: https://forensicsfromthesausagefactory.blogspot.com/2010/06/safari-history-spotlight-webhistory.html
code: rm -rfv ~/Library/Caches/Metadata/Safari/History
-
name: Clear search history embedded in Safari preferences
name: Clear search term history embedded in Safari preferences
docs: https://krypted.com/tag/recentsearchstrings/
code: defaults write ~/Library/Preferences/com.apple.Safari RecentSearchStrings '( )'
-
@@ -215,11 +215,11 @@ actions:
docs: https://blog.d204n6.com/2020/09/macos-safari-preferences-and-privacy.html
code: rm -f ~/Library/Safari/PerSiteZoomPreferences.plist
-
name: Clear URLs that are allowed to display notifications in Safari
name: Clear allowed URLs for Safari notifications
docs: https://blog.d204n6.com/2020/09/macos-safari-preferences-and-privacy.html
code: rm -f ~/Library/Safari/UserNotificationPreferences.plist
-
name: Clear Safari per-site preferences for Downloads, Geolocation, PopUps, and Autoplays
name: Clear Safari preferences for downloads, geolocation, pop-ups, and autoplay per site
docs: https://blog.d204n6.com/2020/09/macos-safari-preferences-and-privacy.html
code: rm -f ~/Library/Safari/PerSitePreferences.db
-
@@ -231,15 +231,15 @@ actions:
sudo rm -rf ~/Library/Caches/Mozilla/
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/netpredictions.sqlite
-
name: Delete Firefox form history
name: Clear 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
name: Clear 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)
name: Clear 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*
@@ -250,7 +250,7 @@ actions:
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
name: Clear Firefox passwords
docs: https://web.archive.org/web/20210425202923/http://kb.mozillazine.org/Password_Manager
code: |-
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/signons.txt
@@ -259,20 +259,20 @@ actions:
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/signons.sqlite
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/logins.json
-
name: Delete Firefox HTML5 cookies
name: Clear Firefox HTML5 cookies
code: rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/webappsstore.sqlite
-
name: Delete Firefox crash reports
name: Clear 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
name: Clear 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
name: Clear Firefox cookies
code: |-
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/cookies.txt
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/cookies.sqlite
@@ -280,7 +280,7 @@ actions:
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
category: Clear third-party application data
children:
-
name: Clear Adobe cache
@@ -290,18 +290,18 @@ actions:
name: Clear Gradle cache
recommend: strict
code: |-
if [ -d "/Users/${HOST}/.gradle/caches" ]; then
if [ -d "~/.gradle/caches" ]; then
rm -rfv ~/.gradle/caches/ &> /dev/null
fi
-
name: Clear Dropbox cache
recommend: standard
code: |-
if [ -d "/Users/${HOST}/Dropbox" ]; then
if [ -d "~/Dropbox/.dropbox.cache" ]; then
sudo rm -rfv ~/Dropbox/.dropbox.cache/* &>/dev/null
fi
-
name: Clear Google Drive file stream cache
name: Clear Google Drive File Stream cache
recommend: standard
code: |-
killall "Google Drive File Stream"
@@ -323,21 +323,54 @@ actions:
brew tap --repair &>/dev/null
fi
-
name: Clear any old versions of Ruby gems
name: Clear old Ruby gem versions
recommend: strict
code: |-
if type "gem" &> /dev/null; then
gem cleanup &>/dev/null
fi
-
name: Clear Docker
name: Clear unused Docker data
recommend: strict
docs: |-
This script frees up disk space, but also improves user privacy by:
1. **Removal of stopped containers**: Containers often run applications or services that might process sensitive
or personal data. Even if a container is stopped, its filesystem remains intact, and potentially sensitive data inside
it can be accessed. By removing stopped containers, we eliminate this potential privacy risk.
2. **Deletion of unused images**: Images can sometimes contain sensitive information, especially if they were built
from `Dockerfile`s that copied local files or were used in scenarios where sensitive data was processed. Deleting unused
images ensures that any inadvertent sensitive information embedded in those images is eradicated.
3. **Cleanup of network configurations**: Networks, especially custom ones, can contain configurations that reveal details
about system architecture, inter-container communication, or even hardcoded secrets. Removing unused networks mitigates
risks associated with lingering, outdated, or insecure configurations.
4. **Elimination of build cache**: The Docker build process uses a cache to speed up image creation. This cache can contain
remnants of previous builds, including potentially sensitive data or files. Pruning the build cache ensures that these remnants
are deleted, further safeguarding privacy.
5. **Footprint reduction**: By consistently pruning unused Docker objects, the overall footprint of Docker on the system is
reduced. This makes it harder for malicious actors to exploit any lingering or overlooked vulnerabilities in the system or Docker
itself.
This script runs `docker system prune -af` command to clean up unused Docker data [1].
Specifically, the command will [1]:
- Remove all stopped containers.
- Remove all networks not used by at least one container.
- Remove all images not used by any container.
- Remove all build cache.
[1]: https://web.archive.org/web/20230810171526/https://docs.docker.com/engine/reference/commandline/system_prune/ "docker system prune | Docker Documentation"
code: |-
if type "docker" &> /dev/null; then
docker system prune -af
fi
-
name: Clear Pyenv-VirtualEnv cache
name: Clear Pyenv-Virtualenv cache
recommend: strict
code: |-
if [ "$PYENV_VIRTUALENV_CACHE_PATH" ]; then
@@ -359,22 +392,22 @@ actions:
yarn cache clean --force
fi
-
category: iOS Cleanup
category: Clear iOS usage data
children:
-
name: Clear iOS applications
name: Clear iOS app copies from iTunes
recommend: strict
code: rm -rfv ~/Music/iTunes/iTunes\ Media/Mobile\ Applications/* &>/dev/null
-
name: Clear iOS photo caches
name: Clear iOS photo cache
recommend: standard
code: rm -rf ~/Pictures/iPhoto\ Library/iPod\ Photo\ Cache/*
-
name: Remove iOS Device Backups
name: Clear iOS Device Backups
recommend: strict
code: rm -rfv ~/Library/Application\ Support/MobileSync/Backup/* &>/dev/null
-
name: Clear iOS Simulators
name: Clear iOS simulators
recommend: strict
code: |-
if type "xcrun" &>/dev/null; then
@@ -385,7 +418,7 @@ actions:
xcrun simctl erase all
fi
-
name: Clear the list of iOS devices connected
name: Clear list of connected iOS devices
recommend: strict
code: |-
sudo defaults delete /Users/$USER/Library/Preferences/com.apple.iPod.plist "conn:128:Last Connect"
@@ -394,7 +427,7 @@ actions:
sudo defaults delete /Library/Preferences/com.apple.iPod.plist Devices
sudo rm -rfv /var/db/lockdown/*
-
name: Clear XCode Derived Data and Archives
name: Clear Xcode's derived data and archives
recommend: strict
code: |-
rm -rfv ~/Library/Developer/Xcode/DerivedData/* &>/dev/null
@@ -407,51 +440,51 @@ actions:
sudo dscacheutil -flushcache
sudo killall -HUP mDNSResponder
-
name: Purge inactive memory
name: Clear inactive memory
recommend: standard
code: sudo purge
-
category: Reset privacy permissions for all applications
category: Clear all privacy permissions for applications
children:
-
name: Reset camera permissions
name: Clear "camera" permissions
code: tccutil reset Camera
-
name: Reset microphone permissions
name: Clear "microphone" permissions
code: tccutil reset Microphone
-
name: Reset accessibility permissions
name: Clear "accessibility" permissions
code: tccutil reset Accessibility
-
name: Reset screen capture permissions
name: Clear "screen capture" permissions
code: tccutil reset ScreenCapture
-
name: Reset reminders permissions
name: Clear "reminders" permissions
code: tccutil reset Reminders
-
name: Reset photos permissions
name: Clear "photos" permissions
code: tccutil reset Photos
-
name: Reset calendar permissions
name: Clear "calendar" permissions
code: tccutil reset Calendar
-
name: Reset full disk access permissions
name: Clear "full disk access" permissions
code: tccutil reset SystemPolicyAllFiles
-
name: Reset contacts permissions
name: Clear "contacts" permissions
code: tccutil reset SystemPolicyAllFiles
-
name: Reset desktop folder permissions
name: Clear "desktop folder" permissions
code: tccutil reset SystemPolicyDesktopFolder
-
name: Reset documents folder permissions
name: Clear "documents folder" permissions
code: tccutil reset SystemPolicyDocumentsFolder
-
name: Reset downloads permissions
name: Clear "downloads" permissions
code: tccutil reset SystemPolicyDownloadsFolder
-
name: Reset all app permissions
code: tccutil reset All
name: Clear all app permissions
code: tccutil reset All
-
category: Configure programs
children:
@@ -468,20 +501,20 @@ actions:
sudo defaults delete /Library/Preferences/org.mozilla.firefox EnterprisePoliciesEnabled
sudo defaults delete /Library/Preferences/org.mozilla.firefox DisableTelemetry
-
name: Disable Microsoft Office diagnostics data sending
name: Disable Microsoft Office telemetry
recommend: standard
code: defaults write com.microsoft.office DiagnosticDataTypePreference -string ZeroDiagnosticData
revertCode: defaults delete com.microsoft.office DiagnosticDataTypePreference
-
name: Uninstall Google update
name: Remove Google Software Update service
recommend: strict
code: |-
googleUpdateFile=~/Library/Google/GoogleSoftwareUpdate/GoogleSoftwareUpdate.bundle/Contents/Resources/ksinstall
if [ -f "$googleUpdateFile" ]; then
$googleUpdateFile --nuke
echo Uninstalled google update
echo 'Uninstalled Google update'
else
echo Google update file does not exist
echo 'Google update file does not exist'
fi
-
name: Disable Homebrew user behavior analytics
@@ -514,12 +547,12 @@ actions:
docs: |-
Parallels Desktop for Mac is software providing hardware virtualization for macOS [1].
When you use it, it collects and share your personal data to third parties [2]. Personal
When you use it, it collects and shares your personal data to third parties [2]. Personal
data include IP address of your device, your broad geographical location (country, state
(if applicable), and city) and used product [2].
It includes third-party ads [3] and automatic check for updates [4] by default. Both of these
behaviors communicate with online services that reveal data about you.
It includes third-party advertisements [3] and automatic check for updates [4] by default.
Both of these behaviors communicate with online services that reveal personal data about you.
[1]: https://web.archive.org/web/20221012155943/https://en.wikipedia.org/wiki/Parallels_Desktop_for_Mac "Parallels Desktop for Mac - Wikipedia | en.wikipedia.org"
[2]: https://web.archive.org/web/20221012155829/https://www.parallels.com/about/legal/privacy/ "Privacy Statement | parallels.com"
@@ -527,7 +560,7 @@ actions:
[4]: https://web.archive.org/web/20221012151953/http://download.parallels.com/stm/docs/en/Parallels_Desktop_Users_Guide/22220.htm "Automatic Updating | Parallels Desktop Users Guide | download.parallels.com"
children:
-
name: Turn off ads in Parallels Desktop
name: Disable Parallels Desktop advertisements
recommend: standard
docs: |-
Parallels Desktop in-product notifications to show ads from Parallels or other third
@@ -544,7 +577,7 @@ actions:
default). It's undocumented but still kept disabled by this script.
[1]: https://web.archive.org/save/https://forum.parallels.com/threads/unable-to-process-the-upgrade-request.345603/ "Unable to process the upgrade request | Parallels Forums | forum.parallels.com"
[2]: https://web.archive.org/web/20221012151800/https://kb.parallels.com/114422 "How do I turn off notifications in Parallels Desktop and Parallels Access? | Knowledge Base | parallels.com"
[2]: https://web.archive.org/web/20221012151800/https://kb.parallels.com/114422 "How do I turn off notifications in Parallels Desktop and Parallels Access? | Knowledge Base | parallels.com"
code: |-
defaults write 'com.parallels.Parallels Desktop' 'ProductPromo.ForcePromoOff' -bool yes
defaults write 'com.parallels.Parallels Desktop' 'WelcomeScreenPromo.PromoOff' -bool yes
@@ -552,16 +585,16 @@ actions:
defaults write 'com.parallels.Parallels Desktop' 'ProductPromo.ForcePromoOff' -bool no
defaults write 'com.parallels.Parallels Desktop' 'WelcomeScreenPromo.PromoOff' -bool yes
-
category: Disable Parallels Desktop auto-updates
category: Disable Parallels Desktop automatic updates
docs: |-
Parallels Desktop by default checks for updates frequently and automatically downloads them [1].
This reveal personal data about [2] you without your control.
This reveal personal data about you [2] without your control.
[1]: https://web.archive.org/web/20221012151953/http://download.parallels.com/stm/docs/en/Parallels_Desktop_Users_Guide/22220.htm "Automatic Updating | Parallels Desktop Users Guide | download.parallels.com"
[2]: https://web.archive.org/web/20221012155829/https://www.parallels.com/about/legal/privacy/ "Privacy Statement | parallels.com"
children:
-
name: Disable automatically downloading Parallels Desktop updates
name: Disable automatic downloads for Parallels Desktop updates
docs: |-
Automatic downloads are enabled by default, and this script disables automatic downloads.
@@ -570,11 +603,11 @@ actions:
- Check: `defaults read 'com.parallels.Parallels Desktop' 'Application preferences.Download updates automatically'`
- Values: 0 - Disabled, 1 - Enabled (default)
[1]: https://web.archive.org/web/20221012153810/https://download.parallels.com/desktop/v18/docs/en_US/Parallels-Desktop-Business-Edition-Administrators-Guide/37744.htm "Parallels Desktop Business Edition Administrator's Guide v18 - Configuring individual Macs | download.parallels.com"
[1]: https://web.archive.org/web/20221012153810/https://download.parallels.com/desktop/v18/docs/en_US/Parallels-Desktop-Business-Edition-Administrators-Guide/37744.htm "Parallels Desktop Business Edition Administrator's Guide v18 - Configuring individual Macs | download.parallels.com"
code: defaults write 'com.parallels.Parallels Desktop' 'Application preferences.Download updates automatically' -bool no
revertCode: defaults write 'com.parallels.Parallels Desktop' 'Application preferences.Download updates automatically' -bool yes
-
name: Disable automatically checking for Parallels Desktop updates
name: Disable automatic checks for Parallels Desktop updates
docs: |-
Automatic checks are weekly by default, and this script disables the checks completely.
@@ -593,7 +626,7 @@ actions:
category: Configure Apple Remote Desktop
children:
-
name: Deactivate the Remote Management Service
name: Disable remote management service
recommend: strict
code: sudo /System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -deactivate -stop
revertCode: sudo /System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -activate -restart -agent -console
@@ -604,26 +637,26 @@ actions:
sudo rm -rf /var/db/RemoteManagement
sudo defaults delete /Library/Preferences/com.apple.RemoteDesktop.plist
defaults delete ~/Library/Preferences/com.apple.RemoteDesktop.plist
sudo rm -r /Library/Application\ Support/Apple/Remote\ Desktop/
sudo rm -rf /Library/Application\ Support/Apple/Remote\ Desktop/
rm -r ~/Library/Application\ Support/Remote\ Desktop/
rm -r ~/Library/Containers/com.apple.RemoteDesktop
-
name: Disable Internet based spell correction
name: Disable online spell correction
code: defaults write NSGlobalDomain WebAutomaticSpellingCorrectionEnabled -bool false
revertCode: defaults delete NSGlobalDomain WebAutomaticSpellingCorrectionEnabled
-
name: Disable Remote Apple Events
name: Disable remote Apple events
recommend: strict
code: sudo systemsetup -setremoteappleevents off
revertCode: sudo systemsetup -setremoteappleevents on
-
name: Do not store documents to iCloud Drive by default
name: Disable automatic storage of documents in iCloud Drive
docs: https://macos-defaults.com/finder/nsdocumentsavenewdocumentstocloud.html
recommend: standard
code: defaults write NSGlobalDomain NSDocumentSaveNewDocumentsToCloud -bool false
revertCode: defaults delete NSGlobalDomain NSDocumentSaveNewDocumentsToCloud
-
name: Do not show recent items on dock
name: Disable display of recent applications on Dock
docs: https://developer.apple.com/documentation/devicemanagement/dock
code: defaults write com.apple.dock show-recents -bool false
revertCode: defaults delete com.apple.dock show-recents
@@ -636,7 +669,7 @@ actions:
category: Configure Siri
children:
-
name: Opt-out from Siri data collection
name: Disable participation in Siri data collection
recommend: standard
code: defaults write com.apple.assistant.support 'Siri Data Sharing Opt-In Status' -int 2
revertCode: defaults delete com.apple.assistant.support 'Siri Data Sharing Opt-In Status'
@@ -683,7 +716,7 @@ actions:
launchctl enable "gui/$UID/com.apple.Siri.agent"
sudo launchctl enable 'system/com.apple.Siri.agent'
if [ $(/usr/bin/csrutil status | awk '/status/ {print $5}' | sed 's/\.$//') = "enabled" ]; then
>&2 echo 'This script requires SIP to be disabled. Read more: https://developer.apple.com/documentation/security/disabling_and_enabling_system_integrity_protection''
>&2 echo 'This script requires SIP to be disabled. Read more: https://developer.apple.com/documentation/security/disabling_and_enabling_system_integrity_protection'
fi
-
name: Disable "Do you want to enable Siri?" pop-up
@@ -694,15 +727,15 @@ actions:
code: defaults write com.apple.SetupAssistant 'DidSeeSiriSetup' -bool True
revertCode: defaults delete com.apple.SetupAssistant 'DidSeeSiriSetup'
-
category: Hide Siri
category: Remove Siri from user interface
children:
-
name: Hide Siri from menu bar
name: Remove Siri from menu bar
recommend: strict
code: defaults write com.apple.systemuiserver 'NSStatusItem Visible Siri' 0
revertCode: defaults write com.apple.systemuiserver 'NSStatusItem Visible Siri' 1
-
name: Hide Siri from status menu
name: Remove Siri from status menu
recommend: strict
docs: https://derflounder.wordpress.com/2016/09/20/blocking-siri-on-macos-sierra/
code: |-
@@ -712,11 +745,11 @@ actions:
defaults delete com.apple.Siri 'StatusMenuVisible'
defaults delete com.apple.Siri 'UserHasDeclinedEnable'
-
name: Disable Spotlight indexing
name: Disable Spotlight indexing
code: sudo mdutil -i off -d /
revertCode: sudo mdutil -i on /
-
name: Disable Personalized advertisements and identifier collection
name: Disable personalized advertisements and identifier tracking
recommend: standard
docs: |-
This script enhances your privacy by deactivating Personalized Ads and disabling the collection
@@ -746,7 +779,7 @@ actions:
Please note: The `forceLimitAdTracking` key limits ad tracking [3] [4] and is found in CIS
benchmarks for macOS [4]. However, the official macOS documentation specifies that it is
applicable only to iOS 7 and later versions, not to macOS [3]. The key does not exist on the OS
applicable only to iOS 7 and newer versions, not to macOS [3]. The key does not exist on the OS
by default.
[1]: https://web.archive.org/web/20230731152633/https://www.apple.com/legal/privacy/data/en/apple-advertising/ "Legal - Apple Advertising & Privacy - Apple"
@@ -789,7 +822,7 @@ actions:
sudo defaults write /Library/Preferences/com.apple.alf globalstate -bool false
defaults write com.apple.security.firewall EnableFirewall -bool false
-
name: Turn on firewall logging
name: Enable firewall logging
recommend: standard
docs:
- https://www.stigviewer.com/stig/apple_os_x_10.13/2018-10-01/finding/V-81671
@@ -801,7 +834,7 @@ actions:
/usr/libexec/ApplicationFirewall/socketfilterfw --setloggingmode off
sudo defaults write /Library/Preferences/com.apple.alf loggingenabled -bool false
-
name: Turn on stealth mode
name: Enable stealth mode
recommend: standard
docs:
- https://www.stigviewer.com/stig/apple_os_x_10.8_mountain_lion_workstation/2015-02-10/finding/V-51327
@@ -816,16 +849,16 @@ actions:
sudo defaults write /Library/Preferences/com.apple.alf stealthenabled -bool false
defaults write com.apple.security.firewall EnableStealthMode -bool false
-
category: Disable auto-permitting incoming traffic for apps
category: Disable automatic permission for incoming traffic in applications
children:
-
name: Prevent automatically allowing incoming connections to signed apps
name: Disable automatic incoming connections for signed apps
docs: https://daiderd.com/nix-darwin/manual/index.html
recommend: strict
code: sudo defaults write /Library/Preferences/com.apple.alf allowsignedenabled -bool false
revertCode: sudo defaults write /Library/Preferences/com.apple.alf allowsignedenabled -bool true
-
name: Prevent automatically allowing incoming connections to downloaded signed apps
name: Disable automatic incoming connections for downloaded signed apps
docs: https://daiderd.com/nix-darwin/manual/index.html
recommend: strict
code: sudo defaults write /Library/Preferences/com.apple.alf allowdownloadsignedenabled -bool false
@@ -845,18 +878,18 @@ actions:
code: sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.captive.control.plist Active -bool false
revertCode: sudo defaults delete /Library/Preferences/SystemConfiguration/com.apple.captive.control.plist Active
-
category: Use screen saver for protection
category: Enable protective screen saver
children:
-
name: Require a password to wake the computer from sleep or screen saver
name: Enable password requirement for waking from sleep or screen saver
# The screen saver acts as a session lock and prevents unauthorized users from accessing the current user's account.
docs: https://www.stigviewer.com/stig/apple_macos_11_big_sur/2020-11-27/finding/V-230744
code: sudo defaults write /Library/Preferences/com.apple.screensaver askForPassword -bool true
revertCode: sudo defaults delete /Library/Preferences/com.apple.screensaver askForPassword
-
name: Initiate session lock five seconds after screen saver is started
name: Enable session lock five seconds after screen saver initiation
docs: https://www.stigviewer.com/stig/apple_macos_11_big_sur/2020-11-27/finding/V-230745
# An unattended system with an excessive grace period is vulnerable to a malicious user.
# An unattended system with an excessive grace period is vulnerable to a malicious user.
code: sudo defaults write /Library/Preferences/com.apple.screensaver 'askForPasswordDelay' -int 5
revertCode: sudo defaults delete /Library/Preferences/com.apple.screensaver 'askForPasswordDelay'
-
@@ -864,36 +897,36 @@ actions:
docs:
- https://www.stigviewer.com/stig/apple_macos_11_big_sur/2021-06-16/finding/V-230823
- https://www.stigviewer.com/stig/apple_os_x_10.13/2018-10-01/finding/V-81615
children:
-
name: Disables signing in as Guest from the login screen
code: sudo defaults write /Library/Preferences/com.apple.loginwindow GuestEnabled -bool NO
revetCode: sudo defaults write /Library/Preferences/com.apple.loginwindow GuestEnabled -bool YES
-
name: Disables Guest access to file shares over AF
code: sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.smb.server AllowGuestAccess -bool NO
revetCode: sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.smb.server AllowGuestAccess -bool YES
-
name: Disables Guest access to file shares over SMB
code: sudo defaults write /Library/Preferences/com.apple.AppleFileServer guestAccess -bool NO
revetCode: sudo defaults write /Library/Preferences/com.apple.AppleFileServer guestAccess -bool YES
-
category: Prevent unauthorized connections
children:
-
name: Disable remote login (incoming SSH and SFTP connections)
name: Disable guest sign-in from login screen
code: sudo defaults write /Library/Preferences/com.apple.loginwindow GuestEnabled -bool NO
revertCode: sudo defaults write /Library/Preferences/com.apple.loginwindow GuestEnabled -bool YES
-
name: Disable guest access to file shares over AF
code: sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.smb.server AllowGuestAccess -bool NO
revertCode: sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.smb.server AllowGuestAccess -bool YES
-
name: Disable guest access to file shares over SMB
code: sudo defaults write /Library/Preferences/com.apple.AppleFileServer guestAccess -bool NO
revertCode: sudo defaults write /Library/Preferences/com.apple.AppleFileServer guestAccess -bool YES
-
category: Disable unauthorized connections
children:
-
name: Disable incoming SSH and SFTP remote logins
recommend: standard
docs: https://osxdaily.com/2016/08/16/enable-ssh-mac-command-line/
# Check if enabled: sudo systemsetup -getremotelogin, returns "Remote Login: On" or "Off"
code: echo 'yes' | sudo systemsetup -setremotelogin off
revertCode: sudo systemsetup -setremotelogin on
-
name: Disable insecure TFTP service
name: Disable the insecure TFTP service
recommend: standard
# If the system does not require Trivial File Transfer Protocol (TFTP), then support for
# it is non-essential and should be disabled. The information system should be configured to
# provide only essential capabilities. Disabling TFTP helps prevent the unauthorized connection
# of devices and the unauthorized transfer of information.
# of devices and the unauthorized transfer of information.
docs: https://www.stigviewer.com/stig/apple_macos_11_big_sur/2021-06-16/finding/V-230813
code: sudo launchctl disable 'system/com.apple.tftpd'
revertCode: sudo launchctl enable 'system/com.apple.tftpd'
@@ -921,13 +954,13 @@ actions:
- https://www.cups.org/doc/security.html # Security risks
children:
-
name: Disable sharing of local printers with other computers
name: Disable local printer sharing with other computers
recommend: standard
docs: https://www.cups.org/doc/man-cupsctl.html
code: cupsctl --no-share-printers
revertCode: cupsctl --share-printers
-
name: Disable printing from any address including the Internet
name: Disable printing from external addresses, including the internet
recommend: standard
docs: https://www.cups.org/doc/man-cupsctl.html
code: cupsctl --no-remote-any
@@ -952,7 +985,7 @@ actions:
category: Clean File Quarantine from downloaded files
children:
-
name: Clear File Quarantine logs of all downloaded files
name: Clear logs of all downloaded files from File Quarantine
recommend: strict
docs:
- https://www.macobserver.com/tips/how-to/your-mac-remembers-everything-you-download-heres-how-to-clear-download-history/
@@ -969,7 +1002,7 @@ actions:
if ls -lO "$db_file" | grep --silent 'schg'; then
sudo chflags noschg "$db_file"
echo "Found and removed system immutable flag"
has_sytem_immutable_flag=true
has_system_immutable_flag=true
fi
if ls -lO "$db_file" | grep --silent 'uchg'; then
sudo chflags nouchg "$db_file"
@@ -978,7 +1011,7 @@ actions:
fi
sqlite3 "$db_file" "$db_query"
echo "Executed the query \"$db_query\""
if [ "$has_sytem_immutable_flag" = true ] ; then
if [ "$has_system_immutable_flag" = true ] ; then
sudo chflags schg "$db_file"
echo "Added system immutable flag back"
fi
@@ -1012,10 +1045,10 @@ actions:
' \
{} \;
-
category: Disable File Quarantine from tracking downloaded files
category: Disable macOS File Quarantine tracking for downloaded files
children:
-
name: Prevent quarantine from logging downloaded files
name: Disable downloaded file logging in quarantine
docs:
- https://eclecticlight.co/2019/04/25/%F0%9F%8E%97-quarantine-apps/
- https://eclecticlight.co/2017/12/11/xattr-com-apple-quarantine-the-quarantine-flag/
@@ -1038,7 +1071,7 @@ actions:
>&2 echo "Cannot revert immutability, file does not exist at\"$file_to_lock\""
fi
-
name: Disable using extended quarantine attribute on downloaded files (disables warning)
name: Disable extended quarantine attribute for downloaded files (disables warning)
# Disables dialogs shown when opening an application for the first time
# i.e. "Application Downloaded from Internet" quarantine warning.
docs:
@@ -1054,7 +1087,7 @@ actions:
# Can protect against unknown threats.
children:
-
name: Prevent Gatekeeper from automatically reactivating itself
name: Disable Gatekeeper's automatic reactivation
docs:
- https://osxdaily.com/2015/11/05/stop-gatekeeper-auto-rearm-mac-os-x/
- https://www.cnet.com/tech/computing/how-to-disable-gatekeeper-permanently-on-os-x/
@@ -1071,8 +1104,8 @@ actions:
code: |-
os_major_ver=$(sw_vers -productVersion | awk -F "." '{print $1}')
os_minor_ver=$(sw_vers -productVersion | awk -F "." '{print $2}')
if [[ $os_major_ver -le 10 \
|| ( $os_major_ver -eq 10 && $os_minor_ver -lt 7 ) \
if [[ $os_major_ver -le 10 \
|| ( $os_major_ver -eq 10 && $os_minor_ver -lt 7 ) \
]]; then
echo "No action needed, Gatekeeper is not available this OS version"
else
@@ -1090,8 +1123,8 @@ actions:
revertCode: |-
os_major_ver=$(sw_vers -productVersion | awk -F "." '{print $1}')
os_minor_ver=$(sw_vers -productVersion | awk -F "." '{print $2}')
if [[ $os_major_ver -le 10 \
|| ( $os_major_ver -eq 10 && $os_minor_ver -lt 7 ) \
if [[ $os_major_ver -le 10 \
|| ( $os_major_ver -eq 10 && $os_minor_ver -lt 7 ) \
]]; then
>&2 echo "Gatekeeper is not available in this OS version"
else
@@ -1107,7 +1140,7 @@ actions:
fi
fi
-
name: Disable Library Validation Entitlement (checks signature of libraries)
name: Disable library validation entitlement (library signature validation)
docs:
- https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_disable-library-validation
- https://www.macenhance.com/docs/general/sip-library-validation.html
@@ -1121,25 +1154,25 @@ actions:
- https://macadminsdoc.readthedocs.io/en/master/Profiles-and-Settings/OS-X-Updates.html
children:
-
name: Disable automatically checking for updates
name: Disable automatic checks for updates
docs: https://developer.apple.com/documentation/devicemanagement/softwareupdate
code: |-
# For OS X Yosemite and later (>= 10.10)
# For OS X Yosemite and newer (>= 10.10)
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticCheckEnabled' -bool false
revertCode: |-
# For OS X Yosemite and later (>= 10.10)
# For OS X Yosemite and newer (>= 10.10)
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticCheckEnabled' -bool true
-
name: Disable automatically downloading new updates when available
name: Disable automatic downloads for updates
docs: https://developer.apple.com/documentation/devicemanagement/softwareupdate
code: |-
# For OS X Yosemite and later (>= 10.10)
# For OS X Yosemite and newer (>= 10.10)
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticDownload' -bool false
revertCode: |-
# For OS X Yosemite and later (>= 10.10)
# For OS X Yosemite and newer (>= 10.10)
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticDownload' -bool true
-
name: Disable automatically installing macOS updates
name: Disable automatic installation of macOS updates
docs:
# References for AutoUpdateRestartRequired
- https://kb.vmware.com/s/article/2960635
@@ -1149,48 +1182,48 @@ actions:
code: |-
# For OS X Yosemite through macOS High Sierra (>= 10.10 && < 10.14)
sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdateRestartRequired' -bool false
# For Mojave and later (>= 10.14)
# For Mojave and newer (>= 10.14)
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallMacOSUpdates' -bool false
revertCode: |-
# For OS X Yosemite through macOS High Sierra (>= 10.10 && < 10.14)
sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdateRestartRequired' -bool true
# For Mojave and later (>= 10.14)
# For Mojave and newer (>= 10.14)
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallMacOSUpdates' -bool true
-
name: Disable automatically updating app from the App Store
name: Disable automatic app updates from the App Store
docs:
- https://kb.vmware.com/s/article/2960635
- https://derflounder.wordpress.com/2018/12/28/enabling-automatic-macos-software-updates-for-os-x-yosemite-through-macos-mojave/
code: |-
# For OS X Yosemite and later (>= 10.10)
# For OS X Yosemite and newer (>= 10.10)
sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdate' -bool false
# For Mojave and later (>= 10.14)
# For Mojave and newer (>= 10.14)
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallAppUpdates' -bool false
revertCode: |-
# For OS X Yosemite and later
# For OS X Yosemite and newer
sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdate' -bool true
# For Mojave and later (>= 10.14)
# For Mojave and newer (>= 10.14)
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallAppUpdates' -bool true
-
name: Disable installation of macOS beta releases
name: Disable macOS beta release installation
docs: https://support.apple.com/en-gb/HT203018
code: |-
# For OS X Yosemite and later (>= 10.10)
# For OS X Yosemite and newer (>= 10.10)
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AllowPreReleaseInstallation' -bool false
revertCode: |-
# For OS X Yosemite and later (>= 10.10)
# For OS X Yosemite and newer (>= 10.10)
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AllowPreReleaseInstallation' -bool true
-
name: Disable automatically installing configuration data (e.g. XProtect, Gatekeeper, MRT)
name: Disable automatic installation for configuration data (e.g. XProtect, Gatekeeper, MRT)
docs: https://derflounder.wordpress.com/2018/12/28/enabling-automatic-macos-software-updates-for-os-x-yosemite-through-macos-mojave/
code: |-
# For OS X Yosemite and later (>= 10.10)
# For OS X Yosemite and newer (>= 10.10)
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'ConfigDataInstall' -bool false
revertCode: |-
# For OS X Yosemite and later (>= 10.10)
# For OS X Yosemite and newer (>= 10.10)
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'ConfigDataInstall' -bool true
-
name: Disable automatically installing system data files and security updates
name: Disable automatic installation for system data files and security updates
docs:
# References for CriticalUpdateInstall
- https://derflounder.wordpress.com/2014/12/24/managing-os-xs-automatic-security-updates/
@@ -1198,10 +1231,10 @@ actions:
# References for softwareupdate --background-critical
- https://managingosx.wordpress.com/2013/04/30/undocumented-options/
code: |-
# For OS X Yosemite and later (>= 10.10)
# For OS X Yosemite and newer (>= 10.10)
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'CriticalUpdateInstall' -bool false
revertCode: |-
# For OS X Yosemite and later (>= 10.10)
# For OS X Yosemite and newer (>= 10.10)
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'CriticalUpdateInstall' -bool true
# Trigger background check with normal scan (critical updates only)
sudo softwareupdate --background-critical

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M464 160c8.8 0 16 7.2 16 16V336c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V176c0-8.8 7.2-16 16-16H464zM80 96C35.8 96 0 131.8 0 176V336c0 44.2 35.8 80 80 80H464c44.2 0 80-35.8 80-80V320c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32V176c0-44.2-35.8-80-80-80H80zm368 96H96V320H448V192z"/></svg>

After

Width:  |  Height:  |  Size: 392 B

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M464 160c8.8 0 16 7.2 16 16V336c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V176c0-8.8 7.2-16 16-16H464zM80 96C35.8 96 0 131.8 0 176V336c0 44.2 35.8 80 80 80H464c44.2 0 80-35.8 80-80V320c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32V176c0-44.2-35.8-80-80-80H80zm208 96H96V320H288V192z"/></svg>

After

Width:  |  Height:  |  Size: 392 B

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>

After

Width:  |  Height:  |  Size: 363 B

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M208 0H332.1c12.7 0 24.9 5.1 33.9 14.1l67.9 67.9c9 9 14.1 21.2 14.1 33.9V336c0 26.5-21.5 48-48 48H208c-26.5 0-48-21.5-48-48V48c0-26.5 21.5-48 48-48zM48 128h80v64H64V448H256V416h64v48c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V176c0-26.5 21.5-48 48-48z"/></svg>

After

Width:  |  Height:  |  Size: 365 B

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M64 0C28.7 0 0 28.7 0 64V352c0 35.3 28.7 64 64 64H240l-10.7 32H160c-17.7 0-32 14.3-32 32s14.3 32 32 32H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H346.7L336 416H512c35.3 0 64-28.7 64-64V64c0-35.3-28.7-64-64-64H64zM512 64V288H64V64H512z"/></svg>

After

Width:  |  Height:  |  Size: 342 B

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M464 256A208 208 0 1 0 48 256a208 208 0 1 0 416 0zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256zm177.6 62.1C192.8 334.5 218.8 352 256 352s63.2-17.5 78.4-33.9c9-9.7 24.2-10.4 33.9-1.4s10.4 24.2 1.4 33.9c-22 23.8-60 49.4-113.6 49.4s-91.7-25.5-113.6-49.4c-9-9.7-8.4-24.9 1.4-33.9s24.9-8.4 33.9 1.4zM144.4 208a32 32 0 1 1 64 0 32 32 0 1 1 -64 0zm192-32a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>

After

Width:  |  Height:  |  Size: 495 B

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M64 0C28.7 0 0 28.7 0 64V448c0 35.3 28.7 64 64 64H320c35.3 0 64-28.7 64-64V160H256c-17.7 0-32-14.3-32-32V0H64zM256 0V128H384L256 0zM216 232V334.1l31-31c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-72 72c-9.4 9.4-24.6 9.4-33.9 0l-72-72c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l31 31V232c0-13.3 10.7-24 24-24s24 10.7 24 24z"/></svg>

After

Width:  |  Height:  |  Size: 428 B

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M64 32C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V173.3c0-17-6.7-33.3-18.7-45.3L352 50.7C340 38.7 323.7 32 306.7 32H64zm0 96c0-17.7 14.3-32 32-32H288c17.7 0 32 14.3 32 32v64c0 17.7-14.3 32-32 32H96c-17.7 0-32-14.3-32-32V128zM224 288a64 64 0 1 1 0 128 64 64 0 1 1 0-128z"/></svg>

After

Width:  |  Height:  |  Size: 407 B

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M384 480h48c11.4 0 21.9-6 27.6-15.9l112-192c5.8-9.9 5.8-22.1 .1-32.1S555.5 224 544 224H144c-11.4 0-21.9 6-27.6 15.9L48 357.1V96c0-8.8 7.2-16 16-16H181.5c4.2 0 8.3 1.7 11.3 4.7l26.5 26.5c21 21 49.5 32.8 79.2 32.8H416c8.8 0 16 7.2 16 16v32h48V160c0-35.3-28.7-64-64-64H298.5c-17 0-33.3-6.7-45.3-18.7L226.7 50.7c-12-12-28.3-18.7-45.3-18.7H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H87.7 384z"/></svg>

After

Width:  |  Height:  |  Size: 503 B

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M0 96C0 60.7 28.7 32 64 32H196.1c19.1 0 37.4 7.6 50.9 21.1L289.9 96H448c35.3 0 64 28.7 64 64V416c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V96zM64 80c-8.8 0-16 7.2-16 16V416c0 8.8 7.2 16 16 16H448c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16H286.6c-10.6 0-20.8-4.2-28.3-11.7L213.1 87c-4.5-4.5-10.6-7-17-7H64z"/></svg>

After

Width:  |  Height:  |  Size: 419 B

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M352 256c0 22.2-1.2 43.6-3.3 64H163.3c-2.2-20.4-3.3-41.8-3.3-64s1.2-43.6 3.3-64H348.7c2.2 20.4 3.3 41.8 3.3 64zm28.8-64H503.9c5.3 20.5 8.1 41.9 8.1 64s-2.8 43.5-8.1 64H380.8c2.1-20.6 3.2-42 3.2-64s-1.1-43.4-3.2-64zm112.6-32H376.7c-10-63.9-29.8-117.4-55.3-151.6c78.3 20.7 142 77.5 171.9 151.6zm-149.1 0H167.7c6.1-36.4 15.5-68.6 27-94.7c10.5-23.6 22.2-40.7 33.5-51.5C239.4 3.2 248.7 0 256 0s16.6 3.2 27.8 13.8c11.3 10.8 23 27.9 33.5 51.5c11.6 26 20.9 58.2 27 94.7zm-209 0H18.6C48.6 85.9 112.2 29.1 190.6 8.4C165.1 42.6 145.3 96.1 135.3 160zM8.1 192H131.2c-2.1 20.6-3.2 42-3.2 64s1.1 43.4 3.2 64H8.1C2.8 299.5 0 278.1 0 256s2.8-43.5 8.1-64zM194.7 446.6c-11.6-26-20.9-58.2-27-94.6H344.3c-6.1 36.4-15.5 68.6-27 94.6c-10.5 23.6-22.2 40.7-33.5 51.5C272.6 508.8 263.3 512 256 512s-16.6-3.2-27.8-13.8c-11.3-10.8-23-27.9-33.5-51.5zM135.3 352c10 63.9 29.8 117.4 55.3 151.6C112.2 482.9 48.6 426.1 18.6 352H135.3zm358.1 0c-30 74.1-93.6 130.9-171.9 151.6c25.5-34.2 45.2-87.7 55.3-151.6H493.4z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M504.3 273.6c4.9-4.5 7.7-10.9 7.7-17.6s-2.8-13-7.7-17.6l-112-104c-7-6.5-17.2-8.2-25.9-4.4s-14.4 12.5-14.4 22l0 56-192 0 0-56c0-9.5-5.7-18.2-14.4-22s-18.9-2.1-25.9 4.4l-112 104C2.8 243 0 249.3 0 256s2.8 13 7.7 17.6l112 104c7 6.5 17.2 8.2 25.9 4.4s14.4-12.5 14.4-22l0-56 192 0 0 56c0 9.5 5.7 18.2 14.4 22s18.9 2.1 25.9-4.4l112-104z"/></svg>

After

Width:  |  Height:  |  Size: 440 B

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"/></svg>

After

Width:  |  Height:  |  Size: 343 B

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M73 39c-14.8-9.1-33.4-9.4-48.5-.9S0 62.6 0 80V432c0 17.4 9.4 33.4 24.5 41.9s33.7 8.1 48.5-.9L361 297c14.3-8.7 23-24.2 23-41s-8.7-32.2-23-41L73 39z"/></svg>

After

Width:  |  Height:  |  Size: 257 B

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M0 80V229.5c0 17 6.7 33.3 18.7 45.3l176 176c25 25 65.5 25 90.5 0L418.7 317.3c25-25 25-65.5 0-90.5l-176-176c-12-12-28.3-18.7-45.3-18.7H48C21.5 32 0 53.5 0 80zm112 32a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>

After

Width:  |  Height:  |  Size: 310 B

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M224 16c-6.7 0-10.8-2.8-15.5-6.1C201.9 5.4 194 0 176 0c-30.5 0-52 43.7-66 89.4C62.7 98.1 32 112.2 32 128c0 14.3 25 27.1 64.6 35.9c-.4 4-.6 8-.6 12.1c0 17 3.3 33.2 9.3 48H45.4C38 224 32 230 32 237.4c0 1.7 .3 3.4 1 5l38.8 96.9C28.2 371.8 0 423.8 0 482.3C0 498.7 13.3 512 29.7 512H418.3c16.4 0 29.7-13.3 29.7-29.7c0-58.5-28.2-110.4-71.7-143L415 242.4c.6-1.6 1-3.3 1-5c0-7.4-6-13.4-13.4-13.4H342.7c6-14.8 9.3-31 9.3-48c0-4.1-.2-8.1-.6-12.1C391 155.1 416 142.3 416 128c0-15.8-30.7-29.9-78-38.6C324 43.7 302.5 0 272 0c-18 0-25.9 5.4-32.5 9.9c-4.8 3.3-8.8 6.1-15.5 6.1zm56 208H267.6c-16.5 0-31.1-10.6-36.3-26.2c-2.3-7-12.2-7-14.5 0c-5.2 15.6-19.9 26.2-36.3 26.2H168c-22.1 0-40-17.9-40-40V169.6c28.2 4.1 61 6.4 96 6.4s67.8-2.3 96-6.4V184c0 22.1-17.9 40-40 40zm-88 96l16 32L176 480 128 288l64 32zm128-32L272 480 240 352l16-32 64-32z"/></svg>

After

Width:  |  Height:  |  Size: 934 B

View File

@@ -0,0 +1 @@
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z"/></svg>

After

Width:  |  Height:  |  Size: 390 B

View File

@@ -7,5 +7,3 @@
@forward "./mixins";
@forward "./components/card";
@forward "./third-party-extensions/tooltip.scss";

View File

@@ -1,45 +0,0 @@
// Based on https://github.com/Akryum/v-tooltip/blob/83615e394c96ca491a4df04b892ae87e833beb97/demo-src/src/App.vue#L179-L303
@use "@/presentation/assets/styles/colors" as *;
.tooltip {
display: block !important;
z-index: 10000;
.tooltip-inner {
background: $color-primary-darkest;
color: $color-on-primary;
border-radius: 16px;
padding: 5px 10px 4px;
}
.tooltip-arrow {
width: 0;
height: 0;
border-style: solid;
position: absolute;
margin: 5px;
border-color: $color-primary-darkest;
z-index: 1;
}
&[x-placement^="top"] {
margin-bottom: 5px;
.tooltip-arrow {
border-width: 5px 5px 0 5px;
border-left-color: transparent !important;
border-right-color: transparent !important;
border-bottom-color: transparent !important;
bottom: -5px;
left: calc(50% - 5px);
margin-top: 0;
margin-bottom: 0;
}
}
&[aria-hidden='true'] {
visibility: hidden;
opacity: 0;
transition: opacity .15s, visibility .15s;
}
&[aria-hidden='false'] {
visibility: visible;
opacity: 1;
transition: opacity .15s;
}
}

View File

@@ -1,7 +1,5 @@
import { IconBootstrapper } from './Modules/IconBootstrapper';
import { VueConstructor, IVueBootstrapper } from './IVueBootstrapper';
import { VueBootstrapper } from './Modules/VueBootstrapper';
import { TooltipBootstrapper } from './Modules/TooltipBootstrapper';
import { RuntimeSanityValidator } from './Modules/RuntimeSanityValidator';
import { AppInitializationLogger } from './Modules/AppInitializationLogger';
@@ -15,9 +13,7 @@ export class ApplicationBootstrapper implements IVueBootstrapper {
private static getAllBootstrappers(): IVueBootstrapper[] {
return [
new IconBootstrapper(),
new VueBootstrapper(),
new TooltipBootstrapper(),
new RuntimeSanityValidator(),
new AppInitializationLogger(),
];

View File

@@ -1,38 +0,0 @@
import { library } from '@fortawesome/fontawesome-svg-core';
import { faGithub } from '@fortawesome/free-brands-svg-icons';
/** BRAND ICONS (PREFIX: fab) */
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
/** REGULAR ICONS (PREFIX: far) */
import { faFolderOpen, faFolder, faSmile } from '@fortawesome/free-regular-svg-icons';
/** SOLID ICONS (PREFIX: fas (default)) */
import {
faTimes, faFileDownload, faCopy, faSearch, faInfoCircle, faUserSecret, faDesktop, faTag, faGlobe,
faSave, faBatteryFull, faBatteryHalf, faPlay, faArrowsAltH,
} from '@fortawesome/free-solid-svg-icons';
import { IVueBootstrapper, VueConstructor } from '../IVueBootstrapper';
export class IconBootstrapper implements IVueBootstrapper {
public bootstrap(vue: VueConstructor): void {
library.add(
faGithub,
faUserSecret,
faSmile,
faDesktop,
faGlobe,
faTag,
faFolderOpen,
faFolder,
faTimes,
faFileDownload,
faSave,
faCopy,
faPlay,
faSearch,
faBatteryFull,
faBatteryHalf,
faInfoCircle,
faArrowsAltH,
);
vue.component('font-awesome-icon', FontAwesomeIcon);
}
}

View File

@@ -1,8 +0,0 @@
import VTooltip from 'v-tooltip';
import { VueConstructor, IVueBootstrapper } from '../IVueBootstrapper';
export class TooltipBootstrapper implements IVueBootstrapper {
public bootstrap(vue: VueConstructor): void {
vue.use(VTooltip);
}
}

View File

@@ -7,11 +7,12 @@
<TheCodeButtons class="app__row app__code-buttons" />
<TheFooter />
</div>
<OptionalDevToolkit />
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { defineAsyncComponent, defineComponent } from 'vue';
import TheHeader from '@/presentation/components/TheHeader.vue';
import TheFooter from '@/presentation/components/TheFooter/TheFooter.vue';
import TheCodeButtons from '@/presentation/components/Code/CodeButtons/TheCodeButtons.vue';
@@ -22,6 +23,10 @@ import { provideDependencies } from '../bootstrapping/DependencyProvider';
const singletonAppContext = await buildContext();
const OptionalDevToolkit = process.env.NODE_ENV !== 'production'
? defineAsyncComponent(() => import('@/presentation/components/DevToolkit/DevToolkit.vue'))
: null;
export default defineComponent({
components: {
TheHeader,
@@ -29,6 +34,7 @@ export default defineComponent({
TheScriptArea,
TheSearchBar,
TheFooter,
OptionalDevToolkit,
},
setup() {
provideDependencies(singletonAppContext); // In Vue 3.0 we can move it to main.ts
@@ -59,5 +65,4 @@ export default defineComponent({
}
}
}
</style>

View File

@@ -4,30 +4,30 @@
type="button"
@click="onClicked"
>
<font-awesome-icon
<AppIcon
class="button__icon"
:icon="[iconPrefix, iconName]"
size="2x"
:icon="iconName"
/>
<div class="button__text">{{text}}</div>
</button>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { defineComponent, PropType } from 'vue';
import { IconName } from '@/presentation/components/Shared/Icon/IconName';
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
export default defineComponent({
components: {
AppIcon,
},
props: {
text: {
type: String,
required: true,
},
iconPrefix: {
type: String,
required: true,
},
iconName: {
type: String,
type: String as PropType<IconName>,
required: true,
},
},
@@ -64,6 +64,10 @@ export default defineComponent({
box-shadow: 0 3px 9px $color-primary-darkest;
border-radius: 4px;
&__icon {
font-size: 2em;
}
@include clickable;
width: 10%;

View File

@@ -3,9 +3,9 @@
<span class="dollar">$</span>
<code><slot /></code>
<TooltipWrapper>
<font-awesome-icon
<AppIcon
class="copy-button"
:icon="['fas', 'copy']"
icon="copy"
@click="copyCode"
/>
<template v-slot:tooltip>
@@ -19,10 +19,12 @@
import { defineComponent, useSlots } from 'vue';
import { Clipboard } from '@/infrastructure/Clipboard';
import TooltipWrapper from '@/presentation/components/Shared/TooltipWrapper.vue';
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
export default defineComponent({
components: {
TooltipWrapper,
AppIcon,
},
setup() {
const slots = useSlots();

View File

@@ -14,7 +14,7 @@
<p>
<strong>2. The hard (manual) alternative</strong>. This requires you to do additional manual
steps. If you are unsure how to follow the instructions, hover on information
(<font-awesome-icon :icon="['fas', 'info-circle']" />)
(<AppIcon icon="circle-info" />)
icons near the steps, or follow the easy alternative described above.
</p>
<p>
@@ -27,9 +27,9 @@
<div class="step__action">
<span>{{ step.action.instruction }}</span>
<TooltipWrapper v-if="step.action.details">
<font-awesome-icon
<AppIcon
class="explanation"
:icon="['fas', 'info-circle']"
icon="circle-info"
/>
<template v-slot:tooltip>
<div v-html="step.action.details" />
@@ -39,9 +39,9 @@
<div v-if="step.code" class="step__code">
<CodeInstruction>{{ step.code.instruction }}</CodeInstruction>
<TooltipWrapper v-if="step.code.details">
<font-awesome-icon
<AppIcon
class="explanation"
:icon="['fas', 'info-circle']"
icon="circle-info"
/>
<template v-slot:tooltip>
<div v-html="step.code.details" />
@@ -62,6 +62,7 @@ import {
import { InjectionKeys } from '@/presentation/injectionSymbols';
import { OperatingSystem } from '@/domain/OperatingSystem';
import TooltipWrapper from '@/presentation/components/Shared/TooltipWrapper.vue';
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
import CodeInstruction from './CodeInstruction.vue';
import { IInstructionListData } from './InstructionListData';
@@ -69,6 +70,7 @@ export default defineComponent({
components: {
CodeInstruction,
TooltipWrapper,
AppIcon,
},
props: {
data: {

View File

@@ -4,19 +4,16 @@
v-if="canRun"
text="Run"
v-on:click="executeCode"
icon-prefix="fas"
icon-name="play"
/>
<IconButton
:text="isDesktopVersion ? 'Save' : 'Download'"
v-on:click="saveCode"
icon-prefix="fas"
:icon-name="isDesktopVersion ? 'save' : 'file-download'"
:icon-name="isDesktopVersion ? 'floppy-disk' : 'file-arrow-down'"
/>
<IconButton
text="Copy"
v-on:click="copyCode"
icon-prefix="fas"
icon-name="copy"
/>
<ModalDialog v-if="instructions" v-model="areInstructionsVisible">

View File

@@ -0,0 +1,71 @@
<template>
<div class="dev-toolkit">
<div class="title">
Tools
</div>
<hr />
<button
v-for="action in devActions"
@click="action.handler"
:key="action.name"
type="button">
{{ action.name }}
</button>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { dumpNames } from './DumpNames';
export default defineComponent({
setup() {
const devActions: readonly DevAction[] = [
{
name: 'Log script/category names',
handler: async () => {
const names = await dumpNames();
console.log(names);
},
},
];
return {
devActions,
};
},
});
interface DevAction {
readonly name: string;
readonly handler: () => void | Promise<void>;
}
</script>
<style scoped lang="scss">
@use "@/presentation/assets/styles/main" as *;
.dev-toolkit {
position: fixed;
top: 0;
right: 0;
background-color: rgba($color-on-surface, 0.5);
color: $color-on-primary;
padding: 10px;
z-index: 10000;
.title {
font-weight: bold;
text-align: center;
}
button {
display: block;
margin-bottom: 10px;
padding: 5px 10px;
background-color: $color-primary;
color: $color-on-primary;
border: none;
cursor: pointer;
}
}
</style>

View File

@@ -0,0 +1,35 @@
import { IApplication } from '@/domain/IApplication';
import { ApplicationFactory } from '@/application/ApplicationFactory';
export async function dumpNames(): Promise<string> {
const application = await ApplicationFactory.Current.getApp();
const names = collectNames(application);
const output = names.join('\n');
return output;
}
function collectNames(application: IApplication): string[] {
const { collections } = application;
const allNames = [
...collections.flatMap((collection) => collection.getAllCategories().map((c) => c.name)),
...collections.flatMap((collection) => collection.getAllScripts().map((c) => c.name)),
];
const uniqueNames = [...new Set(allNames)];
return shuffle(uniqueNames);
}
/*
Shuffle an array of strings, returning a new array with elements in random order.
Uses the Fisher-Yates (or Durstenfeld) algorithm.
*/
function shuffle(array: readonly string[]): string[] {
const shuffledArray = [...array];
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffledArray[i], shuffledArray[j]] = [shuffledArray[j], shuffledArray[i]];
}
return shuffledArray;
}

View File

@@ -4,9 +4,9 @@
:style="{ cursor: cursorCssValue }"
@mousedown="startResize">
<div class="line" />
<font-awesome-icon
<AppIcon
class="icon"
:icon="['fas', 'arrows-alt-h']"
icon="left-right"
/>
<div class="line" />
</div>
@@ -14,8 +14,12 @@
<script lang="ts">
import { defineComponent, onUnmounted } from 'vue';
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
export default defineComponent({
components: {
AppIcon,
},
emits: {
/* eslint-disable @typescript-eslint/no-unused-vars */
resized: (displacementX: number) => true,

View File

@@ -17,18 +17,18 @@
</span>
<span v-else>Oh no 😢</span>
<!-- Expand icon -->
<font-awesome-icon
<AppIcon
class="card__inner__expand-icon"
:icon="['far', isExpanded ? 'folder-open' : 'folder']"
:icon="isExpanded ? 'folder-open' : 'folder'"
/>
<!-- Indeterminate and full states -->
<div class="card__inner__state-icons">
<font-awesome-icon
:icon="['fa', 'battery-half']"
<AppIcon
icon="battery-half"
v-if="isAnyChildSelected && !areAllChildrenSelected"
/>
<font-awesome-icon
:icon="['fa', 'battery-full']"
<AppIcon
icon="battery-full"
v-if="areAllChildrenSelected"
/>
</div>
@@ -38,8 +38,8 @@
<ScriptsTree :categoryId="categoryId" />
</div>
<div class="card__expander__close-button">
<font-awesome-icon
:icon="['fas', 'times']"
<AppIcon
icon="xmark"
v-on:click="collapse()"
/>
</div>
@@ -52,6 +52,7 @@ import {
defineComponent, ref, watch, computed,
inject,
} from 'vue';
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
import { InjectionKeys } from '@/presentation/injectionSymbols';
import ScriptsTree from '@/presentation/components/Scripts/View/Tree/ScriptsTree.vue';
import { sleep } from '@/infrastructure/Threading/AsyncSleep';
@@ -59,6 +60,7 @@ import { sleep } from '@/infrastructure/Threading/AsyncSleep';
export default defineComponent({
components: {
ScriptsTree,
AppIcon,
},
props: {
categoryId: {

View File

@@ -18,7 +18,7 @@
class="search__query__close-button"
v-on:click="clearSearchQuery()"
>
<font-awesome-icon :icon="['fas', 'times']" />
<AppIcon icon="xmark" />
</div>
</div>
<div v-if="!searchHasMatches" class="search-no-matches">
@@ -41,6 +41,7 @@ import {
defineComponent, PropType, ref, computed,
inject,
} from 'vue';
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
import { InjectionKeys } from '@/presentation/injectionSymbols';
import ScriptsTree from '@/presentation/components/Scripts/View/Tree/ScriptsTree.vue';
import CardList from '@/presentation/components/Scripts/View/Cards/CardList.vue';
@@ -52,6 +53,7 @@ export default defineComponent({
components: {
ScriptsTree,
CardList,
AppIcon,
},
props: {
currentView: {

View File

@@ -100,20 +100,27 @@ $text-size: 0.75em; // Lower looks bad on Firefox
}
}
}
/*
Different browsers have different <p>, we should even this out.
See CSS 2.1 specification https://www.w3.org/TR/CSS21/sample.html.
*/
p {
@mixin set-paragraph-vertical-gap($paragraph-vertical-gap) {
p {
/*
Remove default browser margin on paragraphs to ensure:
1. A markdown text represented as a list (e.g. <ul>, <ol>) has same vertical spacing as a standalone paragraph (</p>).
2. The first paragraph in a sequence (first `<p>` usage) does not introduce top spacing.
3. Uniformity, so margin can be set consistently across browsers.
*/
margin: 0;
}
/*
Remove surrounding padding so a markdown text that is a list (e.g. <ul>)
has same outer padding as a paragraph (</p>).
Introduce spacing between successive elements and paragraphs.
E.g., spacing between two paragraphs (`p`), paragraphs after lists (<ul>, <ol>)...
*/
margin: 0;
+ p {
margin-top: 1em;
* {
+ p {
margin-top: $paragraph-vertical-gap;
}
}
}
@include set-paragraph-vertical-gap($text-size);
ul {
// CSS default is 40px, if the text is a bulletpoint, it leads to unexpected padding.
padding-inline-start: 1em;

View File

@@ -6,14 +6,18 @@
v-on:click.stop
v-on:click="toggle()"
>
<font-awesome-icon :icon="['fas', 'info-circle']" />
<AppIcon icon="circle-info" />
</a>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
export default defineComponent({
components: {
AppIcon,
},
emits: [
'show',
'hide',
@@ -52,5 +56,4 @@ export default defineComponent({
color: $color-primary-light;
}
}
</style>

View File

@@ -1,7 +1,8 @@
import { NodeStateChangedEvent } from '../Node/State/StateAccess';
import { TreeNodeStateDescriptor } from '../Node/State/StateDescriptor';
import { ReadOnlyTreeNode } from '../Node/TreeNode';
export interface TreeNodeStateChangedEmittedEvent {
readonly change: NodeStateChangedEvent;
readonly node: ReadOnlyTreeNode;
readonly oldState?: TreeNodeStateDescriptor;
readonly newState: TreeNodeStateDescriptor;
}

View File

@@ -51,7 +51,7 @@ import {
} from 'vue';
import { TreeRoot } from '../TreeRoot/TreeRoot';
import { useCurrentTreeNodes } from '../UseCurrentTreeNodes';
import { NodeRenderingStrategy } from '../Rendering/NodeRenderingStrategy';
import { NodeRenderingStrategy } from '../Rendering/Scheduling/NodeRenderingStrategy';
import { useNodeState } from './UseNodeState';
import { TreeNode } from './TreeNode';
import LeafTreeNode from './LeafTreeNode.vue';

View File

@@ -0,0 +1,3 @@
export interface DelayScheduler {
scheduleNext(callback: () => void, delayInMs: number): void;
}

View File

@@ -0,0 +1,29 @@
import { ReadOnlyTreeNode } from '../../Node/TreeNode';
import { RenderQueueOrderer } from './RenderQueueOrderer';
export class CollapseDepthOrderer implements RenderQueueOrderer {
public orderNodes(nodes: Iterable<ReadOnlyTreeNode>): ReadOnlyTreeNode[] {
return orderNodes(nodes);
}
}
function orderNodes(nodes: Iterable<ReadOnlyTreeNode>): ReadOnlyTreeNode[] {
return [...nodes]
.sort((a, b) => {
const [aCollapseStatus, bCollapseStatus] = [isNodeCollapsed(a), isNodeCollapsed(b)];
if (aCollapseStatus !== bCollapseStatus) {
return (aCollapseStatus ? 1 : 0) - (bCollapseStatus ? 1 : 0);
}
return a.hierarchy.depthInTree - b.hierarchy.depthInTree;
});
}
function isNodeCollapsed(node: ReadOnlyTreeNode): boolean {
if (!node.state.current.isExpanded) {
return true;
}
if (node.hierarchy.parent) {
return isNodeCollapsed(node.hierarchy.parent);
}
return false;
}

View File

@@ -0,0 +1,5 @@
import { ReadOnlyTreeNode } from '../../Node/TreeNode';
export interface RenderQueueOrderer {
orderNodes(nodes: Iterable<ReadOnlyTreeNode>): ReadOnlyTreeNode[];
}

View File

@@ -1,4 +1,4 @@
import { TreeNode } from '../Node/TreeNode';
import { TreeNode } from '../../Node/TreeNode';
export interface NodeRenderingStrategy {
shouldRender(node: TreeNode): boolean;

View File

@@ -0,0 +1,28 @@
import { DelayScheduler } from '../DelayScheduler';
export class TimeoutDelayScheduler implements DelayScheduler {
private timeoutId: ReturnType<typeof setTimeout> | undefined = undefined;
constructor(private readonly timer: TimeFunctions = {
clearTimeout: globalThis.clearTimeout.bind(globalThis),
setTimeout: globalThis.setTimeout.bind(globalThis),
}) { }
public scheduleNext(callback: () => void, delayInMs: number): void {
this.clear();
this.timeoutId = this.timer.setTimeout(callback, delayInMs);
}
private clear(): void {
if (this.timeoutId === undefined) {
return;
}
this.timer.clearTimeout(this.timeoutId);
this.timeoutId = undefined;
}
}
export interface TimeFunctions {
clearTimeout(id: ReturnType<typeof setTimeout>): void;
setTimeout(callback: () => void, delayInMs: number): ReturnType<typeof setTimeout>;
}

View File

@@ -1,44 +1,43 @@
import {
WatchSource, computed, shallowRef, triggerRef, watch,
WatchSource, shallowRef, triggerRef, watch,
} from 'vue';
import { ReadOnlyTreeNode } from '../Node/TreeNode';
import { useNodeStateChangeAggregator } from '../UseNodeStateChangeAggregator';
import { TreeRoot } from '../TreeRoot/TreeRoot';
import { useCurrentTreeNodes } from '../UseCurrentTreeNodes';
import { NodeRenderingStrategy } from './NodeRenderingStrategy';
import { NodeRenderingStrategy } from './Scheduling/NodeRenderingStrategy';
import { DelayScheduler } from './DelayScheduler';
import { TimeoutDelayScheduler } from './Scheduling/TimeoutDelayScheduler';
import { RenderQueueOrderer } from './Ordering/RenderQueueOrderer';
import { CollapseDepthOrderer } from './Ordering/CollapseDepthOrderer';
/**
* Renders tree nodes gradually to prevent UI freeze when loading large amounts of nodes.
*/
export function useGradualNodeRendering(
treeWatcher: WatchSource<TreeRoot>,
useChangeAggregator = useNodeStateChangeAggregator,
useTreeNodes = useCurrentTreeNodes,
scheduler: DelayScheduler = new TimeoutDelayScheduler(),
initialBatchSize = 30,
subsequentBatchSize = 5,
orderer: RenderQueueOrderer = new CollapseDepthOrderer(),
): NodeRenderingStrategy {
const nodesToRender = new Set<ReadOnlyTreeNode>();
const nodesBeingRendered = shallowRef(new Set<ReadOnlyTreeNode>());
let isFirstRender = true;
let isRenderingInProgress = false;
const renderingDelayInMs = 50;
const initialBatchSize = 30;
const subsequentBatchSize = 5;
const { onNodeStateChange } = useNodeStateChangeAggregator(treeWatcher);
const { nodes } = useCurrentTreeNodes(treeWatcher);
const { onNodeStateChange } = useChangeAggregator(treeWatcher);
const { nodes } = useTreeNodes(treeWatcher);
const orderedNodes = computed<readonly ReadOnlyTreeNode[]>(() => nodes.value.flattenedNodes);
watch(() => orderedNodes.value, (newNodes) => {
newNodes.forEach((node) => updateNodeRenderQueue(node));
}, { immediate: true });
function updateNodeRenderQueue(node: ReadOnlyTreeNode) {
if (node.state.current.isVisible
function updateNodeRenderQueue(node: ReadOnlyTreeNode, isVisible: boolean) {
if (isVisible
&& !nodesToRender.has(node)
&& !nodesBeingRendered.value.has(node)) {
nodesToRender.add(node);
if (!isRenderingInProgress) {
scheduleRendering();
}
} else if (!node.state.current.isVisible) {
beginRendering();
} else if (!isVisible) {
if (nodesToRender.has(node)) {
nodesToRender.delete(node);
}
@@ -49,47 +48,57 @@ export function useGradualNodeRendering(
}
}
onNodeStateChange((node, change) => {
if (change.newState.isVisible === change.oldState.isVisible) {
watch(() => nodes.value, (newNodes) => {
nodesToRender.clear();
nodesBeingRendered.value.clear();
if (!newNodes || newNodes.flattenedNodes.length === 0) {
triggerRef(nodesBeingRendered);
return;
}
updateNodeRenderQueue(node);
newNodes
.flattenedNodes
.filter((node) => node.state.current.isVisible)
.forEach((node) => nodesToRender.add(node));
beginRendering();
}, { immediate: true });
onNodeStateChange((change) => {
if (change.newState.isVisible === change.oldState?.isVisible) {
return;
}
updateNodeRenderQueue(change.node, change.newState.isVisible);
});
scheduleRendering();
function scheduleRendering() {
if (isFirstRender) {
renderNodeBatch();
isFirstRender = false;
} else {
const delayScheduler = new DelayScheduler(renderingDelayInMs);
delayScheduler.schedule(renderNodeBatch);
function beginRendering() {
if (isRenderingInProgress) {
return;
}
renderNextBatch(initialBatchSize);
}
function renderNodeBatch() {
function renderNextBatch(batchSize: number) {
if (nodesToRender.size === 0) {
isRenderingInProgress = false;
return;
}
isRenderingInProgress = true;
const batchSize = isFirstRender ? initialBatchSize : subsequentBatchSize;
const sortedNodes = Array.from(nodesToRender).sort(
(a, b) => orderedNodes.value.indexOf(a) - orderedNodes.value.indexOf(b),
);
const currentBatch = sortedNodes.slice(0, batchSize);
const orderedNodes = orderer.orderNodes(nodesToRender);
const currentBatch = orderedNodes.slice(0, batchSize);
if (currentBatch.length === 0) {
return;
}
currentBatch.forEach((node) => {
nodesToRender.delete(node);
nodesBeingRendered.value.add(node);
});
triggerRef(nodesBeingRendered);
if (nodesToRender.size > 0) {
scheduleRendering();
}
scheduler.scheduleNext(
() => renderNextBatch(subsequentBatchSize),
renderingDelayInMs,
);
}
function shouldNodeBeRendered(node: ReadOnlyTreeNode) {
function shouldNodeBeRendered(node: ReadOnlyTreeNode): boolean {
return nodesBeingRendered.value.has(node);
}
@@ -97,21 +106,3 @@ export function useGradualNodeRendering(
shouldRender: shouldNodeBeRendered,
};
}
class DelayScheduler {
private timeoutId: ReturnType<typeof setTimeout> = null;
constructor(private delay: number) {}
schedule(callback: () => void) {
this.clear();
this.timeoutId = setTimeout(callback, this.delay);
}
clear() {
if (this.timeoutId) {
clearTimeout(this.timeoutId);
this.timeoutId = null;
}
}
}

View File

@@ -22,7 +22,7 @@ import {
} from 'vue';
import HierarchicalTreeNode from '../Node/HierarchicalTreeNode.vue';
import { useCurrentTreeNodes } from '../UseCurrentTreeNodes';
import { NodeRenderingStrategy } from '../Rendering/NodeRenderingStrategy';
import { NodeRenderingStrategy } from '../Rendering/Scheduling/NodeRenderingStrategy';
import { TreeRoot } from './TreeRoot';
export default defineComponent({

View File

@@ -68,8 +68,13 @@ export default defineComponent({
const nodeRenderingScheduler = useGradualNodeRendering(() => tree);
const { onNodeStateChange } = useNodeStateChangeAggregator(() => tree);
onNodeStateChange((node, change) => {
emit('nodeStateChanged', { node, change });
onNodeStateChange((change) => {
emit('nodeStateChanged', {
node: change.node,
newState: change.newState,
oldState: change.oldState,
});
});
onMounted(() => {

View File

@@ -6,14 +6,15 @@ import { TreeNodeCheckState } from './Node/State/CheckState';
export function useAutoUpdateChildrenCheckState(
treeWatcher: WatchSource<TreeRoot>,
useChangeAggregator = useNodeStateChangeAggregator,
) {
const { onNodeStateChange } = useNodeStateChangeAggregator(treeWatcher);
const { onNodeStateChange } = useChangeAggregator(treeWatcher);
onNodeStateChange((node, change) => {
if (change.newState.checkState === change.oldState.checkState) {
onNodeStateChange((change) => {
if (change.newState.checkState === change.oldState?.checkState) {
return;
}
updateChildrenCheckedState(node.hierarchy, change.newState.checkState);
updateChildrenCheckedState(change.node.hierarchy, change.newState.checkState);
});
}

View File

@@ -7,14 +7,15 @@ import { ReadOnlyTreeNode } from './Node/TreeNode';
export function useAutoUpdateParentCheckState(
treeWatcher: WatchSource<TreeRoot>,
useChangeAggregator = useNodeStateChangeAggregator,
) {
const { onNodeStateChange } = useNodeStateChangeAggregator(treeWatcher);
const { onNodeStateChange } = useChangeAggregator(treeWatcher);
onNodeStateChange((node, change) => {
if (change.newState.checkState === change.oldState.checkState) {
onNodeStateChange((change) => {
if (change.newState.checkState === change.oldState?.checkState) {
return;
}
updateNodeParentCheckedState(node.hierarchy);
updateNodeParentCheckedState(change.node.hierarchy);
});
}

View File

@@ -8,7 +8,7 @@ import { QueryableNodes } from './TreeRoot/NodeCollection/Query/QueryableNodes';
export function useCurrentTreeNodes(treeWatcher: WatchSource<TreeRoot>) {
const { events } = inject(InjectionKeys.useAutoUnsubscribedEvents)();
const tree = ref<TreeRoot>();
const tree = ref<TreeRoot | undefined>();
const nodes = ref<QueryableNodes | undefined>();
watch(treeWatcher, (newTree) => {

View File

@@ -1,35 +1,83 @@
import { WatchSource, inject, watch } from 'vue';
import {
WatchSource, inject, watch, ref,
} from 'vue';
import { InjectionKeys } from '@/presentation/injectionSymbols';
import { IEventSubscription } from '@/infrastructure/Events/IEventSource';
import { TreeRoot } from './TreeRoot/TreeRoot';
import { TreeNode } from './Node/TreeNode';
import { useCurrentTreeNodes } from './UseCurrentTreeNodes';
import { NodeStateChangedEvent } from './Node/State/StateAccess';
import { TreeNodeStateDescriptor } from './Node/State/StateDescriptor';
type NodeStateChangeEventCallback = (
node: TreeNode,
stateChange: NodeStateChangedEvent,
) => void;
export type NodeStateChangeEventCallback = (args: NodeStateChangeEventArgs) => void;
export function useNodeStateChangeAggregator(treeWatcher: WatchSource<TreeRoot>) {
const { nodes } = useCurrentTreeNodes(treeWatcher);
export function useNodeStateChangeAggregator(
treeWatcher: WatchSource<TreeRoot>,
useTreeNodes = useCurrentTreeNodes,
) {
const { nodes } = useTreeNodes(treeWatcher);
const { events } = inject(InjectionKeys.useAutoUnsubscribedEvents)();
const onNodeChangeCallbacks = new Array<NodeStateChangeEventCallback>();
const onNodeChangeCallback = ref<NodeStateChangeEventCallback>();
watch(() => nodes.value, (newNodes) => {
events.unsubscribeAll();
newNodes.flattenedNodes.forEach((node) => {
events.register([
node.state.changed.on((stateChange) => {
onNodeChangeCallbacks.forEach((callback) => callback(node, stateChange));
}),
]);
});
watch([
() => nodes.value,
() => onNodeChangeCallback.value,
], ([newNodes, callback]) => {
if (!callback) { // might not be registered yet
return;
}
if (!newNodes || newNodes.flattenedNodes.length === 0) {
events.unsubscribeAll();
return;
}
const allNodes = newNodes.flattenedNodes;
events.unsubscribeAllAndRegister(
subscribeToNotifyOnFutureNodeChanges(allNodes, callback),
);
notifyCurrentNodeState(allNodes, callback);
});
function onNodeStateChange(
callback: NodeStateChangeEventCallback,
): void {
if (!callback) {
throw new Error('missing callback');
}
onNodeChangeCallback.value = callback;
}
return {
onNodeStateChange: (
callback: NodeStateChangeEventCallback,
) => onNodeChangeCallbacks.push(callback),
onNodeStateChange,
};
}
export interface NodeStateChangeEventArgs {
readonly node: TreeNode;
readonly newState: TreeNodeStateDescriptor;
readonly oldState?: TreeNodeStateDescriptor;
}
function notifyCurrentNodeState(
nodes: readonly TreeNode[],
callback: NodeStateChangeEventCallback,
) {
nodes.forEach((node) => {
callback({
node,
newState: node.state.current,
});
});
}
function subscribeToNotifyOnFutureNodeChanges(
nodes: readonly TreeNode[],
callback: NodeStateChangeEventCallback,
): IEventSubscription[] {
return nodes.map((node) => node.state.changed.on((stateChange) => {
callback({
node,
oldState: stateChange.oldState,
newState: stateChange.newState,
});
}));
}

View File

@@ -6,12 +6,12 @@ import { TreeNodeStateChangedEmittedEvent } from '../TreeView/Bindings/TreeNodeS
export function useCollectionSelectionStateUpdater() {
const { modifyCurrentState, currentState } = inject(InjectionKeys.useCollectionState)();
const updateNodeSelection = (event: TreeNodeStateChangedEmittedEvent) => {
const { node } = event;
function updateNodeSelection(change: TreeNodeStateChangedEmittedEvent) {
const { node } = change;
if (node.hierarchy.isBranchNode) {
return; // A category, let TreeView handle this
}
if (event.change.oldState.checkState === event.change.newState.checkState) {
if (change.oldState?.checkState === change.newState.checkState) {
return;
}
if (node.state.current.checkState === TreeNodeCheckState.Checked) {
@@ -30,7 +30,7 @@ export function useCollectionSelectionStateUpdater() {
state.selection.removeSelectedScript(node.id);
});
}
};
}
return {
updateNodeSelection,

View File

@@ -0,0 +1,40 @@
<template>
<div v-html="svgContent" class="inline-icon" />
</template>
<script lang="ts">
import {
defineComponent,
PropType,
inject,
} from 'vue';
import { useSvgLoader } from './UseSvgLoader';
import { IconName } from './IconName';
export default defineComponent({
props: {
icon: {
type: String as PropType<IconName>,
required: true,
},
},
setup(props) {
const useSvgLoaderHook = inject('useSvgLoaderHook', useSvgLoader);
const { svgContent } = useSvgLoaderHook(() => props.icon);
return { svgContent };
},
});
</script>
<style lang="scss" scoped>
.inline-icon {
display: inline-block;
::v-deep svg { // using ::v-deep because when v-html is used the content doesn't go through Vue's template compiler.
display: inline-block;
height: 1em;
overflow: visible;
vertical-align: -0.125em;
}
}
</style>

View File

@@ -0,0 +1,22 @@
export const IconNames = [
'magnifying-glass',
'copy',
'circle-info',
'user-secret',
'tag',
'github',
'face-smile',
'globe',
'desktop',
'xmark',
'battery-half',
'battery-full',
'folder',
'folder-open',
'left-right',
'file-arrow-down',
'floppy-disk',
'play',
] as const;
export type IconName = typeof IconNames[number];

View File

@@ -0,0 +1,92 @@
import {
WatchSource, readonly, ref, watch,
} from 'vue';
import { AsyncLazy } from '@/infrastructure/Threading/AsyncLazy';
import { IconName } from './IconName';
export function useSvgLoader(
iconWatcher: WatchSource<IconName>,
loaders: FileLoaders = RawSvgLoaders,
) {
const svgContent = ref<string>('');
watch(iconWatcher, async (iconName) => {
svgContent.value = await lazyLoadSvg(iconName, loaders);
}, { immediate: true });
return {
svgContent: readonly(svgContent),
};
}
export function clearIconCache() {
LazyIconCache.clear();
}
export type FileLoaders = Record<string, () => Promise<string>>;
const LazyIconCache = new Map<IconName, AsyncLazy<string>>();
async function lazyLoadSvg(name: IconName, loaders: FileLoaders): Promise<string> {
let iconLoader = LazyIconCache.get(name);
if (!iconLoader) {
iconLoader = new AsyncLazy<string>(() => loadSvg(name, loaders));
LazyIconCache.set(name, iconLoader);
}
const icon = await iconLoader.getValue();
return icon;
}
async function loadSvg(name: IconName, loaders: FileLoaders): Promise<string> {
const iconPath = `/assets/icons/${name}.svg`;
const loader = loaders[iconPath];
if (!loader) {
throw new Error(`missing icon for "${name}" in "${iconPath}"`);
}
const svgContent = await loader();
const modifiedContent = modifySvg(svgContent);
return modifiedContent;
}
const RawSvgLoaders = import.meta.glob('@/presentation/assets/icons/**/*.svg', {
as: 'raw', // This will load the SVG file content as a string.
/*
Using `eager: true` to preload all icons.
Pros:
- Speed: Icons are instantly accessible post-initial load.
Cons:
- Increased initial load time due to preloading of all icons.
- Increased bundle size.
*/
eager: false,
});
function modifySvg(svgSource: string): string {
const parser = new DOMParser();
const doc = parser.parseFromString(svgSource, 'image/svg+xml');
let svgRoot = doc.documentElement;
svgRoot = removeSvgComments(svgRoot);
svgRoot = fillSvgCurrentColor(svgRoot);
return new XMLSerializer()
.serializeToString(svgRoot);
}
function removeSvgComments(svgRoot: HTMLElement): HTMLElement {
const comments = Array.from(svgRoot.childNodes).filter(
(node) => node.nodeType === Node.COMMENT_NODE,
);
for (const comment of comments) {
svgRoot.removeChild(comment);
}
Array.from(svgRoot.children).forEach((child) => {
removeSvgComments(child as HTMLElement);
});
return svgRoot;
}
function fillSvgCurrentColor(svgRoot: HTMLElement): HTMLElement {
svgRoot.querySelectorAll('path').forEach((el: Element) => {
el.setAttribute('fill', 'currentColor');
});
return svgRoot;
}

View File

@@ -10,9 +10,7 @@
class="dialog__close-button"
@click="hide"
>
<font-awesome-icon
:icon="['fas', 'times']"
/>
<AppIcon icon="xmark" />
</div>
</div>
</ModalContainer>
@@ -20,11 +18,13 @@
<script lang="ts">
import { defineComponent, computed } from 'vue';
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
import ModalContainer from './ModalContainer.vue';
export default defineComponent({
components: {
ModalContainer,
AppIcon,
},
emits: {
/* eslint-disable @typescript-eslint/no-unused-vars */

View File

@@ -1,65 +1,166 @@
<!--
This component acts as a wrapper for the v-tooltip to solve the following:
- Direct inclusion of inline HTML in tooltip components has challenges such as
- absence of linting or editor support,
- involves cumbersome string concatenation.
This component caters to these issues by permitting HTML usage in a slot.
- It provides an abstraction for a third-party component which simplifies
switching and acts as an anti-corruption layer.
-->
<template>
<div class="tooltip-container" v-tooltip.top-center="tooltipHtml">
<slot />
<div class="tooltip-content" ref="tooltipWrapper">
<slot name="tooltip" />
<div class="tooltip">
<div
class="tooltip__trigger"
ref="triggeringElement">
<slot />
</div>
<div
class="tooltip__display"
ref="tooltipDisplayElement"
:style="displayStyles"
>
<div class="tooltip__content">
<slot name="tooltip" />
</div>
<div
ref="arrowElement"
class="tooltip__arrow"
:style="arrowStyles"
/>
</div>
</div>
</template>
<script lang="ts">
import {
defineComponent, ref, onMounted, onUpdated, nextTick,
} from 'vue';
useFloating, arrow, shift, flip, Placement, offset, Side, Coords,
} from '@floating-ui/vue';
import { defineComponent, ref, computed } from 'vue';
import type { CSSProperties } from 'vue/types/jsx'; // In Vue 3.0 import from 'vue'
const GAP_BETWEEN_TOOLTIP_AND_TRIGGER_IN_PX = 2;
const ARROW_SIZE_IN_PX = 4;
const MARGIN_FROM_DOCUMENT_EDGE_IN_PX = 2;
export default defineComponent({
setup() {
const tooltipWrapper = ref<HTMLElement | undefined>();
const tooltipHtml = ref<string | undefined>();
const tooltipDisplayElement = ref<HTMLElement | undefined>();
const triggeringElement = ref<HTMLElement | undefined>();
const arrowElement = ref<HTMLElement | undefined>();
const placement = ref<Placement>('top');
onMounted(() => updateTooltipHTML());
onUpdated(() => {
nextTick(() => {
updateTooltipHTML();
});
const { floatingStyles, middlewareData } = useFloating(
triggeringElement,
tooltipDisplayElement,
{
placement: ref(placement),
middleware: [
offset(ARROW_SIZE_IN_PX + GAP_BETWEEN_TOOLTIP_AND_TRIGGER_IN_PX),
/* Shifts the element along the specified axes in order to keep it in view. */
shift({
padding: MARGIN_FROM_DOCUMENT_EDGE_IN_PX,
}),
/* Changes the placement of the floating element in order to keep it in view,
with the ability to flip to any placement. */
flip(),
arrow({ element: arrowElement }),
],
},
);
const arrowStyles = computed<CSSProperties>(() => {
if (!middlewareData.value.arrow) {
return {
display: 'none',
};
}
return {
...getArrowPositionStyles(middlewareData.value.arrow, placement.value),
...getArrowAppearanceStyles(),
};
});
function updateTooltipHTML() {
const newValue = tooltipWrapper.value?.innerHTML;
const oldValue = tooltipHtml.value;
if (newValue === oldValue) {
return;
}
tooltipHtml.value = newValue;
}
return {
tooltipWrapper,
tooltipHtml,
tooltipDisplayElement,
triggeringElement,
displayStyles: floatingStyles,
arrowStyles,
arrowElement,
placement,
};
},
});
function getArrowAppearanceStyles(): CSSProperties {
return {
width: `${ARROW_SIZE_IN_PX * 2}px`,
height: `${ARROW_SIZE_IN_PX * 2}px`,
rotate: '45deg',
};
}
function getArrowPositionStyles(
coordinations: Partial<Coords>,
placement: Placement,
): CSSProperties {
const style: CSSProperties = {};
style.position = 'absolute';
const { x, y } = coordinations;
if (x) {
style.left = `${x}px`;
} else if (y) { // either X or Y is calculated
style.top = `${y}px`;
}
const oppositeSide = getCounterpartBoxOffsetProperty(placement) as never;
// Cast to `never` due to ts(2590) from JSX import. Remove after migrating to Vue 3.0.
style[oppositeSide] = `-${ARROW_SIZE_IN_PX}px`;
return style;
}
function getCounterpartBoxOffsetProperty(placement: Placement): keyof CSSProperties {
const sideCounterparts: Record<Side, keyof CSSProperties> = {
top: 'bottom',
right: 'left',
bottom: 'top',
left: 'right',
};
const currentSide = placement.split('-')[0] as Side;
return sideCounterparts[currentSide];
}
</script>
<style scoped lang="scss">
@use "@/presentation/assets/styles/main" as *;
.tooltip-container {
display: inline-block;
$color-tooltip-background: $color-primary-darkest;
@mixin set-visibility($isVisible: true) {
@if $isVisible {
visibility: visible;
opacity: 1;
transition: opacity .15s;
} @else {
visibility: hidden;
opacity: 0;
transition: opacity .15s, visibility .15s;
}
}
.tooltip-content {
display: none;
.tooltip {
display: inline-flex;
}
.tooltip__display {
@include set-visibility(false);
}
.tooltip__trigger {
@include hover-or-touch {
+ .tooltip__display {
@include set-visibility(true);
z-index: 10000;
}
}
}
.tooltip__content {
background: $color-tooltip-background;
color: $color-on-primary;
border-radius: 16px;
padding: 5px 10px 4px;
}
.tooltip__arrow {
background: $color-tooltip-background;
}
</style>

View File

@@ -6,7 +6,7 @@
'container-supported': hasCurrentOsDesktopVersion,
}">
<span class="description">
<font-awesome-icon class="description__icon" :icon="['fas', 'desktop']" />
<AppIcon class="description__icon" icon="desktop" />
<span class="description__text">For desktop:</span>
</span>
<span class="urls">
@@ -21,6 +21,7 @@
import { defineComponent, inject } from 'vue';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { InjectionKeys } from '@/presentation/injectionSymbols';
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
import DownloadUrlListItem from './DownloadUrlListItem.vue';
const supportedOperativeSystems: readonly OperatingSystem[] = [
@@ -32,6 +33,7 @@ const supportedOperativeSystems: readonly OperatingSystem[] = [
export default defineComponent({
components: {
DownloadUrlListItem,
AppIcon,
},
setup() {
const { os: currentOs } = inject(InjectionKeys.useRuntimeEnvironment);

View File

@@ -3,7 +3,7 @@
<div class="footer">
<div class="footer__section">
<span v-if="isDesktop" class="footer__section__item">
<font-awesome-icon class="icon" :icon="['fas', 'globe']" />
<AppIcon class="icon" icon="globe" />
<span>
Online version at <a :href="homepageUrl" target="_blank" rel="noopener noreferrer">{{ homepageUrl }}</a>
</span>
@@ -15,24 +15,24 @@
<div class="footer__section">
<div class="footer__section__item">
<a :href="feedbackUrl" target="_blank" rel="noopener noreferrer">
<font-awesome-icon class="icon" :icon="['far', 'smile']" />
<AppIcon class="icon" icon="face-smile" />
<span>Feedback</span>
</a>
</div>
<div class="footer__section__item">
<a :href="repositoryUrl" target="_blank" rel="noopener noreferrer">
<font-awesome-icon class="icon" :icon="['fab', 'github']" />
<AppIcon class="icon" icon="github" />
<span>Source Code</span>
</a>
</div>
<div class="footer__section__item">
<a :href="releaseUrl" target="_blank" rel="noopener noreferrer">
<font-awesome-icon class="icon" :icon="['fas', 'tag']" />
<AppIcon class="icon" icon="tag" />
<span>v{{ version }}</span>
</a>
</div>
<div class="footer__section__item">
<font-awesome-icon class="icon" :icon="['fas', 'user-secret']" />
<AppIcon class="icon" icon="user-secret" />
<a @click="showPrivacyDialog()">Privacy</a>
</div>
</div>
@@ -48,6 +48,7 @@ import {
defineComponent, ref, computed, inject,
} from 'vue';
import ModalDialog from '@/presentation/components/Shared/Modal/ModalDialog.vue';
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
import { InjectionKeys } from '@/presentation/injectionSymbols';
import DownloadUrlList from './DownloadUrlList.vue';
import PrivacyPolicy from './PrivacyPolicy.vue';
@@ -57,6 +58,7 @@ export default defineComponent({
ModalDialog,
PrivacyPolicy,
DownloadUrlList,
AppIcon,
},
setup() {
const { info } = inject(InjectionKeys.useApplication);

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