Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b042b36aea | ||
|
|
be7a886225 |
32
.github/actions/force-ipv4/README.md
vendored
32
.github/actions/force-ipv4/README.md
vendored
@@ -1,32 +0,0 @@
|
|||||||
# force-ipv4
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This GitHub action enforces IPv4 for all outgoing network requests. It addresses connectivity issues encountered in GitHub runners, where IPv6 requests may lead to timeouts due to the lack of IPv6 support [1] [2].
|
|
||||||
|
|
||||||
## Background
|
|
||||||
|
|
||||||
Some applications attempt network connections over IPv6.
|
|
||||||
Such as requests made by Node's `fetch` API causes `UND_ERR_CONNECT_TIMEOUT` [3] [4] and similar issues [5].
|
|
||||||
This happens when the software cannot handle this such as by using Happy Eyeballs [6] [7].
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
To use this action in your GitHub workflow, add the following step before any job that requires network access:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
- name: Enforce IPv4 Connectivity
|
|
||||||
uses: ./.github/actions/force-ipv4
|
|
||||||
```
|
|
||||||
|
|
||||||
## Note
|
|
||||||
|
|
||||||
This action is a workaround addressing specific IPv6-related connectivity issues on GitHub runners and may not be necessary if GitHub's infrastructure evolves to fully support IPv6 in the future.
|
|
||||||
|
|
||||||
[1]: https://archive.ph/2024.03.28-185829/https://github.com/actions/runner/issues/3138 "Actions Runner fails on IPv6 only host · Issue #3138 · actions/runner · GitHub | github.com"
|
|
||||||
[2]: https://archive.ph/2024.03.28-185838/https://github.com/actions/runner-images/issues/668 "IPv6 on GitHub-hosted runners · Issue #668 · actions/runner-images · GitHub | github.com"
|
|
||||||
[3]: https://archive.ph/2024.03.28-185847/https://github.com/actions/runner/issues/3213 "GitHub runner cannot send `fetch` with `node`, failing with IPv6 DNS error `UND_ERR_CONNECT_TIMEOUT` · Issue #3213 · actions/runner · GitHub | github.com"
|
|
||||||
[4]: https://archive.ph/2024.03.28-185853/https://github.com/actions/runner-images/issues/9540 "Cannot send outbound requests using node fetch, failing with IPv6 DNS error UND_ERR_CONNECT_TIMEOUT · Issue #9540 · actions/runner-images · GitHub | github.com"
|
|
||||||
[5]: https://archive.today/2024.03.30-113315/https://github.com/nodejs/node/issues/40537 "\"localhost\" favours IPv6 in node v17, used to favour IPv4 · Issue #40537 · nodejs/node · GitHub"
|
|
||||||
[6]: https://archive.ph/2024.03.28-185900/https://github.com/nodejs/node/issues/41625 "Happy Eyeballs support (address IPv6 issues in Node 17) · Issue #41625 · nodejs/node · GitHub | github.com"
|
|
||||||
[7]: https://archive.ph/2024.03.28-185910/https://github.com/nodejs/undici/issues/1531 "fetch times out in under 5 seconds · Issue #1531 · nodejs/undici · GitHub | github.com"
|
|
||||||
12
.github/actions/force-ipv4/action.yml
vendored
12
.github/actions/force-ipv4/action.yml
vendored
@@ -1,12 +0,0 @@
|
|||||||
inputs:
|
|
||||||
project-root:
|
|
||||||
required: false
|
|
||||||
default: '.'
|
|
||||||
runs:
|
|
||||||
using: composite
|
|
||||||
steps:
|
|
||||||
-
|
|
||||||
name: Run prefer IPv4 script
|
|
||||||
shell: bash
|
|
||||||
run: ./.github/actions/force-ipv4/force-ipv4.sh
|
|
||||||
working-directory: ${{ inputs.project-root }}
|
|
||||||
80
.github/actions/force-ipv4/force-ipv4.sh
vendored
80
.github/actions/force-ipv4/force-ipv4.sh
vendored
@@ -1,80 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
main() {
|
|
||||||
if is_linux; then
|
|
||||||
echo 'Configuring Linux...'
|
|
||||||
|
|
||||||
configure_warp_with_doh_and_ipv6_exclusion_on_linux # [WORKS] Resolves the issue when run independently on GitHub runners lacking IPv6 support.
|
|
||||||
prefer_ipv4_on_linux # [DOES NOT WORK] It does not resolve the issue when run independently on GitHub runners without IPv6 support.
|
|
||||||
|
|
||||||
# Considered alternatives:
|
|
||||||
# - `sysctl` commands, and direct changes to `/proc/sys/net/` and `/etc/sysctl.conf` led to silent
|
|
||||||
# Node 18 exits (code: 13) when using `fetch`.
|
|
||||||
elif is_macos; then
|
|
||||||
echo 'Configuring macOS...'
|
|
||||||
|
|
||||||
configure_warp_with_doh_and_ipv6_exclusion_on_macos # [WORKS] Resolves the issue when run independently on GitHub runners lacking IPv6 support.
|
|
||||||
disable_ipv6_on_macos # [WORKS INCONSISTENTLY] Resolves the issue inconsistently when run independently on GitHub runners without IPv6 support.
|
|
||||||
fi
|
|
||||||
echo "IPv4: $(curl --ipv4 --silent --max-time 15 --retry 3 --user-agent Mozilla https://api.ip.sb/geoip)"
|
|
||||||
echo "IPv6: $(curl --ipv6 --silent --max-time 15 --retry 3 --user-agent Mozilla https://api.ip.sb/geoip)"
|
|
||||||
}
|
|
||||||
|
|
||||||
is_linux() {
|
|
||||||
[[ "$(uname -s)" == "Linux" ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
is_macos() {
|
|
||||||
[[ "$(uname -s)" == "Darwin" ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
configure_warp_with_doh_and_ipv6_exclusion_on_linux() {
|
|
||||||
install_warp_on_debian
|
|
||||||
configure_warp_doh_and_exclude_ipv6
|
|
||||||
}
|
|
||||||
|
|
||||||
configure_warp_with_doh_and_ipv6_exclusion_on_macos() {
|
|
||||||
brew install cloudflare-warp
|
|
||||||
configure_warp_doh_and_exclude_ipv6
|
|
||||||
}
|
|
||||||
|
|
||||||
configure_warp_doh_and_exclude_ipv6() {
|
|
||||||
echo 'Beginning configuration of the Cloudflare WARP client with DNS-over-HTTPS and IPv6 exclusion...'
|
|
||||||
echo 'Initiating client registration with Cloudflare...'
|
|
||||||
warp-cli --accept-tos registration new
|
|
||||||
echo 'Configuring WARP to operate in DNS-over-HTTPS mode (warp+doh)...'
|
|
||||||
warp-cli --accept-tos mode warp+doh
|
|
||||||
echo 'Excluding IPv6 traffic from WARP by configuring it as a split tunnel...'
|
|
||||||
warp-cli --accept-tos add-excluded-route '::/0' # Exclude IPv6, forcing IPv4 resolution
|
|
||||||
# `tunnel ip add` does not work with IP ranges, see https://community.cloudflare.com/t/cant-cidr-for-split-tunnling/630834
|
|
||||||
echo 'Establishing WARP connection...'
|
|
||||||
warp-cli --accept-tos connect
|
|
||||||
}
|
|
||||||
|
|
||||||
install_warp_on_debian() {
|
|
||||||
curl -fsSL https://pkg.cloudflareclient.com/pubkey.gpg | sudo gpg --yes --dearmor --output /usr/share/keyrings/cloudflare-warp-archive-keyring.gpg
|
|
||||||
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg] https://pkg.cloudflareclient.com/ $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/cloudflare-client.list
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y cloudflare-warp
|
|
||||||
}
|
|
||||||
|
|
||||||
disable_ipv6_on_macos() {
|
|
||||||
networksetup -listallnetworkservices \
|
|
||||||
| tail -n +2 \
|
|
||||||
| while IFS= read -r interface; do
|
|
||||||
echo "Disabling IPv6 on: $interface..."
|
|
||||||
networksetup -setv6off "$interface"
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
prefer_ipv4_on_linux() {
|
|
||||||
local -r gai_config_file_path='/etc/gai.conf'
|
|
||||||
if [ ! -f "$gai_config_file_path" ]; then
|
|
||||||
echo "Creating $gai_config_file_path since it doesn't exist..."
|
|
||||||
touch "$gai_config_file_path"
|
|
||||||
fi
|
|
||||||
echo "precedence ::ffff:0:0/96 100" | sudo tee -a "$gai_config_file_path" > /dev/null
|
|
||||||
echo "Configuration complete."
|
|
||||||
}
|
|
||||||
|
|
||||||
main
|
|
||||||
3
.github/actions/setup-node/action.yml
vendored
3
.github/actions/setup-node/action.yml
vendored
@@ -5,5 +5,4 @@ runs:
|
|||||||
name: Setup node
|
name: Setup node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 20.x
|
node-version: 18.x
|
||||||
check-latest: true
|
|
||||||
|
|||||||
8
.github/workflows/checks.build.yaml
vendored
8
.github/workflows/checks.build.yaml
vendored
@@ -95,12 +95,6 @@ jobs:
|
|||||||
-
|
-
|
||||||
name: Run Docker image on port 8080
|
name: Run Docker image on port 8080
|
||||||
run: docker run -d -p 8080:80 --rm --name privacy.sexy undergroundwires/privacy.sexy:latest
|
run: docker run -d -p 8080:80 --rm --name privacy.sexy undergroundwires/privacy.sexy:latest
|
||||||
-
|
|
||||||
name: Enforce IPv4 Connectivity # Used due to GitHub runners' lack of IPv6 support, preventing request timeouts.
|
|
||||||
uses: ./.github/actions/force-ipv4
|
|
||||||
-
|
-
|
||||||
name: Check server is up and returns HTTP 200
|
name: Check server is up and returns HTTP 200
|
||||||
run: >-
|
run: node ./scripts/verify-web-server-status.js --url http://localhost:8080
|
||||||
node ./scripts/verify-web-server-status.js \
|
|
||||||
--url http://localhost:8080 \
|
|
||||||
--max-retries ${{ matrix.os == 'macos' && '90' || '30' }}
|
|
||||||
|
|||||||
11
.github/workflows/checks.external-urls.yaml
vendored
11
.github/workflows/checks.external-urls.yaml
vendored
@@ -1,9 +1,11 @@
|
|||||||
name: checks.external-urls
|
name: checks.external-urls
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 0 * * 0' # at 00:00 on every Sunday
|
- cron: '0 0 * * 0' # at 00:00 on every Sunday
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- tests/checks/external-urls/**
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
run-check:
|
run-check:
|
||||||
@@ -18,13 +20,6 @@ jobs:
|
|||||||
-
|
-
|
||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
uses: ./.github/actions/npm-install-dependencies
|
uses: ./.github/actions/npm-install-dependencies
|
||||||
-
|
|
||||||
name: Enforce IPv4 Connectivity # Used due to GitHub runners' lack of IPv6 support, preventing request timeouts.
|
|
||||||
uses: ./.github/actions/force-ipv4
|
|
||||||
-
|
-
|
||||||
name: Test
|
name: Test
|
||||||
run: npm run check:external-urls
|
run: npm run check:external-urls
|
||||||
env:
|
|
||||||
RANDOMIZED_URL_CHECK_LIMIT: "${{ github.event_name == 'push' && '100' || '3000' }}"
|
|
||||||
# - Scheduled checks has high limit for thorough testing.
|
|
||||||
# - For push events, triggered by code changes, the amount of URLs are limited to provide quick feedback.
|
|
||||||
|
|||||||
18
CHANGELOG.md
18
CHANGELOG.md
@@ -1,23 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 0.13.1 (2024-03-22)
|
|
||||||
|
|
||||||
* ci/cd: Fix cross-platform git command compability | [255c51c](https://github.com/undergroundwires/privacy.sexy/commit/255c51c8a0524d3ea8a3b16ffc1b178650525010)
|
|
||||||
* Fix tooltip falling behind elements on fade out | [1964524](https://github.com/undergroundwires/privacy.sexy/commit/19645248ab7bc78dc872fa176c1a3650d7d6d644)
|
|
||||||
* Improve VSCode detection in `configure_vscode.py` | [98845e6](https://github.com/undergroundwires/privacy.sexy/commit/98845e6caee168db131aaf0736533e450827a52c)
|
|
||||||
* Bump TypeScript to 5.3 with `verbatimModuleSyntax` | [a721e82](https://github.com/undergroundwires/privacy.sexy/commit/a721e82a4fb603c0732ccfdffc87396c2a01363e)
|
|
||||||
* Migrate to Vite 5 and adjust configurations | [4ac1425](https://github.com/undergroundwires/privacy.sexy/commit/4ac1425f76079352268c488f3ff607d1fdc1beb2)
|
|
||||||
* win: improve and unify service start/stop logic | [adc2089](https://github.com/undergroundwires/privacy.sexy/commit/adc20898873d50a8873ffc74c48257e69a45d367)
|
|
||||||
* Upgrade vitest to v1 and fix test definitions | [e721885](https://github.com/undergroundwires/privacy.sexy/commit/e7218850ba62a7bebaf4768b13e46cba0dedd906)
|
|
||||||
* Improve URL checks to reduce false-negatives | [5abf8ff](https://github.com/undergroundwires/privacy.sexy/commit/5abf8ff216a1da737fd489864eeee880f78d6601)
|
|
||||||
* win: improve OneDrive data deletion safety | [5eff3a0](https://github.com/undergroundwires/privacy.sexy/commit/5eff3a04886d0d23a6e4c13a0178bb247105c5cb)
|
|
||||||
* Bump Electron to latest and use native ESM | [840adf9](https://github.com/undergroundwires/privacy.sexy/commit/840adf9429ed47f9e88c05e90f1d3ab930c2dfc4)
|
|
||||||
* Fix tooltip styling inconsistency | [ec34ac1](https://github.com/undergroundwires/privacy.sexy/commit/ec34ac1124e8b8ae53bf31a4dbdc88bb078b3d4e)
|
|
||||||
* win: fix VSCode manual update switch script #312 | [b71ad79](https://github.com/undergroundwires/privacy.sexy/commit/b71ad797a3af0db45143249903cb5e178692de7c)
|
|
||||||
* mac, linux, win: fix dead URLs and improve docs | [abec9de](https://github.com/undergroundwires/privacy.sexy/commit/abec9def075d82fdaee9663ef8fe1a488911f45b)
|
|
||||||
|
|
||||||
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.13.0...0.13.1)
|
|
||||||
|
|
||||||
## 0.13.0 (2024-02-11)
|
## 0.13.0 (2024-02-11)
|
||||||
|
|
||||||
* win: add disabling clipboard features #251, #247 | [c6ebba8](https://github.com/undergroundwires/privacy.sexy/commit/c6ebba85fb1b362be0d81d3078f19db71e0528b2)
|
* win: add disabling clipboard features #251, #247 | [c6ebba8](https://github.com/undergroundwires/privacy.sexy/commit/c6ebba85fb1b362be0d81d3078f19db71e0528b2)
|
||||||
|
|||||||
@@ -122,7 +122,7 @@
|
|||||||
## Get started
|
## Get started
|
||||||
|
|
||||||
- 🌍️ **Online**: [https://privacy.sexy](https://privacy.sexy).
|
- 🌍️ **Online**: [https://privacy.sexy](https://privacy.sexy).
|
||||||
- 🖥️ **Offline**: Download directly for: [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.13.1/privacy.sexy-Setup-0.13.1.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.13.1/privacy.sexy-0.13.1.dmg), [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.13.1/privacy.sexy-0.13.1.AppImage). For more options, see [here](#additional-install-options).
|
- 🖥️ **Offline**: Download directly for: [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.13.0/privacy.sexy-Setup-0.13.0.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.13.0/privacy.sexy-0.13.0.dmg), [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.13.0/privacy.sexy-0.13.0.AppImage). For more options, see [here](#additional-install-options).
|
||||||
|
|
||||||
For a detailed comparison of features between the desktop and web versions of privacy.sexy, see [Desktop vs. Web Features](./docs/desktop-vs-web-features.md).
|
For a detailed comparison of features between the desktop and web versions of privacy.sexy, see [Desktop vs. Web Features](./docs/desktop-vs-web-features.md).
|
||||||
|
|
||||||
|
|||||||
@@ -14,13 +14,14 @@ The presentation layer uses an event-driven architecture for bidirectional react
|
|||||||
- [**`main.ts`**](./../src/presentation/main.ts): Starts Vue app.
|
- [**`main.ts`**](./../src/presentation/main.ts): Starts Vue app.
|
||||||
- [**`index.html`**](./../src/presentation/index.html): The `index.html` entry file, located at the root of the project as required by Vite
|
- [**`index.html`**](./../src/presentation/index.html): The `index.html` entry file, located at the root of the project as required by Vite
|
||||||
- [**`bootstrapping/`**](./../src/presentation/bootstrapping/): Registers Vue components and plugins.
|
- [**`bootstrapping/`**](./../src/presentation/bootstrapping/): Registers Vue components and plugins.
|
||||||
- [**`components/`**](./../src/presentation/components/): Contains Vue components, helpers and styles coupled to Vue components.
|
- [**`components/`**](./../src/presentation/components/): Contains Vue components and helpers.
|
||||||
- [**`Shared/`**](./../src/presentation/components/Shared): Contains shared Vue components and helpers.
|
- [**`Shared/`**](./../src/presentation/components/Shared): Contains shared Vue components and helpers.
|
||||||
- [**`Hooks`**](../src/presentation/components/Shared/Hooks): Hooks used by components through [dependency injection](#dependency-injections).
|
- [**`Hooks`**](../src/presentation/components/Shared/Hooks): Hooks used by components through [dependency injection](#dependency-injections).
|
||||||
- [**`/public/`**](../src/presentation/public/): Contains static assets.
|
- [**`/public/`**](../src/presentation/public/): Contains static assets.
|
||||||
- [**`assets/`**](./../src/presentation/assets/styles/): Contains assets processed by Vite.
|
- [**`assets/`**](./../src/presentation/assets/styles/): Contains assets processed by Vite.
|
||||||
- [**`fonts/`**](./../src/presentation/assets/fonts/): Contains fonts.
|
- [**`fonts/`**](./../src/presentation/assets/fonts/): Contains fonts.
|
||||||
- [**`styles/`**](./../src/presentation/assets/styles/): Contains shared styles.
|
- [**`styles/`**](./../src/presentation/assets/styles/): Contains shared styles.
|
||||||
|
- [**`components/`**](./../src/presentation/assets/styles/components): Contains styles coupled to Vue components.
|
||||||
- [**`main.scss`**](./../src/presentation/assets/styles/main.scss): Main Sass file, imported by other components as single entrypoint..
|
- [**`main.scss`**](./../src/presentation/assets/styles/main.scss): Main Sass file, imported by other components as single entrypoint..
|
||||||
- [**`electron/`**](./../src/presentation/electron/): Contains Electron code.
|
- [**`electron/`**](./../src/presentation/electron/): Contains Electron code.
|
||||||
- [`/main/` **`index.ts`**](./../src/presentation/main.ts): Main entry for Electron, managing application windows and lifecycle events.
|
- [`/main/` **`index.ts`**](./../src/presentation/main.ts): Main entry for Electron, managing application windows and lifecycle events.
|
||||||
@@ -37,13 +38,6 @@ The presentation layer uses an event-driven architecture for bidirectional react
|
|||||||
They should also have different visual state when hovering/touching on them that indicates that they are being clicked, which helps with accessibility.
|
They should also have different visual state when hovering/touching on them that indicates that they are being clicked, which helps with accessibility.
|
||||||
- **Borders**:
|
- **Borders**:
|
||||||
privacy.sexy prefers sharper edges in its design language.
|
privacy.sexy prefers sharper edges in its design language.
|
||||||
- **Fonts**:
|
|
||||||
- Use the primary font for regular text and monospace font for code or specific data.
|
|
||||||
- Use cursive and logo fonts solely for branding.
|
|
||||||
- Refer to [standardized font size variables](../src/presentation/assets/styles/_typography.scss) for font sizing, avoiding arbitrary `px`, `em`, `rem`, or percentage values.
|
|
||||||
- **Spacing**:
|
|
||||||
Use [global spacing variables](../src/presentation/assets/styles/_spacing.scss) for consistent margin, padding, and gap definitions.
|
|
||||||
This provides uniform spatial distribution and alignment of elements, enhancing visual harmony and making the UI more scalable and maintainable.
|
|
||||||
|
|
||||||
## Application data
|
## Application data
|
||||||
|
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ Key attributes of a good script:
|
|||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
- Use credible and reputable sources for references.
|
- Use credible and reputable sources for references.
|
||||||
- Use archived links by using [archive.org](https://archive.org) or [archive.ph](https://archive.ph).
|
- Use archived links by using [archive.org](https://archive.org) or [archive.today](https://archive.today).
|
||||||
- Format archive.today links fully, for example: `https://archive.ph/YYYYMMDDhhmmss/https://privacy.sexy`.
|
- Format archive.today links fully, for example: `https://archive.today/YYYYMMDDhhmmss/https://privacy.sexy`.
|
||||||
- Explain the default behavior if the script is not executed.
|
- Explain the default behavior if the script is not executed.
|
||||||
|
|
||||||
## Shared functions
|
## Shared functions
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
/* eslint-disable no-template-curly-in-string */
|
/* eslint-disable no-template-curly-in-string */
|
||||||
|
|
||||||
const { join } = require('node:path');
|
const { join } = require('node:path');
|
||||||
const { readdirSync } = require('fs');
|
|
||||||
const { electronBundled, electronUnbundled } = require('./dist-dirs.json');
|
const { electronBundled, electronUnbundled } = require('./dist-dirs.json');
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {import('electron-builder').Configuration}
|
|
||||||
* @see https://www.electron.build/configuration/configuration
|
|
||||||
*/
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
// Common options
|
// Common options
|
||||||
publish: {
|
publish: {
|
||||||
@@ -19,9 +14,7 @@ module.exports = {
|
|||||||
output: electronBundled,
|
output: electronBundled,
|
||||||
},
|
},
|
||||||
extraMetadata: {
|
extraMetadata: {
|
||||||
main: findMainEntryFile(
|
main: join(electronUnbundled, 'main/index.cjs'), // do not `path.resolve`, it expects a relative path
|
||||||
join(electronUnbundled, 'main'), // do not `path.resolve`, it expects a relative path
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Windows
|
// Windows
|
||||||
@@ -48,15 +41,3 @@ module.exports = {
|
|||||||
artifactName: '${name}-${version}.${ext}',
|
artifactName: '${name}-${version}.${ext}',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds by accommodating different JS file extensions and module formats.
|
|
||||||
*/
|
|
||||||
function findMainEntryFile(parentDirectory) {
|
|
||||||
const files = readdirSync(parentDirectory);
|
|
||||||
const entryFile = files.find((file) => /^index\.(cjs|mjs|js)$/.test(file));
|
|
||||||
if (!entryFile) {
|
|
||||||
throw new Error(`Main entry file not found in ${parentDirectory}.`);
|
|
||||||
}
|
|
||||||
return join(parentDirectory, entryFile);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const ELECTRON_DIST_SUBDIRECTORIES = {
|
|||||||
renderer: resolveElectronDistSubdirectory('renderer'),
|
renderer: resolveElectronDistSubdirectory('renderer'),
|
||||||
};
|
};
|
||||||
|
|
||||||
process.env.ELECTRON_ENTRY = resolve(ELECTRON_DIST_SUBDIRECTORIES.main, 'index.mjs');
|
process.env.ELECTRON_ENTRY = resolve(ELECTRON_DIST_SUBDIRECTORIES.main, 'index.cjs');
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
main: getSharedElectronConfig({
|
main: getSharedElectronConfig({
|
||||||
@@ -54,23 +54,13 @@ function getSharedElectronConfig(options: {
|
|||||||
},
|
},
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
output: {
|
output: {
|
||||||
format: 'es',
|
// Mark: electron-esm-support
|
||||||
|
// This is needed so `type="module"` works
|
||||||
// Ensure all generated files use '.mjs' for module consistency.
|
entryFileNames: '[name].cjs',
|
||||||
// Otherwise, preloader process get `.mjs` extension but main process get `.js` extension, see https://github.com/alex8088/electron-vite/issues/397.
|
|
||||||
entryFileNames: '[name].mjs',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [externalizeDepsPlugin({
|
plugins: [externalizeDepsPlugin()],
|
||||||
exclude: [
|
|
||||||
// Keep 'electron-log' in bundling process.
|
|
||||||
// This is a workaround for inability of Electron's ESM loader to resolve subpath imports.
|
|
||||||
// Do not externalize `electron-log` so subpath imports such as `electron-log/main` works.
|
|
||||||
// See https://github.com/electron/electron/issues/41241, https://github.com/alex8088/electron-vite/issues/401
|
|
||||||
'electron-log',
|
|
||||||
],
|
|
||||||
})],
|
|
||||||
define: {
|
define: {
|
||||||
...getClientEnvironmentVariables(),
|
...getClientEnvironmentVariables(),
|
||||||
},
|
},
|
||||||
|
|||||||
8453
package-lock.json
generated
8453
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
76
package.json
76
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "privacy.sexy",
|
"name": "privacy.sexy",
|
||||||
"version": "0.13.1",
|
"version": "0.13.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"slogan": "Privacy is sexy",
|
"slogan": "Privacy is sexy",
|
||||||
"description": "Enforce privacy & security best-practices on Windows, macOS and Linux, because privacy is sexy.",
|
"description": "Enforce privacy & security best-practices on Windows, macOS and Linux, because privacy is sexy.",
|
||||||
@@ -33,66 +33,62 @@
|
|||||||
"postuninstall": "electron-builder install-app-deps"
|
"postuninstall": "electron-builder install-app-deps"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@floating-ui/vue": "^1.0.6",
|
"@floating-ui/vue": "^1.0.2",
|
||||||
"@juggle/resize-observer": "^3.4.0",
|
"@juggle/resize-observer": "^3.4.0",
|
||||||
"ace-builds": "^1.33.0",
|
"@types/markdown-it": "^13.0.7",
|
||||||
"electron-log": "^5.1.2",
|
"ace-builds": "^1.30.0",
|
||||||
"electron-progressbar": "^2.2.1",
|
"electron-log": "^5.0.1",
|
||||||
"electron-updater": "^6.1.9",
|
"electron-progressbar": "^2.1.0",
|
||||||
|
"electron-updater": "^6.1.4",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^13.0.2",
|
||||||
"vue": "^3.4.21"
|
"vue": "^3.3.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@modyfi/vite-plugin-yaml": "^1.1.0",
|
"@modyfi/vite-plugin-yaml": "^1.1.0",
|
||||||
"@rushstack/eslint-patch": "^1.10.2",
|
"@rushstack/eslint-patch": "^1.6.1",
|
||||||
"@types/ace": "^0.0.52",
|
"@types/ace": "^0.0.49",
|
||||||
"@types/file-saver": "^2.0.7",
|
"@types/file-saver": "^2.0.5",
|
||||||
"@types/markdown-it": "^14.0.1",
|
"@typescript-eslint/eslint-plugin": "^6.17.0",
|
||||||
"@typescript-eslint/eslint-plugin": "6.21.0",
|
"@typescript-eslint/parser": "^6.17.0",
|
||||||
"@typescript-eslint/parser": "6.21.0",
|
|
||||||
"@vitejs/plugin-legacy": "^5.3.2",
|
"@vitejs/plugin-legacy": "^5.3.2",
|
||||||
"@vitejs/plugin-vue": "^5.0.4",
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
"@vue/eslint-config-airbnb-with-typescript": "^8.0.0",
|
"@vue/eslint-config-airbnb-with-typescript": "^8.0.0",
|
||||||
"@vue/eslint-config-typescript": "12.0.0",
|
"@vue/eslint-config-typescript": "^12.0.0",
|
||||||
"@vue/test-utils": "^2.4.5",
|
"@vue/test-utils": "^2.4.1",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.16",
|
||||||
"cypress": "^13.7.3",
|
"cypress": "^13.3.1",
|
||||||
"electron": "^29.3.0",
|
"electron": "^27.0.0",
|
||||||
"electron-builder": "^24.13.3",
|
"electron-builder": "^24.6.4",
|
||||||
"electron-devtools-installer": "^3.2.0",
|
"electron-devtools-installer": "^3.2.0",
|
||||||
"electron-icon-builder": "^2.0.1",
|
"electron-icon-builder": "^2.0.1",
|
||||||
"electron-vite": "^2.1.0",
|
"electron-vite": "^2.1.0",
|
||||||
"eslint": "8.57.0",
|
"eslint": "^8.56.0",
|
||||||
"eslint-plugin-cypress": "^2.15.1",
|
"eslint-plugin-cypress": "^2.15.1",
|
||||||
"eslint-plugin-vue": "^9.25.0",
|
"eslint-plugin-vue": "^9.19.2",
|
||||||
"eslint-plugin-vuejs-accessibility": "^2.2.1",
|
"eslint-plugin-vuejs-accessibility": "^2.2.0",
|
||||||
"icon-gen": "^4.0.0",
|
"icon-gen": "^4.0.0",
|
||||||
"jsdom": "^24.0.0",
|
"jsdom": "^22.1.0",
|
||||||
"markdownlint-cli": "^0.39.0",
|
"markdownlint-cli": "^0.37.0",
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.4.31",
|
||||||
"remark-cli": "^12.0.0",
|
"remark-cli": "^12.0.0",
|
||||||
"remark-lint-no-dead-urls": "^1.1.0",
|
"remark-lint-no-dead-urls": "^1.1.0",
|
||||||
"remark-preset-lint-consistent": "^6.0.0",
|
"remark-preset-lint-consistent": "^5.1.2",
|
||||||
"remark-validate-links": "^13.0.1",
|
"remark-validate-links": "^13.0.0",
|
||||||
"sass": "^1.75.0",
|
"sass": "^1.69.3",
|
||||||
"start-server-and-test": "^2.0.3",
|
"start-server-and-test": "^2.0.1",
|
||||||
"svgexport": "^0.4.2",
|
"svgexport": "^0.4.2",
|
||||||
"terser": "^5.30.3",
|
"terser": "^5.21.0",
|
||||||
"tslib": "^2.6.2",
|
"tslib": "^2.6.2",
|
||||||
"typescript": "^5.4.5",
|
"typescript": "^5.3.3",
|
||||||
"vite": "^5.2.8",
|
"vite": "^5.1.6",
|
||||||
"vitest": "^1.5.0",
|
"vitest": "^0.34.6",
|
||||||
"vue-tsc": "^2.0.13",
|
"vue-tsc": "^1.8.19",
|
||||||
"yaml-lint": "^1.7.0"
|
"yaml-lint": "^1.7.0"
|
||||||
},
|
},
|
||||||
"//devDependencies": {
|
"//devDependencies": {
|
||||||
"terser": "Used by `@vitejs/plugin-legacy` for minification",
|
"terser": "Used by `@vitejs/plugin-legacy` for minification",
|
||||||
"@rushstack/eslint-patch": "Needed by `@vue/eslint-config-typescript` and `@vue/eslint-config-airbnb-with-typescript`",
|
"@rushstack/eslint-patch": "Needed by `@vue/eslint-config-typescript` and `@vue/eslint-config-airbnb-with-typescript`"
|
||||||
"@typescript-eslint/eslint-plugin": "Cannot migrate to v7 because of `@vue/eslint-config-airbnb-with-typescript`, see https://github.com/vuejs/eslint-config-airbnb/issues/63",
|
|
||||||
"@typescript-eslint/parser": "Cannot migrate to v7 because of `@vue/eslint-config-airbnb-with-typescript`, see https://github.com/vuejs/eslint-config-airbnb/issues/63",
|
|
||||||
"@vue/eslint-config-typescript": "Cannot migrate to v13 because of `@vue/eslint-config-airbnb-with-typescript`, see https://github.com/vuejs/eslint-config-airbnb/issues/63",
|
|
||||||
"eslint": "Cannot migrate to v9 `@typescript-eslint/eslint-plugin` (≤ v7), `@typescript-eslint/parser` (≤ v7), `@vue/eslint-config-airbnb-with-typescript@` (≤ v8) requires `eslint` ≤ v8, see https://github.com/vuejs/eslint-config-airbnb/issues/65, https://github.com/typescript-eslint/typescript-eslint/issues/8211"
|
|
||||||
},
|
},
|
||||||
"homepage": "https://privacy.sexy",
|
"homepage": "https://privacy.sexy",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@@ -1,15 +1,4 @@
|
|||||||
/**
|
#!/usr/bin/env bash
|
||||||
* Description:
|
|
||||||
* This script updates the logo images across the project based on the primary
|
|
||||||
* logo file ('img/logo.svg' file).
|
|
||||||
* It handles the creation and update of various icon sizes for different purposes,
|
|
||||||
* including desktop launcher icons, tray icons, and web favicons from a single source
|
|
||||||
* SVG logo file.
|
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
* node ./scripts/logo-update.js
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { resolve, join } from 'node:path';
|
import { resolve, join } from 'node:path';
|
||||||
import { rm, mkdtemp, stat } from 'node:fs/promises';
|
import { rm, mkdtemp, stat } from 'node:fs/promises';
|
||||||
import { spawn } from 'node:child_process';
|
import { spawn } from 'node:child_process';
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ function getBuildVerificationConfigs() {
|
|||||||
'--electron-unbundled': {
|
'--electron-unbundled': {
|
||||||
printDistDirScriptArgument: '--electron-unbundled',
|
printDistDirScriptArgument: '--electron-unbundled',
|
||||||
filePatterns: [
|
filePatterns: [
|
||||||
/main[/\\]index\.(cjs|mjs|js)/,
|
/main[/\\]index\.cjs/,
|
||||||
/preload[/\\]index\.(cjs|mjs|js)/,
|
/preload[/\\]index\.cjs/,
|
||||||
/renderer[/\\]index\.htm(l)?/,
|
/renderer[/\\]index\.htm(l)?/,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,87 +1,62 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Description:
|
* Description:
|
||||||
* This script checks if a server, provided as a CLI argument, is up
|
* This script checks if a server, provided as a CLI argument, is up
|
||||||
* and returns an HTTP 200 status code.
|
* and returns an HTTP 200 status code.
|
||||||
* It is designed to provide easy verification of server availability
|
* It is designed to provide easy verification of server availability
|
||||||
* and will retry a specified number of times.
|
* and will retry a specified number of times.
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* node ./scripts/verify-web-server-status.js --url [URL] [--max-retries NUMBER]
|
* node ./scripts/verify-web-server-status.js --url [URL]
|
||||||
*
|
*
|
||||||
* Options:
|
* Options:
|
||||||
* --url URL of the server to check
|
* --url URL of the server to check
|
||||||
* --max-retries Maximum number of retry attempts (default: 30)
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const DEFAULT_MAX_RETRIES = 30;
|
import { get } from 'http';
|
||||||
const RETRY_DELAY_IN_SECONDS = 3;
|
|
||||||
const PARAMETER_NAME_URL = '--url';
|
|
||||||
const PARAMETER_NAME_MAX_RETRIES = '--max-retries';
|
|
||||||
|
|
||||||
async function checkServer(currentRetryCount = 1) {
|
const MAX_RETRIES = 30;
|
||||||
const serverUrl = readRequiredParameterValue(PARAMETER_NAME_URL);
|
const RETRY_DELAY_IN_SECONDS = 3;
|
||||||
const maxRetries = parseNumber(
|
const URL_PARAMETER_NAME = '--url';
|
||||||
readOptionalParameterValue(PARAMETER_NAME_MAX_RETRIES, DEFAULT_MAX_RETRIES),
|
|
||||||
);
|
function checkServer(currentRetryCount = 1) {
|
||||||
console.log(`🌐 Requesting ${serverUrl}...`);
|
const serverUrl = getServerUrl();
|
||||||
try {
|
console.log(`Requesting ${serverUrl}...`);
|
||||||
const response = await fetch(serverUrl);
|
get(serverUrl, (res) => {
|
||||||
if (response.status === 200) {
|
if (res.statusCode === 200) {
|
||||||
console.log('🎊 Success: The server is up and returned HTTP 200.');
|
console.log('🎊 Success: The server is up and returned HTTP 200.');
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
} else {
|
} else {
|
||||||
exitWithError(`Server returned unexpected HTTP status code ${response.statusCode}.`);
|
console.log(`Server returned HTTP status code ${res.statusCode}.`);
|
||||||
|
retry(currentRetryCount);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
}).on('error', (err) => {
|
||||||
console.error('Error making the request:', error);
|
console.error('Error making the request:', err);
|
||||||
scheduleNextRetry(maxRetries, currentRetryCount);
|
retry(currentRetryCount);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function scheduleNextRetry(maxRetries, currentRetryCount) {
|
function retry(currentRetryCount) {
|
||||||
console.log(`Attempt ${currentRetryCount}/${maxRetries}:`);
|
console.log(`Attempt ${currentRetryCount}/${MAX_RETRIES}:`);
|
||||||
console.log(`Retrying in ${RETRY_DELAY_IN_SECONDS} seconds.`);
|
console.log(`Retrying in ${RETRY_DELAY_IN_SECONDS} seconds.`);
|
||||||
|
|
||||||
const remainingTime = (maxRetries - currentRetryCount) * RETRY_DELAY_IN_SECONDS;
|
const remainingTime = (MAX_RETRIES - currentRetryCount) * RETRY_DELAY_IN_SECONDS;
|
||||||
console.log(`Time remaining before timeout: ${remainingTime}s`);
|
console.log(`Time remaining before timeout: ${remainingTime}s`);
|
||||||
|
|
||||||
if (currentRetryCount < maxRetries) {
|
if (currentRetryCount < MAX_RETRIES) {
|
||||||
setTimeout(() => checkServer(currentRetryCount + 1), RETRY_DELAY_IN_SECONDS * 1000);
|
setTimeout(() => checkServer(currentRetryCount + 1), RETRY_DELAY_IN_SECONDS * 1000);
|
||||||
} else {
|
} else {
|
||||||
exitWithError('The server at did not return HTTP 200 within the allocated time.');
|
console.log('Failure: The server at did not return HTTP 200 within the allocated time. Exiting.');
|
||||||
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function readRequiredParameterValue(parameterName) {
|
function getServerUrl() {
|
||||||
const parameterValue = readOptionalParameterValue(parameterName);
|
const urlIndex = process.argv.indexOf(URL_PARAMETER_NAME);
|
||||||
if (parameterValue === undefined) {
|
if (urlIndex === -1 || urlIndex === process.argv.length - 1) {
|
||||||
exitWithError(`Parameter "${parameterName}" is required but not provided.`);
|
console.error(`Parameter "${URL_PARAMETER_NAME}" is not provided.`);
|
||||||
|
process.exit(1);
|
||||||
}
|
}
|
||||||
return parameterValue;
|
return process.argv[urlIndex + 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
function readOptionalParameterValue(parameterName, defaultValue) {
|
checkServer();
|
||||||
const index = process.argv.indexOf(parameterName);
|
|
||||||
if (index === -1 || index === process.argv.length - 1) {
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
return process.argv[index + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseNumber(numberLike) {
|
|
||||||
const number = parseInt(numberLike, 10);
|
|
||||||
if (Number.isNaN(number)) {
|
|
||||||
exitWithError(`Invalid number: ${numberLike}`);
|
|
||||||
}
|
|
||||||
return number;
|
|
||||||
}
|
|
||||||
|
|
||||||
function exitWithError(message) {
|
|
||||||
console.error(`Failure: ${message}`);
|
|
||||||
console.log('Exiting');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
await checkServer();
|
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
/*
|
|
||||||
Shuffle an array of strings, returning a new array with elements in random order.
|
|
||||||
Uses the Fisher-Yates (or Durstenfeld) algorithm.
|
|
||||||
*/
|
|
||||||
export function shuffle<T>(array: readonly T[]): T[] {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
@@ -95,7 +95,7 @@ function getLines(code: string): string[] {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
Merges inline here-strings to a single lined string with Windows line terminator (\r\n)
|
Merges inline here-strings to a single lined string with Windows line terminator (\r\n)
|
||||||
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules?view=powershell-7.4#here-strings
|
https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules#here-strings
|
||||||
*/
|
*/
|
||||||
function mergeHereStrings(code: string) {
|
function mergeHereStrings(code: string) {
|
||||||
const regex = /@(['"])\s*(?:\r\n|\r|\n)((.|\n|\r)+?)(\r\n|\r|\n)\1@/g;
|
const regex = /@(['"])\s*(?:\r\n|\r|\n)((.|\n|\r)+?)(\r\n|\r|\n)\1@/g;
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ actions:
|
|||||||
- [tcsh source code](https://web.archive.org/web/20221029212024/https://github.com/tcsh-org/tcsh).
|
- [tcsh source code](https://web.archive.org/web/20221029212024/https://github.com/tcsh-org/tcsh).
|
||||||
|
|
||||||
[1]: https://web.archive.org/web/20221029134950/https://linux.die.net/man/1/tcsh "tcsh(1) - Linux man page | linux.die.net"
|
[1]: https://web.archive.org/web/20221029134950/https://linux.die.net/man/1/tcsh "tcsh(1) - Linux man page | linux.die.net"
|
||||||
[2]: https://web.archive.org/web/20221029135007/https://books.google.com/books?id=LyDP5b2xzaMC&pg=PA56#v=onepage&q&f=false "Sams Teach Yourself FreeBSD in 24 Hours - Michael Urban, Brian Tiemann - Google Books | books.google.com"
|
[2]: https://web.archive.org/web/20221029135041/https://books.google.com/books?id=LyDP5b2xzaMC&pg=PA56 "Sams Teach Yourself FreeBSD in 24 Hours - Michael Urban, Brian Tiemann - Google Books | books.google.com"
|
||||||
call:
|
call:
|
||||||
function: DeleteFileFromUserAndRootHome
|
function: DeleteFileFromUserAndRootHome
|
||||||
parameters:
|
parameters:
|
||||||
@@ -1733,7 +1733,7 @@ actions:
|
|||||||
See also:
|
See also:
|
||||||
- [Source code for the Ubuntu Report tool | github.com](https://web.archive.org/web/20221029221854/https://github.com/ubuntu/ubuntu-report/)
|
- [Source code for the Ubuntu Report tool | github.com](https://web.archive.org/web/20221029221854/https://github.com/ubuntu/ubuntu-report/)
|
||||||
- [Statistics gathered and visualized | ubuntu.com/desktop/statistics](https://web.archive.org/web/20221029221910/https://ubuntu.com/desktop/statistics)
|
- [Statistics gathered and visualized | ubuntu.com/desktop/statistics](https://web.archive.org/web/20221029221910/https://ubuntu.com/desktop/statistics)
|
||||||
- [ubuntu-devel mailing list thread where ubuntu-report was first proposed | lists.ubuntu.com](https://web.archive.org/web/20221029162523/https://lists.ubuntu.com/archives/ubuntu-devel/2018-February/040139.html)
|
- [ubuntu-devel mailing list thread where ubuntu-report was first proposed, | lists.ubuntu.com ](https://web.archive.org/web/20221029221924/https://lists.ubuntu.com/archives/ubuntu-devel/2018-February/040139.html)
|
||||||
|
|
||||||
[1]: https://web.archive.org/web/20221029162505/https://github.com/ubuntu/ubuntu-report/blob/30e902ebc17e4e10d83392d7cd3dc05fc9e35cc4/README.md "ubuntu-report/README.md at master · ubuntu/ubuntu-report | github.com"
|
[1]: https://web.archive.org/web/20221029162505/https://github.com/ubuntu/ubuntu-report/blob/30e902ebc17e4e10d83392d7cd3dc05fc9e35cc4/README.md "ubuntu-report/README.md at master · ubuntu/ubuntu-report | github.com"
|
||||||
[2]: https://web.archive.org/web/20221029162538/https://github.com/ubuntu/ubuntu-report/blob/8e6030ff9bbeacacf41a9b58ea638a5c9a6f864d/README.md "More diagnostics data from desktop | lists.ubuntu.com"
|
[2]: https://web.archive.org/web/20221029162538/https://github.com/ubuntu/ubuntu-report/blob/8e6030ff9bbeacacf41a9b58ea638a5c9a6f864d/README.md "More diagnostics data from desktop | lists.ubuntu.com"
|
||||||
@@ -1974,10 +1974,10 @@ actions:
|
|||||||
|
|
||||||
Read more about Zeitgeist:
|
Read more about Zeitgeist:
|
||||||
|
|
||||||
- [Official website | zeitgeist.freedesktop.org](https://web.archive.org/web/20221029150843/https://zeitgeist.freedesktop.org/)
|
- [Official website | zeitgeist.freedesktop.org](https://web.archive.org/web/20221029222739/https://zeitgeist.freedesktop.org/)
|
||||||
- [Wikipedia article | en.wikipedia.org](https://web.archive.org/web/20221029222921/https://en.wikipedia.org/wiki/Zeitgeist_%28free_software%29)
|
- [Wikipedia article | en.wikipedia.org](https://web.archive.org/web/20221029222921/https://en.wikipedia.org/wiki/Zeitgeist_%28free_software%29)
|
||||||
- [Launchpad project page | launchpad.net](https://web.archive.org/web/20221029223026/https://launchpad.net/zeitgeist/)
|
- [Launchpad project page | launchpad.net](https://web.archive.org/web/20221029223026/https://launchpad.net/zeitgeist/)
|
||||||
- [ArchWiki article | wiki.archlinux.org](https://web.archive.org/web/20221029164539/https://wiki.archlinux.org/title/Zeitgeist)
|
- [ArchWiki article | wiki.archlinux.org](https://web.archive.org/web/20221029223033/https://wiki.archlinux.org/title/Zeitgeist)
|
||||||
|
|
||||||
[1]: https://web.archive.org/web/20221029163704/https://packages.debian.org/en/sid/libdevel/libzeitgeist-2.0-dev "libzeitgeist-2.0-dev | Debian Packages | packages.debian.org"
|
[1]: https://web.archive.org/web/20221029163704/https://packages.debian.org/en/sid/libdevel/libzeitgeist-2.0-dev "libzeitgeist-2.0-dev | Debian Packages | packages.debian.org"
|
||||||
[2]: https://web.archive.org/web/20221029163817/https://gitlab.gnome.org/crvi/gnome-activity-journal "crvi / GNOME Activity Journal · GitLab | gitlab.gnome.org"
|
[2]: https://web.archive.org/web/20221029163817/https://gitlab.gnome.org/crvi/gnome-activity-journal "crvi / GNOME Activity Journal · GitLab | gitlab.gnome.org"
|
||||||
@@ -2116,7 +2116,7 @@ actions:
|
|||||||
[3]: https://web.archive.org/web/20221029170026/https://packages.ubuntu.com/bionic/all/network-manager-config-connectivity-ubuntu/filelist "Ubuntu - File list of package network-manager-config-connectivity-ubuntu/bionic/all | packages.ubuntu.com"
|
[3]: https://web.archive.org/web/20221029170026/https://packages.ubuntu.com/bionic/all/network-manager-config-connectivity-ubuntu/filelist "Ubuntu - File list of package network-manager-config-connectivity-ubuntu/bionic/all | packages.ubuntu.com"
|
||||||
[4]: https://web.archive.org/web/20221029170108/https://github.com/pop-os/connectivity/blob/master/debian/20-connectivity-pop.conf "connectivity/20-connectivity-pop.conf at master · pop-os/connectivity | github.com"
|
[4]: https://web.archive.org/web/20221029170108/https://github.com/pop-os/connectivity/blob/master/debian/20-connectivity-pop.conf "connectivity/20-connectivity-pop.conf at master · pop-os/connectivity | github.com"
|
||||||
[5]: https://web.archive.org/web/20221029170202/https://cgit.freedesktop.org/NetworkManager/NetworkManager/tree/contrib/fedora/rpm/20-connectivity-fedora.conf "20-connectivity-fedora.conf\rpm\fedora\contrib - NetworkManager/NetworkManager - Network connection manager and user applications | reedesktop.org"
|
[5]: https://web.archive.org/web/20221029170202/https://cgit.freedesktop.org/NetworkManager/NetworkManager/tree/contrib/fedora/rpm/20-connectivity-fedora.conf "20-connectivity-fedora.conf\rpm\fedora\contrib - NetworkManager/NetworkManager - Network connection manager and user applications | reedesktop.org"
|
||||||
[6]: https://archive.ph/2023.12.06-185917/https://pkgs.org/download/NetworkManager-config-connectivity-fedora "Networkmanager-config-connectivity-fedora Download (RPM) | pkgs.org"
|
[6]: https://web.archive.org/web/20221029170207/https://fedora.pkgs.org/35/fedora-updates-testing-x86_64/NetworkManager-config-connectivity-fedora-1.32.12-1.fc35.noarch.rpm.html "NetworkManager-config-connectivity-fedora | fedora.pkgs.org"
|
||||||
call:
|
call:
|
||||||
function: RunIfCommandExists
|
function: RunIfCommandExists
|
||||||
parameters:
|
parameters:
|
||||||
@@ -2202,7 +2202,7 @@ actions:
|
|||||||
- Diagnostic information about your system and usage is sent to Microsoft servers [3].
|
- Diagnostic information about your system and usage is sent to Microsoft servers [3].
|
||||||
- Your usage data and data about feature performance [3].
|
- Your usage data and data about feature performance [3].
|
||||||
|
|
||||||
[1]: https://web.archive.org/web/20221029142001/https://en.wikipedia.org/wiki/Visual_Studio_Code "Visual Studio Code - Wikipedia | en.wikipedia.org"
|
[1]: https://web.archive.org/web/20221029170818/https://en.wikipedia.org/wiki/Visual_Studio_Code "Visual Studio Code - Wikipedia | en.wikipedia.org"
|
||||||
[2]: https://web.archive.org/web/20221029170840/https://code.visualstudio.com/updates/v1_26#_offline-mode "Visual Studio Code July 2018 | code.visualstudio.com"
|
[2]: https://web.archive.org/web/20221029170840/https://code.visualstudio.com/updates/v1_26#_offline-mode "Visual Studio Code July 2018 | code.visualstudio.com"
|
||||||
[3]: https://web.archive.org/web/20221029171138/https://code.visualstudio.com/docs/getstarted/telemetry "Visual Studio Code Telemetry | code.visualstudio.com"
|
[3]: https://web.archive.org/web/20221029171138/https://code.visualstudio.com/docs/getstarted/telemetry "Visual Studio Code Telemetry | code.visualstudio.com"
|
||||||
children:
|
children:
|
||||||
@@ -2697,7 +2697,7 @@ actions:
|
|||||||
[2]: https://web.archive.org/web/20231003094154/https://bugzilla.mozilla.org/show_bug.cgi?id=1746646 "1746646 - (tcp-mochitests) [meta] Make mochitests work with TCP enabled (cookieBehavior = 5) | bugzilla.mozilla.org"
|
[2]: https://web.archive.org/web/20231003094154/https://bugzilla.mozilla.org/show_bug.cgi?id=1746646 "1746646 - (tcp-mochitests) [meta] Make mochitests work with TCP enabled (cookieBehavior = 5) | bugzilla.mozilla.org"
|
||||||
[3]: https://web.archive.org/web/20230918172155/https://developer.mozilla.org/en-US/docs/Web/Privacy/State_Partitioning#disable_dynamic_state_partitioning "State Partitioning - Privacy on the web | MDN"
|
[3]: https://web.archive.org/web/20230918172155/https://developer.mozilla.org/en-US/docs/Web/Privacy/State_Partitioning#disable_dynamic_state_partitioning "State Partitioning - Privacy on the web | MDN"
|
||||||
[4]: https://web.archive.org/web/20231003094207/https://bugzilla.mozilla.org/show_bug.cgi?id=1649876#c5 "1649876 - Migrate FPI users to dFPI | bugzilla.mozilla.org"
|
[4]: https://web.archive.org/web/20231003094207/https://bugzilla.mozilla.org/show_bug.cgi?id=1649876#c5 "1649876 - Migrate FPI users to dFPI | bugzilla.mozilla.org"
|
||||||
[5]: https://web.archive.org/web/20231207105610/https://blog.mozilla.org/en/products/firefox/firefox-rolls-out-total-cookie-protection-by-default-to-all-users-worldwide/ "Firefox Rolls Out Total Cookie Protection By Default"
|
[5]: https://blog.mozilla.org/en/products/firefox/firefox-rolls-out-total-cookie-protection-by-default-to-all-users-worldwide/ "Firefox Rolls Out Total Cookie Protection By Default"
|
||||||
[6]: https://web.archive.org/web/20231003094350/https://bugzilla.mozilla.org/show_bug.cgi?id=1631676#c25 "1631676 - Disable dfpi when privacy.firstparty.isolate=true | bugzilla.mozilla.org"
|
[6]: https://web.archive.org/web/20231003094350/https://bugzilla.mozilla.org/show_bug.cgi?id=1631676#c25 "1631676 - Disable dfpi when privacy.firstparty.isolate=true | bugzilla.mozilla.org"
|
||||||
call:
|
call:
|
||||||
function: AddFirefoxPrefs
|
function: AddFirefoxPrefs
|
||||||
@@ -2887,7 +2887,7 @@ actions:
|
|||||||
setting [4].
|
setting [4].
|
||||||
|
|
||||||
[1]: https://web.archive.org/web/20221015102124/https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/internals/preferences.html "Preferences and Defines — Firefox Source Docs documentation | firefox-source-docs.mozilla.org"
|
[1]: https://web.archive.org/web/20221015102124/https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/internals/preferences.html "Preferences and Defines — Firefox Source Docs documentation | firefox-source-docs.mozilla.org"
|
||||||
[2]: https://web.archive.org/web/20221015102338/https://searchfox.org/mozilla-central/source/modules/libpref/Preferences.cpp#3213
|
[2]: https://web.archive.org/web/20221015102305/https://searchfox.org/mozilla-central/source/modules/libpref/Preferences.cpp#3213
|
||||||
[3]: https://web.archive.org/web/20221015102419/https://bugzilla.mozilla.org/show_bug.cgi?id=1422689#c1
|
[3]: https://web.archive.org/web/20221015102419/https://bugzilla.mozilla.org/show_bug.cgi?id=1422689#c1
|
||||||
[4]: https://web.archive.org/web/20221015102604/https://stigviewer.com/stig/mozilla_firefox/2020-12-10/finding/V-223170
|
[4]: https://web.archive.org/web/20221015102604/https://stigviewer.com/stig/mozilla_firefox/2020-12-10/finding/V-223170
|
||||||
call:
|
call:
|
||||||
@@ -3173,7 +3173,7 @@ actions:
|
|||||||
portal is in place and blocking traffic, this feature prevents all other connection attempts,
|
portal is in place and blocking traffic, this feature prevents all other connection attempts,
|
||||||
possibly revealing your usage habits.
|
possibly revealing your usage habits.
|
||||||
|
|
||||||
See also: [Captive portal | Wikipedia](https://web.archive.org/web/20221029163002/https://en.wikipedia.org/wiki/Captive_portal).
|
See also: [Captive portal | Wikipedia](https://web.archive.org/web/20221029223534/https://en.wikipedia.org/wiki/Captive_portal).
|
||||||
|
|
||||||
This script sets `network.captive-portal-service.enabled` to 'false', thereby disabling automatic
|
This script sets `network.captive-portal-service.enabled` to 'false', thereby disabling automatic
|
||||||
connections [1].
|
connections [1].
|
||||||
@@ -3207,7 +3207,7 @@ actions:
|
|||||||
There have been concerns about the potential for Google Safe Browsing to be used for censorship
|
There have been concerns about the potential for Google Safe Browsing to be used for censorship
|
||||||
in the future, although this has not occurred as of yet [3].
|
in the future, although this has not occurred as of yet [3].
|
||||||
|
|
||||||
[1]: https://web.archive.org/web/20221026164502/https://wiki.mozilla.org/Security/Safe_Browsing "Security/Safe Browsing - MozillaWiki | wiki.mozilla.org"
|
[1]: https://web.archive.org/web/20221025192643/https://wiki.mozilla.org/Security/Safe_Browsing "Security/Safe Browsing - MozillaWiki | wiki.mozilla.org"
|
||||||
[2]: https://web.archive.org/web/20221025193000/https://support.mozilla.org/en-US/kb/how-does-phishing-and-malware-protection-work#w_what-information-is-sent-to-mozilla-or-its-partners-when-phishing-and-malware-protection-is-enabled
|
[2]: https://web.archive.org/web/20221025193000/https://support.mozilla.org/en-US/kb/how-does-phishing-and-malware-protection-work#w_what-information-is-sent-to-mozilla-or-its-partners-when-phishing-and-malware-protection-is-enabled
|
||||||
[3]: https://web.archive.org/web/20221025192516/https://www.usnews.com/opinion/articles/2016-06-22/google-is-the-worlds-biggest-censor-and-its-power-must-be-regulated "Google Is the World's Biggest Censor and Its Power Must Be Regulated | usnews.com"
|
[3]: https://web.archive.org/web/20221025192516/https://www.usnews.com/opinion/articles/2016-06-22/google-is-the-worlds-biggest-censor-and-its-power-must-be-regulated "Google Is the World's Biggest Censor and Its Power Must Be Regulated | usnews.com"
|
||||||
children:
|
children:
|
||||||
@@ -3226,7 +3226,7 @@ actions:
|
|||||||
|
|
||||||
If this blocking is removed, the user should be knowledgeable about the potential risks and will take precautions.
|
If this blocking is removed, the user should be knowledgeable about the potential risks and will take precautions.
|
||||||
|
|
||||||
[1]: https://web.archive.org/web/20221026164502/https://wiki.mozilla.org/Security/Safe_Browsing#Prefs "Security/Safe Browsing - MozillaWiki | wiki.mozilla.org"
|
[1]: https://web.archive.org/web/20221025192643/https://wiki.mozilla.org/Security/Safe_Browsing#Prefs "Security/Safe Browsing - MozillaWiki | wiki.mozilla.org"
|
||||||
[2]: https://web.archive.org/web/20230811024650/https://blog.mozilla.org/addons/2020/08/24/introducing-a-scalable-add-ons-blocklist/ "Introducing a scalable add-ons blocklist | Mozilla Add-ons Community Blog"
|
[2]: https://web.archive.org/web/20230811024650/https://blog.mozilla.org/addons/2020/08/24/introducing-a-scalable-add-ons-blocklist/ "Introducing a scalable add-ons blocklist | Mozilla Add-ons Community Blog"
|
||||||
call:
|
call:
|
||||||
function: AddFirefoxPrefs
|
function: AddFirefoxPrefs
|
||||||
@@ -3286,7 +3286,7 @@ actions:
|
|||||||
|
|
||||||
It is active by default [2].
|
It is active by default [2].
|
||||||
|
|
||||||
[1]: https://web.archive.org/web/20221026164502/https://wiki.mozilla.org/Security/Safe_Browsing#Prefs "Security/Safe Browsing - MozillaWiki | wiki.mozilla.org"
|
[1]: https://web.archive.org/web/20221025192643/https://wiki.mozilla.org/Security/Safe_Browsing#Prefs "Security/Safe Browsing - MozillaWiki | wiki.mozilla.org"
|
||||||
[2]: https://web.archive.org/web/20221029173442/https://github.com/mozilla/policy-templates/blob/master/README.md#preferences "policy-templates/README.md at master · mozilla/policy-templates · GitHub | github.com"
|
[2]: https://web.archive.org/web/20221029173442/https://github.com/mozilla/policy-templates/blob/master/README.md#preferences "policy-templates/README.md at master · mozilla/policy-templates · GitHub | github.com"
|
||||||
call:
|
call:
|
||||||
function: AddFirefoxPrefs
|
function: AddFirefoxPrefs
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ actions:
|
|||||||
name: Clear user activity audit logs (login, logout, authentication, etc.)
|
name: Clear user activity audit logs (login, logout, authentication, etc.)
|
||||||
docs:
|
docs:
|
||||||
- https://papers.put.as/papers/macosx/2012/Mac_Log_Analysis_Sarah_Edwards_DFIRSummit2012.pdf
|
- https://papers.put.as/papers/macosx/2012/Mac_Log_Analysis_Sarah_Edwards_DFIRSummit2012.pdf
|
||||||
- https://web.archive.org/web/20240314054514/https://bpb-us-e1.wpmucdn.com/sites.psu.edu/dist/4/24696/files/2016/06/psumac2016-19-osxlogs_macadmins_2016.pdf
|
- http://macadmins.psu.edu/wp-content/uploads/sites/24696/2016/06/psumac2016-19-osxlogs_macadmins_2016.pdf
|
||||||
code: |-
|
code: |-
|
||||||
sudo rm -rfv /var/audit/*
|
sudo rm -rfv /var/audit/*
|
||||||
sudo rm -rfv /private/var/audit/*
|
sudo rm -rfv /private/var/audit/*
|
||||||
@@ -171,7 +171,7 @@ actions:
|
|||||||
-
|
-
|
||||||
name: Clear Safari last session (open tabs) history
|
name: Clear Safari last session (open tabs) history
|
||||||
docs:
|
docs:
|
||||||
- https://web.archive.org/web/20240314061752/https://apple.stackexchange.com/questions/374099/where-does-safari-store-the-open-tabs/374116#374116
|
- https://apple.stackexchange.com/a/374116
|
||||||
- https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2013-7127
|
- https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2013-7127
|
||||||
code: rm -f ~/Library/Safari/LastSession.plist
|
code: rm -f ~/Library/Safari/LastSession.plist
|
||||||
-
|
-
|
||||||
@@ -191,7 +191,7 @@ actions:
|
|||||||
name: Clear Safari webpage previews (thumbnails)
|
name: Clear Safari webpage previews (thumbnails)
|
||||||
docs:
|
docs:
|
||||||
- https://davidkoepi.wordpress.com/2013/04/20/safariforensic/
|
- https://davidkoepi.wordpress.com/2013/04/20/safariforensic/
|
||||||
- https://archive.ph/2024.03.14-100910/https://www.reddit.com/r/apple/comments/18lp92/your_apple_computer_keeps_a_screen_shot_of_nearly/?rdt=59921
|
- 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
|
code: rm -rfv ~/Library/Caches/com.apple.Safari/Webpage\ Previews
|
||||||
-
|
-
|
||||||
name: Clear Safari history copy
|
name: Clear Safari history copy
|
||||||
@@ -204,8 +204,8 @@ actions:
|
|||||||
-
|
-
|
||||||
name: Clear Safari cookies
|
name: Clear Safari cookies
|
||||||
docs:
|
docs:
|
||||||
- https://web.archive.org/web/20240314132018/https://community.spiceworks.com/t/understanding-the-safari-cookies-binarycookies-file-format/928827
|
- https://www.toolbox.com/tech/operating-systems/blogs/understanding-the-safari-cookiesbinarycookies-file-format-010712/
|
||||||
- https://web.archive.org/web/20240314060318/https://link.springer.com/content/pdf/10.1007/0-387-36891-4_13.pdf
|
- https://link.springer.com/content/pdf/10.1007/0-387-36891-4_13.pdf
|
||||||
code: |-
|
code: |-
|
||||||
rm -f ~/Library/Cookies/Cookies.binarycookies
|
rm -f ~/Library/Cookies/Cookies.binarycookies
|
||||||
# Used before Safari 5.1
|
# Used before Safari 5.1
|
||||||
@@ -520,7 +520,7 @@ actions:
|
|||||||
you'll be prompted to grant or deny permission. It's a proactive step to ensure that your sensitive information
|
you'll be prompted to grant or deny permission. It's a proactive step to ensure that your sensitive information
|
||||||
or system services are accessed only with your current and informed consent.
|
or system services are accessed only with your current and informed consent.
|
||||||
children:
|
children:
|
||||||
# Main documentation: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services
|
# Main documentation: https://archive.ph/26Hlq (https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services)
|
||||||
-
|
-
|
||||||
name: Clear **"All"** permissions
|
name: Clear **"All"** permissions
|
||||||
docs: |-
|
docs: |-
|
||||||
@@ -536,7 +536,7 @@ actions:
|
|||||||
This script resets permissions for camera access [1].
|
This script resets permissions for camera access [1].
|
||||||
It ensures no application can access the system camera without explicit user permission, protecting against unauthorized surveillance and data breaches.
|
It ensures no application can access the system camera without explicit user permission, protecting against unauthorized surveillance and data breaches.
|
||||||
|
|
||||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||||
call:
|
call:
|
||||||
function: ResetServicePermissions
|
function: ResetServicePermissions
|
||||||
parameters:
|
parameters:
|
||||||
@@ -547,7 +547,7 @@ actions:
|
|||||||
This script resets permissions for microphone access [1].
|
This script resets permissions for microphone access [1].
|
||||||
It revokes all granted access to the microphone, protecting against eavesdropping and unauthorized audio recording by applications.
|
It revokes all granted access to the microphone, protecting against eavesdropping and unauthorized audio recording by applications.
|
||||||
|
|
||||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||||
call:
|
call:
|
||||||
function: ResetServicePermissions
|
function: ResetServicePermissions
|
||||||
parameters:
|
parameters:
|
||||||
@@ -558,7 +558,7 @@ actions:
|
|||||||
This script resets permissions for accessibility features [1].
|
This script resets permissions for accessibility features [1].
|
||||||
It revokes application access to accessibility services, preventing misuse and ensuring these features are used only with user consent.
|
It revokes application access to accessibility services, preventing misuse and ensuring these features are used only with user consent.
|
||||||
|
|
||||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||||
call:
|
call:
|
||||||
function: ResetServicePermissions
|
function: ResetServicePermissions
|
||||||
parameters:
|
parameters:
|
||||||
@@ -569,7 +569,7 @@ actions:
|
|||||||
This script resets permissions for screen capture [1].
|
This script resets permissions for screen capture [1].
|
||||||
It ensures applications cannot capture screen content without user authorization, protecting sensitive information displayed on the screen.
|
It ensures applications cannot capture screen content without user authorization, protecting sensitive information displayed on the screen.
|
||||||
|
|
||||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||||
call:
|
call:
|
||||||
function: ResetServicePermissions
|
function: ResetServicePermissions
|
||||||
parameters:
|
parameters:
|
||||||
@@ -580,7 +580,7 @@ actions:
|
|||||||
This script resets permissions for accessing reminders information managed by the Reminders app [1].
|
This script resets permissions for accessing reminders information managed by the Reminders app [1].
|
||||||
It ensures applications cannot access or modify reminders data without explicit user permission, maintaining the privacy of personal reminders.
|
It ensures applications cannot access or modify reminders data without explicit user permission, maintaining the privacy of personal reminders.
|
||||||
|
|
||||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||||
call:
|
call:
|
||||||
function: ResetServicePermissions
|
function: ResetServicePermissions
|
||||||
parameters:
|
parameters:
|
||||||
@@ -591,7 +591,7 @@ actions:
|
|||||||
This script resets permissions for accessing the pictures managed by the Photos app [1].
|
This script resets permissions for accessing the pictures managed by the Photos app [1].
|
||||||
It revokes all permissions granted to applications, safeguarding personal photos and media from unauthorized access.
|
It revokes all permissions granted to applications, safeguarding personal photos and media from unauthorized access.
|
||||||
|
|
||||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||||
call:
|
call:
|
||||||
function: ResetServicePermissions
|
function: ResetServicePermissions
|
||||||
parameters:
|
parameters:
|
||||||
@@ -602,7 +602,7 @@ actions:
|
|||||||
This script resets permissions for accessing the calendar information managed by the Calendar app [1].
|
This script resets permissions for accessing the calendar information managed by the Calendar app [1].
|
||||||
It ensures that applications cannot access calendar data without user consent, protecting personal and sensitive calendar information.
|
It ensures that applications cannot access calendar data without user consent, protecting personal and sensitive calendar information.
|
||||||
|
|
||||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||||
call:
|
call:
|
||||||
function: ResetServicePermissions
|
function: ResetServicePermissions
|
||||||
parameters:
|
parameters:
|
||||||
@@ -614,7 +614,7 @@ actions:
|
|||||||
Full disk access allows the application access to all protected files, including system administration files [1].
|
Full disk access allows the application access to all protected files, including system administration files [1].
|
||||||
It revokes broad file access from applications, significantly reducing the risk of data exposure and enhancing overall system security.
|
It revokes broad file access from applications, significantly reducing the risk of data exposure and enhancing overall system security.
|
||||||
|
|
||||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||||
call:
|
call:
|
||||||
function: ResetServicePermissions
|
function: ResetServicePermissions
|
||||||
parameters:
|
parameters:
|
||||||
@@ -626,7 +626,7 @@ actions:
|
|||||||
The contact information managed by the Contacts app [1].
|
The contact information managed by the Contacts app [1].
|
||||||
It ensures that applications cannot access the user's contact list without explicit permission, maintaining the confidentiality of personal contacts.
|
It ensures that applications cannot access the user's contact list without explicit permission, maintaining the confidentiality of personal contacts.
|
||||||
|
|
||||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||||
call:
|
call:
|
||||||
function: ResetServicePermissions
|
function: ResetServicePermissions
|
||||||
parameters:
|
parameters:
|
||||||
@@ -637,7 +637,7 @@ actions:
|
|||||||
This script resets permissions for accessing the Desktop folder [1].
|
This script resets permissions for accessing the Desktop folder [1].
|
||||||
It revokes application access to files on the desktop, protecting personal and work-related documents from unauthorized access.
|
It revokes application access to files on the desktop, protecting personal and work-related documents from unauthorized access.
|
||||||
|
|
||||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||||
call:
|
call:
|
||||||
function: ResetServicePermissions
|
function: ResetServicePermissions
|
||||||
parameters:
|
parameters:
|
||||||
@@ -648,7 +648,7 @@ actions:
|
|||||||
This script resets permissions for accessing the Documents folder [1].
|
This script resets permissions for accessing the Documents folder [1].
|
||||||
It prevents applications from accessing files in this folder without user consent, safeguarding important and private documents.
|
It prevents applications from accessing files in this folder without user consent, safeguarding important and private documents.
|
||||||
|
|
||||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||||
call:
|
call:
|
||||||
function: ResetServicePermissions
|
function: ResetServicePermissions
|
||||||
parameters:
|
parameters:
|
||||||
@@ -659,7 +659,7 @@ actions:
|
|||||||
This script resets permissions for accessing the Downloads folder [1].
|
This script resets permissions for accessing the Downloads folder [1].
|
||||||
It ensures that applications cannot access downloaded files without user authorization, protecting downloaded content from misuse.
|
It ensures that applications cannot access downloaded files without user authorization, protecting downloaded content from misuse.
|
||||||
|
|
||||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||||
call:
|
call:
|
||||||
function: ResetServicePermissions
|
function: ResetServicePermissions
|
||||||
parameters:
|
parameters:
|
||||||
@@ -670,7 +670,7 @@ actions:
|
|||||||
This script resets permissions for Apple Events [1].
|
This script resets permissions for Apple Events [1].
|
||||||
It revokes permissions for applications to send restricted Apple Events to other processes [1], enhancing privacy and security.
|
It revokes permissions for applications to send restricted Apple Events to other processes [1], enhancing privacy and security.
|
||||||
|
|
||||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||||
call:
|
call:
|
||||||
function: ResetServicePermissions
|
function: ResetServicePermissions
|
||||||
parameters:
|
parameters:
|
||||||
@@ -681,7 +681,7 @@ actions:
|
|||||||
This script resets permissions for File Provider Presence [1].
|
This script resets permissions for File Provider Presence [1].
|
||||||
It revokes the ability of File Provider applications to know when the user is accessing their managed files [1], enhancing user privacy.
|
It revokes the ability of File Provider applications to know when the user is accessing their managed files [1], enhancing user privacy.
|
||||||
|
|
||||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||||
call:
|
call:
|
||||||
function: ResetServicePermissions
|
function: ResetServicePermissions
|
||||||
parameters:
|
parameters:
|
||||||
@@ -692,7 +692,7 @@ actions:
|
|||||||
This script resets "ListenEvent" permissions [1].
|
This script resets "ListenEvent" permissions [1].
|
||||||
It revokes application access to listen to system events [1], preventing unauthorized monitoring of user interactions with the system.
|
It revokes application access to listen to system events [1], preventing unauthorized monitoring of user interactions with the system.
|
||||||
|
|
||||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||||
call:
|
call:
|
||||||
function: ResetServicePermissions
|
function: ResetServicePermissions
|
||||||
parameters:
|
parameters:
|
||||||
@@ -703,7 +703,7 @@ actions:
|
|||||||
This script resets permissions for accessing the Media Library [1].
|
This script resets permissions for accessing the Media Library [1].
|
||||||
It ensures that applications cannot access Apple Music, music and video activity, and the media library [1] without user consent.
|
It ensures that applications cannot access Apple Music, music and video activity, and the media library [1] without user consent.
|
||||||
|
|
||||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||||
call:
|
call:
|
||||||
function: ResetServicePermissions
|
function: ResetServicePermissions
|
||||||
parameters:
|
parameters:
|
||||||
@@ -714,7 +714,7 @@ actions:
|
|||||||
This script resets permissions for sending "PostEvent" [1].
|
This script resets permissions for sending "PostEvent" [1].
|
||||||
It prevents applications from using CoreGraphics APIs to send system events [1], safeguarding against potential misuse.
|
It prevents applications from using CoreGraphics APIs to send system events [1], safeguarding against potential misuse.
|
||||||
|
|
||||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||||
call:
|
call:
|
||||||
function: ResetServicePermissions
|
function: ResetServicePermissions
|
||||||
parameters:
|
parameters:
|
||||||
@@ -726,7 +726,7 @@ actions:
|
|||||||
This script resets permissions for using Speech Recognition [1].
|
This script resets permissions for using Speech Recognition [1].
|
||||||
It revokes application access to the speech recognition facility and sending speech data to Apple [1], protecting user privacy.
|
It revokes application access to the speech recognition facility and sending speech data to Apple [1], protecting user privacy.
|
||||||
|
|
||||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||||
call:
|
call:
|
||||||
function: ResetServicePermissions
|
function: ResetServicePermissions
|
||||||
parameters:
|
parameters:
|
||||||
@@ -737,7 +737,7 @@ actions:
|
|||||||
This script resets permissions for modifying other apps [1].
|
This script resets permissions for modifying other apps [1].
|
||||||
It prevents applications from updating or deleting other apps [1], maintaining system integrity and user control.
|
It prevents applications from updating or deleting other apps [1], maintaining system integrity and user control.
|
||||||
|
|
||||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||||
call:
|
call:
|
||||||
function: ResetServicePermissions
|
function: ResetServicePermissions
|
||||||
parameters:
|
parameters:
|
||||||
@@ -748,7 +748,7 @@ actions:
|
|||||||
This script resets permissions for accessing application data [1].
|
This script resets permissions for accessing application data [1].
|
||||||
It revokes application access to specific application data, enhancing privacy and data security.
|
It revokes application access to specific application data, enhancing privacy and data security.
|
||||||
|
|
||||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||||
call:
|
call:
|
||||||
function: ResetServicePermissions
|
function: ResetServicePermissions
|
||||||
parameters:
|
parameters:
|
||||||
@@ -759,7 +759,7 @@ actions:
|
|||||||
This script resets permissions for accessing files on network volumes [1].
|
This script resets permissions for accessing files on network volumes [1].
|
||||||
It ensures applications cannot access network files without user authorization.
|
It ensures applications cannot access network files without user authorization.
|
||||||
|
|
||||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||||
call:
|
call:
|
||||||
function: ResetServicePermissions
|
function: ResetServicePermissions
|
||||||
parameters:
|
parameters:
|
||||||
@@ -770,7 +770,7 @@ actions:
|
|||||||
This script resets permissions for accessing files on removable volumes [1].
|
This script resets permissions for accessing files on removable volumes [1].
|
||||||
It protects data on external drives from unauthorized application access.
|
It protects data on external drives from unauthorized application access.
|
||||||
|
|
||||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||||
call:
|
call:
|
||||||
function: ResetServicePermissions
|
function: ResetServicePermissions
|
||||||
parameters:
|
parameters:
|
||||||
@@ -781,7 +781,7 @@ actions:
|
|||||||
This script resets permissions for accessing system administration files [1].
|
This script resets permissions for accessing system administration files [1].
|
||||||
It enhances system security by restricting application access to critical system files.
|
It enhances system security by restricting application access to critical system files.
|
||||||
|
|
||||||
[1]: https://archive.ph/2023.11.24-170934/https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol/services "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
[1]: https://archive.ph/26Hlq "PrivacyPreferencesPolicyControl.Services | Apple Developer Documentation | apple.com"
|
||||||
call:
|
call:
|
||||||
function: ResetServicePermissions
|
function: ResetServicePermissions
|
||||||
parameters:
|
parameters:
|
||||||
@@ -877,7 +877,7 @@ actions:
|
|||||||
There is also `WelcomeScreenPromo.PromoOff` setting that's pre-configured to `1` (`no` as
|
There is also `WelcomeScreenPromo.PromoOff` setting that's pre-configured to `1` (`no` as
|
||||||
default). It's undocumented but still kept disabled by this script.
|
default). It's undocumented but still kept disabled by this script.
|
||||||
|
|
||||||
[1]: https://web.archive.org/web/20240314062932/https://forum.parallels.com/threads/unable-to-process-the-upgrade-request.345603/ "Unable to process the upgrade request | Parallels Forums | forum.parallels.com"
|
[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: |-
|
code: |-
|
||||||
defaults write 'com.parallels.Parallels Desktop' 'ProductPromo.ForcePromoOff' -bool yes
|
defaults write 'com.parallels.Parallels Desktop' 'ProductPromo.ForcePromoOff' -bool yes
|
||||||
@@ -988,16 +988,16 @@ actions:
|
|||||||
recommend: strict
|
recommend: strict
|
||||||
docs:
|
docs:
|
||||||
- https://github.com/privacysexy-forks/starter/blob/master/system/siri.sh
|
- https://github.com/privacysexy-forks/starter/blob/master/system/siri.sh
|
||||||
- https://web.archive.org/web/20201002133713/https://machippie.github.io/system/
|
- https://machippie.github.io/system/
|
||||||
code: defaults write com.apple.assistant.backedup 'Use device speaker for TTS' -int 3
|
code: defaults write com.apple.assistant.backedup 'Use device speaker for TTS' -int 3
|
||||||
revertCode: defaults write com.apple.assistant.backedup 'Use device speaker for TTS' -int 2
|
revertCode: defaults write com.apple.assistant.backedup 'Use device speaker for TTS' -int 2
|
||||||
-
|
-
|
||||||
name: Disable Siri services (Siri and assistantd)
|
name: Disable Siri services (Siri and assistantd)
|
||||||
recommend: strict
|
recommend: strict
|
||||||
docs:
|
docs:
|
||||||
- https://web.archive.org/web/20240314060540/https://apple.stackexchange.com/questions/57514/what-is-assistantd
|
- https://apple.stackexchange.com/questions/57514/what-is-assistantd
|
||||||
- https://archive.ph/2024.03.14-055010/https://community.jamf.com/t5/jamf-pro/kill-siri/td-p/171543
|
- https://www.jamf.com/jamf-nation/discussions/22757/kill-siri#responseChild137563
|
||||||
- https://web.archive.org/web/20240314060501/https://apple.stackexchange.com/questions/258816/how-to-completely-disable-siri-on-sierra/370426#370426
|
- https://apple.stackexchange.com/a/370426
|
||||||
# To see status: • `launchctl print-disabled system` • `launchctl print-disabled user/$UID` • `launchctl print-disabled gui/$UID`
|
# To see status: • `launchctl print-disabled system` • `launchctl print-disabled user/$UID` • `launchctl print-disabled gui/$UID`
|
||||||
code: |-
|
code: |-
|
||||||
launchctl disable "user/$UID/com.apple.assistantd"
|
launchctl disable "user/$UID/com.apple.assistantd"
|
||||||
@@ -1021,20 +1021,10 @@ actions:
|
|||||||
fi
|
fi
|
||||||
-
|
-
|
||||||
name: Disable "Do you want to enable Siri?" pop-up
|
name: Disable "Do you want to enable Siri?" pop-up
|
||||||
docs: |-
|
docs:
|
||||||
This script stops the "Enable Siri" pop-up [1] from appearing the first time a user logs into macOS [2].
|
- https://discussions.apple.com/thread/7694127?answerId=30752577022#30752577022
|
||||||
|
- https://windowsreport.com/mac/siri-keeps-popping-up/
|
||||||
Introduced in macOS version 10.12 [2], this pop-up asks, "Do you want to enable Siri?" [1]
|
- https://www.jamf.com/jamf-nation/discussions/21783/disable-siri-setup-assistant-in-macos-sierra#responseChild131588
|
||||||
which could lead to Siri being enabled unintentionally.
|
|
||||||
|
|
||||||
This script configures the `com.apple.SetupAssistant!DidSeeSiriSetup` setting to suppress this pop-up [1] [2] [3] [4].
|
|
||||||
This command tells the system that the Siri setup is complete, preventing the pop-up in future sessions and
|
|
||||||
enhancing privacy by avoiding unintended Siri activation.
|
|
||||||
|
|
||||||
[1]: https://archive.ph/2024.03.14-053325/https://discussions.apple.com/thread/7694127?answerId=30752577022&sortBy=best%2330752577022 "macOS keeps nagging me about enabling Siri - Apple Community | discussions.apple.com"
|
|
||||||
[2]: https://web.archive.org/web/20240314052600/https://derflounder.wordpress.com/2016/09/20/supressing-siri-pop-up-windows-on-macos-sierra/ "Suppressing Siri pop-up windows on macOS Sierra | Der Flounder"
|
|
||||||
[3]: https://web.archive.org/web/20240314052901/https://windowsreport.com/mac/siri-keeps-popping-up/ "Siri keeps popping up on Mac? Here's how to easily fix that • MacTips | windowsreport.com"
|
|
||||||
[4]: https://web.archive.org/web/20240314052247/https://community.jamf.com/t5/jamf-pro/disable-siri-setup-assistant-in-macos-sierra/m-p/205836/highlight/true#M194536 "Solved: Re: Disable Siri setup assistant in macOS Sierra - Jamf Nation Community - 205834 | community.jamf.com"
|
|
||||||
code: defaults write com.apple.SetupAssistant 'DidSeeSiriSetup' -bool True
|
code: defaults write com.apple.SetupAssistant 'DidSeeSiriSetup' -bool True
|
||||||
revertCode: defaults delete com.apple.SetupAssistant 'DidSeeSiriSetup'
|
revertCode: defaults delete com.apple.SetupAssistant 'DidSeeSiriSetup'
|
||||||
-
|
-
|
||||||
@@ -1094,7 +1084,7 @@ actions:
|
|||||||
by default.
|
by default.
|
||||||
|
|
||||||
[1]: https://web.archive.org/web/20230731152633/https://www.apple.com/legal/privacy/data/en/apple-advertising/ "Legal - Apple Advertising & Privacy - Apple"
|
[1]: https://web.archive.org/web/20230731152633/https://www.apple.com/legal/privacy/data/en/apple-advertising/ "Legal - Apple Advertising & Privacy - Apple"
|
||||||
[2]: https://web.archive.org/web/20220805052411/https://support.apple.com/en-sg/guide/mac-help/mh32356/mac "Change Privacy preferences on Mac - Apple Support (SG)"
|
[2]: https://web.archive.org/web/20220805052411/https://support.apple.com/en-sg/guide/mac-help/mh32356/mac: "Change Privacy preferences on Mac - Apple Support (SG)"
|
||||||
[3]: https://web.archive.org/web/20230731155827/https://developer.apple.com/documentation/devicemanagement/restrictions "Restrictions | Apple Developer Documentation"
|
[3]: https://web.archive.org/web/20230731155827/https://developer.apple.com/documentation/devicemanagement/restrictions "Restrictions | Apple Developer Documentation"
|
||||||
[4]: https://web.archive.org/web/20230731155653/https://paper.bobylive.com/Security/CIS/CIS_Apple_macOS_11_0_Big_Sur_Benchmark_v2_0_0.pdf "CIS Apple macOS 11.0 Big Sur Benchmark"
|
[4]: https://web.archive.org/web/20230731155653/https://paper.bobylive.com/Security/CIS/CIS_Apple_macOS_11_0_Big_Sur_Benchmark_v2_0_0.pdf "CIS Apple macOS 11.0 Big Sur Benchmark"
|
||||||
[5]: https://web.archive.org/web/20230731155131/https://developer.apple.com/documentation/adsupport/asidentifiermanager/1614151-advertisingidentifier "advertisingIdentifier | Apple Developer Documentation"
|
[5]: https://web.archive.org/web/20230731155131/https://developer.apple.com/documentation/adsupport/asidentifiermanager/1614151-advertisingidentifier "advertisingIdentifier | Apple Developer Documentation"
|
||||||
@@ -1290,7 +1280,7 @@ actions:
|
|||||||
# OS tracks downloaded files with help of quarantine-aware applications
|
# OS tracks downloaded files with help of quarantine-aware applications
|
||||||
# (such as Safari, Chrome) adding quarantine extended attributes to files.
|
# (such as Safari, Chrome) adding quarantine extended attributes to files.
|
||||||
# then OS warns and asks if you really want to open it
|
# then OS warns and asks if you really want to open it
|
||||||
docs: https://web.archive.org/web/20210319081714/https://support.apple.com/en-gb/HT202491
|
docs: https://support.apple.com/en-gb/HT202491
|
||||||
children:
|
children:
|
||||||
-
|
-
|
||||||
category: Clean File Quarantine from downloaded files
|
category: Clean File Quarantine from downloaded files
|
||||||
@@ -1401,7 +1391,7 @@ actions:
|
|||||||
name: Disable Gatekeeper's automatic reactivation
|
name: Disable Gatekeeper's automatic reactivation
|
||||||
docs:
|
docs:
|
||||||
- https://osxdaily.com/2015/11/05/stop-gatekeeper-auto-rearm-mac-os-x/
|
- https://osxdaily.com/2015/11/05/stop-gatekeeper-auto-rearm-mac-os-x/
|
||||||
- https://web.archive.org/web/20230327050142/https://www.cnet.com/tech/computing/how-to-disable-gatekeeper-permanently-on-os-x/
|
- https://www.cnet.com/tech/computing/how-to-disable-gatekeeper-permanently-on-os-x/
|
||||||
code: sudo defaults write /Library/Preferences/com.apple.security GKAutoRearm -bool true
|
code: sudo defaults write /Library/Preferences/com.apple.security GKAutoRearm -bool true
|
||||||
revertCode: sudo defaults write /Library/Preferences/com.apple.security GKAutoRearm -bool false
|
revertCode: sudo defaults write /Library/Preferences/com.apple.security GKAutoRearm -bool false
|
||||||
-
|
-
|
||||||
@@ -1460,19 +1450,13 @@ actions:
|
|||||||
revertCode: sudo defaults write /Library/Preferences/com.apple.security.libraryvalidation.plist 'DisableLibraryValidation' -bool false
|
revertCode: sudo defaults write /Library/Preferences/com.apple.security.libraryvalidation.plist 'DisableLibraryValidation' -bool false
|
||||||
-
|
-
|
||||||
category: Disable automatic updates
|
category: Disable automatic updates
|
||||||
docs: |-
|
docs:
|
||||||
This category contains scripts to disable automatic operating system updates.
|
- https://developer.apple.com/documentation/devicemanagement/deviceinformationresponse/queryresponses/osupdatesettings
|
||||||
|
- https://macadminsdoc.readthedocs.io/en/master/Profiles-and-Settings/OS-X-Updates.html
|
||||||
Disabling automatic updates gives users full control over when and which updates are applied to their system.
|
|
||||||
It improves privacy by preventing unwanted data collection, new vulnerabilities and unapproved changes to system settings.
|
|
||||||
|
|
||||||
> **Caution**:
|
|
||||||
> Disabling automatic updates can leave your system vulnerable to unpatched exploits.
|
|
||||||
> Manually check and and apply updates to stay protected.
|
|
||||||
children:
|
children:
|
||||||
-
|
-
|
||||||
name: Disable automatic checks for updates
|
name: Disable automatic checks for updates
|
||||||
docs: https://archive.ph/2024.03.21-180353/https://developer.apple.com/documentation/devicemanagement/softwareupdate
|
docs: https://developer.apple.com/documentation/devicemanagement/softwareupdate
|
||||||
code: |-
|
code: |-
|
||||||
# For OS X Yosemite and newer (>= 10.10)
|
# For OS X Yosemite and newer (>= 10.10)
|
||||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticCheckEnabled' -bool false
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticCheckEnabled' -bool false
|
||||||
@@ -1481,7 +1465,7 @@ actions:
|
|||||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticCheckEnabled' -bool true
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticCheckEnabled' -bool true
|
||||||
-
|
-
|
||||||
name: Disable automatic downloads for updates
|
name: Disable automatic downloads for updates
|
||||||
docs: https://archive.ph/2024.03.21-180353/https://developer.apple.com/documentation/devicemanagement/softwareupdate
|
docs: https://developer.apple.com/documentation/devicemanagement/softwareupdate
|
||||||
code: |-
|
code: |-
|
||||||
# For OS X Yosemite and newer (>= 10.10)
|
# For OS X Yosemite and newer (>= 10.10)
|
||||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticDownload' -bool false
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticDownload' -bool false
|
||||||
@@ -1490,41 +1474,12 @@ actions:
|
|||||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticDownload' -bool true
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticDownload' -bool true
|
||||||
-
|
-
|
||||||
name: Disable automatic installation of macOS updates
|
name: Disable automatic installation of macOS updates
|
||||||
docs: |-
|
docs:
|
||||||
This script stops macOS from automatically installing updates.
|
# References for AutoUpdateRestartRequired
|
||||||
|
- https://kb.vmware.com/s/article/2960635
|
||||||
This script improves privacy by reducing unwanted data collection and ensuring updates don't change
|
- https://derflounder.wordpress.com/2018/12/28/enabling-automatic-macos-software-updates-for-os-x-yosemite-through-macos-mojave/
|
||||||
settings or data without your approval.
|
# References for AutomaticallyInstallMacOSUpdates
|
||||||
|
- https://developer.apple.com/documentation/devicemanagement/softwareupdate
|
||||||
The Center for Internet Security (CIS) advises against automatic updates in scenarios where changes require
|
|
||||||
thorough testing and approval processes to avoid operational disruptions [1] [2] [3] [4].
|
|
||||||
|
|
||||||
This script configures following to stop macOS from installing updates automatically:
|
|
||||||
|
|
||||||
1. `/Library/Preferences/com.apple.commerce!AutoUpdateRestartRequired`:
|
|
||||||
This preference stops the system from automatically installing macOS updates [1] [2] [3] [4] [5] [6] [7] [8].
|
|
||||||
By doing this, updates will only be installed when you decide, giving you a chance to check them first [1] [2] [3] [4] [5] [6] [7] [8].
|
|
||||||
This setting applies to OS X Yosemite through macOS High Sierra [7] [9].
|
|
||||||
|
|
||||||
2. `/Library/Preferences/com.apple.commerce!AutomaticallyInstallMacOSUpdates`:
|
|
||||||
Changing this setting stops macOS from installing updates automatically [3] [5] [9] [10], giving you control over when to update.
|
|
||||||
If restricts the *Install macOS Updates* option and prevents the user from changing the option [10].
|
|
||||||
While this setting enhances privacy, it's generally not advised by NIST due to potential security risks [9].
|
|
||||||
This setting applies to macOS Mojave and newer versions [9].
|
|
||||||
|
|
||||||
> **Caution**: Disabling automatic updates requires you to manually check and apply updates to stay protected against security threats [1] [2] [3] [4].
|
|
||||||
|
|
||||||
[1]: https://web.archive.org/web/20240321165149/https://www.tenable.com/audits/items/CIS_Apple_macOS_10.12_v1.1.0_Level_1.audit:e02dfdd6bec9556a3ce537f60b91b549 "CIS Apple macOS 10.12 L1 v1.1.0 | 1.5 Enable OS X update installs | Tenable®"
|
|
||||||
[2]: https://web.archive.org/web/20240321165851/https://paper.bobylive.com/Security/CIS/CIS_Apple_macOS_10_13_Benchmark_v1_1_0---PDF.pdf "CIS Apple macOS 10.13 Benchmark v1.1.0 | paper.bobylive.com"
|
|
||||||
[3]: https://web.archive.org/web/20240321170400/https://www.tenable.com/audits/items/CIS_Apple_macOS_13.0_Ventura_v1.0.0_L1.audit:fe03c59a39c7c949507ff20d07f89993 "1.4 Ensure Install of macOS Updates Is Enabled | Tenable® | www.tenable.com"
|
|
||||||
[4]: https://web.archive.org/web/20240321170036/https://paper.bobylive.com/Security/CIS/CIS_Apple_macOS_10_14_Benchmark_v1_4_0_PDF.pdf "CIS Apple macOS 10.14 Benchmark v1.4.0 | paper.bobylive.com"
|
|
||||||
[5]: https://web.archive.org/web/20240321164917/https://www.ncsc.gov.uk/files/macos_provisioning_script.sh_.txt "macOS provisioning script | UK National Cyber Security Centre | www.ncsc.gov.uk"
|
|
||||||
[6]: https://web.archive.org/web/20240321165118/https://macadminsdoc.readthedocs.io/en/master/Profiles-and-Settings/OS-X-Updates.html "macOS Updates — MacAdmins Community Documentation documentation | macadminsdoc.readthedocs.io"
|
|
||||||
[7]: https://web.archive.org/web/20240321165304/https://derflounder.wordpress.com/2014/12/29/managing-automatic-app-store-and-os-x-update-installation-on-yosemite/ "Managing automatic App Store and OS X update installation on Yosemite | Der Flounder | derflounder.wordpress.com"
|
|
||||||
[8]: https://web.archive.org/web/20240321170034/https://krypted.com/mac-os-x/app-store-preferences-set-server-5-4-macos-high-sierra/ "App Store Preferences To Set In On Server 5.4 for macOS High Sierra – krypted | krypted.com"
|
|
||||||
[9]: https://web.archive.org/web/20240321170251/https://derflounder.wordpress.com/2018/12/28/enabling-automatic-macos-software-updates-for-os-x-yosemite-through-macos-mojave/ "Enabling automatic macOS software updates for OS X Yosemite through macOS Mojave | Der Flounder | derflounder.wordpress.com"
|
|
||||||
[10]: https://archive.ph/2024.03.21-180353/https://developer.apple.com/documentation/devicemanagement/softwareupdate "SoftwareUpdate | Apple Developer Documentation | developer.apple.com"
|
|
||||||
[11]: https://web.archive.org/web/20240321165931/https://csrc.nist.gov/CSRC/media/Projects/national-vulnerability-database/documents/CCE/CCE-macos_monterey.xls "CCE-91129-7 | CCE-macos_monterey.xls | Sheet 1 - NIST Computer Security Resource Center | csrc.nist.gov"
|
|
||||||
code: |-
|
code: |-
|
||||||
# For OS X Yosemite through macOS High Sierra (>= 10.10 && < 10.14)
|
# For OS X Yosemite through macOS High Sierra (>= 10.10 && < 10.14)
|
||||||
sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdateRestartRequired' -bool false
|
sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdateRestartRequired' -bool false
|
||||||
@@ -1537,44 +1492,9 @@ actions:
|
|||||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallMacOSUpdates' -bool true
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallMacOSUpdates' -bool true
|
||||||
-
|
-
|
||||||
name: Disable automatic app updates from the App Store
|
name: Disable automatic app updates from the App Store
|
||||||
docs: |-
|
docs:
|
||||||
This script disables automatic app updates [1] [2] [3] [4] from the App Store [5] [6] [7] [8] [9] [10] [11] [12] [13].
|
- https://kb.vmware.com/s/article/2960635
|
||||||
It prevents automatic installation of application updates as soon as they become available from Apple [2] [3] [6] [9] [11] [12] [13].
|
- https://derflounder.wordpress.com/2018/12/28/enabling-automatic-macos-software-updates-for-os-x-yosemite-through-macos-mojave/
|
||||||
Thus, applications are updated only when you choose to do so [5].
|
|
||||||
|
|
||||||
Disabling automatic updates prevents unexpected app behavior or settings changes.
|
|
||||||
It helps you to maintain your current app configurations and privacy settings.
|
|
||||||
It also protects against potential zero-day vulnerabilities in your apps.
|
|
||||||
This gives you the ability to choose which updates to install and when, enabling you to review the details of updates before deciding to proceed.
|
|
||||||
|
|
||||||
The script modifies the following settings:
|
|
||||||
|
|
||||||
1. `/Library/Preferences/com.apple.commerce!AutoUpdate`:
|
|
||||||
Disables automated app updates [1] [2] [3] [6] [9] [10] [13] from the App Store [7] [8].
|
|
||||||
This setting applies to OS X Yosemite and newer versions [1].
|
|
||||||
2. `/Library/Preferences/com.apple.SoftwareUpdate!AutomaticallyInstallAppUpdates`:
|
|
||||||
Stops the automatic installation of app updates [1] [4] from App Store [9] [10] [11] [12] [13].
|
|
||||||
It deselects the *Install app updates from the App Store* option and prevents the user from changing the option [10].
|
|
||||||
While this setting enhances privacy, it's generally not advised by NIST due to potential security risks [4].
|
|
||||||
This setting applies to macOS Mojave and newer versions [1].
|
|
||||||
|
|
||||||
> **Caution**:
|
|
||||||
> Disabling app updates means you should manually check for and install important security patches for every application
|
|
||||||
> to protect against vulnerabilities [2] [3] [5] [6] [9] [11] [12] [13].
|
|
||||||
|
|
||||||
[1]: https://web.archive.org/web/20240321170251/https://derflounder.wordpress.com/2018/12/28/enabling-automatic-macos-software-updates-for-os-x-yosemite-through-macos-mojave/ "Enabling automatic macOS software updates for OS X Yosemite through macOS Mojave | Der Flounder | derflounder.wordpress.com"
|
|
||||||
[2]: https://web.archive.org/web/20240321190032/https://www.irs.gov/pub/irs-utl/safeguards-scsem-macosx-v6-1-093021.xlsx "SCSEM OSX 10.14 | Internal Revenue Service Office of Safeguards | www.irs.gov"
|
|
||||||
[3]: https://web.archive.org/web/20240321170036/https://paper.bobylive.com/Security/CIS/CIS_Apple_macOS_10_14_Benchmark_v1_4_0_PDF.pdf "CIS Apple macOS 10.14 Benchmark v1.4.0 | paper.bobylive.com"
|
|
||||||
[5]: https://web.archive.org/web/20240321190244/https://github-wiki-see.page/m/edamametechnologies/threatmodels/wiki/threatmodel-macOS-EN "threatmodel macOS EN - edamametechnologies/threatmodels GitHub Wiki | github-wiki-see.page"
|
|
||||||
[6]: https://web.archive.org/web/20240321190315/https://www.tenable.com/audits/items/CIS_Apple_macOS_14.0_Sonoma_v1.0.0_L1.audit:66d3b86318384ba7947a3409e0c6e902 "1.5 Ensure Install Application Updates from the App Store Is E... | Tenable® | www.tenable.com"
|
|
||||||
[7]: https://web.archive.org/web/20240321165304/https://derflounder.wordpress.com/2014/12/29/managing-automatic-app-store-and-os-x-update-installation-on-yosemite/ "Managing automatic App Store and OS X update installation on Yosemite | Der Flounder | derflounder.wordpress.com"
|
|
||||||
[8]: https://web.archive.org/web/20240321190410/https://krypted.com/mac-security/app-store-preferences-set-server-5-2-macos-sierra/ "App Store Preferences To Set In On Server 5.2 for macOS Sierra – krypted | krypted.com"
|
|
||||||
[4]: https://web.archive.org/web/20240321165931/https://csrc.nist.gov/CSRC/media/Projects/national-vulnerability-database/documents/CCE/CCE-macos_monterey.xls "CCE-91129-7 | CCE-macos_monterey.xls | Sheet 1 - NIST Computer Security Resource Center | csrc.nist.gov"
|
|
||||||
[9]: https://web.archive.org/web/20240321190114/https://www.irs.gov/pub/irs-utl/safeguards-scsem-macosx.xlsx "SCSEM OSX 13.0 | Internal Revenue Service Office of Safeguards | www.irs.gov"
|
|
||||||
[10]: https://archive.ph/2024.03.21-180353/https://developer.apple.com/documentation/devicemanagement/softwareupdate "SoftwareUpdate | Apple Developer Documentation | developer.apple.com"
|
|
||||||
[11]: https://web.archive.org/web/20240321190122/https://paper.bobylive.com/Security/CIS/CIS_Apple_macOS_12_0_Monterey_Benchmark_v1_0_0.pdf "CIS Apple macOS 12.0 Monterey | CIS Benchmarks | paper.bobylive.com"
|
|
||||||
[12]: https://web.archive.org/web/20240321190537/https://www.tenable.com/audits/items/CIS_Apple_macOS_11_v2.0.0_L1.audit:55e8759872dce781b8dbc5a3f42e23b9 "1.4 Ensure Installation of App Update Is Enabled | Tenable® | www.tenable.com"
|
|
||||||
[13]: https://web.archive.org/web/20240321164917/https://www.ncsc.gov.uk/files/macos_provisioning_script.sh_.txt "macOS provisioning script | UK National Cyber Security Centre | www.ncsc.gov.uk"
|
|
||||||
code: |-
|
code: |-
|
||||||
# For OS X Yosemite and newer (>= 10.10)
|
# For OS X Yosemite and newer (>= 10.10)
|
||||||
sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdate' -bool false
|
sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdate' -bool false
|
||||||
@@ -1587,7 +1507,7 @@ actions:
|
|||||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallAppUpdates' -bool true
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallAppUpdates' -bool true
|
||||||
-
|
-
|
||||||
name: Disable macOS beta release installation
|
name: Disable macOS beta release installation
|
||||||
docs: https://web.archive.org/web/20170106103856/https://support.apple.com/en-gb/HT203018
|
docs: https://support.apple.com/en-gb/HT203018
|
||||||
code: |-
|
code: |-
|
||||||
# For OS X Yosemite and newer (>= 10.10)
|
# For OS X Yosemite and newer (>= 10.10)
|
||||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AllowPreReleaseInstallation' -bool false
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AllowPreReleaseInstallation' -bool false
|
||||||
@@ -1596,7 +1516,7 @@ actions:
|
|||||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AllowPreReleaseInstallation' -bool true
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AllowPreReleaseInstallation' -bool true
|
||||||
-
|
-
|
||||||
name: Disable automatic installation for configuration data (e.g. XProtect, Gatekeeper, MRT)
|
name: Disable automatic installation for configuration data (e.g. XProtect, Gatekeeper, MRT)
|
||||||
docs: https://web.archive.org/web/20240321170251/https://derflounder.wordpress.com/2018/12/28/enabling-automatic-macos-software-updates-for-os-x-yosemite-through-macos-mojave/
|
docs: https://derflounder.wordpress.com/2018/12/28/enabling-automatic-macos-software-updates-for-os-x-yosemite-through-macos-mojave/
|
||||||
code: |-
|
code: |-
|
||||||
# For OS X Yosemite and newer (>= 10.10)
|
# For OS X Yosemite and newer (>= 10.10)
|
||||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'ConfigDataInstall' -bool false
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'ConfigDataInstall' -bool false
|
||||||
@@ -1605,47 +1525,12 @@ actions:
|
|||||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'ConfigDataInstall' -bool true
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'ConfigDataInstall' -bool true
|
||||||
-
|
-
|
||||||
name: Disable automatic installation for system data files and security updates
|
name: Disable automatic installation for system data files and security updates
|
||||||
docs: |-
|
docs:
|
||||||
This script stops automatic installations of critical updates [1],
|
# References for CriticalUpdateInstall
|
||||||
including security [1] [2] [3] [4] [5] [6] [7] and system data file [1] [8] updates.
|
- https://derflounder.wordpress.com/2014/12/24/managing-os-xs-automatic-security-updates/
|
||||||
|
- https://developer.apple.com/documentation/devicemanagement/softwareupdate
|
||||||
It improves privacy by providing:
|
# References for softwareupdate --background-critical
|
||||||
|
- https://managingosx.wordpress.com/2013/04/30/undocumented-options/
|
||||||
- **Control Over Update Timing**:
|
|
||||||
Users can review updates before installation to ensure they meet privacy standards and do not introduce
|
|
||||||
unwanted telemetry or changes.
|
|
||||||
- **Reduced External Communications**:
|
|
||||||
Reduces how often it connects to update servers, potentially protection user information.
|
|
||||||
|
|
||||||
The script configures the `/Library/Preferences/com.apple.SoftwareUpdate!CriticalUpdateInstall` setting [1] [4] [5] [7] [8].
|
|
||||||
This action prevents automatic downloads and installations of updates [1].
|
|
||||||
It also prevents users from changing the Install system data files and security updates option manually [1].
|
|
||||||
This script is compatible with OS X Yosemite and later versions [6] [8].
|
|
||||||
|
|
||||||
The revert script triggers `softwareupdate --background-critical` to install any pending critical updates directly [2] [9].
|
|
||||||
|
|
||||||
> **Caution:**
|
|
||||||
> Only disable automatic updates if you're committed to manually installing them quickly to maintain your computer's security [4] [5] [8].
|
|
||||||
> It's important to install updates soon to protect your computer. [4] [5] [8].
|
|
||||||
>
|
|
||||||
> This script disables:
|
|
||||||
>
|
|
||||||
> - Definition updates for **XProtect** and **Gatekeeper** that keep your computer safe from new threats [5].
|
|
||||||
> - **Rapid Security Response** [10] [11].
|
|
||||||
> **Rapid Security Responses** are software releases providing important security improvements between standard updates [12].
|
|
||||||
|
|
||||||
[1]: https://archive.ph/2024.03.21-180353/https://developer.apple.com/documentation/devicemanagement/softwareupdate "SoftwareUpdate | Apple Developer Documentation | developer.apple.com"
|
|
||||||
[2]: https://web.archive.org/web/20240321201417/https://derflounder.wordpress.com/2014/12/24/managing-os-xs-automatic-security-updates/ "Managing OS X’s automatic security updates | Der Flounder | derflounder.wordpress.com"
|
|
||||||
[3]: https://web.archive.org/web/20240321165118/https://macadminsdoc.readthedocs.io/en/master/Profiles-and-Settings/OS-X-Updates.html "macOS Updates — MacAdmins Community Documentation documentation | macadminsdoc.readthedocs.io"
|
|
||||||
[4]: https://web.archive.org/web/20240321165931/https://csrc.nist.gov/CSRC/media/Projects/national-vulnerability-database/documents/CCE/CCE-macos_monterey.xls "CCE-91129-7 | CCE-macos_monterey.xls | Sheet 1 - NIST Computer Security Resource Center | csrc.nist.gov"
|
|
||||||
[5]: https://web.archive.org/web/20240321201450/https://paper.bobylive.com/Security/CIS/CIS_Apple_OSX_10_9_Benchmark_v1_3_0.pdf "CIS Apple OSX 10.9 Benchmark | paper.bobylive.com"
|
|
||||||
[6]: https://web.archive.org/web/20240321201643/https://derflounder.wordpress.com/2014/12/27/managing-automatic-installation-of-configdata-and-security-software-updates-on-yosemite/ "Managing automatic installation of ConfigData and security software updates on Yosemite | Der Flounder | derflounder.wordpress.com"
|
|
||||||
[7]: https://web.archive.org/web/20240321201652/https://ss64.com/mac/syntax-defaults.html "System preference settings for macOS - macOS - SS64.com | ss64.com"
|
|
||||||
[8]: https://web.archive.org/web/20240321201436/https://www.tenable.com/audits/items/CIS_OSX_10.10_v1.2.0_L1.audit:97f36c2eaa06045e85a1beff1a76a088 "1.4 Enable system data files and security update installs - 'C... | Tenable® | www.tenable.com"
|
|
||||||
[9]: https://web.archive.org/web/20240321201406/https://managingosx.wordpress.com/2013/04/30/undocumented-options/ "Undocumented options – Managing OS X | managingosx.wordpress.com"
|
|
||||||
[10]: https://web.archive.org/web/20240321201558/https://www.intuneirl.com/rapid-security-response/ "Managing Rapid Security Response on Apple Devices | www.intuneirl.com"
|
|
||||||
[11]: https://web.archive.org/web/20240321201614/https://onsitegroup.co.za/rapid-security-response/ "Rapid security response - Onsite | onsitegroup.co.za"
|
|
||||||
[12]: https://web.archive.org/web/20240321201623/https://support.apple.com/en-us/102657 "About Rapid Security Responses for iOS, iPadOS, and macOS - Apple Support | support.apple.com"
|
|
||||||
code: |-
|
code: |-
|
||||||
# For OS X Yosemite and newer (>= 10.10)
|
# For OS X Yosemite and newer (>= 10.10)
|
||||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'CriticalUpdateInstall' -bool false
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'CriticalUpdateInstall' -bool false
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -128,8 +128,3 @@
|
|||||||
$calculated-width-in-em: calc(#{$estimated-width-per-character-in-em} * #{$value-in-ch});
|
$calculated-width-in-em: calc(#{$estimated-width-per-character-in-em} * #{$value-in-ch});
|
||||||
#{$property}: $calculated-width-in-em;
|
#{$property}: $calculated-width-in-em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin base-font-style {
|
|
||||||
font-family: $font-family-main;
|
|
||||||
font-size: $font-size-absolute-normal;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
// Use for fixed-size elements where consistent spacing is important
|
|
||||||
// regardless of context.
|
|
||||||
$spacing-absolute-xx-small: 3px;
|
|
||||||
$spacing-absolute-x-small : 4px;
|
|
||||||
$spacing-absolute-small : 6px;
|
|
||||||
$spacing-absolute-medium : 10px;
|
|
||||||
$spacing-absolute-large : 15px;
|
|
||||||
$spacing-absolute-x-large : 20px;
|
|
||||||
$spacing-absolute-xx-large: 30px;
|
|
||||||
|
|
||||||
// Use for elements with text content where spacing should
|
|
||||||
// scale with text size.
|
|
||||||
$spacing-relative-x-small : 0.25em;
|
|
||||||
$spacing-relative-small : 0.5em;
|
|
||||||
$spacing-relative-medium : 1em;
|
|
||||||
$spacing-relative-large : 2em;
|
|
||||||
@@ -5,23 +5,26 @@
|
|||||||
CSS Base applies a style foundation for HTML elements that is consistent for baseline browsers
|
CSS Base applies a style foundation for HTML elements that is consistent for baseline browsers
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@use "../colors" as *;
|
@use "@/presentation/assets/styles/colors" as *;
|
||||||
@use "../mixins" as *;
|
@use "@/presentation/assets/styles/mixins" as *;
|
||||||
@use "../vite-path" as *;
|
@use "@/presentation/assets/styles/vite-path" as *;
|
||||||
@use "../typography" as *;
|
@use "@/presentation/assets/styles/typography" as *;
|
||||||
@use "../spacing" as *;
|
|
||||||
@use "_code-styling" as *;
|
@use "_code-styling" as *;
|
||||||
@use "_margin-padding" as *;
|
@use "_margin-padding" as *;
|
||||||
@use "_link-styling" as *;
|
@use "_link-styling" as *;
|
||||||
|
|
||||||
|
$base-spacing: 1em;
|
||||||
|
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: $color-background;
|
background: $color-background;
|
||||||
@include base-font-style;
|
font-family: $font-family-main;
|
||||||
@include apply-uniform-spacing;
|
font-size: $font-size-absolute-normal;
|
||||||
|
|
||||||
|
@include apply-uniform-spacing($base-spacing: $base-spacing)
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
@@ -29,12 +32,12 @@ input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
blockquote {
|
blockquote {
|
||||||
padding: 0 $spacing-relative-medium;
|
padding: 0 $base-spacing;
|
||||||
border-left: $spacing-absolute-x-small solid $color-primary;
|
border-left: .25em solid $color-primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include style-code-elements(
|
@include style-code-elements(
|
||||||
$code-block-padding: $spacing-relative-medium,
|
$code-block-padding: $base-spacing,
|
||||||
$color-background: $color-primary-darker,
|
$color-background: $color-primary-darker,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
@use 'sass:math';
|
@use 'sass:math';
|
||||||
@use "../spacing" as *;
|
|
||||||
|
|
||||||
@mixin no-margin($selectors) {
|
@mixin no-margin($selectors) {
|
||||||
#{$selectors} {
|
#{$selectors} {
|
||||||
@@ -27,7 +26,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin apply-uniform-vertical-spacing {
|
@mixin apply-uniform-vertical-spacing($base-vertical-spacing) {
|
||||||
/* Reset default top/bottom margins added by browser. */
|
/* Reset default top/bottom margins added by browser. */
|
||||||
@include no-margin('p');
|
@include no-margin('p');
|
||||||
@include no-margin('h1, h2, h3, h4, h5, h6');
|
@include no-margin('h1, h2, h3, h4, h5, h6');
|
||||||
@@ -37,27 +36,29 @@
|
|||||||
@include no-margin('ul, ol');
|
@include no-margin('ul, ol');
|
||||||
|
|
||||||
/* Add spacing between elements using `margin-bottom` only (bottom-up instead of top-down strategy). */
|
/* Add spacing between elements using `margin-bottom` only (bottom-up instead of top-down strategy). */
|
||||||
@include bottom-margin('p', $spacing-relative-medium);
|
$small-vertical-spacing: math.div($base-vertical-spacing, 2);
|
||||||
@include bottom-margin('li > p', $spacing-relative-small); // Reduce margin for paragraphs directly within list items to visually group related content.
|
@include bottom-margin('p', $base-vertical-spacing);
|
||||||
@include bottom-margin('h1, h2, h3, h4, h5, h6', $spacing-relative-small);
|
@include bottom-margin('li > p', $small-vertical-spacing); // Reduce margin for paragraphs directly within list items to visually group related content.
|
||||||
@include bottom-margin('ul, ol', $spacing-relative-medium);
|
@include bottom-margin('h1, h2, h3, h4, h5, h6', $small-vertical-spacing);
|
||||||
@include bottom-margin('li', $spacing-relative-small);
|
@include bottom-margin('ul, ol', $base-vertical-spacing);
|
||||||
@include bottom-margin('table', $spacing-relative-medium);
|
@include bottom-margin('li', $small-vertical-spacing);
|
||||||
@include bottom-margin('blockquote', $spacing-relative-medium);
|
@include bottom-margin('table', $base-vertical-spacing);
|
||||||
@include bottom-margin('pre', $spacing-relative-medium);
|
@include bottom-margin('blockquote', $base-vertical-spacing);
|
||||||
@include bottom-margin('article', $spacing-relative-medium);
|
@include bottom-margin('pre', $base-vertical-spacing);
|
||||||
@include bottom-margin('hr', $spacing-relative-medium);
|
@include bottom-margin('article', $base-vertical-spacing);
|
||||||
|
@include bottom-margin('hr', $base-vertical-spacing);
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin apply-uniform-horizontal-spacing {
|
@mixin apply-uniform-horizontal-spacing($base-horizontal-spacing) {
|
||||||
/* Reset default left/right paddings added by browser. */
|
/* Reset default left/right paddings added by browser. */
|
||||||
@include no-padding('ul, ol');
|
@include no-padding('ul, ol');
|
||||||
|
|
||||||
/* Add spacing for list items. */
|
/* Add spacing for list items. */
|
||||||
@include left-padding('ul, ol', $spacing-relative-large);
|
$large-horizontal-spacing: $base-horizontal-spacing * 2;
|
||||||
|
@include left-padding('ul, ol', $large-horizontal-spacing);
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin apply-uniform-spacing {
|
@mixin apply-uniform-spacing($base-spacing) {
|
||||||
@include apply-uniform-vertical-spacing;
|
@include apply-uniform-vertical-spacing($base-spacing);
|
||||||
@include apply-uniform-horizontal-spacing;
|
@include apply-uniform-horizontal-spacing($base-spacing);
|
||||||
}
|
}
|
||||||
|
|||||||
1
src/presentation/assets/styles/components/_card.scss
Normal file
1
src/presentation/assets/styles/components/_card.scss
Normal file
@@ -0,0 +1 @@
|
|||||||
|
$card-gap: 15px;
|
||||||
@@ -5,5 +5,6 @@
|
|||||||
@forward "./media";
|
@forward "./media";
|
||||||
@forward "./colors";
|
@forward "./colors";
|
||||||
@forward "./base";
|
@forward "./base";
|
||||||
@forward "./spacing";
|
|
||||||
@forward "./mixins";
|
@forward "./mixins";
|
||||||
|
|
||||||
|
@forward "./components/card";
|
||||||
|
|||||||
@@ -50,48 +50,24 @@ function getOptionalDevToolkitComponent(): Component | undefined {
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@use "@/presentation/assets/styles/main" as *;
|
@use "@/presentation/assets/styles/main" as *;
|
||||||
@use 'sass:math';
|
|
||||||
|
|
||||||
@mixin responsive-spacing {
|
|
||||||
// Avoid using percentage-based values for spacing the avoid unintended layout shifts.
|
|
||||||
margin-left: $spacing-absolute-medium;
|
|
||||||
margin-right: $spacing-absolute-medium;
|
|
||||||
padding: $spacing-absolute-xx-large;
|
|
||||||
@media screen and (max-width: $media-screen-big-width) {
|
|
||||||
margin-left: $spacing-absolute-small;
|
|
||||||
margin-right: $spacing-absolute-small;
|
|
||||||
padding: $spacing-absolute-x-large;
|
|
||||||
}
|
|
||||||
@media screen and (max-width: $media-screen-medium-width) {
|
|
||||||
margin-left: $spacing-absolute-x-small;
|
|
||||||
margin-right: $spacing-absolute-x-small;
|
|
||||||
padding: $spacing-absolute-medium;
|
|
||||||
}
|
|
||||||
@media screen and (max-width: $media-screen-small-width) {
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: 0;
|
|
||||||
padding: $spacing-absolute-small;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
max-width: 1600px;
|
max-width: 1600px;
|
||||||
.app__wrapper {
|
.app__wrapper {
|
||||||
|
margin: 0% 2% 0% 2%;
|
||||||
background-color: $color-surface;
|
background-color: $color-surface;
|
||||||
color: $color-on-surface;
|
color: $color-on-surface;
|
||||||
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.06);
|
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.06);
|
||||||
|
padding: 2%;
|
||||||
@include responsive-spacing;
|
|
||||||
|
|
||||||
display:flex;
|
display:flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
.app__row {
|
.app__row {
|
||||||
margin-bottom: $spacing-absolute-large;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
.app__code-buttons {
|
.app__code-buttons {
|
||||||
padding-bottom: $spacing-absolute-medium;
|
padding-bottom: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ export default defineComponent({
|
|||||||
color: $color-on-secondary;
|
color: $color-on-secondary;
|
||||||
|
|
||||||
border: none;
|
border: none;
|
||||||
|
padding: 20px;
|
||||||
transition-duration: 0.4s;
|
transition-duration: 0.4s;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: 0 3px 9px $color-primary-darkest;
|
box-shadow: 0 3px 9px $color-primary-darkest;
|
||||||
|
|||||||
@@ -54,14 +54,14 @@ export default defineComponent({
|
|||||||
|
|
||||||
.copyable-command {
|
.copyable-command {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
padding: $spacing-relative-x-small;
|
padding: 0.25em;
|
||||||
font-size: $font-size-absolute-small;
|
font-size: $font-size-absolute-small;
|
||||||
.dollar {
|
.dollar {
|
||||||
margin-right: $spacing-relative-small;
|
margin-right: 0.5rem;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
.copy-action-container {
|
.copy-action-container {
|
||||||
margin-left: $spacing-relative-medium;
|
margin-left: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
// Empty component for ESLint compatibility, workaround for https://github.com/vuejs/vue-eslint-parser/issues/125.
|
name: 'InstructionSteps', // Define component name for empty component for Vue build and ESLint compatibility.
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,6 @@
|
|||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
// Empty component for ESLint compatibility, workaround for https://github.com/vuejs/vue-eslint-parser/issues/125.
|
name: 'InstructionSteps', // Define component name for empty component for Vue build and ESLint compatibility.
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -34,15 +34,12 @@ export default defineComponent({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@use "@/presentation/assets/styles/main" as *;
|
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: $spacing-absolute-xx-large;
|
gap: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.code-button {
|
.code-button {
|
||||||
width: 10%;
|
width: 10%;
|
||||||
min-width: 90px;
|
min-width: 90px;
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ interface DevAction {
|
|||||||
right: 0;
|
right: 0;
|
||||||
background-color: rgba($color-on-surface, 0.5);
|
background-color: rgba($color-on-surface, 0.5);
|
||||||
color: $color-on-primary;
|
color: $color-on-primary;
|
||||||
padding: $spacing-absolute-medium;
|
padding: 10px;
|
||||||
z-index: 10000;
|
z-index: 10000;
|
||||||
|
|
||||||
display:flex;
|
display:flex;
|
||||||
@@ -113,14 +113,14 @@ interface DevAction {
|
|||||||
.action-buttons {
|
.action-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: $spacing-absolute-medium;
|
gap: 10px;
|
||||||
@include reset-ul;
|
@include reset-ul;
|
||||||
|
|
||||||
.action-button {
|
.action-button {
|
||||||
@include reset-button;
|
@include reset-button;
|
||||||
|
|
||||||
display: block;
|
display: block;
|
||||||
padding: $spacing-absolute-x-small $spacing-absolute-medium;
|
padding: 5px 10px;
|
||||||
background-color: $color-primary;
|
background-color: $color-primary;
|
||||||
color: $color-on-primary;
|
color: $color-on-primary;
|
||||||
border: none;
|
border: none;
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export default defineComponent({
|
|||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@use "@/presentation/assets/styles/main" as *;
|
@use "@/presentation/assets/styles/main" as *;
|
||||||
|
|
||||||
$gap: $spacing-relative-x-small;
|
$gap: 0.25rem;
|
||||||
.list {
|
.list {
|
||||||
display: flex;
|
display: flex;
|
||||||
:deep(.items) {
|
:deep(.items) {
|
||||||
|
|||||||
@@ -37,10 +37,8 @@ export default defineComponent({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@use "@/presentation/assets/styles/main" as *;
|
|
||||||
|
|
||||||
.circle-rating {
|
.circle-rating {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
gap: $spacing-relative-x-small;
|
gap: 0.2em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<span class="circle-container">
|
<svg
|
||||||
<svg :viewBox="viewBox">
|
:style="{
|
||||||
<circle
|
'--circle-stroke-width': `${circleStrokeWidthInPx}px`,
|
||||||
:cx="circleRadiusInPx"
|
}"
|
||||||
:cy="circleRadiusInPx"
|
:viewBox="viewBox"
|
||||||
:r="circleRadiusWithoutStrokeInPx"
|
>
|
||||||
:stroke-width="circleStrokeWidthStyleValue"
|
<circle
|
||||||
:class="{
|
:cx="circleRadiusInPx"
|
||||||
filled,
|
:cy="circleRadiusInPx"
|
||||||
}"
|
:r="circleRadiusWithoutStrokeInPx"
|
||||||
/>
|
:class="{
|
||||||
</svg>
|
filled,
|
||||||
</span>
|
}"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@@ -41,13 +43,10 @@ export default defineComponent({
|
|||||||
const height = circleDiameterInPx + circleStrokeWidthInPx;
|
const height = circleDiameterInPx + circleStrokeWidthInPx;
|
||||||
return `${minX} ${minY} ${width} ${height}`;
|
return `${minX} ${minY} ${width} ${height}`;
|
||||||
});
|
});
|
||||||
const circleStrokeWidthStyleValue = computed(() => {
|
|
||||||
return `${circleStrokeWidthInPx}px`;
|
|
||||||
});
|
|
||||||
return {
|
return {
|
||||||
circleRadiusInPx,
|
circleRadiusInPx,
|
||||||
circleDiameterInPx,
|
circleDiameterInPx,
|
||||||
circleStrokeWidthStyleValue,
|
circleStrokeWidthInPx,
|
||||||
circleRadiusWithoutStrokeInPx,
|
circleRadiusWithoutStrokeInPx,
|
||||||
viewBox,
|
viewBox,
|
||||||
};
|
};
|
||||||
@@ -56,18 +55,17 @@ export default defineComponent({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@use "@/presentation/assets/styles/main" as *;
|
$circleColor: currentColor;
|
||||||
|
$circleHeight: 0.8em;
|
||||||
$circle-color: currentColor;
|
$circleStrokeWidth: var(--circle-stroke-width);
|
||||||
$circle-height: $font-size-relative-smaller;
|
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
font-size: $circle-height;
|
height: $circleHeight;
|
||||||
height: 1em;
|
|
||||||
circle {
|
circle {
|
||||||
stroke: $circle-color;
|
stroke: $circleColor;
|
||||||
|
stroke-width: $circleStrokeWidth;
|
||||||
&.filled {
|
&.filled {
|
||||||
fill: $circle-color;
|
fill: $circleColor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
@mixin horizontal-stack {
|
@mixin horizontal-stack {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: $spacing-relative-small;
|
gap: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin apply-icon-color($color) {
|
@mixin apply-icon-color($color) {
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
@mixin horizontal-stack {
|
@mixin horizontal-stack {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: $spacing-relative-small;
|
gap: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin apply-icon-color($color) {
|
@mixin apply-icon-color($color) {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="scripts-menu">
|
<div id="container">
|
||||||
<div class="scripts-menu-item scripts-menu-rows">
|
<div class="item rows">
|
||||||
<TheRecommendationSelector class="scripts-menu-item" />
|
<TheRecommendationSelector class="item" />
|
||||||
<TheRevertSelector class="scripts-menu-item" />
|
<TheRevertSelector class="item" />
|
||||||
</div>
|
</div>
|
||||||
<TheOsChanger class="scripts-menu-item" />
|
<TheOsChanger class="item" />
|
||||||
<TheViewChanger
|
<TheViewChanger
|
||||||
v-if="!isSearching"
|
v-if="!isSearching"
|
||||||
class="scripts-menu-item"
|
class="item"
|
||||||
@view-changed="$emit('viewChanged', $event)"
|
@view-changed="$emit('viewChanged', $event)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -67,44 +67,29 @@ export default defineComponent({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@use "@/presentation/assets/styles/main" as *;
|
$margin-between-lines: 7px;
|
||||||
@use 'sass:math';
|
#container {
|
||||||
|
|
||||||
@mixin center-middle-flex-item {
|
|
||||||
&:first-child, &:last-child {
|
|
||||||
flex-grow: 1;
|
|
||||||
flex-basis: 0;
|
|
||||||
}
|
|
||||||
&:last-child {
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$responsive-alignment-breakpoint: $media-screen-medium-width;
|
|
||||||
|
|
||||||
.scripts-menu {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
column-gap: $spacing-relative-medium;
|
margin-top: -$margin-between-lines;
|
||||||
row-gap: $spacing-relative-small;
|
.item {
|
||||||
flex-wrap: wrap;
|
flex: 1;
|
||||||
align-items: center;
|
white-space: nowrap;
|
||||||
margin-left: $spacing-absolute-small;
|
|
||||||
margin-right: $spacing-absolute-small;
|
|
||||||
@media screen and (max-width: $responsive-alignment-breakpoint) {
|
|
||||||
justify-content: space-around;
|
|
||||||
}
|
|
||||||
.scripts-menu-item {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
@media screen and (min-width: $responsive-alignment-breakpoint) {
|
justify-content: center;
|
||||||
@include center-middle-flex-item;
|
align-items: center;
|
||||||
|
margin: $margin-between-lines 5px 0 5px;
|
||||||
|
&:first-child {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
&:last-child {
|
||||||
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.scripts-menu-rows {
|
.rows {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
row-gap: $spacing-relative-x-small;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="slider">
|
<div
|
||||||
|
class="slider"
|
||||||
|
:style="{
|
||||||
|
'--vertical-margin': verticalMargin,
|
||||||
|
'--first-min-width': firstMinWidth,
|
||||||
|
'--first-initial-width': firstInitialWidth,
|
||||||
|
'--second-min-width': secondMinWidth,
|
||||||
|
}"
|
||||||
|
>
|
||||||
<div ref="firstElement" class="first">
|
<div ref="firstElement" class="first">
|
||||||
<slot name="first" />
|
<slot name="first" />
|
||||||
</div>
|
</div>
|
||||||
@@ -19,6 +27,10 @@ export default defineComponent({
|
|||||||
SliderHandle,
|
SliderHandle,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
verticalMargin: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
firstMinWidth: {
|
firstMinWidth: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
@@ -59,18 +71,21 @@ export default defineComponent({
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
.first {
|
.first {
|
||||||
min-width: v-bind(firstMinWidth);
|
min-width: var(--first-min-width);
|
||||||
width: v-bind(firstInitialWidth);
|
width: var(--first-initial-width);
|
||||||
}
|
}
|
||||||
.second {
|
.second {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: v-bind(secondMinWidth);
|
min-width: var(--second-min-width);
|
||||||
}
|
}
|
||||||
@media screen and (max-width: $media-vertical-view-breakpoint) {
|
@media screen and (max-width: $media-vertical-view-breakpoint) {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
.first {
|
.first {
|
||||||
width: auto !important;
|
width: auto !important;
|
||||||
}
|
}
|
||||||
|
.second {
|
||||||
|
margin-top: var(--vertical-margin);
|
||||||
|
}
|
||||||
.handle {
|
.handle {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ $cursor : v-bind(cursorCssValue);
|
|||||||
.icon {
|
.icon {
|
||||||
color: $color;
|
color: $color;
|
||||||
}
|
}
|
||||||
margin-right: $spacing-absolute-small;
|
margin-right: 5px;
|
||||||
margin-left: $spacing-absolute-small;
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
<div class="script-area">
|
<div class="script-area">
|
||||||
<TheScriptsMenu @view-changed="currentView = $event" />
|
<TheScriptsMenu @view-changed="currentView = $event" />
|
||||||
<HorizontalResizeSlider
|
<HorizontalResizeSlider
|
||||||
class="horizontal-slider"
|
class="row"
|
||||||
|
vertical-margin="15px"
|
||||||
first-initial-width="55%"
|
first-initial-width="55%"
|
||||||
first-min-width="20%"
|
first-min-width="20%"
|
||||||
second-min-width="20%"
|
second-min-width="20%"
|
||||||
@@ -41,17 +42,9 @@ export default defineComponent({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@use "@/presentation/assets/styles/main" as *;
|
|
||||||
|
|
||||||
.script-area {
|
.script-area {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: $spacing-absolute-medium;
|
gap: 6px;
|
||||||
}
|
|
||||||
|
|
||||||
.horizontal-slider {
|
|
||||||
// Add row gap between lines on mobile (smaller screens)
|
|
||||||
// when the slider turns into rows.
|
|
||||||
row-gap: $spacing-absolute-large;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
<template>
|
|
||||||
<transition name="card-expand-collapse-transition">
|
|
||||||
<slot />
|
|
||||||
</transition>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
// Empty component for ESLint compatibility, workaround for https://github.com/vuejs/vue-eslint-parser/issues/125.
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
@use "@/presentation/assets/styles/main" as *;
|
|
||||||
|
|
||||||
.card-expand-collapse-transition-enter-active {
|
|
||||||
transition:
|
|
||||||
opacity 0.3s ease-in-out,
|
|
||||||
max-height 0.3s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-expand-collapse-transition-enter-from {
|
|
||||||
opacity: 0; // Fade-in
|
|
||||||
max-height: 0; // Expand
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
<template>
|
||||||
|
<div class="card__expander">
|
||||||
|
<div class="card__expander__close-button">
|
||||||
|
<FlatButton
|
||||||
|
icon="xmark"
|
||||||
|
@click="collapse()"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="card__expander__content">
|
||||||
|
<ScriptsTree
|
||||||
|
:category-id="categoryId"
|
||||||
|
:has-top-padding="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
defineComponent,
|
||||||
|
} from 'vue';
|
||||||
|
import FlatButton from '@/presentation/components/Shared/FlatButton.vue';
|
||||||
|
import ScriptsTree from '@/presentation/components/Scripts/View/Tree/ScriptsTree.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
ScriptsTree,
|
||||||
|
FlatButton,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
categoryId: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: {
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
onCollapse: () => true,
|
||||||
|
/* eslint-enable @typescript-eslint/no-unused-vars */
|
||||||
|
},
|
||||||
|
setup(_, { emit }) {
|
||||||
|
function collapse() {
|
||||||
|
emit('onCollapse');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
collapse,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@use "@/presentation/assets/styles/main" as *;
|
||||||
|
|
||||||
|
$expanded-margin-top : 30px;
|
||||||
|
|
||||||
|
.card__expander {
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
position: relative;
|
||||||
|
background-color: $color-primary-darker;
|
||||||
|
color: $color-on-primary;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
margin-top: $expanded-margin-top;
|
||||||
|
|
||||||
|
.card__expander__content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
word-break: break-word;
|
||||||
|
max-width: 100%; // Prevents horizontal expansion of inner content (e.g., when a code block is shown)
|
||||||
|
width: 100%; // Expands the container to fill available horizontal space, enabling alignment of child items.
|
||||||
|
}
|
||||||
|
|
||||||
|
.card__expander__close-button {
|
||||||
|
font-size: $font-size-absolute-large;
|
||||||
|
align-self: flex-end;
|
||||||
|
margin-right: 0.25em;
|
||||||
|
@include clickable;
|
||||||
|
color: $color-primary-light;
|
||||||
|
@include hover-or-touch {
|
||||||
|
color: $color-primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -4,25 +4,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
// Empty component for ESLint compatibility, workaround for https://github.com/vuejs/vue-eslint-parser/issues/125.
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@use "@/presentation/assets/styles/main" as *;
|
@use "@/presentation/assets/styles/main" as *;
|
||||||
|
|
||||||
$arrow-size: $font-size-absolute-normal;
|
$arrow-size : 15px;
|
||||||
|
|
||||||
.arrow-container {
|
.arrow-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
.arrow {
|
.arrow {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: calc(50% - $arrow-size * 1.5);
|
left: calc(50% - $arrow-size * 1.5);
|
||||||
top: calc(-0.35 * $arrow-size);
|
top: calc(1.5 * $arrow-size);
|
||||||
border: solid $color-primary-darker;
|
border: solid $color-primary-darker;
|
||||||
border-width: 0 $arrow-size $arrow-size 0;
|
border-width: 0 $arrow-size $arrow-size 0;
|
||||||
padding: $arrow-size;
|
padding: $arrow-size;
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
v-for="categoryId of categoryIds"
|
v-for="categoryId of categoryIds"
|
||||||
:key="categoryId"
|
:key="categoryId"
|
||||||
class="card"
|
class="card"
|
||||||
|
:total-cards-per-row="cardsPerRow"
|
||||||
:class="{
|
:class="{
|
||||||
'small-screen': width <= 500,
|
'small-screen': width <= 500,
|
||||||
'medium-screen': width > 500 && width < 750,
|
'medium-screen': width > 500 && width < 750,
|
||||||
@@ -62,6 +63,19 @@ export default defineComponent({
|
|||||||
);
|
);
|
||||||
const activeCategoryId = ref<number | undefined>(undefined);
|
const activeCategoryId = ref<number | undefined>(undefined);
|
||||||
|
|
||||||
|
const cardsPerRow = computed<number>(() => {
|
||||||
|
if (width.value === undefined) {
|
||||||
|
throw new Error('Unknown width, total cards should not be calculated');
|
||||||
|
}
|
||||||
|
if (width.value <= 500) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (width.value < 750) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
return 3;
|
||||||
|
});
|
||||||
|
|
||||||
function onSelected(categoryId: number, isExpanded: boolean) {
|
function onSelected(categoryId: number, isExpanded: boolean) {
|
||||||
activeCategoryId.value = isExpanded ? categoryId : undefined;
|
activeCategoryId.value = isExpanded ? categoryId : undefined;
|
||||||
}
|
}
|
||||||
@@ -108,6 +122,7 @@ export default defineComponent({
|
|||||||
width,
|
width,
|
||||||
categoryIds,
|
categoryIds,
|
||||||
activeCategoryId,
|
activeCategoryId,
|
||||||
|
cardsPerRow,
|
||||||
onSelected,
|
onSelected,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -125,7 +140,6 @@ function isClickable(element: Element) {
|
|||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@use "@/presentation/assets/styles/main" as *;
|
@use "@/presentation/assets/styles/main" as *;
|
||||||
@use "./card-gap" as *;
|
|
||||||
|
|
||||||
.cards {
|
.cards {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -136,7 +150,7 @@ function isClickable(element: Element) {
|
|||||||
It ensures that there's room to grow, so the animation is shown without overflowing
|
It ensures that there's room to grow, so the animation is shown without overflowing
|
||||||
with scrollbars.
|
with scrollbars.
|
||||||
*/
|
*/
|
||||||
padding: $spacing-absolute-medium;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
ref="cardElement"
|
ref="cardElement"
|
||||||
class="card"
|
class="card"
|
||||||
:class="{
|
:class="{
|
||||||
|
'is-collapsed': !isExpanded,
|
||||||
'is-inactive': activeCategoryId && activeCategoryId !== categoryId,
|
'is-inactive': activeCategoryId && activeCategoryId !== categoryId,
|
||||||
'is-expanded': isExpanded,
|
'is-expanded': isExpanded,
|
||||||
}"
|
}"
|
||||||
@@ -28,28 +29,15 @@
|
|||||||
:category-id="categoryId"
|
:category-id="categoryId"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<CardExpandTransition>
|
<CardExpansionPanelArrow v-show="isExpanded" />
|
||||||
<div v-show="isExpanded">
|
<ExpandCollapseTransition>
|
||||||
<CardExpansionArrow />
|
<CardExpansionPanel
|
||||||
<div
|
v-show="isExpanded"
|
||||||
class="card__expander"
|
:category-id="categoryId"
|
||||||
@click.stop
|
@on-collapse="collapse"
|
||||||
>
|
@click.stop
|
||||||
<div class="card__expander__close-button">
|
/>
|
||||||
<FlatButton
|
</ExpandCollapseTransition>
|
||||||
icon="xmark"
|
|
||||||
@click="collapse()"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="card__expander__content">
|
|
||||||
<ScriptsTree
|
|
||||||
:category-id="categoryId"
|
|
||||||
:has-top-padding="false"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardExpandTransition>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -58,28 +46,30 @@ import {
|
|||||||
defineComponent, computed, shallowRef,
|
defineComponent, computed, shallowRef,
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
|
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
|
||||||
import FlatButton from '@/presentation/components/Shared/FlatButton.vue';
|
import ExpandCollapseTransition from '@/presentation/components/Shared/ExpandCollapse/ExpandCollapseTransition.vue';
|
||||||
import { injectKey } from '@/presentation/injectionSymbols';
|
import { injectKey } from '@/presentation/injectionSymbols';
|
||||||
import ScriptsTree from '@/presentation/components/Scripts/View/Tree/ScriptsTree.vue';
|
|
||||||
import { sleep } from '@/infrastructure/Threading/AsyncSleep';
|
import { sleep } from '@/infrastructure/Threading/AsyncSleep';
|
||||||
import CardSelectionIndicator from './CardSelectionIndicator.vue';
|
import CardSelectionIndicator from './CardSelectionIndicator.vue';
|
||||||
import CardExpandTransition from './CardExpandTransition.vue';
|
import CardExpansionPanel from './CardExpansionPanel.vue';
|
||||||
import CardExpansionArrow from './CardExpansionArrow.vue';
|
import CardExpansionPanelArrow from './CardExpansionPanelArrow.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
ScriptsTree,
|
|
||||||
AppIcon,
|
AppIcon,
|
||||||
CardSelectionIndicator,
|
CardSelectionIndicator,
|
||||||
FlatButton,
|
CardExpansionPanel,
|
||||||
CardExpandTransition,
|
ExpandCollapseTransition,
|
||||||
CardExpansionArrow,
|
CardExpansionPanelArrow,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
categoryId: {
|
categoryId: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
totalCardsPerRow: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
activeCategoryId: {
|
activeCategoryId: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: undefined,
|
default: undefined,
|
||||||
@@ -105,6 +95,14 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const cardWidth = computed<string>(() => {
|
||||||
|
const totalTimesGapIsUsedInRow = props.totalCardsPerRow - 1;
|
||||||
|
const totalGapWidthInRow = `calc(${totalTimesGapIsUsedInRow} * 15px)`; // TODO: 15px is hardcoded, $card-gap variable should be used
|
||||||
|
const availableRowWidthForCards = `calc(100% - (${totalGapWidthInRow}))`;
|
||||||
|
const availableWidthPerCard = `calc((${availableRowWidthForCards}) / ${totalTimesGapIsUsedInRow})`;
|
||||||
|
return availableWidthPerCard;
|
||||||
|
});
|
||||||
|
|
||||||
const cardElement = shallowRef<HTMLElement>();
|
const cardElement = shallowRef<HTMLElement>();
|
||||||
|
|
||||||
const cardTitle = computed<string>(() => {
|
const cardTitle = computed<string>(() => {
|
||||||
@@ -129,6 +127,7 @@ export default defineComponent({
|
|||||||
cardTitle,
|
cardTitle,
|
||||||
isExpanded,
|
isExpanded,
|
||||||
cardElement,
|
cardElement,
|
||||||
|
cardWidth,
|
||||||
collapse,
|
collapse,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -138,14 +137,27 @@ export default defineComponent({
|
|||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@use "@/presentation/assets/styles/main" as *;
|
@use "@/presentation/assets/styles/main" as *;
|
||||||
@use "./card-gap" as *;
|
|
||||||
|
|
||||||
$card-inner-padding : $spacing-absolute-xx-large;
|
$card-inner-padding : 30px;
|
||||||
$expanded-margin-top : $spacing-absolute-xx-large;
|
$arrow-size : 15px;
|
||||||
$card-horizontal-gap : $card-gap;
|
$expanded-margin-top : 30px;
|
||||||
|
|
||||||
|
.expansion__arrow {
|
||||||
|
position: relative;
|
||||||
|
.expansion__arrow__inner {
|
||||||
|
position: absolute;
|
||||||
|
left: calc(50% - $arrow-size * 1.5);
|
||||||
|
top: calc(1.5 * $arrow-size);
|
||||||
|
border: solid $color-primary-darker;
|
||||||
|
border-width: 0 $arrow-size $arrow-size 0;
|
||||||
|
padding: $arrow-size;
|
||||||
|
transform: rotate(-135deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
.card__inner {
|
width: v-bind(cardWidth);
|
||||||
|
&__inner {
|
||||||
padding-top: $card-inner-padding;
|
padding-top: $card-inner-padding;
|
||||||
padding-right: $card-inner-padding;
|
padding-right: $card-inner-padding;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
@@ -158,7 +170,7 @@ $card-horizontal-gap : $card-gap;
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
transition: transform 0.2s ease-in-out;
|
transition: all 0.2s ease-in-out;
|
||||||
|
|
||||||
display:flex;
|
display:flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -179,45 +191,17 @@ $card-horizontal-gap : $card-gap;
|
|||||||
.card__inner__selection_indicator {
|
.card__inner__selection_indicator {
|
||||||
height: $card-inner-padding;
|
height: $card-inner-padding;
|
||||||
margin-right: -$card-inner-padding;
|
margin-right: -$card-inner-padding;
|
||||||
padding-right: $spacing-absolute-medium;
|
padding-right: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
.card__inner__expand-icon {
|
.card__inner__expand-icon {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: $spacing-relative-x-small;
|
margin-top: .25em;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
font-size: $font-size-absolute-normal;
|
font-size: $font-size-absolute-normal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.card__expander {
|
|
||||||
position: relative;
|
|
||||||
background-color: $color-primary-darker;
|
|
||||||
color: $color-on-primary;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.card__expander__content {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
word-break: break-word;
|
|
||||||
max-width: 100%; // Prevents horizontal expansion of inner content (e.g., when a code block is shown)
|
|
||||||
width: 100%; // Expands the container to fill available horizontal space, enabling alignment of child items.
|
|
||||||
}
|
|
||||||
|
|
||||||
.card__expander__close-button {
|
|
||||||
font-size: $font-size-absolute-large;
|
|
||||||
align-self: flex-end;
|
|
||||||
margin-right: $spacing-absolute-small;
|
|
||||||
@include clickable;
|
|
||||||
color: $color-primary-light;
|
|
||||||
@include hover-or-touch {
|
|
||||||
color: $color-primary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-expanded {
|
&.is-expanded {
|
||||||
.card__inner {
|
.card__inner {
|
||||||
@@ -226,10 +210,6 @@ $card-horizontal-gap : $card-gap;
|
|||||||
color: $color-on-secondary;
|
color: $color-on-secondary;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card__expander {
|
|
||||||
margin-top: $expanded-margin-top;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include hover-or-touch {
|
@include hover-or-touch {
|
||||||
.card__inner {
|
.card__inner {
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
@@ -254,26 +234,26 @@ $card-horizontal-gap : $card-gap;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
@mixin adaptive-card($cards-in-row) {
|
@mixin adaptive-card($cards-in-row) {
|
||||||
&.card {
|
.card {
|
||||||
$total-times-gap-is-used-in-row: $cards-in-row - 1;
|
$total-times-gap-is-used-in-row: $cards-in-row - 1;
|
||||||
$total-gap-width-in-row: $total-times-gap-is-used-in-row * $card-horizontal-gap;
|
$total-gap-width-in-row: $total-times-gap-is-used-in-row * $card-horizontal-gap;
|
||||||
$available-row-width-for-cards: calc(100% - #{$total-gap-width-in-row});
|
$available-row-width-for-cards: calc(100% - #{$total-gap-width-in-row});
|
||||||
$available-width-per-card: calc(#{$available-row-width-for-cards} / #{$cards-in-row});
|
$available-width-per-card: calc(#{$available-row-width-for-cards} / #{$cards-in-row});
|
||||||
width:$available-width-per-card;
|
width:$available-width-per-card;
|
||||||
.card__expander {
|
// .card__expander {
|
||||||
$all-cards-width: 100% * $cards-in-row;
|
// $all-cards-width: 100% * $cards-in-row;
|
||||||
$additional-padding-width: $card-horizontal-gap * ($cards-in-row - 1);
|
// $additional-padding-width: $card-horizontal-gap * ($cards-in-row - 1);
|
||||||
width: calc(#{$all-cards-width} + #{$additional-padding-width});
|
// width: calc(#{$all-cards-width} + #{$additional-padding-width});
|
||||||
}
|
// }
|
||||||
@for $nth-card from 2 through $cards-in-row { // From second card to rest
|
// @for $nth-card from 2 through $cards-in-row { // From second card to rest
|
||||||
&:nth-of-type(#{$cards-in-row}n+#{$nth-card}) {
|
// &:nth-of-type(#{$cards-in-row}n+#{$nth-card}) {
|
||||||
.card__expander {
|
// .card__expander {
|
||||||
$card-left: -100% * ($nth-card - 1);
|
// $card-left: -100% * ($nth-card - 1);
|
||||||
$additional-space: $card-horizontal-gap * ($nth-card - 1);
|
// $additional-space: $card-horizontal-gap * ($nth-card - 1);
|
||||||
margin-left: calc(#{$card-left} - #{$additional-space});
|
// margin-left: calc(#{$card-left} - #{$additional-space});
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
// Ensure new line after last row
|
// Ensure new line after last row
|
||||||
$card-after-last: $cards-in-row + 1;
|
$card-after-last: $cards-in-row + 1;
|
||||||
&:nth-of-type(#{$cards-in-row}n+#{$card-after-last}) {
|
&:nth-of-type(#{$cards-in-row}n+#{$card-after-last}) {
|
||||||
@@ -281,8 +261,4 @@ $card-horizontal-gap : $card-gap;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.big-screen { @include adaptive-card(3); }
|
|
||||||
.medium-screen { @include adaptive-card(2); }
|
|
||||||
.small-screen { @include adaptive-card(1); }
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
@use "@/presentation/assets/styles/main" as *;
|
|
||||||
|
|
||||||
$card-gap: $spacing-absolute-large;
|
|
||||||
@@ -128,7 +128,10 @@ export default defineComponent({
|
|||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@use "@/presentation/assets/styles/main" as *;
|
@use "@/presentation/assets/styles/main" as *;
|
||||||
|
|
||||||
|
$margin-inner: 4px;
|
||||||
|
|
||||||
.scripts-view {
|
.scripts-view {
|
||||||
|
margin-top: $margin-inner;
|
||||||
@media screen and (min-width: $media-vertical-view-breakpoint) {
|
@media screen and (min-width: $media-vertical-view-breakpoint) {
|
||||||
// so the current code is always visible
|
// so the current code is always visible
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
@@ -140,17 +143,16 @@ export default defineComponent({
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background-color: $color-scripts-bg;
|
background-color: $color-scripts-bg;
|
||||||
padding-top: $spacing-absolute-large;
|
|
||||||
padding-bottom:$spacing-absolute-large;
|
|
||||||
.search__query {
|
.search__query {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
margin-top: 1em;
|
||||||
color: $color-primary-light;
|
color: $color-primary-light;
|
||||||
.search__query__close-button {
|
.search__query__close-button {
|
||||||
font-size: $font-size-absolute-large;
|
font-size: $font-size-absolute-large;
|
||||||
margin-left: $spacing-relative-x-small;
|
margin-left: 0.25rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.search-no-matches {
|
.search-no-matches {
|
||||||
@@ -159,9 +161,11 @@ export default defineComponent({
|
|||||||
word-break:break-word;
|
word-break:break-word;
|
||||||
color: $color-on-primary;
|
color: $color-on-primary;
|
||||||
font-size: $font-size-absolute-large;
|
font-size: $font-size-absolute-large;
|
||||||
padding: $spacing-absolute-medium;
|
padding:10px;
|
||||||
text-align:center;
|
text-align:center;
|
||||||
gap: $spacing-relative-small;
|
> div {
|
||||||
|
padding-bottom:13px;
|
||||||
|
}
|
||||||
a {
|
a {
|
||||||
color: $color-primary;
|
color: $color-primary;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export default defineComponent({
|
|||||||
flex: 1; // Expands the container to fill available horizontal space, enabling alignment of child items.
|
flex: 1; // Expands the container to fill available horizontal space, enabling alignment of child items.
|
||||||
max-width: 100%; // Prevents horizontal expansion of inner content (e.g., when a code block is shown)
|
max-width: 100%; // Prevents horizontal expansion of inner content (e.g., when a code block is shown)
|
||||||
*:not(:first-child) {
|
*:not(:first-child) {
|
||||||
margin-left: $spacing-absolute-small;
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
.header {
|
.header {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -80,10 +80,10 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
.docs {
|
.docs {
|
||||||
background: $color-primary-darkest;
|
background: $color-primary-darkest;
|
||||||
margin-top: $spacing-relative-x-small;
|
margin-top: 0.25em;
|
||||||
color: $color-on-primary;
|
color: $color-on-primary;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
padding: $spacing-absolute-medium;
|
padding: 0.5em;
|
||||||
&-collapsed {
|
&-collapsed {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import MarkdownIt from 'markdown-it';
|
import MarkdownIt from 'markdown-it';
|
||||||
import type { MarkdownRenderer } from '../MarkdownRenderer';
|
import type { MarkdownRenderer } from '../MarkdownRenderer';
|
||||||
import type { RenderRule } from 'markdown-it/lib/renderer.mjs'; // eslint-disable-line import/extensions
|
import type { RenderRule } from 'markdown-it/lib/renderer';
|
||||||
|
|
||||||
export class MarkdownItHtmlRenderer implements MarkdownRenderer {
|
export class MarkdownItHtmlRenderer implements MarkdownRenderer {
|
||||||
public render(markdownContent: string): string {
|
public render(markdownContent: string): string {
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<DocumentableNode
|
<DocumentableNode :docs="nodeMetadata.docs">
|
||||||
class="node-content-wrapper"
|
<div id="node">
|
||||||
:docs="nodeMetadata.docs"
|
<div class="item">
|
||||||
>
|
|
||||||
<div class="node-content">
|
|
||||||
<div class="node-content-item">
|
|
||||||
<NodeTitle :title="nodeMetadata.text" />
|
<NodeTitle :title="nodeMetadata.text" />
|
||||||
</div>
|
</div>
|
||||||
<RevertToggle
|
<RevertToggle
|
||||||
v-if="nodeMetadata.isReversible"
|
v-if="nodeMetadata.isReversible"
|
||||||
class="node-content-item"
|
class="item"
|
||||||
:node="nodeMetadata"
|
:node="nodeMetadata"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -41,22 +38,13 @@ export default defineComponent({
|
|||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@use "@/presentation/assets/styles/main" as *;
|
@use "@/presentation/assets/styles/main" as *;
|
||||||
|
|
||||||
.node-content-wrapper {
|
#node {
|
||||||
/*
|
display: flex;
|
||||||
Compensate word breaking when it causes overflows of the node content,
|
flex-direction: row;
|
||||||
This issue happens on small devices when nodes are being rendered during search where the node header or
|
flex-wrap: wrap;
|
||||||
documentation grows to cause to overflow.
|
|
||||||
*/
|
|
||||||
overflow-wrap: anywhere;
|
|
||||||
|
|
||||||
.node-content {
|
.item:not(:first-child) {
|
||||||
display: flex;
|
margin-left: 5px;
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
|
|
||||||
.node-content-item:not(:first-child) {
|
|
||||||
margin-left: $spacing-relative-small;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
v-model="isReverted"
|
v-model="isReverted"
|
||||||
:stop-click-propagation="true"
|
:stop-click-propagation="true"
|
||||||
label="Revert"
|
:label="'Revert'"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -83,12 +83,12 @@ $color-text-unchecked : $color-on-primary;
|
|||||||
$color-text-checked : $color-on-secondary;
|
$color-text-checked : $color-on-secondary;
|
||||||
$color-bg-unchecked : $color-primary;
|
$color-bg-unchecked : $color-primary;
|
||||||
$color-bg-checked : $color-secondary;
|
$color-bg-checked : $color-secondary;
|
||||||
$padding-horizontal : $spacing-relative-small;
|
$padding-horizontal : calc($font-size * 0.4);
|
||||||
$padding-vertical : $spacing-absolute-small;
|
$padding-vertical : calc($font-size * 0.3);
|
||||||
$size-height : calc($font-size + ($padding-vertical * 2));
|
$size-height : calc($font-size + ($padding-vertical * 2));
|
||||||
$size-circle : calc($size-height * 2/3);
|
$size-circle : math.div($size-height * 2, 3);
|
||||||
|
|
||||||
$gap-between-circle-and-text : $spacing-relative-x-small;
|
$gap-between-circle-and-text : 0.25em;
|
||||||
|
|
||||||
@mixin locateNearCircle($direction: 'left') {
|
@mixin locateNearCircle($direction: 'left') {
|
||||||
$circle-width: calc(#{$size-circle} + #{$padding-horizontal});
|
$circle-width: calc(#{$size-circle} + #{$padding-horizontal});
|
||||||
|
|||||||
@@ -73,8 +73,7 @@ export default defineComponent({
|
|||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@use "@/presentation/assets/styles/main" as *;
|
@use "@/presentation/assets/styles/main" as *;
|
||||||
|
|
||||||
$padding-horizontal : $spacing-absolute-large;
|
$padding: 20px;
|
||||||
$padding-vertical : $spacing-absolute-x-large;
|
|
||||||
|
|
||||||
.scripts-tree-container {
|
.scripts-tree-container {
|
||||||
display: flex; // We could provide `block`, but `flex` is more versatile.
|
display: flex; // We could provide `block`, but `flex` is more versatile.
|
||||||
@@ -85,11 +84,11 @@ $padding-vertical : $spacing-absolute-x-large;
|
|||||||
|
|
||||||
flex: 1; // Expands the container to fill available horizontal space, enabling alignment of child items.
|
flex: 1; // Expands the container to fill available horizontal space, enabling alignment of child items.
|
||||||
|
|
||||||
padding-bottom: $padding-vertical;
|
padding-bottom: $padding;
|
||||||
padding-left: $padding-horizontal;
|
padding-left: $padding;
|
||||||
padding-right: $padding-horizontal;
|
padding-right: $padding;
|
||||||
&.top-padding {
|
&.top-padding {
|
||||||
padding-top: $padding-vertical;
|
padding-top: $padding;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
:tree-root="treeRoot"
|
:tree-root="treeRoot"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="expand-collapse-caret"
|
class="expand-collapse-arrow"
|
||||||
:class="{
|
:class="{
|
||||||
expanded: isExpanded,
|
expanded: isExpanded,
|
||||||
'has-children': hasChildren,
|
'has-children': hasChildren,
|
||||||
@@ -141,13 +141,10 @@ export default defineComponent({
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.expand-collapse-caret {
|
.expand-collapse-arrow {
|
||||||
$caret-size: 24px;
|
|
||||||
$padding-right: $spacing-absolute-small;
|
|
||||||
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
height: $caret-size;
|
height: 30px;
|
||||||
margin-left: $caret-size + $padding-right;
|
margin-left: 30px;
|
||||||
width: 0;
|
width: 0;
|
||||||
|
|
||||||
@include clickable;
|
@include clickable;
|
||||||
@@ -160,32 +157,25 @@ export default defineComponent({
|
|||||||
|
|
||||||
&.has-children {
|
&.has-children {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
width: $caret-size + $padding-right;
|
width: 30px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
$caret-dimension: $caret-size * 0.375;
|
|
||||||
$caret-stroke-width: 1.5px;
|
|
||||||
&:after {
|
&:after {
|
||||||
border: $caret-stroke-width solid $color-node-arrow;
|
border: 1.5px solid $color-node-arrow;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
border-left: 0;
|
border-left: 0;
|
||||||
border-top: 0;
|
border-top: 0;
|
||||||
left: $caret-dimension;
|
left: 9px;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
height: $caret-dimension;
|
height: 9px;
|
||||||
width: $caret-dimension;
|
width: 9px;
|
||||||
transform:
|
transform: rotate(-45deg) translateY(-50%) translateX(0);
|
||||||
rotate(-45deg)
|
|
||||||
translateY(-50%)
|
|
||||||
translateX($caret-dimension * 0.2);
|
|
||||||
transition: transform .25s;
|
transition: transform .25s;
|
||||||
transform-origin: center;
|
transform-origin: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.expanded:after {
|
&.expanded:after {
|
||||||
transform:
|
transform: rotate(45deg) translateY(-50%) translateX(-5px);
|
||||||
rotate(45deg)
|
|
||||||
translateY(-50%)
|
|
||||||
translateX($caret-dimension * -0.5);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,18 +76,18 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.node {
|
.node {
|
||||||
margin-bottom: $spacing-absolute-xx-small;
|
margin-bottom: 3px;
|
||||||
margin-top: $spacing-absolute-xx-small;
|
margin-top: 3px;
|
||||||
padding-bottom: $spacing-absolute-xx-small;
|
padding-bottom: 3px;
|
||||||
padding-top: $spacing-absolute-xx-small;
|
padding-top: 3px;
|
||||||
padding-right: $spacing-absolute-small;
|
padding-right: 6px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
display: flex; // We could provide `block`, but `flex` is more versatile.
|
display: flex; // We could provide `block`, but `flex` is more versatile.
|
||||||
color: $color-node-fg;
|
color: $color-node-fg;
|
||||||
padding-left: $spacing-relative-small;
|
padding-left: 9px;
|
||||||
padding-right: $spacing-absolute-x-small;
|
padding-right: 6px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
.flat-button {
|
.flat-button {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
gap: $spacing-relative-small;
|
gap: 0.5em;
|
||||||
&.disabled {
|
&.disabled {
|
||||||
@include flat-button($disabled: true);
|
@include flat-button($disabled: true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
@click="onBackgroundOverlayClick"
|
@click="onBackgroundOverlayClick"
|
||||||
/>
|
/>
|
||||||
<ModalContent
|
<ModalContent
|
||||||
|
class="modal-content"
|
||||||
:show="isOpen"
|
:show="isOpen"
|
||||||
@transitioned-out="onContentTransitionedOut"
|
@transitioned-out="onContentTransitionedOut"
|
||||||
>
|
>
|
||||||
@@ -137,8 +138,6 @@ export default defineComponent({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@use "@/presentation/assets/styles/main" as *;
|
|
||||||
|
|
||||||
.modal-container {
|
.modal-container {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|||||||
@@ -55,11 +55,11 @@ export default defineComponent({
|
|||||||
$modal-content-transition-duration: 400ms;
|
$modal-content-transition-duration: 400ms;
|
||||||
$modal-content-color-shadow: $color-on-surface;
|
$modal-content-color-shadow: $color-on-surface;
|
||||||
$modal-content-color-background: $color-surface;
|
$modal-content-color-background: $color-surface;
|
||||||
$modal-content-offset-upward: $spacing-absolute-x-large;
|
$modal-content-offset-upward: 20px;
|
||||||
|
|
||||||
@mixin scrollable() {
|
@mixin scrollable() {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
max-height: 100%;
|
max-height: 90vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content-wrapper {
|
.modal-content-wrapper {
|
||||||
@@ -74,17 +74,6 @@ $modal-content-offset-upward: $spacing-absolute-x-large;
|
|||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
||||||
// Margin around modal content ensures visual comfort and consistency across devices.
|
|
||||||
// It provides:
|
|
||||||
// - A visually comfortable space preventing a claustrophobic feeling, especially on smaller screens.
|
|
||||||
// - Consistent appearance on various screen sizes by using absolute spacing.
|
|
||||||
// - Focus on the modal by dimming the background and emphasizing the task.
|
|
||||||
// - Sufficient space on small devices for users to tap outside and close the modal.
|
|
||||||
margin: $spacing-absolute-xx-large;
|
|
||||||
@media screen and (max-width: $media-screen-small-width) {
|
|
||||||
margin: $spacing-absolute-x-large; // Google and Apple recommend at least 44x44px for touch targets
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content-content {
|
.modal-content-content {
|
||||||
|
|||||||
@@ -63,25 +63,19 @@ export default defineComponent({
|
|||||||
@use "@/presentation/assets/styles/main" as *;
|
@use "@/presentation/assets/styles/main" as *;
|
||||||
|
|
||||||
.dialog {
|
.dialog {
|
||||||
margin-bottom: $spacing-absolute-medium;
|
margin-bottom: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
||||||
&__content {
|
&__content {
|
||||||
margin: $spacing-absolute-xx-large;
|
margin: 5%;
|
||||||
@media screen and (max-width: $media-screen-big-width) {
|
|
||||||
margin: $spacing-absolute-x-large;
|
|
||||||
}
|
|
||||||
@media screen and (max-width: $media-screen-medium-width) {
|
|
||||||
margin: $spacing-absolute-large;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog__close-button {
|
.dialog__close-button {
|
||||||
color: $color-primary-dark;
|
color: $color-primary-dark;
|
||||||
width: auto;
|
width: auto;
|
||||||
font-size: $font-size-absolute-large;
|
font-size: $font-size-absolute-large;
|
||||||
margin-right: $spacing-absolute-small;
|
margin-right: 0.25em;
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -219,10 +219,8 @@ $color-tooltip-background: $color-primary-darkest;
|
|||||||
background: $color-tooltip-background;
|
background: $color-tooltip-background;
|
||||||
color: $color-on-primary;
|
color: $color-on-primary;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
padding: $spacing-absolute-large $spacing-absolute-medium;
|
padding: 12px 10px;
|
||||||
|
font-size: $font-size-absolute-normal;
|
||||||
// Explicitly set font styling for tooltips to prevent inconsistent appearances due to style inheritance from trigger elements.
|
|
||||||
@include base-font-style;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
This margin creates a visual buffer between the tooltip and the edges of the document.
|
This margin creates a visual buffer between the tooltip and the edges of the document.
|
||||||
@@ -230,8 +228,8 @@ $color-tooltip-background: $color-primary-darkest;
|
|||||||
and balanced layout.
|
and balanced layout.
|
||||||
Avoiding setting vertical margin as it disrupts the arrow rendering.
|
Avoiding setting vertical margin as it disrupts the arrow rendering.
|
||||||
*/
|
*/
|
||||||
margin-left: $spacing-absolute-xx-small;
|
margin-left: 2px;
|
||||||
margin-right: $spacing-absolute-xx-small;
|
margin-right: 2px;
|
||||||
|
|
||||||
// Setting max-width increases readability and consistency reducing overlap and clutter.
|
// Setting max-width increases readability and consistency reducing overlap and clutter.
|
||||||
@include set-property-ch-value-with-fallback(
|
@include set-property-ch-value-with-fallback(
|
||||||
|
|||||||
@@ -67,10 +67,10 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
.description {
|
.description {
|
||||||
&__icon {
|
&__icon {
|
||||||
margin-right: $spacing-relative-small;
|
margin-right: 0.5em;
|
||||||
}
|
}
|
||||||
&__text {
|
&__text {
|
||||||
margin-right: $spacing-relative-x-small;
|
margin-right: 0.3em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,7 +79,7 @@ export default defineComponent({
|
|||||||
&:not(:first-child)::before {
|
&:not(:first-child)::before {
|
||||||
content: "|";
|
content: "|";
|
||||||
font-size: $font-size-absolute-x-small;
|
font-size: $font-size-absolute-x-small;
|
||||||
padding: 0 $spacing-relative-small;
|
padding: 0 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ export default defineComponent({
|
|||||||
@use "@/presentation/assets/styles/main" as *;
|
@use "@/presentation/assets/styles/main" as *;
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
margin-right: $spacing-relative-small;
|
margin-right: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
@@ -119,19 +119,18 @@ export default defineComponent({
|
|||||||
@media screen and (max-width: $media-screen-big-width) {
|
@media screen and (max-width: $media-screen-big-width) {
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
column-gap: $spacing-relative-medium;
|
|
||||||
&:not(:first-child) {
|
&:not(:first-child) {
|
||||||
margin-top: $spacing-relative-small;
|
margin-top: 0.7em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
&__item:not(:first-child) {
|
&__item:not(:first-child) {
|
||||||
&::before {
|
&::before {
|
||||||
content: "|";
|
content: "|";
|
||||||
padding: 0 $spacing-relative-small;
|
padding: 0 5px;
|
||||||
}
|
}
|
||||||
@media screen and (max-width: $media-screen-big-width) {
|
@media screen and (max-width: $media-screen-big-width) {
|
||||||
margin-top: $spacing-absolute-xx-small;
|
margin-top: 3px;
|
||||||
&::before {
|
&::before {
|
||||||
content: "";
|
content: "";
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|||||||
@@ -106,9 +106,10 @@ export default defineComponent({
|
|||||||
min-width: 60px;
|
min-width: 60px;
|
||||||
border: 1.5px solid $color-primary;
|
border: 1.5px solid $color-primary;
|
||||||
border-right: none;
|
border-right: none;
|
||||||
|
height: 36px;
|
||||||
border-radius: 3px 0 0 3px;
|
border-radius: 3px 0 0 3px;
|
||||||
padding-left: $spacing-absolute-medium;
|
padding-left:10px;
|
||||||
padding-right: $spacing-absolute-medium;
|
padding-right:10px;
|
||||||
outline: none;
|
outline: none;
|
||||||
color: $color-primary;
|
color: $color-primary;
|
||||||
font-size: $font-size-absolute-normal;
|
font-size: $font-size-absolute-normal;
|
||||||
@@ -126,6 +127,6 @@ export default defineComponent({
|
|||||||
color: $color-on-primary;
|
color: $color-on-primary;
|
||||||
border-radius: 0 5px 5px 0;
|
border-radius: 0 5px 5px 0;
|
||||||
font-size: $font-size-absolute-large;
|
font-size: $font-size-absolute-large;
|
||||||
padding: $spacing-absolute-x-small;
|
padding:5px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -13,4 +13,4 @@ export const RENDERER_URL = process.env.ELECTRON_RENDERER_URL;
|
|||||||
|
|
||||||
export const RENDERER_HTML_PATH = join('file://', __dirname, '../renderer/index.html');
|
export const RENDERER_HTML_PATH = join('file://', __dirname, '../renderer/index.html');
|
||||||
|
|
||||||
export const PRELOADER_SCRIPT_PATH = join(__dirname, '../preload/index.mjs');
|
export const PRELOADER_SCRIPT_PATH = join(__dirname, '../preload/index.cjs');
|
||||||
|
|||||||
@@ -1,20 +1,18 @@
|
|||||||
import { app, dialog } from 'electron/main';
|
import { app, dialog } from 'electron/main';
|
||||||
|
import { autoUpdater, type UpdateInfo } from 'electron-updater';
|
||||||
import { ElectronLogger } from '@/infrastructure/Log/ElectronLogger';
|
import { ElectronLogger } from '@/infrastructure/Log/ElectronLogger';
|
||||||
import { UpdateProgressBar } from './UpdateProgressBar';
|
import { UpdateProgressBar } from './UpdateProgressBar';
|
||||||
import { getAutoUpdater } from './ElectronAutoUpdaterFactory';
|
|
||||||
import type { AppUpdater, UpdateInfo } from 'electron-updater';
|
|
||||||
import type { ProgressInfo } from 'electron-builder';
|
import type { ProgressInfo } from 'electron-builder';
|
||||||
|
|
||||||
export async function handleAutoUpdate() {
|
export async function handleAutoUpdate() {
|
||||||
const autoUpdater = getAutoUpdater();
|
|
||||||
if (await askDownloadAndInstall() === DownloadDialogResult.NotNow) {
|
if (await askDownloadAndInstall() === DownloadDialogResult.NotNow) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
startHandlingUpdateProgress(autoUpdater);
|
startHandlingUpdateProgress();
|
||||||
await autoUpdater.downloadUpdate();
|
await autoUpdater.downloadUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
function startHandlingUpdateProgress(autoUpdater: AppUpdater) {
|
function startHandlingUpdateProgress() {
|
||||||
const progressBar = new UpdateProgressBar();
|
const progressBar = new UpdateProgressBar();
|
||||||
progressBar.showIndeterminateState();
|
progressBar.showIndeterminateState();
|
||||||
autoUpdater.on('error', (e) => {
|
autoUpdater.on('error', (e) => {
|
||||||
@@ -31,11 +29,11 @@ function startHandlingUpdateProgress(autoUpdater: AppUpdater) {
|
|||||||
autoUpdater.on('update-downloaded', async (info: UpdateInfo) => {
|
autoUpdater.on('update-downloaded', async (info: UpdateInfo) => {
|
||||||
ElectronLogger.info('@update-downloaded@\n', info);
|
ElectronLogger.info('@update-downloaded@\n', info);
|
||||||
progressBar.close();
|
progressBar.close();
|
||||||
await handleUpdateDownloaded(autoUpdater);
|
await handleUpdateDownloaded();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleUpdateDownloaded(autoUpdater: AppUpdater) {
|
async function handleUpdateDownloaded() {
|
||||||
if (await askRestartAndInstall() === InstallDialogResult.NotNow) {
|
if (await askRestartAndInstall() === InstallDialogResult.NotNow) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
import electronUpdater, { type AppUpdater } from 'electron-updater';
|
|
||||||
|
|
||||||
export function getAutoUpdater(): AppUpdater {
|
|
||||||
// Using destructuring to access autoUpdater due to the CommonJS module of 'electron-updater'.
|
|
||||||
// It is a workaround for ESM compatibility issues, see https://github.com/electron-userland/electron-builder/issues/7976.
|
|
||||||
const { autoUpdater } = electronUpdater;
|
|
||||||
return autoUpdater;
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,13 @@
|
|||||||
|
import { autoUpdater, type UpdateInfo } from 'electron-updater';
|
||||||
import { ElectronLogger } from '@/infrastructure/Log/ElectronLogger';
|
import { ElectronLogger } from '@/infrastructure/Log/ElectronLogger';
|
||||||
import { requiresManualUpdate, startManualUpdateProcess } from './ManualUpdater/ManualUpdateCoordinator';
|
import { requiresManualUpdate, startManualUpdateProcess } from './ManualUpdater/ManualUpdateCoordinator';
|
||||||
import { handleAutoUpdate } from './AutomaticUpdateCoordinator';
|
import { handleAutoUpdate } from './AutomaticUpdateCoordinator';
|
||||||
import { getAutoUpdater } from './ElectronAutoUpdaterFactory';
|
|
||||||
import type { UpdateInfo } from 'electron-updater';
|
|
||||||
|
|
||||||
interface Updater {
|
interface Updater {
|
||||||
checkForUpdates(): Promise<void>;
|
checkForUpdates(): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setupAutoUpdater(): Updater {
|
export function setupAutoUpdater(): Updater {
|
||||||
const autoUpdater = getAutoUpdater();
|
|
||||||
|
|
||||||
autoUpdater.logger = ElectronLogger;
|
autoUpdater.logger = ElectronLogger;
|
||||||
|
|
||||||
// Auto-downloads are disabled to allow separate handling of 'check' and 'download' actions,
|
// Auto-downloads are disabled to allow separate handling of 'check' and 'download' actions,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
describe, it, beforeAll, afterAll,
|
describe, it, beforeAll, afterAll,
|
||||||
} from 'vitest';
|
} from 'vitest';
|
||||||
import { formatAssertionMessage } from '@tests/shared/FormatAssertionMessage';
|
|
||||||
import { main } from './check-desktop-runtime-errors/main';
|
import { main } from './check-desktop-runtime-errors/main';
|
||||||
import { COMMAND_LINE_FLAGS, CommandLineFlag } from './check-desktop-runtime-errors/cli-args';
|
import { COMMAND_LINE_FLAGS, CommandLineFlag } from './check-desktop-runtime-errors/cli-args';
|
||||||
|
|
||||||
@@ -15,10 +14,7 @@ describe('desktop runtime error checks', () => {
|
|||||||
() => main(),
|
() => main(),
|
||||||
);
|
);
|
||||||
// assert
|
// assert
|
||||||
expect(exitCode).to.equal(0, formatAssertionMessage([
|
expect(exitCode).to.equal(0);
|
||||||
`Test failed with exit code ${exitCode}; expected 0.`,
|
|
||||||
'Examine preceding logs to identify errors.',
|
|
||||||
]));
|
|
||||||
}, {
|
}, {
|
||||||
timeout: 60 /* minutes */ * 60000,
|
timeout: 60 /* minutes */ * 60000,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
import type { IApplication } from '@/domain/IApplication';
|
|
||||||
import type { TestExecutionDetailsLogger } from './TestExecutionDetailsLogger';
|
|
||||||
|
|
||||||
interface UrlExtractionContext {
|
|
||||||
readonly logger: TestExecutionDetailsLogger;
|
|
||||||
readonly application: IApplication;
|
|
||||||
readonly urlExclusionPatterns: readonly RegExp[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function extractDocumentationUrls(
|
|
||||||
context: UrlExtractionContext,
|
|
||||||
): string[] {
|
|
||||||
const urlsInApplication = extractUrlsFromApplication(context.application);
|
|
||||||
context.logger.logLabeledInformation(
|
|
||||||
'Extracted URLs from application',
|
|
||||||
urlsInApplication.length.toString(),
|
|
||||||
);
|
|
||||||
const uniqueUrls = filterDuplicateUrls(urlsInApplication);
|
|
||||||
context.logger.logLabeledInformation(
|
|
||||||
'Unique URLs after deduplication',
|
|
||||||
`${uniqueUrls.length} (duplicates removed)`,
|
|
||||||
);
|
|
||||||
context.logger.logLabeledInformation(
|
|
||||||
'Exclusion patterns for URLs',
|
|
||||||
context.urlExclusionPatterns.length === 0
|
|
||||||
? 'None (all URLs included)'
|
|
||||||
: context.urlExclusionPatterns.map((pattern, index) => `${index + 1}) ${pattern.toString()}`).join('\n'),
|
|
||||||
);
|
|
||||||
const includedUrls = filterUrlsExcludingPatterns(uniqueUrls, context.urlExclusionPatterns);
|
|
||||||
context.logger.logLabeledInformation(
|
|
||||||
'URLs extracted for testing',
|
|
||||||
`${includedUrls.length} (after applying exclusion patterns; ${uniqueUrls.length - includedUrls.length} URLs ignored)`,
|
|
||||||
);
|
|
||||||
return includedUrls;
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractUrlsFromApplication(application: IApplication): string[] {
|
|
||||||
return [ // Get all executables
|
|
||||||
...application.collections.flatMap((c) => c.getAllCategories()),
|
|
||||||
...application.collections.flatMap((c) => c.getAllScripts()),
|
|
||||||
]
|
|
||||||
// Get all docs
|
|
||||||
.flatMap((documentable) => documentable.docs)
|
|
||||||
// Parse all URLs
|
|
||||||
.flatMap((docString) => extractUrlsExcludingCodeBlocks(docString));
|
|
||||||
}
|
|
||||||
|
|
||||||
function filterDuplicateUrls(urls: readonly string[]): string[] {
|
|
||||||
return urls.filter((url, index, array) => array.indexOf(url) === index);
|
|
||||||
}
|
|
||||||
|
|
||||||
function filterUrlsExcludingPatterns(
|
|
||||||
urls: readonly string[],
|
|
||||||
patterns: readonly RegExp[],
|
|
||||||
): string[] {
|
|
||||||
return urls.filter((url) => !patterns.some((pattern) => pattern.test(url)));
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractUrlsExcludingCodeBlocks(textWithInlineCode: string): string[] {
|
|
||||||
/*
|
|
||||||
Matches URLs:
|
|
||||||
- Excludes inline code blocks as they may contain URLs not intended for user interaction
|
|
||||||
and not guaranteed to support expected HTTP methods, leading to false-negatives.
|
|
||||||
- Supports URLs containing parentheses, avoiding matches within code that might not represent
|
|
||||||
actual links.
|
|
||||||
*/
|
|
||||||
const nonCodeBlockUrlRegex = /(?<!`)(https?:\/\/[^\s`"<>()]+(?:\([^\s`"<>()]*\))?[^\s`"<>()]*)/g;
|
|
||||||
return textWithInlineCode.match(nonCodeBlockUrlRegex) || [];
|
|
||||||
}
|
|
||||||
@@ -10,10 +10,7 @@ export async function getUrlStatusesInParallel(
|
|||||||
): Promise<UrlStatus[]> {
|
): Promise<UrlStatus[]> {
|
||||||
// urls = ['https://privacy.sexy']; // Comment out this line to use a hardcoded URL for testing.
|
// urls = ['https://privacy.sexy']; // Comment out this line to use a hardcoded URL for testing.
|
||||||
const uniqueUrls = Array.from(new Set(urls));
|
const uniqueUrls = Array.from(new Set(urls));
|
||||||
const defaultedDomainOptions: Required<DomainOptions> = {
|
const defaultedDomainOptions = { ...DefaultDomainOptions, ...options?.domainOptions };
|
||||||
...DefaultDomainOptions,
|
|
||||||
...options?.domainOptions,
|
|
||||||
};
|
|
||||||
console.log('Batch request options applied:', defaultedDomainOptions);
|
console.log('Batch request options applied:', defaultedDomainOptions);
|
||||||
const results = await request(uniqueUrls, defaultedDomainOptions, options);
|
const results = await request(uniqueUrls, defaultedDomainOptions, options);
|
||||||
return results;
|
return results;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { indentText } from '@tests/shared/Text';
|
|
||||||
import { fetchWithTimeout } from './FetchWithTimeout';
|
import { fetchWithTimeout } from './FetchWithTimeout';
|
||||||
import { getDomainFromUrl } from './UrlDomainProcessing';
|
import { getDomainFromUrl } from './UrlDomainProcessing';
|
||||||
|
|
||||||
@@ -8,12 +7,8 @@ export function fetchFollow(
|
|||||||
fetchOptions?: Partial<RequestInit>,
|
fetchOptions?: Partial<RequestInit>,
|
||||||
followOptions?: Partial<FollowOptions>,
|
followOptions?: Partial<FollowOptions>,
|
||||||
): Promise<Response> {
|
): Promise<Response> {
|
||||||
const defaultedFollowOptions: Required<FollowOptions> = {
|
const defaultedFollowOptions = { ...DefaultFollowOptions, ...followOptions };
|
||||||
...DefaultFollowOptions,
|
if (followRedirects(defaultedFollowOptions)) {
|
||||||
...followOptions,
|
|
||||||
};
|
|
||||||
console.log(indentText(`Follow options: ${JSON.stringify(defaultedFollowOptions)}`));
|
|
||||||
if (!followRedirects(defaultedFollowOptions)) {
|
|
||||||
return fetchWithTimeout(url, timeoutInMs, fetchOptions);
|
return fetchWithTimeout(url, timeoutInMs, fetchOptions);
|
||||||
}
|
}
|
||||||
fetchOptions = { ...fetchOptions, redirect: 'manual' /* handled manually */, mode: 'cors' };
|
fetchOptions = { ...fetchOptions, redirect: 'manual' /* handled manually */, mode: 'cors' };
|
||||||
@@ -27,6 +22,8 @@ export function fetchFollow(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// "cors" | "navigate" | "no-cors" | "same-origin";
|
||||||
|
|
||||||
export interface FollowOptions {
|
export interface FollowOptions {
|
||||||
readonly followRedirects?: boolean;
|
readonly followRedirects?: boolean;
|
||||||
readonly maximumRedirectFollowDepth?: number;
|
readonly maximumRedirectFollowDepth?: number;
|
||||||
@@ -101,11 +98,11 @@ class CookieStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function followRedirects(options: FollowOptions): boolean {
|
function followRedirects(options: FollowOptions): boolean {
|
||||||
if (options.followRedirects !== true) {
|
if (!options.followRedirects) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (options.maximumRedirectFollowDepth === undefined || options.maximumRedirectFollowDepth <= 0) {
|
if (options.maximumRedirectFollowDepth === 0) {
|
||||||
throw new Error('Invalid followRedirects configuration: maximumRedirectFollowDepth must be a positive integer');
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,10 +100,3 @@ console.log(`Status code: ${status.code}`);
|
|||||||
- **`enableCookies`** (*boolean*), default: `true`
|
- **`enableCookies`** (*boolean*), default: `true`
|
||||||
- Enables cookie storage to facilitate seamless navigation through login or other authentication challenges.
|
- Enables cookie storage to facilitate seamless navigation through login or other authentication challenges.
|
||||||
- 💡 Helps to over-come sign-in challenges with callbacks.
|
- 💡 Helps to over-come sign-in challenges with callbacks.
|
||||||
- **`forceHttpGetForUrlPatterns`** (*array*), default: `[]`
|
|
||||||
- Specifies URL patterns that should always use an HTTP GET request instead of the default HTTP HEAD.
|
|
||||||
- This is useful for websites that do not respond to HEAD requests, such as those behind certain CDN or web application firewalls.
|
|
||||||
- Provide patterns as regular expressions (`RegExp`), allowing them to match any part of a URL.
|
|
||||||
- Examples:
|
|
||||||
- To match any URL starting with "https://example.com/api": `/^https:\/\/example\.com\/api/`
|
|
||||||
- To match any domain ending with "cloudflare.com": `/^https:\/\/.*\.cloudflare\.com\//`
|
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ export interface RequestOptions {
|
|||||||
readonly additionalHeadersUrlIgnore?: string[];
|
readonly additionalHeadersUrlIgnore?: string[];
|
||||||
readonly requestTimeoutInMs: number;
|
readonly requestTimeoutInMs: number;
|
||||||
readonly randomizeTlsFingerprint: boolean;
|
readonly randomizeTlsFingerprint: boolean;
|
||||||
readonly forceHttpGetForUrlPatterns: RegExp[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const DefaultOptions: Required<RequestOptions> = {
|
const DefaultOptions: Required<RequestOptions> = {
|
||||||
@@ -33,7 +32,6 @@ const DefaultOptions: Required<RequestOptions> = {
|
|||||||
additionalHeadersUrlIgnore: [],
|
additionalHeadersUrlIgnore: [],
|
||||||
requestTimeoutInMs: 60 /* seconds */ * 1000,
|
requestTimeoutInMs: 60 /* seconds */ * 1000,
|
||||||
randomizeTlsFingerprint: true,
|
randomizeTlsFingerprint: true,
|
||||||
forceHttpGetForUrlPatterns: [],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function fetchUrlStatusWithRetry(
|
function fetchUrlStatusWithRetry(
|
||||||
@@ -43,11 +41,7 @@ function fetchUrlStatusWithRetry(
|
|||||||
): Promise<UrlStatus> {
|
): Promise<UrlStatus> {
|
||||||
const fetchOptions = getFetchOptions(url, requestOptions);
|
const fetchOptions = getFetchOptions(url, requestOptions);
|
||||||
return retryWithExponentialBackOff(async () => {
|
return retryWithExponentialBackOff(async () => {
|
||||||
console.log(`🚀 Initiating request for URL: ${url}`);
|
console.log(`Initiating request for URL: ${url}`);
|
||||||
console.log(indentText([
|
|
||||||
`HTTP method: ${fetchOptions.method}`,
|
|
||||||
`Request options: ${JSON.stringify(requestOptions)}`,
|
|
||||||
].join('\n')));
|
|
||||||
let result: UrlStatus;
|
let result: UrlStatus;
|
||||||
try {
|
try {
|
||||||
const response = await fetchFollow(
|
const response = await fetchFollow(
|
||||||
@@ -62,8 +56,7 @@ function fetchUrlStatusWithRetry(
|
|||||||
url,
|
url,
|
||||||
error: [
|
error: [
|
||||||
'Error:', indentText(JSON.stringify(err, null, '\t') || err.toString()),
|
'Error:', indentText(JSON.stringify(err, null, '\t') || err.toString()),
|
||||||
'Fetch options:', indentText(JSON.stringify(fetchOptions, null, '\t')),
|
'Options:', indentText(JSON.stringify(fetchOptions, null, '\t')),
|
||||||
'Request options:', indentText(JSON.stringify(requestOptions, null, '\t')),
|
|
||||||
'TLS:', indentText(getTlsContextInfo()),
|
'TLS:', indentText(getTlsContextInfo()),
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
};
|
};
|
||||||
@@ -78,7 +71,7 @@ function getFetchOptions(url: string, options: Required<RequestOptions>): Reques
|
|||||||
? {}
|
? {}
|
||||||
: options.additionalHeaders;
|
: options.additionalHeaders;
|
||||||
return {
|
return {
|
||||||
method: getHttpMethod(url, options),
|
method: 'GET', // Fetch only headers without the full response body for better speed
|
||||||
headers: {
|
headers: {
|
||||||
...getDefaultHeaders(url),
|
...getDefaultHeaders(url),
|
||||||
...additionalHeaders,
|
...additionalHeaders,
|
||||||
@@ -87,14 +80,6 @@ function getFetchOptions(url: string, options: Required<RequestOptions>): Reques
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHttpMethod(url: string, options: Required<RequestOptions>): 'HEAD' | 'GET' {
|
|
||||||
if (options.forceHttpGetForUrlPatterns.some((pattern) => url.match(pattern))) {
|
|
||||||
return 'GET';
|
|
||||||
}
|
|
||||||
// By default fetch only headers without the full response body for better speed
|
|
||||||
return 'HEAD';
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDefaultHeaders(url: string): Record<string, string> {
|
function getDefaultHeaders(url: string): Record<string, string> {
|
||||||
return {
|
return {
|
||||||
// Needed for websites that filter out non-browser user agents.
|
// Needed for websites that filter out non-browser user agents.
|
||||||
|
|||||||
@@ -23,12 +23,12 @@ import { indentText } from '@tests/shared/Text';
|
|||||||
|
|
||||||
export function randomizeTlsFingerprint() {
|
export function randomizeTlsFingerprint() {
|
||||||
tls.DEFAULT_CIPHERS = getShuffledCiphers().join(':');
|
tls.DEFAULT_CIPHERS = getShuffledCiphers().join(':');
|
||||||
console.log(indentText(
|
console.log(
|
||||||
`TLS context:\n${indentText([
|
[
|
||||||
'Original ciphers:', indentText(constants.defaultCipherList),
|
'Original ciphers:', indentText(constants.defaultCipherList),
|
||||||
'Current ciphers:', indentText(getTlsContextInfo()),
|
'Current context', indentText(getTlsContextInfo()),
|
||||||
].join('\n'))}`,
|
].join('\n'),
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTlsContextInfo(): string {
|
export function getTlsContextInfo(): string {
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
import { indentText } from '@tests/shared/Text';
|
|
||||||
|
|
||||||
export class TestExecutionDetailsLogger {
|
|
||||||
public logTestSectionStartDelimiter(): void {
|
|
||||||
this.logSectionDelimiterLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
public logTestSectionEndDelimiter(): void {
|
|
||||||
this.logSectionDelimiterLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
public logLabeledInformation(
|
|
||||||
label: string,
|
|
||||||
detailedInformation: string,
|
|
||||||
): void {
|
|
||||||
console.log([
|
|
||||||
`${label}:`,
|
|
||||||
indentText(detailedInformation),
|
|
||||||
].join('\n'));
|
|
||||||
}
|
|
||||||
|
|
||||||
private logSectionDelimiterLine(): void {
|
|
||||||
const horizontalLine = '─'.repeat(40);
|
|
||||||
console.log(horizontalLine);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +1,19 @@
|
|||||||
import { test, expect } from 'vitest';
|
import { test, expect } from 'vitest';
|
||||||
import { parseApplication } from '@/application/Parser/ApplicationParser';
|
import { parseApplication } from '@/application/Parser/ApplicationParser';
|
||||||
|
import type { IApplication } from '@/domain/IApplication';
|
||||||
import { indentText } from '@tests/shared/Text';
|
import { indentText } from '@tests/shared/Text';
|
||||||
import { formatAssertionMessage } from '@tests/shared/FormatAssertionMessage';
|
import { formatAssertionMessage } from '@tests/shared/FormatAssertionMessage';
|
||||||
import { shuffle } from '@/application/Common/Shuffle';
|
|
||||||
import { type UrlStatus, formatUrlStatus } from './StatusChecker/UrlStatus';
|
import { type UrlStatus, formatUrlStatus } from './StatusChecker/UrlStatus';
|
||||||
import { getUrlStatusesInParallel, type BatchRequestOptions } from './StatusChecker/BatchStatusChecker';
|
import { getUrlStatusesInParallel, type BatchRequestOptions } from './StatusChecker/BatchStatusChecker';
|
||||||
import { TestExecutionDetailsLogger } from './TestExecutionDetailsLogger';
|
|
||||||
import { extractDocumentationUrls } from './DocumentationUrlExtractor';
|
|
||||||
|
|
||||||
// arrange
|
// arrange
|
||||||
const logger = new TestExecutionDetailsLogger();
|
|
||||||
logger.logTestSectionStartDelimiter();
|
|
||||||
const app = parseApplication();
|
const app = parseApplication();
|
||||||
let urls = extractDocumentationUrls({
|
const urls = collectUniqueUrls({
|
||||||
logger,
|
application: app,
|
||||||
urlExclusionPatterns: [
|
excludePatterns: [
|
||||||
/^https:\/\/archive\.ph/, // Drops HEAD/GET requests via fetch/curl, responding to Postman/Chromium.
|
/^https:\/\/archive\.ph/, // Drops HEAD/GET requests via fetch/curl, responding to Postman/Chromium.
|
||||||
],
|
],
|
||||||
application: app,
|
|
||||||
});
|
});
|
||||||
urls = filterUrlsToEnvironmentCheckLimit(urls);
|
|
||||||
logger.logLabeledInformation('URLs submitted for testing', urls.length.toString());
|
|
||||||
const requestOptions: BatchRequestOptions = {
|
const requestOptions: BatchRequestOptions = {
|
||||||
domainOptions: {
|
domainOptions: {
|
||||||
sameDomainParallelize: false, // be nice to our third-party servers
|
sameDomainParallelize: false, // be nice to our third-party servers
|
||||||
@@ -37,64 +30,53 @@ const requestOptions: BatchRequestOptions = {
|
|||||||
enableCookies: true,
|
enableCookies: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
logger.logLabeledInformation('HTTP request options', JSON.stringify(requestOptions, null, 2));
|
|
||||||
const testTimeoutInMs = urls.length * 60 /* seconds */ * 1000;
|
const testTimeoutInMs = urls.length * 60 /* seconds */ * 1000;
|
||||||
logger.logLabeledInformation('Scheduled test duration', convertMillisecondsToHumanReadableFormat(testTimeoutInMs));
|
|
||||||
logger.logTestSectionEndDelimiter();
|
|
||||||
test(`all URLs (${urls.length}) should be alive`, async () => {
|
test(`all URLs (${urls.length}) should be alive`, async () => {
|
||||||
// act
|
// act
|
||||||
const results = await getUrlStatusesInParallel(urls, requestOptions);
|
const results = await getUrlStatusesInParallel(urls, requestOptions);
|
||||||
// assert
|
// assert
|
||||||
const deadUrls = results.filter((r) => r.code === undefined || !isOkStatusCode(r.code));
|
const deadUrls = results.filter((r) => r.code === undefined || !isOkStatusCode(r.code));
|
||||||
expect(deadUrls).to.have.lengthOf(
|
expect(deadUrls).to.have.lengthOf(0, formatAssertionMessage([formatUrlStatusReport(deadUrls)]));
|
||||||
0,
|
|
||||||
formatAssertionMessage([createReportForDeadUrlStatuses(deadUrls)]),
|
|
||||||
);
|
|
||||||
}, testTimeoutInMs);
|
}, testTimeoutInMs);
|
||||||
|
|
||||||
function isOkStatusCode(statusCode: number): boolean {
|
function isOkStatusCode(statusCode: number): boolean {
|
||||||
return statusCode >= 200 && statusCode < 300;
|
return statusCode >= 200 && statusCode < 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createReportForDeadUrlStatuses(deadUrlStatuses: readonly UrlStatus[]): string {
|
function collectUniqueUrls(
|
||||||
|
options: {
|
||||||
|
readonly application: IApplication,
|
||||||
|
readonly excludePatterns?: readonly RegExp[],
|
||||||
|
},
|
||||||
|
): string[] {
|
||||||
|
return [ // Get all nodes
|
||||||
|
...options.application.collections.flatMap((c) => c.getAllCategories()),
|
||||||
|
...options.application.collections.flatMap((c) => c.getAllScripts()),
|
||||||
|
]
|
||||||
|
// Get all docs
|
||||||
|
.flatMap((documentable) => documentable.docs)
|
||||||
|
// Parse all URLs
|
||||||
|
.flatMap((docString) => extractUrls(docString))
|
||||||
|
// Remove duplicates
|
||||||
|
.filter((url, index, array) => array.indexOf(url) === index)
|
||||||
|
// Exclude certain URLs based on patterns
|
||||||
|
.filter((url) => !shouldExcludeUrl(url, options.excludePatterns ?? []));
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldExcludeUrl(url: string, patterns: readonly RegExp[]): boolean {
|
||||||
|
return patterns.some((pattern) => pattern.test(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatUrlStatusReport(deadUrlStatuses: readonly UrlStatus[]): string {
|
||||||
return `\n${deadUrlStatuses.map((status) => indentText(formatUrlStatus(status))).join('\n---\n')}\n`;
|
return `\n${deadUrlStatuses.map((status) => indentText(formatUrlStatus(status))).join('\n---\n')}\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterUrlsToEnvironmentCheckLimit(originalUrls: string[]): string[] {
|
function extractUrls(textWithInlineCode: string): string[] {
|
||||||
const { RANDOMIZED_URL_CHECK_LIMIT } = process.env;
|
/*
|
||||||
logger.logLabeledInformation('URL check limit', RANDOMIZED_URL_CHECK_LIMIT || 'Unlimited');
|
Matches all URLs.
|
||||||
if (RANDOMIZED_URL_CHECK_LIMIT !== undefined && RANDOMIZED_URL_CHECK_LIMIT !== '') {
|
Inline code blocks contain URLs not intended for user interaction and not
|
||||||
const maxUrlsInTest = parseInt(RANDOMIZED_URL_CHECK_LIMIT, 10);
|
guaranteed to support expected HTTP methods, leading to false-negatives.
|
||||||
if (Number.isNaN(maxUrlsInTest)) {
|
*/
|
||||||
throw new Error(`Invalid URL limit: ${RANDOMIZED_URL_CHECK_LIMIT}`);
|
const nonCodeBlockUrlRegex = /(?<!`)(https?:\/\/[^\s`"<>()]+)/g;
|
||||||
}
|
return textWithInlineCode.match(nonCodeBlockUrlRegex) || [];
|
||||||
if (maxUrlsInTest < originalUrls.length) {
|
|
||||||
return shuffle(originalUrls).slice(0, maxUrlsInTest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return originalUrls;
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertMillisecondsToHumanReadableFormat(milliseconds: number): string {
|
|
||||||
const timeParts: string[] = [];
|
|
||||||
const addTimePart = (amount: number, label: string) => {
|
|
||||||
if (amount === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
timeParts.push(`${amount} ${label}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const hours = milliseconds / (1000 * 60 * 60);
|
|
||||||
const absoluteHours = Math.floor(hours);
|
|
||||||
addTimePart(absoluteHours, 'hours');
|
|
||||||
|
|
||||||
const minutes = (hours - absoluteHours) * 60;
|
|
||||||
const absoluteMinutes = Math.floor(minutes);
|
|
||||||
addTimePart(absoluteMinutes, 'minutes');
|
|
||||||
|
|
||||||
const seconds = (minutes - absoluteMinutes) * 60;
|
|
||||||
const absoluteSeconds = Math.floor(seconds);
|
|
||||||
addTimePart(absoluteSeconds, 'seconds');
|
|
||||||
|
|
||||||
return timeParts.join(', ');
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import { expectExists } from '@tests/shared/Assertions/ExpectExists';
|
import { expectExists } from '@tests/shared/Assertions/ExpectExists';
|
||||||
import { getCurrentHighlightRange } from './support/interactions/code-area';
|
|
||||||
import { selectAllScripts } from './support/interactions/script-selection';
|
|
||||||
import { openCard } from './support/interactions/card';
|
import { openCard } from './support/interactions/card';
|
||||||
|
|
||||||
describe('script selection highlighting', () => {
|
describe('script selection highlighting', () => {
|
||||||
@@ -8,12 +6,11 @@ describe('script selection highlighting', () => {
|
|||||||
it('highlights more when multiple scripts are selected', () => {
|
it('highlights more when multiple scripts are selected', () => {
|
||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
selectLastScript();
|
selectLastScript();
|
||||||
getNonZeroCurrentHighlightRangeValue().then((lastScriptHighlightRange) => {
|
getCurrentHighlightRange((lastScriptHighlightRange) => {
|
||||||
cy.log(`Highlight height for last script: ${lastScriptHighlightRange}`);
|
cy.log(`Highlight height for last script: ${lastScriptHighlightRange}`);
|
||||||
expect(lastScriptHighlightRange).to.be.greaterThan(0);
|
|
||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
selectAllScripts();
|
selectAllScripts();
|
||||||
getNonZeroCurrentHighlightRangeValue().then((allScriptsHighlightRange) => {
|
getCurrentHighlightRange((allScriptsHighlightRange) => {
|
||||||
cy.log(`Highlight height for all scripts: ${allScriptsHighlightRange}`);
|
cy.log(`Highlight height for all scripts: ${allScriptsHighlightRange}`);
|
||||||
expect(allScriptsHighlightRange).to.be.greaterThan(lastScriptHighlightRange);
|
expect(allScriptsHighlightRange).to.be.greaterThan(lastScriptHighlightRange);
|
||||||
});
|
});
|
||||||
@@ -21,15 +18,6 @@ describe('script selection highlighting', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function getNonZeroCurrentHighlightRangeValue() {
|
|
||||||
return getCurrentHighlightRange()
|
|
||||||
.should('not.equal', '0')
|
|
||||||
.then((rangeValue) => {
|
|
||||||
expectExists(rangeValue);
|
|
||||||
return parseInt(rangeValue, 10);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectLastScript() {
|
function selectLastScript() {
|
||||||
openCard({
|
openCard({
|
||||||
cardIndex: -1, // last card
|
cardIndex: -1, // last card
|
||||||
@@ -38,3 +26,22 @@ function selectLastScript() {
|
|||||||
.last()
|
.last()
|
||||||
.click({ force: true });
|
.click({ force: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function selectAllScripts() {
|
||||||
|
cy.contains('span', 'All')
|
||||||
|
.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentHighlightRange(
|
||||||
|
callback: (highlightedRange: number) => void,
|
||||||
|
) {
|
||||||
|
cy
|
||||||
|
.get('#codeEditor')
|
||||||
|
.invoke('attr', 'data-test-highlighted-range')
|
||||||
|
.should('not.be.empty')
|
||||||
|
.and('not.equal', '0')
|
||||||
|
.then((range) => {
|
||||||
|
expectExists(range);
|
||||||
|
callback(parseInt(range, 10));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,37 +1,38 @@
|
|||||||
import { formatAssertionMessage } from '@tests/shared/FormatAssertionMessage';
|
import { formatAssertionMessage } from '@tests/shared/FormatAssertionMessage';
|
||||||
|
import { ViewportTestScenarios } from './support/scenarios/viewport-test-scenarios';
|
||||||
|
|
||||||
export function assertLayoutStability(selector: string, action: ()=> void): void {
|
describe('Modal interaction and layout stability', () => {
|
||||||
// arrange
|
ViewportTestScenarios.forEach(({ // some shifts are observed only on extra small or large screens
|
||||||
let initialMetrics: ViewportMetrics | undefined;
|
name, width, height,
|
||||||
captureViewportMetrics(selector, (metrics) => {
|
}) => {
|
||||||
initialMetrics = metrics;
|
it(name, () => {
|
||||||
});
|
cy.viewport(width, height);
|
||||||
// act
|
cy.visit('/');
|
||||||
action();
|
|
||||||
// assert
|
|
||||||
captureViewportMetrics(selector, (metrics) => {
|
|
||||||
const finalMetrics = metrics;
|
|
||||||
expect(initialMetrics).to.deep.equal(finalMetrics, formatAssertionMessage([
|
|
||||||
`Expected (initial metrics before action): ${JSON.stringify(initialMetrics)}`,
|
|
||||||
`Actual (final metrics after action): ${JSON.stringify(finalMetrics)}`,
|
|
||||||
]));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function captureViewportMetrics(
|
let metricsBeforeModal: ViewportMetrics | undefined;
|
||||||
selector: string,
|
|
||||||
callback: (metrics: ViewportMetrics) => void,
|
captureViewportMetrics((metrics) => {
|
||||||
): void {
|
metricsBeforeModal = metrics;
|
||||||
cy.window().then((win) => {
|
|
||||||
cy.get(selector)
|
|
||||||
.then((elements) => {
|
|
||||||
const element = elements[0];
|
|
||||||
const position = getElementViewportMetrics(element, win);
|
|
||||||
cy.log(`Captured metrics (\`${selector}\`): ${JSON.stringify(position)}`);
|
|
||||||
callback(position);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
cy
|
||||||
|
.contains('button', 'Privacy')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
cy
|
||||||
|
.get('.modal-content')
|
||||||
|
.should('be.visible');
|
||||||
|
|
||||||
|
captureViewportMetrics((metrics) => {
|
||||||
|
const metricsAfterModal = metrics;
|
||||||
|
expect(metricsBeforeModal).to.deep.equal(metricsAfterModal, formatAssertionMessage([
|
||||||
|
`Expected (initial metrics before modal): ${JSON.stringify(metricsBeforeModal)}`,
|
||||||
|
`Actual (metrics after modal is opened): ${JSON.stringify(metricsAfterModal)}`,
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
|
||||||
interface ViewportMetrics {
|
interface ViewportMetrics {
|
||||||
readonly x: number;
|
readonly x: number;
|
||||||
@@ -43,6 +44,17 @@ interface ViewportMetrics {
|
|||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function captureViewportMetrics(callback: (metrics: ViewportMetrics) => void): void {
|
||||||
|
cy.window().then((win) => {
|
||||||
|
cy.get('body')
|
||||||
|
.then((body) => {
|
||||||
|
const position = getElementViewportMetrics(body[0], win);
|
||||||
|
cy.log(`Captured metrics: ${JSON.stringify(position)}`);
|
||||||
|
callback(position);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function getElementViewportMetrics(element: HTMLElement, win: Window): ViewportMetrics {
|
function getElementViewportMetrics(element: HTMLElement, win: Window): ViewportMetrics {
|
||||||
const elementXRelativeToViewport = getElementXRelativeToViewport(element, win);
|
const elementXRelativeToViewport = getElementXRelativeToViewport(element, win);
|
||||||
const elementYRelativeToViewport = getElementYRelativeToViewport(element, win);
|
const elementYRelativeToViewport = getElementYRelativeToViewport(element, win);
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
import { ViewportTestScenarios } from './support/scenarios/viewport-test-scenarios';
|
|
||||||
import { openCard } from './support/interactions/card';
|
|
||||||
import { selectAllScripts, unselectAllScripts } from './support/interactions/script-selection';
|
|
||||||
import { assertLayoutStability } from './support/assert/layout-stability';
|
|
||||||
|
|
||||||
describe('Layout stability', () => {
|
|
||||||
ViewportTestScenarios.forEach(({ // some shifts are observed only on extra small or large screens
|
|
||||||
name, width, height,
|
|
||||||
}) => {
|
|
||||||
// Regression test for a bug where opening a modal caused layout shift
|
|
||||||
describe('Modal interaction', () => {
|
|
||||||
it(name, () => {
|
|
||||||
// arrange
|
|
||||||
cy.viewport(width, height);
|
|
||||||
cy.visit('/');
|
|
||||||
// act & assert
|
|
||||||
assertLayoutStability('body', () => {
|
|
||||||
cy
|
|
||||||
.contains('button', 'Privacy')
|
|
||||||
.click();
|
|
||||||
cy
|
|
||||||
.get('.modal-content-content')
|
|
||||||
.should('be.visible');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Regression test for a bug where selecting a script with an open card caused layout shift
|
|
||||||
describe('Initial script selection', () => {
|
|
||||||
it(name, () => {
|
|
||||||
// arrange
|
|
||||||
cy.viewport(width, height);
|
|
||||||
cy.visit('/');
|
|
||||||
cy.contains('span', 'Windows')
|
|
||||||
.click();
|
|
||||||
// act & assert
|
|
||||||
assertLayoutStability('#app', () => {
|
|
||||||
openCard({
|
|
||||||
cardIndex: 0,
|
|
||||||
});
|
|
||||||
selectAllScripts();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Regression test for a bug where unselecting selected with an open card caused layout shift
|
|
||||||
describe('Deselection script selection', () => {
|
|
||||||
it(name, () => {
|
|
||||||
// arrange
|
|
||||||
cy.viewport(width, height);
|
|
||||||
cy.visit('/');
|
|
||||||
cy.contains('span', 'Windows')
|
|
||||||
.click();
|
|
||||||
openCard({
|
|
||||||
cardIndex: 0,
|
|
||||||
});
|
|
||||||
selectAllScripts();
|
|
||||||
// act & assert
|
|
||||||
assertLayoutStability('#app', () => {
|
|
||||||
unselectAllScripts();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -4,27 +4,3 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import './commands';
|
import './commands';
|
||||||
|
|
||||||
// Mitigates a Chrome-specific 'ResizeObserver' error in Cypress tests.
|
|
||||||
// The 'ResizeObserver loop limit exceeded' error is non-critical but can cause
|
|
||||||
// false negatives in CI/CD environments, particularly with GitHub runners.
|
|
||||||
// The issue is absent in actual browser usage and local Cypress testing.
|
|
||||||
// Community discussions and contributions have led to this handler being
|
|
||||||
// recommended as a user-level fix rather than a Cypress core inclusion.
|
|
||||||
// Relevant discussions and attempted core fixes:
|
|
||||||
// - Original fix suggestion: https://github.com/cypress-io/cypress/issues/8418#issuecomment-992564877
|
|
||||||
// - Proposed Cypress core PRs:
|
|
||||||
// https://github.com/cypress-io/cypress/pull/20257
|
|
||||||
// https://github.com/cypress-io/cypress/pull/20284
|
|
||||||
// - Current issue tracking: https://github.com/cypress-io/cypress/issues/20341
|
|
||||||
// - Related Quasar framework issue: https://github.com/quasarframework/quasar/issues/2233
|
|
||||||
// - Chromium bug tracker discussion: https://issues.chromium.org/issues/41369140
|
|
||||||
// - Stack Overflow on safely ignoring the error:
|
|
||||||
// https://stackoverflow.com/questions/49384120/resizeobserver-loop-limit-exceeded/50387233#50387233
|
|
||||||
// https://stackoverflow.com/questions/63653605/resizeobserver-loop-limit-exceeded-api-is-never-used/63653711#63653711
|
|
||||||
// - Spec issue related to 'ResizeObserver': https://github.com/WICG/resize-observer/issues/38
|
|
||||||
Cypress.on(
|
|
||||||
'uncaught:exception',
|
|
||||||
// Ignore this specific error to prevent false test failures
|
|
||||||
(err) => !err.message.includes('ResizeObserver loop limit exceeded'),
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
export function getCurrentHighlightRange() {
|
|
||||||
return cy
|
|
||||||
.get('#codeEditor')
|
|
||||||
.invoke('attr', 'data-test-highlighted-range')
|
|
||||||
.should('be.a', 'string')
|
|
||||||
.should('not.equal', '');
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { getCurrentHighlightRange } from './code-area';
|
|
||||||
|
|
||||||
export function selectAllScripts() {
|
|
||||||
cy.contains('span', 'All')
|
|
||||||
.click();
|
|
||||||
getCurrentHighlightRange()
|
|
||||||
.should('not.equal', '0');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function unselectAllScripts() {
|
|
||||||
cy.contains('span', 'None')
|
|
||||||
.click();
|
|
||||||
getCurrentHighlightRange()
|
|
||||||
.should('equal', '0');
|
|
||||||
}
|
|
||||||
@@ -12,9 +12,7 @@ describe('TreeView', () => {
|
|||||||
it('renders all provided root nodes correctly', async () => {
|
it('renders all provided root nodes correctly', async () => {
|
||||||
// arrange
|
// arrange
|
||||||
const nodes = createSampleNodes();
|
const nodes = createSampleNodes();
|
||||||
const { wrapper } = mountWrapperComponent({
|
const wrapper = createTreeViewWrapper(nodes);
|
||||||
initialNodeData: nodes,
|
|
||||||
});
|
|
||||||
|
|
||||||
// act
|
// act
|
||||||
await waitForStableDom(wrapper.element);
|
await waitForStableDom(wrapper.element);
|
||||||
@@ -35,12 +33,10 @@ describe('TreeView', () => {
|
|||||||
const secondNodeLabel = 'Node 2';
|
const secondNodeLabel = 'Node 2';
|
||||||
const initialNodes: TreeInputNodeDataWithMetadata[] = [{ id: 'node1', data: { label: firstNodeLabel } }];
|
const initialNodes: TreeInputNodeDataWithMetadata[] = [{ id: 'node1', data: { label: firstNodeLabel } }];
|
||||||
const updatedNodes: TreeInputNodeDataWithMetadata[] = [{ id: 'node2', data: { label: secondNodeLabel } }];
|
const updatedNodes: TreeInputNodeDataWithMetadata[] = [{ id: 'node2', data: { label: secondNodeLabel } }];
|
||||||
const { wrapper, nodes } = mountWrapperComponent({
|
const wrapper = createTreeViewWrapper(initialNodes);
|
||||||
initialNodeData: initialNodes,
|
|
||||||
});
|
|
||||||
|
|
||||||
// act
|
// act
|
||||||
nodes.value = updatedNodes;
|
await wrapper.setProps({ nodes: updatedNodes });
|
||||||
await waitForStableDom(wrapper.element);
|
await waitForStableDom(wrapper.element);
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
@@ -49,17 +45,15 @@ describe('TreeView', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function mountWrapperComponent(options?: {
|
function createTreeViewWrapper(initialNodeData: readonly TreeInputNodeDataWithMetadata[]) {
|
||||||
readonly initialNodeData?: readonly TreeInputNodeDataWithMetadata[],
|
return mount(defineComponent({
|
||||||
}) {
|
|
||||||
const nodes = shallowRef(options?.initialNodeData ?? createSampleNodes());
|
|
||||||
const wrapper = mount(defineComponent({
|
|
||||||
components: {
|
components: {
|
||||||
TreeView,
|
TreeView,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
provideDependencies(new ApplicationContextStub());
|
provideDependencies(new ApplicationContextStub());
|
||||||
|
|
||||||
|
const nodes = shallowRef(initialNodeData);
|
||||||
const selectedLeafNodeIds = shallowRef<readonly string[]>([]);
|
const selectedLeafNodeIds = shallowRef<readonly string[]>([]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -78,10 +72,6 @@ function mountWrapperComponent(options?: {
|
|||||||
</TreeView>
|
</TreeView>
|
||||||
`,
|
`,
|
||||||
}));
|
}));
|
||||||
return {
|
|
||||||
wrapper,
|
|
||||||
nodes,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TreeInputMetadata {
|
interface TreeInputMetadata {
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
|
||||||
import { shuffle } from '@/application/Common/Shuffle';
|
|
||||||
|
|
||||||
describe('Shuffle', () => {
|
|
||||||
describe('shuffle', () => {
|
|
||||||
it('returns a new array', () => {
|
|
||||||
// arrange
|
|
||||||
const inputArray = ['a', 'b', 'c', 'd'];
|
|
||||||
// act
|
|
||||||
const result = shuffle(inputArray);
|
|
||||||
// assert
|
|
||||||
expect(result).not.to.equal(inputArray);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns an array of the same length', () => {
|
|
||||||
// arrange
|
|
||||||
const inputArray = ['a', 'b', 'c', 'd'];
|
|
||||||
// act
|
|
||||||
const result = shuffle(inputArray);
|
|
||||||
// assert
|
|
||||||
expect(result.length).toBe(inputArray.length);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('contains the same elements', () => {
|
|
||||||
// arrange
|
|
||||||
const inputArray = ['a', 'b', 'c', 'd'];
|
|
||||||
// act
|
|
||||||
const result = shuffle(inputArray);
|
|
||||||
// assert
|
|
||||||
expect(result).to.have.members(inputArray);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not modify the input array', () => {
|
|
||||||
// arrange
|
|
||||||
const inputArray = ['a', 'b', 'c', 'd'];
|
|
||||||
const inputArrayCopy = [...inputArray];
|
|
||||||
// act
|
|
||||||
shuffle(inputArray);
|
|
||||||
// assert
|
|
||||||
expect(inputArray).to.deep.equal(inputArrayCopy);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('handles an empty array correctly', () => {
|
|
||||||
// arrange
|
|
||||||
const inputArray: string[] = [];
|
|
||||||
// act
|
|
||||||
const result = shuffle(inputArray);
|
|
||||||
// assert
|
|
||||||
expect(result).have.lengthOf(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -2,7 +2,6 @@ import { describe, it, expect } from 'vitest';
|
|||||||
import { FunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection';
|
import { FunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection';
|
||||||
import { FunctionCallArgumentStub } from '@tests/unit/shared/Stubs/FunctionCallArgumentStub';
|
import { FunctionCallArgumentStub } from '@tests/unit/shared/Stubs/FunctionCallArgumentStub';
|
||||||
import { itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
import { itEachAbsentStringValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||||
import type { IFunctionCallArgument } from '@/application/Parser/Script/Compiler/Function/Call/Argument/IFunctionCallArgument';
|
|
||||||
|
|
||||||
describe('FunctionCallArgumentCollection', () => {
|
describe('FunctionCallArgumentCollection', () => {
|
||||||
describe('addArgument', () => {
|
describe('addArgument', () => {
|
||||||
@@ -21,25 +20,21 @@ describe('FunctionCallArgumentCollection', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('getAllParameterNames', () => {
|
describe('getAllParameterNames', () => {
|
||||||
describe('returns as expected', () => {
|
it('returns as expected', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const testCases: ReadonlyArray<{
|
const testCases = [{
|
||||||
readonly description: string;
|
name: 'no args',
|
||||||
readonly args: readonly IFunctionCallArgument[];
|
|
||||||
readonly expectedParameterNames: string[];
|
|
||||||
}> = [{
|
|
||||||
description: 'no args',
|
|
||||||
args: [],
|
args: [],
|
||||||
expectedParameterNames: [],
|
expected: [],
|
||||||
}, {
|
}, {
|
||||||
description: 'with some args',
|
name: 'with some args',
|
||||||
args: [
|
args: [
|
||||||
new FunctionCallArgumentStub().withParameterName('a-param-name'),
|
new FunctionCallArgumentStub().withParameterName('a-param-name'),
|
||||||
new FunctionCallArgumentStub().withParameterName('b-param-name')],
|
new FunctionCallArgumentStub().withParameterName('b-param-name')],
|
||||||
expectedParameterNames: ['a-param-name', 'b-param-name'],
|
expected: ['a-param-name', 'b-param-name'],
|
||||||
}];
|
}];
|
||||||
for (const testCase of testCases) {
|
for (const testCase of testCases) {
|
||||||
it(testCase.description, () => {
|
it(testCase.name, () => {
|
||||||
const sut = new FunctionCallArgumentCollection();
|
const sut = new FunctionCallArgumentCollection();
|
||||||
// act
|
// act
|
||||||
for (const arg of testCase.args) {
|
for (const arg of testCase.args) {
|
||||||
@@ -47,7 +42,7 @@ describe('FunctionCallArgumentCollection', () => {
|
|||||||
}
|
}
|
||||||
const actual = sut.getAllParameterNames();
|
const actual = sut.getAllParameterNames();
|
||||||
// assert
|
// assert
|
||||||
expect(actual).to.deep.equal(testCase.expectedParameterNames);
|
expect(actual).to.equal(testCase.expected);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ describe('SharedFunction', () => {
|
|||||||
// assert
|
// assert
|
||||||
expect(sut.name).equal(expected);
|
expect(sut.name).equal(expected);
|
||||||
});
|
});
|
||||||
describe('throws when absent', () => {
|
it('throws when absent', () => {
|
||||||
itEachAbsentStringValue((absentValue) => {
|
itEachAbsentStringValue((absentValue) => {
|
||||||
// arrange
|
// arrange
|
||||||
const expectedError = 'missing function name';
|
const expectedError = 'missing function name';
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { InjectionKeys } from '@/presentation/injectionSymbols';
|
|||||||
import { provideDependencies, type VueDependencyInjectionApi } from '@/presentation/bootstrapping/DependencyProvider';
|
import { provideDependencies, type VueDependencyInjectionApi } from '@/presentation/bootstrapping/DependencyProvider';
|
||||||
import { ApplicationContextStub } from '@tests/unit/shared/Stubs/ApplicationContextStub';
|
import { ApplicationContextStub } from '@tests/unit/shared/Stubs/ApplicationContextStub';
|
||||||
import { itIsSingleton } from '@tests/unit/shared/TestCases/SingletonTests';
|
import { itIsSingleton } from '@tests/unit/shared/TestCases/SingletonTests';
|
||||||
import type { IApplicationContext } from '@/application/Context/IApplicationContext';
|
|
||||||
|
|
||||||
describe('DependencyProvider', () => {
|
describe('DependencyProvider', () => {
|
||||||
describe('provideDependencies', () => {
|
describe('provideDependencies', () => {
|
||||||
@@ -75,25 +74,25 @@ function createSingletonTests() {
|
|||||||
const registeredObject = api.inject(injectionKey);
|
const registeredObject = api.inject(injectionKey);
|
||||||
expect(registeredObject).to.be.instanceOf(Object);
|
expect(registeredObject).to.be.instanceOf(Object);
|
||||||
});
|
});
|
||||||
describe('should return the same instance for singleton dependency', () => {
|
it('should return the same instance for singleton dependency', () => {
|
||||||
// arrange
|
|
||||||
const singletonContext = new ApplicationContextStub();
|
|
||||||
const api = new VueDependencyInjectionApiStub();
|
|
||||||
new ProvideDependenciesBuilder()
|
|
||||||
.withContext(singletonContext)
|
|
||||||
.withApi(api)
|
|
||||||
.provideDependencies();
|
|
||||||
// act
|
|
||||||
const getRegisteredInstance = () => api.inject(injectionKey);
|
|
||||||
// assert
|
|
||||||
itIsSingleton({
|
itIsSingleton({
|
||||||
getter: getRegisteredInstance,
|
getter: () => {
|
||||||
|
// arrange
|
||||||
|
const api = new VueDependencyInjectionApiStub();
|
||||||
|
// act
|
||||||
|
new ProvideDependenciesBuilder()
|
||||||
|
.withApi(api)
|
||||||
|
.provideDependencies();
|
||||||
|
// expect
|
||||||
|
const registeredObject = api.inject(injectionKey);
|
||||||
|
return registeredObject;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
class ProvideDependenciesBuilder {
|
class ProvideDependenciesBuilder {
|
||||||
private context: IApplicationContext = new ApplicationContextStub();
|
private context = new ApplicationContextStub();
|
||||||
|
|
||||||
private api: VueDependencyInjectionApi = new VueDependencyInjectionApiStub();
|
private api: VueDependencyInjectionApi = new VueDependencyInjectionApiStub();
|
||||||
|
|
||||||
@@ -102,11 +101,6 @@ class ProvideDependenciesBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public withContext(context: IApplicationContext): this {
|
|
||||||
this.context = context;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public provideDependencies() {
|
public provideDependencies() {
|
||||||
return provideDependencies(this.context, this.api);
|
return provideDependencies(this.context, this.api);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import { shallowMount } from '@vue/test-utils';
|
import { shallowMount } from '@vue/test-utils';
|
||||||
import RatingCircle from '@/presentation/components/Scripts/Menu/Recommendation/Rating/RatingCircle.vue';
|
import RatingCircle from '@/presentation/components/Scripts/Menu/Recommendation/Rating/RatingCircle.vue';
|
||||||
import { formatAssertionMessage } from '@tests/shared/FormatAssertionMessage';
|
|
||||||
|
|
||||||
const DOM_SVG_SELECTOR = 'svg';
|
const DOM_SVG_SELECTOR = 'svg';
|
||||||
const DOM_CIRCLE_SELECTOR = `${DOM_SVG_SELECTOR} > circle`;
|
const DOM_CIRCLE_SELECTOR = `${DOM_SVG_SELECTOR} > circle`;
|
||||||
@@ -40,6 +39,12 @@ describe('RatingCircle.vue', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('SVG and circle styles', () => {
|
describe('SVG and circle styles', () => {
|
||||||
|
it('sets --circle-stroke-width style correctly', () => {
|
||||||
|
const wrapper = shallowMount(RatingCircle);
|
||||||
|
const svgElement = wrapper.find(DOM_SVG_SELECTOR).element;
|
||||||
|
expect(svgElement.style.getPropertyValue('--circle-stroke-width')).to.equal('2px');
|
||||||
|
});
|
||||||
|
|
||||||
it('renders circle with correct fill attribute when filled prop is true', () => {
|
it('renders circle with correct fill attribute when filled prop is true', () => {
|
||||||
const wrapper = shallowMount(RatingCircle, {
|
const wrapper = shallowMount(RatingCircle, {
|
||||||
propsData: {
|
propsData: {
|
||||||
@@ -53,49 +58,32 @@ describe('RatingCircle.vue', () => {
|
|||||||
|
|
||||||
it('renders circle with the correct viewBox property', () => {
|
it('renders circle with the correct viewBox property', () => {
|
||||||
const wrapper = shallowMount(RatingCircle);
|
const wrapper = shallowMount(RatingCircle);
|
||||||
const circleElement = wrapper.find(DOM_SVG_SELECTOR);
|
const circle = wrapper.find(DOM_SVG_SELECTOR);
|
||||||
|
|
||||||
expect(circleElement.attributes('viewBox')).to.equal('-1 -1 22 22');
|
expect(circle.attributes('viewBox')).to.equal('-1 -1 22 22');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('circle attributes', () => {
|
describe('circle attributes', () => {
|
||||||
const testScenarios: ReadonlyArray<{
|
it('renders circle with the correct cx attribute', () => {
|
||||||
readonly attributeKey: string;
|
const wrapper = shallowMount(RatingCircle);
|
||||||
readonly expectedValue: string;
|
const circleElement = wrapper.find(DOM_CIRCLE_SELECTOR);
|
||||||
}> = [
|
|
||||||
{
|
expect(circleElement.attributes('cx')).to.equal('10'); // Based on circleDiameterInPx = 20
|
||||||
attributeKey: 'stroke-width',
|
});
|
||||||
expectedValue: '2px', // Based on circleStrokeWidthInPx = 2
|
|
||||||
},
|
it('renders circle with the correct cy attribute', () => {
|
||||||
{
|
const wrapper = shallowMount(RatingCircle);
|
||||||
attributeKey: 'cx',
|
const circleElement = wrapper.find(DOM_CIRCLE_SELECTOR);
|
||||||
expectedValue: '10', // Based on circleDiameterInPx = 20
|
|
||||||
},
|
expect(circleElement.attributes('cy')).to.equal('10'); // Based on circleDiameterInPx = 20
|
||||||
{
|
});
|
||||||
attributeKey: 'cy',
|
|
||||||
expectedValue: '10', // Based on circleStrokeWidthInPx = 2
|
it('renders circle with the correct r attribute', () => {
|
||||||
},
|
const wrapper = shallowMount(RatingCircle);
|
||||||
{
|
const circleElement = wrapper.find(DOM_CIRCLE_SELECTOR);
|
||||||
attributeKey: 'r',
|
|
||||||
expectedValue: '9', // Based on circleRadiusWithoutStrokeInPx = circleDiameterInPx / 2 - circleStrokeWidthInPx / 2
|
expect(circleElement.attributes('r')).to.equal('9'); // Based on circleRadiusWithoutStrokeInPx = circleDiameterInPx / 2 - circleStrokeWidthInPx / 2
|
||||||
},
|
|
||||||
];
|
|
||||||
testScenarios.forEach(({
|
|
||||||
attributeKey, expectedValue,
|
|
||||||
}) => {
|
|
||||||
it(`renders circle with the correct ${attributeKey} attribute`, () => {
|
|
||||||
// act
|
|
||||||
const wrapper = shallowMount(RatingCircle);
|
|
||||||
const circleElement = wrapper.find(DOM_CIRCLE_SELECTOR);
|
|
||||||
const actualValue = circleElement.attributes(attributeKey);
|
|
||||||
// assert
|
|
||||||
expect(actualValue).to.equal(expectedValue, formatAssertionMessage([
|
|
||||||
`Expected value: ${expectedValue}`,
|
|
||||||
`Actual value: ${actualValue}`,
|
|
||||||
`Attribute: ${attributeKey}`,
|
|
||||||
]));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import type { TreeInputNodeData } from '@/presentation/components/Scripts/View/T
|
|||||||
|
|
||||||
describe('TreeNodeInitializerAndUpdater', () => {
|
describe('TreeNodeInitializerAndUpdater', () => {
|
||||||
describe('updateRootNodes', () => {
|
describe('updateRootNodes', () => {
|
||||||
describe('should throw an error if no data is provided', () => {
|
it('should throw an error if no data is provided', () => {
|
||||||
itEachAbsentCollectionValue<TreeInputNodeData>((absentValue) => {
|
itEachAbsentCollectionValue<TreeInputNodeData>((absentValue) => {
|
||||||
// arrange
|
// arrange
|
||||||
const expectedError = 'missing data';
|
const expectedError = 'missing data';
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { ScriptDiagnosticsCollectorStub } from '../../../shared/Stubs/ScriptDiag
|
|||||||
|
|
||||||
describe('IpcRegistration', () => {
|
describe('IpcRegistration', () => {
|
||||||
describe('registerAllIpcChannels', () => {
|
describe('registerAllIpcChannels', () => {
|
||||||
describe('registers all defined IPC channels', () => {
|
it('registers all defined IPC channels', () => {
|
||||||
Object.entries(IpcChannelDefinitions).forEach(([key, expectedChannel]) => {
|
Object.entries(IpcChannelDefinitions).forEach(([key, expectedChannel]) => {
|
||||||
it(key, () => {
|
it(key, () => {
|
||||||
// arrange
|
// arrange
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ function getPathAliasesFromTsConfig(): ViteAliasDefinitions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getElectronProcessSpecificModuleAliases(): ViteAliasDefinitions {
|
function getElectronProcessSpecificModuleAliases(): ViteAliasDefinitions {
|
||||||
// Workaround for Vite not being able to build tests with scoped Electron module imports.
|
// Workaround for scoped Electron module imports due to https://github.com/alex8088/electron-vite/issues/372
|
||||||
const electronProcessScopedModuleAliases = [
|
const electronProcessScopedModuleAliases = [
|
||||||
'electron/main',
|
'electron/main',
|
||||||
'electron/renderer',
|
'electron/renderer',
|
||||||
|
|||||||
Reference in New Issue
Block a user