mirror of
https://github.com/Troplo/Colubrina.git
synced 2024-11-22 19:27:55 +11:00
update
This commit is contained in:
parent
87302afa1d
commit
be90a91e52
22 changed files with 2768 additions and 1119 deletions
|
@ -7,3 +7,4 @@ VUE_APP_MATOMO_TRACKER=flow
|
|||
VUE_APP_MATOMO_DOMAINS=*.troplo.com
|
||||
VUE_APP_MATOMO_ENABLED=true
|
||||
VUE_APP_SENTRY_ENABLED=true
|
||||
VUE_APP_BASE_URL=
|
|
@ -34,12 +34,36 @@ module.exports = async function (socket, next) {
|
|||
next()
|
||||
}
|
||||
} else {
|
||||
//
|
||||
socket.user = {
|
||||
id: null,
|
||||
username: "Not Authenticated"
|
||||
}
|
||||
socket.compassUser = {
|
||||
id: null,
|
||||
username: "BC-NOAUTH"
|
||||
}
|
||||
next()
|
||||
}
|
||||
} else {
|
||||
//
|
||||
socket.user = {
|
||||
id: null,
|
||||
username: "Not Authenticated"
|
||||
}
|
||||
socket.compassUser = {
|
||||
id: null,
|
||||
username: "BC-NOAUTH"
|
||||
}
|
||||
next()
|
||||
}
|
||||
} catch (error) {
|
||||
//
|
||||
socket.user = {
|
||||
id: null,
|
||||
username: "Not Authenticated"
|
||||
}
|
||||
socket.compassUser = {
|
||||
id: null,
|
||||
username: "BC-NOAUTH"
|
||||
}
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@ module.exports = {
|
|||
id: socket.user.id
|
||||
}
|
||||
})
|
||||
if (user && socket.user.id) {
|
||||
console.log(socket.user.id)
|
||||
socket.join(user.id)
|
||||
socket.emit("siteState", {
|
||||
release: process.env.RELEASE,
|
||||
|
@ -42,6 +44,15 @@ module.exports = {
|
|||
socket.on("ping", () => {
|
||||
socket.emit("pong")
|
||||
})
|
||||
socket.on("bcBots/deleteMessage", (e) => {
|
||||
if (socket.user.bot) {
|
||||
socket.to(e.userId).emit("deleteMessage", e)
|
||||
} else {
|
||||
socket.emit("bcBots/deleteMessage", {
|
||||
error: "You cannot perform this action."
|
||||
})
|
||||
}
|
||||
})
|
||||
socket.on("idle", async () => {
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
|
@ -97,6 +108,22 @@ module.exports = {
|
|||
status: "offline"
|
||||
})
|
||||
})
|
||||
} else {
|
||||
socket.join(-1)
|
||||
socket.emit("siteState", {
|
||||
release: process.env.RELEASE,
|
||||
notification: process.env.NOTIFICATION,
|
||||
notificationType: process.env.NOTIFICATION_TYPE,
|
||||
latestVersion: require("../../package.json").version
|
||||
})
|
||||
socket.emit("unauthorized", {
|
||||
message: "Please reauth."
|
||||
})
|
||||
console.log("Unauthenticated user")
|
||||
socket.on("reAuth", async () => {
|
||||
socket.disconnect()
|
||||
})
|
||||
}
|
||||
})
|
||||
console.log("WS OK")
|
||||
app.set("io", io)
|
||||
|
|
43
backend/migrations/20220706061900-nicknames.js
Normal file
43
backend/migrations/20220706061900-nicknames.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
"use strict"
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable("Nicknames", {
|
||||
id: {
|
||||
allowNull: false,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
type: Sequelize.BIGINT
|
||||
},
|
||||
userId: {
|
||||
allowNull: false,
|
||||
type: Sequelize.BIGINT
|
||||
},
|
||||
nickname: {
|
||||
allowNull: false,
|
||||
type: Sequelize.STRING
|
||||
},
|
||||
friendId: {
|
||||
allowNull: false,
|
||||
type: Sequelize.BIGINT
|
||||
},
|
||||
createdAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE
|
||||
},
|
||||
updatedAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
/**
|
||||
* Add reverting commands here.
|
||||
*
|
||||
* Example:
|
||||
* await queryInterface.dropTable('users');
|
||||
*/
|
||||
}
|
||||
}
|
20
backend/migrations/20220712133850-bot.js
Normal file
20
backend/migrations/20220712133850-bot.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
"use strict"
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.addColumn("Users", "bot", {
|
||||
type: Sequelize.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false
|
||||
})
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
/**
|
||||
* Add reverting commands here.
|
||||
*
|
||||
* Example:
|
||||
* await queryInterface.dropTable('users');
|
||||
*/
|
||||
}
|
||||
}
|
55
backend/models/nicknames.js
Normal file
55
backend/models/nicknames.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
"use strict"
|
||||
const { Model } = require("sequelize")
|
||||
module.exports = (sequelize, DataTypes) => {
|
||||
class Nickname extends Model {
|
||||
/**
|
||||
* Helper method for defining associations.
|
||||
* This method is not a part of Sequelize lifecycle.
|
||||
* The `models/index` file will call this method automatically.
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
static associate(models) {
|
||||
Nickname.belongsTo(models.User, {
|
||||
foreignKey: "userId",
|
||||
as: "user"
|
||||
})
|
||||
Nickname.belongsTo(models.User, {
|
||||
foreignKey: "friendId",
|
||||
as: "userNickname"
|
||||
})
|
||||
}
|
||||
}
|
||||
Nickname.init(
|
||||
{
|
||||
userId: {
|
||||
allowNull: false,
|
||||
type: DataTypes.BIGINT
|
||||
},
|
||||
nickname: {
|
||||
allowNull: false,
|
||||
type: DataTypes.STRING,
|
||||
validate: {
|
||||
notEmpty: true,
|
||||
len: [1, 20]
|
||||
}
|
||||
},
|
||||
friendId: {
|
||||
allowNull: false,
|
||||
type: DataTypes.BIGINT
|
||||
},
|
||||
createdAt: {
|
||||
allowNull: false,
|
||||
type: DataTypes.DATE
|
||||
},
|
||||
updatedAt: {
|
||||
allowNull: false,
|
||||
type: DataTypes.DATE
|
||||
}
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
modelName: "Nickname"
|
||||
}
|
||||
)
|
||||
return Nickname
|
||||
}
|
|
@ -16,6 +16,10 @@ module.exports = (sequelize, DataTypes) => {
|
|||
as: "friends",
|
||||
foreignKey: "userId"
|
||||
})
|
||||
User.hasOne(models.Nickname, {
|
||||
foreignKey: "friendId",
|
||||
as: "nickname"
|
||||
})
|
||||
}
|
||||
}
|
||||
User.init(
|
||||
|
@ -135,6 +139,11 @@ module.exports = (sequelize, DataTypes) => {
|
|||
avatar: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
bot: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -342,40 +342,6 @@ router.put("/settings/:type", auth, async (req, res, next) => {
|
|||
return false
|
||||
}
|
||||
}
|
||||
async function checkPassword(password) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.post(
|
||||
"http://localhost:23994/services/admin.svc/GetApiKey",
|
||||
{
|
||||
password: password,
|
||||
schoolId: req.body.schoolId,
|
||||
sussiId: req.user.sussiId
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
compassInstance:
|
||||
req.header("compassInstance") ||
|
||||
req.query.compassInstance ||
|
||||
"devices"
|
||||
}
|
||||
}
|
||||
)
|
||||
.then((resp) => {
|
||||
if (resp.data.d) {
|
||||
resolve(true)
|
||||
return true
|
||||
} else {
|
||||
resolve(false)
|
||||
return false
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
reject(e)
|
||||
return false
|
||||
})
|
||||
})
|
||||
}
|
||||
try {
|
||||
const io = req.app.get("io")
|
||||
if (req.params.type === "full") {
|
||||
|
@ -452,66 +418,6 @@ router.put("/settings/:type", auth, async (req, res, next) => {
|
|||
} else {
|
||||
throw Errors.invalidParameter("Password or Code")
|
||||
}
|
||||
} else if (req.params.type === "totpConfirm") {
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
id: req.user.id
|
||||
}
|
||||
})
|
||||
if (user.totp) {
|
||||
const verified = speakeasy.totp.verify({
|
||||
secret: user.totp,
|
||||
encoding: "base32",
|
||||
token: req.body.code
|
||||
})
|
||||
if (verified) {
|
||||
await User.update(
|
||||
{
|
||||
totpEnabled: true
|
||||
},
|
||||
{
|
||||
where: {
|
||||
id: req.user.id
|
||||
}
|
||||
}
|
||||
)
|
||||
res.sendStatus(204)
|
||||
} else {
|
||||
throw Errors.invalidTotp
|
||||
}
|
||||
} else {
|
||||
throw Errors.unknown
|
||||
}
|
||||
} else if (req.params.type === "bcSessionsEnable") {
|
||||
/*
|
||||
const match = await checkPassword(req.body.password)
|
||||
if (match) {
|
||||
await User.update(
|
||||
{
|
||||
bcSessionsEnabled: true
|
||||
},
|
||||
{
|
||||
where: {
|
||||
id: req.user.id
|
||||
}
|
||||
}
|
||||
)
|
||||
const session = await Session.create({
|
||||
session:
|
||||
"BETTERCOMPASS-" +
|
||||
cryptoRandomString({
|
||||
length: 64
|
||||
}),
|
||||
compassUserId: req.user.compassUserId,
|
||||
sussiId: req.user.sussiId,
|
||||
instance: req.user.instance,
|
||||
other: {},
|
||||
userId: req.user.id
|
||||
})
|
||||
} else {
|
||||
throw Errors.invalidCredentials
|
||||
}*/
|
||||
throw Errors.experimentsOptIn
|
||||
} else if (req.params.type === "password") {
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
"@mdi/js": "^6.6.95",
|
||||
"@sentry/tracing": "^6.19.3",
|
||||
"@sentry/vue": "^6.19.3",
|
||||
"@tauri-apps/api": "^1.0.2",
|
||||
"apollo-boost": "^0.4.9",
|
||||
"axios": "^0.26.1",
|
||||
"brace": "^0.11.1",
|
||||
|
|
160
src/App.vue
160
src/App.vue
|
@ -404,27 +404,6 @@
|
|||
</v-dialog>
|
||||
<v-main>
|
||||
<Header></Header>
|
||||
<v-container
|
||||
v-if="
|
||||
$store.state.user?.compact === 'nagPending' &&
|
||||
$vuetify.breakpoint.lgAndDown &&
|
||||
!$vuetify.breakpoint.mobile
|
||||
"
|
||||
>
|
||||
<v-alert dense text color="info" dismissible v-model="compactModeNag">
|
||||
Introducing <strong>Compact Mode</strong>, a better experience for
|
||||
lower resolution devices.
|
||||
<v-btn
|
||||
to="/settings/appearance"
|
||||
text
|
||||
color="primary"
|
||||
outlined
|
||||
class="ml-1"
|
||||
>
|
||||
Settings
|
||||
</v-btn>
|
||||
</v-alert>
|
||||
</v-container>
|
||||
<v-container
|
||||
v-if="$store.state.site.latestVersion > $store.state.versioning.version"
|
||||
id="update-notify-banner"
|
||||
|
@ -466,10 +445,9 @@
|
|||
<style></style>
|
||||
<script>
|
||||
import AjaxErrorHandler from "@/lib/errorHandler"
|
||||
import Vue from "vue"
|
||||
import Vuetify from "@/plugins/vuetify"
|
||||
import { VueFinalModal } from "vue-final-modal"
|
||||
import Header from "@/components/Header"
|
||||
import Vue from "vue"
|
||||
export default {
|
||||
name: "App",
|
||||
components: {
|
||||
|
@ -580,35 +558,6 @@ export default {
|
|||
return index.charAt(0).toUpperCase() + index.slice(1)
|
||||
}
|
||||
},
|
||||
computeColor(event) {
|
||||
if (this.$vuetify?.theme?.themes) {
|
||||
if (event.color === "#003300") {
|
||||
return this.$vuetify.theme.themes[
|
||||
this.$store.state.user.theme || "dark"
|
||||
].calendarActivityType8
|
||||
} else if (event.color === "#133897") {
|
||||
return this.$vuetify.theme.themes[
|
||||
this.$store.state.user.theme || "dark"
|
||||
].calendarExternalActivity
|
||||
} else if (event.activityType === 7 || event.color === "#f4dcdc") {
|
||||
return this.$vuetify.theme.themes[
|
||||
this.$store.state.user.theme || "dark"
|
||||
].calendarActivityType7
|
||||
} else if (event.color === "#dce6f4") {
|
||||
return this.$vuetify.theme.themes[
|
||||
this.$store.state.user.theme || "dark"
|
||||
].calendarNormalActivity
|
||||
} else if (event.activityType === 10) {
|
||||
return this.$vuetify.theme.themes[
|
||||
this.$store.state.user.theme || "dark"
|
||||
].calendarActivityType10
|
||||
} else {
|
||||
return this.$vuetify.theme.themes[
|
||||
this.$store.state.user.theme || "dark"
|
||||
].calendarNormalActivity
|
||||
}
|
||||
}
|
||||
},
|
||||
editorInit() {
|
||||
require("brace/ext/language_tools")
|
||||
require("brace/mode/css")
|
||||
|
@ -617,19 +566,6 @@ export default {
|
|||
require("brace/theme/chrome")
|
||||
require("brace/snippets/css")
|
||||
},
|
||||
baseRole() {
|
||||
if (this.$store.state.user?.baseRole) {
|
||||
return (
|
||||
this.$store.state.user.baseRole
|
||||
.toLowerCase()
|
||||
.charAt(0)
|
||||
.toUpperCase() +
|
||||
this.$store.state.user.baseRole.toLowerCase().slice(1)
|
||||
)
|
||||
} else {
|
||||
return "Not Authenticated"
|
||||
}
|
||||
},
|
||||
saveSettings() {
|
||||
this.loading = true
|
||||
this.$vuetify.theme.dark = this.$store.state.user?.theme === "dark"
|
||||
|
@ -643,15 +579,6 @@ export default {
|
|||
AjaxErrorHandler(this.$store)(e)
|
||||
})
|
||||
},
|
||||
completeGuidedWizard() {
|
||||
this.loadingGuidedWizard = true
|
||||
this.$store.dispatch("saveOnlineSettings", {
|
||||
guidedWizard: false
|
||||
})
|
||||
this.$store.dispatch("getUserInfo").then(() => {
|
||||
this.loadingGuidedWizard = false
|
||||
})
|
||||
},
|
||||
getThemes() {
|
||||
this.axios.get("/api/v1/themes").then((res) => {
|
||||
this.themes = res.data.map((theme) => {
|
||||
|
@ -692,25 +619,27 @@ export default {
|
|||
.catch((e) => {
|
||||
AjaxErrorHandler(this.$store)(e)
|
||||
})
|
||||
},
|
||||
retryConnection() {
|
||||
this.connectionLoading = true
|
||||
this.$store.dispatch("getState").finally(() => {
|
||||
this.connectionLoading = false
|
||||
})
|
||||
},
|
||||
validate(value, defaultValue) {
|
||||
if (value === undefined || value === null) {
|
||||
return defaultValue
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
Vue.axios.defaults.headers.common["Authorization"] =
|
||||
localStorage.getItem("session")
|
||||
if (this.$vuetify.breakpoint.mobile) {
|
||||
this.$store.state.drawer = false
|
||||
}
|
||||
if (localStorage.getItem("cssTipsDismissed")) {
|
||||
this.cssTips = false
|
||||
}
|
||||
if (localStorage.getItem("userPanel")) {
|
||||
this.$store.state.userPanel = JSON.parse(
|
||||
localStorage.getItem("userPanel")
|
||||
)
|
||||
} else {
|
||||
this.$store.state.userPanel = true
|
||||
}
|
||||
if (this.$vuetify.breakpoint.mobile) {
|
||||
this.$store.state.userPanel = false
|
||||
}
|
||||
window.addEventListener("offline", () => {
|
||||
this.$store.commit("setOnline", false)
|
||||
this.$store.dispatch("getState")
|
||||
|
@ -721,56 +650,11 @@ export default {
|
|||
this.$store.dispatch("getUserInfo")
|
||||
})
|
||||
this.$socket.connect()
|
||||
Vue.axios.defaults.headers.common["CompassAPIKey"] =
|
||||
localStorage.getItem("apiKey")
|
||||
Vue.axios.defaults.headers.common["Authorization"] =
|
||||
localStorage.getItem("bcToken")
|
||||
document.title = this.$route.name
|
||||
? this.$route.name + " - " + this.$store.state.site.name
|
||||
: this.$store.state.site.name
|
||||
this.$store.commit("setLoading", true)
|
||||
this.$vuetify.theme.dark = this.$store.state.user?.theme === "dark" || true
|
||||
this.axios.defaults.headers.common["compassInstance"] =
|
||||
localStorage.getItem("schoolInstance")
|
||||
this.axios.defaults.headers.common["compassSchoolId"] =
|
||||
localStorage.getItem("schoolId")
|
||||
if (JSON.parse(localStorage.getItem("userCache"))?.id) {
|
||||
const user = JSON.parse(localStorage.getItem("userCache"))
|
||||
const name = user.themeObject.id
|
||||
const dark = user.themeObject.theme.dark
|
||||
const light = user.themeObject.theme.light
|
||||
if (user.accentColor) {
|
||||
user.themeObject.theme.dark.primary = user.accentColor
|
||||
user.themeObject.theme.light.primary = user.accentColor
|
||||
}
|
||||
if (JSON.parse(localStorage.getItem("subjectsCache"))) {
|
||||
this.$store.commit(
|
||||
"setSubjects",
|
||||
JSON.parse(localStorage.getItem("subjectsCache"))
|
||||
)
|
||||
}
|
||||
Vuetify.framework.theme.themes.dark = dark
|
||||
Vuetify.framework.theme.themes.light = light
|
||||
Vuetify.framework.theme.themes.name = name
|
||||
Vuetify.framework.theme.themes.primaryType =
|
||||
user.themeObject.theme.primaryType
|
||||
this.$store.commit(
|
||||
"setUser",
|
||||
JSON.parse(localStorage.getItem("userCache"))
|
||||
)
|
||||
}
|
||||
if (localStorage.getItem("calendarCache")?.length) {
|
||||
this.$store.commit(
|
||||
"setCalendar",
|
||||
JSON.parse(localStorage.getItem("calendarCache")).map((event) => {
|
||||
return {
|
||||
...event,
|
||||
start: new Date(event.start),
|
||||
end: new Date(event.finish)
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
this.$store.dispatch("getState")
|
||||
this.$store.dispatch("checkAuth").catch(() => {
|
||||
this.$store.dispatch("logout")
|
||||
|
@ -853,9 +737,6 @@ export default {
|
|||
this.$store.state.site.notification = state.notification
|
||||
this.$store.state.site.notificationType = state.notificationType
|
||||
})
|
||||
setInterval(() => {
|
||||
this.$socket.emit("ping")
|
||||
}, 10000)
|
||||
// eslint-disable-next-line no-undef
|
||||
if (JSON.parse(process.env.VUE_APP_MATOMO_ENABLED)) {
|
||||
// eslint-disable-next-line no-undef
|
||||
|
@ -870,11 +751,8 @@ export default {
|
|||
})
|
||||
},
|
||||
watch: {
|
||||
compactModeNag(val) {
|
||||
if (!val) {
|
||||
this.$store.state.user.compact = "disabled"
|
||||
this.$store.dispatch("saveOnlineSettings")
|
||||
}
|
||||
"$store.state.userPanel"(val) {
|
||||
localStorage.setItem("userPanel", val)
|
||||
},
|
||||
cssTips(val) {
|
||||
localStorage.setItem("cssTipsDismissed", !val)
|
||||
|
@ -908,7 +786,7 @@ export default {
|
|||
search() {
|
||||
if (this.search) {
|
||||
if (this.search.id) {
|
||||
this.$router.push("/activity/activity/" + this.search.id)
|
||||
this.$router.push("/communications/" + this.search.id)
|
||||
this.$store.commit("setSearch", false)
|
||||
this.search = null
|
||||
this.$nextTick(() => {
|
||||
|
|
292
src/components/CommsInput.vue
Normal file
292
src/components/CommsInput.vue
Normal file
|
@ -0,0 +1,292 @@
|
|||
<template>
|
||||
<div>
|
||||
<v-toolbar
|
||||
elevation="0"
|
||||
outlined
|
||||
height="40"
|
||||
color="card"
|
||||
v-if="file"
|
||||
style="cursor: pointer; overflow: hidden"
|
||||
class="mb-2"
|
||||
>
|
||||
<v-toolbar-title>
|
||||
<v-icon> mdi-attachment </v-icon>
|
||||
{{ file.name }}
|
||||
</v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn icon @click="file = null" small>
|
||||
<v-icon> mdi-close </v-icon>
|
||||
</v-btn>
|
||||
</v-toolbar>
|
||||
<v-textarea
|
||||
style="margin-bottom: 5px"
|
||||
autofocus
|
||||
label="Type a message"
|
||||
placeholder="Keep it civil"
|
||||
type="text"
|
||||
ref="message-input"
|
||||
:id="edit ? 'edit-input' : 'message-input'"
|
||||
outlined
|
||||
append-outer-icon="mdi-send"
|
||||
auto-grow
|
||||
@keyup.up="editLastMessage"
|
||||
@keyup.esc="handleEsc"
|
||||
@click:append-outer="handleMessage()"
|
||||
@keydown.enter.exact.prevent="handleMessage()"
|
||||
v-model="message"
|
||||
@paste="handlePaste"
|
||||
@change="handleChange"
|
||||
rows="1"
|
||||
single-line
|
||||
dense
|
||||
hide-details
|
||||
@keydown.enter.shift.exact.prevent="newLine($event)"
|
||||
>
|
||||
<template v-slot:append-outer>
|
||||
<v-btn
|
||||
icon
|
||||
small
|
||||
@click="handleMessage()"
|
||||
v-if="!edit"
|
||||
:retain-focus-on-click="false"
|
||||
class="no-focus"
|
||||
>
|
||||
<v-icon> mdi-send </v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<template v-slot:prepend>
|
||||
<v-file-input
|
||||
style="margin-top: -18px"
|
||||
single-line
|
||||
hide-input
|
||||
v-model="file"
|
||||
@change="getURLForImage"
|
||||
v-if="!edit"
|
||||
></v-file-input>
|
||||
</template>
|
||||
</v-textarea>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AjaxErrorHandler from "@/lib/errorHandler"
|
||||
|
||||
export default {
|
||||
name: "CommsInput",
|
||||
props: {
|
||||
replying: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
edit: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
autoScroll: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
},
|
||||
editLastMessage: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
},
|
||||
endEdit: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
},
|
||||
endSend: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
},
|
||||
chat: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
message: "",
|
||||
file: null,
|
||||
blobURL: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleMessage() {
|
||||
if (this.edit) {
|
||||
this.editMessage()
|
||||
} else {
|
||||
this.sendMessage()
|
||||
}
|
||||
},
|
||||
editMessage() {
|
||||
if (this.message.length > 0) {
|
||||
this.axios
|
||||
.put(
|
||||
process.env.VUE_APP_BASE_URL +
|
||||
"/api/v1/communications/" +
|
||||
this.$route.params.id +
|
||||
"/message/edit",
|
||||
{
|
||||
id: this.edit.id,
|
||||
content: this.message
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
this.edit.editing = false
|
||||
this.edit.id = null
|
||||
this.edit.content = ""
|
||||
this.focusInput()
|
||||
this.endEdit()
|
||||
// response will be handled via WebSocket
|
||||
})
|
||||
.catch((e) => {
|
||||
AjaxErrorHandler(this.$store)(e)
|
||||
})
|
||||
}
|
||||
},
|
||||
newLine(event) {
|
||||
this.message = event.target.value + "\n"
|
||||
},
|
||||
handleEsc() {
|
||||
if (!this.edit) {
|
||||
if (this.replying) {
|
||||
this.endSend()
|
||||
}
|
||||
} else {
|
||||
this.endEdit()
|
||||
}
|
||||
},
|
||||
getURLForImage() {
|
||||
const file = this.file
|
||||
const reader = new FileReader()
|
||||
reader.onload = (e) => {
|
||||
this.blobURL = e.target.result
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
},
|
||||
handleChange() {
|
||||
if (this.$store.state.user.bcUser.storedStatus !== "invisible") {
|
||||
if (this.typingDate) {
|
||||
const now = new Date()
|
||||
if (now - this.typingDate > 5000) {
|
||||
this.typingDate = now
|
||||
this.axios.put(
|
||||
process.env.VUE_APP_BASE_URL +
|
||||
"/api/v1/communications/" +
|
||||
this.chat.id +
|
||||
"/typing"
|
||||
)
|
||||
}
|
||||
} else {
|
||||
this.typingDate = new Date()
|
||||
this.axios.put(
|
||||
process.env.VUE_APP_BASE_URL +
|
||||
"/api/v1/communications/" +
|
||||
this.chat.id +
|
||||
"/typing"
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
handlePaste(data) {
|
||||
if (data.clipboardData.items.length) {
|
||||
const item = data.clipboardData.items[0]
|
||||
if (item.kind === "file") {
|
||||
this.file = item.getAsFile()
|
||||
this.getURLForImage()
|
||||
}
|
||||
}
|
||||
},
|
||||
focusInput() {
|
||||
const input = document.getElementById("message-input")
|
||||
input.focus()
|
||||
},
|
||||
sendMessage() {
|
||||
this.focusInput()
|
||||
let message = this.message
|
||||
this.message = ""
|
||||
if (this.file || message.length > 0) {
|
||||
const emojis = require("../lib/emojis.json")
|
||||
message = message.replaceAll(
|
||||
/:([a-zA-Z0-9_\-+]+):/g,
|
||||
(match, group1) => {
|
||||
const emoji = emojis.find((emoji) => {
|
||||
return emoji.aliases.includes(group1)
|
||||
})
|
||||
if (emoji) {
|
||||
return emoji.emoji
|
||||
} else {
|
||||
return match
|
||||
}
|
||||
}
|
||||
)
|
||||
if (!this.file) {
|
||||
this.axios
|
||||
.post(
|
||||
process.env.VUE_APP_BASE_URL +
|
||||
"/api/v1/communications/" +
|
||||
this.$route.params.id +
|
||||
"/message",
|
||||
{
|
||||
message: message,
|
||||
replyId: this.replying?.id
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
this.focusInput()
|
||||
this.message = ""
|
||||
this.autoScroll()
|
||||
this.endSend()
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e)
|
||||
AjaxErrorHandler(this.$store)(e)
|
||||
})
|
||||
} else {
|
||||
const formData = new FormData()
|
||||
formData.append("message", message)
|
||||
if (this.replying) {
|
||||
formData.append("replyId", this.replying.id)
|
||||
}
|
||||
formData.append("file", this.file)
|
||||
this.axios
|
||||
.post(
|
||||
process.env.VUE_APP_BASE_URL +
|
||||
"/api/v1/communications/" +
|
||||
this.$route.params.id +
|
||||
"/formData/message",
|
||||
formData,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data"
|
||||
}
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
this.focusInput()
|
||||
this.message = ""
|
||||
this.autoScroll()
|
||||
this.endSend()
|
||||
this.file = null
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e)
|
||||
AjaxErrorHandler(this.$store)(e)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.edit) {
|
||||
this.message = this.edit.content
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.no-focus:focus::before {
|
||||
opacity: 0 !important;
|
||||
}
|
||||
</style>
|
|
@ -3,6 +3,39 @@
|
|||
<v-overlay :value="!$store.state.wsConnected" absolute>
|
||||
<v-progress-circular indeterminate size="64"></v-progress-circular>
|
||||
</v-overlay>
|
||||
<NicknameDialog :nickname="nickname" />
|
||||
<v-menu
|
||||
v-model="context.user.value"
|
||||
:position-x="context.user.x"
|
||||
:position-y="context.user.y"
|
||||
absolute
|
||||
offset-y
|
||||
class="rounded-l"
|
||||
>
|
||||
<v-list class="rounded-l" v-if="context.user.item">
|
||||
<v-list-item
|
||||
v-if="context.user.item.type === 'direct'"
|
||||
@click="
|
||||
nickname.user = context.user.item
|
||||
nickname.dialog = true
|
||||
"
|
||||
>
|
||||
<v-list-item-title>Change Friend Nickname</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="groupSettings(context.user.id)">
|
||||
<v-list-item-title>Group Settings</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item
|
||||
@click="
|
||||
leave.item = context.user.item
|
||||
leave.dialog = true
|
||||
"
|
||||
color="error"
|
||||
>
|
||||
<v-list-item-title>Leave Group</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
<v-dialog
|
||||
v-model="settings.addMembers.dialog"
|
||||
max-width="400px"
|
||||
|
@ -263,6 +296,38 @@
|
|||
</v-card>
|
||||
</v-dialog>
|
||||
<v-app-bar app color="bg">
|
||||
<v-app-bar-nav-icon
|
||||
@click.stop="$store.state.drawer = !$store.state.drawer"
|
||||
v-if="$vuetify.breakpoint.mobile"
|
||||
></v-app-bar-nav-icon>
|
||||
<button
|
||||
style="display: none"
|
||||
v-shortkey="['ctrl', 'k']"
|
||||
@shortkey="$store.commit('setSearch', true)"
|
||||
>
|
||||
Debug
|
||||
</button>
|
||||
<button
|
||||
style="display: none"
|
||||
v-shortkey="['meta', 'k']"
|
||||
@shortkey="$store.commit('setSearch', true)"
|
||||
>
|
||||
Debug
|
||||
</button>
|
||||
<button
|
||||
style="display: none"
|
||||
v-shortkey="['ctrl', 'alt', 'd']"
|
||||
@shortkey="$store.dispatch('toggleCSS')"
|
||||
>
|
||||
Style Toggle
|
||||
</button>
|
||||
<button
|
||||
style="display: none"
|
||||
v-shortkey="['f9']"
|
||||
@shortkey="$store.dispatch('toggleCSS')"
|
||||
>
|
||||
Style Toggle
|
||||
</button>
|
||||
<template v-if="$route.name === 'Communications'">
|
||||
<v-toolbar-title v-if="$store.state.selectedChat?.chat?.type">
|
||||
{{
|
||||
|
@ -271,7 +336,6 @@
|
|||
: $store.state.selectedChat?.chat?.name
|
||||
}}
|
||||
</v-toolbar-title>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn
|
||||
icon
|
||||
|
@ -279,11 +343,7 @@
|
|||
>
|
||||
<v-icon>mdi-magnify</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
icon
|
||||
@click="$store.state.userPanel = !$store.state.userPanel"
|
||||
v-if="selected?.chat?.type === 'group'"
|
||||
>
|
||||
<v-btn icon @click="$store.state.userPanel = !$store.state.userPanel">
|
||||
<v-icon>mdi-account-group</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
@ -293,8 +353,39 @@
|
|||
</v-toolbar-title>
|
||||
</template>
|
||||
</v-app-bar>
|
||||
<v-navigation-drawer color="bg" floating app style="max-height: 100%">
|
||||
<v-navigation-drawer
|
||||
color="bg"
|
||||
floating
|
||||
app
|
||||
style="max-height: 100%"
|
||||
v-model="$store.state.drawer"
|
||||
:width="$vuetify.breakpoint.mobile ? 270 : 320"
|
||||
>
|
||||
<v-container>
|
||||
<v-list dense nav id="comms-sidebar-list">
|
||||
<template v-if="$vuetify.breakpoint.mobile">
|
||||
<v-btn
|
||||
color="toolbar"
|
||||
to="/communications/friends"
|
||||
width="48%"
|
||||
class="mb-3 mr-1 rounded-xl"
|
||||
elevation="2"
|
||||
>
|
||||
<v-icon left>mdi-account-multiple</v-icon>
|
||||
Friends
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="toolbar"
|
||||
width="48%"
|
||||
class="mb-3 ml-1 rounded-xl"
|
||||
elevation="2"
|
||||
@click="dialogs.new = true"
|
||||
>
|
||||
<v-icon left>mdi-plus</v-icon>
|
||||
New
|
||||
</v-btn>
|
||||
</template>
|
||||
<template v-else>
|
||||
<v-btn
|
||||
color="toolbar"
|
||||
to="/communications/friends"
|
||||
|
@ -323,14 +414,15 @@
|
|||
<v-btn icon @click="dialogs.new = true">
|
||||
<v-icon>mdi-plus</v-icon>
|
||||
</v-btn>
|
||||
</v-toolbar>
|
||||
<v-card height="100%" color="transparent" class="mt-2" elevation="0">
|
||||
<v-list height="100%" two-line color="transparent" elevation="0">
|
||||
<v-list-item-group v-model="selected">
|
||||
<template v-for="(item, index) in $store.state.chats">
|
||||
</v-toolbar></template
|
||||
>
|
||||
<v-list v-for="item in $store.state.chats" :key="item.id">
|
||||
<template>
|
||||
<v-list-item
|
||||
:key="item.title"
|
||||
:to="'/communications/' + item.id"
|
||||
@contextmenu="
|
||||
show($event, 'user', getDirectRecipient(item), item.id)
|
||||
"
|
||||
>
|
||||
<v-badge
|
||||
bordered
|
||||
|
@ -339,7 +431,7 @@
|
|||
v-if="item.chat.type === 'direct'"
|
||||
dot
|
||||
offset-x="24"
|
||||
offset-y="26"
|
||||
offset-y="20"
|
||||
>
|
||||
<v-list-item-avatar
|
||||
:color="$vuetify.theme.themes.dark.primary"
|
||||
|
@ -352,7 +444,11 @@
|
|||
item.chat.type === 'direct' &&
|
||||
getDirectRecipient(item).avatar
|
||||
"
|
||||
:src="'/usercontent/' + getDirectRecipient(item).avatar"
|
||||
:src="
|
||||
$store.state.baseURL +
|
||||
'/usercontent/' +
|
||||
getDirectRecipient(item).avatar
|
||||
"
|
||||
/>
|
||||
<v-icon v-else-if="item.chat.type === 'direct'">
|
||||
mdi-account
|
||||
|
@ -371,70 +467,33 @@
|
|||
<template>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title v-if="item.chat.type === 'direct'">
|
||||
{{ getDirectRecipient(item).username }}
|
||||
<v-badge
|
||||
v-if="
|
||||
getLastRead(item).count >= 1 &&
|
||||
$route.params.id !== item.id.toString()
|
||||
"
|
||||
color="red"
|
||||
class="ml-2"
|
||||
:content="getLastRead(item).count"
|
||||
>
|
||||
</v-badge>
|
||||
{{ getDirectRecipient(item).name }}
|
||||
</v-list-item-title>
|
||||
<v-list-item-title v-else>
|
||||
{{ item.chat.name }}
|
||||
<v-badge
|
||||
v-if="
|
||||
getLastRead(item).count >= 1 &&
|
||||
$route.params.id !== item.id.toString()
|
||||
"
|
||||
color="red"
|
||||
class="ml-2"
|
||||
:content="getLastRead(item).count"
|
||||
>
|
||||
</v-badge>
|
||||
<span> {{ item.chat.name }} </span>
|
||||
</v-list-item-title>
|
||||
|
||||
<v-list-item-subtitle v-if="item.chat.type === 'group'">
|
||||
{{ item.chat.users.length }} Members
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
<v-list-item-action>
|
||||
<v-icon
|
||||
@click="
|
||||
leave.item = item
|
||||
leave.dialog = true
|
||||
<v-list-item-action
|
||||
v-if="
|
||||
item.unread >= 1 &&
|
||||
$route.params.id !== item.id.toString()
|
||||
"
|
||||
>
|
||||
mdi-exit-to-app
|
||||
</v-icon>
|
||||
</v-list-item-action>
|
||||
<v-list-item-action>
|
||||
<v-icon
|
||||
@click="
|
||||
settings.item = item
|
||||
settings.dialog = true
|
||||
"
|
||||
>
|
||||
mdi-cog
|
||||
</v-icon>
|
||||
<v-badge color="red" inline :content="item.unread">
|
||||
</v-badge>
|
||||
</v-list-item-action>
|
||||
</template>
|
||||
</v-list-item>
|
||||
|
||||
<v-divider
|
||||
v-if="index < $store.state.chats.length - 1"
|
||||
:key="index"
|
||||
></v-divider>
|
||||
</template>
|
||||
</v-list-item-group>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-list>
|
||||
</v-container>
|
||||
<template v-slot:append>
|
||||
<v-card tile color="card" elevation="0">
|
||||
<v-card tile color="bg" elevation="0">
|
||||
<v-overlay :value="!$store.state.wsConnected" absolute>
|
||||
<v-progress-circular indeterminate size="48"></v-progress-circular>
|
||||
</v-overlay>
|
||||
|
@ -522,11 +581,19 @@
|
|||
|
||||
<script>
|
||||
import AjaxErrorHandler from "@/lib/errorHandler"
|
||||
import NicknameDialog from "@/components/NicknameDialog"
|
||||
import Vue from "vue"
|
||||
|
||||
export default {
|
||||
name: "Header",
|
||||
components: { NicknameDialog },
|
||||
data() {
|
||||
return {
|
||||
nickname: {
|
||||
dialog: false,
|
||||
nickname: "",
|
||||
user: {}
|
||||
},
|
||||
copyTooltip: false,
|
||||
settings: {
|
||||
dialog: false,
|
||||
|
@ -538,6 +605,28 @@ export default {
|
|||
},
|
||||
item: null
|
||||
},
|
||||
context: {
|
||||
user: {
|
||||
value: false,
|
||||
x: null,
|
||||
y: null,
|
||||
item: null,
|
||||
id: 0
|
||||
},
|
||||
userPopout: {
|
||||
value: false,
|
||||
x: null,
|
||||
y: null,
|
||||
item: null,
|
||||
id: 0
|
||||
},
|
||||
message: {
|
||||
value: false,
|
||||
x: null,
|
||||
y: null,
|
||||
item: null
|
||||
}
|
||||
},
|
||||
selected: [2],
|
||||
loading: true,
|
||||
leave: {
|
||||
|
@ -556,6 +645,23 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
groupSettings(id) {
|
||||
this.settings.item = this.$store.state.chats.find(
|
||||
(chat) => chat.id === id
|
||||
)
|
||||
this.settings.dialog = true
|
||||
},
|
||||
show(e, context, item, id) {
|
||||
e.preventDefault()
|
||||
this.context[context].value = false
|
||||
this.context[context].x = e.clientX
|
||||
this.context[context].y = e.clientY
|
||||
this.context[context].item = item
|
||||
this.context[context].id = id
|
||||
this.$nextTick(() => {
|
||||
this.context[context].value = true
|
||||
})
|
||||
},
|
||||
setStatus(status) {
|
||||
const previousStatus = {
|
||||
status: this.$store.state.user.status,
|
||||
|
@ -740,13 +846,30 @@ export default {
|
|||
}
|
||||
},
|
||||
getDirectRecipient(item) {
|
||||
const user = item.chat.users.find(
|
||||
let user = item.chat.users.find(
|
||||
(user) => user.id !== this.$store.state.user.id
|
||||
)
|
||||
if (user) {
|
||||
return user
|
||||
if (user.nickname?.nickname) {
|
||||
user.name = user.nickname.nickname
|
||||
} else {
|
||||
return item.chat.users[0]
|
||||
user.name = user.username
|
||||
}
|
||||
return {
|
||||
...user,
|
||||
type: item.chat.type
|
||||
}
|
||||
} else {
|
||||
let user = item.chat.users[0]
|
||||
if (user.nickname?.nickname) {
|
||||
user.name = user.nickname.nickname
|
||||
} else {
|
||||
user.name = user.username
|
||||
}
|
||||
return {
|
||||
...user,
|
||||
type: item.chat.type
|
||||
}
|
||||
}
|
||||
},
|
||||
createConversation() {
|
||||
|
@ -756,7 +879,6 @@ export default {
|
|||
users: this.newConversation.users
|
||||
})
|
||||
.then(() => {
|
||||
this.getChats()
|
||||
this.newConversation.name = ""
|
||||
this.newConversation.users = []
|
||||
this.newConversation.loading = false
|
||||
|
@ -774,10 +896,10 @@ export default {
|
|||
searchUsers() {
|
||||
this.newConversation.loading = true
|
||||
this.axios
|
||||
.get("/api/v1/communications/search?query=" + this.newConversation.name)
|
||||
.get("/api/v1/communications/search?query=")
|
||||
.then((res) => {
|
||||
this.newConversation.loading = false
|
||||
this.newConversation.results.push(...res.data)
|
||||
this.newConversation.results = res.data
|
||||
})
|
||||
.catch(() => {
|
||||
this.newConversation.loading = false
|
||||
|
@ -785,12 +907,10 @@ export default {
|
|||
},
|
||||
searchUsersForGroupAdmin() {
|
||||
this.axios
|
||||
.get(
|
||||
"/api/v1/communications/search?query=" + this.settings.addMembers.name
|
||||
)
|
||||
.get("/api/v1/communications/search?query=")
|
||||
.then((res) => {
|
||||
if (this.settings.item) {
|
||||
this.settings.addMembers.results.push(...res.data)
|
||||
this.settings.addMembers.results = res.data
|
||||
this.settings.addMembers.results =
|
||||
this.settings.addMembers.results.filter(
|
||||
(user) =>
|
||||
|
@ -802,6 +922,8 @@ export default {
|
|||
}
|
||||
},
|
||||
mounted() {
|
||||
Vue.axios.defaults.headers.common["Authorization"] =
|
||||
localStorage.getItem("session")
|
||||
this.searchUsers()
|
||||
this.searchUsersForGroupAdmin()
|
||||
this.$store.dispatch("getChats")
|
||||
|
@ -834,7 +956,6 @@ export default {
|
|||
)
|
||||
if (chat) {
|
||||
const index = this.$store.state.chats.indexOf(chat)
|
||||
this.$store.state.chats[index].chat.lastMessages.unshift(message)
|
||||
this.$store.state.chats.splice(index, 1)
|
||||
this.$store.state.chats.unshift(chat)
|
||||
}
|
||||
|
|
84
src/components/NicknameDialog.vue
Normal file
84
src/components/NicknameDialog.vue
Normal file
|
@ -0,0 +1,84 @@
|
|||
<template>
|
||||
<v-dialog v-model="nickname.dialog" width="500px">
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<span class="headline">{{ nickname.user.username }}</span>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-text-field
|
||||
v-model="nickname.nickname"
|
||||
label="Nickname"
|
||||
required
|
||||
@keyup.enter="setFriendNickname"
|
||||
></v-text-field>
|
||||
<small>Friend nicknames only show to you.</small>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn
|
||||
color="blue darken-1"
|
||||
text
|
||||
@click="
|
||||
nickname.dialog = false
|
||||
nickname.nickname = ''
|
||||
nickname.user = {}
|
||||
"
|
||||
>
|
||||
Cancel
|
||||
</v-btn>
|
||||
<v-btn color="blue darken-1" text @click="setFriendNickname">
|
||||
Apply
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AjaxErrorHandler from "@/lib/errorHandler"
|
||||
|
||||
export default {
|
||||
name: "NicknameDialog",
|
||||
props: ["nickname"],
|
||||
methods: {
|
||||
setFriendNickname() {
|
||||
this.axios
|
||||
.post(
|
||||
process.env.VUE_APP_BASE_URL +
|
||||
"/api/v1/communications/nickname/" +
|
||||
this.nickname.user.id,
|
||||
{
|
||||
nickname: this.nickname.nickname
|
||||
}
|
||||
)
|
||||
.then((res) => {
|
||||
this.$store.state.chats.forEach((item) => {
|
||||
item.chat.associations.forEach((a) => {
|
||||
if (a.user.id === this.nickname.user.id) {
|
||||
a.user.nickname = {
|
||||
nickname: res.data.nickname
|
||||
}
|
||||
}
|
||||
})
|
||||
item.chat.users.forEach((u) => {
|
||||
if (u.id === this.nickname.user.id) {
|
||||
u.nickname = {
|
||||
nickname: res.data.nickname
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
this.nickname.dialog = false
|
||||
this.nickname.nickname = ""
|
||||
this.nickname.user = {}
|
||||
this.$toast.success("Nickname changed successfully.")
|
||||
})
|
||||
.catch((e) => {
|
||||
AjaxErrorHandler(this.$store)(e)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -2,8 +2,14 @@ module.exports = function (vuex) {
|
|||
return function (res, ignorePathErrorCb) {
|
||||
let errors = []
|
||||
|
||||
if (res.response === undefined || res.response.data.errors === undefined) {
|
||||
if (res.response.status === 429) {
|
||||
if (
|
||||
res?.response === undefined ||
|
||||
res?.response?.data?.errors === undefined
|
||||
) {
|
||||
if (vuex.state.site.release === "dev") {
|
||||
console.error(res)
|
||||
}
|
||||
if (res?.response?.status === 429) {
|
||||
vuex._vm.$toast.error(
|
||||
"You are being rate limited, retry in " +
|
||||
res.response.headers["ratelimit-reset"] +
|
||||
|
|
|
@ -4,17 +4,37 @@ import Vuetify from "../plugins/vuetify"
|
|||
import AjaxErrorHandler from "@/lib/errorHandler"
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
function getDirectRecipient(context, item) {
|
||||
let user = item.chat.users.find((user) => user.id !== context.state.user.id)
|
||||
if (user) {
|
||||
if (user.nickname?.nickname) {
|
||||
user.name = user.nickname.nickname
|
||||
} else {
|
||||
user.name = user.username
|
||||
}
|
||||
return user
|
||||
} else {
|
||||
let user = item.chat.users[0]
|
||||
if (user.nickname?.nickname) {
|
||||
user.name = user.nickname.nickname
|
||||
} else {
|
||||
user.name = user.username
|
||||
}
|
||||
return user
|
||||
}
|
||||
}
|
||||
export default new Vuex.Store({
|
||||
state: {
|
||||
online: true,
|
||||
selectedChat: null,
|
||||
chats: [],
|
||||
baseURL: process.env.VUE_APP_BASE_URL,
|
||||
versioning: {
|
||||
date: process.env.VUE_APP_BUILD_DATE,
|
||||
version: process.env.VUE_APP_VERSION,
|
||||
release: process.env.RELEASE
|
||||
},
|
||||
drawer: true,
|
||||
site: {
|
||||
release: "stable",
|
||||
loading: true
|
||||
|
@ -97,7 +117,7 @@ export default new Vuex.Store({
|
|||
state.modals.search = value
|
||||
},
|
||||
addChat(state, chat) {
|
||||
state.chats.push(chat)
|
||||
state.chats.unshift(chat)
|
||||
},
|
||||
updateQuickSwitchCache(state, value) {
|
||||
state.quickSwitchCache.push({
|
||||
|
@ -119,32 +139,14 @@ export default new Vuex.Store({
|
|||
})
|
||||
},
|
||||
getCommunicationsUnread(context) {
|
||||
Vue.axios.defaults.headers.common["Authorization"] =
|
||||
localStorage.getItem("session")
|
||||
Vue.axios.get("/api/v1/communications").then((res) => {
|
||||
Vue.axios
|
||||
.get(process.env.VUE_APP_BASE_URL + "/api/v1/communications")
|
||||
.then((res) => {
|
||||
context.commit("setChats", res.data)
|
||||
context.dispatch("updateQuickSwitch")
|
||||
context.state.communicationNotifications = 0
|
||||
res.data.forEach((item) => {
|
||||
const message = item.chat.lastMessages.find(
|
||||
(message) => message.id === item.lastRead
|
||||
)
|
||||
const lastMessage = item.chat.lastMessages.find(
|
||||
(message) => message.userId !== context.state.user.id
|
||||
)
|
||||
let count
|
||||
if (message && lastMessage) {
|
||||
const index = item.chat.lastMessages.indexOf(message)
|
||||
const indexLast = item.chat.lastMessages.indexOf(lastMessage)
|
||||
let value = index - indexLast
|
||||
if (value < 0) {
|
||||
value = 0
|
||||
}
|
||||
count = value
|
||||
} else if (!message) {
|
||||
count = item.chat.lastMessages.length
|
||||
} else {
|
||||
count = 0
|
||||
}
|
||||
context.state.communicationNotifications += count
|
||||
context.state.communicationNotifications += item.unread
|
||||
})
|
||||
})
|
||||
},
|
||||
|
@ -323,9 +325,12 @@ export default new Vuex.Store({
|
|||
})
|
||||
context.state.chats.forEach((chat) => {
|
||||
context.commit("updateQuickSwitchCache", {
|
||||
...chat,
|
||||
customType: 2,
|
||||
route: "/communications/" + chat.id
|
||||
id: chat.id,
|
||||
subjectLongName:
|
||||
chat.chat.type === "group"
|
||||
? chat.chat.name
|
||||
: getDirectRecipient(context, chat).name,
|
||||
customType: 3
|
||||
})
|
||||
})
|
||||
},
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -21,8 +21,6 @@
|
|||
<v-list-item-avatar
|
||||
@click="userProfile(user)"
|
||||
style="cursor: pointer"
|
||||
>
|
||||
<v-list-item-avatar
|
||||
:color="$vuetify.theme.themes.dark.primary"
|
||||
>
|
||||
<v-img
|
||||
|
@ -31,7 +29,6 @@
|
|||
/>
|
||||
<v-icon v-else> mdi-account </v-icon>
|
||||
</v-list-item-avatar>
|
||||
</v-list-item-avatar>
|
||||
|
||||
<v-list-item-content
|
||||
@click="userProfile(user)"
|
||||
|
@ -90,17 +87,16 @@
|
|||
v-for="friend in computePendingIncoming"
|
||||
:key="friend.id"
|
||||
>
|
||||
<v-list-item-avatar @click="userProfile(friend.user2)">
|
||||
<v-list-item-avatar
|
||||
@click="userProfile(friend.user2)"
|
||||
:color="$vuetify.theme.themes.dark.primary"
|
||||
>
|
||||
<img
|
||||
<v-img
|
||||
:src="'/usercontent/' + friend.user2.avatar"
|
||||
v-if="friend.user2.avatar"
|
||||
/>
|
||||
<v-icon v-else> mdi-account </v-icon>
|
||||
</v-list-item-avatar>
|
||||
</v-list-item-avatar>
|
||||
|
||||
<v-list-item-content @click="userProfile(friend.user2)">
|
||||
<v-list-item-title>
|
||||
|
@ -136,17 +132,14 @@
|
|||
<v-list-item-avatar
|
||||
@click="userProfile(friend.user2)"
|
||||
style="cursor: pointer"
|
||||
>
|
||||
<v-list-item-avatar
|
||||
:color="$vuetify.theme.themes.dark.primary"
|
||||
>
|
||||
<img
|
||||
<v-img
|
||||
:src="'/usercontent/' + friend.user2.avatar"
|
||||
v-if="friend.user2.avatar"
|
||||
/>
|
||||
<v-icon v-else> mdi-account </v-icon>
|
||||
</v-list-item-avatar>
|
||||
</v-list-item-avatar>
|
||||
|
||||
<v-list-item-content
|
||||
@click="userProfile(friend.user2)"
|
||||
|
@ -175,17 +168,14 @@
|
|||
<v-list-item-avatar
|
||||
@click="userProfile(friend.user2)"
|
||||
style="cursor: pointer"
|
||||
>
|
||||
<v-list-item-avatar
|
||||
:color="$vuetify.theme.themes.dark.primary"
|
||||
>
|
||||
<img
|
||||
<v-img
|
||||
:src="'/usercontent/' + friend.user2.avatar"
|
||||
v-if="friend.user2.avatar"
|
||||
/>
|
||||
<v-icon v-else> mdi-account </v-icon>
|
||||
</v-list-item-avatar>
|
||||
</v-list-item-avatar>
|
||||
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>
|
||||
|
|
|
@ -118,6 +118,7 @@ export default {
|
|||
this.$store.commit("setToken", res.data.session)
|
||||
await this.$store.dispatch("getUserInfo")
|
||||
this.loading = false
|
||||
this.$socket.disconnect()
|
||||
this.$socket.connect()
|
||||
this.$router.push("/")
|
||||
})
|
||||
|
|
|
@ -1,27 +1,5 @@
|
|||
<template>
|
||||
<div id="login" v-if="!$store.state.user?.bcUser?.id">
|
||||
<v-dialog v-model="totpDialog" max-width="500px">
|
||||
<v-card color="card">
|
||||
<v-toolbar color="toolbar">
|
||||
<v-toolbar-title> Two-Factor Authentication </v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<v-container>
|
||||
<v-card-text
|
||||
>You are seeing this because you have enabled Two-Factor
|
||||
Authentication on BetterCompass.<br />
|
||||
Please check your phone, or authenticator app to obtain the 6 digit
|
||||
code and enter it here.</v-card-text
|
||||
>
|
||||
<v-otp-input
|
||||
length="6"
|
||||
@keyup.enter="doLogin()"
|
||||
@finish="doLogin()"
|
||||
v-model="totp"
|
||||
:disabled="loading"
|
||||
></v-otp-input>
|
||||
</v-container>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
<div :class="{ outer: !$vuetify.breakpoint.mobile }">
|
||||
<div :class="{ middle: !$vuetify.breakpoint.mobile }">
|
||||
<div :class="{ innerLogin: !$vuetify.breakpoint.mobile }">
|
||||
|
@ -121,6 +99,7 @@ export default {
|
|||
this.$store.commit("setToken", res.data.session)
|
||||
await this.$store.dispatch("getUserInfo")
|
||||
this.loading = false
|
||||
this.$socket.disconnect()
|
||||
this.$socket.connect()
|
||||
this.$router.push("/")
|
||||
})
|
||||
|
|
|
@ -179,12 +179,7 @@ export default {
|
|||
sheet: "#181818",
|
||||
text: "#000000",
|
||||
dark: "#151515",
|
||||
bg: "#151515",
|
||||
calendarNormalActivity: "#3f51b5",
|
||||
calendarActivityType7: "#f44336",
|
||||
calendarActivityType8: "#4caf50",
|
||||
calendarActivityType10: "#ff9800",
|
||||
calendarExternalActivity: "#2196f3"
|
||||
bg: "#151515"
|
||||
},
|
||||
light: {
|
||||
primary: "#0190ea",
|
||||
|
@ -199,68 +194,14 @@ export default {
|
|||
sheet: "#f8f8f8",
|
||||
text: "#000000",
|
||||
dark: "#f8f8f8",
|
||||
bg: "#f8f8f8",
|
||||
calendarNormalActivity: "#3f51b5",
|
||||
calendarActivityType7: "#f44336",
|
||||
calendarActivityType8: "#4caf50",
|
||||
calendarActivityType10: "#ff9800",
|
||||
calendarExternalActivity: "#2196f3"
|
||||
bg: "#f8f8f8"
|
||||
}
|
||||
},
|
||||
createTheme: false,
|
||||
createThemeCSS: false,
|
||||
name: "",
|
||||
creatorType: "create",
|
||||
themes: [],
|
||||
fakeEvents: [
|
||||
{
|
||||
name: "English",
|
||||
content: "English",
|
||||
color: "#dce6f4",
|
||||
start: new Date().setHours(9, 0, 0, 0),
|
||||
end: new Date().setHours(10, 0, 0, 0),
|
||||
timed: true,
|
||||
activityType: 1,
|
||||
activityId: 0,
|
||||
calendarId: 0
|
||||
},
|
||||
{
|
||||
name: "Maths",
|
||||
content: "Maths",
|
||||
color: "#f4dcdc",
|
||||
start: new Date().setHours(10, 0, 0, 0),
|
||||
end: new Date().setHours(11, 0, 0, 0),
|
||||
timed: true,
|
||||
activityType: 1,
|
||||
activityId: 0,
|
||||
instanceId: 0,
|
||||
calendarId: 0
|
||||
},
|
||||
{
|
||||
name: "Lunch Break",
|
||||
content: "Lunch Break",
|
||||
color: "#dce6f4",
|
||||
start: new Date().setHours(11, 0, 0, 0),
|
||||
end: new Date().setHours(11, 30, 0, 0),
|
||||
timed: true,
|
||||
activityType: 1,
|
||||
activityId: 0,
|
||||
instanceId: 0,
|
||||
calendarId: 0
|
||||
},
|
||||
{
|
||||
name: "Excursion",
|
||||
content: "Excursion",
|
||||
color: "#dce6f4",
|
||||
start: new Date().setHours(11, 0, 0, 0),
|
||||
end: new Date().setHours(13, 30, 0, 0),
|
||||
timed: true,
|
||||
activityType: 1,
|
||||
activityId: 0,
|
||||
instanceId: 0,
|
||||
calendarId: 0
|
||||
}
|
||||
]
|
||||
themes: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -308,33 +249,6 @@ div {
|
|||
document.head.appendChild(style)
|
||||
this.$store.dispatch("saveOnlineSettings")
|
||||
},
|
||||
computeColor(event) {
|
||||
if (event.color === "#003300") {
|
||||
return this.$vuetify.theme.themes[
|
||||
this.$store.state.user.theme || "dark"
|
||||
].calendarActivityType8
|
||||
} else if (event.color === "#133897") {
|
||||
return this.$vuetify.theme.themes[
|
||||
this.$store.state.user.theme || "dark"
|
||||
].calendarExternalActivity
|
||||
} else if (event.activityType === 7 || event.color === "#f4dcdc") {
|
||||
return this.$vuetify.theme.themes[
|
||||
this.$store.state.user.theme || "dark"
|
||||
].calendarActivityType7
|
||||
} else if (event.color === "#dce6f4") {
|
||||
return this.$vuetify.theme.themes[
|
||||
this.$store.state.user.theme || "dark"
|
||||
].calendarNormalActivity
|
||||
} else if (event.activityType === 10) {
|
||||
return this.$vuetify.theme.themes[
|
||||
this.$store.state.user.theme || "dark"
|
||||
].calendarActivityType10
|
||||
} else {
|
||||
return this.$vuetify.theme.themes[
|
||||
this.$store.state.user.theme || "dark"
|
||||
].calendarNormalActivity
|
||||
}
|
||||
},
|
||||
editorInit() {
|
||||
require("brace/ext/language_tools")
|
||||
require("brace/mode/css")
|
||||
|
@ -357,18 +271,7 @@ div {
|
|||
sheet: "#" + Math.floor(Math.random() * 16777215).toString(16),
|
||||
text: "#" + Math.floor(Math.random() * 16777215).toString(16),
|
||||
dark: "#" + Math.floor(Math.random() * 16777215).toString(16),
|
||||
bg: "#" + Math.floor(Math.random() * 16777215).toString(16),
|
||||
calendarNormalActivity:
|
||||
"#" + Math.floor(Math.random() * 16777215).toString(16),
|
||||
calendarActivityType7:
|
||||
"#" + Math.floor(Math.random() * 16777215).toString(16),
|
||||
calendarActivityType8:
|
||||
"#" + Math.floor(Math.random() * 16777215).toString(16),
|
||||
calendarActivityType10:
|
||||
"#" + Math.floor(Math.random() * 16777215).toString(16),
|
||||
calendarExternalActivity: Math.floor(Math.random() * 16777215).toString(
|
||||
16
|
||||
)
|
||||
bg: "#" + Math.floor(Math.random() * 16777215).toString(16)
|
||||
}
|
||||
this.creator.dark = {
|
||||
primary: "#" + Math.floor(Math.random() * 16777215).toString(16),
|
||||
|
@ -383,18 +286,7 @@ div {
|
|||
sheet: "#" + Math.floor(Math.random() * 16777215).toString(16),
|
||||
text: "#" + Math.floor(Math.random() * 16777215).toString(16),
|
||||
dark: "#" + Math.floor(Math.random() * 16777215).toString(16),
|
||||
bg: "#" + Math.floor(Math.random() * 16777215).toString(16),
|
||||
calendarNormalActivity:
|
||||
"#" + Math.floor(Math.random() * 16777215).toString(16),
|
||||
calendarActivityType7:
|
||||
"#" + Math.floor(Math.random() * 16777215).toString(16),
|
||||
calendarActivityType8:
|
||||
"#" + Math.floor(Math.random() * 16777215).toString(16),
|
||||
calendarActivityType10:
|
||||
"#" + Math.floor(Math.random() * 16777215).toString(16),
|
||||
calendarExternalActivity: Math.floor(Math.random() * 16777215).toString(
|
||||
16
|
||||
)
|
||||
bg: "#" + Math.floor(Math.random() * 16777215).toString(16)
|
||||
}
|
||||
},
|
||||
doDeleteTheme(theme) {
|
||||
|
@ -500,17 +392,7 @@ div {
|
|||
a.dispatchEvent(e)
|
||||
},
|
||||
friendlyName(index) {
|
||||
if (index === "calendarNormalActivity") {
|
||||
return "Standard Class"
|
||||
} else if (index === "calendarActivityType7") {
|
||||
return "Relief Event"
|
||||
} else if (index === "calendarActivityType8") {
|
||||
return "Generic Type 8"
|
||||
} else if (index === "calendarActivityType10") {
|
||||
return "Learning Task"
|
||||
} else if (index === "calendarExternalActivity") {
|
||||
return "External Activity"
|
||||
} else if (index === "bg") {
|
||||
if (index === "bg") {
|
||||
return "Background"
|
||||
} else if (index === "dark") {
|
||||
return "Sidebar & Header"
|
||||
|
@ -581,7 +463,6 @@ div {
|
|||
}
|
||||
},
|
||||
mounted() {
|
||||
console.log(document.querySelectorAll(".editor__toolbar"))
|
||||
this.defineAccent = this.$store.state.user.accentColor !== null
|
||||
this.accent = this.$store.state.user.accentColor
|
||||
this.name = this.$vuetify.theme.themes.name
|
||||
|
|
|
@ -1194,6 +1194,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@soda/get-current-script/-/get-current-script-1.0.2.tgz#a53515db25d8038374381b73af20bb4f2e508d87"
|
||||
integrity sha512-T7VNNlYVM1SgQ+VsMYhnDkcGmWhQdL0bDyGm5TlQ3GBXnJscEClUUOKduWTmm2zCnvNLC1hc3JpuXjs/nFOc5w==
|
||||
|
||||
"@tauri-apps/api@^1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-1.0.2.tgz#5228720e35d50fd08df87067dc29e7306c1f7a10"
|
||||
integrity sha512-yuNW0oeJ1/ZA7wNF1KgxhHrSu5viPVzY/UgUczzN5ptLM8dH15Juy5rEGkoHfeXGju90Y/l22hi3BtIrp/za+w==
|
||||
|
||||
"@types/body-parser@*":
|
||||
version "1.19.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0"
|
||||
|
|
Loading…
Reference in a new issue