From c27796104209decb6a8872b56c79e06f415a507c Mon Sep 17 00:00:00 2001 From: rmlibre Date: Tue, 10 Sep 2024 18:57:32 -0400 Subject: [PATCH 01/59] refactor: convert hex primary color to easily tinted oklch #533 --- hushline/static/css/style.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hushline/static/css/style.css b/hushline/static/css/style.css index e6a3e8e0..c6512e07 100644 --- a/hushline/static/css/style.css +++ b/hushline/static/css/style.css @@ -2,8 +2,8 @@ --color-border: rgba(0, 0, 0, 0.18); --color-border-dark: #7d7d7d; --color-border-dark-1: #555; - --color-brand: #7d25c1; - --color-brand-dark: #ecdafa; + --color-brand: oklch(48.55% 0.222 304.47); /* #7d25c1 */ + --color-brand-dark: oklch(from var(--color-brand) calc(1.8797 * l) calc(0.2117 * c) calc(1.0197 * h)); /* #ecdafa */ --color-text: #333; --color-text-dark-alt-2: #e4e4e4; --color-text-dark-alt: #c2c2c2; From f8a8407a41a2a08179de62583ed85b46eecc3608 Mon Sep 17 00:00:00 2001 From: rmlibre Date: Tue, 10 Sep 2024 19:00:18 -0400 Subject: [PATCH 02/59] feat: add branding tab to settings template page #533 --- hushline/templates/settings.html | 57 ++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/hushline/templates/settings.html b/hushline/templates/settings.html index 3d6c09b2..3769cca6 100644 --- a/hushline/templates/settings.html +++ b/hushline/templates/settings.html @@ -43,6 +43,19 @@

Settings

Email & Encryption +
  • + +
  • + + +

    App Name

    +
    + + + +
    + +
    Date: Tue, 10 Sep 2024 19:03:04 -0400 Subject: [PATCH 03/59] style: add CSS section for input='color' branding element #533 --- hushline/static/css/style.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hushline/static/css/style.css b/hushline/static/css/style.css index c6512e07..e710588f 100644 --- a/hushline/static/css/style.css +++ b/hushline/static/css/style.css @@ -39,6 +39,12 @@ --line-height-base: 1.40625; } +#brand-primary-color { + padding: 0.25rem; + width: 4rem; + height: 2rem; +} + @font-face { font-family: "Atkinson Hyperlegible"; src: From 19c8d40d8338fa39b17f2910fe5930efd1521b5c Mon Sep 17 00:00:00 2001 From: rmlibre Date: Tue, 10 Sep 2024 19:05:40 -0400 Subject: [PATCH 04/59] feat: add form types for persisting brand changes to server db #533 --- hushline/settings.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/hushline/settings.py b/hushline/settings.py index a17bb7de..14849e53 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -169,6 +169,14 @@ class ProfileForm(FlaskForm): ) +class UpdateBrandPrimaryColorForm(FlaskForm): + hex_color = StringField("HEX Color", validators=[DataRequired(), Length(min=3, max=7)]) + + +class UpdateBrandAppNameForm(FlaskForm): + app_name = StringField("App Name", validators=[DataRequired(), Length(min=2, max=30)]) + + def set_field_attribute(input_field: Field, attribute: str, value: str) -> None: if input_field.render_kw is None: input_field.render_kw = {} From ce8a5c52ed7b4b5cb95b60add8f3366b29d1c9d2 Mon Sep 17 00:00:00 2001 From: rmlibre Date: Tue, 10 Sep 2024 19:08:28 -0400 Subject: [PATCH 05/59] feat: add preliminary routes to consume branding changes #533 --- hushline/settings.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/hushline/settings.py b/hushline/settings.py index 14849e53..89fe5114 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -664,6 +664,50 @@ def update_smtp_settings() -> Response | str: flash("👍 SMTP settings updated successfully") return redirect(url_for(".index")) + @bp.route("/update-brand-primary-color", methods=["POST"]) + @authentication_required + def update_brand_primary_color() -> Response | str: + user_id = session.get("user_id") + if not user_id: + flash("Please log in to continue.") + return redirect(url_for("login")) + + user = db.session.get(User, user_id) + if user.is_admin: + # TODO: + # db persistence logic + form retrieval + update root style variable + form = UpdateBrandPrimaryColorForm() + if form.validate_on_submit(): + user.hex_color = form.hex_color + db.session.commit() + flash("👍 Brand primary color updated successfully.") + return redirect(url_for(".index")) + + flash("User not found. Please log in again.") + return redirect(url_for("login")) + + @bp.route("/update-brand-app-name", methods=["POST"]) + @authentication_required + def update_brand_app_name() -> Response | str: + user_id = session.get("user_id") + if not user_id: + flash("Please log in to continue.") + return redirect(url_for("login")) + + user = db.session.get(User, user_id) + if user.is_admin: + # TODO: + # db persistence logic + form retrieval + update h1 + form = UpdateBrandAppNameForm() + if form.validate_on_submit(): + user.brand_app_name = form.app_name + db.session.commit() + flash("👍 Brand app name updated successfully.") + return redirect(url_for(".index")) + + flash("User not found. Please log in again.") + return redirect(url_for("login")) + @bp.route("/delete-account", methods=["POST"]) @authentication_required def delete_account() -> Response | str: From d847a509ef134cc86cea4dc00a4778d97e73d579 Mon Sep 17 00:00:00 2001 From: rmlibre Date: Thu, 12 Sep 2024 12:46:34 -0400 Subject: [PATCH 06/59] fix: use consistent route admin-required & redirect logic #573 For Roadmap Item #533 Suggested in review: https://github.com/scidsg/hushline/pull/573#pullrequestreview-2300269011 https://github.com/scidsg/hushline/pull/573#discussion_r1756839214 https://github.com/scidsg/hushline/pull/573#discussion_r1756850694 https://github.com/scidsg/hushline/pull/573#discussion_r1756851086 https://github.com/scidsg/hushline/pull/573#discussion_r1756853462 Co-authored-by: Jeremy Moore --- hushline/settings.py | 64 +++++++++++++------------------- hushline/templates/settings.html | 4 +- 2 files changed, 27 insertions(+), 41 deletions(-) diff --git a/hushline/settings.py b/hushline/settings.py index f86d25a5..9782c0de 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -39,7 +39,7 @@ from .db import db from .forms import ComplexPassword, TwoFactorForm from .model import Message, SecondaryUsername, SMTPEncryption, User -from .utils import authentication_required, create_smtp_config +from .utils import admin_authentication_required, authentication_required, create_smtp_config class ChangePasswordForm(FlaskForm): @@ -713,48 +713,34 @@ def update_smtp_settings() -> Response | str: return redirect(url_for(".index")) @bp.route("/update-brand-primary-color", methods=["POST"]) - @authentication_required - def update_brand_primary_color() -> Response | str: - user_id = session.get("user_id") - if not user_id: - flash("Please log in to continue.") - return redirect(url_for("login")) - - user = db.session.get(User, user_id) - if user.is_admin: - # TODO: - # db persistence logic + form retrieval + update root style variable - form = UpdateBrandPrimaryColorForm() - if form.validate_on_submit(): - user.hex_color = form.hex_color - db.session.commit() - flash("👍 Brand primary color updated successfully.") - return redirect(url_for(".index")) + @admin_authentication_required + def update_brand_primary_color(user: User) -> Response | str: + # TODO: + # db persistence logic + form retrieval + update root style variable + form = UpdateBrandPrimaryColorForm() + if form.validate_on_submit(): + user.hex_color = form.hex_color + db.session.commit() + flash("👍 Brand primary color updated successfully.") + return redirect(url_for(".index")) - flash("User not found. Please log in again.") - return redirect(url_for("login")) + flash("⛔ Invalid form data. Please try again.") + return redirect(url_for(".index")) @bp.route("/update-brand-app-name", methods=["POST"]) - @authentication_required - def update_brand_app_name() -> Response | str: - user_id = session.get("user_id") - if not user_id: - flash("Please log in to continue.") - return redirect(url_for("login")) - - user = db.session.get(User, user_id) - if user.is_admin: - # TODO: - # db persistence logic + form retrieval + update h1 - form = UpdateBrandAppNameForm() - if form.validate_on_submit(): - user.brand_app_name = form.app_name - db.session.commit() - flash("👍 Brand app name updated successfully.") - return redirect(url_for(".index")) + @admin_authentication_required + def update_brand_app_name(user: User) -> Response | str: + # TODO: + # db persistence logic + form retrieval + update h1 + form = UpdateBrandAppNameForm() + if form.validate_on_submit(): + user.brand_app_name = form.app_name + db.session.commit() + flash("👍 Brand app name updated successfully.") + return redirect(url_for(".index")) - flash("User not found. Please log in again.") - return redirect(url_for("login")) + flash("⛔ Invalid form data. Please try again.") + return redirect(url_for(".index")) @bp.route("/delete-account", methods=["POST"]) @authentication_required diff --git a/hushline/templates/settings.html b/hushline/templates/settings.html index ac5b9705..f3668c0c 100644 --- a/hushline/templates/settings.html +++ b/hushline/templates/settings.html @@ -490,7 +490,7 @@

    Message Encryption

    Primary Color

    @@ -507,7 +507,7 @@

    Primary Color

    App Name

    From bd6c6a16ce597b72c46119424b49f96e67cc35e0 Mon Sep 17 00:00:00 2001 From: rmlibre Date: Thu, 12 Sep 2024 13:10:30 -0400 Subject: [PATCH 07/59] feat: apply single-source color theme transformations to CSS #573 For Roadmap Item #533 --- hushline/static/css/style.css | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/hushline/static/css/style.css b/hushline/static/css/style.css index d63ff5be..7b196fbc 100644 --- a/hushline/static/css/style.css +++ b/hushline/static/css/style.css @@ -2,17 +2,17 @@ --color-border: rgba(0, 0, 0, 0.18); --color-border-dark: #7d7d7d; --color-border-dark-1: #555; - --color-brand: oklch(48.55% 0.222 304.47); /* #7d25c1 */ - --color-brand-dark: oklch(from var(--color-brand) calc(1.8797 * l) calc(0.2117 * c) calc(1.0197 * h)); /* #ecdafa */ + --color-brand: oklch(48.55% 0.222 304.47); /* #7d25c1 */ + --color-brand-dark: oklch(from var(--color-brand) calc(1.8797 * l) calc(0.2117 * c) calc(1.0197 * h)); /* #ecdafa */ --color-text: #333; --color-text-dark-alt-2: #e4e4e4; --color-text-dark-alt: #c2c2c2; --color-text-dark: #dadada; --color-text-light: #595959; - --color-highlight: rgba(125, 37, 193, 0.1); - --color-highlight-dark: rgba(220, 198, 237, 0.2); - --color-brand-bg: #fbf3ff; - --color-dark-bg: #222; + --color-highlight: oklch(from var(--color-brand) l c h / 0.1); /* rgba(125, 37, 193, 0.1) */ + --color-highlight-dark: oklch(from var(--color-brand-dark) l c h / 0.2); /* rgba(220, 198, 237, 0.2) */ + --color-brand-bg: oklch(from var(--color-brand) calc(2.0058 * l) calc(0.0811 * c) calc(1.0337 * h)); /* #fbf3ff */ + --color-dark-bg: oklch(from var(--color-brand) calc(0.5191 * l) calc(0.0676 * c) calc(1.0541 * h)); /* was #222 now with brand hue */ --color-dark-bg-alt: #333; --color-dark-bg-alt-1: #444; --color-dark-bg-alt-transparent: rgba(51, 51, 51, 0.7); @@ -147,7 +147,7 @@ /* Change background color of tabs on hover */ .tab:hover, .subtab:hover { - background-color: #fbf3ff; + background-color: var(--color-brand-bg); /* #fbf3ff */ } /* Create an active/current tablink class */ @@ -193,7 +193,7 @@ background-color: white; color: var(--color-brand); border: 1px solid var(--color-brand); - box-shadow: 0px 2px 0px 0px rgba(125, 37, 193, 0.25); + box-shadow: 0px 2px 0px 0px oklch(from var(--color-brand) l c h / 0.25); /* rgba(125, 37, 193, 0.25) */ } .formBody input[type="submit"]:hover, @@ -208,7 +208,7 @@ .btn:active, input[type="file"]::file-selector-button:active, select:active { - box-shadow: inset 0px 2px 0px 0px rgba(125, 37, 193, 0.25); + box-shadow: inset 0px 2px 0px 0px oklch(from var(--color-brand) l c h / 0.25); /* rgba(125, 37, 193, 0.25) */ } .btn-danger, @@ -435,7 +435,7 @@ /* Change background color of tabs on hover */ .tab:hover, .subtab:hover { - background-color: rgba(255, 255, 255, 0.1); + background-color: oklch(from var(--color-dark-bg) l c h / 0.35); /* was rgba(255, 255, 255, 0.1) now with color */ } /* Create an active/current tablink class */ @@ -481,7 +481,7 @@ background-color: var(--color-dark); color: var(--color-brand-dark); border: 1px solid var(--color-brand-dark); - box-shadow: 0px 2px 0px 0px rgba(125, 37, 193, 0.25); + box-shadow: 0px 2px 0px 0px oklch(from var(--color-brand) l c h / 0.25); /* rgba(125, 37, 193, 0.25) */ } .formBody input[type="submit"]:hover, @@ -495,7 +495,7 @@ .formBody button:active, .btn:active, input[type="file"]::file-selector-button:active { - box-shadow: inset 0px 2px 0px 0px rgba(125, 37, 193, 0.25); + box-shadow: inset 0px 2px 0px 0px oklch(from var(--color-brand) l c h / 0.25); /* rgba(125, 37, 193, 0.25) */ } .btn-danger, @@ -618,7 +618,7 @@ } .banner { - background-color: #ecdafa; + background-color: var(--color-brand-dark); /* #ecdafa */ color: #333; } @@ -1171,7 +1171,7 @@ button, input[type="file"]::file-selector-button { cursor: pointer; text-decoration: none; - box-shadow: 0px 2px 0px 0px rgba(125, 37, 193, 0.25); + box-shadow: 0px 2px 0px 0px oklch(from var(--color-brand) l c h / 0.25); /* rgba(125, 37, 193, 0.25) */ } textarea { From 75269c70e3cbd667e271e8d7e3be12a0118ca6ed Mon Sep 17 00:00:00 2001 From: rmlibre Date: Thu, 12 Sep 2024 15:16:31 -0400 Subject: [PATCH 08/59] fix: add hex color code validator consistent with input element #573 For Roadmap Item #533 Suggested in review: https://github.com/scidsg/hushline/pull/573#discussion_r1756836679 Co-authored-by: Jeremy Moore --- hushline/forms.py | 9 +++++++++ hushline/settings.py | 6 ++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/hushline/forms.py b/hushline/forms.py index 3bc1a0ef..05ab89e1 100644 --- a/hushline/forms.py +++ b/hushline/forms.py @@ -25,5 +25,14 @@ def __call__(self, form: Form, field: Field) -> None: raise ValidationError(self.message) +class HexColor: + hex_color_regex: re.Pattern = re.compile(r"^#[0-9a-fA-F]{6}$") + + def __call__(self, form: Form, field: Field) -> None: + color: str = field.data + if not self.hex_color_regex.match(color): + raise ValidationError(f"{color=} is an invalid 6-hexit color code.") + + class TwoFactorForm(FlaskForm): verification_code = StringField("2FA Code", validators=[DataRequired(), Length(min=6, max=6)]) diff --git a/hushline/settings.py b/hushline/settings.py index 9782c0de..21e83294 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -37,7 +37,7 @@ from .crypto import is_valid_pgp_key from .db import db -from .forms import ComplexPassword, TwoFactorForm +from .forms import ComplexPassword, HexColor, TwoFactorForm from .model import Message, SecondaryUsername, SMTPEncryption, User from .utils import admin_authentication_required, authentication_required, create_smtp_config @@ -195,7 +195,9 @@ class ProfileForm(FlaskForm): class UpdateBrandPrimaryColorForm(FlaskForm): - hex_color = StringField("HEX Color", validators=[DataRequired(), Length(min=3, max=7)]) + hex_color = StringField( + "HEX Color", validators=[DataRequired(), HexColor(), Length(min=3, max=7)] + ) class UpdateBrandAppNameForm(FlaskForm): From 241b1df73bb09391f6ec0c635be905367aedd594 Mon Sep 17 00:00:00 2001 From: rmlibre Date: Thu, 12 Sep 2024 15:23:27 -0400 Subject: [PATCH 09/59] fix: give brand form data to renamed db object attributes #573 For Roadmap Item #533 Suggested in review: https://github.com/scidsg/hushline/pull/573#discussion_r1756841440 Co-authored-by: Jeremy Moore --- hushline/settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hushline/settings.py b/hushline/settings.py index 21e83294..fef57e0a 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -721,7 +721,7 @@ def update_brand_primary_color(user: User) -> Response | str: # db persistence logic + form retrieval + update root style variable form = UpdateBrandPrimaryColorForm() if form.validate_on_submit(): - user.hex_color = form.hex_color + user.brand_primary_hex_color = form.hex_color.data db.session.commit() flash("👍 Brand primary color updated successfully.") return redirect(url_for(".index")) @@ -736,7 +736,7 @@ def update_brand_app_name(user: User) -> Response | str: # db persistence logic + form retrieval + update h1 form = UpdateBrandAppNameForm() if form.validate_on_submit(): - user.brand_app_name = form.app_name + user.brand_app_name = form.app_name.data db.session.commit() flash("👍 Brand app name updated successfully.") return redirect(url_for(".index")) From 5f246a8ea6caeb6b8559f562d878e68e8ce9a417 Mon Sep 17 00:00:00 2001 From: rmlibre Date: Thu, 12 Sep 2024 15:57:33 -0400 Subject: [PATCH 10/59] fix: remove redundant length validator covered by ``HexColor`` #573 For Roadmap Item #533 Co-authored-by: Jeremy Moore --- hushline/forms.py | 1 + hushline/settings.py | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/hushline/forms.py b/hushline/forms.py index 05ab89e1..345be088 100644 --- a/hushline/forms.py +++ b/hushline/forms.py @@ -26,6 +26,7 @@ def __call__(self, form: Form, field: Field) -> None: class HexColor: + # HTML input color elements only give & accept 6-hexit color codes hex_color_regex: re.Pattern = re.compile(r"^#[0-9a-fA-F]{6}$") def __call__(self, form: Form, field: Field) -> None: diff --git a/hushline/settings.py b/hushline/settings.py index fef57e0a..7565c6c4 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -195,9 +195,7 @@ class ProfileForm(FlaskForm): class UpdateBrandPrimaryColorForm(FlaskForm): - hex_color = StringField( - "HEX Color", validators=[DataRequired(), HexColor(), Length(min=3, max=7)] - ) + hex_color = StringField("HEX Color", validators=[DataRequired(), HexColor()]) class UpdateBrandAppNameForm(FlaskForm): From f421b9bd1f5116c154412ebbf12ef395d1430b97 Mon Sep 17 00:00:00 2001 From: rmlibre Date: Thu, 12 Sep 2024 21:28:42 -0400 Subject: [PATCH 11/59] fix: match template structure for brand color & name sections #573 Prepare for the default color to be inserted as a fallback if no specific branding is specified. For Roadmap Item #533 --- hushline/templates/settings.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hushline/templates/settings.html b/hushline/templates/settings.html index f3668c0c..268e1315 100644 --- a/hushline/templates/settings.html +++ b/hushline/templates/settings.html @@ -496,8 +496,10 @@

    Primary Color

    +
    From 732059658b7dbe3ea2a503509777a83472226f7a Mon Sep 17 00:00:00 2001 From: rmlibre Date: Sat, 14 Sep 2024 14:02:10 -0400 Subject: [PATCH 25/59] refactor: clean up redundant &/or unused values #573 For Roadmap Item #533 --- hushline/templates/base.html | 4 ++-- hushline/templates/settings.html | 14 ++++---------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/hushline/templates/base.html b/hushline/templates/base.html index 0453ce85..84f348a1 100644 --- a/hushline/templates/base.html +++ b/hushline/templates/base.html @@ -67,13 +67,13 @@ diff --git a/hushline/templates/settings.html b/hushline/templates/settings.html index e2fd0e27..772b2aa1 100644 --- a/hushline/templates/settings.html +++ b/hushline/templates/settings.html @@ -499,12 +499,9 @@

    Primary Color

    type="color" name="brand_primary_hex_color" id="brand-primary-color" - class="color-picker" - value="{{ host_org.brand_primary_hex_color or '#7d25c1' }}" + value="{{ host_org.brand_primary_hex_color }}" /> - +

    App Name

    @@ -519,12 +516,9 @@

    App Name

    type="text" name="brand_app_name" id="brand-app-name" - placeholder="🤫 Hush Line" - value="{{ host_org.brand_app_name or '' }}" + value="{{ host_org.brand_app_name }}" /> - + From 25c49d250eaac2b95f020284238e7209451ed032 Mon Sep 17 00:00:00 2001 From: rmlibre Date: Sat, 14 Sep 2024 14:07:59 -0400 Subject: [PATCH 26/59] feat: validate app names are canonical under html conversions #573 For Roadmap Item #533 --- hushline/forms.py | 8 ++++++++ hushline/settings.py | 6 ++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/hushline/forms.py b/hushline/forms.py index 345be088..33e33425 100644 --- a/hushline/forms.py +++ b/hushline/forms.py @@ -1,3 +1,4 @@ +import html import re from flask_wtf import FlaskForm @@ -35,5 +36,12 @@ def __call__(self, form: Form, field: Field) -> None: raise ValidationError(f"{color=} is an invalid 6-hexit color code.") +class CanonicalHTML: + def __call__(self, form: Form, field: Field) -> None: + text: str = field.data + if text != html.escape(text).strip(): + raise ValidationError(f"{text=} is ambiguous or unescaped.") + + class TwoFactorForm(FlaskForm): verification_code = StringField("2FA Code", validators=[DataRequired(), Length(min=6, max=6)]) diff --git a/hushline/settings.py b/hushline/settings.py index 4ad9c2d9..3aae89b7 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -38,7 +38,7 @@ from .crypto import is_valid_pgp_key from .db import db -from .forms import ComplexPassword, HexColor, TwoFactorForm +from .forms import CanonicalHTML, ComplexPassword, HexColor, TwoFactorForm from .model import HostOrganization, Message, SecondaryUsername, SMTPEncryption, User from .utils import admin_authentication_required, authentication_required, create_smtp_config @@ -200,7 +200,9 @@ class UpdateBrandPrimaryColorForm(FlaskForm): class UpdateBrandAppNameForm(FlaskForm): - brand_app_name = StringField("App Name", validators=[DataRequired(), Length(min=2, max=30)]) + brand_app_name = StringField( + "App Name", validators=[CanonicalHTML(), DataRequired(), Length(min=2, max=30)] + ) def set_field_attribute(input_field: Field, attribute: str, value: str) -> None: From d745fdd284f72c08b1f5776b2fc8ba1aa008ab6f Mon Sep 17 00:00:00 2001 From: rmlibre Date: Sat, 14 Sep 2024 14:33:20 -0400 Subject: [PATCH 27/59] refactor: remove redundant form validator #573 For Roadmap Item #533 --- hushline/settings.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/hushline/settings.py b/hushline/settings.py index 3aae89b7..1939ae93 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -200,9 +200,7 @@ class UpdateBrandPrimaryColorForm(FlaskForm): class UpdateBrandAppNameForm(FlaskForm): - brand_app_name = StringField( - "App Name", validators=[CanonicalHTML(), DataRequired(), Length(min=2, max=30)] - ) + brand_app_name = StringField("App Name", validators=[CanonicalHTML(), Length(min=2, max=30)]) def set_field_attribute(input_field: Field, attribute: str, value: str) -> None: From 7c390b51b9e6d55e7f962d65fbd2e570a725ab80 Mon Sep 17 00:00:00 2001 From: rmlibre Date: Sat, 14 Sep 2024 14:54:31 -0400 Subject: [PATCH 28/59] revert: "refactor: remove redundant form validator #573" This reverts commit d745fdd284f72c08b1f5776b2fc8ba1aa008ab6f. The removed validator is necessary since it's ensured not to be skipped when form fields are empty. For Roadmap Item #533 --- hushline/settings.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hushline/settings.py b/hushline/settings.py index 1939ae93..3aae89b7 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -200,7 +200,9 @@ class UpdateBrandPrimaryColorForm(FlaskForm): class UpdateBrandAppNameForm(FlaskForm): - brand_app_name = StringField("App Name", validators=[CanonicalHTML(), Length(min=2, max=30)]) + brand_app_name = StringField( + "App Name", validators=[CanonicalHTML(), DataRequired(), Length(min=2, max=30)] + ) def set_field_attribute(input_field: Field, attribute: str, value: str) -> None: From 22127762bd41ae578783ddcc3f5f277cb612a1f9 Mon Sep 17 00:00:00 2001 From: rmlibre Date: Sat, 14 Sep 2024 18:02:13 -0400 Subject: [PATCH 29/59] fix: apply admin-only controls for rendering branding features #573 For Roadmap Item #533 --- hushline/static/js/settings.js | 42 +++++++++++++++++--------------- hushline/templates/settings.html | 8 ++++-- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/hushline/static/js/settings.js b/hushline/static/js/settings.js index 9b0f9024..9fa34555 100644 --- a/hushline/static/js/settings.js +++ b/hushline/static/js/settings.js @@ -75,28 +75,30 @@ document.addEventListener("DOMContentLoaded", function () { }); }); - // Update color in real-time as they're being browsed - document - .getElementById("brand-primary-color") - .addEventListener("input", function(event) { - const brandColor = `oklch(from ${event.target.value} l c h)`; - document.documentElement.style.setProperty("--color-brand", brandColor); - }); + if (document.getElementById("branding")) { + // Update color in real-time as they're being browsed + document + .getElementById("brand-primary-color") + .addEventListener("input", function(event) { + const brandColor = `oklch(from ${event.target.value} l c h)`; + document.documentElement.style.setProperty("--color-brand", brandColor); + }); - // Update color once it's been finalized - document - .getElementById("brand-primary-color") - .addEventListener("change", function(event) { - const brandColor = `oklch(from ${event.target.value} l c h)`; - document.documentElement.style.setProperty("--color-brand", brandColor); - }); + // Update color once it's been finalized + document + .getElementById("brand-primary-color") + .addEventListener("change", function(event) { + const brandColor = `oklch(from ${event.target.value} l c h)`; + document.documentElement.style.setProperty("--color-brand", brandColor); + }); - // Update app name in real-time as it's being typed - document - .getElementById("brand-app-name") - .addEventListener("input", function(event) { - document.querySelector("h1").innerText = event.target.value; - }); + // Update app name in real-time as it's being typed + document + .getElementById("brand-app-name") + .addEventListener("input", function(event) { + document.querySelector("h1").innerText = event.target.value; + }); + } var forwarding_enabled = document.querySelector( "input[id='forwarding_enabled']", diff --git a/hushline/templates/settings.html b/hushline/templates/settings.html index 772b2aa1..782096da 100644 --- a/hushline/templates/settings.html +++ b/hushline/templates/settings.html @@ -43,10 +43,11 @@

    Settings

    Email & Encryption
  • + {% if user.is_admin %}
  • + {% endif %}
  • + {% endif %}
    Date: Sat, 14 Sep 2024 23:41:33 -0400 Subject: [PATCH 30/59] fix: increase contrast ratios to minimum recommendations #573 Key text elements now pass AAA Success Criterion 1.4.6 with a contrast ratio of +7:1 as described in the WCAG 2.2 standard. WCAG 2.2: https://www.w3.org/TR/WCAG22/#contrast-enhanced Contrast Tool: https://webaim.org/resources/contrastchecker/ For Roadmap Item #533 --- hushline/static/css/style.css | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/hushline/static/css/style.css b/hushline/static/css/style.css index 519699fe..8bc1eded 100644 --- a/hushline/static/css/style.css +++ b/hushline/static/css/style.css @@ -6,6 +6,9 @@ --color-brand-min-contrast: oklch( from var(--color-brand) clamp(0.1, l, 0.6) c h ); + --color-brand-max-contrast: oklch( + from var(--color-brand-min-contrast) max(l - 0.175, 0.1) calc(1.25 * c) h + ); --color-brand-dark: oklch( /* was #ecdafa now slightly more saturated */ from var(--color-brand) clamp(0.85, l + 0.4271, 0.95) calc(0.25 * c) calc(h + 6) @@ -23,11 +26,11 @@ ); --color-brand-bg: oklch( /* #fbf3ff */ from var(--color-brand) - clamp(0.6, l + 0.4883, 0.98) min(c, 0.0179) calc(h + 10.26) + clamp(0.8, l + 0.4883, 0.98) min(c, 0.0179) calc(h + 10.26) ); --color-dark-bg: oklch( /* was #222 now with brand hue */ from var(--color-brand) - clamp(0.15, l - 0.2335, 0.4) min(c, 0.012) calc(h + 16.47) + clamp(0.15, l - 0.2335, 0.275) min(c, 0.012) calc(h + 16.47) ); --color-dark-bg-alt: #333; --color-dark-bg-alt-1: #444; @@ -90,7 +93,7 @@ } a { - color: var(--color-brand-min-contrast); + color: var(--color-brand-max-contrast); } p.meta { @@ -157,7 +160,9 @@ /* Change background color of tabs on hover */ .tab:hover, .subtab:hover { - background-color: var(--color-brand-bg); /* #fbf3ff */ + background-color: oklch( /* #fbf3ff */ + from var(--color-brand-bg) 0.97 c h / 0.85 + ); } /* Create an active/current tablink class */ @@ -201,7 +206,7 @@ .formBody button, .btn { background-color: white; - color: var(--color-brand-min-contrast); + color: var(--color-brand-max-contrast); border: 1px solid var(--color-brand-min-contrast); box-shadow: 0px 2px 0px 0px oklch( /* rgba(125, 37, 193, 0.25) */ from var(--color-brand) min(l, 0.95) c h / 0.25 @@ -233,7 +238,7 @@ p.badge { background-color: white; - color: var(--color-brand-min-contrast); + color: var(--color-brand-max-contrast); border: 1px solid var(--color-brand-min-contrast); } @@ -450,7 +455,7 @@ .tab:hover, .subtab:hover { background-color: oklch( /* was rgba(255, 255, 255, 0.1) now with color */ - from var(--color-dark-bg) l c h / 0.2 + from var(--color-dark-bg) 0.15 calc(12 * c) h / 0.0675 ); } From 2ab4fa435e42a8c98206c5b707e0b41d4f79a5ad Mon Sep 17 00:00:00 2001 From: rmlibre Date: Sun, 15 Sep 2024 00:00:45 -0400 Subject: [PATCH 31/59] fix: require data for brand app name form to be sent #573 For Roadmap Item #533 --- hushline/templates/settings.html | 1 + 1 file changed, 1 insertion(+) diff --git a/hushline/templates/settings.html b/hushline/templates/settings.html index 782096da..ba5d83cc 100644 --- a/hushline/templates/settings.html +++ b/hushline/templates/settings.html @@ -520,6 +520,7 @@

    App Name

    name="brand_app_name" id="brand-app-name" value="{{ host_org.brand_app_name }}" + required /> From a236353ffa79396bd8a5d681e1ecc49293559f86 Mon Sep 17 00:00:00 2001 From: rmlibre Date: Sun, 15 Sep 2024 11:32:17 -0400 Subject: [PATCH 32/59] fix: make dark mode brand colors more vibrant #573 For Roadmap Item #533 --- hushline/static/css/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hushline/static/css/style.css b/hushline/static/css/style.css index 8bc1eded..3d6fc59e 100644 --- a/hushline/static/css/style.css +++ b/hushline/static/css/style.css @@ -11,7 +11,7 @@ ); --color-brand-dark: oklch( /* was #ecdafa now slightly more saturated */ from var(--color-brand) - clamp(0.85, l + 0.4271, 0.95) calc(0.25 * c) calc(h + 6) + clamp(0.9126, l + 0.4271, 0.95) calc(0.35 * c) calc(h + 6) ); --color-text: #333; --color-text-dark-alt-2: #e4e4e4; From 55d0a5d177ba38a3258aa806de350ece540605f6 Mon Sep 17 00:00:00 2001 From: rmlibre Date: Sun, 15 Sep 2024 14:01:55 -0400 Subject: [PATCH 33/59] fix: apply only medium contrast tinting for bold text #573 For Roadmap Item #533 --- hushline/static/css/style.css | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/hushline/static/css/style.css b/hushline/static/css/style.css index 3d6fc59e..25d41751 100644 --- a/hushline/static/css/style.css +++ b/hushline/static/css/style.css @@ -6,8 +6,11 @@ --color-brand-min-contrast: oklch( from var(--color-brand) clamp(0.1, l, 0.6) c h ); + --color-brand-mid-contrast: oklch( + from var(--color-brand-min-contrast) max(l - 0.125, 0.1) calc(1.25 * c) h + ); --color-brand-max-contrast: oklch( - from var(--color-brand-min-contrast) max(l - 0.175, 0.1) calc(1.25 * c) h + from var(--color-brand-min-contrast) max(l - 0.25, 0.1) calc(1.25 * c) h ); --color-brand-dark: oklch( /* was #ecdafa now slightly more saturated */ from var(--color-brand) @@ -198,7 +201,7 @@ .btn, input[type="file"]::file-selector-button { border: 0px; - background-color: var(--color-brand-min-contrast); + background-color: var(--color-brand-mid-contrast); color: white; } @@ -206,7 +209,7 @@ .formBody button, .btn { background-color: white; - color: var(--color-brand-max-contrast); + color: var(--color-brand-mid-contrast); border: 1px solid var(--color-brand-min-contrast); box-shadow: 0px 2px 0px 0px oklch( /* rgba(125, 37, 193, 0.25) */ from var(--color-brand) min(l, 0.95) c h / 0.25 @@ -238,7 +241,7 @@ p.badge { background-color: white; - color: var(--color-brand-max-contrast); + color: var(--color-brand-mid-contrast); border: 1px solid var(--color-brand-min-contrast); } From 6d7eaff0d179ab962f3c0316eb011bf8b5a28b1a Mon Sep 17 00:00:00 2001 From: rmlibre Date: Mon, 16 Sep 2024 10:07:17 -0400 Subject: [PATCH 34/59] refactor: improve the readability of JS code additions #573 For Roadmap Item #533 --- hushline/static/js/settings.js | 31 ++++++++++++++----------------- hushline/templates/base.html | 5 ++--- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/hushline/static/js/settings.js b/hushline/static/js/settings.js index 9fa34555..9d585e78 100644 --- a/hushline/static/js/settings.js +++ b/hushline/static/js/settings.js @@ -76,28 +76,25 @@ document.addEventListener("DOMContentLoaded", function () { }); if (document.getElementById("branding")) { - // Update color in real-time as they're being browsed - document - .getElementById("brand-primary-color") - .addEventListener("input", function(event) { - const brandColor = `oklch(from ${event.target.value} l c h)`; - document.documentElement.style.setProperty("--color-brand", brandColor); - }); - // Update color once it's been finalized - document - .getElementById("brand-primary-color") - .addEventListener("change", function(event) { + // Update color in real-time as they're being browsed & finalized + const colorPicker = document.getElementById("brand-primary-color"); + + for (const eventName of ["input", "change"]) { + colorPicker.addEventListener(eventName, function (event) { const brandColor = `oklch(from ${event.target.value} l c h)`; - document.documentElement.style.setProperty("--color-brand", brandColor); + const cssVariable = ["--color-brand", brandColor]; + document.documentElement.style.setProperty(...cssVariable); }); + } // Update app name in real-time as it's being typed - document - .getElementById("brand-app-name") - .addEventListener("input", function(event) { - document.querySelector("h1").innerText = event.target.value; - }); + const appNameBox = document.getElementById("brand-app-name"); + + appNameBox.addEventListener("input", function (event) { + document.querySelector("h1").innerText = event.target.value; + }); + } var forwarding_enabled = document.querySelector( diff --git a/hushline/templates/base.html b/hushline/templates/base.html index 84f348a1..3a1a0ded 100644 --- a/hushline/templates/base.html +++ b/hushline/templates/base.html @@ -68,9 +68,8 @@ function importHostConfiguration() { // Update page theme with host's brand color from the database const brandColor = "{{ host_org.brand_primary_hex_color }}"; - document.documentElement.style.setProperty( - "--color-brand", `oklch(from ${brandColor} l c h)` - ); + const cssVariable = ["--color-brand", brandColor]; + document.documentElement.style.setProperty(...cssVariable); // Update page h1 with host's brand name from the database const brandName = "{{ host_org.brand_app_name }}"; From 47d49da26ac78d123b0ad310de2b76f70ce2f222 Mon Sep 17 00:00:00 2001 From: rmlibre Date: Mon, 16 Sep 2024 10:14:15 -0400 Subject: [PATCH 35/59] style: adjust jinja formatting to reduce extra whitespace #573 For Roadmap Item #533 --- hushline/templates/settings.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hushline/templates/settings.html b/hushline/templates/settings.html index ba5d83cc..b9bec0db 100644 --- a/hushline/templates/settings.html +++ b/hushline/templates/settings.html @@ -43,7 +43,7 @@

    Settings

    Email & Encryption
  • - {% if user.is_admin %} + {% if user.is_admin -%}
  • - {% endif %} + {%- endif %}
  • - {% endif %} + {%- endif %}
    Date: Mon, 16 Sep 2024 14:31:36 -0400 Subject: [PATCH 36/59] fix: remove early creation of all db tables #573 Suggested in review: https://github.com/scidsg/hushline/pull/573#discussion_r1761586224 For Roadmap Item #533 Co-authored-by: Jeremy Moore --- hushline/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/hushline/__init__.py b/hushline/__init__.py index 9c1368f9..15c6ab84 100644 --- a/hushline/__init__.py +++ b/hushline/__init__.py @@ -61,9 +61,6 @@ def create_app() -> Flask: db.init_app(app) Migrate(app, db) - with app.app_context(): - db.create_all() - routes.init_app(app) for module in [admin, settings]: app.register_blueprint(module.create_blueprint()) From ab0a21251f9a94858515c724e86c00f3f172ec27 Mon Sep 17 00:00:00 2001 From: rmlibre Date: Mon, 16 Sep 2024 14:36:12 -0400 Subject: [PATCH 37/59] fix: limit hex color-code db field length to 7 characters #573 Suggested in review: https://github.com/scidsg/hushline/pull/573#discussion_r1761591879 For Roadmap Item #533 Co-authored-by: Jeremy Moore --- hushline/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hushline/model.py b/hushline/model.py index 1b5b0aec..4866a070 100644 --- a/hushline/model.py +++ b/hushline/model.py @@ -33,7 +33,7 @@ class HostOrganization(Model): id: Mapped[int] = mapped_column(primary_key=True, default=1) brand_app_name: Mapped[str] = mapped_column(db.String(255), default="🤫 Hush Line") - brand_primary_hex_color: Mapped[str] = mapped_column(db.String(255), default="#7d25c1") + brand_primary_hex_color: Mapped[str] = mapped_column(db.String(7), default="#7d25c1") class User(Model): From 377035437ec61993ba2a3fe76e3dd94aa9c30e7f Mon Sep 17 00:00:00 2001 From: rmlibre Date: Tue, 17 Sep 2024 11:10:32 -0400 Subject: [PATCH 38/59] fix: improve dark themes accuracy, dynamism, & vibrancy #573 For Roadmap Item #533 --- hushline/static/css/style.css | 51 +++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/hushline/static/css/style.css b/hushline/static/css/style.css index 25d41751..b080c4cb 100644 --- a/hushline/static/css/style.css +++ b/hushline/static/css/style.css @@ -12,9 +12,17 @@ --color-brand-max-contrast: oklch( from var(--color-brand-min-contrast) max(l - 0.25, 0.1) calc(1.25 * c) h ); - --color-brand-dark: oklch( /* was #ecdafa now slightly more saturated */ + --color-brand-dark-min-saturation: oklch( from var(--color-brand) - clamp(0.9126, l + 0.4271, 0.95) calc(0.35 * c) calc(h + 6) + clamp(0.96, l + 0.425, 0.98) calc(0.5 * c) h + ); + --color-brand-dark-mid-saturation: oklch( + from var(--color-brand) + clamp(0.93, l + 0.4, 0.98) calc(0.75 * c) h + ); + --color-brand-dark-max-saturation: oklch( + from var(--color-brand) + clamp(0.8, l + 0.175, 0.94) calc(1.5 * c) calc(h - 6) ); --color-text: #333; --color-text-dark-alt-2: #e4e4e4; @@ -25,7 +33,7 @@ from var(--color-brand) l c h / 0.1 ); --color-highlight-dark: oklch( /* rgba(220, 198, 237, 0.2) */ - from var(--color-brand-dark) l c h / 0.2 + from var(--color-brand-dark-mid-saturation) l c h / 0.2 ); --color-brand-bg: oklch( /* #fbf3ff */ from var(--color-brand) @@ -33,7 +41,7 @@ ); --color-dark-bg: oklch( /* was #222 now with brand hue */ from var(--color-brand) - clamp(0.15, l - 0.2335, 0.275) min(c, 0.012) calc(h + 16.47) + clamp(0.18, l - 0.275, 0.215) min(c, 0.01) calc(h + 10.26) ); --color-dark-bg-alt: #333; --color-dark-bg-alt-1: #444; @@ -381,7 +389,7 @@ } a { - color: var(--color-brand-dark); + color: var(--color-brand-dark-mid-saturation); } p.meta { @@ -465,8 +473,8 @@ /* Create an active/current tablink class */ .tab.active, .subtab.active { - box-shadow: 0 -2px 0 inset var(--color-brand-dark); - border-bottom: 1px solid var(--color-brand-dark); + box-shadow: 0 -2px 0 inset var(--color-brand-dark-min-saturation); + border-bottom: 1px solid var(--color-brand-dark-min-saturation); } .user, @@ -486,16 +494,17 @@ button:focus, input[type="file"]::file-selector-button:focus, select:focus { - outline: 4px double var(--color-brand-dark); - border: 1px solid var(--color-brand-dark); + outline: 4px double var(--color-brand-dark-min-saturation); + border: 1px solid var(--color-brand-dark-min-saturation); box-shadow: none; } input[type="submit"], button, - .btn { + .btn, + input[type="file"]::file-selector-button { border: 0px; - background-color: var(--color-brand-dark); + background-color: var(--color-brand-dark-min-saturation); color: var(--color-text); } @@ -503,10 +512,10 @@ .formBody button, .btn { background-color: var(--color-dark); - color: var(--color-brand-dark); - border: 1px solid var(--color-brand-dark); + color: var(--color-brand-dark-min-saturation); + border: 1px solid var(--color-brand-dark-max-saturation); box-shadow: 0px 2px 0px 0px oklch( /* rgba(125, 37, 193, 0.25) */ - from var(--color-brand) l c h / 0.25 + from var(--color-brand-dark-max-saturation) l c h / 0.25 ); } @@ -528,14 +537,16 @@ .btn-danger, .formBody .btn-danger { - color: lightpink; - border-color: lightpink; + background-color: darkred; + color: white; + border: 1px solid oklch(1 0 0 / 0.55); + box-shadow: 0px 4px 8px -4px oklch(1 0 0 / 0.25); } p.badge { background-color: var(--color-dark-bg-alt-1); - color: var(--color-brand-dark); - border: 1px solid var(--color-brand-dark); + color: var(--color-brand-dark-min-saturation); + border: 1px solid var(--color-brand-dark-max-saturation); } h2.submit + .badgeContainer p.badge { @@ -598,7 +609,7 @@ } .toggle-ui input[type="checkbox"]:checked ~ label .toggle { - background: var(--color-brand-dark); + background: var(--color-brand-dark-mid-saturation); } footer { @@ -646,7 +657,7 @@ } .banner { - background-color: var(--color-brand-dark); /* #ecdafa */ + background-color: var(--color-brand-dark-min-saturation); /* #ecdafa */ color: #333; } From 7f154fe070b83e2868b01abb5db0bb50ba50e3e7 Mon Sep 17 00:00:00 2001 From: Jeremy Moore Date: Tue, 17 Sep 2024 13:17:12 -0400 Subject: [PATCH 39/59] lint --- hushline/static/css/style.css | 78 ++++++++++++----------- hushline/static/js/settings.js | 2 - hushline/templates/settings.html | 106 +++++++++++++++---------------- 3 files changed, 94 insertions(+), 92 deletions(-) diff --git a/hushline/static/css/style.css b/hushline/static/css/style.css index b080c4cb..9fea8801 100644 --- a/hushline/static/css/style.css +++ b/hushline/static/css/style.css @@ -2,7 +2,7 @@ --color-border: rgba(0, 0, 0, 0.18); --color-border-dark: #7d7d7d; --color-border-dark-1: #555; - --color-brand: oklch(48.55% 0.222 304.47); /* #7d25c1 */ + --color-brand: oklch(48.55% 0.222 304.47); /* #7d25c1 */ --color-brand-min-contrast: oklch( from var(--color-brand) clamp(0.1, l, 0.6) c h ); @@ -13,35 +13,34 @@ from var(--color-brand-min-contrast) max(l - 0.25, 0.1) calc(1.25 * c) h ); --color-brand-dark-min-saturation: oklch( - from var(--color-brand) - clamp(0.96, l + 0.425, 0.98) calc(0.5 * c) h + from var(--color-brand) clamp(0.96, l + 0.425, 0.98) calc(0.5 * c) h ); --color-brand-dark-mid-saturation: oklch( - from var(--color-brand) - clamp(0.93, l + 0.4, 0.98) calc(0.75 * c) h + from var(--color-brand) clamp(0.93, l + 0.4, 0.98) calc(0.75 * c) h ); --color-brand-dark-max-saturation: oklch( - from var(--color-brand) - clamp(0.8, l + 0.175, 0.94) calc(1.5 * c) calc(h - 6) + from var(--color-brand) clamp(0.8, l + 0.175, 0.94) calc(1.5 * c) + calc(h - 6) ); --color-text: #333; --color-text-dark-alt-2: #e4e4e4; --color-text-dark-alt: #c2c2c2; --color-text-dark: #dadada; --color-text-light: #595959; - --color-highlight: oklch( /* rgba(125, 37, 193, 0.1) */ - from var(--color-brand) l c h / 0.1 + --color-highlight: oklch( + /* rgba(125, 37, 193, 0.1) */ from var(--color-brand) l c h / 0.1 ); - --color-highlight-dark: oklch( /* rgba(220, 198, 237, 0.2) */ - from var(--color-brand-dark-mid-saturation) l c h / 0.2 + --color-highlight-dark: oklch( + /* rgba(220, 198, 237, 0.2) */ from var(--color-brand-dark-mid-saturation) l + c h / 0.2 ); - --color-brand-bg: oklch( /* #fbf3ff */ - from var(--color-brand) - clamp(0.8, l + 0.4883, 0.98) min(c, 0.0179) calc(h + 10.26) + --color-brand-bg: oklch( + /* #fbf3ff */ from var(--color-brand) clamp(0.8, l + 0.4883, 0.98) + min(c, 0.0179) calc(h + 10.26) ); - --color-dark-bg: oklch( /* was #222 now with brand hue */ - from var(--color-brand) - clamp(0.18, l - 0.275, 0.215) min(c, 0.01) calc(h + 10.26) + --color-dark-bg: oklch( + /* was #222 now with brand hue */ from var(--color-brand) + clamp(0.18, l - 0.275, 0.215) min(c, 0.01) calc(h + 10.26) ); --color-dark-bg-alt: #333; --color-dark-bg-alt-1: #444; @@ -171,8 +170,8 @@ /* Change background color of tabs on hover */ .tab:hover, .subtab:hover { - background-color: oklch( /* #fbf3ff */ - from var(--color-brand-bg) 0.97 c h / 0.85 + background-color: oklch( + /* #fbf3ff */ from var(--color-brand-bg) 0.97 c h / 0.85 ); } @@ -219,9 +218,11 @@ background-color: white; color: var(--color-brand-mid-contrast); border: 1px solid var(--color-brand-min-contrast); - box-shadow: 0px 2px 0px 0px oklch( /* rgba(125, 37, 193, 0.25) */ - from var(--color-brand) min(l, 0.95) c h / 0.25 - ); + box-shadow: 0px 2px 0px 0px + oklch( + /* rgba(125, 37, 193, 0.25) */ from var(--color-brand) min(l, 0.95) c h / + 0.25 + ); } .formBody input[type="submit"]:hover, @@ -236,9 +237,11 @@ .btn:active, input[type="file"]::file-selector-button:active, select:active { - box-shadow: inset 0px 2px 0px 0px oklch( /* rgba(125, 37, 193, 0.25) */ - from var(--color-brand) min(l, 0.95) c h / 0.25 - ); + box-shadow: inset 0px 2px 0px 0px + oklch( + /* rgba(125, 37, 193, 0.25) */ from var(--color-brand) min(l, 0.95) c h / + 0.25 + ); } .btn-danger, @@ -465,8 +468,9 @@ /* Change background color of tabs on hover */ .tab:hover, .subtab:hover { - background-color: oklch( /* was rgba(255, 255, 255, 0.1) now with color */ - from var(--color-dark-bg) 0.15 calc(12 * c) h / 0.0675 + background-color: oklch( + /* was rgba(255, 255, 255, 0.1) now with color */ from + var(--color-dark-bg) 0.15 calc(12 * c) h / 0.0675 ); } @@ -514,9 +518,11 @@ background-color: var(--color-dark); color: var(--color-brand-dark-min-saturation); border: 1px solid var(--color-brand-dark-max-saturation); - box-shadow: 0px 2px 0px 0px oklch( /* rgba(125, 37, 193, 0.25) */ - from var(--color-brand-dark-max-saturation) l c h / 0.25 - ); + box-shadow: 0px 2px 0px 0px + oklch( + /* rgba(125, 37, 193, 0.25) */ from + var(--color-brand-dark-max-saturation) l c h / 0.25 + ); } .formBody input[type="submit"]:hover, @@ -530,9 +536,8 @@ .formBody button:active, .btn:active, input[type="file"]::file-selector-button:active { - box-shadow: inset 0px 2px 0px 0px oklch( /* rgba(125, 37, 193, 0.25) */ - from var(--color-brand) l c h / 0.25 - ); + box-shadow: inset 0px 2px 0px 0px + oklch(/* rgba(125, 37, 193, 0.25) */ from var(--color-brand) l c h / 0.25); } .btn-danger, @@ -657,7 +662,7 @@ } .banner { - background-color: var(--color-brand-dark-min-saturation); /* #ecdafa */ + background-color: var(--color-brand-dark-min-saturation); /* #ecdafa */ color: #333; } @@ -1216,9 +1221,8 @@ button, input[type="file"]::file-selector-button { cursor: pointer; text-decoration: none; - box-shadow: 0px 2px 0px 0px oklch( /* rgba(125, 37, 193, 0.25) */ - from var(--color-brand) l c h / 0.25 - ); + box-shadow: 0px 2px 0px 0px + oklch(/* rgba(125, 37, 193, 0.25) */ from var(--color-brand) l c h / 0.25); } textarea { diff --git a/hushline/static/js/settings.js b/hushline/static/js/settings.js index 9d585e78..f3bf64d8 100644 --- a/hushline/static/js/settings.js +++ b/hushline/static/js/settings.js @@ -76,7 +76,6 @@ document.addEventListener("DOMContentLoaded", function () { }); if (document.getElementById("branding")) { - // Update color in real-time as they're being browsed & finalized const colorPicker = document.getElementById("brand-primary-color"); @@ -94,7 +93,6 @@ document.addEventListener("DOMContentLoaded", function () { appNameBox.addEventListener("input", function (event) { document.querySelector("h1").innerText = event.target.value; }); - } var forwarding_enabled = document.querySelector( diff --git a/hushline/templates/settings.html b/hushline/templates/settings.html index b9bec0db..eb4a2d09 100644 --- a/hushline/templates/settings.html +++ b/hushline/templates/settings.html @@ -44,19 +44,19 @@

    Settings

  • {% if user.is_admin -%} -
  • - -
  • +
  • + +
  • {%- endif %}
  • - +

    Primary Color

    +
    + {{ update_brand_primary_color_form.hidden_tag() }} + + + +
    -

    App Name

    -
    - {{ update_brand_app_name_form.hidden_tag() }} - - - -
    - +

    App Name

    +
    + {{ update_brand_app_name_form.hidden_tag() }} + + + +
    + {%- endif %}
    Date: Tue, 17 Sep 2024 15:37:17 -0400 Subject: [PATCH 40/59] fix: make dark danger button border more subtle #573 For Roadmap Item #533 --- hushline/static/css/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hushline/static/css/style.css b/hushline/static/css/style.css index b080c4cb..d4c44dfc 100644 --- a/hushline/static/css/style.css +++ b/hushline/static/css/style.css @@ -539,7 +539,7 @@ .formBody .btn-danger { background-color: darkred; color: white; - border: 1px solid oklch(1 0 0 / 0.55); + border: 1px solid oklch(1 0 0 / 0.15); box-shadow: 0px 4px 8px -4px oklch(1 0 0 / 0.25); } From 65c183f511f3796b75107beb8b07e03a1bc6bc62 Mon Sep 17 00:00:00 2001 From: rmlibre Date: Tue, 17 Sep 2024 15:40:09 -0400 Subject: [PATCH 41/59] fix: convert the dark hamburger menu icon to color neutral #573 For Roadmap Item #533 --- hushline/static/img/app/icon-menu-light.png | Bin 244 -> 686 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/hushline/static/img/app/icon-menu-light.png b/hushline/static/img/app/icon-menu-light.png index b111b82911da30345ee5dceafb4ccae6e78a9979..9e32607c142cb6686c531e6520c7eb458c663968 100644 GIT binary patch delta 649 zcmV;40(SlM0j>p*9De~I1<4Tr00D$)LqkwWLqi~Na&Km7Y-IodD3N`UJxIeq9K~N- zr79H>JE%Bhs7@B7ia2T&iclfc3avVrT>1q~8j=(jN5Qq=;KyRs!Nplu2UkH5`~Y!w za8h)U691PJTEuv8+>dwn9(V5mf4$69vtt5KHOojP;zB07Dt`oD(T#o#UU6f~epZjz4Dmjw@K7n|a>4rtTK|H-_>74h8!>lMN#OK8023?T&k?XR{ zZ=4Gb`*~*ANT=qB!^A?Njpa6GMMEW?B917kM*04X%L?Z$&T6^Jn)l={4CS~|df(QXJs(&b<3=2_OHBwBZXg}`ZA9nmAxny#cz{s(H3RFmrAN&t~cWdS+V{TF? z4s^cQ_Qxm?+yxpn+x|Yb?ZycZcm}StmcLR5WlN54hX`2A_1v zkQ^yM)1S`+?`QN)SzzE6=vi}nYwqLp0Z3C<$s6F{5EvLMQ1-gVySv(Z`}a($o&@0p*H$-k)iq_8WZc8RrOB90bs!?vDlp6gJzqEbSaheeZQIG#g@#Mnf=_* z^M+`erbp@!#WprgB%|npF1v9#sJ=xwKIY-`2RDF8A692 jahe$+RAmSO08q6zg5Ex|v8~y100000NkvXXu0mjfBQGw4 delta 205 zcmV;;05bos1@r-s90dSZ0{MrNu>lx=0drDELIAGL9O(c600d`2O+f$vv5yPm`k|+J&0G4d) zEK2|Y002-aE1~-iZ`o%p>~cRnRp_7$`=y=8H6XQGnxhvglnU|xH$J>Vh_AOiW2-`` z3^dY55n4g Date: Tue, 17 Sep 2024 17:04:53 -0400 Subject: [PATCH 42/59] fix: apply subtle theme color to dark menu label text #573 For Roadmap Item #533 --- hushline/static/css/style.css | 1 + 1 file changed, 1 insertion(+) diff --git a/hushline/static/css/style.css b/hushline/static/css/style.css index b7c81bb9..be2771ae 100644 --- a/hushline/static/css/style.css +++ b/hushline/static/css/style.css @@ -416,6 +416,7 @@ .mobileNav { background-image: url("../img/app/icon-menu-light.png"); + color: var(--color-brand-dark-min-saturation); } .container { From 416db1b64cd145b81db3d33346095126d613a6f0 Mon Sep 17 00:00:00 2001 From: rmlibre Date: Tue, 17 Sep 2024 17:05:59 -0400 Subject: [PATCH 43/59] fix: improve dark theme accessibility with wider contrasts #573 In dark mode, now the greyest text has a +7:1 contrast on top of the lightest background. https://webaim.org/resources/contrastchecker/?fcolor=C4C4C4&bcolor=353535 For Roadmap Item #533 --- hushline/static/css/style.css | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/hushline/static/css/style.css b/hushline/static/css/style.css index be2771ae..4b1ea633 100644 --- a/hushline/static/css/style.css +++ b/hushline/static/css/style.css @@ -1,7 +1,7 @@ :root { --color-border: rgba(0, 0, 0, 0.18); - --color-border-dark: #7d7d7d; - --color-border-dark-1: #555; + --color-border-dark: #787878; + --color-border-dark-1: #505050; --color-brand: oklch(48.55% 0.222 304.47); /* #7d25c1 */ --color-brand-min-contrast: oklch( from var(--color-brand) clamp(0.1, l, 0.6) c h @@ -23,9 +23,9 @@ calc(h - 6) ); --color-text: #333; - --color-text-dark-alt-2: #e4e4e4; - --color-text-dark-alt: #c2c2c2; - --color-text-dark: #dadada; + --color-text-dark-alt-2: #e6e6e6; + --color-text-dark-alt: #c4c4c4; + --color-text-dark: #dcdcdc; --color-text-light: #595959; --color-highlight: oklch( /* rgba(125, 37, 193, 0.1) */ from var(--color-brand) l c h / 0.1 @@ -40,10 +40,10 @@ ); --color-dark-bg: oklch( /* was #222 now with brand hue */ from var(--color-brand) - clamp(0.18, l - 0.275, 0.215) min(c, 0.01) calc(h + 10.26) + clamp(0.18, l - 0.275, 0.2) min(c, 0.01) calc(h + 10.26) ); - --color-dark-bg-alt: #333; - --color-dark-bg-alt-1: #444; + --color-dark-bg-alt: #272727; + --color-dark-bg-alt-1: #353535; --color-dark-bg-alt-transparent: rgba(51, 51, 51, 0.7); --border: 1px solid var(--color-border); From 2cf9518c8cac279d9f03b1ee1b4b9bf8e5148295 Mon Sep 17 00:00:00 2001 From: rmlibre Date: Wed, 18 Sep 2024 09:12:34 -0400 Subject: [PATCH 44/59] docs: provide hex color code example in error message #573 Suggested in review: https://github.com/scidsg/hushline/pull/573#discussion_r1764482960 For Roadmap Item #533 Co-authored-by: brassy endomorph --- hushline/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hushline/forms.py b/hushline/forms.py index 33e33425..d5aed42c 100644 --- a/hushline/forms.py +++ b/hushline/forms.py @@ -33,7 +33,7 @@ class HexColor: def __call__(self, form: Form, field: Field) -> None: color: str = field.data if not self.hex_color_regex.match(color): - raise ValidationError(f"{color=} is an invalid 6-hexit color code.") + raise ValidationError(f"{color=} is an invalid 6-hexit color code. (eg. #7d25c1)") class CanonicalHTML: From 007cf1b1688684d8cf8751d28d8f4537788fb390 Mon Sep 17 00:00:00 2001 From: rmlibre Date: Wed, 18 Sep 2024 11:34:14 -0400 Subject: [PATCH 45/59] refactor: apply DRY principles & avoid magic value use #573 Removed reliance on javascript for insertion of database values, instead directly rely on jinja to fill elements from the database. Suggested in review: https://github.com/scidsg/hushline/pull/573#discussion_r1764484915 https://github.com/scidsg/hushline/pull/573#discussion_r1764487410 For Roadmap Item #533 Co-authored-by: brassy endomorph --- hushline/__init__.py | 2 +- hushline/model.py | 22 ++++++++++++++++++---- hushline/settings.py | 4 ++-- hushline/templates/base.html | 20 +++++--------------- 4 files changed, 26 insertions(+), 22 deletions(-) diff --git a/hushline/__init__.py b/hushline/__init__.py index 15c6ab84..b56a6326 100644 --- a/hushline/__init__.py +++ b/hushline/__init__.py @@ -79,7 +79,7 @@ def inject_user() -> dict[str, Any]: @app.context_processor def inject_host() -> dict[str, HostOrganization]: - if (host_org := db.session.get(HostOrganization, 1)) is None: + if (host_org := HostOrganization.default()) is None: host_org = HostOrganization() db.session.add(host_org) db.session.commit() diff --git a/hushline/model.py b/hushline/model.py index 4866a070..d29c960c 100644 --- a/hushline/model.py +++ b/hushline/model.py @@ -1,7 +1,7 @@ import enum import secrets from datetime import datetime, timedelta, timezone -from typing import TYPE_CHECKING, Optional, Set +from typing import TYPE_CHECKING, Optional, Self, Set from flask import current_app from flask_sqlalchemy.model import Model @@ -31,9 +31,23 @@ def default(cls) -> "SMTPEncryption": class HostOrganization(Model): __tablename__ = "host_organization" - id: Mapped[int] = mapped_column(primary_key=True, default=1) - brand_app_name: Mapped[str] = mapped_column(db.String(255), default="🤫 Hush Line") - brand_primary_hex_color: Mapped[str] = mapped_column(db.String(7), default="#7d25c1") + _DEFAULT_PRIMARY_KEY: int = 1 + _DEFAULT_BRAND_PRIMARY_HEX_COLOR: str = "#7d25c1" + _DEFAULT_BRAND_APP_NAME: str = "🤫 Hush Line" + + id: Mapped[int] = mapped_column(primary_key=True) + brand_app_name: Mapped[str] = mapped_column(db.String(255), default=_DEFAULT_BRAND_APP_NAME) + brand_primary_hex_color: Mapped[str] = mapped_column( + db.String(7), default=_DEFAULT_BRAND_PRIMARY_HEX_COLOR + ) + + @classmethod + def default(cls) -> Self | None: + return db.session.get(cls, cls._DEFAULT_PRIMARY_KEY) + + def __init__(self, primary_key: int | None = None) -> None: + super().__init__() + self.id = primary_key if isinstance(primary_key, int) else self._DEFAULT_PRIMARY_KEY class User(Model): diff --git a/hushline/settings.py b/hushline/settings.py index 3aae89b7..e9ffc808 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -722,7 +722,7 @@ def update_smtp_settings() -> Response | str: @bp.route("/update-brand-primary-color", methods=["POST"]) @admin_authentication_required def update_brand_primary_color() -> Response | str: - if (host_org := db.session.get(HostOrganization, 1)) is None: + if (host_org := HostOrganization.default()) is None: abort(500) form = UpdateBrandPrimaryColorForm() @@ -738,7 +738,7 @@ def update_brand_primary_color() -> Response | str: @bp.route("/update-brand-app-name", methods=["POST"]) @admin_authentication_required def update_brand_app_name() -> Response | str: - if (host_org := db.session.get(HostOrganization, 1)) is None: + if (host_org := HostOrganization.default()) is None: abort(500) form = UpdateBrandAppNameForm() diff --git a/hushline/templates/base.html b/hushline/templates/base.html index 3a1a0ded..5ec737f1 100644 --- a/hushline/templates/base.html +++ b/hushline/templates/base.html @@ -64,18 +64,11 @@ rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}" /> - + @@ -107,7 +100,7 @@
    -

    🤫 Hush Line

    +

    {{ host_org.brand_app_name }}

    -
    From 4097ffe5741400d260a55d27607f60e495481419 Mon Sep 17 00:00:00 2001 From: rmlibre Date: Wed, 18 Sep 2024 12:33:28 -0400 Subject: [PATCH 46/59] fix(exc): log missing ``host_org`` record error state #573 Suggested in review: https://github.com/scidsg/hushline/pull/573#discussion_r1764486752 For Roadmap Item #533 Co-authored-by: brassy endomorph --- hushline/settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hushline/settings.py b/hushline/settings.py index e9ffc808..c0e1dec0 100644 --- a/hushline/settings.py +++ b/hushline/settings.py @@ -723,6 +723,7 @@ def update_smtp_settings() -> Response | str: @admin_authentication_required def update_brand_primary_color() -> Response | str: if (host_org := HostOrganization.default()) is None: + current_app.logger.error("Fatal! `host_org` record is undefined.") abort(500) form = UpdateBrandPrimaryColorForm() @@ -739,6 +740,7 @@ def update_brand_primary_color() -> Response | str: @admin_authentication_required def update_brand_app_name() -> Response | str: if (host_org := HostOrganization.default()) is None: + current_app.logger.error("Fatal! `host_org` record is undefined.") abort(500) form = UpdateBrandAppNameForm() From 06042b73b1b8367c9a117296d3addc114357e772 Mon Sep 17 00:00:00 2001 From: rmlibre Date: Wed, 18 Sep 2024 13:59:40 -0400 Subject: [PATCH 47/59] style: attempt prettier ignore on HTML style element #573 For Roadmap Item #533 --- hushline/templates/base.html | 1 + 1 file changed, 1 insertion(+) diff --git a/hushline/templates/base.html b/hushline/templates/base.html index 5ec737f1..919e6f34 100644 --- a/hushline/templates/base.html +++ b/hushline/templates/base.html @@ -64,6 +64,7 @@ rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}" /> +