Merge pull request #4489 from thelounge/maxleiter/tabbedSettings
Refactor settings to their own tabs and routes
This commit is contained in:
commit
5f7acbf994
171
client/components/Settings/Account.vue
Normal file
171
client/components/Settings/Account.vue
Normal file
@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
v-if="
|
||||
!$store.state.serverConfiguration.public &&
|
||||
!$store.state.serverConfiguration.ldapEnabled
|
||||
"
|
||||
id="change-password"
|
||||
role="group"
|
||||
aria-labelledby="label-change-password"
|
||||
>
|
||||
<h2 id="label-change-password">Change password</h2>
|
||||
<div class="password-container">
|
||||
<label for="current-password" class="sr-only"> Enter current password </label>
|
||||
<RevealPassword v-slot:default="slotProps">
|
||||
<input
|
||||
id="current-password"
|
||||
autocomplete="current-password"
|
||||
:type="slotProps.isVisible ? 'text' : 'password'"
|
||||
name="old_password"
|
||||
class="input"
|
||||
placeholder="Enter current password"
|
||||
/>
|
||||
</RevealPassword>
|
||||
</div>
|
||||
<div class="password-container">
|
||||
<label for="new-password" class="sr-only"> Enter desired new password </label>
|
||||
<RevealPassword v-slot:default="slotProps">
|
||||
<input
|
||||
id="new-password"
|
||||
:type="slotProps.isVisible ? 'text' : 'password'"
|
||||
name="new_password"
|
||||
autocomplete="new-password"
|
||||
class="input"
|
||||
placeholder="Enter desired new password"
|
||||
/>
|
||||
</RevealPassword>
|
||||
</div>
|
||||
<div class="password-container">
|
||||
<label for="new-password-verify" class="sr-only"> Repeat new password </label>
|
||||
<RevealPassword v-slot:default="slotProps">
|
||||
<input
|
||||
id="new-password-verify"
|
||||
:type="slotProps.isVisible ? 'text' : 'password'"
|
||||
name="verify_password"
|
||||
autocomplete="new-password"
|
||||
class="input"
|
||||
placeholder="Repeat new password"
|
||||
/>
|
||||
</RevealPassword>
|
||||
</div>
|
||||
<div
|
||||
v-if="passwordChangeStatus && passwordChangeStatus.success"
|
||||
class="feedback success"
|
||||
>
|
||||
Successfully updated your password
|
||||
</div>
|
||||
<div
|
||||
v-else-if="passwordChangeStatus && passwordChangeStatus.error"
|
||||
class="feedback error"
|
||||
>
|
||||
{{ passwordErrors[passwordChangeStatus.error] }}
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit" class="btn" @click.prevent="changePassword">
|
||||
Change password
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!$store.state.serverConfiguration.public" class="session-list" role="group">
|
||||
<h2>Sessions</h2>
|
||||
|
||||
<h3>Current session</h3>
|
||||
<Session v-if="currentSession" :session="currentSession" />
|
||||
|
||||
<template v-if="activeSessions.length > 0">
|
||||
<h3>Active sessions</h3>
|
||||
<Session
|
||||
v-for="session in activeSessions"
|
||||
:key="session.token"
|
||||
:session="session"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<h3>Other sessions</h3>
|
||||
<p v-if="$store.state.sessions.length === 0">Loading…</p>
|
||||
<p v-else-if="otherSessions.length === 0">
|
||||
<em>You are not currently logged in to any other device.</em>
|
||||
</p>
|
||||
<Session
|
||||
v-for="session in otherSessions"
|
||||
v-else
|
||||
:key="session.token"
|
||||
:session="session"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import socket from "../../js/socket";
|
||||
import RevealPassword from "../RevealPassword.vue";
|
||||
import Session from "../Session.vue";
|
||||
|
||||
export default {
|
||||
name: "UserSettings",
|
||||
components: {
|
||||
RevealPassword,
|
||||
Session,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
passwordChangeStatus: null,
|
||||
passwordErrors: {
|
||||
missing_fields: "Please enter a new password",
|
||||
password_mismatch: "Both new password fields must match",
|
||||
password_incorrect:
|
||||
"The current password field does not match your account password",
|
||||
update_failed: "Failed to update your password",
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
currentSession() {
|
||||
return this.$store.state.sessions.find((item) => item.current);
|
||||
},
|
||||
activeSessions() {
|
||||
return this.$store.state.sessions.filter((item) => !item.current && item.active > 0);
|
||||
},
|
||||
otherSessions() {
|
||||
return this.$store.state.sessions.filter((item) => !item.current && !item.active);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
socket.emit("sessions:get");
|
||||
},
|
||||
methods: {
|
||||
changePassword() {
|
||||
const allFields = new FormData(this.$refs.settingsForm);
|
||||
const data = {
|
||||
old_password: allFields.get("old_password"),
|
||||
new_password: allFields.get("new_password"),
|
||||
verify_password: allFields.get("verify_password"),
|
||||
};
|
||||
|
||||
if (!data.old_password || !data.new_password || !data.verify_password) {
|
||||
this.passwordChangeStatus = {
|
||||
success: false,
|
||||
error: "missing_fields",
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.new_password !== data.verify_password) {
|
||||
this.passwordChangeStatus = {
|
||||
success: false,
|
||||
error: "password_mismatch",
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
socket.once("change-password", (response) => {
|
||||
this.passwordChangeStatus = response;
|
||||
});
|
||||
|
||||
socket.emit("change-password", data);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
169
client/components/Settings/Appearance.vue
Normal file
169
client/components/Settings/Appearance.vue
Normal file
@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2>Messages</h2>
|
||||
<div>
|
||||
<label class="opt">
|
||||
<input :checked="$store.state.settings.motd" type="checkbox" name="motd" />
|
||||
Show <abbr title="Message Of The Day">MOTD</abbr>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label class="opt">
|
||||
<input
|
||||
:checked="$store.state.settings.showSeconds"
|
||||
type="checkbox"
|
||||
name="showSeconds"
|
||||
/>
|
||||
Include seconds in timestamp
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label class="opt">
|
||||
<input
|
||||
:checked="$store.state.settings.use12hClock"
|
||||
type="checkbox"
|
||||
name="use12hClock"
|
||||
/>
|
||||
Use 12-hour timestamps
|
||||
</label>
|
||||
</div>
|
||||
<template v-if="$store.state.serverConfiguration.prefetch">
|
||||
<h2>Link previews</h2>
|
||||
<div>
|
||||
<label class="opt">
|
||||
<input :checked="$store.state.settings.media" type="checkbox" name="media" />
|
||||
Auto-expand media
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label class="opt">
|
||||
<input :checked="$store.state.settings.links" type="checkbox" name="links" />
|
||||
Auto-expand websites
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
<h2 id="label-status-messages">
|
||||
Status messages
|
||||
<span
|
||||
class="tooltipped tooltipped-n tooltipped-no-delay"
|
||||
aria-label="Joins, parts, quits, kicks, nick changes, and mode changes"
|
||||
>
|
||||
<button class="extra-help" />
|
||||
</span>
|
||||
</h2>
|
||||
<div role="group" aria-labelledby="label-status-messages">
|
||||
<label class="opt">
|
||||
<input
|
||||
:checked="$store.state.settings.statusMessages === 'shown'"
|
||||
type="radio"
|
||||
name="statusMessages"
|
||||
value="shown"
|
||||
/>
|
||||
Show all status messages individually
|
||||
</label>
|
||||
<label class="opt">
|
||||
<input
|
||||
:checked="$store.state.settings.statusMessages === 'condensed'"
|
||||
type="radio"
|
||||
name="statusMessages"
|
||||
value="condensed"
|
||||
/>
|
||||
Condense status messages together
|
||||
</label>
|
||||
<label class="opt">
|
||||
<input
|
||||
:checked="$store.state.settings.statusMessages === 'hidden'"
|
||||
type="radio"
|
||||
name="statusMessages"
|
||||
value="hidden"
|
||||
/>
|
||||
Hide all status messages
|
||||
</label>
|
||||
</div>
|
||||
<h2>Visual Aids</h2>
|
||||
<div>
|
||||
<label class="opt">
|
||||
<input
|
||||
:checked="$store.state.settings.coloredNicks"
|
||||
type="checkbox"
|
||||
name="coloredNicks"
|
||||
/>
|
||||
Enable colored nicknames
|
||||
</label>
|
||||
<label class="opt">
|
||||
<input
|
||||
:checked="$store.state.settings.autocomplete"
|
||||
type="checkbox"
|
||||
name="autocomplete"
|
||||
/>
|
||||
Enable autocomplete
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label class="opt">
|
||||
<label for="nickPostfix" class="opt">
|
||||
Nick autocomplete postfix
|
||||
<span
|
||||
class="tooltipped tooltipped-n tooltipped-no-delay"
|
||||
aria-label="Nick autocomplete postfix (for example a comma)"
|
||||
>
|
||||
<button class="extra-help" />
|
||||
</span>
|
||||
</label>
|
||||
<input
|
||||
id="nickPostfix"
|
||||
:value="$store.state.settings.nickPostfix"
|
||||
type="text"
|
||||
name="nickPostfix"
|
||||
class="input"
|
||||
placeholder="Nick autocomplete postfix (e.g. ', ')"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<h2>Theme</h2>
|
||||
<div>
|
||||
<label for="theme-select" class="sr-only">Theme</label>
|
||||
<select
|
||||
id="theme-select"
|
||||
:value="$store.state.settings.theme"
|
||||
name="theme"
|
||||
class="input"
|
||||
>
|
||||
<option
|
||||
v-for="theme in $store.state.serverConfiguration.themes"
|
||||
:key="theme.name"
|
||||
:value="theme.name"
|
||||
>
|
||||
{{ theme.displayName }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>Custom Stylesheet</h2>
|
||||
<label for="user-specified-css-input" class="sr-only">
|
||||
Custom stylesheet. You can override any style with CSS here.
|
||||
</label>
|
||||
<textarea
|
||||
id="user-specified-css-input"
|
||||
:value="$store.state.settings.userStyles"
|
||||
class="input"
|
||||
name="userStyles"
|
||||
placeholder="/* You can override any style with CSS here */"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
textarea#user-specified-css-input {
|
||||
height: 100px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "AppearanceSettings",
|
||||
};
|
||||
</script>
|
143
client/components/Settings/General.vue
Normal file
143
client/components/Settings/General.vue
Normal file
@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="canRegisterProtocol || hasInstallPromptEvent">
|
||||
<h2>Native app</h2>
|
||||
<button
|
||||
v-if="hasInstallPromptEvent"
|
||||
type="button"
|
||||
class="btn"
|
||||
@click.prevent="nativeInstallPrompt"
|
||||
>
|
||||
Add The Lounge to Home screen
|
||||
</button>
|
||||
<button
|
||||
v-if="canRegisterProtocol"
|
||||
type="button"
|
||||
class="btn"
|
||||
@click.prevent="registerProtocol"
|
||||
>
|
||||
Open irc:// URLs with The Lounge
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="$store.state.serverConfiguration.fileUpload">
|
||||
<h2>File uploads</h2>
|
||||
<div>
|
||||
<label class="opt">
|
||||
<input
|
||||
:checked="$store.state.settings.uploadCanvas"
|
||||
type="checkbox"
|
||||
name="uploadCanvas"
|
||||
/>
|
||||
Attempt to remove metadata from images before uploading
|
||||
<span
|
||||
class="tooltipped tooltipped-n tooltipped-no-delay"
|
||||
aria-label="This option renders the image into a canvas element to remove metadata from the image.
|
||||
This may break orientation if your browser does not support that."
|
||||
>
|
||||
<button class="extra-help" />
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!$store.state.serverConfiguration.public">
|
||||
<h2>Settings synchronisation</h2>
|
||||
<label class="opt">
|
||||
<input
|
||||
:checked="$store.state.settings.syncSettings"
|
||||
type="checkbox"
|
||||
name="syncSettings"
|
||||
/>
|
||||
Synchronize settings with other clients
|
||||
</label>
|
||||
<template v-if="!$store.state.settings.syncSettings">
|
||||
<div v-if="$store.state.serverHasSettings" class="settings-sync-panel">
|
||||
<p>
|
||||
<strong>Warning:</strong> Checking this box will override the settings of
|
||||
this client with those stored on the server.
|
||||
</p>
|
||||
<p>
|
||||
Use the button below to enable synchronization, and override any settings
|
||||
already synced to the server.
|
||||
</p>
|
||||
<button type="button" class="btn btn-small" @click="onForceSyncClick">
|
||||
Sync settings and enable
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="settings-sync-panel">
|
||||
<p>
|
||||
<strong>Warning:</strong> No settings have been synced before. Enabling this
|
||||
will sync all settings of this client as the base for other clients.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="!$store.state.serverConfiguration.public">
|
||||
<h2>Automatic away message</h2>
|
||||
|
||||
<label class="opt">
|
||||
<label for="awayMessage" class="sr-only">Automatic away message</label>
|
||||
<input
|
||||
id="awayMessage"
|
||||
:value="$store.state.settings.awayMessage"
|
||||
type="text"
|
||||
name="awayMessage"
|
||||
class="input"
|
||||
placeholder="Away message if The Lounge is not open"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style></style>
|
||||
|
||||
<script>
|
||||
let installPromptEvent = null;
|
||||
|
||||
window.addEventListener("beforeinstallprompt", (e) => {
|
||||
e.preventDefault();
|
||||
installPromptEvent = e;
|
||||
});
|
||||
|
||||
export default {
|
||||
name: "GeneralSettings",
|
||||
data() {
|
||||
return {
|
||||
canRegisterProtocol: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
hasInstallPromptEvent() {
|
||||
// TODO: This doesn't hide the button after clicking
|
||||
return installPromptEvent !== null;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
// Enable protocol handler registration if supported,
|
||||
// and the network configuration is not locked
|
||||
this.canRegisterProtocol =
|
||||
window.navigator.registerProtocolHandler &&
|
||||
!this.$store.state.serverConfiguration.lockNetwork;
|
||||
},
|
||||
methods: {
|
||||
nativeInstallPrompt() {
|
||||
installPromptEvent.prompt();
|
||||
installPromptEvent = null;
|
||||
},
|
||||
onForceSyncClick() {
|
||||
this.$store.dispatch("settings/syncAll", true);
|
||||
this.$store.dispatch("settings/update", {
|
||||
name: "syncSettings",
|
||||
value: true,
|
||||
sync: true,
|
||||
});
|
||||
},
|
||||
registerProtocol() {
|
||||
const uri = document.location.origin + document.location.pathname + "?uri=%s";
|
||||
|
||||
window.navigator.registerProtocolHandler("irc", uri, "The Lounge");
|
||||
window.navigator.registerProtocolHandler("ircs", uri, "The Lounge");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
102
client/components/Settings/Navigation.vue
Normal file
102
client/components/Settings/Navigation.vue
Normal file
@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<!-- 220px is the width of the sidebar, and we add 100px to allow for the text -->
|
||||
<aside class="settings-menu">
|
||||
<h2>Settings</h2>
|
||||
<ul role="navigation" aria-label="Settings tabs">
|
||||
<SettingTabItem name="General" class-name="general" to="" />
|
||||
<SettingTabItem name="Appearance" class-name="appearance" to="appearance" />
|
||||
<SettingTabItem name="Notifications" class-name="notifications" to="notifications" />
|
||||
<SettingTabItem name="Account" class-name="account" to="account" />
|
||||
</ul>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.settings-menu {
|
||||
position: fixed;
|
||||
/* top: Header + (padding bottom of h2 - border) */
|
||||
top: calc(45px + 5px);
|
||||
/* Mid page minus width of container and 30 pixels for padding */
|
||||
margin-left: calc(50% - 480px - 30px);
|
||||
}
|
||||
|
||||
/** The calculation is mobile + 2/3 of container width. Fairly arbitrary. */
|
||||
@media screen and (max-width: calc(768px + 320px)) {
|
||||
.settings-menu {
|
||||
position: static;
|
||||
width: min(480px, 100%);
|
||||
align-self: center;
|
||||
margin: 0 auto;
|
||||
padding: 0 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-menu ul {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.settings-menu li {
|
||||
font-size: 18px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.settings-menu button {
|
||||
color: var(--body-color-muted);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.settings-menu li:not(:last-of-type) button {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.settings-menu button::before {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
display: inline-block;
|
||||
content: "";
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.settings-menu .appearance::before {
|
||||
content: "\f108"; /* http://fontawesome.io/icon/desktop/ */
|
||||
}
|
||||
|
||||
.settings-menu .account::before {
|
||||
content: "\f007"; /* http://fontawesome.io/icon/user/ */
|
||||
}
|
||||
|
||||
.settings-menu .messages::before {
|
||||
content: "\f0e0"; /* http://fontawesome.io/icon/envelope/ */
|
||||
}
|
||||
|
||||
.settings-menu .notifications::before {
|
||||
content: "\f0f3"; /* http://fontawesome.io/icon/bell/ */
|
||||
}
|
||||
|
||||
.settings-menu .general::before {
|
||||
content: "\f013"; /* http://fontawesome.io/icon/cog/ */
|
||||
}
|
||||
|
||||
.settings-menu button:hover,
|
||||
.settings-menu button.active {
|
||||
color: var(--body-color);
|
||||
}
|
||||
|
||||
.settings-menu button.active {
|
||||
cursor: default;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import SettingTabItem from "./SettingTabItem.vue";
|
||||
|
||||
export default {
|
||||
name: "SettingsTabs",
|
||||
components: {
|
||||
SettingTabItem,
|
||||
},
|
||||
};
|
||||
</script>
|
187
client/components/Settings/Notifications.vue
Normal file
187
client/components/Settings/Notifications.vue
Normal file
@ -0,0 +1,187 @@
|
||||
<template>
|
||||
<div>
|
||||
<template v-if="!$store.state.serverConfiguration.public">
|
||||
<h2>Push Notifications</h2>
|
||||
<div>
|
||||
<button
|
||||
id="pushNotifications"
|
||||
type="button"
|
||||
class="btn"
|
||||
:disabled="
|
||||
$store.state.pushNotificationState !== 'supported' &&
|
||||
$store.state.pushNotificationState !== 'subscribed'
|
||||
"
|
||||
@click="onPushButtonClick"
|
||||
>
|
||||
<template v-if="$store.state.pushNotificationState === 'subscribed'">
|
||||
Unsubscribe from push notifications
|
||||
</template>
|
||||
<template v-else-if="$store.state.pushNotificationState === 'loading'">
|
||||
Loading…
|
||||
</template>
|
||||
<template v-else> Subscribe to push notifications </template>
|
||||
</button>
|
||||
<div v-if="$store.state.pushNotificationState === 'nohttps'" class="error">
|
||||
<strong>Warning</strong>: Push notifications are only supported over HTTPS
|
||||
connections.
|
||||
</div>
|
||||
<div v-if="$store.state.pushNotificationState === 'unsupported'" class="error">
|
||||
<strong>Warning</strong>:
|
||||
<span>Push notifications are not supported by your browser.</span>
|
||||
|
||||
<div v-if="isIOS" class="apple-push-unsupported">
|
||||
Safari does
|
||||
<a
|
||||
href="https://bugs.webkit.org/show_bug.cgi?id=182566"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>not support the web push notification specification</a
|
||||
>, and because all browsers on iOS use Safari under the hood, The Lounge is
|
||||
unable to provide push notifications on iOS devices.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<h2>Browser Notifications</h2>
|
||||
<div>
|
||||
<label class="opt">
|
||||
<input
|
||||
id="desktopNotifications"
|
||||
:checked="$store.state.settings.desktopNotifications"
|
||||
:disabled="$store.state.desktopNotificationState === 'nohttps'"
|
||||
type="checkbox"
|
||||
name="desktopNotifications"
|
||||
/>
|
||||
Enable browser notifications<br />
|
||||
<div v-if="$store.state.desktopNotificationState === 'unsupported'" class="error">
|
||||
<strong>Warning</strong>: Notifications are not supported by your browser.
|
||||
</div>
|
||||
<div
|
||||
v-if="$store.state.desktopNotificationState === 'nohttps'"
|
||||
id="warnBlockedDesktopNotifications"
|
||||
class="error"
|
||||
>
|
||||
<strong>Warning</strong>: Notifications are only supported over HTTPS
|
||||
connections.
|
||||
</div>
|
||||
<div
|
||||
v-if="$store.state.desktopNotificationState === 'blocked'"
|
||||
id="warnBlockedDesktopNotifications"
|
||||
class="error"
|
||||
>
|
||||
<strong>Warning</strong>: Notifications are blocked by your browser.
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label class="opt">
|
||||
<input
|
||||
:checked="$store.state.settings.notification"
|
||||
type="checkbox"
|
||||
name="notification"
|
||||
/>
|
||||
Enable notification sound
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<div class="opt">
|
||||
<button id="play" @click.prevent="playNotification">Play sound</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="opt">
|
||||
<input
|
||||
:checked="$store.state.settings.notifyAllMessages"
|
||||
type="checkbox"
|
||||
name="notifyAllMessages"
|
||||
/>
|
||||
Enable notification for all messages
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div v-if="!$store.state.serverConfiguration.public">
|
||||
<label class="opt">
|
||||
<label for="highlights" class="opt">
|
||||
Custom highlights
|
||||
<span
|
||||
class="tooltipped tooltipped-n tooltipped-no-delay"
|
||||
aria-label="If a message contains any of these comma-separated
|
||||
expressions, it will trigger a highlight."
|
||||
>
|
||||
<button class="extra-help" />
|
||||
</span>
|
||||
</label>
|
||||
<input
|
||||
id="highlights"
|
||||
:value="$store.state.settings.highlights"
|
||||
type="text"
|
||||
name="highlights"
|
||||
class="input"
|
||||
autocomplete="off"
|
||||
placeholder="Comma-separated, e.g.: word, some more words, anotherword"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div v-if="!$store.state.serverConfiguration.public">
|
||||
<label class="opt">
|
||||
<label for="highlightExceptions" class="opt">
|
||||
Highlight exceptions
|
||||
<span
|
||||
class="tooltipped tooltipped-n tooltipped-no-delay"
|
||||
aria-label="If a message contains any of these comma-separated
|
||||
expressions, it will not trigger a highlight even if it contains
|
||||
your nickname or expressions defined in custom highlights."
|
||||
>
|
||||
<button class="extra-help" />
|
||||
</span>
|
||||
</label>
|
||||
<input
|
||||
id="highlightExceptions"
|
||||
:value="$store.state.settings.highlightExceptions"
|
||||
type="text"
|
||||
name="highlightExceptions"
|
||||
class="input"
|
||||
autocomplete="off"
|
||||
placeholder="Comma-separated, e.g.: word, some more words, anotherword"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import webpush from "../../js/webpush";
|
||||
|
||||
export default {
|
||||
name: "NotificationSettings",
|
||||
data() {
|
||||
return {
|
||||
// https://stackoverflow.com/questions/9038625/detect-if-device-is-ios
|
||||
isIOS: () =>
|
||||
[
|
||||
"iPad Simulator",
|
||||
"iPhone Simulator",
|
||||
"iPod Simulator",
|
||||
"iPad",
|
||||
"iPhone",
|
||||
"iPod",
|
||||
].includes(navigator.platform) ||
|
||||
// iPad on iOS 13 detection
|
||||
(navigator.userAgent.includes("Mac") && "ontouchend" in document),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
playNotification() {
|
||||
const pop = new Audio();
|
||||
pop.src = "audio/pop.wav";
|
||||
pop.play();
|
||||
},
|
||||
onPushButtonClick() {
|
||||
webpush.togglePushSubscription();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
38
client/components/Settings/SettingTabItem.vue
Normal file
38
client/components/Settings/SettingTabItem.vue
Normal file
@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<li :aria-label="name">
|
||||
<router-link
|
||||
v-slot:default="{navigate, isExactActive}"
|
||||
:to="'/settings/' + to"
|
||||
:class="['icon', className]"
|
||||
:aria-label="name"
|
||||
role="tab"
|
||||
aria-controls="settings"
|
||||
:aria-selected="$route.name === name"
|
||||
custom
|
||||
>
|
||||
<button :class="{active: isExactActive}" @click="navigate" @keypress.enter="navigate">
|
||||
{{ name }}
|
||||
</button>
|
||||
</router-link>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "SettingTabListItem",
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
to: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
@ -52,7 +52,7 @@
|
||||
aria-label="Settings"
|
||||
role="tab"
|
||||
aria-controls="settings"
|
||||
:aria-selected="$route.name === 'Settings'"
|
||||
:aria-selected="$route.name === 'General'"
|
||||
/></span>
|
||||
<span
|
||||
class="tooltipped tooltipped-n tooltipped-no-touch"
|
||||
|
@ -3,597 +3,25 @@
|
||||
<div class="header">
|
||||
<SidebarToggle />
|
||||
</div>
|
||||
<form
|
||||
ref="settingsForm"
|
||||
class="container"
|
||||
autocomplete="off"
|
||||
@change="onChange"
|
||||
@submit.prevent
|
||||
>
|
||||
<h1 class="title">Settings</h1>
|
||||
<Navigation />
|
||||
|
||||
<div>
|
||||
<label class="opt">
|
||||
<input
|
||||
:checked="$store.state.settings.advanced"
|
||||
type="checkbox"
|
||||
name="advanced"
|
||||
/>
|
||||
Advanced settings
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div v-if="canRegisterProtocol || hasInstallPromptEvent">
|
||||
<h2>Native app</h2>
|
||||
<button
|
||||
v-if="hasInstallPromptEvent"
|
||||
type="button"
|
||||
class="btn"
|
||||
@click.prevent="nativeInstallPrompt"
|
||||
>
|
||||
Add The Lounge to Home screen
|
||||
</button>
|
||||
<button
|
||||
v-if="canRegisterProtocol"
|
||||
type="button"
|
||||
class="btn"
|
||||
@click.prevent="registerProtocol"
|
||||
>
|
||||
Open irc:// URLs with The Lounge
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="!$store.state.serverConfiguration.public && $store.state.settings.advanced">
|
||||
<h2>Settings synchronisation</h2>
|
||||
<label class="opt">
|
||||
<input
|
||||
:checked="$store.state.settings.syncSettings"
|
||||
type="checkbox"
|
||||
name="syncSettings"
|
||||
/>
|
||||
Synchronize settings with other clients
|
||||
</label>
|
||||
<template v-if="!$store.state.settings.syncSettings">
|
||||
<div v-if="$store.state.serverHasSettings" class="settings-sync-panel">
|
||||
<p>
|
||||
<strong>Warning:</strong> Checking this box will override the settings
|
||||
of this client with those stored on the server.
|
||||
</p>
|
||||
<p>
|
||||
Use the button below to enable synchronization, and override any
|
||||
settings already synced to the server.
|
||||
</p>
|
||||
<button type="button" class="btn btn-small" @click="onForceSyncClick">
|
||||
Sync settings and enable
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="settings-sync-panel">
|
||||
<p>
|
||||
<strong>Warning:</strong> No settings have been synced before. Enabling
|
||||
this will sync all settings of this client as the base for other
|
||||
clients.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<h2>Messages</h2>
|
||||
<div>
|
||||
<label class="opt">
|
||||
<input :checked="$store.state.settings.motd" type="checkbox" name="motd" />
|
||||
Show <abbr title="Message Of The Day">MOTD</abbr>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label class="opt">
|
||||
<input
|
||||
:checked="$store.state.settings.showSeconds"
|
||||
type="checkbox"
|
||||
name="showSeconds"
|
||||
/>
|
||||
Include seconds in timestamp
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label class="opt">
|
||||
<input
|
||||
:checked="$store.state.settings.use12hClock"
|
||||
type="checkbox"
|
||||
name="use12hClock"
|
||||
/>
|
||||
Use 12-hour timestamps
|
||||
</label>
|
||||
</div>
|
||||
<div v-if="!$store.state.serverConfiguration.public && $store.state.settings.advanced">
|
||||
<h2>Automatic away message</h2>
|
||||
|
||||
<label class="opt">
|
||||
<label for="awayMessage" class="sr-only">Automatic away message</label>
|
||||
<input
|
||||
id="awayMessage"
|
||||
:value="$store.state.settings.awayMessage"
|
||||
type="text"
|
||||
name="awayMessage"
|
||||
class="input"
|
||||
placeholder="Away message if The Lounge is not open"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<h2 id="label-status-messages">
|
||||
Status messages
|
||||
<span
|
||||
class="tooltipped tooltipped-n tooltipped-no-delay"
|
||||
aria-label="Joins, parts, quits, kicks, nick changes, and mode changes"
|
||||
>
|
||||
<button class="extra-help" />
|
||||
</span>
|
||||
</h2>
|
||||
<div role="group" aria-labelledby="label-status-messages">
|
||||
<label class="opt">
|
||||
<input
|
||||
:checked="$store.state.settings.statusMessages === 'shown'"
|
||||
type="radio"
|
||||
name="statusMessages"
|
||||
value="shown"
|
||||
/>
|
||||
Show all status messages individually
|
||||
</label>
|
||||
<label class="opt">
|
||||
<input
|
||||
:checked="$store.state.settings.statusMessages === 'condensed'"
|
||||
type="radio"
|
||||
name="statusMessages"
|
||||
value="condensed"
|
||||
/>
|
||||
Condense status messages together
|
||||
</label>
|
||||
<label class="opt">
|
||||
<input
|
||||
:checked="$store.state.settings.statusMessages === 'hidden'"
|
||||
type="radio"
|
||||
name="statusMessages"
|
||||
value="hidden"
|
||||
/>
|
||||
Hide all status messages
|
||||
</label>
|
||||
</div>
|
||||
<h2>Visual Aids</h2>
|
||||
<div>
|
||||
<label class="opt">
|
||||
<input
|
||||
:checked="$store.state.settings.coloredNicks"
|
||||
type="checkbox"
|
||||
name="coloredNicks"
|
||||
/>
|
||||
Enable colored nicknames
|
||||
</label>
|
||||
<label class="opt">
|
||||
<input
|
||||
:checked="$store.state.settings.autocomplete"
|
||||
type="checkbox"
|
||||
name="autocomplete"
|
||||
/>
|
||||
Enable autocomplete
|
||||
</label>
|
||||
</div>
|
||||
<div v-if="$store.state.settings.advanced">
|
||||
<label class="opt">
|
||||
<label for="nickPostfix" class="opt">
|
||||
Nick autocomplete postfix
|
||||
<span
|
||||
class="tooltipped tooltipped-n tooltipped-no-delay"
|
||||
aria-label="Nick autocomplete postfix (for example a comma)"
|
||||
>
|
||||
<button class="extra-help" />
|
||||
</span>
|
||||
</label>
|
||||
<input
|
||||
id="nickPostfix"
|
||||
:value="$store.state.settings.nickPostfix"
|
||||
type="text"
|
||||
name="nickPostfix"
|
||||
class="input"
|
||||
placeholder="Nick autocomplete postfix (e.g. ', ')"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<h2>Theme</h2>
|
||||
<div>
|
||||
<label for="theme-select" class="sr-only">Theme</label>
|
||||
<select
|
||||
id="theme-select"
|
||||
:value="$store.state.settings.theme"
|
||||
name="theme"
|
||||
class="input"
|
||||
>
|
||||
<option
|
||||
v-for="theme in $store.state.serverConfiguration.themes"
|
||||
:key="theme.name"
|
||||
:value="theme.name"
|
||||
>
|
||||
{{ theme.displayName }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<template v-if="$store.state.serverConfiguration.prefetch">
|
||||
<h2>Link previews</h2>
|
||||
<div>
|
||||
<label class="opt">
|
||||
<input
|
||||
:checked="$store.state.settings.media"
|
||||
type="checkbox"
|
||||
name="media"
|
||||
/>
|
||||
Auto-expand media
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label class="opt">
|
||||
<input
|
||||
:checked="$store.state.settings.links"
|
||||
type="checkbox"
|
||||
name="links"
|
||||
/>
|
||||
Auto-expand websites
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div
|
||||
v-if="$store.state.settings.advanced && $store.state.serverConfiguration.fileUpload"
|
||||
>
|
||||
<h2>File uploads</h2>
|
||||
<div>
|
||||
<label class="opt">
|
||||
<input
|
||||
:checked="$store.state.settings.uploadCanvas"
|
||||
type="checkbox"
|
||||
name="uploadCanvas"
|
||||
/>
|
||||
Attempt to remove metadata from images before uploading
|
||||
<span
|
||||
class="tooltipped tooltipped-n tooltipped-no-delay"
|
||||
aria-label="This option renders the image into a canvas element to remove metadata from the image.
|
||||
This may break orientation if your browser does not support that."
|
||||
>
|
||||
<button class="extra-help" />
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="!$store.state.serverConfiguration.public">
|
||||
<h2>Push Notifications</h2>
|
||||
<div>
|
||||
<button
|
||||
id="pushNotifications"
|
||||
type="button"
|
||||
class="btn"
|
||||
:disabled="
|
||||
$store.state.pushNotificationState !== 'supported' &&
|
||||
$store.state.pushNotificationState !== 'subscribed'
|
||||
"
|
||||
@click="onPushButtonClick"
|
||||
>
|
||||
<template v-if="$store.state.pushNotificationState === 'subscribed'">
|
||||
Unsubscribe from push notifications
|
||||
</template>
|
||||
<template v-else-if="$store.state.pushNotificationState === 'loading'">
|
||||
Loading…
|
||||
</template>
|
||||
<template v-else> Subscribe to push notifications </template>
|
||||
</button>
|
||||
<div v-if="$store.state.pushNotificationState === 'nohttps'" class="error">
|
||||
<strong>Warning</strong>: Push notifications are only supported over HTTPS
|
||||
connections.
|
||||
</div>
|
||||
<div v-if="$store.state.pushNotificationState === 'unsupported'" class="error">
|
||||
<strong>Warning</strong>:
|
||||
<span>Push notifications are not supported by your browser.</span>
|
||||
|
||||
<div v-if="isIOS" class="apple-push-unsupported">
|
||||
Safari does
|
||||
<a
|
||||
href="https://bugs.webkit.org/show_bug.cgi?id=182566"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>not support the web push notification specification</a
|
||||
>, and because all browsers on iOS use Safari under the hood, The Lounge
|
||||
is unable to provide push notifications on iOS devices.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<h2>Browser Notifications</h2>
|
||||
<div>
|
||||
<label class="opt">
|
||||
<input
|
||||
id="desktopNotifications"
|
||||
:checked="$store.state.settings.desktopNotifications"
|
||||
:disabled="$store.state.desktopNotificationState === 'nohttps'"
|
||||
type="checkbox"
|
||||
name="desktopNotifications"
|
||||
/>
|
||||
Enable browser notifications<br />
|
||||
<div
|
||||
v-if="$store.state.desktopNotificationState === 'unsupported'"
|
||||
class="error"
|
||||
>
|
||||
<strong>Warning</strong>: Notifications are not supported by your browser.
|
||||
</div>
|
||||
<div
|
||||
v-if="$store.state.desktopNotificationState === 'nohttps'"
|
||||
id="warnBlockedDesktopNotifications"
|
||||
class="error"
|
||||
>
|
||||
<strong>Warning</strong>: Notifications are only supported over HTTPS
|
||||
connections.
|
||||
</div>
|
||||
<div
|
||||
v-if="$store.state.desktopNotificationState === 'blocked'"
|
||||
id="warnBlockedDesktopNotifications"
|
||||
class="error"
|
||||
>
|
||||
<strong>Warning</strong>: Notifications are blocked by your browser.
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label class="opt">
|
||||
<input
|
||||
:checked="$store.state.settings.notification"
|
||||
type="checkbox"
|
||||
name="notification"
|
||||
/>
|
||||
Enable notification sound
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<div class="opt">
|
||||
<button id="play" @click.prevent="playNotification">Play sound</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="$store.state.settings.advanced">
|
||||
<label class="opt">
|
||||
<input
|
||||
:checked="$store.state.settings.notifyAllMessages"
|
||||
type="checkbox"
|
||||
name="notifyAllMessages"
|
||||
/>
|
||||
Enable notification for all messages
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div v-if="!$store.state.serverConfiguration.public && $store.state.settings.advanced">
|
||||
<label class="opt">
|
||||
<label for="highlights" class="opt">
|
||||
Custom highlights
|
||||
<span
|
||||
class="tooltipped tooltipped-n tooltipped-no-delay"
|
||||
aria-label="If a message contains any of these comma-separated
|
||||
expressions, it will trigger a highlight."
|
||||
>
|
||||
<button class="extra-help" />
|
||||
</span>
|
||||
</label>
|
||||
<input
|
||||
id="highlights"
|
||||
:value="$store.state.settings.highlights"
|
||||
type="text"
|
||||
name="highlights"
|
||||
class="input"
|
||||
autocomplete="off"
|
||||
placeholder="Comma-separated, e.g.: word, some more words, anotherword"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div v-if="!$store.state.serverConfiguration.public && $store.state.settings.advanced">
|
||||
<label class="opt">
|
||||
<label for="highlightExceptions" class="opt">
|
||||
Highlight exceptions
|
||||
<span
|
||||
class="tooltipped tooltipped-n tooltipped-no-delay"
|
||||
aria-label="If a message contains any of these comma-separated
|
||||
expressions, it will not trigger a highlight even if it contains
|
||||
your nickname or expressions defined in custom highlights."
|
||||
>
|
||||
<button class="extra-help" />
|
||||
</span>
|
||||
</label>
|
||||
<input
|
||||
id="highlightExceptions"
|
||||
:value="$store.state.settings.highlightExceptions"
|
||||
type="text"
|
||||
name="highlightExceptions"
|
||||
class="input"
|
||||
autocomplete="off"
|
||||
placeholder="Comma-separated, e.g.: word, some more words, anotherword"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="
|
||||
!$store.state.serverConfiguration.public &&
|
||||
!$store.state.serverConfiguration.ldapEnabled
|
||||
"
|
||||
id="change-password"
|
||||
role="group"
|
||||
aria-labelledby="label-change-password"
|
||||
>
|
||||
<h2 id="label-change-password">Change password</h2>
|
||||
<div class="password-container">
|
||||
<label for="current-password" class="sr-only"> Enter current password </label>
|
||||
<RevealPassword v-slot:default="slotProps">
|
||||
<input
|
||||
id="current-password"
|
||||
autocomplete="current-password"
|
||||
:type="slotProps.isVisible ? 'text' : 'password'"
|
||||
name="old_password"
|
||||
class="input"
|
||||
placeholder="Enter current password"
|
||||
/>
|
||||
</RevealPassword>
|
||||
</div>
|
||||
<div class="password-container">
|
||||
<label for="new-password" class="sr-only"> Enter desired new password </label>
|
||||
<RevealPassword v-slot:default="slotProps">
|
||||
<input
|
||||
id="new-password"
|
||||
:type="slotProps.isVisible ? 'text' : 'password'"
|
||||
name="new_password"
|
||||
autocomplete="new-password"
|
||||
class="input"
|
||||
placeholder="Enter desired new password"
|
||||
/>
|
||||
</RevealPassword>
|
||||
</div>
|
||||
<div class="password-container">
|
||||
<label for="new-password-verify" class="sr-only"> Repeat new password </label>
|
||||
<RevealPassword v-slot:default="slotProps">
|
||||
<input
|
||||
id="new-password-verify"
|
||||
:type="slotProps.isVisible ? 'text' : 'password'"
|
||||
name="verify_password"
|
||||
autocomplete="new-password"
|
||||
class="input"
|
||||
placeholder="Repeat new password"
|
||||
/>
|
||||
</RevealPassword>
|
||||
</div>
|
||||
<div
|
||||
v-if="passwordChangeStatus && passwordChangeStatus.success"
|
||||
class="feedback success"
|
||||
>
|
||||
Successfully updated your password
|
||||
</div>
|
||||
<div
|
||||
v-else-if="passwordChangeStatus && passwordChangeStatus.error"
|
||||
class="feedback error"
|
||||
>
|
||||
{{ passwordErrors[passwordChangeStatus.error] }}
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit" class="btn" @click.prevent="changePassword">
|
||||
Change password
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="$store.state.settings.advanced">
|
||||
<h2>Custom Stylesheet</h2>
|
||||
<label for="user-specified-css-input" class="sr-only">
|
||||
Custom stylesheet. You can override any style with CSS here.
|
||||
</label>
|
||||
<textarea
|
||||
id="user-specified-css-input"
|
||||
:value="$store.state.settings.userStyles"
|
||||
class="input"
|
||||
name="userStyles"
|
||||
placeholder="/* You can override any style with CSS here */"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="!$store.state.serverConfiguration.public" class="session-list" role="group">
|
||||
<h2>Sessions</h2>
|
||||
|
||||
<h3>Current session</h3>
|
||||
<Session v-if="currentSession" :session="currentSession" />
|
||||
|
||||
<template v-if="activeSessions.length > 0">
|
||||
<h3>Active sessions</h3>
|
||||
<Session
|
||||
v-for="session in activeSessions"
|
||||
:key="session.token"
|
||||
:session="session"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<h3>Other sessions</h3>
|
||||
<p v-if="$store.state.sessions.length === 0">Loading…</p>
|
||||
<p v-else-if="otherSessions.length === 0">
|
||||
<em>You are not currently logged in to any other device.</em>
|
||||
</p>
|
||||
<Session
|
||||
v-for="session in otherSessions"
|
||||
v-else
|
||||
:key="session.token"
|
||||
:session="session"
|
||||
/>
|
||||
</div>
|
||||
<div class="container">
|
||||
<form ref="settingsForm" autocomplete="off" @change="onChange" @submit.prevent>
|
||||
<router-view></router-view>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
textarea#user-specified-css-input {
|
||||
height: 100px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import socket from "../../js/socket";
|
||||
import webpush from "../../js/webpush";
|
||||
import RevealPassword from "../RevealPassword.vue";
|
||||
import Session from "../Session.vue";
|
||||
import SidebarToggle from "../SidebarToggle.vue";
|
||||
|
||||
let installPromptEvent = null;
|
||||
|
||||
window.addEventListener("beforeinstallprompt", (e) => {
|
||||
e.preventDefault();
|
||||
installPromptEvent = e;
|
||||
});
|
||||
import Navigation from "../Settings/Navigation.vue";
|
||||
|
||||
export default {
|
||||
name: "Settings",
|
||||
components: {
|
||||
RevealPassword,
|
||||
Session,
|
||||
SidebarToggle,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
canRegisterProtocol: false,
|
||||
passwordChangeStatus: null,
|
||||
passwordErrors: {
|
||||
missing_fields: "Please enter a new password",
|
||||
password_mismatch: "Both new password fields must match",
|
||||
password_incorrect:
|
||||
"The current password field does not match your account password",
|
||||
update_failed: "Failed to update your password",
|
||||
},
|
||||
isIOS: navigator.platform.match(/(iPhone|iPod|iPad)/i) || false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
hasInstallPromptEvent() {
|
||||
// TODO: This doesn't hide the button after clicking
|
||||
return installPromptEvent !== null;
|
||||
},
|
||||
currentSession() {
|
||||
return this.$store.state.sessions.find((item) => item.current);
|
||||
},
|
||||
activeSessions() {
|
||||
return this.$store.state.sessions.filter((item) => !item.current && item.active > 0);
|
||||
},
|
||||
otherSessions() {
|
||||
return this.$store.state.sessions.filter((item) => !item.current && !item.active);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
socket.emit("sessions:get");
|
||||
|
||||
// Enable protocol handler registration if supported,
|
||||
// and the network configuration is not locked
|
||||
this.canRegisterProtocol =
|
||||
window.navigator.registerProtocolHandler &&
|
||||
!this.$store.state.serverConfiguration.lockNetwork;
|
||||
Navigation,
|
||||
},
|
||||
methods: {
|
||||
onChange(event) {
|
||||
@ -615,62 +43,6 @@ export default {
|
||||
|
||||
this.$store.dispatch("settings/update", {name, value, sync: true});
|
||||
},
|
||||
changePassword() {
|
||||
const allFields = new FormData(this.$refs.settingsForm);
|
||||
const data = {
|
||||
old_password: allFields.get("old_password"),
|
||||
new_password: allFields.get("new_password"),
|
||||
verify_password: allFields.get("verify_password"),
|
||||
};
|
||||
|
||||
if (!data.old_password || !data.new_password || !data.verify_password) {
|
||||
this.passwordChangeStatus = {
|
||||
success: false,
|
||||
error: "missing_fields",
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.new_password !== data.verify_password) {
|
||||
this.passwordChangeStatus = {
|
||||
success: false,
|
||||
error: "password_mismatch",
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
socket.once("change-password", (response) => {
|
||||
this.passwordChangeStatus = response;
|
||||
});
|
||||
|
||||
socket.emit("change-password", data);
|
||||
},
|
||||
onForceSyncClick() {
|
||||
this.$store.dispatch("settings/syncAll", true);
|
||||
this.$store.dispatch("settings/update", {
|
||||
name: "syncSettings",
|
||||
value: true,
|
||||
sync: true,
|
||||
});
|
||||
},
|
||||
registerProtocol() {
|
||||
const uri = document.location.origin + document.location.pathname + "?uri=%s";
|
||||
|
||||
window.navigator.registerProtocolHandler("irc", uri, "The Lounge");
|
||||
window.navigator.registerProtocolHandler("ircs", uri, "The Lounge");
|
||||
},
|
||||
nativeInstallPrompt() {
|
||||
installPromptEvent.prompt();
|
||||
installPromptEvent = null;
|
||||
},
|
||||
playNotification() {
|
||||
const pop = new Audio();
|
||||
pop.src = "audio/pop.wav";
|
||||
pop.play();
|
||||
},
|
||||
onPushButtonClick() {
|
||||
webpush.togglePushSubscription();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -298,6 +298,7 @@ p {
|
||||
#connect .extra-help,
|
||||
#settings .extra-help,
|
||||
#settings #play::before,
|
||||
#settings .settings-menu .icon::before,
|
||||
#form #upload::before,
|
||||
#form #submit::before,
|
||||
#chat .msg[data-type="away"] .from::before,
|
||||
@ -397,7 +398,9 @@ p {
|
||||
}
|
||||
|
||||
#footer .connect::before { content: "\f067"; /* http://fontawesome.io/icon/plus/ */ }
|
||||
|
||||
#footer .settings::before { content: "\f013"; /* http://fontawesome.io/icon/cog/ */ }
|
||||
|
||||
#footer .help::before { content: "\f059"; /* http://fontawesome.io/icon/question/ */ }
|
||||
|
||||
#form #upload::before { content: "\f0c6"; /* https://fontawesome.com/icons/paperclip?style=solid */ }
|
||||
@ -722,7 +725,7 @@ background on hover (unless active) */
|
||||
background-color: rgb(48 62 74 / 50%); /* #303e4a x 50% alpha */
|
||||
}
|
||||
|
||||
/* Darker background and defualt cursor for active channels */
|
||||
/* Darker background and default cursor for active channels */
|
||||
#footer button.active,
|
||||
.channel-list-item.active {
|
||||
background-color: #303e4a;
|
||||
|
@ -17,6 +17,11 @@ import SearchResults from "../components/Windows/SearchResults.vue";
|
||||
import RoutedChat from "../components/RoutedChat.vue";
|
||||
import store from "./store";
|
||||
|
||||
import AppearanceSettings from "../components/Settings/Appearance.vue";
|
||||
import GeneralSettings from "../components/Settings/General.vue";
|
||||
import AccountSettings from "../components/Settings/Account.vue";
|
||||
import NotificationSettings from "../components/Settings/Notifications.vue";
|
||||
|
||||
const router = new VueRouter({
|
||||
routes: [
|
||||
{
|
||||
@ -40,9 +45,30 @@ const router = new VueRouter({
|
||||
props: (route) => ({queryParams: route.query}),
|
||||
},
|
||||
{
|
||||
name: "Settings",
|
||||
path: "/settings",
|
||||
component: Settings,
|
||||
children: [
|
||||
{
|
||||
name: "General",
|
||||
path: "",
|
||||
component: GeneralSettings,
|
||||
},
|
||||
{
|
||||
name: "Appearance",
|
||||
path: "appearance",
|
||||
component: AppearanceSettings,
|
||||
},
|
||||
{
|
||||
name: "Account",
|
||||
path: "account",
|
||||
component: AccountSettings,
|
||||
},
|
||||
{
|
||||
name: "Notifications",
|
||||
path: "notifications",
|
||||
component: NotificationSettings,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Help",
|
||||
|
Loading…
Reference in New Issue
Block a user