mirror of
https://github.com/Troplo/Colubrina.git
synced 2024-12-27 00:41:53 +11:00
1.0.6
This commit is contained in:
parent
d80c7a76e6
commit
3d7df1c98a
13 changed files with 224 additions and 103 deletions
|
@ -4,4 +4,5 @@ RELEASE=stable
|
|||
NOTIFICATION=
|
||||
NOTIFICATION_TYPE=info
|
||||
SITE_NAME=Colubrina
|
||||
ALLOW_REGISTRATIONS=true
|
||||
ALLOW_REGISTRATIONS=true
|
||||
PUBLIC_USERS=false
|
|
@ -30,7 +30,8 @@ app.get("/api/v1/state", async (req, res) => {
|
|||
notificationType: process.env.NOTIFICATION_TYPE,
|
||||
latestVersion: require("../frontend/package.json").version,
|
||||
name: process.env.SITE_NAME,
|
||||
allowRegistrations: JSON.parse(process.env.ALLOW_REGISTRATIONS)
|
||||
allowRegistrations: process.env.ALLOW_REGISTRATIONS === "true",
|
||||
publicUsers: process.env.PUBLIC_USERS === "true"
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -348,22 +348,26 @@ router.get("/mutual/:id/groups", auth, async (req, res, next) => {
|
|||
|
||||
router.get("/users", auth, async (req, res, next) => {
|
||||
try {
|
||||
const users = await User.findAll({
|
||||
attributes: [
|
||||
"id",
|
||||
"username",
|
||||
"name",
|
||||
"avatar",
|
||||
"createdAt",
|
||||
"updatedAt",
|
||||
"status",
|
||||
"admin"
|
||||
],
|
||||
where: {
|
||||
banned: false
|
||||
}
|
||||
})
|
||||
res.json(users)
|
||||
if (process.env.PUBLIC_USERS === "true") {
|
||||
const users = await User.findAll({
|
||||
attributes: [
|
||||
"id",
|
||||
"username",
|
||||
"name",
|
||||
"avatar",
|
||||
"createdAt",
|
||||
"updatedAt",
|
||||
"status",
|
||||
"admin"
|
||||
],
|
||||
where: {
|
||||
banned: false
|
||||
}
|
||||
})
|
||||
res.json(users)
|
||||
} else {
|
||||
res.json([])
|
||||
}
|
||||
} catch (err) {
|
||||
next(err)
|
||||
}
|
||||
|
@ -1361,9 +1365,26 @@ router.get("/:id/messages", auth, async (req, res, next) => {
|
|||
]
|
||||
})
|
||||
if (chat) {
|
||||
let or
|
||||
if (parseInt(req.query.offset)) {
|
||||
or = {
|
||||
[Op.or]: [
|
||||
{
|
||||
id: {
|
||||
[Op.lt]: parseInt(req.query.offset)
|
||||
? parseInt(req.query.offset)
|
||||
: 0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
} else {
|
||||
or = {}
|
||||
}
|
||||
const messages = await Message.findAll({
|
||||
where: {
|
||||
chatId: chat.chat.id
|
||||
chatId: chat.chat.id,
|
||||
...or
|
||||
},
|
||||
include: [
|
||||
{
|
||||
|
@ -1422,7 +1443,6 @@ router.get("/:id/messages", auth, async (req, res, next) => {
|
|||
]
|
||||
}
|
||||
],
|
||||
offset: parseInt(req.query.offset) || 0,
|
||||
order: [["id", "DESC"]],
|
||||
limit: 50
|
||||
})
|
||||
|
|
|
@ -192,7 +192,7 @@ router.post("/register", async (req, res, next) => {
|
|||
}
|
||||
}
|
||||
try {
|
||||
if (!JSON.parse(process.env.ALLOW_REGISTRATIONS)) {
|
||||
if (process.env.ALLOW_REGISTRATIONS !== "true") {
|
||||
throw Errors.registrationsDisabled
|
||||
}
|
||||
if (req.body.password.length < 8) {
|
||||
|
|
40
cli/index.js
40
cli/index.js
|
@ -6,7 +6,7 @@ const { Sequelize } = require("sequelize")
|
|||
const argon2 = require("argon2")
|
||||
const axios = require("axios")
|
||||
const os = require("os")
|
||||
const { execSync } = require('child_process');
|
||||
const { execSync } = require("child_process")
|
||||
|
||||
console.log("Troplo/Colubrina CLI")
|
||||
console.log("Colubrina version", require("../frontend/package.json").version)
|
||||
|
@ -15,12 +15,19 @@ async function checkForUpdates() {
|
|||
await axios
|
||||
.get("https://services.troplo.com/api/v1/state", {
|
||||
headers: {
|
||||
"X-Troplo-Project": "colubrina"
|
||||
"X-Troplo-Project": "colubrina",
|
||||
"X-Troplo-Project-Version": require("../frontend/package.json")
|
||||
.version
|
||||
},
|
||||
timeout: 1000
|
||||
})
|
||||
.then((res) => {
|
||||
if (require("../frontend/package.json").version !== res.data.latestVersion) {
|
||||
if (res.data.warning) {
|
||||
console.log(res.data.warning)
|
||||
}
|
||||
if (
|
||||
require("../frontend/package.json").version !== res.data.latestVersion
|
||||
) {
|
||||
console.log("A new version of Colubrina is available!")
|
||||
console.log("Latest version:", res.data.latestVersion)
|
||||
} else {
|
||||
|
@ -139,7 +146,7 @@ async function dbSetup() {
|
|||
}
|
||||
async function runMigrations() {
|
||||
console.log("Running migrations")
|
||||
execSync("cd ../backend && sequelize db:migrate", () => {
|
||||
execSync("cd ../backend && sequelize db:migrate", () => {
|
||||
console.log("Migrations applied")
|
||||
})
|
||||
}
|
||||
|
@ -218,6 +225,12 @@ async function configureDotEnv() {
|
|||
default: false
|
||||
})
|
||||
)
|
||||
setEnvValue(
|
||||
"PUBLIC_USERS",
|
||||
await input.text("Show instance users publicly", {
|
||||
default: false
|
||||
})
|
||||
)
|
||||
setEnvValue("NOTIFICATION", "")
|
||||
setEnvValue("NOTIFICATION_TYPE", "info")
|
||||
setEnvValue("RELEASE", "stable")
|
||||
|
@ -240,8 +253,8 @@ async function init() {
|
|||
execSync("cd ../backend && yarn install --frozen-lockfile", () => {
|
||||
console.log("yarn install complete (backend)")
|
||||
})
|
||||
execSync("cd ../frontend && yarn install --frozen-lockfile", () => {
|
||||
console.log("yarn install complete (frontend)")
|
||||
execSync("cd ../frontend && yarn install --frozen-lockfile", () => {
|
||||
console.log("yarn install complete (frontend)")
|
||||
})
|
||||
if (fs.existsSync(path.join(__dirname, "../backend/.env"))) {
|
||||
const option = await input.confirm(".env already exists, overwrite?", {
|
||||
|
@ -253,7 +266,9 @@ async function init() {
|
|||
} else {
|
||||
await configureDotEnv()
|
||||
}
|
||||
if (fs.existsSync(path.join(__dirname, "../backend/config/config.json"))) {
|
||||
if (
|
||||
fs.existsSync(path.join(__dirname, "../backend/config/config.json"))
|
||||
) {
|
||||
const option = await input.select(
|
||||
`config/config.json already exists. Do you want to overwrite it?`,
|
||||
["Yes", "No"]
|
||||
|
@ -315,11 +330,14 @@ async function init() {
|
|||
await runMigrations()
|
||||
} else if (option === "Check for updates") {
|
||||
await checkForUpdates()
|
||||
} else if(option === "Build frontend for production") {
|
||||
} else if (option === "Build frontend for production") {
|
||||
console.log("Building...")
|
||||
execSync("cd ../frontend && yarn install --frozen-lockfile && yarn build", () => {
|
||||
console.log("yarn build complete")
|
||||
})
|
||||
execSync(
|
||||
"cd ../frontend && yarn install --frozen-lockfile && yarn build",
|
||||
() => {
|
||||
console.log("yarn build complete")
|
||||
}
|
||||
)
|
||||
} else if (option === "Exit") {
|
||||
process.exit(0)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "colubrina-chat",
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.6",
|
||||
"private": true,
|
||||
"author": "Troplo <troplo@troplo.com>",
|
||||
"license": "GPL-3.0",
|
||||
|
|
|
@ -386,8 +386,9 @@
|
|||
{{ $store.state.site.name }} QuickSwitcher
|
||||
</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<v-container>
|
||||
<v-container v-if="$store.state.modals.search">
|
||||
<v-autocomplete
|
||||
@keydown.enter="handleEnter"
|
||||
auto-select-first
|
||||
v-model="search"
|
||||
:items="$store.state.quickSwitchCache"
|
||||
|
@ -405,7 +406,10 @@
|
|||
<v-main>
|
||||
<Header></Header>
|
||||
<v-container
|
||||
v-if="$store.state.site.latestVersion > $store.state.versioning.version"
|
||||
v-if="
|
||||
$store.state.site.latestVersion > $store.state.versioning.version &&
|
||||
$store.state.site.release !== 'dev'
|
||||
"
|
||||
id="update-notify-banner"
|
||||
>
|
||||
<v-alert class="mx-4 rounded-xl" type="info" text dismissible>
|
||||
|
@ -506,6 +510,16 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
handleEnter() {
|
||||
if (
|
||||
!this.searchInput &&
|
||||
this.$store.state.lastRoute &&
|
||||
this.$store.state.lastRoute !== this.$route.path
|
||||
) {
|
||||
this.$router.push(this.$store.state.lastRoute)
|
||||
this.$store.state.modals.search = false
|
||||
}
|
||||
},
|
||||
registerSocket() {
|
||||
if (!this.$store.state.wsRegistered) {
|
||||
this.$store.state.wsRegistered = true
|
||||
|
@ -797,8 +811,9 @@ export default {
|
|||
},
|
||||
deep: true
|
||||
},
|
||||
$route(to) {
|
||||
$route(to, from) {
|
||||
document.title = to.name + " - " + this.$store.state.site.name
|
||||
this.$store.state.lastRoute = from.path
|
||||
},
|
||||
search() {
|
||||
if (this.search) {
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
@keydown.enter.exact.prevent="handleMessage()"
|
||||
v-model="message"
|
||||
@paste="handlePaste"
|
||||
@change="handleChange"
|
||||
rows="1"
|
||||
single-line
|
||||
dense
|
||||
|
@ -112,7 +111,7 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
handleEditMessage() {
|
||||
if(!this.message?.length) {
|
||||
if (!this.message?.length) {
|
||||
this.editLastMessage()
|
||||
}
|
||||
},
|
||||
|
@ -283,6 +282,11 @@ export default {
|
|||
if (this.edit) {
|
||||
this.message = this.edit.content
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
message() {
|
||||
this.handleChange()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -669,7 +669,8 @@ export default {
|
|||
"endEdit",
|
||||
"autoScroll",
|
||||
"index",
|
||||
"show"
|
||||
"show",
|
||||
"setImagePreview"
|
||||
],
|
||||
components: {
|
||||
CommsInput
|
||||
|
|
|
@ -92,6 +92,10 @@ Vue.directive("markdown", {
|
|||
el.innerHTML = md.render(el.innerHTML)
|
||||
}
|
||||
})
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.log("Colubrina is running in development mode.")
|
||||
Vue.config.performance = true
|
||||
}
|
||||
new Vue({
|
||||
router,
|
||||
store,
|
||||
|
|
|
@ -29,6 +29,7 @@ export default new Vuex.Store({
|
|||
selectedChat: null,
|
||||
chats: [],
|
||||
baseURL: process.env.VUE_APP_BASE_URL,
|
||||
lastRoute: null,
|
||||
versioning: {
|
||||
date: process.env.VUE_APP_BUILD_DATE,
|
||||
version: process.env.VUE_APP_VERSION,
|
||||
|
|
|
@ -126,38 +126,37 @@
|
|||
elevation="0"
|
||||
>
|
||||
<v-card-text class="flex-grow-1 overflow-y-auto" id="message-list">
|
||||
<v-list two-line color="card" ref="message-list">
|
||||
<v-card-title v-if="reachedTop">
|
||||
Welcome to the start of
|
||||
{{
|
||||
$store.state.selectedChat?.chat?.type === "direct"
|
||||
? getDirectRecipient($store.state.selectedChat).username
|
||||
: $store.state.selectedChat?.chat?.name
|
||||
}}
|
||||
</v-card-title>
|
||||
<v-progress-circular
|
||||
v-if="loadingMessages"
|
||||
indeterminate
|
||||
size="128"
|
||||
class="justify-center"
|
||||
></v-progress-circular>
|
||||
<template v-for="(message, index) in messages">
|
||||
<Message
|
||||
:key="message.keyId"
|
||||
:message="message"
|
||||
:jump-to-message="jumpToMessage"
|
||||
:edit="edit"
|
||||
:focus-input="focusInput"
|
||||
:replying="setReply"
|
||||
:get-name="getName"
|
||||
:end-edit="endEdit"
|
||||
:auto-scroll="autoScroll"
|
||||
:chat="chat"
|
||||
:index="index"
|
||||
:show="show"
|
||||
></Message>
|
||||
</template>
|
||||
</v-list>
|
||||
<v-card-title v-if="reachedTop">
|
||||
Welcome to the start of
|
||||
{{
|
||||
$store.state.selectedChat?.chat?.type === "direct"
|
||||
? getDirectRecipient($store.state.selectedChat).username
|
||||
: $store.state.selectedChat?.chat?.name
|
||||
}}
|
||||
</v-card-title>
|
||||
<v-progress-circular
|
||||
v-if="loadingMessages"
|
||||
indeterminate
|
||||
size="64"
|
||||
style="display: block; width: 100px; margin: 0 auto"
|
||||
></v-progress-circular>
|
||||
<template v-for="(message, index) in messages">
|
||||
<Message
|
||||
:key="message.keyId"
|
||||
:message="message"
|
||||
:jump-to-message="jumpToMessage"
|
||||
:edit="edit"
|
||||
:focus-input="focusInput"
|
||||
:replying="setReply"
|
||||
:get-name="getName"
|
||||
:end-edit="endEdit"
|
||||
:auto-scroll="autoScroll"
|
||||
:chat="chat"
|
||||
:index="index"
|
||||
:show="show"
|
||||
:set-image-preview="setImagePreview"
|
||||
></Message>
|
||||
</template>
|
||||
</v-card-text>
|
||||
<v-card-text>
|
||||
<v-toolbar
|
||||
|
@ -167,7 +166,6 @@
|
|||
color="card"
|
||||
v-if="replying"
|
||||
style="cursor: pointer; overflow: hidden"
|
||||
class="mb-2"
|
||||
>
|
||||
<v-icon class="mr-2">mdi-reply</v-icon>
|
||||
<v-avatar size="24" class="mr-2">
|
||||
|
@ -194,17 +192,29 @@
|
|||
<v-icon> mdi-close </v-icon>
|
||||
</v-btn>
|
||||
</v-toolbar>
|
||||
<v-toolbar
|
||||
height="29"
|
||||
color="transparent"
|
||||
elevation="0"
|
||||
style="margin-bottom: -12px; padding-top: 0"
|
||||
>
|
||||
<p v-if="usersTyping.length" style="float: left">
|
||||
{{ usersTyping.map((user) => getName(user)).join(", ") }}
|
||||
{{ usersTyping.length > 1 ? " are" : " is" }} typing...
|
||||
</p>
|
||||
</v-toolbar>
|
||||
<v-fade-transition v-model="avoidAutoScroll">
|
||||
<v-toolbar
|
||||
height="22"
|
||||
color="toolbar"
|
||||
elevation="0"
|
||||
style="
|
||||
border-radius: 20px 20px 0 0;
|
||||
cursor: pointer;
|
||||
z-index: 50;
|
||||
position: relative;
|
||||
top: -30px;
|
||||
margin-bottom: -27px;
|
||||
"
|
||||
width="100%"
|
||||
@click="forceBottom"
|
||||
v-if="avoidAutoScroll"
|
||||
>
|
||||
<div>
|
||||
<v-icon size="16px"> mdi-arrow-down </v-icon>
|
||||
Jump to bottom...
|
||||
</div>
|
||||
</v-toolbar>
|
||||
</v-fade-transition>
|
||||
<CommsInput
|
||||
:chat="chat"
|
||||
:replying="replying"
|
||||
|
@ -212,6 +222,27 @@
|
|||
:autoScroll="autoScroll"
|
||||
:endSend="endSend"
|
||||
></CommsInput>
|
||||
<v-fade-transition v-model="usersTyping.length">
|
||||
<v-toolbar
|
||||
height="22"
|
||||
elevation="0"
|
||||
style="
|
||||
border-radius: 0 0 20px 20px;
|
||||
position: relative;
|
||||
margin-bottom: -2px;
|
||||
margin-top: -20px;
|
||||
bottom: -14px;
|
||||
"
|
||||
width="100%"
|
||||
color="toolbar"
|
||||
v-if="usersTyping.length"
|
||||
>
|
||||
<div style="overflow: hidden">
|
||||
{{ usersTyping.map((user) => getName(user)).join(", ") }}
|
||||
{{ usersTyping.length > 1 ? " are" : " is" }} typing...
|
||||
</div>
|
||||
</v-toolbar>
|
||||
</v-fade-transition>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
@ -812,6 +843,10 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
forceBottom() {
|
||||
this.avoidAutoScroll = false
|
||||
this.autoScroll()
|
||||
},
|
||||
getDirectRecipient(item) {
|
||||
let user = item.chat.users.find(
|
||||
(user) => user.id !== this.$store.state.user.id
|
||||
|
@ -841,7 +876,9 @@ export default {
|
|||
},
|
||||
async scrollEvent(e) {
|
||||
this.avoidAutoScroll =
|
||||
e.target.scrollTop + e.target.offsetHeight !== e.target.scrollHeight
|
||||
e.target.scrollHeight -
|
||||
Math.round(e.target.scrollTop + e.target.offsetHeight) >
|
||||
50
|
||||
if (
|
||||
e.target.scrollTop === 0 &&
|
||||
!this.rateLimit &&
|
||||
|
@ -1077,26 +1114,32 @@ export default {
|
|||
this.edit.id = ""
|
||||
this.focusInput()
|
||||
},
|
||||
autoScroll() {
|
||||
autoScroll(smooth = false) {
|
||||
if (!this.avoidAutoScroll) {
|
||||
this.$nextTick(() => {
|
||||
try {
|
||||
const lastIndex = this.messages.length - 1
|
||||
const lastMessage = document.querySelector(`#message-${lastIndex}`)
|
||||
try {
|
||||
const lastIndex = this.messages.length - 1
|
||||
const lastMessage = document.querySelector(`#message-${lastIndex}`)
|
||||
if (smooth) {
|
||||
lastMessage.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "nearest",
|
||||
inline: "start"
|
||||
})
|
||||
} else {
|
||||
lastMessage.scrollIntoView()
|
||||
this.autoScrollRetry = 0
|
||||
} catch (e) {
|
||||
console.log("Could not auto scroll, retrying...")
|
||||
if (this.autoScrollRetry < 20) {
|
||||
setTimeout(() => {
|
||||
this.autoScroll()
|
||||
}, 50)
|
||||
this.autoScrollRetry++
|
||||
} else {
|
||||
console.log("Could not auto scroll, retry limit reached")
|
||||
}
|
||||
}
|
||||
})
|
||||
this.autoScrollRetry = 0
|
||||
} catch (e) {
|
||||
console.log("Could not auto scroll, retrying...")
|
||||
if (this.autoScrollRetry < 20) {
|
||||
setTimeout(() => {
|
||||
this.autoScroll()
|
||||
}, 50)
|
||||
this.autoScrollRetry++
|
||||
} else {
|
||||
console.log("Could not auto scroll, retry limit reached")
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
getMessages() {
|
||||
|
@ -1107,7 +1150,7 @@ export default {
|
|||
"/api/v1/communications/" +
|
||||
this.$route.params.id +
|
||||
"/messages?limit=50&offset=" +
|
||||
this.offset
|
||||
this.messages[0]?.id || 0
|
||||
)
|
||||
.then((res) => {
|
||||
if (!res.data.length) {
|
||||
|
@ -1132,9 +1175,15 @@ export default {
|
|||
"/read"
|
||||
)
|
||||
this.markAsRead()
|
||||
},
|
||||
focusKey() {
|
||||
if (document.activeElement.tagName === "BODY") {
|
||||
this.focusInput()
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener("keypress", this.focusKey)
|
||||
document
|
||||
.getElementById("message-list")
|
||||
.addEventListener("scroll", this.scrollEvent)
|
||||
|
@ -1231,6 +1280,10 @@ export default {
|
|||
this.offset = 0
|
||||
this.getMessages()
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
document.removeEventListener("keypress", this.focusKey)
|
||||
document.removeEventListener("scroll", this.scrollEvent)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<v-toolbar-title> Users </v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<v-card color="card" elevation="0">
|
||||
<v-list color="card">
|
||||
<v-list color="card" v-if="$store.state.site.publicUsers">
|
||||
<v-list-item v-for="user in users" :key="user.id">
|
||||
<v-list-item-avatar
|
||||
@click="userProfile(user)"
|
||||
|
@ -68,6 +68,9 @@
|
|||
</v-list-item-action>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<v-card-title v-else
|
||||
>Public users are not enabled on this instance.</v-card-title
|
||||
>
|
||||
</v-card>
|
||||
</v-card>
|
||||
</v-tab-item>
|
||||
|
|
Loading…
Reference in a new issue