Compare commits

..

No commits in common. "411e3d38e097f5d2e293a3ee4ec4bf01f21fb321" and "747107169b162898a244a10a1464c45716bca09c" have entirely different histories.

4 changed files with 153 additions and 220 deletions

View File

@ -9,10 +9,9 @@
<h2 class="help-version-title">
<span>About Hard Lounge</span>
<small>
v{{
store.state.serverConfiguration?.version
}}
(<router-link id="view-changelog" to="/changelog"
v{{ store.state.serverConfiguration?.version }} (<router-link
id="view-changelog"
to="/changelog"
>release notes</router-link
>)
</small>
@ -54,8 +53,8 @@
<div class="help-item">
<div class="description">
<p>
IRC.SUPERNETS.ORG #SUPERBOWL FUCK YOUR NETWORK COLD HARD
CHATS THIS IS NOT YOUR DADS FOOTBALL CHANNEL
IRC.SUPERNETS.ORG #SUPERBOWL FUCK YOUR NETWORK COLD HARD CHATS THIS IS NOT
YOUR DADS FOOTBALL CHANNEL
</p>
</div>
</div>
@ -94,9 +93,7 @@
<div class="help-item">
<div class="subject">
<span v-if="!isApple"
><kbd>Alt</kbd> <kbd>Shift</kbd> <kbd></kbd></span
>
<span v-if="!isApple"><kbd>Alt</kbd> <kbd>Shift</kbd> <kbd></kbd></span>
<span v-else><kbd></kbd> <kbd></kbd> <kbd></kbd></span>
</div>
<div class="description">
@ -106,9 +103,7 @@
<div class="help-item">
<div class="subject">
<span v-if="!isApple"
><kbd>Alt</kbd> <kbd>Shift</kbd> <kbd></kbd></span
>
<span v-if="!isApple"><kbd>Alt</kbd> <kbd>Shift</kbd> <kbd></kbd></span>
<span v-else><kbd></kbd> <kbd></kbd> <kbd></kbd></span>
</div>
<div class="description">
@ -118,9 +113,7 @@
<div class="help-item">
<div class="subject">
<span v-if="!isApple"
><kbd>Alt</kbd> <kbd>Shift</kbd> <kbd></kbd></span
>
<span v-if="!isApple"><kbd>Alt</kbd> <kbd>Shift</kbd> <kbd></kbd></span>
<span v-else><kbd></kbd> <kbd></kbd> <kbd></kbd></span>
</div>
<div class="description">
@ -130,9 +123,7 @@
<div class="help-item">
<div class="subject">
<span v-if="!isApple"
><kbd>Alt</kbd> <kbd>Shift</kbd> <kbd></kbd></span
>
<span v-if="!isApple"><kbd>Alt</kbd> <kbd>Shift</kbd> <kbd></kbd></span>
<span v-else><kbd></kbd> <kbd></kbd> <kbd></kbd></span>
</div>
<div class="description">
@ -226,8 +217,8 @@
</div>
<div class="description">
<p>
Close current contextual window (context menu, image
viewer, topic edit, etc) and remove focus from input.
Close current contextual window (context menu, image viewer, topic edit,
etc) and remove focus from input.
</p>
</div>
</div>
@ -241,17 +232,15 @@
</div>
<div class="description">
<p>
Mark any text typed after this shortcut to be colored.
After hitting this shortcut, enter an integer in the
range
<code>015</code> to select the desired color, or use
the autocompletion menu to choose a color name (see
below).
Mark any text typed after this shortcut to be colored. After hitting this
shortcut, enter an integer in the range
<code>015</code> to select the desired color, or use the autocompletion
menu to choose a color name (see below).
</p>
<p>
Background color can be specified by putting a comma and
another integer in the range <code>015</code> after the
foreground color number (autocompletion works too).
Background color can be specified by putting a comma and another integer in
the range <code>015</code> after the foreground color number
(autocompletion works too).
</p>
<p>
A color reference can be found
@ -337,8 +326,8 @@
</div>
<div class="description">
<p>
Mark all text typed after this shortcut to be reset to
its original formatting.
Mark all text typed after this shortcut to be reset to its original
formatting.
</p>
</div>
</div>
@ -346,11 +335,10 @@
<h2>Autocompletion</h2>
<p>
To auto-complete nicknames, channels and commands, type one of
the characters below to open a suggestion list. Use the
<kbd></kbd> and <kbd></kbd> keys to highlight an item, and
insert it by pressing <kbd>Tab</kbd> or <kbd>Enter</kbd> (or by
clicking the desired item).
To auto-complete nicknames, channels, commands, and emoji, type one of the
characters below to open a suggestion list. Use the <kbd></kbd> and
<kbd></kbd> keys to highlight an item, and insert it by pressing <kbd>Tab</kbd> or
<kbd>Enter</kbd> (or by clicking the desired item).
</p>
<p>Autocompletion can be disabled in settings.</p>
@ -381,6 +369,18 @@
</div>
</div>
<div class="help-item">
<div class="subject">
<code>:</code>
</div>
<div class="description">
<p>
Emoji (note: requires two search characters, to avoid conflicting with
common emoticons like <code>:)</code>)
</p>
</div>
</div>
<h2>Commands</h2>
<div class="help-item">
@ -397,9 +397,7 @@
<code>/back</code>
</div>
<div class="description">
<p>
Remove your away status (set with <code>/away</code>).
</p>
<p>Remove your away status (set with <code>/away</code>).</p>
</div>
</div>
@ -409,8 +407,8 @@
</div>
<div class="description">
<p>
Ban (<code>+b</code>) a user from the current channel.
This can be a nickname or a hostmask.
Ban (<code>+b</code>) a user from the current channel. This can be a
nickname or a hostmask.
</p>
</div>
</div>
@ -430,8 +428,7 @@
</div>
<div class="description">
<p>
Collapse all previews in the current channel (opposite
of
Collapse all previews in the current channel (opposite of
<code>/expand</code>)
</p>
</div>
@ -443,9 +440,8 @@
</div>
<div class="description">
<p>
Connect to a new IRC network. If
<code>port</code> starts with a <code>+</code> sign, the
connection will be made secure using TLS.
Connect to a new IRC network. If <code>port</code> starts with a
<code>+</code> sign, the connection will be made secure using TLS.
</p>
<p>Alias: <code>/server</code></p>
</div>
@ -457,8 +453,7 @@
</div>
<div class="description">
<p>
Send a
<abbr title="Client-to-client protocol">CTCP</abbr>
Send a <abbr title="Client-to-client protocol">CTCP</abbr>
request. Read more about this on
<a
href="https://en.wikipedia.org/wiki/Client-to-client_protocol"
@ -476,8 +471,8 @@
</div>
<div class="description">
<p>
Remove op (<code>-o</code>) from one or several users in
the current channel.
Remove op (<code>-o</code>) from one or several users in the current
channel.
</p>
</div>
</div>
@ -488,8 +483,8 @@
</div>
<div class="description">
<p>
Remove voice (<code>-v</code>) from one or several users
in the current channel.
Remove voice (<code>-v</code>) from one or several users in the current
channel.
</p>
</div>
</div>
@ -499,10 +494,7 @@
<code>/disconnect [message]</code>
</div>
<div class="description">
<p>
Disconnect from the current network with an
optionally-provided message.
</p>
<p>Disconnect from the current network with an optionally-provided message.</p>
</div>
</div>
@ -525,8 +517,8 @@
<div class="description">
<p>
Invite a user to the specified channel. If
<code>channel</code> is omitted, user will be invited to
the current channel.
<code>channel</code> is omitted, user will be invited to the current
channel.
</p>
</div>
</div>
@ -537,8 +529,8 @@
</div>
<div class="description">
<p>
Block any messages from the specified user on the
current network. This can be a nickname or a hostmask.
Block any messages from the specified user on the current network. This can
be a nickname or a hostmask.
</p>
</div>
</div>
@ -548,9 +540,7 @@
<code>/ignorelist</code>
</div>
<div class="description">
<p>
Load the list of ignored users for the current network.
</p>
<p>Load the list of ignored users for the current network.</p>
</div>
</div>
@ -560,8 +550,8 @@
</div>
<div class="description">
<p>
Join a channel. Password is only needed in protected
channels and can usually be omitted.
Join a channel. Password is only needed in protected channels and can
usually be omitted.
</p>
</div>
</div>
@ -581,10 +571,8 @@
</div>
<div class="description">
<p>
Kick and ban (<code>+b</code>) a user from the current
channel. Unlike
<code>/ban</code>, only nicknames (and not host masks)
can be used.
Kick and ban (<code>+b</code>) a user from the current channel. Unlike
<code>/ban</code>, only nicknames (and not host masks) can be used.
</p>
</div>
</div>
@ -594,9 +582,7 @@
<code>/list</code>
</div>
<div class="description">
<p>
Retrieve a list of available channels on this network.
</p>
<p>Retrieve a list of available channels on this network.</p>
</div>
</div>
@ -606,9 +592,8 @@
</div>
<div class="description">
<p>
Send an action message to the current channel. Hard
Lounge will display it inline, as if the message was
posted in the third person.
Send an action message to the current channel. Hard Lounge will display it
inline, as if the message was posted in the third person.
</p>
</div>
</div>
@ -619,10 +604,9 @@
</div>
<div class="description">
<p>
Set the given flags to the current channel if the active
window is a channel, another user if the active window
is a private message window, or yourself if the current
window is a server window.
Set the given flags to the current channel if the active window is a
channel, another user if the active window is a private message window, or
yourself if the current window is a server window.
</p>
</div>
</div>
@ -642,12 +626,10 @@
</div>
<div class="description">
<p>
Prevent messages from generating any feedback for a
channel. This turns off the highlight indicator, hides
mentions and inhibits push notifications. Muting a
network lobby mutes the entire network. Not specifying
any channel target mutes the current channel. Revert
with <code>/unmute</code>.
Prevent messages from generating any feedback for a channel. This turns off
the highlight indicator, hides mentions and inhibits push notifications.
Muting a network lobby mutes the entire network. Not specifying any channel
target mutes the current channel. Revert with <code>/unmute</code>.
</p>
</div>
</div>
@ -675,10 +657,7 @@
<code>/op nick [...nick]</code>
</div>
<div class="description">
<p>
Give op (<code>+o</code>) to one or several users in the
current channel.
</p>
<p>Give op (<code>+o</code>) to one or several users in the current channel.</p>
</div>
</div>
@ -688,9 +667,8 @@
</div>
<div class="description">
<p>
Close the specified channel or private message window,
or the current channel if <code>channel</code> is
omitted.
Close the specified channel or private message window, or the current
channel if <code>channel</code> is omitted.
</p>
<p>Aliases: <code>/close</code>, <code>/leave</code></p>
</div>
@ -702,9 +680,8 @@
</div>
<div class="description">
<p>
Leave and immediately rejoin the current channel. Useful
to quickly get op from ChanServ in an empty channel, for
example.
Leave and immediately rejoin the current channel. Useful to quickly get op
from ChanServ in an empty channel, for example.
</p>
<p>Alias: <code>/cycle</code></p>
</div>
@ -724,10 +701,7 @@
<code>/quit [message]</code>
</div>
<div class="description">
<p>
Disconnect from the current network with an optional
message.
</p>
<p>Disconnect from the current network with an optional message.</p>
</div>
</div>
@ -765,9 +739,8 @@
</div>
<div class="description">
<p>
Get the topic in the current channel. If
<code>newtopic</code> is specified, sets the topic in
the current channel.
Get the topic in the current channel. If <code>newtopic</code> is specified,
sets the topic in the current channel.
</p>
</div>
</div>
@ -778,8 +751,8 @@
</div>
<div class="description">
<p>
Unban (<code>-b</code>) a user from the current channel.
This can be a nickname or a hostmask.
Unban (<code>-b</code>) a user from the current channel. This can be a
nickname or a hostmask.
</p>
</div>
</div>
@ -790,8 +763,8 @@
</div>
<div class="description">
<p>
Unblock messages from the specified user on the current
network. This can be a nickname or a hostmask.
Unblock messages from the specified user on the current network. This can be
a nickname or a hostmask.
</p>
</div>
</div>
@ -802,9 +775,8 @@
</div>
<div class="description">
<p>
Un-mutes the given channel(s) or the current channel if
no channel is provided. See <code>/mute</code> for more
information.
Un-mutes the given channel(s) or the current channel if no channel is
provided. See <code>/mute</code> for more information.
</p>
</div>
</div>
@ -815,8 +787,7 @@
</div>
<div class="description">
<p>
Give voice (<code>+v</code>) to one or several users in
the current channel.
Give voice (<code>+v</code>) to one or several users in the current channel.
</p>
</div>
</div>
@ -826,10 +797,7 @@
<code>/whois nick</code>
</div>
<div class="description">
<p>
Retrieve information about the given user on the current
network.
</p>
<p>Retrieve information about the given user on the current network.</p>
</div>
</div>
@ -1225,8 +1193,8 @@
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
import { useStore } from "../../js/store";
import {defineComponent, ref} from "vue";
import {useStore} from "../../js/store";
import SidebarToggle from "../SidebarToggle.vue";
import VersionChecker from "../VersionChecker.vue";
@ -1238,8 +1206,7 @@ export default defineComponent({
},
setup() {
const store = useStore();
const isApple =
navigator.platform.match(/(Mac|iPhone|iPod|iPad)/i) || false;
const isApple = navigator.platform.match(/(Mac|iPhone|iPod|iPad)/i) || false;
const isTouch = navigator.maxTouchPoints > 0;
return {

View File

@ -1,15 +1,35 @@
import constants from "./constants";
import Mousetrap from "mousetrap";
import { Strategy, Textcomplete, StrategyProps } from "@textcomplete/core";
import { TextareaEditor } from "@textcomplete/textarea";
import {Strategy, Textcomplete, StrategyProps} from "@textcomplete/core";
import {TextareaEditor} from "@textcomplete/textarea";
import fuzzy from "fuzzy";
import { store } from "./store";
import emojiMap from "./helpers/simplemap.json";
import {store} from "./store";
export default enableAutocomplete;
const emojiSearchTerms = Object.keys(emojiMap);
const emojiStrategy: StrategyProps = {
id: "emoji",
match: /(^|\s):([-+\w:?]{2,}):?$/,
search(term: string, callback: (matches) => void) {
// Trim colon from the matched term,
// as we are unable to get a clean string from match regex
term = term.replace(/:$/, "");
callback(fuzzyGrep(term, emojiSearchTerms));
},
template([string, original]: [string, string]) {
return `<span class="emoji">${String(emojiMap[original])}</span> ${string}`;
},
replace([, original]: [string, string]) {
return "$1" + String(emojiMap[original]);
},
index: 2,
};
const nicksStrategy: StrategyProps = {
id: "nicks",
match: /(^|\s)(@([a-zA-Z_[\]\\^{}|`@][a-zA-Z0-9_[\]\\^{}|`-]*)?)$/,
@ -19,12 +39,7 @@ const nicksStrategy: StrategyProps = {
if (term[0] === "@") {
// TODO: type
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
callback(
completeNicks(term.slice(1), true).map((val) => [
"@" + val[0],
"@" + val[1],
])
);
callback(completeNicks(term.slice(1), true).map((val) => ["@" + val[0], "@" + val[1]]));
} else {
callback(completeNicks(term, true));
}
@ -93,9 +108,7 @@ const foregroundColorStrategy: StrategyProps = {
callback(matchingColorCodes);
},
template(value: string[]) {
return `<span class="irc-fg${parseInt(value[0], 10)}">${
value[1]
}</span>`;
return `<span class="irc-fg${parseInt(value[0], 10)}">${value[1]}</span>`;
},
replace(value: string) {
return "\x03" + value[0];
@ -106,11 +119,7 @@ const foregroundColorStrategy: StrategyProps = {
const backgroundColorStrategy: StrategyProps = {
id: "background-colors",
match: /\x03(\d{2}),(\d{0,2}|[A-Za-z ]{0,10})$/,
search(
term: string,
callback: (matchingColorCodes: string[][]) => void,
match: string[]
) {
search(term: string, callback: (matchingColorCodes: string[][]) => void, match: string[]) {
term = term.toLowerCase();
const matchingColorCodes = constants.colorCodeMap
.filter((i) => fuzzy.test(term, i[0]) || fuzzy.test(term, i[1]))
@ -132,10 +141,10 @@ const backgroundColorStrategy: StrategyProps = {
callback(matchingColorCodes);
},
template(value: string[]) {
return `<span class="irc-fg${parseInt(
value[2],
return `<span class="irc-fg${parseInt(value[2], 10)} irc-bg irc-bg${parseInt(
value[0],
10
)} irc-bg irc-bg${parseInt(value[0], 10)}">${value[1]}</span>`;
)}">${value[1]}</span>`;
},
replace(value: string[]) {
return "\x03$1," + value[0];
@ -170,9 +179,7 @@ function enableAutocomplete(input: HTMLTextAreaElement) {
const text = input.value;
if (tabCount === 0) {
lastMatch =
text.substring(0, input.selectionStart).split(/\s/).pop() ||
"";
lastMatch = text.substring(0, input.selectionStart).split(/\s/).pop() || "";
if (lastMatch.length === 0) {
return;
@ -212,6 +219,7 @@ function enableAutocomplete(input: HTMLTextAreaElement) {
);
const strategies = [
emojiStrategy,
nicksStrategy,
chanStrategy,
commandStrategy,
@ -253,10 +261,7 @@ function replaceNick(original: string, position = 1) {
}
// If there is whitespace in the input already, append space to nick
if (
position > 0 &&
/\s/.test(store.state.activeChannel?.channel.pendingMessage || "")
) {
if (position > 0 && /\s/.test(store.state.activeChannel?.channel.pendingMessage || "")) {
return original + " ";
}
@ -280,19 +285,14 @@ function rawNicks() {
if (store.state.activeChannel.channel.users.length > 0) {
const users = store.state.activeChannel.channel.users.slice();
return users
.sort((a, b) => b.lastMessage - a.lastMessage)
.map((u) => u.nick);
return users.sort((a, b) => b.lastMessage - a.lastMessage).map((u) => u.nick);
}
const me = store.state.activeChannel.network.nick;
const otherUser = store.state.activeChannel.channel.name;
// If this is a query, add their name to autocomplete
if (
me !== otherUser &&
store.state.activeChannel.channel.type === "query"
) {
if (me !== otherUser && store.state.activeChannel.channel.type === "query") {
return [otherUser, me];
}

View File

@ -1,7 +1,7 @@
services:
hardlounge:
image: git.supernets.org/supernets/hardlounge:latest
build: .
#build: .
ports:
- "9000:9000"
volumes:

View File

@ -1,4 +1,4 @@
import { expect } from "chai";
import {expect} from "chai";
import fs from "fs";
import path from "path";
@ -6,102 +6,68 @@ describe("public folder", function () {
const publicFolder = path.join(__dirname, "..", "..", "public");
it("font awesome files are copied", function () {
expect(
fs.existsSync(path.join(publicFolder, "fonts", "fa-solid-900.woff"))
).to.be.true;
expect(
fs.existsSync(
path.join(publicFolder, "fonts", "fa-solid-900.woff2")
)
).to.be.true;
expect(fs.existsSync(path.join(publicFolder, "fonts", "fa-solid-900.woff"))).to.be.true;
expect(fs.existsSync(path.join(publicFolder, "fonts", "fa-solid-900.woff2"))).to.be.true;
});
it("files in root folder are copied", function () {
expect(fs.existsSync(path.join(publicFolder, "favicon.ico"))).to.be
.true;
expect(fs.existsSync(path.join(publicFolder, "favicon.ico"))).to.be.true;
expect(fs.existsSync(path.join(publicFolder, "robots.txt"))).to.be.true;
expect(fs.existsSync(path.join(publicFolder, "service-worker.js"))).to
.be.true;
expect(fs.existsSync(path.join(publicFolder, "thelounge.webmanifest")))
.to.be.true;
expect(fs.existsSync(path.join(publicFolder, "service-worker.js"))).to.be.true;
expect(fs.existsSync(path.join(publicFolder, "thelounge.webmanifest"))).to.be.true;
});
it("audio files are copied", function () {
expect(fs.existsSync(path.join(publicFolder, "audio", "pop.wav"))).to.be
.true;
expect(fs.existsSync(path.join(publicFolder, "audio", "pop.wav"))).to.be.true;
});
it("index HTML file is not copied", function () {
expect(fs.existsSync(path.join(publicFolder, "index.html"))).to.be
.false;
expect(fs.existsSync(path.join(publicFolder, "index.html.tpl"))).to.be
.false;
expect(fs.existsSync(path.join(publicFolder, "index.html"))).to.be.false;
expect(fs.existsSync(path.join(publicFolder, "index.html.tpl"))).to.be.false;
});
it("javascript files are built", function () {
expect(fs.existsSync(path.join(publicFolder, "js", "bundle.js"))).to.be
.true;
expect(fs.existsSync(path.join(publicFolder, "js", "bundle.vendor.js")))
.to.be.true;
expect(fs.existsSync(path.join(publicFolder, "js", "bundle.js"))).to.be.true;
expect(fs.existsSync(path.join(publicFolder, "js", "bundle.vendor.js"))).to.be.true;
});
it("style files are built", function () {
expect(fs.existsSync(path.join(publicFolder, "css", "style.css"))).to.be
.true;
expect(fs.existsSync(path.join(publicFolder, "css", "style.css.map")))
.to.be.true;
expect(fs.existsSync(path.join(publicFolder, "themes", "default.css")))
.to.be.true;
expect(fs.existsSync(path.join(publicFolder, "themes", "morning.css")))
.to.be.true;
expect(fs.existsSync(path.join(publicFolder, "themes", "oled.css"))).to
.be.true;
expect(fs.existsSync(path.join(publicFolder, "css", "style.css"))).to.be.true;
expect(fs.existsSync(path.join(publicFolder, "css", "style.css.map"))).to.be.true;
expect(fs.existsSync(path.join(publicFolder, "themes", "default.css"))).to.be.true;
expect(fs.existsSync(path.join(publicFolder, "themes", "morning.css"))).to.be.true;
});
it("style files contain expected content", function (done) {
fs.readFile(
path.join(publicFolder, "css", "style.css"),
"utf8",
function (err, contents) {
expect(err).to.be.null;
fs.readFile(path.join(publicFolder, "css", "style.css"), "utf8", function (err, contents) {
expect(err).to.be.null;
expect(contents.includes("var(--body-color)")).to.be.true;
expect(contents.includes("url(../fonts/fa-solid-900.woff2)")).to
.be.true;
expect(contents.includes(".tooltipped{position:relative}")).to
.be.true;
expect(contents.includes("sourceMappingURL")).to.be.true;
expect(contents.includes("var(--body-color)")).to.be.true;
expect(contents.includes("url(../fonts/fa-solid-900.woff2)")).to.be.true;
expect(contents.includes(".tooltipped{position:relative}")).to.be.true;
expect(contents.includes("sourceMappingURL")).to.be.true;
done();
}
);
done();
});
});
it("javascript map is created", function () {
expect(fs.existsSync(path.join(publicFolder, "js", "bundle.js.map"))).to
.be.true;
expect(fs.existsSync(path.join(publicFolder, "js", "bundle.js.map"))).to.be.true;
});
it("loading-error-handlers.js is copied", function () {
expect(
fs.existsSync(
path.join(publicFolder, "js", "loading-error-handlers.js")
)
).to.be.true;
expect(fs.existsSync(path.join(publicFolder, "js", "loading-error-handlers.js"))).to.be
.true;
});
it("service worker has cacheName set", function (done) {
fs.readFile(
path.join(publicFolder, "service-worker.js"),
"utf8",
function (err, contents) {
expect(err).to.be.null;
fs.readFile(path.join(publicFolder, "service-worker.js"), "utf8", function (err, contents) {
expect(err).to.be.null;
expect(contents.includes("const cacheName")).to.be.true;
expect(contents.includes("__HASH__")).to.be.false;
expect(contents.includes("const cacheName")).to.be.true;
expect(contents.includes("__HASH__")).to.be.false;
done();
}
);
done();
});
});
});