Apply global styles for visual consistency

This commit centralizes the styling of key UI elements across the
project to ensure:

- Consistent look and feel.
- Enhanced code reusability.
- Simpified maintenance, improving development speed.

It establishes a uniform foundation that can be leveraged across
different parts of the project, even enabling the styling to be shared
across different websites (supporting issue #49).

Key changes:

- Apply the following shared styles globally:
  * Styling of code, blockquotes, superscripts, horizontal rules and
    anchors.
  * Vertical and horizontal spacing.
- Segregate base styling into dedicated SCSS files for clearer structure
  and increased maintainability.
- Remove custom styling from affected components, enabling global style
  reuse for visual uniformity, reduced redundancy, and enhanced
  semantics.

Other supporting changes:

- Rename `globals.scss` to `base.scss` for better clarity.
- Add `.editorconfig` for `.scss` files to ensure consistent whitespace
  usage.
- Remove `2` file from the project root, that was included in the source
  code by mistake.
- Remove unused font-face imports
This commit is contained in:
undergroundwires
2024-02-17 23:09:58 +01:00
parent d5bbc321f9
commit faa7a38a7d
27 changed files with 484 additions and 556 deletions

View File

@@ -3,7 +3,7 @@ root = true # Top-most EditorConfig file
[*]
end_of_line = lf
[*.{js,jsx,ts,tsx,vue,sh}]
[*.{js,jsx,ts,tsx,vue,sh,scss}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
@@ -24,3 +24,10 @@ indent_style = space
indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true
[*.{scss}] # SASS guidelines: https://archive.today/2024.02.16-232553/https://sass-guidelin.es/
indent_style = space
indent_size = 2 # Recommended by SASS guidelines
max_line_length = 100 # Recommended by SASS guidelines
trim_trailing_whitespace = true
insert_final_newline = true

31
2
View File

@@ -1,31 +0,0 @@
Show error on AV removal on desktop $264, $304
This solves $264 where users do not get error messages when running
script file fails due to antivirus intervention (it being blocking the
script file as soon as privacy.sexy generates it to run it). Now if the
desktop app users tries to save or run a script file and it afils due to
antivirus removal, they'll get a special error message with guiding next
steps.
- Add additional check to able to fail if the file writing fails. This
includes trying to reading the written file back as suggested in $304.
This successfully detects antivirus (Defender) intervation as read
file operation triggers the antivirus scan that deletes the file.
- Show directory and file path in error messages as suggested in $304.
- Show an error message with more detailed information if an antivirus
is detected.
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Tue Jan 16 16:23:08 2024 +0100
#
# On branch master
# Your branch is ahead of 'origin/master' by 1 commit.
# (use "git push" to publish your local commits)
#
# Changes to be committed:
# modified: ../../application/CodeRunner/CodeRunner.ts
# new file: NodeReliableFileWriter.ts
# new file: ReliableFileWriter.ts
#

View File

@@ -6,7 +6,7 @@
/* slabo-27px-regular - latin_latin-ext */
@font-face {
font-display: swap;
font-family: 'Slabo 27px';
font-style: normal;
font-weight: 400;
@@ -24,16 +24,6 @@
url('#{$base-assets-path}/fonts/yesteryear-v18-latin-regular.ttf') format('truetype'); /* Chrome 4+, Firefox 3.5+, IE 9+, Safari 3.1+, iOS 4.2+, Android Browser 2.2+ */
}
/* roboto-slab-regular - latin */
@font-face {
font-display: swap;
font-family: 'Roboto Slab';
font-style: normal;
font-weight: 400;
src: url('#{$base-assets-path}/fonts/roboto-slab-v34-latin-regular.woff2') format('woff2'), /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
url('#{$base-assets-path}/fonts/roboto-slab-v34-latin-regular.ttf') format('truetype'); /* Chrome 4+, Firefox 3.5+, IE 9+, Safari 3.1+, iOS 4.2+, Android Browser 2.2+ */
}
/* roboto-slab-regular - cyrillic_cyrillic-ext_greek_greek-ext_latin_latin-ext_vietnamese */
@font-face {
font-display: swap;
@@ -62,13 +52,3 @@
src: url('#{$base-assets-path}/fonts/source-code-pro-v23-latin-regular.woff2') format('woff2'), /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
url('#{$base-assets-path}/fonts/source-code-pro-v23-latin-regular.ttf') format('truetype'); /* Chrome 4+, Firefox 3.5+, IE 9+, Safari 3.1+, iOS 4.2+, Android Browser 2.2+ */
}
/* roboto-mono-regular - latin */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 400;
src: url('#{$base-assets-path}/fonts/roboto-mono-v23-latin-regular.woff2') format('woff2'), /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
url('#{$base-assets-path}/fonts/roboto-mono-v23-latin-regular.ttf') format('truetype'); /* Chrome 4+, Firefox 3.5+, IE 9+, Safari 3.1+, iOS 4.2+, Android Browser 2.2+ */
}

View File

@@ -1,29 +0,0 @@
/*
Defines global styles that applies to globally defined tags by default (body, main, article, div etc.)
*/
@use "@/presentation/assets/styles/colors" as *;
@use "@/presentation/assets/styles/mixins" as *;
@use "@/presentation/assets/styles/vite-path" as *;
@use "@/presentation/assets/styles/typography" as *;
* {
box-sizing: border-box;
}
a {
color: inherit;
cursor: pointer;
@include flat-button($disabled: false);
}
body {
background: $color-background;
font-family: $font-family-main;
font-size: $font-size-absolute-normal;
}
input {
font-family: unset; // Reset browser default
}

View File

@@ -118,3 +118,13 @@
}
}
}
@mixin set-property-ch-value-with-fallback($property, $value-in-ch) {
@supports (width: 1ch) {
#{$property}: #{$value-in-ch}ch;
}
// For browsers that does not support `ch` unit (e.g., Opera Mini):
$estimated-width-per-character-in-em: calc(1em / 2); // 1 character is approximately half the font size
$calculated-width-in-em: calc(#{$estimated-width-per-character-in-em} * #{$value-in-ch});
#{$property}: $calculated-width-in-em;
}

View File

@@ -1,4 +1,7 @@
@use "@/presentation/assets/styles/main" as *;
@use "@/presentation/assets/styles/colors" as *;
@use "@/presentation/assets/styles/mixins" as *;
@use "@/presentation/assets/styles/vite-path" as *;
@use "@/presentation/assets/styles/typography" as *;
@use 'sass:math';
@mixin code-block() {
@@ -23,13 +26,14 @@
$color-background,
$code-block-padding,
) {
$font-size-code: $font-size-relative-smaller; // Keep relative size to scale right with different text sizes around.
$font-size-code: $font-size-relative-smaller; // Keep relative size to scale right with different text sizes around.
$border-radius: 2px; // Subtle rounding still maintaining sharp design.
@include base-code {
font-family: $font-family-monospace;
border-radius: $border-radius;
font-size: $font-size-code;
color: $color-on-primary;
}
@include inline-code {

View File

@@ -0,0 +1,53 @@
/*
Defines global styles that applies to globally defined tags by default (body, main, article, div etc.).
Styles Fundamental HTML elements.
Contains foundational CSS rules that have a broad impact on the project's styling.
CSS Base applies a style foundation for HTML elements that is consistent for baseline browsers
*/
@use "@/presentation/assets/styles/colors" as *;
@use "@/presentation/assets/styles/mixins" as *;
@use "@/presentation/assets/styles/vite-path" as *;
@use "@/presentation/assets/styles/typography" as *;
@use "_code-styling" as *;
@use "_margin-padding" as *;
@use "_link-styling" as *;
$base-spacing: 1em;
* {
box-sizing: border-box;
}
body {
background: $color-background;
font-family: $font-family-main;
font-size: $font-size-absolute-normal;
@include apply-uniform-spacing($base-spacing: $base-spacing)
}
input {
font-family: unset; // Reset browser default
}
blockquote {
padding: 0 $base-spacing;
border-left: .25em solid $color-primary;
}
@include style-code-elements(
$code-block-padding: $base-spacing,
$color-background: $color-primary-darker,
);
hr {
opacity: 0.6;
}
sup {
@include reset-sup;
vertical-align: super;
font-size: $font-size-relative-smallest;
}

View File

@@ -0,0 +1,42 @@
@use "@/presentation/assets/styles/mixins" as *;
@use "@/presentation/assets/styles/typography" as *;
@use 'sass:math';
a {
color: inherit;
cursor: pointer;
@include flat-button($disabled: false);
&[href] {
word-break: break-word; // Enables long URLs to wrap within the container, preventing horizontal overflow.
}
&[href^="http"]{
&:after {
display: inline-block;
content: '';
/*
Use mask element instead of content/background-image etc.
This way we can apply current font color to it to match the theme
*/
mask: url(@/presentation/assets/icons/external-link.svg) no-repeat 50% 50%;
mask-size: cover;
background-color: currentColor;
/*
Use absolute sizing instead of relative. Relative sizing looks bad and inconsistent if there are external elements
inside small text (such as inside `<sup>`) and bigger elements like in bigger text. Making them always have same size
make the text read and flow better.
*/
width: $font-size-absolute-x-small;
height: $font-size-absolute-x-small;
vertical-align: text-top;
@include set-property-ch-value-with-fallback(
$property: margin-left,
$value-in-ch: 0.25,
)
}
}
}

View File

@@ -0,0 +1,64 @@
@use 'sass:math';
@mixin no-margin($selectors) {
#{$selectors} {
margin: 0;
}
}
@mixin no-padding($selectors) {
#{$selectors} {
padding: 0;
}
}
@mixin left-padding($selectors, $horizontal-spacing) {
#{$selectors} {
padding-inline-start: $horizontal-spacing;
}
}
@mixin bottom-margin($selectors, $vertical-spacing) {
#{$selectors} {
&:not(:last-child) {
margin-bottom: $vertical-spacing;
}
}
}
@mixin apply-uniform-vertical-spacing($base-vertical-spacing) {
/* Reset default top/bottom margins added by browser. */
@include no-margin('p');
@include no-margin('h1, h2, h3, h4, h5, h6');
@include no-margin('blockquote');
@include no-margin('pre');
@include no-margin('hr');
@include no-margin('ul, ol');
/* Add spacing between elements using `margin-bottom` only (bottom-up instead of top-down strategy). */
$small-vertical-spacing: math.div($base-vertical-spacing, 2);
@include bottom-margin('p', $base-vertical-spacing);
@include bottom-margin('li > p', $small-vertical-spacing); // Reduce margin for paragraphs directly within list items to visually group related content.
@include bottom-margin('h1, h2, h3, h4, h5, h6', $small-vertical-spacing);
@include bottom-margin('ul, ol', $base-vertical-spacing);
@include bottom-margin('li', $small-vertical-spacing);
@include bottom-margin('table', $base-vertical-spacing);
@include bottom-margin('blockquote', $base-vertical-spacing);
@include bottom-margin('pre', $base-vertical-spacing);
@include bottom-margin('article', $base-vertical-spacing);
@include bottom-margin('hr', $base-vertical-spacing);
}
@mixin apply-uniform-horizontal-spacing($base-horizontal-spacing) {
/* Reset default left/right paddings added by browser. */
@include no-padding('ul, ol');
/* Add spacing for list items. */
$large-horizontal-spacing: $base-horizontal-spacing * 2;
@include left-padding('ul, ol', $large-horizontal-spacing);
}
@mixin apply-uniform-spacing($base-spacing) {
@include apply-uniform-vertical-spacing($base-spacing);
@include apply-uniform-horizontal-spacing($base-spacing);
}

View File

@@ -4,7 +4,7 @@
@forward "./typography";
@forward "./media";
@forward "./colors";
@forward "./globals";
@forward "./base";
@forward "./mixins";
@forward "./components/card";

View File

@@ -1,12 +1,12 @@
<template>
<div class="info-container">
<span class="info-container">
<TooltipWrapper>
<AppIcon icon="circle-info" />
<template #tooltip>
<slot />
</template>
</TooltipWrapper>
</div>
</span>
</template>
<script lang="ts">
@@ -23,9 +23,23 @@ export default defineComponent({
</script>
<style scoped lang="scss">
@use "@/presentation/assets/styles/main" as *;
@mixin apply-style-when-placed-after-non-text {
* + & {
@content;
}
}
.info-container {
display: inline-block;
margin-left: 0.15em; // Do not style icon itself to ensure correct tooltip alignment
vertical-align: middle;
vertical-align: text-top;
* + & { // If it's followed by any other element
vertical-align: middle;
@include set-property-ch-value-with-fallback(
$property: margin-left,
$value-in-ch: 0.5,
)
}
}
</style>

View File

@@ -1,32 +1,37 @@
<template>
<div class="instructions">
<section>
<p>
You have two alternatives to apply your selection.
</p>
<hr />
<p>
<strong>1. The easy alternative</strong>. Run your script without any manual steps by
<a :href="downloadUrl">downloading desktop version</a> of {{ appName }} on the
{{ osName }} system you wish to configure, and then click on the Run button. This is
recommended for most users.
</p>
<hr />
<p>
<strong>2. The hard (manual) alternative</strong>. This requires you to do additional manual
steps. If you are unsure how to follow the instructions, tap or hover on information
<InfoTooltip>Engage with icons like this for extra wisdom!</InfoTooltip>
icons near the steps, or follow the easy alternative described above.
</p>
<p>
<PlatformInstructionSteps :filename="filename" />
</p>
</div>
<article>
<h3>1. The Easy Alternative</h3>
<p>
Run your script without any manual steps by
<a :href="downloadUrl">downloading desktop version</a> of {{ appName }} on the
{{ osName }} system you wish to configure, and then click on the Run button. This is
recommended for most users.
</p>
</article>
<article>
<h3>2. The Hard (Manual) Alternative</h3>
<p>
This requires you to do additional manual
steps. If you are unsure how to follow the instructions, tap or hover on information
<InfoTooltip>Engage with icons like this for extra wisdom!</InfoTooltip>
icons near the steps, or follow the easy alternative described above.
</p>
<p>
<PlatformInstructionSteps :filename="filename" />
</p>
</article>
</section>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue';
import { injectKey } from '@/presentation/injectionSymbols';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { getOperatingSystemDisplayName } from '@/presentation/components/Shared/OperatingSystemNames';
import InfoTooltip from './InfoTooltip.vue';
import PlatformInstructionSteps from './Steps/PlatformInstructionSteps.vue';
@@ -55,7 +60,7 @@ export default defineComponent({
);
const osName = computed<string>(
() => renderOsName(operatingSystem.value),
() => getOperatingSystemDisplayName(operatingSystem.value),
);
return {
@@ -65,21 +70,7 @@ export default defineComponent({
};
},
});
function renderOsName(os: OperatingSystem): string {
switch (os) {
case OperatingSystem.Windows: return 'Windows';
case OperatingSystem.macOS: return 'macOS';
case OperatingSystem.Linux: return 'Linux';
default: throw new RangeError(`Cannot render os name: ${OperatingSystem[os]}`);
}
}
</script>
<style scoped lang="scss">
@use "@/presentation/assets/styles/main" as *;
.step {
margin: 10px 0;
}
</style>

View File

@@ -1,16 +1,16 @@
<template>
<span class="code-wrapper">
<code class="copyable-command">
<span class="dollar">$</span>
<code ref="codeElement"><slot /></code>
<div class="copy-action-container">
<span ref="copyableTextHolder"><slot /></span>
<span class="copy-action-container">
<TooltipWrapper>
<FlatButton icon="copy" @click="copyCode" />
<template #tooltip>
Copy
</template>
</TooltipWrapper>
</div>
</span>
</span>
</code>
</template>
<script lang="ts">
@@ -27,10 +27,10 @@ export default defineComponent({
setup() {
const { copyText } = injectKey((keys) => keys.useClipboard);
const codeElementRef = shallowRef<HTMLElement | undefined>();
const copyableTextHolderRef = shallowRef<HTMLElement | undefined>();
async function copyCode() {
const element = codeElementRef.value;
const element = copyableTextHolderRef.value;
if (!element) {
throw new Error('Code element could not be found.');
}
@@ -43,7 +43,7 @@ export default defineComponent({
return {
copyCode,
codeElement: codeElementRef,
copyableTextHolder: copyableTextHolderRef,
};
},
});
@@ -52,25 +52,16 @@ export default defineComponent({
<style scoped lang="scss">
@use "@/presentation/assets/styles/main" as *;
.code-wrapper {
.copyable-command {
display: inline-flex;
white-space: nowrap;
justify-content: space-between;
font-family: $font-family-monospace;
background-color: $color-primary-darker;
color: $color-on-primary;
align-items: center;
padding: 0.2rem;
padding: 0.25em;
font-size: $font-size-absolute-small;
.dollar {
margin-right: 0.5rem;
font-size: $font-size-absolute-x-small;
user-select: none;
}
.copy-action-container {
margin-left: 1rem;
}
code {
font-size: $font-size-absolute-small;
}
}
</style>

View File

@@ -13,7 +13,4 @@ export default defineComponent({
</script>
<style scoped lang="scss">
.step {
margin: 10px 0;
}
</style>

View File

@@ -46,9 +46,7 @@
Navigate to the folder where you downloaded the file e.g.:
</p>
<p>
<CopyableCommand>
cd ~/Downloads
</CopyableCommand>
<CopyableCommand>cd ~/Downloads</CopyableCommand>
<InfoTooltip>
<p>
Press on <code>enter/return</code> key after running the command.
@@ -72,9 +70,7 @@
Give the file execute permissions:
</p>
<p>
<CopyableCommand>
chmod +x {{ filename }}
</CopyableCommand>
<CopyableCommand>chmod +x {{ filename }}</CopyableCommand>
<InfoTooltip>
<p>
Press on <code>enter/return</code> key after running the command.
@@ -101,11 +97,11 @@
Execute the file:
</p>
<p>
<CopyableCommand>
./{{ filename }}
</CopyableCommand>
<CopyableCommand>./{{ filename }}</CopyableCommand>
<InfoTooltip>
If you have desktop environment, instead of running this command you can alternatively:
<p>
If you have desktop environment, instead of running this command you can alternatively:
</p>
<ol>
<li>Locate the file using your file manager.</li>
<li>Right click on the file, select "Run as program".</li>

View File

@@ -49,9 +49,7 @@
Give the file execute permissions:
</p>
<p>
<CopyableCommand>
chmod +x {{ filename }}
</CopyableCommand>
<CopyableCommand>chmod +x {{ filename }}</CopyableCommand>
<InfoTooltip>
<p>
Press on <code>enter/return</code> key after running the command.
@@ -67,9 +65,7 @@
Execute the file:
</p>
<p>
<CopyableCommand>
./{{ filename }}
</CopyableCommand>
<CopyableCommand>./{{ filename }}</CopyableCommand>
<InfoTooltip>
Alternatively you can locate the file in <strong>Finder</strong> and double click on it.
</InfoTooltip>

View File

@@ -23,12 +23,12 @@
</p>
<p>
In <strong>Edge</strong>:
<ol>
<li>Select <strong>Keep</strong> from the downloads section.</li>
<li>Click <strong>Show more</strong> on the next warning.</li>
<li>Select <strong>Keep anyway</strong>.</li>
</ol>
</p>
<ol>
<li>Select <strong>Keep</strong> from the downloads section.</li>
<li>Click <strong>Show more</strong> on the next warning.</li>
<li>Select <strong>Keep anyway</strong>.</li>
</ol>
<p>
For <strong>Firefox</strong> and <strong>Chrome</strong>, typically no additional
action is needed.
@@ -53,25 +53,26 @@
</p>
<p>
To handle false warnings in Microsoft Defender:
<ol>
<li>
Open <strong>Virus & threat protection</strong> from
the <strong>Start</strong> menu.
</li>
<li>
Locate the event in <strong>Protection history</strong>
that pertains to the script.
</li>
<li>In the event details, select <strong>Actions</strong> > <strong>Allow</strong>.</li>
<li>If the script was deleted, please re-download it.</li>
</ol>
</p>
<ol>
<li>
Open <strong>Virus & threat protection</strong> from
the <strong>Start</strong> menu.
</li>
<li>
Locate the event in <strong>Protection history</strong>
that pertains to the script.
</li>
<li>In the event details, select <strong>Actions</strong> > <strong>Allow</strong>.</li>
<li>If the script was deleted, please re-download it.</li>
</ol>
<blockquote>
<strong>Caution:</strong> For your security, remember to:
<strong>Caution:</strong>
<p>For your security, remember to</p>
<ul>
<li>Only allow scripts from trusted sources.</li>
<li>Avoid broad exclusions in your antivirus settings.</li>
<li>Keep real-time protection enabled whenever possible.</li>
<li>only allow scripts from trusted sources,</li>
<li>avoid broad exclusions in your antivirus settings,</li>
<li>and keep real-time protection enabled whenever possible.</li>
</ul>
</blockquote>
</InfoTooltip>

View File

@@ -1,24 +1,27 @@
<template>
<div v-if="isOpen" class="dev-toolkit-container">
<div class="dev-toolkit">
<div class="toolkit-header">
<div class="title">
<h3 class="toolkit-header">
<span class="title">
Tools
</div>
</span>
<FlatButton icon="xmark" class="close-button" @click="close" />
</div>
</h3>
<hr />
<div class="action-buttons">
<button
<ul class="action-buttons">
<li
v-for="action in devActions"
:key="action.name"
type="button"
class="action-button"
@click="action.handler"
>
{{ action.name }}
</button>
</div>
<button
type="button"
class="action-button"
@click="action.handler"
>
{{ action.name }}
</button>
</li>
</ul>
</div>
</div>
</template>
@@ -91,10 +94,6 @@ interface DevAction {
display:flex;
flex-direction: column;
hr {
width: 100%;
}
.toolkit-header {
display:flex;
flex-direction: row;
@@ -108,7 +107,6 @@ interface DevAction {
}
.title {
font-weight: bold;
text-align: center;
}
@@ -116,8 +114,11 @@ interface DevAction {
display: flex;
flex-direction: column;
gap: 10px;
@include reset-ul;
.action-button {
@include reset-button;
button {
display: block;
padding: 5px 10px;
background-color: $color-primary;

View File

@@ -1,53 +1,51 @@
<template>
<div>
<section>
<p class="privacy-rating">
Privacy: <CircleRating :rating="privacyRating" />
</p>
<hr />
<div class="sections">
<section>
{{ description }}
</section>
<section class="recommendation">
<AppIcon icon="lightbulb" class="icon" />
<span class="text">{{ recommendation }}</span>
</section>
<section
v-if="includes?.length > 0"
class="includes"
>
<AppIcon icon="square-check" class="icon" />
<span class="text">
Includes:
<ul>
<li
v-for="inclusionItem in includes"
:key="inclusionItem"
>
{{ inclusionItem }}
</li>
</ul>
</span>
</section>
<section
v-if="considerations?.length > 0"
class="considerations"
>
<AppIcon icon="triangle-exclamation" class="icon" />
<span class="text">
Considerations:
<ul>
<li
v-for="considerationItem in considerations"
:key="considerationItem"
>
{{ considerationItem }}
</li>
</ul>
</span>
</section>
</div>
</div>
<p>
{{ description }}
</p>
<p class="recommendation">
<AppIcon icon="lightbulb" class="icon" />
<span>{{ recommendation }}</span>
</p>
<p
v-if="includes?.length > 0"
class="includes"
>
<AppIcon icon="square-check" class="icon" />
<span>
Includes:
<ul>
<li
v-for="inclusionItem in includes"
:key="inclusionItem"
>
{{ inclusionItem }}
</li>
</ul>
</span>
</p>
<p
v-if="considerations?.length > 0"
class="considerations"
>
<AppIcon icon="triangle-exclamation" class="icon" />
<span>
<strong>Considerations:</strong>
<ul>
<li
v-for="considerationItem in considerations"
:key="considerationItem"
>
{{ considerationItem }}
</li>
</ul>
</span>
</p>
</section>
</template>
<script lang="ts">
@@ -88,48 +86,32 @@ export default defineComponent({
<style scoped lang="scss">
@use "@/presentation/assets/styles/main" as *;
@mixin horizontal-stack {
display: flex;
gap: 0.5em;
}
@mixin apply-icon-color($color) {
.icon {
color: $color;
}
}
.privacy-rating {
margin: 0.5em;
text-align: center;
}
hr {
margin: 1em 0;
opacity: 0.6;
}
ul {
@include reset-ul;
padding-left: 0em;
margin-top: 0.25em;
list-style: disc;
}
.sections {
display: flex;
flex-direction: column;
gap: 0.75em;
margin-bottom: 0.75em;
.includes {
display: flex;
gap: 0.5em;
.icon {
color: $color-success;
}
}
.considerations {
display: flex;
gap: 0.5em;
font-weight: bold;
.icon {
color: $color-danger;
}
}
.recommendation {
display: flex;
align-items: center;
gap: 0.5em;
.icon {
color: $color-caution;
}
}
.includes {
@include horizontal-stack;
@include apply-icon-color($color-success);
}
.considerations {
@include horizontal-stack;
@include apply-icon-color($color-danger);
}
.recommendation {
@include horizontal-stack;
@include apply-icon-color($color-caution);
align-items: center;
}
</style>

View File

@@ -1,29 +1,27 @@
<template>
<div>
<div class="sections">
<section class="description">
<AppIcon :icon="icon" class="icon" />
<span class="text">{{ description }}</span>
</section>
<section
v-if="considerations.length > 0"
class="considerations"
>
<AppIcon icon="triangle-exclamation" class="icon" />
<span class="text">
Considerations:
<ul>
<li
v-for="considerationItem in considerations"
:key="considerationItem"
>
{{ considerationItem }}
</li>
</ul>
</span>
</section>
</div>
</div>
<section>
<p class="description">
<AppIcon :icon="icon" class="icon" />
<span>{{ description }}</span>
</p>
<p
v-if="considerations.length > 0"
class="considerations"
>
<AppIcon icon="triangle-exclamation" class="icon" />
<span>
Considerations:
<ul>
<li
v-for="considerationItem in considerations"
:key="considerationItem"
>
{{ considerationItem }}
</li>
</ul>
</span>
</p>
</section>
</template>
<script lang="ts">
@@ -55,33 +53,24 @@ export default defineComponent({
<style scoped lang="scss">
@use "@/presentation/assets/styles/main" as *;
ul {
@include reset-ul;
padding-left: 0em;
margin-top: 0.25em;
list-style: disc;
li {
line-height: 1.2em;
@mixin horizontal-stack {
display: flex;
gap: 0.5em;
}
@mixin apply-icon-color($color) {
.icon {
color: $color;
}
}
.sections {
display: flex;
flex-direction: column;
gap: 0.75em;
.considerations {
display: flex;
gap: 0.5em;
.icon {
color: $color-caution;
}
}
.description {
display: flex;
align-items: center;
gap: 0.5em;
.icon {
color: $color-success;
}
}
.considerations {
@include horizontal-stack;
@include apply-icon-color($color-caution);
}
.description {
@include horizontal-stack;
@include apply-icon-color($color-success);
align-items: center;
}
</style>

View File

@@ -41,7 +41,7 @@
<template #tooltip>
<RevertStatusDocumentation
icon="rotate-left"
description="Revert selected scripts back to their default settings where possible, balancing system functionality with privacy."
description="Reverts selected scripts back to their default settings where possible, balancing system functionality with privacy."
:considerations="createConsiderationsConditionally({
warnAlways: ['Reverting changes may reduce the level of privacy protection.'],
warnIrreversibleScripts: (irreversibleCount) =>

View File

@@ -34,13 +34,18 @@ function convertMarkdownToHtml(markdownText: string): string {
<style lang="scss"> /* Not scoped due to element styling such as "a". */
@use "@/presentation/assets/styles/main" as *;
@import './markdown-styles.scss';
$text-color: $color-on-primary;
.markdown-text {
color: $text-color;
font-size: $font-size-absolute-normal;
@include markdown-text-styles;
ul {
/*
Set list style explicitly, because otherwise it changes based on parent <ul>s.
We reset the style from here.
*/
list-style: square;
}
}
</style>

View File

@@ -1,131 +0,0 @@
@use "@/presentation/assets/styles/main" as *;
@use "_code-styling" as *;
@use 'sass:math';
@mixin no-margin($selectors) {
#{$selectors} {
margin: 0;
}
}
@mixin no-padding($selectors) {
#{$selectors} {
padding: 0;
}
}
@mixin left-padding($selectors, $horizontal-spacing) {
#{$selectors} {
padding-inline-start: $horizontal-spacing;
}
}
@mixin bottom-margin($selectors, $vertical-spacing) {
#{$selectors} {
&:not(:last-child) {
margin-bottom: $vertical-spacing;
}
}
}
@mixin apply-uniform-vertical-spacing($base-vertical-spacing) {
/* Reset default top/bottom margins added by browser. */
@include no-margin('p');
@include no-margin('h1, h2, h3, h4, h5, h6');
@include no-margin('blockquote');
@include no-margin('pre');
/* Add spacing between elements using `margin-bottom` only (bottom-up instead of top-down strategy). */
$small-vertical-spacing: math.div($base-vertical-spacing, 2);
@include bottom-margin('p', $base-vertical-spacing);
@include bottom-margin('h1, h2, h3, h4, h5, h6', $base-vertical-spacing);
@include bottom-margin('ul, ol', $base-vertical-spacing);
@include bottom-margin('li', $small-vertical-spacing);
@include bottom-margin('table', $base-vertical-spacing);
@include bottom-margin('blockquote', $base-vertical-spacing);
@include bottom-margin('pre', $base-vertical-spacing);
}
@mixin apply-uniform-horizontal-spacing($base-horizontal-spacing) {
/* Reset default left/right paddings added by browser. */
@include no-padding('ul, ol');
/* Add spacing for list items. */
$large-horizontal-spacing: $base-horizontal-spacing * 2;
@include left-padding('ul, ol', $large-horizontal-spacing);
}
@mixin markdown-text-styles {
$base-spacing: 1em;
a {
&[href] {
word-break: break-word; // Enables long URLs to wrap within the container, preventing horizontal overflow.
}
&[href^="http"]{
&:after {
/*
Use mask element instead of content/background-image etc.
This way we can apply current font color to it to match the theme
*/
mask: url(@/presentation/assets/icons/external-link.svg) no-repeat 50% 50%;
mask-size: cover;
content: '';
display: inline-block;
/*
Use absolute sizing instead of relative. Relative sizing looks bad and inconsistent if there are external elements
inside small text (such as inside `<sup>`) and bigger elements like in bigger text. Making them always have same size
make the text read and flow better.
*/
width: $font-size-absolute-x-small;
height: $font-size-absolute-x-small;
vertical-align: text-top;
background-color: $text-color;
margin-left: math.div(1em, 4);
}
/*
Match color of global hover behavior. We need to do it manually because global hover sets
`color` property but here we need to modify `background-color` property because color only
works if SVG is embedded as HTML element (as `<svg/>`) not as `url(..)` as we do. Then the
only option is to use `mask` and `background-color` properties.
*/
@include hover-or-touch {
&::after{
background-color: $color-highlight;
}
}
}
}
@include apply-uniform-vertical-spacing($base-spacing);
@include apply-uniform-horizontal-spacing($base-spacing);
ul {
/*
Set list style explicitly, because otherwise it changes based on parent <ul>s in tree view.
We reset the style from here.
*/
list-style: square;
}
blockquote {
padding: 0 $base-spacing;
border-left: .25em solid $color-primary;
}
@include style-code-elements(
$code-block-padding: $base-spacing,
$color-background: $color-primary-darker,
);
sup {
@include reset-sup;
vertical-align: super;
font-size: $font-size-relative-smallest;
}
}

View File

@@ -158,7 +158,7 @@ $gap-between-circle-and-text : 0.25em;
}
.label {
font-weight: bold; // TODO: Babaya sor
font-weight: bold;
transition: all 0.3s ease-out, color 0s;
&.label-off {
@include locateNearCircle('left');

View File

@@ -129,7 +129,6 @@ function getCounterpartBoxOffsetProperty(placement: Placement): keyof CSSPropert
</script>
<style scoped lang="scss">
@use 'sass:math';
@use "@/presentation/assets/styles/main" as *;
$color-tooltip-background: $color-primary-darkest;
@@ -210,21 +209,11 @@ $color-tooltip-background: $color-primary-darkest;
}
}
@mixin set-max-width($total-characters) {
@supports (width: 1ch) {
max-width: #{$total-characters}ch;
}
// For browsers that does not support `ch` unit (e.g., Opera Mini):
$estimated-character-size: calc(1em / 2); // 1 character is approximately half the font size
$estimated-width: calc(#{$estimated-character-size} * #{$total-characters});
max-width: $estimated-width;
}
.tooltip__content {
background: $color-tooltip-background;
color: $color-on-primary;
border-radius: 16px;
padding: 5px 10px 4px;
padding: 12px 10px;
font-size: $font-size-absolute-normal;
/*
@@ -237,13 +226,14 @@ $color-tooltip-background: $color-primary-darkest;
margin-right: 2px;
// Setting max-width increases readability and consistency reducing overlap and clutter.
@include set-max-width(
@include set-property-ch-value-with-fallback(
$property: max-width,
/*
Research in typography suggests that an optimal line length for text readability is between 50-75 characters per line.
Tooltips should be brief, so aiming for the for the lower end of this range (around 50 characters).
*/
$total-characters: 50
);
$value-in-ch: 50,
)
}
.tooltip__arrow {

View File

@@ -1,56 +1,46 @@
<template>
<div class="privacy-policy">
<div v-if="!isRunningAsDesktopApplication" class="line">
<div class="line__emoji">
🚫🍪
</div>
<div>No cookies!</div>
</div>
<div v-if="isRunningAsDesktopApplication" class="line">
<div class="line__emoji">
🚫🌐
</div>
<div>
Everything is offline, except single request GitHub
to check for updates on application start.
</div>
</div>
<div class="line">
<div class="line__emoji">
🚫👀
</div>
<div>No user behavior / IP address collection!</div>
</div>
<div class="line">
<div class="line__emoji">
🤖
</div>
<div>
All transparent: Deployed automatically from the master branch
of the <a :href="repositoryUrl" target="_blank" rel="noopener noreferrer">source code</a> with no changes.
</div>
</div>
<div v-if="!isRunningAsDesktopApplication" class="line">
<div class="line__emoji">
📈
</div>
<div>
Basic <a href="https://aws.amazon.com/cloudfront/reporting/" target="_blank" rel="noopener noreferrer">CDN statistics</a>
are collected by AWS but they cannot be traced to you or your behavior.
You can download the offline version if you don't want any CDN data collection.
</div>
</div>
<div class="line">
<div class="line__emoji">
🎉
</div>
<div>
As almost no data is collected, the application gets better
only with your active feedback.
Feel free to <a :href="feedbackUrl" target="_blank" rel="noopener noreferrer">create an issue</a> 😊
</div>
</div>
</div>
<section class="privacy-policy">
<ul>
<li v-if="!isRunningAsDesktopApplication">
<span class="emoji">🚫🍪</span>
<span>No cookies!</span>
</li>
<li v-if="isRunningAsDesktopApplication">
<span class="emoji">🚫🌐</span>
<span>
Everything is offline, except single request GitHub
to check for updates on application start.
</span>
</li>
<li>
<span class="emoji">🚫👀</span>
<span>No user behavior / IP address collection!</span>
</li>
<li>
<span class="emoji">🤖</span>
<span>
All transparent: Deployed automatically from the master branch
of the <a :href="repositoryUrl" target="_blank" rel="noopener noreferrer">source code</a> with no changes.
</span>
</li>
<li v-if="!isRunningAsDesktopApplication">
<span class="emoji">📈</span>
<span>
Basic <a href="https://aws.amazon.com/cloudfront/reporting/" target="_blank" rel="noopener noreferrer">CDN statistics</a>
are collected by AWS but they cannot be traced to you or your behavior.
You can download the offline version if you don't want any CDN data collection.
</span>
</li>
<li>
<span class="emoji">🎉</span>
<span>
As almost no data is collected, the application gets better
only with your active feedback.
Feel free to <a :href="feedbackUrl" target="_blank" rel="noopener noreferrer">create an issue</a> 😊
</span>
</li>
</ul>
</section>
</template>
<script lang="ts">
@@ -81,14 +71,13 @@ export default defineComponent({
display: flex;
flex-direction: column;
text-align:center;
}
.line {
display: flex;
flex-direction: column;
ul {
@include reset-ul;
}
&:not(:first-child) {
margin-top:0.2rem;
}
}
.emoji {
display: block;
}
</style>

View File

@@ -1,6 +1,7 @@
import { describe, it, expect } from 'vitest';
import { shallowMount } from '@vue/test-utils';
import CodeInstruction from '@/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/CopyableCommand.vue';
import { VueWrapper, shallowMount } from '@vue/test-utils';
import { ComponentPublicInstance } from 'vue';
import CopyableCommand from '@/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/CopyableCommand.vue';
import { expectThrowsAsync } from '@tests/shared/Assertions/ExpectThrowsAsync';
import { InjectionKeys } from '@/presentation/injectionSymbols';
import { Clipboard } from '@/presentation/components/Shared/Hooks/Clipboard/Clipboard';
@@ -9,10 +10,10 @@ import { ClipboardStub } from '@tests/unit/shared/Stubs/ClipboardStub';
import { expectExists } from '@tests/shared/Assertions/ExpectExists';
import FlatButton from '@/presentation/components/Shared/FlatButton.vue';
const DOM_SELECTOR_CODE_SLOT = 'code';
const DOM_SELECTOR_CODE_SLOT = 'code > span:nth-of-type(2)';
const COMPONENT_TOOLTIP_WRAPPER_NAME = 'TooltipWrapper';
describe('CodeInstruction.vue', () => {
describe('CopyableCommand.vue', () => {
it('renders a slot content inside a <code> element', () => {
// arrange
const expectedSlotContent = 'Example Code';
@@ -33,7 +34,8 @@ describe('CodeInstruction.vue', () => {
const wrapper = mountComponent({
clipboard: clipboardStub,
});
wrapper.vm.codeElement = { textContent: expectedCode } as HTMLElement;
const referencedElement = createElementWithTextContent(expectedCode);
setSlotContainerElement(wrapper, referencedElement);
// act
const copyButton = wrapper.findComponent(FlatButton);
await copyButton.trigger('click');
@@ -45,21 +47,23 @@ describe('CodeInstruction.vue', () => {
const [actualCode] = call.args;
expect(actualCode).to.equal(expectedCode);
});
it('throws an error when codeElement is not found during copy', async () => {
it('throws an error when referenced element is undefined during copy', async () => {
// arrange
const expectedError = 'Code element could not be found.';
const wrapper = mountComponent();
wrapper.vm.codeElement = undefined;
const referencedElement = undefined;
setSlotContainerElement(wrapper, referencedElement);
// act
const act = () => wrapper.vm.copyCode();
// assert
await expectThrowsAsync(act, expectedError);
});
it('throws an error when codeElement has no textContent during copy', async () => {
it('throws an error when reference element has no textContent during copy', async () => {
// arrange
const expectedError = 'Code element does not contain any text.';
const wrapper = mountComponent();
wrapper.vm.codeElement = { textContent: '' } as HTMLElement;
const referencedElement = createElementWithTextContent('');
setSlotContainerElement(wrapper, referencedElement);
// act
const act = () => wrapper.vm.copyCode();
// assert
@@ -72,7 +76,7 @@ function mountComponent(options?: {
readonly clipboard?: Clipboard,
readonly slotContent?: string,
}) {
return shallowMount(CodeInstruction, {
return shallowMount(CopyableCommand, {
global: {
provide: {
[InjectionKeys.useClipboard.key]:
@@ -95,3 +99,16 @@ function mountComponent(options?: {
},
});
}
function setSlotContainerElement(
wrapper: VueWrapper<unknown>,
element?: HTMLElement,
) {
(wrapper.vm as ComponentPublicInstance<typeof CopyableCommand>).copyableTextHolder = element;
}
function createElementWithTextContent(textContent: string): HTMLElement {
const element = document.createElement('span');
element.textContent = textContent;
return element;
}