mirror of
https://github.com/thelounge/thelounge.git
synced 2024-05-05 07:53:27 +02:00
Merge with master
This commit is contained in:
commit
2721395ada
|
@ -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.
|
||||||
|
|
45
CHANGELOG.md
45
CHANGELOG.md
|
@ -4,6 +4,51 @@ 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.4 - 2021-07-01 [Pre-release]
|
||||||
|
|
||||||
|
[See the full changelog](https://github.com/thelounge/thelounge/compare/v4.3.0-pre.3...v4.3.0-pre.4)
|
||||||
|
|
||||||
|
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.3 - 2021-06-29 [Pre-release]
|
||||||
|
|
||||||
|
[See the full changelog](https://github.com/thelounge/thelounge/compare/v4.3.0-pre.2...v4.3.0-pre.3)
|
||||||
|
|
||||||
|
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.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>.
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="viewport" :class="viewportClasses" role="tablist">
|
<div id="viewport" :class="viewportClasses" role="tablist">
|
||||||
<Sidebar v-if="$store.state.appLoaded" :overlay="$refs.overlay" />
|
<Sidebar v-if="$store.state.appLoaded" :overlay="$refs.overlay" />
|
||||||
<div id="sidebar-overlay" ref="overlay" @click="$store.commit('sidebarOpen', false)" />
|
<div
|
||||||
|
id="sidebar-overlay"
|
||||||
|
ref="overlay"
|
||||||
|
aria-hidden="true"
|
||||||
|
@click="$store.commit('sidebarOpen', false)"
|
||||||
|
/>
|
||||||
<router-view ref="window"></router-view>
|
<router-view ref="window"></router-view>
|
||||||
<Mentions />
|
<Mentions />
|
||||||
<ImageViewer ref="imageViewer" />
|
<ImageViewer ref="imageViewer" />
|
||||||
|
@ -51,6 +56,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 +78,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 +105,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();
|
||||||
|
|
|
@ -54,20 +54,25 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
getAriaLabel() {
|
getAriaLabel() {
|
||||||
const extra = [];
|
const extra = [];
|
||||||
|
const type = this.channel.type;
|
||||||
|
|
||||||
if (this.channel.unread > 0) {
|
if (this.channel.unread > 0) {
|
||||||
extra.push(`${this.channel.unread} unread`);
|
if (this.channel.unread > 1) {
|
||||||
|
extra.push(`${this.channel.unread} unread messages`);
|
||||||
|
} else {
|
||||||
|
extra.push(`${this.channel.unread} unread message`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.channel.highlight > 0) {
|
if (this.channel.highlight > 0) {
|
||||||
extra.push(`${this.channel.highlight} mention`);
|
if (this.channel.highlight > 1) {
|
||||||
|
extra.push(`${this.channel.highlight} mentions`);
|
||||||
|
} else {
|
||||||
|
extra.push(`${this.channel.highlight} mention`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (extra.length > 0) {
|
return `${type}: ${this.channel.name} ${extra.length ? `(${extra.join(", ")})` : ""}`;
|
||||||
return `${this.channel.name} (${extra.join(", ")})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.channel.name;
|
|
||||||
},
|
},
|
||||||
click() {
|
click() {
|
||||||
if (this.isFiltering) {
|
if (this.isFiltering) {
|
||||||
|
|
|
@ -18,7 +18,9 @@
|
||||||
>
|
>
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<SidebarToggle />
|
<SidebarToggle />
|
||||||
<span class="title">{{ channel.name }}</span>
|
<span class="title" :aria-label="'Currently open ' + channel.type">{{
|
||||||
|
channel.name
|
||||||
|
}}</span>
|
||||||
<div v-if="channel.editTopic === true" class="topic-container">
|
<div v-if="channel.editTopic === true" class="topic-container">
|
||||||
<input
|
<input
|
||||||
ref="topicInput"
|
ref="topicInput"
|
||||||
|
@ -93,13 +95,13 @@
|
||||||
>
|
>
|
||||||
<div class="scroll-down-arrow" />
|
<div class="scroll-down-arrow" />
|
||||||
</div>
|
</div>
|
||||||
|
<ChatUserList v-if="channel.type === 'channel'" :channel="channel" />
|
||||||
<MessageList
|
<MessageList
|
||||||
ref="messageList"
|
ref="messageList"
|
||||||
:network="network"
|
:network="network"
|
||||||
:channel="channel"
|
:channel="channel"
|
||||||
:focused="focused"
|
:focused="focused"
|
||||||
/>
|
/>
|
||||||
<ChatUserList v-if="channel.type === 'channel'" :channel="channel" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -140,22 +140,53 @@ export default {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const oldValue = this.$refs.input.value;
|
||||||
|
const oldPosition = this.$refs.input.selectionStart;
|
||||||
|
const onRow = (oldValue.slice(null, oldPosition).match(/\n/g) || []).length;
|
||||||
|
const totalRows = (oldValue.match(/\n/g) || []).length;
|
||||||
|
|
||||||
const {channel} = this;
|
const {channel} = this;
|
||||||
|
|
||||||
if (channel.inputHistoryPosition === 0) {
|
if (channel.inputHistoryPosition === 0) {
|
||||||
channel.inputHistory[channel.inputHistoryPosition] = channel.pendingMessage;
|
channel.inputHistory[channel.inputHistoryPosition] = channel.pendingMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === "up") {
|
if (key === "up" && onRow === 0) {
|
||||||
if (channel.inputHistoryPosition < channel.inputHistory.length - 1) {
|
if (channel.inputHistoryPosition < channel.inputHistory.length - 1) {
|
||||||
channel.inputHistoryPosition++;
|
channel.inputHistoryPosition++;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} else if (channel.inputHistoryPosition > 0) {
|
} else if (key === "down" && channel.inputHistoryPosition > 0 && onRow === totalRows) {
|
||||||
channel.inputHistoryPosition--;
|
channel.inputHistoryPosition--;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
channel.pendingMessage = channel.inputHistory[channel.inputHistoryPosition];
|
channel.pendingMessage = channel.inputHistory[channel.inputHistoryPosition];
|
||||||
this.$refs.input.value = channel.pendingMessage;
|
const newValue = channel.pendingMessage;
|
||||||
|
this.$refs.input.value = newValue;
|
||||||
|
|
||||||
|
let newPosition;
|
||||||
|
|
||||||
|
if (key === "up") {
|
||||||
|
const lastIndexOfNewLine = newValue.lastIndexOf("\n");
|
||||||
|
const lastLine = newValue.slice(null, lastIndexOfNewLine);
|
||||||
|
newPosition =
|
||||||
|
oldPosition > lastLine.length
|
||||||
|
? newValue.length
|
||||||
|
: lastIndexOfNewLine + oldPosition + 1;
|
||||||
|
} else {
|
||||||
|
const lastPositionOnFirstLine =
|
||||||
|
newValue.indexOf("\n") === -1 ? newValue.length + 1 : newValue.indexOf("\n");
|
||||||
|
const relativeRowPos = oldPosition - oldValue.lastIndexOf("\n") - 1;
|
||||||
|
newPosition =
|
||||||
|
relativeRowPos > lastPositionOnFirstLine
|
||||||
|
? lastPositionOnFirstLine
|
||||||
|
: relativeRowPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$refs.input.setSelectionRange(newPosition, newPosition);
|
||||||
this.setInputSize();
|
this.setInputSize();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -183,6 +214,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;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<aside ref="userlist" class="userlist" @mouseleave="removeHoverUser">
|
<aside
|
||||||
|
ref="userlist"
|
||||||
|
class="userlist"
|
||||||
|
:aria-label="'User list for ' + channel.name"
|
||||||
|
@mouseleave="removeHoverUser"
|
||||||
|
>
|
||||||
<div class="count">
|
<div class="count">
|
||||||
<input
|
<input
|
||||||
ref="input"
|
ref="input"
|
||||||
|
|
|
@ -13,8 +13,12 @@
|
||||||
:data-command="message.command"
|
:data-command="message.command"
|
||||||
:data-from="message.from && message.from.nick"
|
:data-from="message.from && message.from.nick"
|
||||||
>
|
>
|
||||||
<span :aria-label="messageTimeLocale" class="time tooltipped tooltipped-e"
|
<span
|
||||||
>{{ messageTime }}
|
aria-hidden="true"
|
||||||
|
:aria-label="messageTimeLocale"
|
||||||
|
class="time tooltipped tooltipped-e"
|
||||||
|
>
|
||||||
|
{{ messageTime }}
|
||||||
</span>
|
</span>
|
||||||
<template v-if="message.type === 'unhandled'">
|
<template v-if="message.type === 'unhandled'">
|
||||||
<span class="from">[{{ message.command }}]</span>
|
<span class="from">[{{ message.command }}]</span>
|
||||||
|
@ -47,23 +51,23 @@
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<span v-if="message.type === 'message'" class="from">
|
<span v-if="message.type === 'message'" class="from">
|
||||||
<template v-if="message.from && message.from.nick">
|
<template v-if="message.from && message.from.nick">
|
||||||
<span class="only-copy"><</span>
|
<span class="only-copy" aria-hidden="true"><</span>
|
||||||
<Username :user="message.from" :network="network" :channel="channel" />
|
<Username :user="message.from" :network="network" :channel="channel" />
|
||||||
<span class="only-copy">> </span>
|
<span class="only-copy" aria-hidden="true">> </span>
|
||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="message.type === 'plugin'" class="from">
|
<span v-else-if="message.type === 'plugin'" class="from">
|
||||||
<template v-if="message.from && message.from.nick">
|
<template v-if="message.from && message.from.nick">
|
||||||
<span class="only-copy">[</span>
|
<span class="only-copy" aria-hidden="true">[</span>
|
||||||
{{ message.from.nick }}
|
{{ message.from.nick }}
|
||||||
<span class="only-copy">] </span>
|
<span class="only-copy" aria-hidden="true">] </span>
|
||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
<span v-else class="from">
|
<span v-else class="from">
|
||||||
<template v-if="message.from && message.from.nick">
|
<template v-if="message.from && message.from.nick">
|
||||||
<span class="only-copy">-</span>
|
<span class="only-copy" aria-hidden="true">-</span>
|
||||||
<Username :user="message.from" :network="network" :channel="channel" />
|
<Username :user="message.from" :network="network" :channel="channel" />
|
||||||
<span class="only-copy">- </span>
|
<span class="only-copy" aria-hidden="true">- </span>
|
||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
<span class="content" dir="auto">
|
<span class="content" dir="auto">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -98,6 +98,80 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h2>Proxy Settings</h2>
|
||||||
|
<div class="connect-row">
|
||||||
|
<label></label>
|
||||||
|
<div class="input-wrap">
|
||||||
|
<label for="connect:proxyEnabled">
|
||||||
|
<input
|
||||||
|
id="connect:proxyEnabled"
|
||||||
|
v-model="defaults.proxyEnabled"
|
||||||
|
type="checkbox"
|
||||||
|
name="proxyEnabled"
|
||||||
|
/>
|
||||||
|
Enable Proxy
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template v-if="defaults.proxyEnabled">
|
||||||
|
<div class="connect-row">
|
||||||
|
<label for="connect:proxyHost">SOCKS Address</label>
|
||||||
|
<div class="input-wrap">
|
||||||
|
<input
|
||||||
|
id="connect:proxyHost"
|
||||||
|
v-model="defaults.proxyHost"
|
||||||
|
class="input"
|
||||||
|
name="proxyHost"
|
||||||
|
aria-label="Proxy host"
|
||||||
|
maxlength="255"
|
||||||
|
/>
|
||||||
|
<span id="connect:proxyPortSeparator">:</span>
|
||||||
|
<input
|
||||||
|
id="connect:proxyPort"
|
||||||
|
v-model="defaults.proxyPort"
|
||||||
|
class="input"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
max="65535"
|
||||||
|
name="proxyPort"
|
||||||
|
aria-label="SOCKS port"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="connect-row">
|
||||||
|
<label for="connect:proxyUsername">Proxy username</label>
|
||||||
|
<input
|
||||||
|
id="connect:proxyUsername"
|
||||||
|
ref="proxyUsernameInput"
|
||||||
|
v-model="defaults.proxyUsername"
|
||||||
|
class="input username"
|
||||||
|
name="proxyUsername"
|
||||||
|
maxlength="100"
|
||||||
|
placeholder="Proxy username"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="connect-row">
|
||||||
|
<label for="connect:proxyPassword">Proxy password</label>
|
||||||
|
<RevealPassword
|
||||||
|
v-slot:default="slotProps"
|
||||||
|
class="input-wrap password-container"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
id="connect:proxyPassword"
|
||||||
|
ref="proxyPassword"
|
||||||
|
v-model="defaults.proxyPassword"
|
||||||
|
class="input"
|
||||||
|
:type="slotProps.isVisible ? 'text' : 'password'"
|
||||||
|
placeholder="Proxy password"
|
||||||
|
name="password"
|
||||||
|
maxlength="300"
|
||||||
|
/>
|
||||||
|
</RevealPassword>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="config.lockNetwork && !$store.state.serverConfiguration.public">
|
<template v-else-if="config.lockNetwork && !$store.state.serverConfiguration.public">
|
||||||
<h2>Network settings</h2>
|
<h2>Network settings</h2>
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="$store.state.networks.length === 0" class="empty">
|
<div
|
||||||
|
v-if="$store.state.networks.length === 0"
|
||||||
|
class="empty"
|
||||||
|
role="navigation"
|
||||||
|
aria-label="Network and Channel list"
|
||||||
|
>
|
||||||
You are not connected to any networks yet.
|
You are not connected to any networks yet.
|
||||||
</div>
|
</div>
|
||||||
<div v-else ref="networklist">
|
<div v-else ref="networklist" role="navigation" aria-label="Network and Channel list">
|
||||||
<div class="jump-to-input">
|
<div class="jump-to-input">
|
||||||
<input
|
<input
|
||||||
ref="searchInput"
|
ref="searchInput"
|
||||||
|
@ -74,6 +79,7 @@
|
||||||
}"
|
}"
|
||||||
class="network"
|
class="network"
|
||||||
role="region"
|
role="region"
|
||||||
|
aria-live="polite"
|
||||||
>
|
>
|
||||||
<NetworkLobby
|
<NetworkLobby
|
||||||
:network="network"
|
:network="network"
|
||||||
|
|
|
@ -6,11 +6,13 @@
|
||||||
:src="`img/logo-${isPublic() ? 'horizontal-' : ''}transparent-bg.svg`"
|
:src="`img/logo-${isPublic() ? 'horizontal-' : ''}transparent-bg.svg`"
|
||||||
class="logo"
|
class="logo"
|
||||||
alt="The Lounge"
|
alt="The Lounge"
|
||||||
|
role="presentation"
|
||||||
/>
|
/>
|
||||||
<img
|
<img
|
||||||
:src="`img/logo-${isPublic() ? 'horizontal-' : ''}transparent-bg-inverted.svg`"
|
:src="`img/logo-${isPublic() ? 'horizontal-' : ''}transparent-bg-inverted.svg`"
|
||||||
class="logo-inverted"
|
class="logo-inverted"
|
||||||
alt="The Lounge"
|
alt="The Lounge"
|
||||||
|
role="presentation"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
v-if="isDevelopment"
|
v-if="isDevelopment"
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -91,7 +91,7 @@
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
name="showSeconds"
|
name="showSeconds"
|
||||||
/>
|
/>
|
||||||
Show seconds in timestamp
|
Include seconds in timestamp
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -101,7 +101,7 @@
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
name="use12hClock"
|
name="use12hClock"
|
||||||
/>
|
/>
|
||||||
Show 12-hour timestamps
|
Use 12-hour timestamps
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!$store.state.serverConfiguration.public && $store.state.settings.advanced">
|
<div v-if="!$store.state.serverConfiguration.public && $store.state.settings.advanced">
|
||||||
|
@ -306,6 +306,7 @@
|
||||||
<input
|
<input
|
||||||
id="desktopNotifications"
|
id="desktopNotifications"
|
||||||
:checked="$store.state.settings.desktopNotifications"
|
:checked="$store.state.settings.desktopNotifications"
|
||||||
|
:disabled="$store.state.desktopNotificationState === 'nohttps'"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
name="desktopNotifications"
|
name="desktopNotifications"
|
||||||
/>
|
/>
|
||||||
|
@ -316,6 +317,14 @@
|
||||||
>
|
>
|
||||||
<strong>Warning</strong>: Notifications are not supported by your browser.
|
<strong>Warning</strong>: Notifications are not supported by your browser.
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="$store.state.desktopNotificationState === 'nohttps'"
|
||||||
|
id="warnBlockedDesktopNotifications"
|
||||||
|
class="error"
|
||||||
|
>
|
||||||
|
<strong>Warning</strong>: Notifications are only supported over HTTPS
|
||||||
|
connections.
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="$store.state.desktopNotificationState === 'blocked'"
|
v-if="$store.state.desktopNotificationState === 'blocked'"
|
||||||
id="warnBlockedDesktopNotifications"
|
id="warnBlockedDesktopNotifications"
|
||||||
|
|
|
@ -1147,6 +1147,7 @@ textarea.input {
|
||||||
|
|
||||||
#chat .chat-content {
|
#chat .chat-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -1195,7 +1196,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 +1508,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 +1520,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;
|
||||||
|
@ -1840,19 +1847,23 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#connect .tls input {
|
#connect .tls input,
|
||||||
|
#connect input[name="proxyEnabled"] {
|
||||||
margin: 3px 10px 0 0;
|
margin: 3px 10px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#connect\:host {
|
#connect\:host,
|
||||||
|
#connect\:proxyHost {
|
||||||
width: 70%;
|
width: 70%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#connect\:port {
|
#connect\:port,
|
||||||
|
#connect\:proxyPort {
|
||||||
width: 25%;
|
width: 25%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#connect\:portseparator {
|
#connect\:portseparator,
|
||||||
|
#connect\:proxyPortSeparator {
|
||||||
width: 5%;
|
width: 5%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -2624,6 +2635,7 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */
|
||||||
right: 0;
|
right: 0;
|
||||||
transform: translateX(180px);
|
transform: translateX(180px);
|
||||||
transition: transform 0.2s;
|
transition: transform 0.2s;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#viewport.userlist-open #chat .userlist {
|
#viewport.userlist-open #chat .userlist {
|
||||||
|
|
|
@ -52,8 +52,8 @@
|
||||||
<div id="loading">
|
<div id="loading">
|
||||||
<div class="window">
|
<div class="window">
|
||||||
<div id="loading-status-container">
|
<div id="loading-status-container">
|
||||||
<img src="img/logo-vertical-transparent-bg.svg" class="logo" alt="The Lounge" width="256" height="170">
|
<img src="img/logo-vertical-transparent-bg.svg" class="logo" alt="" width="256" height="170">
|
||||||
<img src="img/logo-vertical-transparent-bg-inverted.svg" class="logo-inverted" alt="The Lounge" width="256" height="170">
|
<img src="img/logo-vertical-transparent-bg-inverted.svg" class="logo-inverted" alt="" width="256" height="170">
|
||||||
<p id="loading-page-message">The Lounge requires a modern browser with JavaScript enabled.</p>
|
<p id="loading-page-message">The Lounge requires a modern browser with JavaScript enabled.</p>
|
||||||
</div>
|
</div>
|
||||||
<div id="loading-reload-container">
|
<div id="loading-reload-container">
|
||||||
|
|
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};
|
|
@ -1238,8 +1238,8 @@
|
||||||
"credit_card": "💳",
|
"credit_card": "💳",
|
||||||
"receipt": "🧾",
|
"receipt": "🧾",
|
||||||
"chart": "💹",
|
"chart": "💹",
|
||||||
"email": "✉️",
|
|
||||||
"envelope": "✉️",
|
"envelope": "✉️",
|
||||||
|
"email": "📧",
|
||||||
"e_mail": "📧",
|
"e_mail": "📧",
|
||||||
"incoming_envelope": "📨",
|
"incoming_envelope": "📨",
|
||||||
"envelope_with_arrow": "📩",
|
"envelope_with_arrow": "📩",
|
||||||
|
|
|
@ -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;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,6 +12,8 @@ function detectDesktopNotificationState() {
|
||||||
return "unsupported";
|
return "unsupported";
|
||||||
} else if (Notification.permission === "granted") {
|
} else if (Notification.permission === "granted") {
|
||||||
return "granted";
|
return "granted";
|
||||||
|
} else if (!window.isSecureContext) {
|
||||||
|
return "nohttps";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "blocked";
|
return "blocked";
|
||||||
|
@ -190,7 +192,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,
|
||||||
|
|
24
package.json
24
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.4",
|
||||||
"preferGlobal": true,
|
"preferGlobal": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"thelounge": "index.js"
|
"thelounge": "index.js"
|
||||||
|
@ -43,19 +43,19 @@
|
||||||
"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.2",
|
||||||
"irc-framework": "4.9.0",
|
"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.31",
|
||||||
"node-forge": "0.10.0",
|
"node-forge": "0.10.0",
|
||||||
"package-json": "6.5.0",
|
"package-json": "6.5.0",
|
||||||
"read": "1.0.7",
|
"read": "1.0.7",
|
||||||
|
@ -73,8 +73,8 @@
|
||||||
"better-sqlite3": "7.4.0"
|
"better-sqlite3": "7.4.0"
|
||||||
},
|
},
|
||||||
"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,14 +83,14 @@
|
||||||
"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.2",
|
||||||
"eslint": "7.23.0",
|
"eslint": "7.23.0",
|
||||||
"eslint-config-prettier": "6.15.0",
|
"eslint-config-prettier": "6.15.0",
|
||||||
"eslint-plugin-vue": "7.5.0",
|
"eslint-plugin-vue": "7.5.0",
|
||||||
"fuzzy": "0.1.3",
|
"fuzzy": "0.1.3",
|
||||||
"husky": "4.3.5",
|
"husky": "4.3.8",
|
||||||
"mini-css-extract-plugin": "1.3.6",
|
"mini-css-extract-plugin": "1.3.6",
|
||||||
"mocha": "8.2.1",
|
"mocha": "8.2.1",
|
||||||
"mousetrap": "1.6.5",
|
"mousetrap": "1.6.5",
|
||||||
|
|
0
scripts/generate-emoji.js
Normal file → Executable file
0
scripts/generate-emoji.js
Normal file → Executable file
|
@ -257,6 +257,12 @@ Client.prototype.connect = function (args, isStartup = false) {
|
||||||
commands: args.commands || [],
|
commands: args.commands || [],
|
||||||
channels: channels,
|
channels: channels,
|
||||||
ignoreList: args.ignoreList ? args.ignoreList : [],
|
ignoreList: args.ignoreList ? args.ignoreList : [],
|
||||||
|
|
||||||
|
proxyEnabled: !!args.proxyEnabled,
|
||||||
|
proxyHost: String(args.proxyHost || ""),
|
||||||
|
proxyPort: parseInt(args.proxyPort, 10),
|
||||||
|
proxyUsername: String(args.proxyUsername || ""),
|
||||||
|
proxyPassword: String(args.proxyPassword || ""),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set network lobby channel id
|
// Set network lobby channel id
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -46,6 +46,13 @@ function Network(attr) {
|
||||||
PREFIX: ["!", "@", "%", "+"],
|
PREFIX: ["!", "@", "%", "+"],
|
||||||
NETWORK: "",
|
NETWORK: "",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
proxyHost: "",
|
||||||
|
proxyPort: 1080,
|
||||||
|
proxyUsername: "",
|
||||||
|
proxyPassword: "",
|
||||||
|
proxyEnabled: false,
|
||||||
|
|
||||||
chanCache: [],
|
chanCache: [],
|
||||||
ignoreList: [],
|
ignoreList: [],
|
||||||
keepNick: null,
|
keepNick: null,
|
||||||
|
@ -90,6 +97,12 @@ Network.prototype.validate = function (client) {
|
||||||
this.saslAccount = cleanString(this.saslAccount);
|
this.saslAccount = cleanString(this.saslAccount);
|
||||||
this.saslPassword = cleanString(this.saslPassword);
|
this.saslPassword = cleanString(this.saslPassword);
|
||||||
|
|
||||||
|
this.proxyHost = cleanString(this.proxyHost);
|
||||||
|
this.proxyPort = this.proxyPort || 1080;
|
||||||
|
this.proxyUsername = cleanString(this.proxyUsername);
|
||||||
|
this.proxyPassword = cleanString(this.proxyPassword);
|
||||||
|
this.proxyEnabled = !!this.proxyEnabled;
|
||||||
|
|
||||||
if (!this.port) {
|
if (!this.port) {
|
||||||
this.port = this.tls ? 6697 : 6667;
|
this.port = this.tls ? 6697 : 6667;
|
||||||
}
|
}
|
||||||
|
@ -208,6 +221,17 @@ Network.prototype.setIrcFrameworkOptions = function (client) {
|
||||||
this.irc.options.webirc = this.createWebIrc(client);
|
this.irc.options.webirc = this.createWebIrc(client);
|
||||||
this.irc.options.client_certificate = null;
|
this.irc.options.client_certificate = null;
|
||||||
|
|
||||||
|
if (this.proxyEnabled) {
|
||||||
|
this.irc.options.socks = {
|
||||||
|
host: this.proxyHost,
|
||||||
|
port: this.proxyPort,
|
||||||
|
user: this.proxyUsername,
|
||||||
|
pass: this.proxyPassword,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
delete this.irc.options.socks;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.sasl) {
|
if (!this.sasl) {
|
||||||
delete this.irc.options.sasl_mechanism;
|
delete this.irc.options.sasl_mechanism;
|
||||||
delete this.irc.options.account;
|
delete this.irc.options.account;
|
||||||
|
@ -274,6 +298,12 @@ Network.prototype.edit = function (client, args) {
|
||||||
this.saslAccount = String(args.saslAccount || "");
|
this.saslAccount = String(args.saslAccount || "");
|
||||||
this.saslPassword = String(args.saslPassword || "");
|
this.saslPassword = String(args.saslPassword || "");
|
||||||
|
|
||||||
|
this.proxyHost = String(args.proxyHost || "");
|
||||||
|
this.proxyPort = parseInt(args.proxyPort, 10);
|
||||||
|
this.proxyUsername = String(args.proxyUsername || "");
|
||||||
|
this.proxyPassword = String(args.proxyPassword || "");
|
||||||
|
this.proxyEnabled = !!args.proxyEnabled;
|
||||||
|
|
||||||
// Split commands into an array
|
// Split commands into an array
|
||||||
this.commands = String(args.commands || "")
|
this.commands = String(args.commands || "")
|
||||||
.replace(/\r\n|\r|\n/g, "\n")
|
.replace(/\r\n|\r|\n/g, "\n")
|
||||||
|
@ -455,6 +485,12 @@ Network.prototype.exportForEdit = function () {
|
||||||
"saslAccount",
|
"saslAccount",
|
||||||
"saslPassword",
|
"saslPassword",
|
||||||
"commands",
|
"commands",
|
||||||
|
|
||||||
|
"proxyEnabled",
|
||||||
|
"proxyHost",
|
||||||
|
"proxyPort",
|
||||||
|
"proxyUsername",
|
||||||
|
"proxyPassword",
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!Helper.config.lockNetwork) {
|
if (!Helper.config.lockNetwork) {
|
||||||
|
@ -491,6 +527,11 @@ Network.prototype.export = function () {
|
||||||
"saslPassword",
|
"saslPassword",
|
||||||
"commands",
|
"commands",
|
||||||
"ignoreList",
|
"ignoreList",
|
||||||
|
|
||||||
|
"proxyHost",
|
||||||
|
"proxyPort",
|
||||||
|
"proxyUsername",
|
||||||
|
"proxyEnabled",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
network.channels = this.channels
|
network.channels = this.channels
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const clientSideCommands = ["/collapse", "/expand"];
|
const clientSideCommands = ["/collapse", "/expand", "/search"];
|
||||||
|
|
||||||
const passThroughCommands = [
|
const passThroughCommands = [
|
||||||
"/as",
|
"/as",
|
||||||
|
|
|
@ -3,10 +3,14 @@
|
||||||
const Chan = require("../../models/chan");
|
const Chan = require("../../models/chan");
|
||||||
const Msg = require("../../models/msg");
|
const Msg = require("../../models/msg");
|
||||||
|
|
||||||
exports.commands = ["mode", "op", "deop", "hop", "dehop", "voice", "devoice"];
|
exports.commands = ["mode", "umode", "op", "deop", "hop", "dehop", "voice", "devoice"];
|
||||||
|
|
||||||
exports.input = function ({irc, nick}, chan, cmd, args) {
|
exports.input = function ({irc, nick}, chan, cmd, args) {
|
||||||
if (cmd !== "mode") {
|
if (cmd === "umode") {
|
||||||
|
irc.raw("MODE", nick, ...args);
|
||||||
|
|
||||||
|
return;
|
||||||
|
} else if (cmd !== "mode") {
|
||||||
if (chan.type !== Chan.Type.CHANNEL) {
|
if (chan.type !== Chan.Type.CHANNEL) {
|
||||||
chan.pushMessage(
|
chan.pushMessage(
|
||||||
this,
|
this,
|
||||||
|
@ -19,7 +23,9 @@ exports.input = function ({irc, nick}, chan, cmd, args) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.length === 0) {
|
const target = args.filter((arg) => arg !== "");
|
||||||
|
|
||||||
|
if (target.length === 0) {
|
||||||
chan.pushMessage(
|
chan.pushMessage(
|
||||||
this,
|
this,
|
||||||
new Msg({
|
new Msg({
|
||||||
|
@ -40,9 +46,13 @@ exports.input = function ({irc, nick}, chan, cmd, args) {
|
||||||
devoice: "-v",
|
devoice: "-v",
|
||||||
}[cmd];
|
}[cmd];
|
||||||
|
|
||||||
args.forEach(function (target) {
|
const limit = parseInt(irc.network.supports("MODES")) || target.length;
|
||||||
irc.raw("MODE", chan.name, mode, target);
|
|
||||||
});
|
for (let i = 0; i < target.length; i += limit) {
|
||||||
|
const targets = target.slice(i, i + limit);
|
||||||
|
const amode = `${mode[0]}${mode[1].repeat(targets.length)}`;
|
||||||
|
irc.raw("MODE", chan.name, amode, ...targets);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -11,12 +11,6 @@ module.exports = function (irc, network) {
|
||||||
const client = this;
|
const client = this;
|
||||||
|
|
||||||
irc.on("notice", function (data) {
|
irc.on("notice", function (data) {
|
||||||
// Some servers send notices without any nickname
|
|
||||||
if (!data.nick) {
|
|
||||||
data.from_server = true;
|
|
||||||
data.nick = data.hostname || network.host;
|
|
||||||
}
|
|
||||||
|
|
||||||
data.type = Msg.Type.NOTICE;
|
data.type = Msg.Type.NOTICE;
|
||||||
handleMessage(data);
|
handleMessage(data);
|
||||||
});
|
});
|
||||||
|
@ -33,7 +27,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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -44,6 +38,12 @@ module.exports = function (irc, network) {
|
||||||
let showInActive = false;
|
let showInActive = false;
|
||||||
const self = data.nick === irc.user.nick;
|
const self = data.nick === irc.user.nick;
|
||||||
|
|
||||||
|
// Some servers send messages without any nickname
|
||||||
|
if (!data.nick) {
|
||||||
|
data.from_server = true;
|
||||||
|
data.nick = data.hostname || network.host;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the sender is in our ignore list
|
// Check if the sender is in our ignore list
|
||||||
const shouldIgnore =
|
const shouldIgnore =
|
||||||
!self &&
|
!self &&
|
||||||
|
@ -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>",
|
||||||
|
|
|
@ -17,15 +17,38 @@ describe("Commands", function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
const testableNetwork = {
|
const testableNetwork = {
|
||||||
|
firstCommand: null,
|
||||||
lastCommand: null,
|
lastCommand: null,
|
||||||
nick: "xPaw",
|
nick: "xPaw",
|
||||||
irc: {
|
irc: {
|
||||||
|
network: {
|
||||||
|
supports(type) {
|
||||||
|
if (type.toUpperCase() === "MODES") {
|
||||||
|
return "4";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
raw(...args) {
|
raw(...args) {
|
||||||
|
testableNetwork.firstCommand = testableNetwork.lastCommand;
|
||||||
testableNetwork.lastCommand = args.join(" ");
|
testableNetwork.lastCommand = args.join(" ");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const testableNetworkNoSupports = Object.assign({}, testableNetwork, {
|
||||||
|
irc: {
|
||||||
|
network: {
|
||||||
|
supports() {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
raw(...args) {
|
||||||
|
testableNetworkNoSupports.firstCommand = testableNetworkNoSupports.lastCommand;
|
||||||
|
testableNetworkNoSupports.lastCommand = args.join(" ");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
it("should not mess with the given target", function () {
|
it("should not mess with the given target", function () {
|
||||||
const test = function (expected, args) {
|
const test = function (expected, args) {
|
||||||
ModeCommand.input(testableNetwork, channel, "mode", Array.from(args));
|
ModeCommand.input(testableNetwork, channel, "mode", Array.from(args));
|
||||||
|
@ -81,10 +104,34 @@ describe("Commands", function () {
|
||||||
|
|
||||||
ModeCommand.input(testableNetwork, channel, "devoice", ["xPaw"]);
|
ModeCommand.input(testableNetwork, channel, "devoice", ["xPaw"]);
|
||||||
expect(testableNetwork.lastCommand).to.equal("MODE #thelounge -v xPaw");
|
expect(testableNetwork.lastCommand).to.equal("MODE #thelounge -v xPaw");
|
||||||
|
});
|
||||||
|
|
||||||
// Multiple arguments are supported, sent as separate commands
|
it("should use ISUPPORT MODES on shorthand commands", function () {
|
||||||
ModeCommand.input(testableNetwork, channel, "devoice", ["xPaw", "Max-P"]);
|
ModeCommand.input(testableNetwork, channel, "voice", ["xPaw", "Max-P"]);
|
||||||
expect(testableNetwork.lastCommand).to.equal("MODE #thelounge -v Max-P");
|
expect(testableNetwork.lastCommand).to.equal("MODE #thelounge +vv xPaw Max-P");
|
||||||
|
|
||||||
|
// since the limit for modes on tests is 4, it should send two commands
|
||||||
|
ModeCommand.input(testableNetwork, channel, "devoice", [
|
||||||
|
"xPaw",
|
||||||
|
"Max-P",
|
||||||
|
"hey",
|
||||||
|
"idk",
|
||||||
|
"thelounge",
|
||||||
|
]);
|
||||||
|
expect(testableNetwork.firstCommand).to.equal(
|
||||||
|
"MODE #thelounge -vvvv xPaw Max-P hey idk"
|
||||||
|
);
|
||||||
|
expect(testableNetwork.lastCommand).to.equal("MODE #thelounge -v thelounge");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fallback to all modes at once for shorthand commands", function () {
|
||||||
|
ModeCommand.input(testableNetworkNoSupports, channel, "voice", ["xPaw"]);
|
||||||
|
expect(testableNetworkNoSupports.lastCommand).to.equal("MODE #thelounge +v xPaw");
|
||||||
|
|
||||||
|
ModeCommand.input(testableNetworkNoSupports, channel, "devoice", ["xPaw", "Max-P"]);
|
||||||
|
expect(testableNetworkNoSupports.lastCommand).to.equal(
|
||||||
|
"MODE #thelounge -vv xPaw Max-P"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
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";
|
||||||
|
|
|
@ -46,6 +46,10 @@ describe("Network", function () {
|
||||||
saslPassword: "testpassword",
|
saslPassword: "testpassword",
|
||||||
commands: [],
|
commands: [],
|
||||||
nick: "chillin`",
|
nick: "chillin`",
|
||||||
|
proxyEnabled: false,
|
||||||
|
proxyHost: "",
|
||||||
|
proxyPort: 1080,
|
||||||
|
proxyUsername: "",
|
||||||
channels: [
|
channels: [
|
||||||
{name: "#thelounge", key: ""},
|
{name: "#thelounge", key: ""},
|
||||||
{name: "&foobar", key: ""},
|
{name: "&foobar", key: ""},
|
||||||
|
@ -91,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;
|
||||||
|
@ -103,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;
|
||||||
});
|
});
|
||||||
|
@ -265,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);
|
||||||
|
@ -278,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);
|
||||||
|
@ -299,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);
|
||||||
|
@ -393,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);
|
||||||
|
@ -404,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 a new issue