Merge branch 'master' into feature/socks-support
This commit is contained in:
commit
d106889127
@ -2,7 +2,7 @@
|
|||||||
root: true
|
root: true
|
||||||
|
|
||||||
parserOptions:
|
parserOptions:
|
||||||
ecmaVersion: 2018
|
ecmaVersion: 2020
|
||||||
|
|
||||||
env:
|
env:
|
||||||
es6: true
|
es6: true
|
||||||
|
2
.github/ISSUE_TEMPLATE/Bug_Report.md
vendored
2
.github/ISSUE_TEMPLATE/Bug_Report.md
vendored
@ -4,7 +4,7 @@ about: Create a bug report
|
|||||||
labels: "Type: Bug"
|
labels: "Type: Bug"
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- Have a question? Join #thelounge on freenode -->
|
<!-- Have a question? Join #thelounge on Libera.Chat -->
|
||||||
|
|
||||||
- _Node version:_
|
- _Node version:_
|
||||||
- _Browser version:_
|
- _Browser version:_
|
||||||
|
2
.github/ISSUE_TEMPLATE/Feature_Request.md
vendored
2
.github/ISSUE_TEMPLATE/Feature_Request.md
vendored
@ -4,7 +4,7 @@ about: Request a new feature
|
|||||||
labels: "Type: Feature"
|
labels: "Type: Feature"
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- Have a question? Join #thelounge on freenode. -->
|
<!-- Have a question? Join #thelounge on Libera.Chat. -->
|
||||||
<!-- Make sure to check the existing issues prior to submitting your suggestion. -->
|
<!-- Make sure to check the existing issues prior to submitting your suggestion. -->
|
||||||
|
|
||||||
### Feature Description
|
### Feature Description
|
||||||
|
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -13,4 +13,4 @@ contact_links:
|
|||||||
|
|
||||||
- name: General support
|
- name: General support
|
||||||
url: https://demo.thelounge.chat/?join=%23thelounge
|
url: https://demo.thelounge.chat/?join=%23thelounge
|
||||||
about: "Join #thelounge on Freenode to ask a question before creating an issue"
|
about: "Join #thelounge on Libera.Chat to ask a question before creating an issue"
|
||||||
|
2
.github/SUPPORT.md
vendored
2
.github/SUPPORT.md
vendored
@ -6,6 +6,6 @@ need help, you have a few options:
|
|||||||
- Check out [existing questions on Stack Overflow](https://stackoverflow.com/questions/tagged/thelounge)
|
- Check out [existing questions on Stack Overflow](https://stackoverflow.com/questions/tagged/thelounge)
|
||||||
to see if yours has been answered before. If not, feel free to [ask for a new question](https://stackoverflow.com/questions/ask?tags=thelounge)
|
to see if yours has been answered before. If not, feel free to [ask for a new question](https://stackoverflow.com/questions/ask?tags=thelounge)
|
||||||
(using `thelounge` tag so that other people can easily find it).
|
(using `thelounge` tag so that other people can easily find it).
|
||||||
- Find us on the Freenode channel `#thelounge`. You might not get an answer
|
- Find us on the Libera.Chat channel `#thelounge`. You might not get an answer
|
||||||
right away, but this channel is full of nice people who will be happy to
|
right away, but this channel is full of nice people who will be happy to
|
||||||
help you.
|
help you.
|
||||||
|
15
CHANGELOG.md
15
CHANGELOG.md
@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
<!-- New entries go after this line -->
|
<!-- New entries go after this line -->
|
||||||
|
|
||||||
|
## v4.3.0-pre.2 - 2021-06-07 [Pre-release]
|
||||||
|
|
||||||
|
[See the full changelog](https://github.com/thelounge/thelounge/compare/v4.3.0-pre.1...v4.3.0-pre.2)
|
||||||
|
|
||||||
|
This is a pre-release for v4.3.0 to offer latest changes without having to wait for a stable release.
|
||||||
|
At this stage, features may still be added or modified until the first release candidate for this version gets released.
|
||||||
|
|
||||||
|
Please refer to the commit list given above for a complete list of changes, or wait for the stable release to get a thoroughly prepared change log entry.
|
||||||
|
|
||||||
|
As with all pre-releases, this version requires explicit use of the `next` tag to be installed:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yarn global add thelounge@next
|
||||||
|
```
|
||||||
|
|
||||||
## v4.3.0-pre.1 - 2021-03-02 [Pre-release]
|
## v4.3.0-pre.1 - 2021-03-02 [Pre-release]
|
||||||
|
|
||||||
[See the full changelog](https://github.com/thelounge/thelounge/compare/v4.2.0...v4.3.0-pre.1)
|
[See the full changelog](https://github.com/thelounge/thelounge/compare/v4.2.0...v4.3.0-pre.1)
|
||||||
|
@ -20,8 +20,8 @@
|
|||||||
</p>
|
</p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://demo.thelounge.chat/"><img
|
<a href="https://demo.thelounge.chat/"><img
|
||||||
alt="#thelounge IRC channel on freenode"
|
alt="#thelounge IRC channel on Libera.Chat"
|
||||||
src="https://img.shields.io/badge/freenode-%23thelounge-415364.svg?colorA=ff9e18"></a>
|
src="https://img.shields.io/badge/Libera.Chat-%23thelounge-415364.svg?colorA=ff9e18"></a>
|
||||||
<a href="https://yarn.pm/thelounge"><img
|
<a href="https://yarn.pm/thelounge"><img
|
||||||
alt="npm version"
|
alt="npm version"
|
||||||
src="https://img.shields.io/npm/v/thelounge.svg?colorA=333a41&maxAge=3600"></a>
|
src="https://img.shields.io/npm/v/thelounge.svg?colorA=333a41&maxAge=3600"></a>
|
||||||
|
@ -4,6 +4,6 @@
|
|||||||
- Contact us privately first, in a
|
- Contact us privately first, in a
|
||||||
[responsible disclosure](https://en.wikipedia.org/wiki/Responsible_disclosure)
|
[responsible disclosure](https://en.wikipedia.org/wiki/Responsible_disclosure)
|
||||||
manner.
|
manner.
|
||||||
- On IRC, send a private message to any voiced user on our Freenode channel,
|
- On IRC, send a private message to any voiced user on our Libera.Chat channel,
|
||||||
`#thelounge`.
|
`#thelounge`.
|
||||||
- By email, send us your report at <security@thelounge.chat>.
|
- By email, send us your report at <security@thelounge.chat>.
|
||||||
|
@ -51,6 +51,7 @@ export default {
|
|||||||
Mousetrap.bind("esc", this.escapeKey);
|
Mousetrap.bind("esc", this.escapeKey);
|
||||||
Mousetrap.bind("alt+u", this.toggleUserList);
|
Mousetrap.bind("alt+u", this.toggleUserList);
|
||||||
Mousetrap.bind("alt+s", this.toggleSidebar);
|
Mousetrap.bind("alt+s", this.toggleSidebar);
|
||||||
|
Mousetrap.bind("alt+m", this.toggleMentions);
|
||||||
|
|
||||||
// Make a single throttled resize listener available to all components
|
// Make a single throttled resize listener available to all components
|
||||||
this.debouncedResize = throttle(() => {
|
this.debouncedResize = throttle(() => {
|
||||||
@ -72,6 +73,7 @@ export default {
|
|||||||
Mousetrap.unbind("esc", this.escapeKey);
|
Mousetrap.unbind("esc", this.escapeKey);
|
||||||
Mousetrap.unbind("alt+u", this.toggleUserList);
|
Mousetrap.unbind("alt+u", this.toggleUserList);
|
||||||
Mousetrap.unbind("alt+s", this.toggleSidebar);
|
Mousetrap.unbind("alt+s", this.toggleSidebar);
|
||||||
|
Mousetrap.unbind("alt+m", this.toggleMentions);
|
||||||
|
|
||||||
window.removeEventListener("resize", this.debouncedResize);
|
window.removeEventListener("resize", this.debouncedResize);
|
||||||
clearTimeout(this.dayChangeTimeout);
|
clearTimeout(this.dayChangeTimeout);
|
||||||
@ -98,6 +100,11 @@ export default {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
toggleMentions() {
|
||||||
|
if (this.$store.state.networks.length !== 0) {
|
||||||
|
eventbus.emit("mentions:toggle");
|
||||||
|
}
|
||||||
|
},
|
||||||
msUntilNextDay() {
|
msUntilNextDay() {
|
||||||
// Compute how many milliseconds are remaining until the next day starts
|
// Compute how many milliseconds are remaining until the next day starts
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
|
@ -183,6 +183,10 @@ export default {
|
|||||||
},
|
},
|
||||||
setInputSize() {
|
setInputSize() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
|
if (!this.$refs.input) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const style = window.getComputedStyle(this.$refs.input);
|
const style = window.getComputedStyle(this.$refs.input);
|
||||||
const lineHeight = parseFloat(style.lineHeight, 10) || 1;
|
const lineHeight = parseFloat(style.lineHeight, 10) || 1;
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<input
|
<input
|
||||||
ref="searchInputField"
|
ref="searchInputField"
|
||||||
v-model="searchInput"
|
v-model="searchInput"
|
||||||
type="text"
|
type="search"
|
||||||
name="search"
|
name="search"
|
||||||
class="input"
|
class="input"
|
||||||
placeholder="Search messages…"
|
placeholder="Search messages…"
|
||||||
@ -104,6 +104,10 @@ export default {
|
|||||||
mounted() {
|
mounted() {
|
||||||
this.searchInput = this.$route.query.q;
|
this.searchInput = this.$route.query.q;
|
||||||
this.searchOpened = this.onSearchPage;
|
this.searchOpened = this.onSearchPage;
|
||||||
|
|
||||||
|
if (!this.searchInput && this.searchOpened) {
|
||||||
|
this.$refs.searchInputField.focus();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
closeSearch() {
|
closeSearch() {
|
||||||
|
@ -6,17 +6,20 @@
|
|||||||
>username to <b>{{ message.new_ident }}</b></span
|
>username to <b>{{ message.new_ident }}</b></span
|
||||||
>
|
>
|
||||||
<span v-if="message.new_host"
|
<span v-if="message.new_host"
|
||||||
>hostname to <i class="hostmask">{{ message.new_host }}</i></span
|
>hostname to
|
||||||
>
|
<i class="hostmask"><ParsedMessage :network="network" :text="message.new_host" /></i
|
||||||
|
></span>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import ParsedMessage from "../ParsedMessage.vue";
|
||||||
import Username from "../Username.vue";
|
import Username from "../Username.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "MessageTypeChangeHost",
|
name: "MessageTypeChangeHost",
|
||||||
components: {
|
components: {
|
||||||
|
ParsedMessage,
|
||||||
Username,
|
Username,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<span class="content">
|
<span class="content">
|
||||||
<Username :user="message.from" />
|
<Username :user="message.from" />
|
||||||
<i class="hostmask"> ({{ message.hostmask }})</i>
|
<i class="hostmask"> (<ParsedMessage :network="network" :text="message.hostmask" />)</i>
|
||||||
<template v-if="message.account">
|
<template v-if="message.account">
|
||||||
<i class="account"> [{{ message.account }}]</i>
|
<i class="account"> [{{ message.account }}]</i>
|
||||||
</template>
|
</template>
|
||||||
@ -13,11 +13,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import ParsedMessage from "../ParsedMessage.vue";
|
||||||
import Username from "../Username.vue";
|
import Username from "../Username.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "MessageTypeJoin",
|
name: "MessageTypeJoin",
|
||||||
components: {
|
components: {
|
||||||
|
ParsedMessage,
|
||||||
Username,
|
Username,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<span class="content">
|
<span class="content">
|
||||||
<Username :user="message.from" />
|
<Username :user="message.from" />
|
||||||
<i class="hostmask"> ({{ message.hostmask }})</i> has left the channel
|
<i class="hostmask"> (<ParsedMessage :network="network" :text="message.hostmask" />)</i> has
|
||||||
|
left the channel
|
||||||
<i v-if="message.text" class="part-reason"
|
<i v-if="message.text" class="part-reason"
|
||||||
>(<ParsedMessage :network="network" :message="message" />)</i
|
>(<ParsedMessage :network="network" :message="message" />)</i
|
||||||
>
|
>
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<span class="content">
|
<span class="content">
|
||||||
<Username :user="message.from" />
|
<Username :user="message.from" />
|
||||||
<i class="hostmask"> ({{ message.hostmask }})</i> has quit
|
<i class="hostmask"> (<ParsedMessage :network="network" :text="message.hostmask" />)</i> has
|
||||||
|
quit
|
||||||
<i v-if="message.text" class="quit-reason"
|
<i v-if="message.text" class="quit-reason"
|
||||||
>(<ParsedMessage :network="network" :message="message" />)</i
|
>(<ParsedMessage :network="network" :message="message" />)</i
|
||||||
>
|
>
|
||||||
|
@ -12,7 +12,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<dt>Host mask:</dt>
|
<dt>Host mask:</dt>
|
||||||
<dd class="hostmask">{{ message.whois.ident }}@{{ message.whois.hostname }}</dd>
|
<dd class="hostmask">
|
||||||
|
<ParsedMessage
|
||||||
|
:network="network"
|
||||||
|
:text="message.whois.ident + '@' + message.whois.hostname"
|
||||||
|
/>
|
||||||
|
</dd>
|
||||||
|
|
||||||
<template v-if="message.whois.actual_hostname">
|
<template v-if="message.whois.actual_hostname">
|
||||||
<dt>Actual host:</dt>
|
<dt>Actual host:</dt>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="ban in channel.data" :key="ban.hostmask">
|
<tr v-for="ban in channel.data" :key="ban.hostmask">
|
||||||
<td class="hostmask">{{ ban.hostmask }}</td>
|
<td class="hostmask"><ParsedMessage :network="network" :text="ban.hostmask" /></td>
|
||||||
<td class="banned_by">{{ ban.banned_by }}</td>
|
<td class="banned_by">{{ ban.banned_by }}</td>
|
||||||
<td class="banned_at">{{ localetime(ban.banned_at) }}</td>
|
<td class="banned_at">{{ localetime(ban.banned_at) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -18,10 +18,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import ParsedMessage from "../ParsedMessage.vue";
|
||||||
import localetime from "../../js/helpers/localetime";
|
import localetime from "../../js/helpers/localetime";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "ListBans",
|
name: "ListBans",
|
||||||
|
components: {
|
||||||
|
ParsedMessage,
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
network: Object,
|
network: Object,
|
||||||
channel: Object,
|
channel: Object,
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="user in channel.data" :key="user.hostmask">
|
<tr v-for="user in channel.data" :key="user.hostmask">
|
||||||
<td class="hostmask">{{ user.hostmask }}</td>
|
<td class="hostmask"><ParsedMessage :network="network" :text="user.hostmask" /></td>
|
||||||
<td class="when">{{ localetime(user.when) }}</td>
|
<td class="when">{{ localetime(user.when) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -16,10 +16,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import ParsedMessage from "../ParsedMessage.vue";
|
||||||
import localetime from "../../js/helpers/localetime";
|
import localetime from "../../js/helpers/localetime";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "ListIgnored",
|
name: "ListIgnored",
|
||||||
|
components: {
|
||||||
|
ParsedMessage,
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
network: Object,
|
network: Object,
|
||||||
channel: Object,
|
channel: Object,
|
||||||
|
@ -9,7 +9,9 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="invite in channel.data" :key="invite.hostmask">
|
<tr v-for="invite in channel.data" :key="invite.hostmask">
|
||||||
<td class="hostmask">{{ invite.hostmask }}</td>
|
<td class="hostmask">
|
||||||
|
<ParsedMessage :network="network" :text="invite.hostmask" />
|
||||||
|
</td>
|
||||||
<td class="invitened_by">{{ invite.invited_by }}</td>
|
<td class="invitened_by">{{ invite.invited_by }}</td>
|
||||||
<td class="invitened_at">{{ localetime(invite.invited_at) }}</td>
|
<td class="invitened_at">{{ localetime(invite.invited_at) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -18,10 +20,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import ParsedMessage from "../ParsedMessage.vue";
|
||||||
import localetime from "../../js/helpers/localetime";
|
import localetime from "../../js/helpers/localetime";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "ListInvites",
|
name: "ListInvites",
|
||||||
|
components: {
|
||||||
|
ParsedMessage,
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
network: Object,
|
network: Object,
|
||||||
channel: Object,
|
channel: Object,
|
||||||
|
@ -189,6 +189,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="help-item">
|
||||||
|
<div class="subject">
|
||||||
|
<span v-if="!isApple"><kbd>Alt</kbd> <kbd>M</kbd></span>
|
||||||
|
<span v-else><kbd>⌥</kbd> <kbd>M</kbd></span>
|
||||||
|
</div>
|
||||||
|
<div class="description">
|
||||||
|
<p>Toggle recent mentions popup.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="help-item">
|
<div class="help-item">
|
||||||
<div class="subject">
|
<div class="subject">
|
||||||
<span><kbd>Esc</kbd></span>
|
<span><kbd>Esc</kbd></span>
|
||||||
@ -673,6 +683,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="help-item">
|
||||||
|
<div class="subject">
|
||||||
|
<code>/search query</code>
|
||||||
|
</div>
|
||||||
|
<div class="description">
|
||||||
|
<p>Search for messages in the current channel / user</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="help-item">
|
<div class="help-item">
|
||||||
<div class="subject">
|
<div class="subject">
|
||||||
<code>/topic [newtopic]</code>
|
<code>/topic [newtopic]</code>
|
||||||
|
@ -1195,7 +1195,6 @@ textarea.input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#chat .show-more {
|
#chat .show-more {
|
||||||
margin-top: 50px;
|
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
@ -1508,8 +1507,11 @@ textarea.input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#chat .msg[data-type="notice"] .time,
|
#chat .msg[data-type="notice"] .time,
|
||||||
|
#chat .msg[data-type="wallops"] .time,
|
||||||
#chat .msg[data-type="notice"] .content,
|
#chat .msg[data-type="notice"] .content,
|
||||||
#chat .msg[data-type="notice"] .user {
|
#chat .msg[data-type="wallops"] .content,
|
||||||
|
#chat .msg[data-type="notice"] .user,
|
||||||
|
#chat .msg[data-type="wallops"] .user {
|
||||||
color: #0074d9;
|
color: #0074d9;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1517,6 +1519,10 @@ textarea.input {
|
|||||||
content: "Notice: ";
|
content: "Notice: ";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#chat .msg[data-type="wallops"] .from .user::before {
|
||||||
|
content: "Wallops: ";
|
||||||
|
}
|
||||||
|
|
||||||
#chat .msg[data-type="error"],
|
#chat .msg[data-type="error"],
|
||||||
#chat .msg[data-type="error"] .from {
|
#chat .msg[data-type="error"] .from {
|
||||||
color: #e74c3c;
|
color: #e74c3c;
|
||||||
|
20
client/js/commands/search.js
Normal file
20
client/js/commands/search.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
import store from "../store";
|
||||||
|
import {router} from "../router";
|
||||||
|
|
||||||
|
function input(args) {
|
||||||
|
router.push({
|
||||||
|
name: "SearchResults",
|
||||||
|
params: {
|
||||||
|
id: store.state.activeChannel.channel.id,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
q: args.join(" "),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {input};
|
@ -6,17 +6,24 @@ import socket from "../socket";
|
|||||||
import store from "../store";
|
import store from "../store";
|
||||||
|
|
||||||
socket.on("more", function (data) {
|
socket.on("more", function (data) {
|
||||||
const channel = store.getters.findChannel(data.chan);
|
const channel = store.getters.findChannel(data.chan)?.channel;
|
||||||
|
|
||||||
if (!channel) {
|
if (!channel) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
channel.channel.moreHistoryAvailable =
|
channel.inputHistory = channel.inputHistory.concat(
|
||||||
data.totalMessages > channel.channel.messages.length + data.messages.length;
|
data.messages
|
||||||
channel.channel.messages.unshift(...data.messages);
|
.filter((m) => m.self && m.text && m.type === "message")
|
||||||
|
.map((m) => m.text)
|
||||||
|
.reverse()
|
||||||
|
.slice(null, 100 - channel.inputHistory.length)
|
||||||
|
);
|
||||||
|
channel.moreHistoryAvailable =
|
||||||
|
data.totalMessages > channel.messages.length + data.messages.length;
|
||||||
|
channel.messages.unshift(...data.messages);
|
||||||
|
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
channel.channel.historyLoading = false;
|
channel.historyLoading = false;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -190,7 +190,14 @@ const store = new Vuex.Store({
|
|||||||
// TODO: This should be a mutation
|
// TODO: This should be a mutation
|
||||||
channel.pendingMessage = "";
|
channel.pendingMessage = "";
|
||||||
channel.inputHistoryPosition = 0;
|
channel.inputHistoryPosition = 0;
|
||||||
channel.inputHistory = [""];
|
|
||||||
|
channel.inputHistory = [""].concat(
|
||||||
|
channel.messages
|
||||||
|
.filter((m) => m.self && m.text && m.type === "message")
|
||||||
|
.map((m) => m.text)
|
||||||
|
.reverse()
|
||||||
|
.slice(null, 99)
|
||||||
|
);
|
||||||
channel.historyLoading = false;
|
channel.historyLoading = false;
|
||||||
channel.scrolledToBottom = true;
|
channel.scrolledToBottom = true;
|
||||||
channel.editTopic = false;
|
channel.editTopic = false;
|
||||||
|
@ -231,12 +231,12 @@ module.exports = {
|
|||||||
// - `join`: Comma-separated list of channels to auto-join once connected.
|
// - `join`: Comma-separated list of channels to auto-join once connected.
|
||||||
//
|
//
|
||||||
// This value is set to connect to the official channel of The Lounge on
|
// This value is set to connect to the official channel of The Lounge on
|
||||||
// Freenode by default:
|
// Libera.Chat by default:
|
||||||
//
|
//
|
||||||
// ```js
|
// ```js
|
||||||
// defaults: {
|
// defaults: {
|
||||||
// name: "Freenode",
|
// name: "Libera.Chat",
|
||||||
// host: "chat.freenode.net",
|
// host: "irc.libera.chat",
|
||||||
// port: 6697,
|
// port: 6697,
|
||||||
// password: "",
|
// password: "",
|
||||||
// tls: true,
|
// tls: true,
|
||||||
@ -248,8 +248,8 @@ module.exports = {
|
|||||||
// }
|
// }
|
||||||
// ```
|
// ```
|
||||||
defaults: {
|
defaults: {
|
||||||
name: "Freenode",
|
name: "Libera.Chat",
|
||||||
host: "chat.freenode.net",
|
host: "irc.libera.chat",
|
||||||
port: 6697,
|
port: 6697,
|
||||||
password: "",
|
password: "",
|
||||||
tls: true,
|
tls: true,
|
||||||
|
16
package.json
16
package.json
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "thelounge",
|
"name": "thelounge",
|
||||||
"description": "The self-hosted Web IRC client",
|
"description": "The self-hosted Web IRC client",
|
||||||
"version": "4.3.0-pre.1",
|
"version": "4.3.0-pre.2",
|
||||||
"preferGlobal": true,
|
"preferGlobal": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"thelounge": "index.js"
|
"thelounge": "index.js"
|
||||||
@ -43,18 +43,18 @@
|
|||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"busboy": "0.3.1",
|
"busboy": "0.3.1",
|
||||||
"chalk": "4.1.1",
|
"chalk": "4.1.1",
|
||||||
"cheerio": "1.0.0-rc.5",
|
"cheerio": "1.0.0-rc.10",
|
||||||
"commander": "7.2.0",
|
"commander": "7.2.0",
|
||||||
"content-disposition": "0.5.3",
|
"content-disposition": "0.5.3",
|
||||||
"express": "4.17.1",
|
"express": "4.17.1",
|
||||||
"file-type": "16.2.0",
|
"file-type": "16.2.0",
|
||||||
"filenamify": "4.2.0",
|
"filenamify": "4.2.0",
|
||||||
"got": "11.8.1",
|
"got": "11.8.1",
|
||||||
"irc-framework": "github:mstrodl/irc-framework#feature/fix-socks",
|
"irc-framework": "4.11.0",
|
||||||
"is-utf8": "0.2.1",
|
"is-utf8": "0.2.1",
|
||||||
"ldapjs": "2.2.3",
|
"ldapjs": "2.2.3",
|
||||||
"linkify-it": "3.0.2",
|
"linkify-it": "3.0.2",
|
||||||
"lodash": "4.17.20",
|
"lodash": "4.17.21",
|
||||||
"mime-types": "2.1.28",
|
"mime-types": "2.1.28",
|
||||||
"node-forge": "0.10.0",
|
"node-forge": "0.10.0",
|
||||||
"package-json": "6.5.0",
|
"package-json": "6.5.0",
|
||||||
@ -73,8 +73,8 @@
|
|||||||
"sqlite3": "5.0.2"
|
"sqlite3": "5.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.14.0",
|
"@babel/core": "7.14.6",
|
||||||
"@babel/preset-env": "7.14.0",
|
"@babel/preset-env": "7.14.7",
|
||||||
"@fortawesome/fontawesome-free": "5.15.3",
|
"@fortawesome/fontawesome-free": "5.15.3",
|
||||||
"@vue/server-test-utils": "1.1.3",
|
"@vue/server-test-utils": "1.1.3",
|
||||||
"@vue/test-utils": "1.1.3",
|
"@vue/test-utils": "1.1.3",
|
||||||
@ -83,8 +83,8 @@
|
|||||||
"chai": "4.3.4",
|
"chai": "4.3.4",
|
||||||
"copy-webpack-plugin": "7.0.0",
|
"copy-webpack-plugin": "7.0.0",
|
||||||
"css-loader": "5.1.1",
|
"css-loader": "5.1.1",
|
||||||
"cssnano": "4.1.10",
|
"cssnano": "4.1.11",
|
||||||
"dayjs": "1.10.4",
|
"dayjs": "1.10.5",
|
||||||
"emoji-regex": "9.2.1",
|
"emoji-regex": "9.2.1",
|
||||||
"eslint": "7.23.0",
|
"eslint": "7.23.0",
|
||||||
"eslint-config-prettier": "6.15.0",
|
"eslint-config-prettier": "6.15.0",
|
||||||
|
@ -34,7 +34,7 @@ try {
|
|||||||
createPackagesFolder();
|
createPackagesFolder();
|
||||||
|
|
||||||
// Merge config key-values passed as CLI options into the main config
|
// Merge config key-values passed as CLI options into the main config
|
||||||
Helper.mergeConfig(Helper.config, program.config);
|
Helper.mergeConfig(Helper.config, program.opts().config);
|
||||||
|
|
||||||
require("./start");
|
require("./start");
|
||||||
|
|
||||||
|
@ -13,6 +13,8 @@ program
|
|||||||
.on("--help", Utils.extraHelp)
|
.on("--help", Utils.extraHelp)
|
||||||
.action(function (packageName) {
|
.action(function (packageName) {
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
const fspromises = fs.promises;
|
||||||
|
const path = require("path");
|
||||||
const packageJson = require("package-json");
|
const packageJson = require("package-json");
|
||||||
|
|
||||||
if (!fs.existsSync(Helper.getConfigPath())) {
|
if (!fs.existsSync(Helper.getConfigPath())) {
|
||||||
@ -21,22 +23,31 @@ program
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.info("Retrieving information about the package...");
|
log.info("Retrieving information about the package...");
|
||||||
|
let readFile = null;
|
||||||
|
let isLocalFile = false;
|
||||||
|
|
||||||
const split = packageName.split("@");
|
if (packageName.startsWith("file:")) {
|
||||||
packageName = split[0];
|
isLocalFile = true;
|
||||||
const packageVersion = split[1] || "latest";
|
readFile = fspromises
|
||||||
|
.readFile(path.join(packageName.substr("file:".length), "package.json"), "utf-8")
|
||||||
|
.then((data) => JSON.parse(data));
|
||||||
|
} else {
|
||||||
|
const split = packageName.split("@");
|
||||||
|
packageName = split[0];
|
||||||
|
const packageVersion = split[1] || "latest";
|
||||||
|
|
||||||
packageJson(packageName, {
|
readFile = packageJson(packageName, {
|
||||||
fullMetadata: true,
|
fullMetadata: true,
|
||||||
version: packageVersion,
|
version: packageVersion,
|
||||||
})
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
readFile
|
||||||
.then((json) => {
|
.then((json) => {
|
||||||
|
const humanVersion = isLocalFile ? packageName : `${json.name} v${json.version}`;
|
||||||
|
|
||||||
if (!("thelounge" in json)) {
|
if (!("thelounge" in json)) {
|
||||||
log.error(
|
log.error(`${colors.red(humanVersion)} does not have The Lounge metadata.`);
|
||||||
`${colors.red(
|
|
||||||
json.name + " v" + json.version
|
|
||||||
)} does not have The Lounge metadata.`
|
|
||||||
);
|
|
||||||
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@ -47,7 +58,7 @@ program
|
|||||||
) {
|
) {
|
||||||
log.error(
|
log.error(
|
||||||
`${colors.red(
|
`${colors.red(
|
||||||
json.name + " v" + json.version
|
humanVersion
|
||||||
)} does not support The Lounge v${Helper.getVersionNumber()}. Supported version(s): ${
|
)} does not support The Lounge v${Helper.getVersionNumber()}. Supported version(s): ${
|
||||||
json.thelounge.supports
|
json.thelounge.supports
|
||||||
}`
|
}`
|
||||||
@ -56,20 +67,23 @@ program
|
|||||||
process.exit(2);
|
process.exit(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info(`Installing ${colors.green(json.name + " v" + json.version)}...`);
|
log.info(`Installing ${colors.green(humanVersion)}...`);
|
||||||
|
const yarnVersion = isLocalFile ? packageName : `${json.name}@${json.version}`;
|
||||||
return Utils.executeYarnCommand("add", "--exact", `${json.name}@${json.version}`)
|
return Utils.executeYarnCommand("add", "--exact", yarnVersion)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
log.info(
|
log.info(`${colors.green(humanVersion)} has been successfully installed.`);
|
||||||
`${colors.green(
|
|
||||||
json.name + " v" + json.version
|
if (isLocalFile) {
|
||||||
)} has been successfully installed.`
|
// yarn v1 is buggy if a local filepath is used and doesn't update
|
||||||
);
|
// the lockfile properly. We need to run an install in that case
|
||||||
|
// even though that's supposed to be done by the add subcommand
|
||||||
|
return Utils.executeYarnCommand("install").catch((err) => {
|
||||||
|
throw `Failed to update lockfile after package install ${err}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch((code) => {
|
.catch((code) => {
|
||||||
throw `Failed to install ${colors.green(
|
throw `Failed to install ${colors.red(humanVersion)}. Exit code: ${code}`;
|
||||||
json.name + " v" + json.version
|
|
||||||
)}. Exit code: ${code}`;
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
|
@ -79,6 +79,7 @@ Msg.Type = {
|
|||||||
WHOIS: "whois",
|
WHOIS: "whois",
|
||||||
RAW: "raw",
|
RAW: "raw",
|
||||||
PLUGIN: "plugin",
|
PLUGIN: "plugin",
|
||||||
|
WALLOPS: "wallops",
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = Msg;
|
module.exports = Msg;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const clientSideCommands = ["/collapse", "/expand"];
|
const clientSideCommands = ["/collapse", "/expand", "/search"];
|
||||||
|
|
||||||
const passThroughCommands = [
|
const passThroughCommands = [
|
||||||
"/as",
|
"/as",
|
||||||
|
@ -222,6 +222,7 @@ function parse(msg, chan, preview, res, client) {
|
|||||||
case "image/gif":
|
case "image/gif":
|
||||||
case "image/jpg":
|
case "image/jpg":
|
||||||
case "image/jpeg":
|
case "image/jpeg":
|
||||||
|
case "image/jxl":
|
||||||
case "image/webp":
|
case "image/webp":
|
||||||
case "image/avif":
|
case "image/avif":
|
||||||
if (!Helper.config.prefetchStorage && Helper.config.disableMediaPreview) {
|
if (!Helper.config.prefetchStorage && Helper.config.disableMediaPreview) {
|
||||||
|
@ -33,7 +33,7 @@ module.exports = function (irc, network) {
|
|||||||
|
|
||||||
irc.on("wallops", function (data) {
|
irc.on("wallops", function (data) {
|
||||||
data.from_server = true;
|
data.from_server = true;
|
||||||
data.type = Msg.Type.NOTICE;
|
data.type = Msg.Type.WALLOPS;
|
||||||
handleMessage(data);
|
handleMessage(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -51,8 +51,13 @@ module.exports = function (irc, network) {
|
|||||||
return Helper.compareHostmask(entry, data);
|
return Helper.compareHostmask(entry, data);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Server messages go to server window, no questions asked
|
// Server messages that aren't targeted at a channel go to the server window
|
||||||
if (data.from_server) {
|
if (
|
||||||
|
data.from_server &&
|
||||||
|
(!data.target ||
|
||||||
|
!network.getChannel(data.target) ||
|
||||||
|
network.getChannel(data.target).type !== Chan.Type.CHANNEL)
|
||||||
|
) {
|
||||||
chan = network.channels[0];
|
chan = network.channels[0];
|
||||||
from = chan.getUser(data.nick);
|
from = chan.getUser(data.nick);
|
||||||
} else {
|
} else {
|
||||||
|
@ -29,6 +29,7 @@ const inlineContentDispositionTypes = {
|
|||||||
"image/png": "image.png",
|
"image/png": "image.png",
|
||||||
"image/webp": "image.webp",
|
"image/webp": "image.webp",
|
||||||
"image/avif": "image.avif",
|
"image/avif": "image.avif",
|
||||||
|
"image/jxl": "image.jxl",
|
||||||
"text/plain": "text.txt",
|
"text/plain": "text.txt",
|
||||||
"video/mp4": "video.mp4",
|
"video/mp4": "video.mp4",
|
||||||
"video/ogg": "video.ogv",
|
"video/ogg": "video.ogv",
|
||||||
|
@ -55,8 +55,8 @@ describe("cleanIrcMessage", function () {
|
|||||||
expected: "bold bold",
|
expected: "bold bold",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "\x02irc\x0f://\x1dfreenode.net\x0f/\x034,8thelounge",
|
input: "\x02irc\x0f://\x1dirc.example.com\x0f/\x034,8thelounge",
|
||||||
expected: "irc://freenode.net/thelounge",
|
expected: "irc://irc.example.com/thelounge",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "\x02#\x038,9thelounge",
|
input: "\x02#\x038,9thelounge",
|
||||||
|
@ -8,12 +8,12 @@ const {
|
|||||||
|
|
||||||
describe("findLinks", () => {
|
describe("findLinks", () => {
|
||||||
it("should find url", () => {
|
it("should find url", () => {
|
||||||
const input = "irc://freenode.net/thelounge";
|
const input = "irc://irc.example.com/thelounge";
|
||||||
const expected = [
|
const expected = [
|
||||||
{
|
{
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 28,
|
end: 31,
|
||||||
link: "irc://freenode.net/thelounge",
|
link: "irc://irc.example.com/thelounge",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -63,10 +63,10 @@ describe("IRC formatted message parser", () => {
|
|||||||
it("should find urls", async () => {
|
it("should find urls", async () => {
|
||||||
const testCases = [
|
const testCases = [
|
||||||
{
|
{
|
||||||
input: "irc://freenode.net/thelounge",
|
input: "irc://irc.example.com/thelounge",
|
||||||
expected:
|
expected:
|
||||||
'<a href="irc://freenode.net/thelounge" dir="auto" target="_blank" rel="noopener">' +
|
'<a href="irc://irc.example.com/thelounge" dir="auto" target="_blank" rel="noopener">' +
|
||||||
"irc://freenode.net/thelounge" +
|
"irc://irc.example.com/thelounge" +
|
||||||
"</a>",
|
"</a>",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -416,12 +416,12 @@ describe("IRC formatted message parser", () => {
|
|||||||
it("should go bonkers like mirc", async () => {
|
it("should go bonkers like mirc", async () => {
|
||||||
const testCases = [
|
const testCases = [
|
||||||
{
|
{
|
||||||
input: "\x02irc\x0f://\x1dfreenode.net\x0f/\x034,8thelounge",
|
input: "\x02irc\x0f://\x1dirc.example.com\x0f/\x034,8thelounge",
|
||||||
expected:
|
expected:
|
||||||
'<a href="irc://freenode.net/thelounge" dir="auto" target="_blank" rel="noopener">' +
|
'<a href="irc://irc.example.com/thelounge" dir="auto" target="_blank" rel="noopener">' +
|
||||||
'<span class="irc-bold">irc</span>' +
|
'<span class="irc-bold">irc</span>' +
|
||||||
"://" +
|
"://" +
|
||||||
'<span class="irc-italic">freenode.net</span>' +
|
'<span class="irc-italic">irc.example.com</span>' +
|
||||||
"/" +
|
"/" +
|
||||||
'<span class="irc-fg4 irc-bg8">thelounge</span>' +
|
'<span class="irc-fg4 irc-bg8">thelounge</span>' +
|
||||||
"</a>",
|
"</a>",
|
||||||
|
2
test/fixtures/.thelounge/config.js
vendored
2
test/fixtures/.thelounge/config.js
vendored
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
var config = require("../../../defaults/config.js");
|
var config = require("../../../defaults/config.js");
|
||||||
|
|
||||||
|
config.defaults.name = "Example IRC Server";
|
||||||
|
config.defaults.host = "irc.example.com";
|
||||||
config.public = true;
|
config.public = true;
|
||||||
config.prefetch = true;
|
config.prefetch = true;
|
||||||
config.host = config.bind = "127.0.0.1";
|
config.host = config.bind = "127.0.0.1";
|
||||||
|
@ -95,7 +95,7 @@ describe("Network", function () {
|
|||||||
rejectUnauthorized: false,
|
rejectUnauthorized: false,
|
||||||
});
|
});
|
||||||
expect(network.validate()).to.be.true;
|
expect(network.validate()).to.be.true;
|
||||||
expect(network.host).to.equal("chat.freenode.net");
|
expect(network.host).to.equal("irc.example.com");
|
||||||
expect(network.port).to.equal(6697);
|
expect(network.port).to.equal(6697);
|
||||||
expect(network.tls).to.be.true;
|
expect(network.tls).to.be.true;
|
||||||
expect(network.rejectUnauthorized).to.be.true;
|
expect(network.rejectUnauthorized).to.be.true;
|
||||||
@ -107,7 +107,7 @@ describe("Network", function () {
|
|||||||
host: "some.fake.tld",
|
host: "some.fake.tld",
|
||||||
});
|
});
|
||||||
expect(network2.validate()).to.be.true;
|
expect(network2.validate()).to.be.true;
|
||||||
expect(network2.host).to.equal("chat.freenode.net");
|
expect(network2.host).to.equal("irc.example.com");
|
||||||
|
|
||||||
Helper.config.lockNetwork = false;
|
Helper.config.lockNetwork = false;
|
||||||
});
|
});
|
||||||
@ -269,7 +269,7 @@ describe("Network", function () {
|
|||||||
// Lobby and initial channel
|
// Lobby and initial channel
|
||||||
expect(network.channels.length).to.equal(2);
|
expect(network.channels.length).to.equal(2);
|
||||||
|
|
||||||
const newChan = new Chan({name: "#freenode"});
|
const newChan = new Chan({name: "#foo"});
|
||||||
network.addChannel(newChan);
|
network.addChannel(newChan);
|
||||||
|
|
||||||
expect(network.channels.length).to.equal(3);
|
expect(network.channels.length).to.equal(3);
|
||||||
@ -282,13 +282,13 @@ describe("Network", function () {
|
|||||||
|
|
||||||
const network = new Network({
|
const network = new Network({
|
||||||
channels: [chan1, chan2, chan3],
|
channels: [chan1, chan2, chan3],
|
||||||
name: "freenode",
|
name: "foo",
|
||||||
});
|
});
|
||||||
|
|
||||||
const newChan = new Chan({name: "#freenode"});
|
const newChan = new Chan({name: "#foo"});
|
||||||
network.addChannel(newChan);
|
network.addChannel(newChan);
|
||||||
|
|
||||||
expect(network.channels[0].name).to.equal("freenode");
|
expect(network.channels[0].name).to.equal("foo");
|
||||||
expect(network.channels[1]).to.equal(chan1);
|
expect(network.channels[1]).to.equal(chan1);
|
||||||
expect(network.channels[2]).to.equal(newChan);
|
expect(network.channels[2]).to.equal(newChan);
|
||||||
expect(network.channels[3]).to.equal(chan2);
|
expect(network.channels[3]).to.equal(chan2);
|
||||||
@ -303,7 +303,7 @@ describe("Network", function () {
|
|||||||
channels: [chan1, chan2],
|
channels: [chan1, chan2],
|
||||||
});
|
});
|
||||||
|
|
||||||
const newChan = new Chan({name: "#freenode"});
|
const newChan = new Chan({name: "#foo"});
|
||||||
network.addChannel(newChan);
|
network.addChannel(newChan);
|
||||||
|
|
||||||
expect(network.channels[1]).to.equal(chan1);
|
expect(network.channels[1]).to.equal(chan1);
|
||||||
@ -397,7 +397,7 @@ describe("Network", function () {
|
|||||||
channels: [banlist, chan1, user1],
|
channels: [banlist, chan1, user1],
|
||||||
});
|
});
|
||||||
|
|
||||||
const newChan = new Chan({name: "#freenode"});
|
const newChan = new Chan({name: "#foo"});
|
||||||
network.addChannel(newChan);
|
network.addChannel(newChan);
|
||||||
|
|
||||||
expect(network.channels[1]).to.equal(newChan);
|
expect(network.channels[1]).to.equal(newChan);
|
||||||
@ -408,7 +408,7 @@ describe("Network", function () {
|
|||||||
|
|
||||||
it("should never add something in front of the lobby", function () {
|
it("should never add something in front of the lobby", function () {
|
||||||
const network = new Network({
|
const network = new Network({
|
||||||
name: "freenode",
|
name: "foo",
|
||||||
channels: [],
|
channels: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user