Add a Light theme and a theme detection/selection feature

This commit is contained in:
Bubka 2023-02-01 17:21:55 +01:00
parent 4b0f2186da
commit eadebb41ac
32 changed files with 439 additions and 168 deletions

View File

@ -20,6 +20,7 @@ public function index()
$subdir = config('2fauth.config.appSubdirectory') ? '/' . config('2fauth.config.appSubdirectory') : '';
return view('landing')->with([
'theme' => Settings::get('theme'),
'appSettings' => Settings::all()->toJson(),
'appConfig' => collect([
'proxyAuth' => config('auth.defaults.guard') === 'reverse-proxy-guard' ? true : false,

View File

@ -71,6 +71,7 @@
'checkForUpdate' => true,
'lastRadarScan' => 0,
'latestRelease' => false,
'theme' => 'dark',
],
];

2
public/css/app.css vendored

File diff suppressed because one or more lines are too long

2
public/js/app.js vendored

File diff suppressed because one or more lines are too long

View File

@ -1,12 +1,3 @@
/*
* [hi-base32]{@link https://github.com/emn178/hi-base32}
*
* @version 0.5.0
* @author Chen, Yi-Cyuan [emn178@gmail.com]
* @copyright Chen, Yi-Cyuan 2015-2018
* @license MIT
*/
/*!
* Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)

View File

@ -1,6 +1,6 @@
{
"/js/app.js": "/js/app.js?id=240f28920699c7791d10dc3255e51edd",
"/js/app.js": "/js/app.js?id=31a3cc6392bca913e0ffd620b73f373d",
"/js/manifest.js": "/js/manifest.js?id=af5ab3286fe62cebba2085465b83b8b5",
"/css/app.css": "/css/app.css?id=7a62b621b3fdb6e0e88a7230fdfbd075",
"/css/app.css": "/css/app.css?id=d169eec83285d15944c82fa6f28c8bb2",
"/js/vendor.js": "/js/vendor.js?id=5b9def2003ec2c6749359957e3a52c0e"
}

27
resources/js/app.js vendored
View File

@ -18,7 +18,32 @@ const app = new Vue({
appSettings: window.appSettings,
appConfig: window.appConfig,
isDemoApp: window.isDemoApp,
isTestingApp: window.isTestingApp
isTestingApp: window.isTestingApp,
prefersDarkScheme: window.matchMedia('(prefers-color-scheme: dark)').matches
},
computed: {
showDarkMode: function() {
return this.appSettings.theme == 'dark' ||
(this.appSettings.theme == 'system' && this.prefersDarkScheme)
}
},
mounted () {
this.mediaQueryList = window.matchMedia('(prefers-color-scheme: dark)')
this.$nextTick(() => {
this.mediaQueryList.addEventListener('change', this.setDarkScheme)
})
},
beforeDestroy () {
this.mediaQueryList.removeEventListener('change', this.setDarkScheme)
},
methods: {
setDarkScheme ({ matches }) {
this.prefersDarkScheme = matches
}
},
i18n,
router,

View File

@ -1,5 +1,5 @@
<template>
<footer class="has-background-black-ter">
<footer>
<div class="columns is-gapless" v-if="showButtons">
<div class="column has-text-centered">
<div class="field is-grouped">

View File

@ -26,14 +26,14 @@
<div v-if="showRules" class="columns is-mobile is-size-7 mt-0">
<div class="column is-one-third">
<span class="has-text-weight-semibold">{{ $t("auth.forms.mandatory_rules") }}</span><br />
<span class="is-underscored" :class="{'has-background-success-dark is-dot' : IsLongEnough}"></span>{{ $t('auth.forms.is_long_enough') }}<br/>
<span class="is-underscored" :class="{'is-dot' : IsLongEnough}"></span>{{ $t('auth.forms.is_long_enough') }}<br/>
</div>
<div class="column">
<span class="has-text-weight-semibold">{{ $t("auth.forms.optional_rules_you_should_follow") }}</span><br />
<span class="is-underscored" :class="{'has-background-success-dark is-dot' : hasLowerCase}"></span>{{ $t('auth.forms.has_lower_case') }}<br/>
<span class="is-underscored" :class="{'has-background-success-dark is-dot' : hasUpperCase}"></span>{{ $t('auth.forms.has_upper_case') }}<br/>
<span class="is-underscored" :class="{'has-background-success-dark is-dot' : hasSpecialChar}"></span>{{ $t('auth.forms.has_special_char') }}<br/>
<span class="is-underscored" :class="{'has-background-success-dark is-dot' : hasNumber}"></span>{{ $t('auth.forms.has_number') }}
<span class="is-underscored" :class="{'is-dot' : hasLowerCase}"></span>{{ $t('auth.forms.has_lower_case') }}<br/>
<span class="is-underscored" :class="{'is-dot' : hasUpperCase}"></span>{{ $t('auth.forms.has_upper_case') }}<br/>
<span class="is-underscored" :class="{'is-dot' : hasSpecialChar}"></span>{{ $t('auth.forms.has_special_char') }}<br/>
<span class="is-underscored" :class="{'is-dot' : hasNumber}"></span>{{ $t('auth.forms.has_number') }}
</div>
</div>
</div>

View File

@ -5,12 +5,15 @@
<button
role="radio"
type="button"
class="button is-dark"
class="button"
:aria-checked="form[fieldName] === choice.value"
:disabled="isDisabled"
v-for="(choice, index) in choices"
:key="index"
:class="{ 'is-link' : form[fieldName] === choice.value }"
:class="{
'is-link' : form[fieldName] === choice.value,
'is-dark' : $root.showDarkMode
}"
v-on:click.stop="setRadio(choice.value)"
>
<input

View File

@ -5,7 +5,7 @@
<section class="section">
<div class="columns is-centered">
<div class="column is-three-quarters">
<div class="box has-text-centered has-background-black-ter is-shadowless">
<div class="modal-slot box has-text-centered is-shadowless">
<slot></slot>
</div>
</div>
@ -14,7 +14,7 @@
</div>
<div v-if="this.showcloseButton" class="fullscreen-footer">
<!-- Close button -->
<button ref="closeModalButton" class="button is-dark is-rounded" @click.stop="closeModal">
<button ref="closeModalButton" class="button is-rounded" :class="{'is-dark' : $root.showDarkMode}" @click.stop="closeModal">
{{ $t('commons.close') }}
</button>
</div>

View File

@ -3,10 +3,10 @@
<figure class="image is-64x64" :class="{ 'no-icon': !internal_icon }" style="display: inline-block">
<img :src="$root.appConfig.subdirectory + '/storage/icons/' + internal_icon" v-if="internal_icon" :alt="$t('twofaccounts.icon_to_illustrate_the_account')">
</figure>
<p class="is-size-4 has-text-grey-light has-ellipsis">{{ internal_service }}</p>
<p class="is-size-6 has-text-grey has-ellipsis">{{ internal_account }}</p>
<p class="is-size-4 has-ellipsis" :class="$root.showDarkMode ? 'has-text-grey-light' : 'has-text-grey'">{{ internal_service }}</p>
<p class="is-size-6 has-ellipsis" :class="$root.showDarkMode ? 'has-text-grey' : 'has-text-grey-light'">{{ internal_account }}</p>
<p>
<span role="log" ref="otp" tabindex="0" class="otp is-size-1 has-text-white is-clickable px-3" @click="copyOTP(internal_password)" @keyup.enter="copyOTP(internal_password)" :title="$t('commons.copy_to_clipboard')">
<span role="log" ref="otp" tabindex="0" class="otp is-size-1 is-clickable px-3" :class="$root.showDarkMode ? 'has-text-white' : 'has-text-grey-dark'" @click="copyOTP(internal_password)" @keyup.enter="copyOTP(internal_password)" :title="$t('commons.copy_to_clipboard')">
{{ displayedOtp }}
</span>
</p>

View File

@ -1,5 +1,5 @@
<template>
<div class="options-header has-background-black-ter">
<div class="options-header">
<responsive-width-wrapper>
<div class="tabs is-centered is-fullwidth">
<ul>

View File

@ -97,6 +97,10 @@ Vue.mixin({
// url
// week
},
setTheme(theme) {
document.documentElement.dataset.theme = theme;
}
}
})

View File

@ -38,7 +38,10 @@ import {
faEyeSlash,
faExternalLinkAlt,
faCamera,
faFileDownload
faFileDownload,
faSun,
faMoon,
faDesktop
} from '@fortawesome/free-solid-svg-icons'
import {
@ -81,7 +84,10 @@ library.add(
faEyeSlash,
faExternalLinkAlt,
faCamera,
faFileDownload
faFileDownload,
faSun,
faMoon,
faDesktop
);
Vue.component('font-awesome-icon', FontAwesomeIcon)

View File

@ -2,10 +2,10 @@
<responsive-width-wrapper>
<h1 class="title has-text-grey-dark">{{ pagetitle }}</h1>
<p class="block">
<span class="has-text-white"><span class="is-size-5">2FAuth</span> v{{ appVersion }}</span><br />
<span :class="$root.showDarkMode ? 'has-text-white':'has-text-black'"><span class="is-size-5">2FAuth</span> v{{ appVersion }}</span><br />
{{ $t('commons.2fauth_teaser')}}
</p>
<img src="logo.svg" style="height: 32px" alt="2FAuth logo" />
<img class="about-logo" src="logo.svg" alt="2FAuth logo" />
<p class="block" :class="showUserOptions ? 'mb-5' : '' ">
©Bubka <a class="is-size-7" href="https://github.com/Bubka/2FAuth/blob/master/LICENSE">AGPL-3.0 license</a>
</p>
@ -13,25 +13,25 @@
{{ $t('commons.resources') }}
</h2>
<div class="buttons">
<a class="button is-dark" href="https://github.com/Bubka/2FAuth" target="_blank">
<a class="button" :class="{'is-dark' : $root.showDarkMode}" href="https://github.com/Bubka/2FAuth" target="_blank">
<span class="icon is-small">
<font-awesome-icon :icon="['fab', 'github-alt']" />
</span>
<span>Github</span>
</a>
<a class="button is-dark" href="https://docs.2fauth.app/" target="_blank">
<a class="button" :class="{'is-dark' : $root.showDarkMode}" href="https://docs.2fauth.app/" target="_blank">
<span class="icon is-small">
<font-awesome-icon :icon="['fas', 'book']" />
</span>
<span>Docs</span>
</a>
<a class="button is-dark" href="https://demo.2fauth.app/" target="_blank">
<a class="button" :class="{'is-dark' : $root.showDarkMode}" href="https://demo.2fauth.app/" target="_blank">
<span class="icon is-small">
<font-awesome-icon :icon="['fas', 'flask']" />
</span>
<span>Demo</span>
</a>
<a class="button is-dark" href="https://docs.2fauth.app/resources/rapidoc.html" target="_blank">
<a class="button" :class="{'is-dark' : $root.showDarkMode}" href="https://docs.2fauth.app/resources/rapidoc.html" target="_blank">
<span class="icon is-small">
<font-awesome-icon :icon="['fas', 'code']" />
</span>
@ -51,7 +51,7 @@
<h2 class="title is-5 has-text-grey-light">
{{ $t('commons.environment') }}
</h2>
<div class="box has-background-black-bis is-family-monospace is-size-7">
<div class="about-debug box is-family-monospace is-size-7">
<button :aria-label="$t('commons.copy_to_clipboard')" class="button is-like-text is-pulled-right is-small is-text" v-clipboard="() => this.$refs.listInfos.innerText" v-clipboard:success="clipboardSuccessHandler">
<font-awesome-icon :icon="['fas', 'copy']" />
</button>
@ -63,7 +63,7 @@
<h2 class="title is-5 has-text-grey-light">
{{ $t('settings.user_options') }}
</h2>
<div class="box has-background-black-bis is-family-monospace is-size-7">
<div class="about-debug box is-family-monospace is-size-7">
<button :aria-label="$t('commons.copy_to_clipboard')" class="button is-like-text is-pulled-right is-small is-text" v-clipboard="() => this.$refs.listUserOptions.innerText" v-clipboard:success="clipboardSuccessHandler">
<font-awesome-icon :icon="['fas', 'copy']" />
</button>
@ -76,7 +76,7 @@
<vue-footer :showButtons="true">
<!-- close button -->
<p class="control">
<router-link :to="{ name: 'accounts', params: { toRefresh: true } }" role="button" :aria-label="$t('commons.close_the_x_page', {pagetitle: pagetitle})" class="button is-dark is-rounded">{{ $t('commons.close') }}</router-link>
<router-link :to="{ name: 'accounts', params: { toRefresh: true } }" role="button" :aria-label="$t('commons.close_the_x_page', {pagetitle: pagetitle})" class="button is-rounded" :class="{'is-dark' : $root.showDarkMode}">{{ $t('commons.close') }}</router-link>
</p>
</vue-footer>
</responsive-width-wrapper>

View File

@ -6,7 +6,7 @@
<div class="column is-one-third-tablet is-one-quarter-desktop is-one-quarter-widescreen is-one-quarter-fullhd">
<div class="columns is-multiline">
<div class="column is-full" v-for="group in groups" v-if="group.twofaccounts_count > 0" :key="group.id">
<button class="button is-fullwidth is-dark has-text-light is-outlined" @click="setActiveGroup(group.id)">{{ group.name }}</button>
<button class="button is-fullwidth" :class="{'is-dark has-text-light is-outlined':$root.showDarkMode}" @click="setActiveGroup(group.id)">{{ group.name }}</button>
</div>
</div>
<div class="columns is-centered">
@ -19,7 +19,7 @@
<vue-footer :showButtons="true">
<!-- Close Group switch button -->
<p class="control">
<button class="button is-dark is-rounded" @click="closeGroupSwitch()">{{ $t('commons.close') }}</button>
<button class="button is-rounded" :class="{'is-dark' : $root.showDarkMode}" @click="closeGroupSwitch()">{{ $t('commons.close') }}</button>
</p>
</vue-footer>
</div>
@ -32,7 +32,7 @@
<div class="column is-one-third-tablet is-one-quarter-desktop is-one-quarter-widescreen is-one-quarter-fullhd">
<div class="columns is-multiline">
<div class="column is-full" v-for="group in groups" :key="group.id">
<button class="button is-fullwidth is-dark has-text-light is-outlined" :class="{ 'is-link' : moveAccountsTo === group.id}" @click="moveAccountsTo = group.id">
<button class="button is-fullwidth" :class="{'is-link' : moveAccountsTo === group.id, 'is-dark has-text-light is-outlined':$root.showDarkMode}" @click="moveAccountsTo = group.id">
<span v-if="group.id === 0" class="is-italic">
{{ $t('groups.no_group') }}
</span>
@ -56,12 +56,12 @@
</p>
<!-- Cancel button -->
<p class="control">
<button class="button is-dark is-rounded" @click="showGroupSelector = false">{{ $t('commons.cancel') }}</button>
<button class="button is-rounded" :class="{'is-dark' : $root.showDarkMode}" @click="showGroupSelector = false">{{ $t('commons.cancel') }}</button>
</p>
</vue-footer>
</div>
<!-- header -->
<div class="header has-background-black-ter" v-if="this.showAccounts || this.showGroupSwitch">
<div class="header" v-if="this.showAccounts || this.showGroupSwitch">
<div class="columns is-gapless is-mobile is-centered">
<div class="column is-three-quarters-mobile is-one-third-tablet is-one-quarter-desktop is-one-quarter-widescreen is-one-quarter-fullhd">
<!-- search -->
@ -79,19 +79,19 @@
<div class="columns">
<div class="column">
<!-- selected label -->
<span class="mr-1">{{ selectedAccounts.length }}&nbsp;{{ $t('commons.selected') }}</span>
<span class="has-text-grey mr-1">{{ selectedAccounts.length }}&nbsp;{{ $t('commons.selected') }}</span>
<!-- deselect all -->
<button @click="clearSelected" class="clear-selection delete mr-4" :style="{visibility: selectedAccounts.length > 0 ? 'visible' : 'hidden'}" :title="$t('commons.clear_selection')"></button>
<!-- select all button -->
<button @click="selectAll" class="button mr-5 has-line-height p-1 is-ghost has-background-black-ter has-text-grey" :title="$t('commons.select_all')">
<button @click="selectAll" class="button mr-5 has-line-height p-1 is-ghost has-text-grey" :title="$t('commons.select_all')">
<span>{{ $t('commons.all') }}</span>
<font-awesome-icon class="ml-1" :icon="['fas', 'check-square']" />
</button>
<!-- sort asc/desc buttons -->
<button @click="sortAsc" class="button has-line-height p-1 is-ghost has-background-black-ter has-text-grey" :title="$t('commons.sort_ascending')">
<button @click="sortAsc" class="button has-line-height p-1 is-ghost has-text-grey" :title="$t('commons.sort_ascending')">
<font-awesome-icon :icon="['fas', 'sort-alpha-down']" />
</button>
<button @click="sortDesc" class="button has-line-height p-1 is-ghost has-background-black-ter has-text-grey" :title="$t('commons.sort_descending')">
<button @click="sortDesc" class="button has-line-height p-1 is-ghost has-text-grey" :title="$t('commons.sort_descending')">
<font-awesome-icon :icon="['fas', 'sort-alpha-up']" />
</button>
</div>
@ -101,13 +101,13 @@
<div v-else class="has-text-centered">
<div class="columns">
<div class="column" v-if="!showGroupSwitch">
<button :title="$t('groups.show_group_selector')" tabindex="1" class="button is-text is-like-text" @click.stop="toggleGroupSwitch">
<button :title="$t('groups.show_group_selector')" tabindex="1" class="button is-text is-like-text" :class="{'has-text-grey' : !$root.showDarkMode}" @click.stop="toggleGroupSwitch">
{{ activeGroupName }} ({{ filteredAccounts.length }})&nbsp;
<font-awesome-icon :icon="['fas', 'caret-down']" />
</button>
</div>
<div class="column" v-else>
<button :title="$t('groups.hide_group_selector')" tabindex="1" class="button is-text is-like-text" @click.stop="toggleGroupSwitch">
<button :title="$t('groups.hide_group_selector')" tabindex="1" class="button is-text is-like-text" :class="{'has-text-grey' : !$root.showDarkMode}" @click.stop="toggleGroupSwitch">
{{ $t('groups.select_accounts_to_show') }}
</button>
</div>
@ -131,12 +131,12 @@
}" > -->
<draggable v-model="filteredAccounts" @start="drag = true" @end="saveOrder" ghost-class="ghost" handle=".tfa-dots" animation="200" class="accounts">
<transition-group class="columns is-multiline" :class="{ 'is-centered': $root.appSettings.displayMode === 'grid' }" type="transition" :name="!drag ? 'flip-list' : null">
<div :class="[$root.appSettings.displayMode === 'grid' ? 'tfa-grid' : 'tfa-list']" class="column is-narrow has-text-white" v-for="account in filteredAccounts" :key="account.id">
<div :class="[$root.appSettings.displayMode === 'grid' ? 'tfa-grid' : 'tfa-list']" class="column is-narrow" v-for="account in filteredAccounts" :key="account.id">
<div class="tfa-container">
<transition name="slideCheckbox">
<div class="tfa-cell tfa-checkbox" v-if="editMode">
<div class="field">
<input class="is-checkradio is-small is-white" :id="'ckb_' + account.id" :value="account.id" type="checkbox" :name="'ckb_' + account.id" v-model="selectedAccounts">
<input class="is-checkradio is-small" :class="$root.showDarkMode ? 'is-white':'is-info'" :id="'ckb_' + account.id" :value="account.id" type="checkbox" :name="'ckb_' + account.id" v-model="selectedAccounts">
<label tabindex="0" :for="'ckb_' + account.id" v-on:keypress.space.prevent="selectAccount(account.id)"></label>
</div>
</div>
@ -151,10 +151,10 @@
<transition name="fadeInOut">
<div class="tfa-cell tfa-edit has-text-grey" v-if="editMode">
<!-- <div class="tags has-addons"> -->
<router-link :to="{ name: 'editAccount', params: { twofaccountId: account.id }}" class="tag is-dark is-rounded mr-1">
<router-link :to="{ name: 'editAccount', params: { twofaccountId: account.id }}" class="tag is-rounded mr-1" :class="$root.showDarkMode ? 'is-dark' : 'is-white'">
{{ $t('commons.edit') }}
</router-link>
<router-link :to="{ name: 'showQRcode', params: { twofaccountId: account.id }}" class="tag is-dark is-rounded" :title="$t('twofaccounts.show_qrcode')">
<router-link :to="{ name: 'showQRcode', params: { twofaccountId: account.id }}" class="tag is-rounded" :class="$root.showDarkMode ? 'is-dark' : 'is-white'" :title="$t('twofaccounts.show_qrcode')">
<font-awesome-icon :icon="['fas', 'qrcode']" />
</router-link>
<!-- </div> -->
@ -182,13 +182,13 @@
</p>
<!-- Manage button -->
<p class="control" v-if="!editMode">
<button class="button is-dark is-rounded" @click="setEditModeTo(true)">{{ $t('commons.manage') }}</button>
<button class="button is-rounded" :class="{'is-dark' : $root.showDarkMode}" @click="setEditModeTo(true)">{{ $t('commons.manage') }}</button>
</p>
<!-- move button -->
<p class="control" v-if="editMode">
<button
:disabled='selectedAccounts.length == 0' class="button is-outlined is-rounded"
:class="selectedAccounts.length > 0 ? 'is-link' : 'is-dark'"
:disabled='selectedAccounts.length == 0' class="button is-rounded"
:class="[{'is-outlined': $root.showDarkMode||selectedAccounts.length == 0}, selectedAccounts.length == 0 ? 'is-dark': 'is-link']"
@click="showGroupSelector = true"
:title="$t('groups.move_selected_to_group')" >
{{ $t('commons.move') }}
@ -197,8 +197,8 @@
<!-- delete button -->
<p class="control" v-if="editMode">
<button
:disabled='selectedAccounts.length == 0' class="button is-outlined is-rounded"
:class="selectedAccounts.length > 0 ? 'is-link' : 'is-dark'"
:disabled='selectedAccounts.length == 0' class="button is-rounded"
:class="[{'is-outlined': $root.showDarkMode||selectedAccounts.length == 0}, selectedAccounts.length == 0 ? 'is-dark': 'is-link']"
@click="destroyAccounts" >
{{ $t('commons.delete') }}
</button>
@ -206,8 +206,8 @@
<!-- export button -->
<p class="control" v-if="editMode">
<button
:disabled='selectedAccounts.length == 0' class="button is-outlined is-rounded"
:class="selectedAccounts.length > 0 ? 'is-link' : 'is-dark'"
:disabled='selectedAccounts.length == 0' class="button is-rounded"
:class="[{'is-outlined': $root.showDarkMode||selectedAccounts.length == 0}, selectedAccounts.length == 0 ? 'is-dark': 'is-link']"
@click="exportAccounts"
:title="$t('twofaccounts.export_selected_to_json')" >
{{ $t('commons.export') }}

View File

@ -5,13 +5,13 @@
<section class="section">
<div class="columns is-centered">
<div class="column is-three-quarters">
<div class="box has-text-centered has-background-black-ter is-shadowless">
<div class="modal-slot box has-text-centered is-shadowless">
<div v-if="errorText">
<p class="block is-size-5">{{ $t('twofaccounts.stream.live_scan_cant_start') }}</p>
<p class="has-text-light block">{{ $t('twofaccounts.stream.' + errorText + '.reason') }}</p>
<p class="block" :class="{'has-text-light': $root.showDarkMode}">{{ $t('twofaccounts.stream.' + errorText + '.reason') }}</p>
<p class="is-size-7">{{ $t('twofaccounts.stream.' + errorText + '.solution') }}</p>
</div>
<span v-else class="is-size-4 has-text-light">
<span v-else class="is-size-4" :class="$root.showDarkMode ? 'has-text-light':'has-text-grey-dark'">
<font-awesome-icon :icon="['fas', 'spinner']" size="2x" spin />
</span>
</div>

View File

@ -12,10 +12,10 @@
</router-link>
</div>
<div v-if="groups.length > 0">
<div v-for="group in groups" :key="group.id" class="group-item has-text-light is-size-5 is-size-6-mobile">
<div v-for="group in groups" :key="group.id" class="group-item is-size-5 is-size-6-mobile">
{{ group.name }}
<!-- delete icon -->
<button class="button tag is-dark is-pulled-right" @click="deleteGroup(group.id)" :title="$t('commons.delete')">
<button class="button tag is-pulled-right" :class="$root.showDarkMode ? 'is-dark' : 'is-white'" @click="deleteGroup(group.id)" :title="$t('commons.delete')">
{{ $t('commons.delete') }}
</button>
<!-- edit link -->
@ -37,7 +37,7 @@
<vue-footer :showButtons="true">
<!-- close button -->
<p class="control">
<router-link :to="{ name: 'accounts', params: { toRefresh: true } }" class="button is-dark is-rounded">{{ $t('commons.close') }}</router-link>
<router-link :to="{ name: 'accounts', params: { toRefresh: true } }" class="button is-rounded" :class="{'is-dark' : $root.showDarkMode}">{{ $t('commons.close') }}</router-link>
</p>
</vue-footer>
</responsive-width-wrapper>

View File

@ -23,7 +23,7 @@
</div>
<!-- alternative methods -->
<div class="column is-full">
<div class="block has-text-light">{{ $t('twofaccounts.forms.alternative_methods') }}</div>
<div class="block" :class="$root.showDarkMode ? 'has-text-light':'has-text-grey-dark'">{{ $t('twofaccounts.forms.alternative_methods') }}</div>
<!-- upload a qr code -->
<div class="block has-text-link" v-if="!$root.appSettings.useBasicQrcodeReader">
<label role="button" tabindex="0" class="button is-link is-outlined is-rounded" ref="qrcodeInputLabel" @keyup.enter="$refs.qrcodeInputLabel.click()">
@ -49,7 +49,7 @@
<vue-footer :showButtons="true" >
<!-- back button -->
<p class="control" v-if="accountCount > 0">
<router-link class="button is-dark is-rounded" :to="{ name: returnToView }" >
<router-link class="button is-rounded" :class="{'is-dark' : $root.showDarkMode}" :to="{ name: returnToView }" >
{{ $t('commons.back') }}
</router-link>
</p>

View File

@ -37,7 +37,7 @@
<vue-footer :showButtons="true">
<!-- Cancel button -->
<p class="control">
<button class="button is-dark is-rounded" @click.stop="exitSettings">
<button class="button is-rounded" :class="{'is-dark' : $root.showDarkMode}" @click.stop="exitSettings">
{{ $t('commons.close') }}
</button>
</p>

View File

@ -14,12 +14,12 @@
</a>
</div>
<div v-if="tokens.length > 0">
<div v-for="token in tokens" :key="token.id" class="group-item has-text-light is-size-5 is-size-6-mobile">
<div v-for="token in tokens" :key="token.id" class="group-item is-size-5 is-size-6-mobile">
<font-awesome-icon v-if="token.value" class="has-text-success" :icon="['fas', 'check']" /> {{ token.name }}
<!-- revoke link -->
<div class="tags is-pulled-right">
<button v-if="token.value" class="button tag" v-clipboard="() => token.value" v-clipboard:success="clipboardSuccessHandler">{{ $t('commons.copy') }}</button>
<button class="button tag is-dark " @click="revokeToken(token.id)" :title="$t('settings.revoke')">{{ $t('settings.revoke') }}</button>
<button v-if="token.value" class="button tag" :class="{'is-link': !$root.showDarkMode}" v-clipboard="() => token.value" v-clipboard:success="clipboardSuccessHandler">{{ $t('commons.copy') }}</button>
<button class="button tag" :class="$root.showDarkMode ? 'is-dark':'is-white'" @click="revokeToken(token.id)" :title="$t('settings.revoke')">{{ $t('settings.revoke') }}</button>
</div>
<!-- edit link -->
<!-- <router-link :to="{ name: 'settings.oauth.editPAT' }" class="has-text-grey pl-1" :title="$t('commons.edit')">
@ -47,7 +47,7 @@
<vue-footer :showButtons="true">
<!-- close button -->
<p class="control">
<router-link :to="{ name: 'accounts', params: { toRefresh: false } }" class="button is-dark is-rounded" tabindex="0">{{ $t('commons.close') }}</router-link>
<router-link :to="{ name: 'accounts', params: { toRefresh: false } }" class="button is-rounded" :class="{'is-dark' : $root.showDarkMode}" tabindex="0">{{ $t('commons.close') }}</router-link>
</p>
</vue-footer>
</form-wrapper>

View File

@ -20,6 +20,8 @@
</div>
<!-- display mode -->
<form-toggle v-on:displayMode="saveSetting('displayMode', $event)" :choices="layouts" :form="form" fieldName="displayMode" :label="$t('settings.forms.display_mode.label')" :help="$t('settings.forms.display_mode.help')" />
<!-- theme -->
<form-toggle v-on:theme="saveSetting('theme', $event)" :choices="themes" :form="form" fieldName="theme" :label="$t('settings.forms.theme.label')" :help="$t('settings.forms.theme.help')" />
<!-- show icon -->
<form-checkbox v-on:showAccountsIcons="saveSetting('showAccountsIcons', $event)" :form="form" fieldName="showAccountsIcons" :label="$t('settings.forms.show_accounts_icons.label')" :help="$t('settings.forms.show_accounts_icons.help')" />
<!-- Official icons -->
@ -56,7 +58,7 @@
<vue-footer :showButtons="true">
<!-- Cancel button -->
<p class="control">
<button class="button is-dark is-rounded" @click.stop="exitSettings">
<button class="button is-rounded" :class="{'is-dark' : $root.showDarkMode}" @click.stop="exitSettings">
{{ $t('commons.close') }}
</button>
</p>
@ -104,11 +106,17 @@
rememberActiveGroup: true,
getOfficialIcons: null,
checkForUpdate: null,
theme: 'dark',
}),
layouts: [
{ text: this.$t('settings.forms.grid'), value: 'grid', icon: 'th' },
{ text: this.$t('settings.forms.list'), value: 'list', icon: 'list' },
],
themes: [
{ text: this.$t('settings.forms.light'), value: 'light', icon: 'sun' },
{ text: this.$t('settings.forms.dark'), value: 'dark', icon: 'moon' },
{ text: this.$t('settings.forms.automatic'), value: 'system', icon: 'desktop' },
],
kickUserAfters: [
{ text: this.$t('settings.forms.never'), value: '0' },
{ text: this.$t('settings.forms.on_otp_copy'), value: '-1' },
@ -199,6 +207,10 @@
}
else {
this.$root.appSettings[response.data.key] = response.data.value
if(settingName === 'theme') {
this.setTheme(response.data.value)
}
}
})
},

View File

@ -15,10 +15,10 @@
</div>
<!-- credentials list -->
<div v-if="credentials.length > 0" class="field">
<div v-for="credential in credentials" :key="credential.id" class="group-item has-text-light is-size-5 is-size-6-mobile">
<div v-for="credential in credentials" :key="credential.id" class="group-item is-size-5 is-size-6-mobile">
{{ displayName(credential) }}
<!-- revoke link -->
<button class="button tag is-dark is-pulled-right" @click="revokeCredential(credential.id)" :title="$t('settings.revoke')">
<button class="button tag is-pulled-right" :class="$root.showDarkMode ? 'is-dark':'is-white'" @click="revokeCredential(credential.id)" :title="$t('settings.revoke')">
{{ $t('settings.revoke') }}
</button>
<!-- edit link -->
@ -49,7 +49,7 @@
<vue-footer :showButtons="true">
<!-- close button -->
<p class="control">
<router-link :to="{ name: 'accounts', params: { toRefresh: false } }" class="button is-dark is-rounded">{{ $t('commons.close') }}</router-link>
<router-link :to="{ name: 'accounts', params: { toRefresh: false } }" class="button is-rounded" :class="{'is-dark' : $root.showDarkMode}">{{ $t('commons.close') }}</router-link>
</p>
</vue-footer>
</form-wrapper>

View File

@ -66,7 +66,7 @@
<div class="field is-grouped">
<!-- i'm lucky button -->
<div class="control" v-if="$root.appSettings.getOfficialIcons">
<v-button @click="fetchLogo" :color="'is-dark'" :nativeType="'button'" :isDisabled="form.service.length < 1">
<v-button @click="fetchLogo" :color="$root.showDarkMode ? 'is-dark' : ''" :nativeType="'button'" :isDisabled="form.service.length < 1">
<span class="icon is-small">
<font-awesome-icon :icon="['fas', 'globe']" />
</span>
@ -75,7 +75,7 @@
</div>
<!-- upload button -->
<div class="control">
<div role="button" tabindex="0" class="file is-dark" @keyup.enter="$refs.iconInputLabel.click()">
<div role="button" tabindex="0" class="file" :class="$root.showDarkMode ? 'is-dark' : 'is-white'" @keyup.enter="$refs.iconInputLabel.click()">
<label class="file-label" ref="iconInputLabel">
<input aria-hidden="true" tabindex="-1" class="file-input" type="file" accept="image/*" v-on:change="uploadIcon" ref="iconInput">
<span class="file-cta">
@ -85,7 +85,7 @@
<span class="file-label">{{ $t('twofaccounts.forms.choose_image') }}</span>
</span>
</label>
<span class="tag is-black is-large" v-if="tempIcon">
<span class="tag is-large" :class="$root.showDarkMode ? 'is-dark' : 'is-white'" v-if="tempIcon">
<img class="icon-preview" :src="$root.appConfig.subdirectory + '/storage/icons/' + tempIcon" :alt="$t('twofaccounts.icon_to_illustrate_the_account')">
<button class="clear-selection delete is-small" @click.prevent="deleteIcon" :aria-label="$t('twofaccounts.remove_icon')"></button>
</span>
@ -149,7 +149,7 @@
<div class="block">
{{ $t('errors.data_of_qrcode_is_not_valid_URI') }}
</div>
<div class="block has-text-light mb-6" v-html="uri"></div>
<div class="block mb-6" :class="$root.showDarkMode ? 'has-text-light':'has-text-grey-dark'" v-html="uri"></div>
<!-- Copy to clipboard -->
<div class="block has-text-link">
<button class="button is-link is-outlined is-rounded" v-clipboard="() => uri" v-clipboard:success="clipboardSuccessHandler">

View File

@ -10,7 +10,7 @@
<div class="field is-grouped">
<!-- i'm lucky button -->
<div class="control" v-if="$root.appSettings.getOfficialIcons">
<v-button @click="fetchLogo" :color="'is-dark'" :nativeType="'button'" :isDisabled="form.service.length < 3">
<v-button @click="fetchLogo" :color="$root.showDarkMode ? 'is-dark' : ''" :nativeType="'button'" :isDisabled="form.service.length < 3">
<span class="icon is-small">
<font-awesome-icon :icon="['fas', 'globe']" />
</span>
@ -19,7 +19,7 @@
</div>
<!-- upload button -->
<div class="control">
<div role="button" tabindex="0" class="file is-dark" @keyup.enter="$refs.iconInputLabel.click()">
<div role="button" tabindex="0" class="file" :class="$root.showDarkMode ? 'is-dark' : 'is-white'" @keyup.enter="$refs.iconInputLabel.click()">
<label class="file-label" ref="iconInputLabel">
<input aria-hidden="true" tabindex="-1" class="file-input" type="file" accept="image/*" v-on:change="uploadIcon" ref="iconInput">
<span class="file-cta">
@ -29,7 +29,7 @@
<span class="file-label">{{ $t('twofaccounts.forms.choose_image') }}</span>
</span>
</label>
<span class="tag is-black is-large" v-if="tempIcon">
<span class="tag is-large" :class="$root.showDarkMode ? 'is-dark' : 'is-white'" v-if="tempIcon">
<img class="icon-preview" :src="$root.appConfig.subdirectory + '/storage/icons/' + tempIcon" :alt="$t('twofaccounts.icon_to_illustrate_the_account')">
<button class="clear-selection delete is-small" @click.prevent="deleteIcon" :aria-label="$t('twofaccounts.remove_icon')"></button>
</span>
@ -50,14 +50,14 @@
<input :id="this.inputId('text','secret')" class="input" type="text" v-model="form.secret" :disabled="secretIsLocked">
</p>
<p class="control" v-if="secretIsLocked">
<button type="button" class="button is-dark field-lock" @click.stop="secretIsLocked = false" :title="$t('twofaccounts.forms.unlock.title')">
<button type="button" class="button field-lock" :class="{'is-dark' : $root.showDarkMode}" @click.stop="secretIsLocked = false" :title="$t('twofaccounts.forms.unlock.title')">
<span class="icon">
<font-awesome-icon :icon="['fas', 'lock']" />
</span>
</button>
</p>
<p class="control" v-else>
<button type="button" class="button is-dark field-unlock" @click.stop="secretIsLocked = true" :title="$t('twofaccounts.forms.lock.title')">
<button type="button" class="button field-unlock" :class="{'is-dark' : $root.showDarkMode}" @click.stop="secretIsLocked = true" :title="$t('twofaccounts.forms.lock.title')">
<span class="icon has-text-danger">
<font-awesome-icon :icon="['fas', 'lock-open']" />
</span>
@ -89,14 +89,14 @@
<input class="input" type="text" placeholder="" v-model="form.counter" :disabled="counterIsLocked" />
</div>
<div class="control" v-if="counterIsLocked">
<button type="button" class="button is-dark field-lock" @click="counterIsLocked = false" :title="$t('twofaccounts.forms.unlock.title')">
<button type="button" class="button field-lock" :class="{'is-dark' : $root.showDarkMode}" @click="counterIsLocked = false" :title="$t('twofaccounts.forms.unlock.title')">
<span class="icon">
<font-awesome-icon :icon="['fas', 'lock']" />
</span>
</button>
</div>
<div class="control" v-else>
<button type="button" class="button is-dark field-unlock" @click="counterIsLocked = true" :title="$t('twofaccounts.forms.lock.title')">
<button type="button" class="button field-unlock" :class="{'is-dark' : $root.showDarkMode}" @click="counterIsLocked = true" :title="$t('twofaccounts.forms.lock.title')">
<span class="icon has-text-danger">
<font-awesome-icon :icon="['fas', 'lock-open']" />
</span>

View File

@ -39,43 +39,19 @@
<p class="help">{{ $t('twofaccounts.import.supported_formats_for_file_upload') }}</p>
</div>
<!-- Supported migration resources -->
<h5 class="title is-5 mb-3 has-text-grey-dark">{{ $t('twofaccounts.import.supported_migration_formats') }}</h5>
<h5 class="title is-6 mb-3 has-text-grey-dark">{{ $t('twofaccounts.import.supported_migration_formats') }}</h5>
<div class="field is-grouped is-grouped-multiline pt-0">
<div class="control">
<div v-for="(source, index) in supportedSources" :key="index" class="control">
<div class="tags has-addons">
<span class="tag is-dark">2FAuth</span>
<span class="tag is-black">JSON</span>
</div>
</div>
<div class="control">
<div class="tags has-addons">
<span class="tag is-dark">Google Auth</span>
<span class="tag is-black">{{ $t('twofaccounts.import.qr_code') }}</span>
</div>
</div>
<div class="control">
<div class="tags has-addons">
<span class="tag is-dark">Aegis Auth</span>
<span class="tag is-black">JSON</span>
</div>
</div>
<div class="control">
<div class="tags has-addons">
<span class="tag is-dark">Aegis Auth</span>
<span class="tag is-black">{{ $t('twofaccounts.import.plain_text') }}</span>
</div>
</div>
<div class="control">
<div class="tags has-addons">
<span class="tag is-dark">2FAS Auth</span>
<span class="tag is-black">JSON</span>
<span class="tag" :class="$root.showDarkMode ? 'is-dark' : 'is-white'">{{ source.app }}</span>
<span class="tag" :class="$root.showDarkMode ? 'is-black' : 'has-background-grey-lighter has-text-black'">{{ source.format }}</span>
</div>
</div>
</div>
<span class="is-size-7" v-html="$t('twofaccounts.import.do_not_set_password_or_encryption')"></span>
</div>
<div v-else>
<div v-for="(account, index) in exportedAccounts" :key="account.name" class="group-item has-text-light is-size-5 is-size-6-mobile">
<div v-for="(account, index) in exportedAccounts" :key="account.name" class="group-item is-size-5 is-size-6-mobile">
<div class="is-flex is-justify-content-space-between">
<!-- Account name -->
<div v-if="account.id > -2 && account.imported !== 0" class="is-flex-grow-1 has-ellipsis is-clickable" @click="previewAccount(index)" :title="$t('twofaccounts.import.generate_a_test_password')">
@ -86,7 +62,7 @@
<!-- buttons -->
<div v-if="account.imported === -1" class="tags is-flex-wrap-nowrap">
<!-- discard button -->
<button class="button tag is-dark has-text-grey-light" @click="discardAccount(index)" :title="$t('twofaccounts.import.discard_this_account')">
<button class="button tag" :class="{'is-dark has-text-grey-light' : $root.showDarkMode}" @click="discardAccount(index)" :title="$t('twofaccounts.import.discard_this_account')">
<font-awesome-icon :icon="['fas', 'trash']" />
</button>
<!-- import button -->
@ -148,7 +124,7 @@
</p>
<!-- close button -->
<p class="control">
<router-link :to="{ name: 'accounts', params: { toRefresh: true } }" class="button is-dark is-rounded" v-html="importableCount > 0 ? $t('commons.cancel') : $t('commons.close')"></router-link>
<router-link :to="{ name: 'accounts', params: { toRefresh: true } }" class="button is-rounded" :class="{'is-dark' : $root.showDarkMode}" v-html="importableCount > 0 ? $t('commons.cancel') : $t('commons.close')"></router-link>
</p>
</vue-footer>
</responsive-width-wrapper>
@ -184,6 +160,13 @@
}),
uploadForm: new Form(),
ShowTwofaccountInModal : false,
supportedSources: [
{app: '2FAuth', format: 'JSON'},
{app: 'Google Auth', format: this.$t('twofaccounts.import.qr_code')},
{app: 'Aegis Auth', format: 'JSON'},
{app: 'Aegis Auth', format: this.$t('twofaccounts.import.plain_text')},
{app: '2FAS auth', format: 'JSON'},
]
}
},

View File

@ -8,7 +8,7 @@
</div>
<div class="fullscreen-footer">
<!-- Close button -->
<button class="button is-dark is-rounded" @click.stop="$router.push({name: 'accounts', params: {initialEditMode: true}});">
<button class="button is-rounded" :class="{'is-dark' : $root.showDarkMode}" @click.stop="$router.push({name: 'accounts', params: {initialEditMode: true}});">
{{ $t('commons.close') }}
</button>
</div>

View File

@ -70,6 +70,13 @@
],
'grid' => 'Grid',
'list' => 'List',
'theme' => [
'label' => 'Theme',
'help' => 'Force a specific theme or apply the theme defined in your system/browser preferences'
],
'light' => 'Light',
'dark' => 'Dark',
'automatic' => 'Auto',
'show_accounts_icons' => [
'label' => 'Show icons',
'help' => 'Show icons accounts in the main view'

View File

@ -156,7 +156,7 @@
'possible_duplicate' => 'An account with the exact same data already exists',
'invalid_account' => '- invalid account -',
'invalid_service' => '- invalid service -',
'do_not_set_password_or_encryption' => 'Do NOT set a password or encryption On when you export data from a 2FA app.',
'do_not_set_password_or_encryption' => 'Do NOT enable Password protection or Encryption when you export data (from a 2FA app) you want to import into 2FAuth.',
],
];

View File

@ -2,6 +2,43 @@
@import '~bulma-checkradio';
@import '~bulma-switch';
:root,
footer,
.header,
.modal-background,
.modal-slot,
.options-header {
background-color: $white-ter;
}
:root[data-theme="dark"],
:root[data-theme="dark"] footer,
:root[data-theme="dark"] .header,
:root[data-theme="dark"] .modal-background,
:root[data-theme="dark"] .modal-background,
:root[data-theme="dark"] .options-header {
background-color: $black-ter;
}
@media (prefers-color-scheme: dark) {
:root[data-theme="system"],
:root[data-theme="system"] footer,
:root[data-theme="system"] .header,
:root[data-theme="system"] .modal-background,
:root[data-theme="system"] .modal-background,
:root[data-theme="system"] .options-header {
background-color: $black-ter;
}
}
:root[data-theme="dark"] .about-debug {
background-color: $black-bis;
}
@media (prefers-color-scheme: dark) {
:root[data-theme="system"] .about-debug {
background-color: $black-bis;
}
}
a:hover {
color: hsl(204, 86%, 53%);
}
@ -52,6 +89,40 @@ a:hover {
}
}
.toolbar button {
background-color: $white-ter;
}
:root[data-theme="dark"] .toolbar button {
background-color: $black-ter;
}
@media (prefers-color-scheme: dark) {
:root[data-theme="system"] .toolbar button {
background-color: $black-ter;
}
}
.toolbar button.delete {
background-color: $grey;
}
.toolbar button.delete::before, .toolbar button.delete::after {
background-color: $white;
}
.toolbar button.delete:hover, .toolbar button.delete:focus {
background-color: $grey;
}
:root[data-theme="dark"] .toolbar button.delete::before, :root[data-theme="dark"] .toolbar button.delete::after {
background-color: $white;
}
:root[data-theme="dark"] .toolbar button.delete:hover, :root[data-theme="dark"] .toolbar button.delete:focus {
background-color: rgba(10, 10, 10, 0.3);
}
@media (prefers-color-scheme: dark) {
:root[data-theme="dark"] .toolbar button.delete::before, :root[data-theme="dark"] .toolbar button.delete::after {
background-color: $white;
}
}
.modal-otp {
z-index: 2000;
}
@ -66,10 +137,21 @@ a:hover {
outline: none;
}
// has-text-weight-semibold / $weight-semibold
.group-item {
border-bottom: 1px solid hsl(0, 0%, 21%);
border-bottom: 1px solid $grey-lighter;
padding: 0.75rem;
}
:root[data-theme="dark"] .group-item {
border-color: $grey-darker;
color: $light;
}
@media (prefers-color-scheme: dark) {
:root[data-theme="system"] .group-item {
border-color: $grey-darker;
color: $light;
}
}
.group-item:first-of-type {
margin-top: 2.5rem;
@ -122,32 +204,43 @@ a:hover {
padding-top: 0.25rem;
}
.modal-background {
background-color: hsl(0, 0%, 14%) !important;
}
.pull-down-header {
background-color: hsl(0, 0%, 21%) !important;
background-color: $grey-darker !important;
}
.tfa-grid {
border-radius: 6px;
text-align: center;
background-color: hsl(0, 0%, 10%);
/*black-bis from Bulma*/
background-color: $white;
padding: 0.75rem 3rem;
margin: 0.5rem;
}
:root[data-theme="dark"] .tfa-grid {
background-color: hsl(0, 0%, 10%);
}
@media (prefers-color-scheme: dark) {
:root[data-theme="system"] .tfa-grid {
background-color: hsl(0, 0%, 10%);
}
}
.tfa-list {
text-align: inherit;
border-bottom: 1px solid hsl(0, 0%, 21%);
background-color: hsl(0, 0%, 14%);
/*black-ter from Bulma*/
border-bottom: 1px solid $grey-lighter;
margin: 0 1%;
padding: 0.5rem 0.5rem 0.5rem 0.5rem;
width: 31.3% !important;
}
:root[data-theme="dark"] .tfa-list {
background-color: $black-ter;
border-bottom: 1px solid $grey-darker;
}
@media (prefers-color-scheme: dark) {
:root[data-theme="system"] .tfa-list {
background-color: $black-ter;
border-bottom: 1px solid $grey-darker;
}
}
@media screen and (max-width: 1217px) {
.tfa-list {
@ -159,11 +252,7 @@ a:hover {
.tfa-list {
border-radius: unset;
text-align: inherit;
border-bottom: 1px solid hsl(0, 0%, 21%);
background-color: hsl(0, 0%, 14%);
/*black-ter from Bulma*/
margin: 0;
padding: 0.5rem 0.5rem 0.5rem 0.5rem;
max-width: none;
width: auto !important;
}
@ -255,6 +344,14 @@ a:hover {
// max-width: 300px;
cursor: pointer;
}
:root[data-theme="dark"] .tfa-text {
color: $white;
}
@media (prefers-color-scheme: dark) {
:root[data-theme="system"] .tfa-text {
color: $white;
}
}
.tfa-container img {
height: 0.75em;
@ -339,16 +436,22 @@ a:hover {
}
figure.no-icon {
border: 1px dashed hsl(0, 0%, 7%);
border: 1px dashed $black-bis;
}
.file-input {
cursor: pointer;
}
.add-icon-button,
.add-icon-button:hover {
color: hsl(0, 0%, 7%);
:root[data-theme="dark"] .add-icon-button,
:root[data-theme="dark"] .add-icon-button:hover {
color: $black-bis;
}
@media (prefers-color-scheme: dark) {
:root[data-theme="system"] .add-icon-button,
:root[data-theme="system"] .add-icon-button:hover {
color: $black-bis;
}
}
.dots {
@ -367,8 +470,15 @@ figure.no-icon {
}
.dots li[data-is-active]~li {
background: hsl(0, 0%, 7%);
/* grey */
background: $grey;
}
:root[data-theme="dark"] .dots li[data-is-active]~li {
background: $black-bis;
}
@media (prefers-color-scheme: dark) {
:root[data-theme="system"] .dots li[data-is-active]~li {
background: $black-bis;
}
}
.dots li:nth-child(-n+9) {
@ -385,19 +495,35 @@ figure.no-icon {
// display:none;
// }
.input,
.select select,
.textarea {
background-color: hsl(0, 0%, 21%);
:root[data-theme="dark"] .input,
:root[data-theme="dark"] .select select,
:root[data-theme="dark"] .textarea {
background-color: $grey-darker;
border-color: hsl(0, 0%, 29%);
color: hsl(0, 0%, 100%);
}
@media (prefers-color-scheme: dark) {
:root[data-theme="system"] .input,
:root[data-theme="system"] .select select,
:root[data-theme="system"] .textarea {
background-color: $grey-darker;
border-color: hsl(0, 0%, 29%);
color: hsl(0, 0%, 100%);
}
}
.select select::placeholder,
.textarea::placeholder,
.input::placeholder {
:root[data-theme="dark"] .select select::placeholder,
:root[data-theme="dark"] .textarea::placeholder,
:root[data-theme="dark"] .input::placeholder {
color: hsl(0, 0%, 48%);
}
@media (prefers-color-scheme: dark) {
:root[data-theme="system"] .select select::placeholder,
:root[data-theme="system"] .textarea::placeholder,
:root[data-theme="system"] .input::placeholder {
color: hsl(0, 0%, 48%);
}
}
.select select[disabled],
[disabled].textarea,
@ -406,20 +532,56 @@ fieldset[disabled] .select select,
.select fieldset[disabled] select,
fieldset[disabled] .textarea,
fieldset[disabled] .input {
border-color: $grey-lighter;
background-color: $white;
opacity: 0.5;
}
:root[data-theme="dark"] .select select[disabled],
:root[data-theme="dark"] [disabled].textarea,
:root[data-theme="dark"] [disabled].input,
:root[data-theme="dark"] fieldset[disabled] .select select,
:root[data-theme="dark"] .select fieldset[disabled] select,
:root[data-theme="dark"] fieldset[disabled] .textarea,
:root[data-theme="dark"] fieldset[disabled] .input {
background-color: hsl(0, 0%, 10%);
border-color: hsl(0, 0%, 21%);
border-color: $grey-darker;
box-shadow: none;
color: hsl(0, 0%, 21%);
color: $grey-darker;
}
@media (prefers-color-scheme: dark) {
:root[data-theme="system"] .select select[disabled],
:root[data-theme="system"] [disabled].textarea,
:root[data-theme="system"] [disabled].input,
:root[data-theme="system"] fieldset[disabled] .select select,
:root[data-theme="system"] .select fieldset[disabled] select,
:root[data-theme="system"] fieldset[disabled] .textarea,
:root[data-theme="system"] fieldset[disabled] .input {
background-color: hsl(0, 0%, 10%);
border-color: $grey-darker;
box-shadow: none;
color: $grey-darker;
}
}
.select select[disabled]::placeholder,
[disabled].textarea::placeholder,
[disabled].input::placeholder,
fieldset[disabled] .select select::placeholder,
.select fieldset[disabled] select::placeholder,
fieldset[disabled] .textarea::placeholder,
fieldset[disabled] .input::placeholder {
color: hsl(0, 0%, 21%);
:root[data-theme="dark"] .select select[disabled]::placeholder,
:root[data-theme="dark"] [disabled].textarea::placeholder,
:root[data-theme="dark"] [disabled].input::placeholder,
:root[data-theme="dark"] fieldset[disabled] .select select::placeholder,
:root[data-theme="dark"] .select fieldset[disabled] select::placeholder,
:root[data-theme="dark"] fieldset[disabled] .textarea::placeholder,
:root[data-theme="dark"] fieldset[disabled] .input::placeholder {
color: $grey-darker;
}
@media (prefers-color-scheme: dark) {
:root[data-theme="system"] .select select[disabled]::placeholder,
:root[data-theme="system"] [disabled].textarea::placeholder,
:root[data-theme="system"] [disabled].input::placeholder,
:root[data-theme="system"] fieldset[disabled] .select select::placeholder,
:root[data-theme="system"] .select fieldset[disabled] select::placeholder,
:root[data-theme="system"] fieldset[disabled] .textarea::placeholder,
:root[data-theme="system"] fieldset[disabled] .input::placeholder {
color: $grey-darker;
}
}
.button.has-line-height {
@ -693,6 +855,15 @@ a.tag.is-dark.is-rounded:focus:not(:focus-visible) {
outline: none;
}
button.button.tag.is-white,
.tfa-cell .tag.is-white {
border-color: $input-border-color;
}
.tfa-cell .tag.is-white {
border-style: solid;
border-width: $control-border-width;
}
.tabs a:focus-visible {
outline-offset: -4px;
}
@ -726,11 +897,36 @@ a.tag.is-dark.is-rounded:focus:not(:focus-visible) {
box-shadow: $input-focus-box-shadow-size $input-focus-box-shadow-color;
}
.is-checkradio[type="checkbox"]+label::before,
.is-checkradio[type="checkbox"]+label::before {
border-color: $grey-light;
border-width: 1px !important;
background-color: $white;
}
.tfa-checkbox .is-checkradio[type="checkbox"]+label::before {
border-color: $grey;
}
:root[data-theme="dark"] .is-checkradio[type="checkbox"]+label::before {
border-color: $grey;
background: none;
}
@media (prefers-color-scheme: dark) {
:root[data-theme="system"] .is-checkradio[type="checkbox"]+label::before {
border-color: $grey;
background: none;
}
}
.is-checkradio[type="checkbox"]:checked + label::before {
border-color: $grey;
}
:root[data-theme="dark"] .is-checkradio[type="checkbox"]:checked + label::before {
border-color: $grey-lighter;
}
@media (prefers-color-scheme: dark) {
:root[data-theme="system"] .is-checkradio[type="checkbox"]:checked + label::before {
border-color: $grey-lighter;
}
}
.label {
color: hsl(0, 0%, 48%);
@ -747,6 +943,16 @@ a.tag.is-dark.is-rounded:focus:not(:focus-visible) {
.is-underscored.is-dot {
border: none;
border-radius: 50%;
background-color: $success;
}
:root[data-theme="dark"] .is-underscored.is-dot {
background-color: $success-dark;
}
@media (prefers-color-scheme: dark) {
:root[data-theme="system"] .is-underscored.is-dot {
background-color: $success-dark;
}
}
.is-mid-width-field input {
@ -770,13 +976,24 @@ a.tag.is-dark.is-rounded:focus:not(:focus-visible) {
text-transform: uppercase !important;
}
.control.has-icons-left .icon,
.control.has-icons-right .icon {
:root[data-theme="dark"] .control.has-icons-left .icon,
:root[data-theme="dark"] .control.has-icons-right .icon {
color: inherit;
}
@media (prefers-color-scheme: dark) {
:root[data-theme="system"] .control.has-icons-left .icon,
:root[data-theme="system"] .control.has-icons-right .icon {
color: inherit;
}
}
.is-search {
border-color: hsl(0, 0%, 21%);
:root[data-theme="dark"].is-search {
border-color: $grey-darker;
}
@media (prefers-color-scheme: dark) {
:root[data-theme="system"].is-search {
border-color: $grey-darker;
}
}
.modal .field.is-grouped:last-child {
@ -904,12 +1121,20 @@ footer .field.is-grouped {
position: absolute;
left: 0;
width: 100%;
opacity: 0.05;
opacity: 0.1;
height: 256px;
background-repeat: no-repeat;
background-position: top left 50%;
background-image: url('data:image/png;base64,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');
}
:root[data-theme="dark"] quick-uploader-button::before {
opacity: 0.05;
}
@media (prefers-color-scheme: dark) {
:root[data-theme="system"] quick-uploader-button::before {
opacity: 0.05;
}
}
.error-404,
.error-generic,
@ -953,6 +1178,19 @@ footer .field.is-grouped {
margin: 0 5px;
}
.about-logo {
height: 32px;
filter: invert(1);
}
:root[data-theme="dark"] .about-logo {
filter: none;
}
@media (prefers-color-scheme: dark) {
:root[data-theme="system"] .about-logo {
filter: none;
}
}
.fadeInOut-enter-active {
animation: fadeIn 500ms
}

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html class="has-background-black-ter" lang="{!! $lang !!}">
<html data-theme='{{ $theme }}' lang="{!! $lang !!}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
@ -19,7 +19,7 @@
<link href="{!! $subdirectory !!}{{ mix('css/app.css') }}" rel="stylesheet">
</head>
<body class="has-text-lighter">
<body>
<div id="app">
<app></app>
</div>