Format js/vue with prettier
This commit is contained in:
parent
48eeb11391
commit
133e7bf710
@ -1,8 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div id="viewport" role="tablist">
|
||||||
id="viewport"
|
|
||||||
role="tablist"
|
|
||||||
>
|
|
||||||
<aside id="sidebar">
|
<aside id="sidebar">
|
||||||
<div class="scrollable-area">
|
<div class="scrollable-area">
|
||||||
<div class="logo-container">
|
<div class="logo-container">
|
||||||
@ -10,62 +7,55 @@
|
|||||||
: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"
|
||||||
>
|
/>
|
||||||
<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"
|
||||||
>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<NetworkList
|
<NetworkList :networks="networks" :active-channel="activeChannel" />
|
||||||
:networks="networks"
|
|
||||||
:active-channel="activeChannel"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<footer id="footer">
|
<footer id="footer">
|
||||||
<span
|
<span class="tooltipped tooltipped-n tooltipped-no-touch" aria-label="Sign in"
|
||||||
class="tooltipped tooltipped-n tooltipped-no-touch"
|
><button
|
||||||
aria-label="Sign in"
|
class="icon sign-in"
|
||||||
><button
|
data-target="#sign-in"
|
||||||
class="icon sign-in"
|
aria-label="Sign in"
|
||||||
data-target="#sign-in"
|
role="tab"
|
||||||
aria-label="Sign in"
|
aria-controls="sign-in"
|
||||||
role="tab"
|
aria-selected="false"
|
||||||
aria-controls="sign-in"
|
|
||||||
aria-selected="false"
|
|
||||||
/></span>
|
/></span>
|
||||||
<span
|
<span
|
||||||
class="tooltipped tooltipped-n tooltipped-no-touch"
|
class="tooltipped tooltipped-n tooltipped-no-touch"
|
||||||
aria-label="Connect to network"
|
aria-label="Connect to network"
|
||||||
><button
|
><button
|
||||||
class="icon connect"
|
class="icon connect"
|
||||||
data-target="#connect"
|
data-target="#connect"
|
||||||
aria-label="Connect to network"
|
aria-label="Connect to network"
|
||||||
role="tab"
|
role="tab"
|
||||||
aria-controls="connect"
|
aria-controls="connect"
|
||||||
aria-selected="false"
|
aria-selected="false"
|
||||||
/></span>
|
/></span>
|
||||||
<span
|
<span class="tooltipped tooltipped-n tooltipped-no-touch" aria-label="Settings"
|
||||||
class="tooltipped tooltipped-n tooltipped-no-touch"
|
><button
|
||||||
aria-label="Settings"
|
class="icon settings"
|
||||||
><button
|
data-target="#settings"
|
||||||
class="icon settings"
|
aria-label="Settings"
|
||||||
data-target="#settings"
|
role="tab"
|
||||||
aria-label="Settings"
|
aria-controls="settings"
|
||||||
role="tab"
|
aria-selected="false"
|
||||||
aria-controls="settings"
|
|
||||||
aria-selected="false"
|
|
||||||
/></span>
|
/></span>
|
||||||
<span
|
<span class="tooltipped tooltipped-n tooltipped-no-touch" aria-label="Help"
|
||||||
class="tooltipped tooltipped-n tooltipped-no-touch"
|
><button
|
||||||
aria-label="Help"
|
class="icon help"
|
||||||
><button
|
data-target="#help"
|
||||||
class="icon help"
|
aria-label="Help"
|
||||||
data-target="#help"
|
role="tab"
|
||||||
aria-label="Help"
|
aria-controls="help"
|
||||||
role="tab"
|
aria-selected="false"
|
||||||
aria-controls="help"
|
|
||||||
aria-selected="false"
|
|
||||||
/></span>
|
/></span>
|
||||||
</footer>
|
</footer>
|
||||||
</aside>
|
</aside>
|
||||||
@ -76,35 +66,11 @@
|
|||||||
:network="activeChannel.network"
|
:network="activeChannel.network"
|
||||||
:channel="activeChannel.channel"
|
:channel="activeChannel.channel"
|
||||||
/>
|
/>
|
||||||
<div
|
<div id="sign-in" class="window" role="tabpanel" aria-label="Sign-in" />
|
||||||
id="sign-in"
|
<div id="connect" class="window" role="tabpanel" aria-label="Connect" />
|
||||||
class="window"
|
<div id="settings" class="window" role="tabpanel" aria-label="Settings" />
|
||||||
role="tabpanel"
|
<div id="help" class="window" role="tabpanel" aria-label="Help" />
|
||||||
aria-label="Sign-in"
|
<div id="changelog" class="window" aria-label="Changelog" />
|
||||||
/>
|
|
||||||
<div
|
|
||||||
id="connect"
|
|
||||||
class="window"
|
|
||||||
role="tabpanel"
|
|
||||||
aria-label="Connect"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
id="settings"
|
|
||||||
class="window"
|
|
||||||
role="tabpanel"
|
|
||||||
aria-label="Settings"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
id="help"
|
|
||||||
class="window"
|
|
||||||
role="tabpanel"
|
|
||||||
aria-label="Help"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
id="changelog"
|
|
||||||
class="window"
|
|
||||||
aria-label="Changelog"
|
|
||||||
/>
|
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,15 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<ChannelWrapper
|
<ChannelWrapper :network="network" :channel="channel" :active-channel="activeChannel">
|
||||||
:network="network"
|
|
||||||
:channel="channel"
|
|
||||||
:active-channel="activeChannel"
|
|
||||||
>
|
|
||||||
<span class="name">{{ channel.name }}</span>
|
<span class="name">{{ channel.name }}</span>
|
||||||
<span
|
<span v-if="channel.unread" :class="{highlight: channel.highlight}" class="badge">{{
|
||||||
v-if="channel.unread"
|
channel.unread | roundBadgeNumber
|
||||||
:class="{ highlight: channel.highlight }"
|
}}</span>
|
||||||
class="badge"
|
|
||||||
>{{ channel.unread | roundBadgeNumber }}</span>
|
|
||||||
<template v-if="channel.type === 'channel'">
|
<template v-if="channel.type === 'channel'">
|
||||||
<span
|
<span
|
||||||
v-if="channel.state === 0"
|
v-if="channel.state === 0"
|
||||||
@ -18,25 +12,13 @@
|
|||||||
>
|
>
|
||||||
<span class="parted-channel-icon" />
|
<span class="parted-channel-icon" />
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span class="close-tooltip tooltipped tooltipped-w" aria-label="Leave">
|
||||||
class="close-tooltip tooltipped tooltipped-w"
|
<button class="close" aria-label="Leave" />
|
||||||
aria-label="Leave"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="close"
|
|
||||||
aria-label="Leave"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<span
|
<span class="close-tooltip tooltipped tooltipped-w" aria-label="Close">
|
||||||
class="close-tooltip tooltipped tooltipped-w"
|
<button class="close" aria-label="Close" />
|
||||||
aria-label="Close"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="close"
|
|
||||||
aria-label="Close"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</ChannelWrapper>
|
</ChannelWrapper>
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-if="!network.isCollapsed || channel.highlight || channel.type === 'lobby' || (activeChannel && channel === activeChannel.channel)"
|
v-if="
|
||||||
|
!network.isCollapsed ||
|
||||||
|
channel.highlight ||
|
||||||
|
channel.type === 'lobby' ||
|
||||||
|
(activeChannel && channel === activeChannel.channel)
|
||||||
|
"
|
||||||
:class="[
|
:class="[
|
||||||
'chan',
|
'chan',
|
||||||
channel.type,
|
channel.type,
|
||||||
{ active: activeChannel && channel === activeChannel.channel },
|
{active: activeChannel && channel === activeChannel.channel},
|
||||||
{ 'parted-channel': channel.type === 'channel' && channel.state === 0 }
|
{'parted-channel': channel.type === 'channel' && channel.state === 0},
|
||||||
]"
|
]"
|
||||||
:aria-label="getAriaLabel()"
|
:aria-label="getAriaLabel()"
|
||||||
:title="getAriaLabel()"
|
:title="getAriaLabel()"
|
||||||
@ -16,11 +21,7 @@
|
|||||||
:aria-selected="activeChannel && channel === activeChannel.channel"
|
:aria-selected="activeChannel && channel === activeChannel.channel"
|
||||||
role="tab"
|
role="tab"
|
||||||
>
|
>
|
||||||
<slot
|
<slot :network="network" :channel="channel" :activeChannel="activeChannel" />
|
||||||
:network="network"
|
|
||||||
:channel="channel"
|
|
||||||
:activeChannel="activeChannel"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div id="chat-container" class="window">
|
||||||
id="chat-container"
|
|
||||||
class="window"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
id="chat"
|
id="chat"
|
||||||
:data-id="channel.id"
|
:data-id="channel.id"
|
||||||
@ -21,38 +18,24 @@
|
|||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
>
|
>
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<button
|
<button class="lt" aria-label="Toggle channel list" />
|
||||||
class="lt"
|
|
||||||
aria-label="Toggle channel list"
|
|
||||||
/>
|
|
||||||
<span class="title">{{ channel.name }}</span>
|
<span class="title">{{ channel.name }}</span>
|
||||||
<span
|
<span :title="channel.topic" class="topic"
|
||||||
:title="channel.topic"
|
><ParsedMessage
|
||||||
class="topic"
|
v-if="channel.topic"
|
||||||
><ParsedMessage
|
:network="network"
|
||||||
v-if="channel.topic"
|
:text="channel.topic"
|
||||||
:network="network"
|
|
||||||
:text="channel.topic"
|
|
||||||
/></span>
|
/></span>
|
||||||
<button
|
<button class="menu" aria-label="Open the context menu" />
|
||||||
class="menu"
|
|
||||||
aria-label="Open the context menu"
|
|
||||||
/>
|
|
||||||
<span
|
<span
|
||||||
v-if="channel.type === 'channel'"
|
v-if="channel.type === 'channel'"
|
||||||
class="rt-tooltip tooltipped tooltipped-w"
|
class="rt-tooltip tooltipped tooltipped-w"
|
||||||
aria-label="Toggle user list"
|
aria-label="Toggle user list"
|
||||||
>
|
>
|
||||||
<button
|
<button class="rt" aria-label="Toggle user list" />
|
||||||
class="rt"
|
|
||||||
aria-label="Toggle user list"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div v-if="channel.type === 'special'" class="chat-content">
|
||||||
v-if="channel.type === 'special'"
|
|
||||||
class="chat-content"
|
|
||||||
>
|
|
||||||
<div class="chat">
|
<div class="chat">
|
||||||
<div class="messages">
|
<div class="messages">
|
||||||
<div class="msg">
|
<div class="msg">
|
||||||
@ -65,26 +48,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div v-else class="chat-content">
|
||||||
v-else
|
|
||||||
class="chat-content"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
:class="['scroll-down tooltipped tooltipped-w tooltipped-no-touch', {'scroll-down-shown': !channel.scrolledToBottom}]"
|
:class="[
|
||||||
|
'scroll-down tooltipped tooltipped-w tooltipped-no-touch',
|
||||||
|
{'scroll-down-shown': !channel.scrolledToBottom},
|
||||||
|
]"
|
||||||
aria-label="Jump to recent messages"
|
aria-label="Jump to recent messages"
|
||||||
@click="$refs.messageList.jumpToBottom()"
|
@click="$refs.messageList.jumpToBottom()"
|
||||||
>
|
>
|
||||||
<div class="scroll-down-arrow" />
|
<div class="scroll-down-arrow" />
|
||||||
</div>
|
</div>
|
||||||
<MessageList
|
<MessageList ref="messageList" :network="network" :channel="channel" />
|
||||||
ref="messageList"
|
<ChatUserList v-if="channel.type === 'channel'" :channel="channel" />
|
||||||
:network="network"
|
|
||||||
:channel="channel"
|
|
||||||
/>
|
|
||||||
<ChatUserList
|
|
||||||
v-if="channel.type === 'channel'"
|
|
||||||
:channel="channel"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -92,12 +68,11 @@
|
|||||||
v-if="this.$root.currentUserVisibleError"
|
v-if="this.$root.currentUserVisibleError"
|
||||||
id="user-visible-error"
|
id="user-visible-error"
|
||||||
@click="hideUserVisibleError"
|
@click="hideUserVisibleError"
|
||||||
>{{ this.$root.currentUserVisibleError }}</div>
|
>
|
||||||
|
{{ this.$root.currentUserVisibleError }}
|
||||||
|
</div>
|
||||||
<span id="upload-progressbar" />
|
<span id="upload-progressbar" />
|
||||||
<ChatInput
|
<ChatInput :network="network" :channel="channel" />
|
||||||
:network="network"
|
|
||||||
:channel="channel"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -126,10 +101,14 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
specialComponent() {
|
specialComponent() {
|
||||||
switch (this.channel.special) {
|
switch (this.channel.special) {
|
||||||
case "list_bans": return ListBans;
|
case "list_bans":
|
||||||
case "list_invites": return ListInvites;
|
return ListBans;
|
||||||
case "list_channels": return ListChannels;
|
case "list_invites":
|
||||||
case "list_ignored": return ListIgnored;
|
return ListInvites;
|
||||||
|
case "list_channels":
|
||||||
|
return ListChannels;
|
||||||
|
case "list_ignored":
|
||||||
|
return ListIgnored;
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -1,10 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<form
|
<form id="form" method="post" action="" @submit.prevent="onSubmit">
|
||||||
id="form"
|
|
||||||
method="post"
|
|
||||||
action=""
|
|
||||||
@submit.prevent="onSubmit"
|
|
||||||
>
|
|
||||||
<span id="nick">{{ network.nick }}</span>
|
<span id="nick">{{ network.nick }}</span>
|
||||||
<textarea
|
<textarea
|
||||||
id="input"
|
id="input"
|
||||||
@ -23,12 +18,7 @@
|
|||||||
aria-label="Upload file"
|
aria-label="Upload file"
|
||||||
@click="openFileUpload"
|
@click="openFileUpload"
|
||||||
>
|
>
|
||||||
<input
|
<input id="upload-input" ref="uploadInput" type="file" multiple />
|
||||||
id="upload-input"
|
|
||||||
ref="uploadInput"
|
|
||||||
type="file"
|
|
||||||
multiple
|
|
||||||
>
|
|
||||||
<button
|
<button
|
||||||
id="upload"
|
id="upload"
|
||||||
type="button"
|
type="button"
|
||||||
@ -80,7 +70,7 @@ const bracketWraps = {
|
|||||||
"*": "*",
|
"*": "*",
|
||||||
"`": "`",
|
"`": "`",
|
||||||
"~": "~",
|
"~": "~",
|
||||||
"_": "_",
|
_: "_",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -130,7 +120,9 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.channel.inputHistoryPosition === 0) {
|
if (this.channel.inputHistoryPosition === 0) {
|
||||||
this.channel.inputHistory[this.channel.inputHistoryPosition] = this.channel.pendingMessage;
|
this.channel.inputHistory[
|
||||||
|
this.channel.inputHistoryPosition
|
||||||
|
] = this.channel.pendingMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === "up") {
|
if (key === "up") {
|
||||||
@ -141,7 +133,9 @@ export default {
|
|||||||
this.channel.inputHistoryPosition--;
|
this.channel.inputHistoryPosition--;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.channel.pendingMessage = this.$refs.input.value = this.channel.inputHistory[this.channel.inputHistoryPosition];
|
this.channel.pendingMessage = this.$refs.input.value = this.channel.inputHistory[
|
||||||
|
this.channel.inputHistoryPosition
|
||||||
|
];
|
||||||
this.setInputSize();
|
this.setInputSize();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -173,7 +167,8 @@ export default {
|
|||||||
// Use scrollHeight to calculate how many lines there are in input, and ceil the value
|
// Use scrollHeight to calculate how many lines there are in input, and ceil the value
|
||||||
// because some browsers tend to incorrently round the values when using high density
|
// because some browsers tend to incorrently round the values when using high density
|
||||||
// displays or using page zoom feature
|
// displays or using page zoom feature
|
||||||
this.$refs.input.style.height = Math.ceil(this.$refs.input.scrollHeight / lineHeight) * lineHeight + "px";
|
this.$refs.input.style.height =
|
||||||
|
Math.ceil(this.$refs.input.scrollHeight / lineHeight) * lineHeight + "px";
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getInputPlaceholder(channel) {
|
getInputPlaceholder(channel) {
|
||||||
@ -219,7 +214,10 @@ export default {
|
|||||||
const args = text.substr(1).split(" ");
|
const args = text.substr(1).split(" ");
|
||||||
const cmd = args.shift().toLowerCase();
|
const cmd = args.shift().toLowerCase();
|
||||||
|
|
||||||
if (Object.prototype.hasOwnProperty.call(commands, cmd) && commands[cmd].input(args)) {
|
if (
|
||||||
|
Object.prototype.hasOwnProperty.call(commands, cmd) &&
|
||||||
|
commands[cmd].input(args)
|
||||||
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<aside
|
<aside ref="userlist" class="userlist" @mouseleave="removeHoverUser">
|
||||||
ref="userlist"
|
|
||||||
class="userlist"
|
|
||||||
@mouseleave="removeHoverUser"
|
|
||||||
>
|
|
||||||
<div class="count">
|
<div class="count">
|
||||||
<input
|
<input
|
||||||
ref="input"
|
ref="input"
|
||||||
:value="userSearchInput"
|
:value="userSearchInput"
|
||||||
:placeholder="channel.users.length + ' user' + (channel.users.length === 1 ? '' : 's')"
|
:placeholder="
|
||||||
|
channel.users.length + ' user' + (channel.users.length === 1 ? '' : 's')
|
||||||
|
"
|
||||||
type="search"
|
type="search"
|
||||||
class="search"
|
class="search"
|
||||||
aria-label="Search among the user list"
|
aria-label="Search among the user list"
|
||||||
@ -19,7 +17,7 @@
|
|||||||
@keydown.page-up="navigateUserList($event, -10)"
|
@keydown.page-up="navigateUserList($event, -10)"
|
||||||
@keydown.page-down="navigateUserList($event, 10)"
|
@keydown.page-down="navigateUserList($event, 10)"
|
||||||
@keydown.enter="selectUser"
|
@keydown.enter="selectUser"
|
||||||
>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="names">
|
<div class="names">
|
||||||
<div
|
<div
|
||||||
@ -84,15 +82,11 @@ export default {
|
|||||||
// filteredUsers is computed, to avoid unnecessary filtering
|
// filteredUsers is computed, to avoid unnecessary filtering
|
||||||
// as it is shared between filtering and keybindings.
|
// as it is shared between filtering and keybindings.
|
||||||
filteredUsers() {
|
filteredUsers() {
|
||||||
return fuzzy.filter(
|
return fuzzy.filter(this.userSearchInput, this.channel.users, {
|
||||||
this.userSearchInput,
|
pre: "<b>",
|
||||||
this.channel.users,
|
post: "</b>",
|
||||||
{
|
extract: (u) => u.nick,
|
||||||
pre: "<b>",
|
});
|
||||||
post: "</b>",
|
|
||||||
extract: (u) => u.nick,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
groupedUsers() {
|
groupedUsers() {
|
||||||
const groups = {};
|
const groups = {};
|
||||||
|
@ -1,13 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div :aria-label="localeDate" class="date-marker-container tooltipped tooltipped-s">
|
||||||
:aria-label="localeDate"
|
|
||||||
class="date-marker-container tooltipped tooltipped-s"
|
|
||||||
>
|
|
||||||
<div class="date-marker">
|
<div class="date-marker">
|
||||||
<span
|
<span :data-label="friendlyDate()" class="date-marker-text" />
|
||||||
:data-label="friendlyDate()"
|
|
||||||
class="date-marker-text"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
maxlength="200"
|
maxlength="200"
|
||||||
title="The channel name may not contain spaces"
|
title="The channel name may not contain spaces"
|
||||||
required
|
required
|
||||||
>
|
/>
|
||||||
<input
|
<input
|
||||||
v-model="inputPassword"
|
v-model="inputPassword"
|
||||||
type="password"
|
type="password"
|
||||||
@ -30,11 +30,8 @@
|
|||||||
maxlength="200"
|
maxlength="200"
|
||||||
title="The channel password may not contain spaces"
|
title="The channel password may not contain spaces"
|
||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
>
|
/>
|
||||||
<button
|
<button type="submit" class="btn btn-small">Join</button>
|
||||||
type="submit"
|
|
||||||
class="btn btn-small"
|
|
||||||
>Join</button>
|
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -63,7 +60,9 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
onSubmit() {
|
onSubmit() {
|
||||||
const channelToFind = this.inputChannel.toLowerCase();
|
const channelToFind = this.inputChannel.toLowerCase();
|
||||||
const existingChannel = this.network.channels.find((c) => c.name.toLowerCase() === channelToFind);
|
const existingChannel = this.network.channels.find(
|
||||||
|
(c) => c.name.toLowerCase() === channelToFind
|
||||||
|
);
|
||||||
|
|
||||||
if (existingChannel) {
|
if (existingChannel) {
|
||||||
const $ = require("jquery");
|
const $ = require("jquery");
|
||||||
|
@ -1,13 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div v-if="link.shown" v-show="link.canDisplay" ref="container" class="preview">
|
||||||
v-if="link.shown"
|
|
||||||
v-show="link.canDisplay"
|
|
||||||
ref="container"
|
|
||||||
class="preview"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
ref="content"
|
ref="content"
|
||||||
:class="['toggle-content', 'toggle-type-' + link.type, { opened: isContentShown }]"
|
:class="['toggle-content', 'toggle-type-' + link.type, {opened: isContentShown}]"
|
||||||
>
|
>
|
||||||
<template v-if="link.type === 'link'">
|
<template v-if="link.type === 'link'">
|
||||||
<a
|
<a
|
||||||
@ -25,7 +20,7 @@
|
|||||||
@error="onThumbnailError"
|
@error="onThumbnailError"
|
||||||
@abort="onThumbnailError"
|
@abort="onThumbnailError"
|
||||||
@load="onPreviewReady"
|
@load="onPreviewReady"
|
||||||
>
|
/>
|
||||||
</a>
|
</a>
|
||||||
<div class="toggle-text">
|
<div class="toggle-text">
|
||||||
<div class="head">
|
<div class="head">
|
||||||
@ -35,7 +30,8 @@
|
|||||||
:title="link.head"
|
:title="link.head"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener"
|
rel="noopener"
|
||||||
>{{ link.head }}</a>
|
>{{ link.head }}</a
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@ -44,81 +40,48 @@
|
|||||||
:aria-label="moreButtonLabel"
|
:aria-label="moreButtonLabel"
|
||||||
class="more"
|
class="more"
|
||||||
@click="onMoreClick"
|
@click="onMoreClick"
|
||||||
><span class="more-caret" /></button>
|
>
|
||||||
|
<span class="more-caret" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="body overflowable">
|
<div class="body overflowable">
|
||||||
<a
|
<a :href="link.link" :title="link.body" target="_blank" rel="noopener">{{
|
||||||
:href="link.link"
|
link.body
|
||||||
:title="link.body"
|
}}</a>
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
>{{ link.body }}</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="link.type === 'image'">
|
<template v-else-if="link.type === 'image'">
|
||||||
<a
|
<a :href="link.link" class="toggle-thumbnail" target="_blank" rel="noopener">
|
||||||
:href="link.link"
|
<img :src="link.thumb" decoding="async" alt="" @load="onPreviewReady" />
|
||||||
class="toggle-thumbnail"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
:src="link.thumb"
|
|
||||||
decoding="async"
|
|
||||||
alt=""
|
|
||||||
@load="onPreviewReady"
|
|
||||||
>
|
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="link.type === 'video'">
|
<template v-else-if="link.type === 'video'">
|
||||||
<video
|
<video preload="metadata" controls @canplay="onPreviewReady">
|
||||||
preload="metadata"
|
<source :src="link.media" :type="link.mediaType" />
|
||||||
controls
|
|
||||||
@canplay="onPreviewReady"
|
|
||||||
>
|
|
||||||
<source
|
|
||||||
:src="link.media"
|
|
||||||
:type="link.mediaType"
|
|
||||||
>
|
|
||||||
</video>
|
</video>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="link.type === 'audio'">
|
<template v-else-if="link.type === 'audio'">
|
||||||
<audio
|
<audio controls preload="metadata" @canplay="onPreviewReady">
|
||||||
controls
|
<source :src="link.media" :type="link.mediaType" />
|
||||||
preload="metadata"
|
|
||||||
@canplay="onPreviewReady"
|
|
||||||
>
|
|
||||||
<source
|
|
||||||
:src="link.media"
|
|
||||||
:type="link.mediaType"
|
|
||||||
>
|
|
||||||
</audio>
|
</audio>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="link.type === 'error'">
|
<template v-else-if="link.type === 'error'">
|
||||||
<em v-if="link.error === 'image-too-big'">
|
<em v-if="link.error === 'image-too-big'">
|
||||||
This image is larger than {{ link.maxSize | friendlysize }} and cannot be
|
This image is larger than {{ link.maxSize | friendlysize }} and cannot be
|
||||||
previewed.
|
previewed.
|
||||||
<a
|
<a :href="link.link" target="_blank" rel="noopener">Click here</a>
|
||||||
:href="link.link"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
>Click here</a>
|
|
||||||
to open it in a new window.
|
to open it in a new window.
|
||||||
</em>
|
</em>
|
||||||
<template v-else-if="link.error === 'message'">
|
<template v-else-if="link.error === 'message'">
|
||||||
<div>
|
<div>
|
||||||
<em>
|
<em>
|
||||||
A preview could not be loaded.
|
A preview could not be loaded.
|
||||||
<a
|
<a :href="link.link" target="_blank" rel="noopener">Click here</a>
|
||||||
:href="link.link"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
>Click here</a>
|
|
||||||
to open it in a new window.
|
to open it in a new window.
|
||||||
</em>
|
</em>
|
||||||
<br>
|
<br />
|
||||||
<pre class="prefetch-error">{{ link.message }}</pre>
|
<pre class="prefetch-error">{{ link.message }}</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -127,7 +90,9 @@
|
|||||||
:aria-label="moreButtonLabel"
|
:aria-label="moreButtonLabel"
|
||||||
class="more"
|
class="more"
|
||||||
@click="onMoreClick"
|
@click="onMoreClick"
|
||||||
><span class="more-caret" /></button>
|
>
|
||||||
|
<span class="more-caret" />
|
||||||
|
</button>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@ -217,27 +182,31 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.showMoreButton = this.$refs.content.offsetWidth >= this.$refs.container.offsetWidth;
|
this.showMoreButton =
|
||||||
|
this.$refs.content.offsetWidth >= this.$refs.container.offsetWidth;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
updateShownState() {
|
updateShownState() {
|
||||||
let defaultState = true;
|
let defaultState = true;
|
||||||
|
|
||||||
switch (this.link.type) {
|
switch (this.link.type) {
|
||||||
case "error":
|
case "error":
|
||||||
defaultState = this.link.error === "image-too-big" ? this.$root.settings.media : this.$root.settings.links;
|
defaultState =
|
||||||
break;
|
this.link.error === "image-too-big"
|
||||||
|
? this.$root.settings.media
|
||||||
|
: this.$root.settings.links;
|
||||||
|
break;
|
||||||
|
|
||||||
case "loading":
|
case "loading":
|
||||||
defaultState = false;
|
defaultState = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "link":
|
case "link":
|
||||||
defaultState = this.$root.settings.links;
|
defaultState = this.$root.settings.links;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
defaultState = this.$root.settings.media;
|
defaultState = this.$root.settings.media;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.link.shown = this.link.shown && defaultState;
|
this.link.shown = this.link.shown && defaultState;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
v-if="link.type !== 'loading'"
|
v-if="link.type !== 'loading'"
|
||||||
:class="['toggle-button', 'toggle-preview', { opened: link.shown }]"
|
:class="['toggle-button', 'toggle-preview', {opened: link.shown}]"
|
||||||
:aria-label="ariaLabel"
|
:aria-label="ariaLabel"
|
||||||
@click="onClick"
|
@click="onClick"
|
||||||
/>
|
/>
|
||||||
|
@ -4,26 +4,18 @@
|
|||||||
:class="['msg', message.type, {self: message.self, highlight: message.highlight}]"
|
:class="['msg', message.type, {self: message.self, highlight: message.highlight}]"
|
||||||
:data-from="message.from && message.from.nick"
|
:data-from="message.from && message.from.nick"
|
||||||
>
|
>
|
||||||
<span
|
<span :aria-label="message.time | localetime" class="time tooltipped tooltipped-e"
|
||||||
:aria-label="message.time | localetime"
|
>{{ messageTime }}
|
||||||
class="time tooltipped tooltipped-e"
|
</span>
|
||||||
>{{ messageTime }} </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>
|
||||||
<span class="content">
|
<span class="content">
|
||||||
<span
|
<span v-for="(param, id) in message.params" :key="id">{{ param }} </span>
|
||||||
v-for="(param, id) in message.params"
|
|
||||||
:key="id"
|
|
||||||
>{{ param }} </span>
|
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="isAction()">
|
<template v-else-if="isAction()">
|
||||||
<span class="from"><span class="only-copy">*** </span></span>
|
<span class="from"><span class="only-copy">*** </span></span>
|
||||||
<Component
|
<Component :is="messageComponent" :network="network" :message="message" />
|
||||||
:is="messageComponent"
|
|
||||||
:network="network"
|
|
||||||
:message="message"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="message.type === 'action'">
|
<template v-else-if="message.type === 'action'">
|
||||||
<span class="from"><span class="only-copy">* </span></span>
|
<span class="from"><span class="only-copy">* </span></span>
|
||||||
@ -41,20 +33,14 @@
|
|||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<span
|
<span v-if="message.type === 'message'" class="from">
|
||||||
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"><</span>
|
||||||
<Username :user="message.from" />
|
<Username :user="message.from" />
|
||||||
<span class="only-copy">> </span>
|
<span class="only-copy">> </span>
|
||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span v-else class="from">
|
||||||
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">-</span>
|
||||||
<Username :user="message.from" />
|
<Username :user="message.from" />
|
||||||
@ -62,10 +48,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
<span class="content">
|
<span class="content">
|
||||||
<ParsedMessage
|
<ParsedMessage :network="network" :message="message" />
|
||||||
:network="network"
|
|
||||||
:message="message"
|
|
||||||
/>
|
|
||||||
<LinkPreview
|
<LinkPreview
|
||||||
v-for="preview in message.previews"
|
v-for="preview in message.previews"
|
||||||
:key="preview.link"
|
:key="preview.link"
|
||||||
@ -100,7 +83,9 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
messageTime() {
|
messageTime() {
|
||||||
const format = this.$root.settings.showSeconds ? constants.timeFormats.msgWithSeconds : constants.timeFormats.msgDefault;
|
const format = this.$root.settings.showSeconds
|
||||||
|
? constants.timeFormats.msgWithSeconds
|
||||||
|
: constants.timeFormats.msgDefault;
|
||||||
|
|
||||||
return moment(this.message.time).format(format);
|
return moment(this.message.time).format(format);
|
||||||
},
|
},
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :class="[ 'msg', 'condensed', { closed: isCollapsed } ]">
|
<div :class="['msg', 'condensed', {closed: isCollapsed}]">
|
||||||
<div class="condensed-summary">
|
<div class="condensed-summary">
|
||||||
<span class="time" />
|
<span class="time" />
|
||||||
<span class="from" />
|
<span class="from" />
|
||||||
<span
|
<span class="content" @click="onCollapseClick"
|
||||||
class="content"
|
>{{ condensedText
|
||||||
@click="onCollapseClick"
|
}}<button class="toggle-button" aria-label="Toggle status messages"
|
||||||
>{{ condensedText }}<button
|
|
||||||
class="toggle-button"
|
|
||||||
aria-label="Toggle status messages"
|
|
||||||
/></span>
|
/></span>
|
||||||
</div>
|
</div>
|
||||||
<Message
|
<Message
|
||||||
@ -58,30 +55,60 @@ export default {
|
|||||||
constants.condensedTypes.forEach((type) => {
|
constants.condensedTypes.forEach((type) => {
|
||||||
if (obj[type]) {
|
if (obj[type]) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "away":
|
case "away":
|
||||||
strings.push(obj[type] + (obj[type] > 1 ? " users have gone away" : " user has gone away"));
|
strings.push(
|
||||||
break;
|
obj[type] +
|
||||||
case "back":
|
(obj[type] > 1
|
||||||
strings.push(obj[type] + (obj[type] > 1 ? " users have come back" : " user has come back"));
|
? " users have gone away"
|
||||||
break;
|
: " user has gone away")
|
||||||
case "chghost":
|
);
|
||||||
strings.push(obj[type] + (obj[type] > 1 ? " users have changed hostname" : " user has changed hostname"));
|
break;
|
||||||
break;
|
case "back":
|
||||||
case "join":
|
strings.push(
|
||||||
strings.push(obj[type] + (obj[type] > 1 ? " users have joined" : " user has joined"));
|
obj[type] +
|
||||||
break;
|
(obj[type] > 1
|
||||||
case "part":
|
? " users have come back"
|
||||||
strings.push(obj[type] + (obj[type] > 1 ? " users have left" : " user has left"));
|
: " user has come back")
|
||||||
break;
|
);
|
||||||
case "nick":
|
break;
|
||||||
strings.push(obj[type] + (obj[type] > 1 ? " users have changed nick" : " user has changed nick"));
|
case "chghost":
|
||||||
break;
|
strings.push(
|
||||||
case "kick":
|
obj[type] +
|
||||||
strings.push(obj[type] + (obj[type] > 1 ? " users were kicked" : " user was kicked"));
|
(obj[type] > 1
|
||||||
break;
|
? " users have changed hostname"
|
||||||
case "mode":
|
: " user has changed hostname")
|
||||||
strings.push(obj[type] + (obj[type] > 1 ? " modes were set" : " mode was set"));
|
);
|
||||||
break;
|
break;
|
||||||
|
case "join":
|
||||||
|
strings.push(
|
||||||
|
obj[type] +
|
||||||
|
(obj[type] > 1 ? " users have joined" : " user has joined")
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "part":
|
||||||
|
strings.push(
|
||||||
|
obj[type] + (obj[type] > 1 ? " users have left" : " user has left")
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "nick":
|
||||||
|
strings.push(
|
||||||
|
obj[type] +
|
||||||
|
(obj[type] > 1
|
||||||
|
? " users have changed nick"
|
||||||
|
: " user has changed nick")
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "kick":
|
||||||
|
strings.push(
|
||||||
|
obj[type] +
|
||||||
|
(obj[type] > 1 ? " users were kicked" : " user was kicked")
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "mode":
|
||||||
|
strings.push(
|
||||||
|
obj[type] + (obj[type] > 1 ? " modes were set" : " mode was set")
|
||||||
|
);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div ref="chat" class="chat" tabindex="-1">
|
||||||
ref="chat"
|
<div :class="['show-more', {show: channel.moreHistoryAvailable}]">
|
||||||
class="chat"
|
|
||||||
tabindex="-1"
|
|
||||||
>
|
|
||||||
<div :class="['show-more', { show: channel.moreHistoryAvailable }]">
|
|
||||||
<button
|
<button
|
||||||
ref="loadMoreButton"
|
ref="loadMoreButton"
|
||||||
:disabled="channel.historyLoading || !$root.isConnected"
|
:disabled="channel.historyLoading || !$root.isConnected"
|
||||||
@ -85,7 +81,9 @@ export default {
|
|||||||
|
|
||||||
// If actions are hidden, just return a message list with them excluded
|
// If actions are hidden, just return a message list with them excluded
|
||||||
if (this.$root.settings.statusMessages === "hidden") {
|
if (this.$root.settings.statusMessages === "hidden") {
|
||||||
return this.channel.messages.filter((message) => !constants.condensedTypes.includes(message.type));
|
return this.channel.messages.filter(
|
||||||
|
(message) => !constants.condensedTypes.includes(message.type)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If actions are not condensed, just return raw message list
|
// If actions are not condensed, just return raw message list
|
||||||
@ -99,7 +97,11 @@ export default {
|
|||||||
for (const message of this.channel.messages) {
|
for (const message of this.channel.messages) {
|
||||||
// If this message is not condensable, or its an action affecting our user,
|
// If this message is not condensable, or its an action affecting our user,
|
||||||
// then just append the message to container and be done with it
|
// then just append the message to container and be done with it
|
||||||
if (message.self || message.highlight || !constants.condensedTypes.includes(message.type)) {
|
if (
|
||||||
|
message.self ||
|
||||||
|
message.highlight ||
|
||||||
|
!constants.condensedTypes.includes(message.type)
|
||||||
|
) {
|
||||||
lastCondensedContainer = null;
|
lastCondensedContainer = null;
|
||||||
|
|
||||||
condensed.push(message);
|
condensed.push(message);
|
||||||
@ -199,7 +201,7 @@ export default {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (new Date(previousMessage.time)).getDay() !== (new Date(message.time)).getDay();
|
return new Date(previousMessage.time).getDay() !== new Date(message.time).getDay();
|
||||||
},
|
},
|
||||||
shouldDisplayUnreadMarker(id) {
|
shouldDisplayUnreadMarker(id) {
|
||||||
if (!this.unreadMarkerShown && id > this.channel.firstUnread) {
|
if (!this.unreadMarkerShown && id > this.channel.firstUnread) {
|
||||||
|
@ -1,17 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<span class="content">
|
<span class="content">
|
||||||
<ParsedMessage
|
<ParsedMessage v-if="message.self" :network="network" :message="message" />
|
||||||
v-if="message.self"
|
|
||||||
:network="network"
|
|
||||||
:message="message"
|
|
||||||
/>
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<Username :user="message.from" />
|
<Username :user="message.from" />
|
||||||
is away
|
is away
|
||||||
<i class="away-message">(<ParsedMessage
|
<i class="away-message">(<ParsedMessage :network="network" :message="message" />)</i>
|
||||||
:network="network"
|
|
||||||
:message="message"
|
|
||||||
/>)</i>
|
|
||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<span class="content">
|
<span class="content">
|
||||||
<ParsedMessage
|
<ParsedMessage v-if="message.self" :network="network" :message="message" />
|
||||||
v-if="message.self"
|
|
||||||
:network="network"
|
|
||||||
:message="message"
|
|
||||||
/>
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<Username :user="message.from" />
|
<Username :user="message.from" />
|
||||||
is back
|
is back
|
||||||
|
@ -2,8 +2,12 @@
|
|||||||
<span class="content">
|
<span class="content">
|
||||||
<Username :user="message.from" />
|
<Username :user="message.from" />
|
||||||
has changed
|
has changed
|
||||||
<span v-if="message.new_ident">username to <b>{{ message.new_ident }}</b></span>
|
<span v-if="message.new_ident"
|
||||||
<span v-if="message.new_host">hostname to <i class="hostmask">{{ message.new_host }}</i></span>
|
>username to <b>{{ message.new_ident }}</b></span
|
||||||
|
>
|
||||||
|
<span v-if="message.new_host"
|
||||||
|
>hostname to <i class="hostmask">{{ message.new_host }}</i></span
|
||||||
|
>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<span class="content">
|
<span class="content">
|
||||||
<Username :user="message.from" /> 
|
<Username :user="message.from" /> 
|
||||||
<span class="ctcp-message"><ParsedMessage :text="message.ctcpMessage" /></span>
|
<span class="ctcp-message"><ParsedMessage :text="message.ctcpMessage"/></span>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<span class="content">
|
<span class="content">
|
||||||
<Username :user="message.from" />
|
<Username :user="message.from" />
|
||||||
sent a <abbr title="Client-to-client protocol">CTCP</abbr> request:
|
sent a <abbr title="Client-to-client protocol">CTCP</abbr> request:
|
||||||
<span class="ctcp-message"><ParsedMessage :text="message.ctcpMessage" /></span>
|
<span class="ctcp-message"><ParsedMessage :text="message.ctcpMessage"/></span>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -3,14 +3,8 @@
|
|||||||
<Username :user="message.from" />
|
<Username :user="message.from" />
|
||||||
invited
|
invited
|
||||||
<span v-if="message.invitedYou">you</span>
|
<span v-if="message.invitedYou">you</span>
|
||||||
<Username
|
<Username v-else :user="message.target" />
|
||||||
v-else
|
to <ParsedMessage :network="network" :text="message.channel" />
|
||||||
:user="message.target"
|
|
||||||
/>
|
|
||||||
to <ParsedMessage
|
|
||||||
:network="network"
|
|
||||||
:text="message.channel"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -3,13 +3,9 @@
|
|||||||
<Username :user="message.from" />
|
<Username :user="message.from" />
|
||||||
has kicked
|
has kicked
|
||||||
<Username :user="message.target" />
|
<Username :user="message.target" />
|
||||||
<i
|
<i v-if="message.text" class="part-reason">
|
||||||
v-if="message.text"
|
(<ParsedMessage :network="network" :message="message" />)</i
|
||||||
class="part-reason"
|
>
|
||||||
> (<ParsedMessage
|
|
||||||
:network="network"
|
|
||||||
:message="message"
|
|
||||||
/>)</i>
|
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<span class="content">
|
<span class="content">
|
||||||
<span class="text"><ParsedMessage
|
<span class="text"><ParsedMessage :network="network" :text="cleanText"/></span>
|
||||||
:network="network"
|
|
||||||
:text="cleanText"
|
|
||||||
/></span>
|
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -31,7 +28,7 @@ export default {
|
|||||||
|
|
||||||
// Remove empty lines around the MOTD (but not within it)
|
// Remove empty lines around the MOTD (but not within it)
|
||||||
return lines
|
return lines
|
||||||
.map((line) => line.replace(/\s*$/,""))
|
.map((line) => line.replace(/\s*$/, ""))
|
||||||
.join("\n")
|
.join("\n")
|
||||||
.replace(/^[\r\n]+|[\r\n]+$/g, "");
|
.replace(/^[\r\n]+|[\r\n]+$/g, "");
|
||||||
},
|
},
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
<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
|
<i class="hostmask"> ({{ message.hostmask }})</i> has left the channel
|
||||||
v-if="message.text"
|
<i v-if="message.text" class="part-reason"
|
||||||
class="part-reason"
|
>(<ParsedMessage :network="network" :message="message" />)</i
|
||||||
>(<ParsedMessage
|
>
|
||||||
:network="network"
|
|
||||||
:message="message"
|
|
||||||
/>)</i>
|
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
<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
|
<i class="hostmask"> ({{ message.hostmask }})</i> has quit
|
||||||
v-if="message.text"
|
<i v-if="message.text" class="quit-reason"
|
||||||
class="quit-reason"
|
>(<ParsedMessage :network="network" :message="message" />)</i
|
||||||
>(<ParsedMessage
|
>
|
||||||
:network="network"
|
|
||||||
:message="message"
|
|
||||||
/>)</i>
|
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<span class="content">
|
<span class="content">
|
||||||
<template v-if="message.from && message.from.nick"><Username :user="message.from" /> has changed the topic to: </template>
|
<template v-if="message.from && message.from.nick"
|
||||||
<template v-else>The topic is: </template>
|
><Username :user="message.from" /> has changed the topic to:
|
||||||
<span
|
</template>
|
||||||
v-if="message.text"
|
<template v-else
|
||||||
class="new-topic"
|
>The topic is:
|
||||||
><ParsedMessage
|
</template>
|
||||||
:network="network"
|
<span v-if="message.text" class="new-topic"
|
||||||
:message="message"
|
><ParsedMessage :network="network" :message="message"
|
||||||
/></span>
|
/></span>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
@ -21,17 +21,17 @@
|
|||||||
:href="'https://ipinfo.io/' + message.whois.actual_ip"
|
:href="'https://ipinfo.io/' + message.whois.actual_ip"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener"
|
rel="noopener"
|
||||||
>{{ message.whois.actual_ip }}</a>
|
>{{ message.whois.actual_ip }}</a
|
||||||
<i v-if="message.whois.actual_hostname != message.whois.actual_ip"> ({{ message.whois.actual_hostname }})</i>
|
>
|
||||||
|
<i v-if="message.whois.actual_hostname != message.whois.actual_ip">
|
||||||
|
({{ message.whois.actual_hostname }})</i
|
||||||
|
>
|
||||||
</dd>
|
</dd>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="message.whois.real_name">
|
<template v-if="message.whois.real_name">
|
||||||
<dt>Real name:</dt>
|
<dt>Real name:</dt>
|
||||||
<dd><ParsedMessage
|
<dd><ParsedMessage :network="network" :text="message.whois.real_name" /></dd>
|
||||||
:network="network"
|
|
||||||
:text="message.whois.real_name"
|
|
||||||
/></dd>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="message.whois.registered_nick">
|
<template v-if="message.whois.registered_nick">
|
||||||
@ -41,10 +41,7 @@
|
|||||||
|
|
||||||
<template v-if="message.whois.channels">
|
<template v-if="message.whois.channels">
|
||||||
<dt>Channels:</dt>
|
<dt>Channels:</dt>
|
||||||
<dd><ParsedMessage
|
<dd><ParsedMessage :network="network" :text="message.whois.channels" /></dd>
|
||||||
:network="network"
|
|
||||||
:text="message.whois.channels"
|
|
||||||
/></dd>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="message.whois.modes">
|
<template v-if="message.whois.modes">
|
||||||
@ -76,10 +73,7 @@
|
|||||||
|
|
||||||
<template v-if="message.whois.away">
|
<template v-if="message.whois.away">
|
||||||
<dt>Away:</dt>
|
<dt>Away:</dt>
|
||||||
<dd><ParsedMessage
|
<dd><ParsedMessage :network="network" :text="message.whois.away" /></dd>
|
||||||
:network="network"
|
|
||||||
:text="message.whois.away"
|
|
||||||
/></dd>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="message.whois.secure">
|
<template v-if="message.whois.secure">
|
||||||
@ -89,7 +83,9 @@
|
|||||||
|
|
||||||
<template v-if="message.whois.server">
|
<template v-if="message.whois.server">
|
||||||
<dt>Connected to:</dt>
|
<dt>Connected to:</dt>
|
||||||
<dd>{{ message.whois.server }} <i>({{ message.whois.server_info }})</i></dd>
|
<dd>
|
||||||
|
{{ message.whois.server }} <i>({{ message.whois.server_info }})</i>
|
||||||
|
</dd>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="message.whois.logonTime">
|
<template v-if="message.whois.logonTime">
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div v-if="networks.length === 0" class="empty">
|
||||||
v-if="networks.length === 0"
|
|
||||||
class="empty"
|
|
||||||
>
|
|
||||||
You are not connected to any networks yet.
|
You are not connected to any networks yet.
|
||||||
</div>
|
</div>
|
||||||
<Draggable
|
<Draggable
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<ChannelWrapper
|
<ChannelWrapper :network="network" :channel="channel" :active-channel="activeChannel">
|
||||||
:network="network"
|
|
||||||
:channel="channel"
|
|
||||||
:active-channel="activeChannel"
|
|
||||||
>
|
|
||||||
<button
|
<button
|
||||||
v-if="network.channels.length > 1"
|
v-if="network.channels.length > 1"
|
||||||
:aria-controls="'network-' + network.uuid"
|
:aria-controls="'network-' + network.uuid"
|
||||||
@ -11,16 +7,12 @@
|
|||||||
:aria-expanded="!network.isCollapsed"
|
:aria-expanded="!network.isCollapsed"
|
||||||
class="collapse-network"
|
class="collapse-network"
|
||||||
@click.stop="onCollapseClick"
|
@click.stop="onCollapseClick"
|
||||||
><span class="collapse-network-icon" /></button>
|
>
|
||||||
<span
|
<span class="collapse-network-icon" />
|
||||||
v-else
|
</button>
|
||||||
class="collapse-network"
|
<span v-else class="collapse-network" />
|
||||||
/>
|
|
||||||
<div class="lobby-wrap">
|
<div class="lobby-wrap">
|
||||||
<span
|
<span :title="channel.name" class="name">{{ channel.name }}</span>
|
||||||
:title="channel.name"
|
|
||||||
class="name"
|
|
||||||
>{{ channel.name }}</span>
|
|
||||||
<span
|
<span
|
||||||
v-if="network.status.connected && !network.status.secure"
|
v-if="network.status.connected && !network.status.secure"
|
||||||
class="not-secure-tooltip tooltipped tooltipped-w"
|
class="not-secure-tooltip tooltipped tooltipped-w"
|
||||||
@ -35,18 +27,16 @@
|
|||||||
>
|
>
|
||||||
<span class="not-connected-icon" />
|
<span class="not-connected-icon" />
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span v-if="channel.unread" :class="{highlight: channel.highlight}" class="badge">{{
|
||||||
v-if="channel.unread"
|
channel.unread | roundBadgeNumber
|
||||||
:class="{ highlight: channel.highlight }"
|
}}</span>
|
||||||
class="badge"
|
|
||||||
>{{ channel.unread | roundBadgeNumber }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
:aria-label="joinChannelLabel"
|
:aria-label="joinChannelLabel"
|
||||||
class="add-channel-tooltip tooltipped tooltipped-w tooltipped-no-touch"
|
class="add-channel-tooltip tooltipped tooltipped-w tooltipped-no-touch"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
:class="['add-channel', { opened: isJoinChannelShown }]"
|
:class="['add-channel', {opened: isJoinChannelShown}]"
|
||||||
:aria-controls="'join-channel-' + channel.id"
|
:aria-controls="'join-channel-' + channel.id"
|
||||||
:aria-label="joinChannelLabel"
|
:aria-label="joinChannelLabel"
|
||||||
@click.stop="$emit('toggleJoinChannel')"
|
@click.stop="$emit('toggleJoinChannel')"
|
||||||
|
@ -12,7 +12,9 @@ export default {
|
|||||||
render(createElement, context) {
|
render(createElement, context) {
|
||||||
return parse(
|
return parse(
|
||||||
createElement,
|
createElement,
|
||||||
typeof context.props.text !== "undefined" ? context.props.text : context.props.message.text,
|
typeof context.props.text !== "undefined"
|
||||||
|
? context.props.text
|
||||||
|
: context.props.message.text,
|
||||||
context.props.message,
|
context.props.message,
|
||||||
context.props.network
|
context.props.network
|
||||||
);
|
);
|
||||||
|
@ -8,10 +8,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr
|
<tr v-for="ban in channel.data" :key="ban.hostmask">
|
||||||
v-for="ban in channel.data"
|
|
||||||
:key="ban.hostmask"
|
|
||||||
>
|
|
||||||
<td class="hostmask">{{ ban.hostmask }}</td>
|
<td class="hostmask">{{ ban.hostmask }}</td>
|
||||||
<td class="banned_by">{{ ban.banned_by }}</td>
|
<td class="banned_by">{{ ban.banned_by }}</td>
|
||||||
<td class="banned_at">{{ ban.banned_at | localetime }}</td>
|
<td class="banned_at">{{ ban.banned_at | localetime }}</td>
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<span v-if="channel.data.text">{{ channel.data.text }}</span>
|
<span v-if="channel.data.text">{{ channel.data.text }}</span>
|
||||||
<table
|
<table v-else class="channel-list">
|
||||||
v-else
|
|
||||||
class="channel-list"
|
|
||||||
>
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="channel">Channel</th>
|
<th class="channel">Channel</th>
|
||||||
@ -12,19 +9,10 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr
|
<tr v-for="chan in channel.data" :key="chan.channel">
|
||||||
v-for="chan in channel.data"
|
<td class="channel"><ParsedMessage :network="network" :text="chan.channel" /></td>
|
||||||
:key="chan.channel"
|
|
||||||
>
|
|
||||||
<td class="channel"><ParsedMessage
|
|
||||||
:network="network"
|
|
||||||
:text="chan.channel"
|
|
||||||
/></td>
|
|
||||||
<td class="users">{{ chan.num_users }}</td>
|
<td class="users">{{ chan.num_users }}</td>
|
||||||
<td class="topic"><ParsedMessage
|
<td class="topic"><ParsedMessage :network="network" :text="chan.topic" /></td>
|
||||||
:network="network"
|
|
||||||
:text="chan.topic"
|
|
||||||
/></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -7,10 +7,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr
|
<tr v-for="user in channel.data" :key="user.hostmask">
|
||||||
v-for="user in channel.data"
|
|
||||||
:key="user.hostmask"
|
|
||||||
>
|
|
||||||
<td class="hostmask">{{ user.hostmask }}</td>
|
<td class="hostmask">{{ user.hostmask }}</td>
|
||||||
<td class="when">{{ user.when | localetime }}</td>
|
<td class="when">{{ user.when | localetime }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -8,10 +8,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr
|
<tr v-for="invite in channel.data" :key="invite.hostmask">
|
||||||
v-for="invite in channel.data"
|
|
||||||
:key="invite.hostmask"
|
|
||||||
>
|
|
||||||
<td class="hostmask">{{ invite.hostmask }}</td>
|
<td class="hostmask">{{ invite.hostmask }}</td>
|
||||||
<td class="invitened_by">{{ invite.invited_by }}</td>
|
<td class="invitened_by">{{ invite.invited_by }}</td>
|
||||||
<td class="invitened_at">{{ invite.invited_at | localetime }}</td>
|
<td class="invitened_at">{{ invite.invited_at | localetime }}</td>
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<span
|
<span
|
||||||
:class="['user', $options.filters.colorClass(user.nick), { active: active }]"
|
:class="['user', $options.filters.colorClass(user.nick), {active: active}]"
|
||||||
:data-name="user.nick"
|
:data-name="user.nick"
|
||||||
role="button"
|
role="button"
|
||||||
v-on="onHover ? { mouseover: hover } : {}"
|
v-on="onHover ? {mouseover: hover} : {}"
|
||||||
>{{ user.mode }}{{ user.nick }}</span>
|
>{{ user.mode }}{{ user.nick }}</span
|
||||||
|
>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<span
|
<span
|
||||||
:class="['user', $options.filters.colorClass(user.original.nick), { active: active }]"
|
:class="['user', $options.filters.colorClass(user.original.nick), {active: active}]"
|
||||||
:data-name="user.original.nick"
|
:data-name="user.original.nick"
|
||||||
role="button"
|
role="button"
|
||||||
@mouseover="hover"
|
@mouseover="hover"
|
||||||
|
@ -33,8 +33,7 @@ const emojiStrategy = {
|
|||||||
search(term, callback) {
|
search(term, callback) {
|
||||||
// Trim colon from the matched term,
|
// Trim colon from the matched term,
|
||||||
// as we are unable to get a clean string from match regex
|
// as we are unable to get a clean string from match regex
|
||||||
term = term.replace(/:$/, ""),
|
(term = term.replace(/:$/, "")), callback(fuzzyGrep(term, emojiSearchTerms));
|
||||||
callback(fuzzyGrep(term, emojiSearchTerms));
|
|
||||||
},
|
},
|
||||||
template([string, original]) {
|
template([string, original]) {
|
||||||
return `<span class="emoji">${emojiMap[original]}</span> ${string}`;
|
return `<span class="emoji">${emojiMap[original]}</span> ${string}`;
|
||||||
@ -52,8 +51,7 @@ const nicksStrategy = {
|
|||||||
term = term.slice(1);
|
term = term.slice(1);
|
||||||
|
|
||||||
if (term[0] === "@") {
|
if (term[0] === "@") {
|
||||||
callback(completeNicks(term.slice(1), true)
|
callback(completeNicks(term.slice(1), true).map((val) => ["@" + val[0], "@" + val[1]]));
|
||||||
.map((val) => ["@" + val[0], "@" + val[1]]));
|
|
||||||
} else {
|
} else {
|
||||||
callback(completeNicks(term, true));
|
callback(completeNicks(term, true));
|
||||||
}
|
}
|
||||||
@ -118,10 +116,13 @@ const foregroundColorStrategy = {
|
|||||||
.filter((i) => fuzzy.test(term, i[0]) || fuzzy.test(term, i[1]))
|
.filter((i) => fuzzy.test(term, i[0]) || fuzzy.test(term, i[1]))
|
||||||
.map((i) => {
|
.map((i) => {
|
||||||
if (fuzzy.test(term, i[1])) {
|
if (fuzzy.test(term, i[1])) {
|
||||||
return [i[0], fuzzy.match(term, i[1], {
|
return [
|
||||||
pre: "<b>",
|
i[0],
|
||||||
post: "</b>",
|
fuzzy.match(term, i[1], {
|
||||||
}).rendered];
|
pre: "<b>",
|
||||||
|
post: "</b>",
|
||||||
|
}).rendered,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return i;
|
return i;
|
||||||
@ -147,10 +148,13 @@ const backgroundColorStrategy = {
|
|||||||
.filter((i) => fuzzy.test(term, i[0]) || fuzzy.test(term, i[1]))
|
.filter((i) => fuzzy.test(term, i[0]) || fuzzy.test(term, i[1]))
|
||||||
.map((pair) => {
|
.map((pair) => {
|
||||||
if (fuzzy.test(term, pair[1])) {
|
if (fuzzy.test(term, pair[1])) {
|
||||||
return [pair[0], fuzzy.match(term, pair[1], {
|
return [
|
||||||
pre: "<b>",
|
pair[0],
|
||||||
post: "</b>",
|
fuzzy.match(term, pair[1], {
|
||||||
}).rendered];
|
pre: "<b>",
|
||||||
|
post: "</b>",
|
||||||
|
}).rendered,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return pair;
|
return pair;
|
||||||
@ -160,7 +164,10 @@ const backgroundColorStrategy = {
|
|||||||
callback(matchingColorCodes);
|
callback(matchingColorCodes);
|
||||||
},
|
},
|
||||||
template(value) {
|
template(value) {
|
||||||
return `<span class="irc-fg${parseInt(value[2], 10)} irc-bg irc-bg${parseInt(value[0], 10)}">${value[1]}</span>`;
|
return `<span class="irc-fg${parseInt(value[2], 10)} irc-bg irc-bg${parseInt(
|
||||||
|
value[0],
|
||||||
|
10
|
||||||
|
)}">${value[1]}</span>`;
|
||||||
},
|
},
|
||||||
replace(value) {
|
replace(value) {
|
||||||
return "\x03$1," + value[0];
|
return "\x03$1," + value[0];
|
||||||
@ -185,46 +192,55 @@ function enableAutocomplete(inputRef) {
|
|||||||
lastMatch = "";
|
lastMatch = "";
|
||||||
});
|
});
|
||||||
|
|
||||||
Mousetrap(input.get(0)).bind("tab", (e) => {
|
Mousetrap(input.get(0)).bind(
|
||||||
if (vueApp.isAutoCompleting) {
|
"tab",
|
||||||
return;
|
(e) => {
|
||||||
}
|
if (vueApp.isAutoCompleting) {
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const text = input.val();
|
|
||||||
|
|
||||||
if (input.get(0).selectionStart !== text.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tabCount === 0) {
|
|
||||||
lastMatch = text.split(/\s/).pop();
|
|
||||||
|
|
||||||
if (lastMatch.length === 0) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentMatches = completeNicks(lastMatch, false);
|
e.preventDefault();
|
||||||
|
|
||||||
if (currentMatches.length === 0) {
|
const text = input.val();
|
||||||
|
|
||||||
|
if (input.get(0).selectionStart !== text.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const position = input.get(0).selectionStart - lastMatch.length;
|
if (tabCount === 0) {
|
||||||
const newMatch = nicksStrategy.replace([0, currentMatches[tabCount % currentMatches.length]], position);
|
lastMatch = text.split(/\s/).pop();
|
||||||
|
|
||||||
input.val(text.substr(0, position) + newMatch);
|
if (lastMatch.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Propagate change to Vue model
|
currentMatches = completeNicks(lastMatch, false);
|
||||||
input.get(0).dispatchEvent(new CustomEvent("input", {
|
|
||||||
detail: "autocomplete",
|
|
||||||
}));
|
|
||||||
|
|
||||||
lastMatch = newMatch;
|
if (currentMatches.length === 0) {
|
||||||
tabCount++;
|
return;
|
||||||
}, "keydown");
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const position = input.get(0).selectionStart - lastMatch.length;
|
||||||
|
const newMatch = nicksStrategy.replace(
|
||||||
|
[0, currentMatches[tabCount % currentMatches.length]],
|
||||||
|
position
|
||||||
|
);
|
||||||
|
|
||||||
|
input.val(text.substr(0, position) + newMatch);
|
||||||
|
|
||||||
|
// Propagate change to Vue model
|
||||||
|
input.get(0).dispatchEvent(
|
||||||
|
new CustomEvent("input", {
|
||||||
|
detail: "autocomplete",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
lastMatch = newMatch;
|
||||||
|
tabCount++;
|
||||||
|
},
|
||||||
|
"keydown"
|
||||||
|
);
|
||||||
|
|
||||||
const editor = new Textarea(input.get(0));
|
const editor = new Textarea(input.get(0));
|
||||||
textcomplete = new Textcomplete(editor, {
|
textcomplete = new Textcomplete(editor, {
|
||||||
@ -265,14 +281,10 @@ function enableAutocomplete(inputRef) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function fuzzyGrep(term, array) {
|
function fuzzyGrep(term, array) {
|
||||||
const results = fuzzy.filter(
|
const results = fuzzy.filter(term, array, {
|
||||||
term,
|
pre: "<b>",
|
||||||
array,
|
post: "</b>",
|
||||||
{
|
});
|
||||||
pre: "<b>",
|
|
||||||
post: "</b>",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return results.map((el) => [el.string, el.original]);
|
return results.map((el) => [el.string, el.original]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,10 +315,7 @@ function completeNicks(word, isFuzzy) {
|
|||||||
return fuzzyGrep(word, users);
|
return fuzzyGrep(word, users);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $.grep(
|
return $.grep(users, (w) => !w.toLowerCase().indexOf(word));
|
||||||
users,
|
|
||||||
(w) => !w.toLowerCase().indexOf(word)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function completeCommands(word) {
|
function completeCommands(word) {
|
||||||
|
@ -19,17 +19,7 @@ const colorCodeMap = [
|
|||||||
["15", "Light Grey"],
|
["15", "Light Grey"],
|
||||||
];
|
];
|
||||||
|
|
||||||
const condensedTypes = [
|
const condensedTypes = ["away", "back", "chghost", "join", "part", "quit", "nick", "kick", "mode"];
|
||||||
"away",
|
|
||||||
"back",
|
|
||||||
"chghost",
|
|
||||||
"join",
|
|
||||||
"part",
|
|
||||||
"quit",
|
|
||||||
"nick",
|
|
||||||
"kick",
|
|
||||||
"mode",
|
|
||||||
];
|
|
||||||
const condensedTypesQuery = "." + condensedTypes.join(", .");
|
const condensedTypesQuery = "." + condensedTypes.join(", .");
|
||||||
|
|
||||||
const timeFormats = {
|
const timeFormats = {
|
||||||
|
@ -16,7 +16,11 @@ module.exports = class ContextMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
show() {
|
show() {
|
||||||
const contextMenu = showContextMenu(this.contextMenuItems, this.selectedElement, this.event);
|
const contextMenu = showContextMenu(
|
||||||
|
this.contextMenuItems,
|
||||||
|
this.selectedElement,
|
||||||
|
this.event
|
||||||
|
);
|
||||||
this.bindEvents(contextMenu);
|
this.bindEvents(contextMenu);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -33,7 +37,8 @@ module.exports = class ContextMenu {
|
|||||||
bindEvents(contextMenu) {
|
bindEvents(contextMenu) {
|
||||||
const contextMenuActions = this.contextMenuActions;
|
const contextMenuActions = this.contextMenuActions;
|
||||||
|
|
||||||
contextMenuActions.execute = (id, ...args) => contextMenuActions[id] && contextMenuActions[id](...args);
|
contextMenuActions.execute = (id, ...args) =>
|
||||||
|
contextMenuActions[id] && contextMenuActions[id](...args);
|
||||||
|
|
||||||
const clickItem = (item) => {
|
const clickItem = (item) => {
|
||||||
const itemData = item.attr("data-data");
|
const itemData = item.attr("data-data");
|
||||||
@ -109,19 +114,25 @@ function showContextMenu(contextMenuItems, selectedElement, event) {
|
|||||||
if (item.divider) {
|
if (item.divider) {
|
||||||
contextMenu.append(templates.contextmenu_divider());
|
contextMenu.append(templates.contextmenu_divider());
|
||||||
} else {
|
} else {
|
||||||
contextMenu.append(templates.contextmenu_item({
|
contextMenu.append(
|
||||||
class: typeof item.className === "function" ? item.className(target) : item.className,
|
templates.contextmenu_item({
|
||||||
action: item.actionId,
|
class:
|
||||||
text: typeof item.displayName === "function" ? item.displayName(target) : item.displayName,
|
typeof item.className === "function"
|
||||||
data: typeof item.data === "function" ? item.data(target) : item.data,
|
? item.className(target)
|
||||||
}));
|
: item.className,
|
||||||
|
action: item.actionId,
|
||||||
|
text:
|
||||||
|
typeof item.displayName === "function"
|
||||||
|
? item.displayName(target)
|
||||||
|
: item.displayName,
|
||||||
|
data: typeof item.data === "function" ? item.data(target) : item.data,
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
contextMenuContainer
|
contextMenuContainer.html(contextMenu).show();
|
||||||
.html(contextMenu)
|
|
||||||
.show();
|
|
||||||
|
|
||||||
contextMenu
|
contextMenu
|
||||||
.css(positionContextMenu(contextMenu, selectedElement, event))
|
.css(positionContextMenu(contextMenu, selectedElement, event))
|
||||||
@ -145,11 +156,11 @@ function positionContextMenu(contextMenu, selectedElement, e) {
|
|||||||
|
|
||||||
offset = {left: e.pageX, top: e.pageY};
|
offset = {left: e.pageX, top: e.pageY};
|
||||||
|
|
||||||
if ((window.innerWidth - offset.left) < menuWidth) {
|
if (window.innerWidth - offset.left < menuWidth) {
|
||||||
offset.left = window.innerWidth - menuWidth;
|
offset.left = window.innerWidth - menuWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((window.innerHeight - offset.top) < menuHeight) {
|
if (window.innerHeight - offset.top < menuHeight) {
|
||||||
offset.top = window.innerHeight - menuHeight;
|
offset.top = window.innerHeight - menuHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,7 +170,9 @@ function addKickItem() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addContextMenuItem({
|
addContextMenuItem({
|
||||||
check: (target) => utils.hasRoleInChannel(target.closest(".chan"), ["op"]) && target.closest(".chan").attr("data-type") === "channel",
|
check: (target) =>
|
||||||
|
utils.hasRoleInChannel(target.closest(".chan"), ["op"]) &&
|
||||||
|
target.closest(".chan").attr("data-type") === "channel",
|
||||||
className: "action-kick",
|
className: "action-kick",
|
||||||
displayName: "Kick",
|
displayName: "Kick",
|
||||||
data: (target) => target.attr("data-name"),
|
data: (target) => target.attr("data-name"),
|
||||||
|
@ -4,10 +4,7 @@ const $ = require("jquery");
|
|||||||
const Mousetrap = require("mousetrap");
|
const Mousetrap = require("mousetrap");
|
||||||
const utils = require("./utils");
|
const utils = require("./utils");
|
||||||
|
|
||||||
Mousetrap.bind([
|
Mousetrap.bind(["alt+up", "alt+down"], function(e, keys) {
|
||||||
"alt+up",
|
|
||||||
"alt+down",
|
|
||||||
], function(e, keys) {
|
|
||||||
const sidebar = $("#sidebar");
|
const sidebar = $("#sidebar");
|
||||||
const channels = sidebar.find(".chan").not(".network.collapsed :not(.lobby)");
|
const channels = sidebar.find(".chan").not(".network.collapsed :not(.lobby)");
|
||||||
const index = channels.index(channels.filter(".active"));
|
const index = channels.index(channels.filter(".active"));
|
||||||
@ -15,13 +12,13 @@ Mousetrap.bind([
|
|||||||
let target;
|
let target;
|
||||||
|
|
||||||
switch (direction) {
|
switch (direction) {
|
||||||
case "up":
|
case "up":
|
||||||
target = (channels.length + (index - 1 + channels.length)) % channels.length;
|
target = (channels.length + (index - 1 + channels.length)) % channels.length;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "down":
|
case "down":
|
||||||
target = (channels.length + (index + 1 + channels.length)) % channels.length;
|
target = (channels.length + (index + 1 + channels.length)) % channels.length;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
target = channels.eq(target).click();
|
target = channels.eq(target).click();
|
||||||
@ -30,10 +27,7 @@ Mousetrap.bind([
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
Mousetrap.bind([
|
Mousetrap.bind(["alt+shift+up", "alt+shift+down"], function(e, keys) {
|
||||||
"alt+shift+up",
|
|
||||||
"alt+shift+down",
|
|
||||||
], function(e, keys) {
|
|
||||||
const sidebar = $("#sidebar");
|
const sidebar = $("#sidebar");
|
||||||
const lobbies = sidebar.find(".lobby");
|
const lobbies = sidebar.find(".lobby");
|
||||||
const direction = keys.split("+").pop();
|
const direction = keys.split("+").pop();
|
||||||
@ -41,23 +35,33 @@ Mousetrap.bind([
|
|||||||
let target;
|
let target;
|
||||||
|
|
||||||
switch (direction) {
|
switch (direction) {
|
||||||
case "up":
|
case "up":
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
target = lobbies.index(sidebar.find(".channel").filter(".active").siblings(".lobby")[0]);
|
target = lobbies.index(
|
||||||
} else {
|
sidebar
|
||||||
target = (lobbies.length + (index - 1 + lobbies.length)) % lobbies.length;
|
.find(".channel")
|
||||||
}
|
.filter(".active")
|
||||||
|
.siblings(".lobby")[0]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
target = (lobbies.length + (index - 1 + lobbies.length)) % lobbies.length;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "down":
|
case "down":
|
||||||
if (index < 0) {
|
if (index < 0) {
|
||||||
index = lobbies.index(sidebar.find(".channel").filter(".active").siblings(".lobby")[0]);
|
index = lobbies.index(
|
||||||
}
|
sidebar
|
||||||
|
.find(".channel")
|
||||||
|
.filter(".active")
|
||||||
|
.siblings(".lobby")[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
target = (lobbies.length + (index + 1 + lobbies.length)) % lobbies.length;
|
target = (lobbies.length + (index + 1 + lobbies.length)) % lobbies.length;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
target = lobbies.eq(target).click();
|
target = lobbies.eq(target).click();
|
||||||
|
@ -13,5 +13,5 @@ module.exports = function(str) {
|
|||||||
due to A being ascii 65 (100 0001)
|
due to A being ascii 65 (100 0001)
|
||||||
while a being ascii 97 (110 0001)
|
while a being ascii 97 (110 0001)
|
||||||
*/
|
*/
|
||||||
return "color-" + (1 + hash % 32);
|
return "color-" + (1 + (hash % 32));
|
||||||
};
|
};
|
||||||
|
@ -3,10 +3,12 @@
|
|||||||
// Return true if any section of "a" or "b" parts (defined by their start/end
|
// Return true if any section of "a" or "b" parts (defined by their start/end
|
||||||
// markers) intersect each other, false otherwise.
|
// markers) intersect each other, false otherwise.
|
||||||
function anyIntersection(a, b) {
|
function anyIntersection(a, b) {
|
||||||
return a.start <= b.start && b.start < a.end ||
|
return (
|
||||||
a.start < b.end && b.end <= a.end ||
|
(a.start <= b.start && b.start < a.end) ||
|
||||||
b.start <= a.start && a.start < b.end ||
|
(a.start < b.end && b.end <= a.end) ||
|
||||||
b.start < a.end && a.end <= b.end;
|
(b.start <= a.start && a.start < b.end) ||
|
||||||
|
(b.start < a.end && a.end <= b.end)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = anyIntersection;
|
module.exports = anyIntersection;
|
||||||
|
@ -25,11 +25,17 @@ const linkify = LinkifyIt()
|
|||||||
// Known schemes to detect in text
|
// Known schemes to detect in text
|
||||||
const commonSchemes = [
|
const commonSchemes = [
|
||||||
"sftp",
|
"sftp",
|
||||||
"smb", "file",
|
"smb",
|
||||||
"irc", "ircs",
|
"file",
|
||||||
"svn", "git",
|
"irc",
|
||||||
"steam", "mumble", "ts3server",
|
"ircs",
|
||||||
"svn+ssh", "ssh",
|
"svn",
|
||||||
|
"git",
|
||||||
|
"steam",
|
||||||
|
"mumble",
|
||||||
|
"ts3server",
|
||||||
|
"svn+ssh",
|
||||||
|
"ssh",
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const schema of commonSchemes) {
|
for (const schema of commonSchemes) {
|
||||||
|
@ -26,24 +26,20 @@ function sortParts(a, b) {
|
|||||||
// fragments will contain duplicate styling attributes.
|
// fragments will contain duplicate styling attributes.
|
||||||
function merge(textParts, styleFragments, cleanText) {
|
function merge(textParts, styleFragments, cleanText) {
|
||||||
// Remove overlapping parts
|
// Remove overlapping parts
|
||||||
textParts = textParts
|
textParts = textParts.sort(sortParts).reduce((prev, curr) => {
|
||||||
.sort(sortParts)
|
const intersection = prev.some((p) => anyIntersection(p, curr));
|
||||||
.reduce((prev, curr) => {
|
|
||||||
const intersection = prev.some((p) => anyIntersection(p, curr));
|
|
||||||
|
|
||||||
if (intersection) {
|
if (intersection) {
|
||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
|
|
||||||
return prev.concat([curr]);
|
return prev.concat([curr]);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Every section of the original text that has not been captured in a "part"
|
// Every section of the original text that has not been captured in a "part"
|
||||||
// is filled with "text" parts, dummy objects with start/end but no extra
|
// is filled with "text" parts, dummy objects with start/end but no extra
|
||||||
// metadata.
|
// metadata.
|
||||||
const allParts = textParts
|
const allParts = textParts.concat(fill(textParts, cleanText)).sort(sortParts); // Sort all parts identified based on their position in the original text
|
||||||
.concat(fill(textParts, cleanText))
|
|
||||||
.sort(sortParts); // Sort all parts identified based on their position in the original text
|
|
||||||
|
|
||||||
// Distribute the style fragments within the text parts
|
// Distribute the style fragments within the text parts
|
||||||
return allParts.map((textPart) => {
|
return allParts.map((textPart) => {
|
||||||
|
@ -33,7 +33,16 @@ function parseStyle(text) {
|
|||||||
|
|
||||||
// At any given time, these carry style information since last time a styling
|
// At any given time, these carry style information since last time a styling
|
||||||
// control code was met.
|
// control code was met.
|
||||||
let colorCodes, bold, textColor, bgColor, hexColor, hexBgColor, italic, underline, strikethrough, monospace;
|
let colorCodes,
|
||||||
|
bold,
|
||||||
|
textColor,
|
||||||
|
bgColor,
|
||||||
|
hexColor,
|
||||||
|
hexBgColor,
|
||||||
|
italic,
|
||||||
|
underline,
|
||||||
|
strikethrough,
|
||||||
|
monospace;
|
||||||
|
|
||||||
const resetStyle = () => {
|
const resetStyle = () => {
|
||||||
bold = false;
|
bold = false;
|
||||||
@ -90,96 +99,96 @@ function parseStyle(text) {
|
|||||||
// encountered since the previous styling character.
|
// encountered since the previous styling character.
|
||||||
while (position < text.length) {
|
while (position < text.length) {
|
||||||
switch (text[position]) {
|
switch (text[position]) {
|
||||||
case RESET:
|
case RESET:
|
||||||
emitFragment();
|
emitFragment();
|
||||||
resetStyle();
|
resetStyle();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Meeting a BOLD character means that the ongoing text is either going to
|
// Meeting a BOLD character means that the ongoing text is either going to
|
||||||
// be in bold or that the previous one was in bold and the following one
|
// be in bold or that the previous one was in bold and the following one
|
||||||
// must be reset.
|
// must be reset.
|
||||||
// This same behavior applies to COLOR, REVERSE, ITALIC, and UNDERLINE.
|
// This same behavior applies to COLOR, REVERSE, ITALIC, and UNDERLINE.
|
||||||
case BOLD:
|
case BOLD:
|
||||||
emitFragment();
|
emitFragment();
|
||||||
bold = !bold;
|
bold = !bold;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case COLOR:
|
case COLOR:
|
||||||
emitFragment();
|
emitFragment();
|
||||||
|
|
||||||
// Go one step further to find the corresponding color
|
// Go one step further to find the corresponding color
|
||||||
colorCodes = text.slice(position + 1).match(colorRx);
|
colorCodes = text.slice(position + 1).match(colorRx);
|
||||||
|
|
||||||
if (colorCodes) {
|
if (colorCodes) {
|
||||||
textColor = Number(colorCodes[1]);
|
textColor = Number(colorCodes[1]);
|
||||||
|
|
||||||
if (colorCodes[2]) {
|
if (colorCodes[2]) {
|
||||||
bgColor = Number(colorCodes[2]);
|
bgColor = Number(colorCodes[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Color code length is > 1, so bump the current position cursor by as
|
||||||
|
// much (and reset the start cursor for the current text block as well)
|
||||||
|
position += colorCodes[0].length;
|
||||||
|
start = position + 1;
|
||||||
|
} else {
|
||||||
|
// If no color codes were found, toggles back to no colors (like BOLD).
|
||||||
|
textColor = undefined;
|
||||||
|
bgColor = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Color code length is > 1, so bump the current position cursor by as
|
break;
|
||||||
// much (and reset the start cursor for the current text block as well)
|
|
||||||
position += colorCodes[0].length;
|
|
||||||
start = position + 1;
|
|
||||||
} else {
|
|
||||||
// If no color codes were found, toggles back to no colors (like BOLD).
|
|
||||||
textColor = undefined;
|
|
||||||
bgColor = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
case HEX_COLOR:
|
||||||
|
emitFragment();
|
||||||
|
|
||||||
case HEX_COLOR:
|
colorCodes = text.slice(position + 1).match(hexColorRx);
|
||||||
emitFragment();
|
|
||||||
|
|
||||||
colorCodes = text.slice(position + 1).match(hexColorRx);
|
if (colorCodes) {
|
||||||
|
hexColor = colorCodes[1].toUpperCase();
|
||||||
|
|
||||||
if (colorCodes) {
|
if (colorCodes[2]) {
|
||||||
hexColor = colorCodes[1].toUpperCase();
|
hexBgColor = colorCodes[2].toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
if (colorCodes[2]) {
|
// Color code length is > 1, so bump the current position cursor by as
|
||||||
hexBgColor = colorCodes[2].toUpperCase();
|
// much (and reset the start cursor for the current text block as well)
|
||||||
|
position += colorCodes[0].length;
|
||||||
|
start = position + 1;
|
||||||
|
} else {
|
||||||
|
// If no color codes were found, toggles back to no colors (like BOLD).
|
||||||
|
hexColor = undefined;
|
||||||
|
hexBgColor = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Color code length is > 1, so bump the current position cursor by as
|
break;
|
||||||
// much (and reset the start cursor for the current text block as well)
|
|
||||||
position += colorCodes[0].length;
|
case REVERSE: {
|
||||||
start = position + 1;
|
emitFragment();
|
||||||
} else {
|
const tmp = bgColor;
|
||||||
// If no color codes were found, toggles back to no colors (like BOLD).
|
bgColor = textColor;
|
||||||
hexColor = undefined;
|
textColor = tmp;
|
||||||
hexBgColor = undefined;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
case ITALIC:
|
||||||
|
emitFragment();
|
||||||
|
italic = !italic;
|
||||||
|
break;
|
||||||
|
|
||||||
case REVERSE: {
|
case UNDERLINE:
|
||||||
emitFragment();
|
emitFragment();
|
||||||
const tmp = bgColor;
|
underline = !underline;
|
||||||
bgColor = textColor;
|
break;
|
||||||
textColor = tmp;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case ITALIC:
|
case STRIKETHROUGH:
|
||||||
emitFragment();
|
emitFragment();
|
||||||
italic = !italic;
|
strikethrough = !strikethrough;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case UNDERLINE:
|
case MONOSPACE:
|
||||||
emitFragment();
|
emitFragment();
|
||||||
underline = !underline;
|
monospace = !monospace;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case STRIKETHROUGH:
|
|
||||||
emitFragment();
|
|
||||||
strikethrough = !strikethrough;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MONOSPACE:
|
|
||||||
emitFragment();
|
|
||||||
monospace = !monospace;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate the next character at the next iteration
|
// Evaluate the next character at the next iteration
|
||||||
@ -192,25 +201,37 @@ function parseStyle(text) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
const properties = ["bold", "textColor", "bgColor", "hexColor", "hexBgColor", "italic", "underline", "strikethrough", "monospace"];
|
const properties = [
|
||||||
|
"bold",
|
||||||
|
"textColor",
|
||||||
|
"bgColor",
|
||||||
|
"hexColor",
|
||||||
|
"hexBgColor",
|
||||||
|
"italic",
|
||||||
|
"underline",
|
||||||
|
"strikethrough",
|
||||||
|
"monospace",
|
||||||
|
];
|
||||||
|
|
||||||
function prepare(text) {
|
function prepare(text) {
|
||||||
return parseStyle(text)
|
return (
|
||||||
// This optimizes fragments by combining them together when all their values
|
parseStyle(text)
|
||||||
// for the properties defined above are equal.
|
// This optimizes fragments by combining them together when all their values
|
||||||
.reduce((prev, curr) => {
|
// for the properties defined above are equal.
|
||||||
if (prev.length) {
|
.reduce((prev, curr) => {
|
||||||
const lastEntry = prev[prev.length - 1];
|
if (prev.length) {
|
||||||
|
const lastEntry = prev[prev.length - 1];
|
||||||
|
|
||||||
if (properties.every((key) => curr[key] === lastEntry[key])) {
|
if (properties.every((key) => curr[key] === lastEntry[key])) {
|
||||||
lastEntry.text += curr.text;
|
lastEntry.text += curr.text;
|
||||||
lastEntry.end += curr.text.length;
|
lastEntry.end += curr.text.length;
|
||||||
return prev;
|
return prev;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return prev.concat([curr]);
|
return prev.concat([curr]);
|
||||||
}, []);
|
}, [])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = prepare;
|
module.exports = prepare;
|
||||||
|
@ -9,7 +9,7 @@ const merge = require("./ircmessageparser/merge");
|
|||||||
const colorClass = require("./colorClass");
|
const colorClass = require("./colorClass");
|
||||||
const emojiMap = require("../fullnamemap.json");
|
const emojiMap = require("../fullnamemap.json");
|
||||||
const LinkPreviewToggle = require("../../../components/LinkPreviewToggle.vue").default;
|
const LinkPreviewToggle = require("../../../components/LinkPreviewToggle.vue").default;
|
||||||
const emojiModifiersRegex = /[\u{1f3fb}-\u{1f3ff}]/ug;
|
const emojiModifiersRegex = /[\u{1f3fb}-\u{1f3ff}]/gu;
|
||||||
|
|
||||||
// Create an HTML `span` with styling information for a given fragment
|
// Create an HTML `span` with styling information for a given fragment
|
||||||
function createFragment(fragment, createElement) {
|
function createFragment(fragment, createElement) {
|
||||||
@ -80,7 +80,7 @@ module.exports = function parse(createElement, text, message = undefined, networ
|
|||||||
const channelParts = findChannels(cleanText, channelPrefixes, userModes);
|
const channelParts = findChannels(cleanText, channelPrefixes, userModes);
|
||||||
const linkParts = findLinks(cleanText);
|
const linkParts = findLinks(cleanText);
|
||||||
const emojiParts = findEmoji(cleanText);
|
const emojiParts = findEmoji(cleanText);
|
||||||
const nameParts = findNames(cleanText, message ? (message.users || []) : []);
|
const nameParts = findNames(cleanText, message ? message.users || [] : []);
|
||||||
|
|
||||||
const parts = channelParts
|
const parts = channelParts
|
||||||
.concat(linkParts)
|
.concat(linkParts)
|
||||||
@ -90,65 +90,85 @@ module.exports = function parse(createElement, text, message = undefined, networ
|
|||||||
// Merge the styling information with the channels / URLs / nicks / text objects and
|
// Merge the styling information with the channels / URLs / nicks / text objects and
|
||||||
// generate HTML strings with the resulting fragments
|
// generate HTML strings with the resulting fragments
|
||||||
return merge(parts, styleFragments, cleanText).map((textPart) => {
|
return merge(parts, styleFragments, cleanText).map((textPart) => {
|
||||||
const fragments = textPart.fragments.map((fragment) => createFragment(fragment, createElement));
|
const fragments = textPart.fragments.map((fragment) =>
|
||||||
|
createFragment(fragment, createElement)
|
||||||
|
);
|
||||||
|
|
||||||
// Wrap these potentially styled fragments with links and channel buttons
|
// Wrap these potentially styled fragments with links and channel buttons
|
||||||
if (textPart.link) {
|
if (textPart.link) {
|
||||||
const preview = message && message.previews.find((p) => p.link === textPart.link);
|
const preview = message && message.previews.find((p) => p.link === textPart.link);
|
||||||
const link = createElement("a", {
|
const link = createElement(
|
||||||
attrs: {
|
"a",
|
||||||
href: textPart.link,
|
{
|
||||||
target: "_blank",
|
attrs: {
|
||||||
rel: "noopener",
|
href: textPart.link,
|
||||||
|
target: "_blank",
|
||||||
|
rel: "noopener",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, fragments);
|
fragments
|
||||||
|
);
|
||||||
|
|
||||||
if (!preview) {
|
if (!preview) {
|
||||||
return link;
|
return link;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [link, createElement(LinkPreviewToggle, {
|
return [
|
||||||
class: ["toggle-button", "toggle-preview"],
|
link,
|
||||||
props: {
|
createElement(
|
||||||
link: preview,
|
LinkPreviewToggle,
|
||||||
},
|
{
|
||||||
}, fragments)];
|
class: ["toggle-button", "toggle-preview"],
|
||||||
|
props: {
|
||||||
|
link: preview,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fragments
|
||||||
|
),
|
||||||
|
];
|
||||||
} else if (textPart.channel) {
|
} else if (textPart.channel) {
|
||||||
return createElement("span", {
|
return createElement(
|
||||||
class: [
|
"span",
|
||||||
"inline-channel",
|
{
|
||||||
],
|
class: ["inline-channel"],
|
||||||
attrs: {
|
attrs: {
|
||||||
"role": "button",
|
role: "button",
|
||||||
"tabindex": 0,
|
tabindex: 0,
|
||||||
"data-chan": textPart.channel,
|
"data-chan": textPart.channel,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, fragments);
|
fragments
|
||||||
|
);
|
||||||
} else if (textPart.emoji) {
|
} else if (textPart.emoji) {
|
||||||
const emojiWithoutModifiers = textPart.emoji.replace(emojiModifiersRegex, "");
|
const emojiWithoutModifiers = textPart.emoji.replace(emojiModifiersRegex, "");
|
||||||
const title = emojiMap[emojiWithoutModifiers] ? `Emoji: ${emojiMap[emojiWithoutModifiers]}` : null;
|
const title = emojiMap[emojiWithoutModifiers]
|
||||||
|
? `Emoji: ${emojiMap[emojiWithoutModifiers]}`
|
||||||
|
: null;
|
||||||
|
|
||||||
return createElement("span", {
|
return createElement(
|
||||||
class: [
|
"span",
|
||||||
"emoji",
|
{
|
||||||
],
|
class: ["emoji"],
|
||||||
attrs: {
|
attrs: {
|
||||||
"role": "img",
|
role: "img",
|
||||||
"aria-label": title,
|
"aria-label": title,
|
||||||
"title": title,
|
title: title,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, fragments);
|
fragments
|
||||||
|
);
|
||||||
} else if (textPart.nick) {
|
} else if (textPart.nick) {
|
||||||
return createElement("span", {
|
return createElement(
|
||||||
class: [
|
"span",
|
||||||
"user",
|
{
|
||||||
colorClass(textPart.nick),
|
class: ["user", colorClass(textPart.nick)],
|
||||||
],
|
attrs: {
|
||||||
attrs: {
|
role: "button",
|
||||||
"role": "button",
|
"data-name": textPart.nick,
|
||||||
"data-name": textPart.nick,
|
},
|
||||||
},
|
},
|
||||||
}, fragments);
|
fragments
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return fragments;
|
return fragments;
|
||||||
|
@ -42,7 +42,8 @@
|
|||||||
|
|
||||||
window.g_LoungeErrorHandler = function LoungeErrorHandler(e) {
|
window.g_LoungeErrorHandler = function LoungeErrorHandler(e) {
|
||||||
var message = document.getElementById("loading-page-message");
|
var message = document.getElementById("loading-page-message");
|
||||||
message.textContent = "An error has occurred that prevented the client from loading correctly.";
|
message.textContent =
|
||||||
|
"An error has occurred that prevented the client from loading correctly.";
|
||||||
|
|
||||||
var summary = document.createElement("summary");
|
var summary = document.createElement("summary");
|
||||||
summary.textContent = "More details";
|
summary.textContent = "More details";
|
||||||
|
@ -69,7 +69,11 @@ window.vueMounted = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
viewport.on("click", "#chat .menu", function(e) {
|
viewport.on("click", "#chat .menu", function(e) {
|
||||||
e.currentTarget = $(`#sidebar .chan[data-id="${$(this).closest(".chan").attr("data-id")}"]`)[0];
|
e.currentTarget = $(
|
||||||
|
`#sidebar .chan[data-id="${$(this)
|
||||||
|
.closest(".chan")
|
||||||
|
.attr("data-id")}"]`
|
||||||
|
)[0];
|
||||||
return contextMenuFactory.createContextMenu($(this), e).show();
|
return contextMenuFactory.createContextMenu($(this), e).show();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -140,8 +144,7 @@ window.vueMounted = () => {
|
|||||||
|
|
||||||
const lastActive = $("#windows > .active");
|
const lastActive = $("#windows > .active");
|
||||||
|
|
||||||
lastActive
|
lastActive.removeClass("active");
|
||||||
.removeClass("active");
|
|
||||||
|
|
||||||
const chan = $(target)
|
const chan = $(target)
|
||||||
.addClass("active")
|
.addClass("active")
|
||||||
|
@ -28,9 +28,7 @@ const noSync = ["syncSettings"];
|
|||||||
|
|
||||||
// alwaysSync is reserved for settings that should be synced
|
// alwaysSync is reserved for settings that should be synced
|
||||||
// to the server regardless of the clients sync setting.
|
// to the server regardless of the clients sync setting.
|
||||||
const alwaysSync = [
|
const alwaysSync = ["highlights"];
|
||||||
"highlights",
|
|
||||||
];
|
|
||||||
|
|
||||||
// Process usersettings from localstorage.
|
// Process usersettings from localstorage.
|
||||||
let userSettings = JSON.parse(storage.get("settings")) || false;
|
let userSettings = JSON.parse(storage.get("settings")) || false;
|
||||||
@ -46,7 +44,10 @@ if (!userSettings) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the setting in local storage has the same type that the code expects
|
// Make sure the setting in local storage has the same type that the code expects
|
||||||
if (typeof userSettings[key] !== "undefined" && typeof settings[key] === typeof userSettings[key]) {
|
if (
|
||||||
|
typeof userSettings[key] !== "undefined" &&
|
||||||
|
typeof settings[key] === typeof userSettings[key]
|
||||||
|
) {
|
||||||
settings[key] = userSettings[key];
|
settings[key] = userSettings[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -59,7 +60,10 @@ if (typeof userSettings.userStyles === "string" && !noCSSparamReg.test(window.lo
|
|||||||
$userStyles.html(userSettings.userStyles);
|
$userStyles.html(userSettings.userStyles);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof userSettings.theme === "string" && $theme.attr("href") !== `themes/${userSettings.theme}.css`) {
|
if (
|
||||||
|
typeof userSettings.theme === "string" &&
|
||||||
|
$theme.attr("href") !== `themes/${userSettings.theme}.css`
|
||||||
|
) {
|
||||||
$theme.attr("href", `themes/${userSettings.theme}.css`);
|
$theme.attr("href", `themes/${userSettings.theme}.css`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,7 +103,7 @@ function applySetting(name, value) {
|
|||||||
} else if (name === "userStyles" && !noCSSparamReg.test(window.location.search)) {
|
} else if (name === "userStyles" && !noCSSparamReg.test(window.location.search)) {
|
||||||
$userStyles.html(value);
|
$userStyles.html(value);
|
||||||
} else if (name === "desktopNotifications") {
|
} else if (name === "desktopNotifications") {
|
||||||
if (("Notification" in window) && value && Notification.permission !== "granted") {
|
if ("Notification" in window && value && Notification.permission !== "granted") {
|
||||||
Notification.requestPermission(updateDesktopNotificationStatus);
|
Notification.requestPermission(updateDesktopNotificationStatus);
|
||||||
} else if (!value) {
|
} else if (!value) {
|
||||||
$warningBlocked.hide();
|
$warningBlocked.hide();
|
||||||
@ -172,8 +176,7 @@ function processSetting(name, value, save) {
|
|||||||
} else if (name === "nickPostfix") {
|
} else if (name === "nickPostfix") {
|
||||||
$settings.find(`input[name=${name}]`).val(value);
|
$settings.find(`input[name=${name}]`).val(value);
|
||||||
} else if (name === "statusMessages") {
|
} else if (name === "statusMessages") {
|
||||||
$settings.find(`input[name=${name}][value=${value}]`)
|
$settings.find(`input[name=${name}][value=${value}]`).prop("checked", true);
|
||||||
.prop("checked", true);
|
|
||||||
} else if (name === "theme") {
|
} else if (name === "theme") {
|
||||||
$settings.find("#theme-select").val(value);
|
$settings.find("#theme-select").val(value);
|
||||||
} else if (typeof value === "boolean") {
|
} else if (typeof value === "boolean") {
|
||||||
@ -208,7 +211,7 @@ function initialize() {
|
|||||||
|
|
||||||
// If browser does not support notifications
|
// If browser does not support notifications
|
||||||
// display proper message in settings.
|
// display proper message in settings.
|
||||||
if (("Notification" in window)) {
|
if ("Notification" in window) {
|
||||||
$warningUnsupported.hide();
|
$warningUnsupported.hide();
|
||||||
$windows.on("show", "#settings", updateDesktopNotificationStatus);
|
$windows.on("show", "#settings", updateDesktopNotificationStatus);
|
||||||
} else {
|
} else {
|
||||||
|
@ -47,40 +47,52 @@ function openImageViewer(link, {pushState = true} = {}) {
|
|||||||
// Only expanded thumbnails are being cycled through.
|
// Only expanded thumbnails are being cycled through.
|
||||||
|
|
||||||
// Previous image
|
// Previous image
|
||||||
let previousImage = link.closest(".preview").prev(".preview")
|
let previousImage = link
|
||||||
.find(".toggle-content .toggle-thumbnail").last();
|
.closest(".preview")
|
||||||
|
.prev(".preview")
|
||||||
|
.find(".toggle-content .toggle-thumbnail")
|
||||||
|
.last();
|
||||||
|
|
||||||
if (!previousImage.length) {
|
if (!previousImage.length) {
|
||||||
previousImage = link.closest(".msg").prevAll()
|
previousImage = link
|
||||||
.find(".toggle-content .toggle-thumbnail").last();
|
.closest(".msg")
|
||||||
|
.prevAll()
|
||||||
|
.find(".toggle-content .toggle-thumbnail")
|
||||||
|
.last();
|
||||||
}
|
}
|
||||||
|
|
||||||
previousImage.addClass("previous-image");
|
previousImage.addClass("previous-image");
|
||||||
|
|
||||||
// Next image
|
// Next image
|
||||||
let nextImage = link.closest(".preview").next(".preview")
|
let nextImage = link
|
||||||
.find(".toggle-content .toggle-thumbnail").first();
|
.closest(".preview")
|
||||||
|
.next(".preview")
|
||||||
|
.find(".toggle-content .toggle-thumbnail")
|
||||||
|
.first();
|
||||||
|
|
||||||
if (!nextImage.length) {
|
if (!nextImage.length) {
|
||||||
nextImage = link.closest(".msg").nextAll()
|
nextImage = link
|
||||||
.find(".toggle-content .toggle-thumbnail").first();
|
.closest(".msg")
|
||||||
|
.nextAll()
|
||||||
|
.find(".toggle-content .toggle-thumbnail")
|
||||||
|
.first();
|
||||||
}
|
}
|
||||||
|
|
||||||
nextImage.addClass("next-image");
|
nextImage.addClass("next-image");
|
||||||
|
|
||||||
imageViewer.html(templates.image_viewer({
|
imageViewer.html(
|
||||||
image: link.find("img").prop("src"),
|
templates.image_viewer({
|
||||||
link: link.prop("href"),
|
image: link.find("img").prop("src"),
|
||||||
type: link.parent().hasClass("toggle-type-link") ? "link" : "image",
|
link: link.prop("href"),
|
||||||
hasPreviousImage: previousImage.length > 0,
|
type: link.parent().hasClass("toggle-type-link") ? "link" : "image",
|
||||||
hasNextImage: nextImage.length > 0,
|
hasPreviousImage: previousImage.length > 0,
|
||||||
}));
|
hasNextImage: nextImage.length > 0,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
// Turn off transitionend listener before opening the viewer,
|
// Turn off transitionend listener before opening the viewer,
|
||||||
// which caused image viewer to become empty in rare cases
|
// which caused image viewer to become empty in rare cases
|
||||||
imageViewer
|
imageViewer.off("transitionend").addClass("opened");
|
||||||
.off("transitionend")
|
|
||||||
.addClass("opened");
|
|
||||||
|
|
||||||
// History management
|
// History management
|
||||||
if (pushState) {
|
if (pushState) {
|
||||||
@ -109,17 +121,14 @@ imageViewer.on("click", ".next-image-btn", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function closeImageViewer({pushState = true} = {}) {
|
function closeImageViewer({pushState = true} = {}) {
|
||||||
imageViewer
|
imageViewer.removeClass("opened").one("transitionend", function() {
|
||||||
.removeClass("opened")
|
imageViewer.empty();
|
||||||
.one("transitionend", function() {
|
});
|
||||||
imageViewer.empty();
|
|
||||||
});
|
|
||||||
|
|
||||||
// History management
|
// History management
|
||||||
if (pushState) {
|
if (pushState) {
|
||||||
const clickTarget =
|
const clickTarget =
|
||||||
"#sidebar " +
|
"#sidebar " + `.chan[data-id="${$("#sidebar .chan.active").attr("data-id")}"]`;
|
||||||
`.chan[data-id="${$("#sidebar .chan.active").attr("data-id")}"]`;
|
|
||||||
history.pushState({clickTarget}, null, null);
|
history.pushState({clickTarget}, null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ function onTouchStart(e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onTouchMove(e) {
|
function onTouchMove(e) {
|
||||||
const touch = touchCurPos = e.touches.item(0);
|
const touch = (touchCurPos = e.touches.item(0));
|
||||||
let distX = touch.screenX - touchStartPos.screenX;
|
let distX = touch.screenX - touchStartPos.screenX;
|
||||||
const distY = touch.screenY - touchStartPos.screenY;
|
const distY = touch.screenY - touchStartPos.screenY;
|
||||||
|
|
||||||
@ -93,7 +93,7 @@ function onTouchEnd() {
|
|||||||
const diff = touchCurPos.screenX - touchStartPos.screenX;
|
const diff = touchCurPos.screenX - touchStartPos.screenX;
|
||||||
const absDiff = Math.abs(diff);
|
const absDiff = Math.abs(diff);
|
||||||
|
|
||||||
if (absDiff > menuWidth / 2 || Date.now() - touchStartTime < 180 && absDiff > 50) {
|
if (absDiff > menuWidth / 2 || (Date.now() - touchStartTime < 180 && absDiff > 50)) {
|
||||||
SlideoutMenu.toggle(diff > 0);
|
SlideoutMenu.toggle(diff > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,9 +62,12 @@ socket.on("auth", function(data) {
|
|||||||
storage.remove("token");
|
storage.remove("token");
|
||||||
|
|
||||||
const error = login.find(".error");
|
const error = login.find(".error");
|
||||||
error.show().closest("form").one("submit", function() {
|
error
|
||||||
error.hide();
|
.show()
|
||||||
});
|
.closest("form")
|
||||||
|
.one("submit", function() {
|
||||||
|
error.hide();
|
||||||
|
});
|
||||||
} else if (user) {
|
} else if (user) {
|
||||||
token = storage.get("token");
|
token = storage.get("token");
|
||||||
|
|
||||||
|
@ -43,10 +43,7 @@ socket.on("changelog", function(data) {
|
|||||||
// When there is a button to refresh the checker available, display it when
|
// When there is a button to refresh the checker available, display it when
|
||||||
// data is expired. Before that, server would return same information anyway.
|
// data is expired. Before that, server would return same information anyway.
|
||||||
if (data.expiresAt) {
|
if (data.expiresAt) {
|
||||||
setTimeout(
|
setTimeout(() => $("#version-checker #check-now").show(), data.expiresAt - Date.now());
|
||||||
() => $("#version-checker #check-now").show(),
|
|
||||||
data.expiresAt - Date.now()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -62,6 +59,7 @@ $("#help").on("click", "#check-now", () => {
|
|||||||
// Given a status and latest release information, update the version checker
|
// Given a status and latest release information, update the version checker
|
||||||
// (CSS class and content)
|
// (CSS class and content)
|
||||||
function renderVersionChecker({status, latest}) {
|
function renderVersionChecker({status, latest}) {
|
||||||
$("#version-checker").prop("class", status)
|
$("#version-checker")
|
||||||
|
.prop("class", status)
|
||||||
.html(templates.version_checker({latest, status}));
|
.html(templates.version_checker({latest, status}));
|
||||||
}
|
}
|
||||||
|
@ -187,8 +187,7 @@ function parseOverrideParams(params, data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// When the network is locked, URL overrides should not affect disabled fields
|
// When the network is locked, URL overrides should not affect disabled fields
|
||||||
if (data.lockNetwork &&
|
if (data.lockNetwork && ["host", "port", "tls", "rejectUnauthorized"].includes(key)) {
|
||||||
["host", "port", "tls", "rejectUnauthorized"].includes(key)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,20 +197,29 @@ function parseOverrideParams(params, data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (key === "join") {
|
if (key === "join") {
|
||||||
value = value.split(",").map((chan) => {
|
value = value
|
||||||
if (!chan.match(/^[#&!+]/)) {
|
.split(",")
|
||||||
return `#${chan}`;
|
.map((chan) => {
|
||||||
}
|
if (!chan.match(/^[#&!+]/)) {
|
||||||
|
return `#${chan}`;
|
||||||
|
}
|
||||||
|
|
||||||
return chan;
|
return chan;
|
||||||
}).join(", ");
|
})
|
||||||
|
.join(", ");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override server provided defaults with parameters passed in the URL if they match the data type
|
// Override server provided defaults with parameters passed in the URL if they match the data type
|
||||||
switch (typeof data.defaults[key]) {
|
switch (typeof data.defaults[key]) {
|
||||||
case "boolean": data.defaults[key] = value === "1" || value === "true"; break;
|
case "boolean":
|
||||||
case "number": data.defaults[key] = Number(value); break;
|
data.defaults[key] = value === "1" || value === "true";
|
||||||
case "string": data.defaults[key] = String(value); break;
|
break;
|
||||||
|
case "number":
|
||||||
|
data.defaults[key] = Number(value);
|
||||||
|
break;
|
||||||
|
case "string":
|
||||||
|
data.defaults[key] = String(value);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,7 +126,10 @@ function mergeNetworkData(newNetworks) {
|
|||||||
|
|
||||||
// Channels require extra care to be merged correctly
|
// Channels require extra care to be merged correctly
|
||||||
if (key === "channels") {
|
if (key === "channels") {
|
||||||
currentNetwork.channels = mergeChannelData(currentNetwork.channels, network.channels);
|
currentNetwork.channels = mergeChannelData(
|
||||||
|
currentNetwork.channels,
|
||||||
|
network.channels
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
currentNetwork[key] = network[key];
|
currentNetwork[key] = network[key];
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,8 @@ const {vueApp, initChannel} = require("../vue");
|
|||||||
socket.on("join", function(data) {
|
socket.on("join", function(data) {
|
||||||
initChannel(data.chan);
|
initChannel(data.chan);
|
||||||
|
|
||||||
vueApp.networks.find((n) => n.uuid === data.network)
|
vueApp.networks
|
||||||
|
.find((n) => n.uuid === data.network)
|
||||||
.channels.splice(data.index || -1, 0, data.chan);
|
.channels.splice(data.index || -1, 0, data.chan);
|
||||||
|
|
||||||
// Queries do not automatically focus, unless the user did a whois
|
// Queries do not automatically focus, unless the user did a whois
|
||||||
|
@ -32,7 +32,11 @@ socket.on("msg", function(data) {
|
|||||||
// Display received notices and errors in currently active channel.
|
// Display received notices and errors in currently active channel.
|
||||||
// Reloading the page will put them back into the lobby window.
|
// Reloading the page will put them back into the lobby window.
|
||||||
// We only want to put errors/notices in active channel if they arrive on the same network
|
// We only want to put errors/notices in active channel if they arrive on the same network
|
||||||
if (data.msg.showInActive && vueApp.activeChannel && vueApp.activeChannel.network === receivingChannel.network) {
|
if (
|
||||||
|
data.msg.showInActive &&
|
||||||
|
vueApp.activeChannel &&
|
||||||
|
vueApp.activeChannel.network === receivingChannel.network
|
||||||
|
) {
|
||||||
channel = vueApp.activeChannel.channel;
|
channel = vueApp.activeChannel.channel;
|
||||||
|
|
||||||
data.chan = channel.id;
|
data.chan = channel.id;
|
||||||
@ -76,7 +80,7 @@ socket.on("msg", function(data) {
|
|||||||
const user = channel.users.find((u) => u.nick === data.msg.from.nick);
|
const user = channel.users.find((u) => u.nick === data.msg.from.nick);
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
user.lastMessage = (new Date(data.msg.time)).getTime() || Date.now();
|
user.lastMessage = new Date(data.msg.time).getTime() || Date.now();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,7 +102,11 @@ function notifyMessage(targetId, channel, activeChannel, msg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.settings.desktopNotifications && ("Notification" in window) && Notification.permission === "granted") {
|
if (
|
||||||
|
options.settings.desktopNotifications &&
|
||||||
|
"Notification" in window &&
|
||||||
|
Notification.permission === "granted"
|
||||||
|
) {
|
||||||
let title;
|
let title;
|
||||||
let body;
|
let body;
|
||||||
|
|
||||||
|
@ -17,7 +17,8 @@ socket.on("network", function(data) {
|
|||||||
vueApp.networks.push(network);
|
vueApp.networks.push(network);
|
||||||
|
|
||||||
vueApp.$nextTick(() => {
|
vueApp.$nextTick(() => {
|
||||||
sidebar.find(".chan")
|
sidebar
|
||||||
|
.find(".chan")
|
||||||
.last()
|
.last()
|
||||||
.trigger("click");
|
.trigger("click");
|
||||||
});
|
});
|
||||||
@ -60,15 +61,19 @@ socket.on("channel:state", function(data) {
|
|||||||
socket.on("network:info", function(data) {
|
socket.on("network:info", function(data) {
|
||||||
$("#connect")
|
$("#connect")
|
||||||
.html(templates.windows.connect(data))
|
.html(templates.windows.connect(data))
|
||||||
.find("form").on("submit", function() {
|
.find("form")
|
||||||
const uuid = $(this).find("input[name=uuid]").val();
|
.on("submit", function() {
|
||||||
const newName = $(this).find("#connect\\:name").val();
|
const uuid = $(this)
|
||||||
|
.find("input[name=uuid]")
|
||||||
|
.val();
|
||||||
|
const newName = $(this)
|
||||||
|
.find("#connect\\:name")
|
||||||
|
.val();
|
||||||
|
|
||||||
const network = vueApp.networks.find((n) => n.uuid === uuid);
|
const network = vueApp.networks.find((n) => n.uuid === uuid);
|
||||||
network.name = network.channels[0].name = newName;
|
network.name = network.channels[0].name = newName;
|
||||||
|
|
||||||
sidebar.find(`.network[data-uuid="${uuid}"] .chan.lobby .name`)
|
sidebar.find(`.network[data-uuid="${uuid}"] .chan.lobby .name`).click();
|
||||||
.click();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
utils.togglePasswordField("#connect .reveal-password");
|
utils.togglePasswordField("#connect .reveal-password");
|
||||||
|
@ -23,7 +23,8 @@ socket.on("open", function(id) {
|
|||||||
channel.channel.unread = 0;
|
channel.channel.unread = 0;
|
||||||
|
|
||||||
if (channel.channel.messages.length > 0) {
|
if (channel.channel.messages.length > 0) {
|
||||||
channel.channel.firstUnread = channel.channel.messages[channel.channel.messages.length - 1].id;
|
channel.channel.firstUnread =
|
||||||
|
channel.channel.messages[channel.channel.messages.length - 1].id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,10 @@ socket.on("part", function(data) {
|
|||||||
const channel = findChannel(data.chan);
|
const channel = findChannel(data.chan);
|
||||||
|
|
||||||
if (channel) {
|
if (channel) {
|
||||||
channel.network.channels.splice(channel.network.channels.findIndex((c) => c.id === data.chan), 1);
|
channel.network.channels.splice(
|
||||||
|
channel.network.channels.findIndex((c) => c.id === data.chan),
|
||||||
|
1
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.synchronizeNotifiedState();
|
utils.synchronizeNotifiedState();
|
||||||
|
@ -4,7 +4,11 @@ const socket = require("../socket");
|
|||||||
const options = require("../options");
|
const options = require("../options");
|
||||||
|
|
||||||
function evaluateSetting(name, value) {
|
function evaluateSetting(name, value) {
|
||||||
if (options.settings.syncSettings && options.settings[name] !== value && !options.noSync.includes(name)) {
|
if (
|
||||||
|
options.settings.syncSettings &&
|
||||||
|
options.settings[name] !== value &&
|
||||||
|
!options.noSync.includes(name)
|
||||||
|
) {
|
||||||
options.processSetting(name, value, true);
|
options.processSetting(name, value, true);
|
||||||
} else if (options.alwaysSync.includes(name)) {
|
} else if (options.alwaysSync.includes(name)) {
|
||||||
options.processSetting(name, value, true);
|
options.processSetting(name, value, true);
|
||||||
|
@ -7,21 +7,21 @@ socket.on("sync_sort", function(data) {
|
|||||||
const order = data.order;
|
const order = data.order;
|
||||||
|
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case "networks":
|
case "networks":
|
||||||
vueApp.networks.sort((a, b) => order.indexOf(a.uuid) - order.indexOf(b.uuid));
|
vueApp.networks.sort((a, b) => order.indexOf(a.uuid) - order.indexOf(b.uuid));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "channels": {
|
case "channels": {
|
||||||
const network = vueApp.networks.find((n) => n.uuid === data.target);
|
const network = vueApp.networks.find((n) => n.uuid === data.target);
|
||||||
|
|
||||||
if (!network) {
|
if (!network) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
network.channels.sort((a, b) => order.indexOf(a.id) - order.indexOf(b.id));
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
network.channels.sort((a, b) => order.indexOf(a.id) - order.indexOf(b.id));
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -128,10 +128,14 @@ class Uploader {
|
|||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
this.xhr = xhr;
|
this.xhr = xhr;
|
||||||
|
|
||||||
xhr.upload.addEventListener("progress", (e) => {
|
xhr.upload.addEventListener(
|
||||||
const percent = Math.floor(e.loaded / e.total * 1000) / 10;
|
"progress",
|
||||||
this.setProgress(percent);
|
(e) => {
|
||||||
}, false);
|
const percent = Math.floor((e.loaded / e.total) * 1000) / 10;
|
||||||
|
this.setProgress(percent);
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
xhr.onreadystatechange = () => {
|
xhr.onreadystatechange = () => {
|
||||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||||
@ -182,12 +186,12 @@ class Uploader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
insertUploadUrl(url) {
|
insertUploadUrl(url) {
|
||||||
const fullURL = (new URL(url, location)).toString();
|
const fullURL = new URL(url, location).toString();
|
||||||
const textbox = document.getElementById("input");
|
const textbox = document.getElementById("input");
|
||||||
const initStart = textbox.selectionStart;
|
const initStart = textbox.selectionStart;
|
||||||
|
|
||||||
// Get the text before the cursor, and add a space if it's not in the beginning
|
// Get the text before the cursor, and add a space if it's not in the beginning
|
||||||
const headToCursor = initStart > 0 ? (textbox.value.substr(0, initStart) + " ") : "";
|
const headToCursor = initStart > 0 ? textbox.value.substr(0, initStart) + " " : "";
|
||||||
|
|
||||||
// Get the remaining text after the cursor
|
// Get the remaining text after the cursor
|
||||||
const cursorToTail = textbox.value.substr(initStart);
|
const cursorToTail = textbox.value.substr(initStart);
|
||||||
@ -220,9 +224,9 @@ function initialize() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called in the `configuration` socket event.
|
* Called in the `configuration` socket event.
|
||||||
* Makes it so the user can be notified if a file is too large without waiting for the upload to finish server-side.
|
* Makes it so the user can be notified if a file is too large without waiting for the upload to finish server-side.
|
||||||
**/
|
**/
|
||||||
function setMaxFileSize(kb) {
|
function setMaxFileSize(kb) {
|
||||||
instance.maxFileSize = kb;
|
instance.maxFileSize = kb;
|
||||||
}
|
}
|
||||||
|
@ -135,7 +135,7 @@ function move(array, old_index, new_index) {
|
|||||||
if (new_index >= array.length) {
|
if (new_index >= array.length) {
|
||||||
let k = new_index - array.length;
|
let k = new_index - array.length;
|
||||||
|
|
||||||
while ((k--) + 1) {
|
while (k-- + 1) {
|
||||||
this.push(undefined);
|
this.push(undefined);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -152,7 +152,8 @@ function closeChan(chan) {
|
|||||||
cmd = "/quit";
|
cmd = "/quit";
|
||||||
const server = chan.find(".name").html();
|
const server = chan.find(".name").html();
|
||||||
|
|
||||||
if (!confirm(`Are you sure you want to remove ${server}?`)) { // eslint-disable-line no-alert
|
if (!confirm(`Are you sure you want to remove ${server}?`)) {
|
||||||
|
// eslint-disable-line no-alert
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,9 @@ let applicationServerKey;
|
|||||||
if ("serviceWorker" in navigator) {
|
if ("serviceWorker" in navigator) {
|
||||||
navigator.serviceWorker.addEventListener("message", (event) => {
|
navigator.serviceWorker.addEventListener("message", (event) => {
|
||||||
if (event.data && event.data.type === "open") {
|
if (event.data && event.data.type === "open") {
|
||||||
$("#sidebar").find(`.chan[data-target="#${event.data.channel}"]`).trigger("click");
|
$("#sidebar")
|
||||||
|
.find(`.chan[data-target="#${event.data.channel}"]`)
|
||||||
|
.trigger("click");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -47,73 +49,89 @@ module.exports.initialize = () => {
|
|||||||
$("#pushNotificationsHttps").hide();
|
$("#pushNotificationsHttps").hide();
|
||||||
|
|
||||||
if ("serviceWorker" in navigator) {
|
if ("serviceWorker" in navigator) {
|
||||||
navigator.serviceWorker.register("service-worker.js").then((registration) => {
|
navigator.serviceWorker
|
||||||
module.exports.hasServiceWorker = true;
|
.register("service-worker.js")
|
||||||
|
.then((registration) => {
|
||||||
|
module.exports.hasServiceWorker = true;
|
||||||
|
|
||||||
if (!registration.pushManager) {
|
if (!registration.pushManager) {
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
return registration.pushManager.getSubscription().then((subscription) => {
|
|
||||||
$("#pushNotificationsUnsupported").hide();
|
|
||||||
|
|
||||||
pushNotificationsButton
|
|
||||||
.prop("disabled", false)
|
|
||||||
.on("click", onPushButton);
|
|
||||||
|
|
||||||
clientSubscribed = !!subscription;
|
|
||||||
|
|
||||||
if (clientSubscribed) {
|
|
||||||
alternatePushButton();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return registration.pushManager.getSubscription().then((subscription) => {
|
||||||
|
$("#pushNotificationsUnsupported").hide();
|
||||||
|
|
||||||
|
pushNotificationsButton.prop("disabled", false).on("click", onPushButton);
|
||||||
|
|
||||||
|
clientSubscribed = !!subscription;
|
||||||
|
|
||||||
|
if (clientSubscribed) {
|
||||||
|
alternatePushButton();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
$("#pushNotificationsUnsupported span").text(err);
|
||||||
});
|
});
|
||||||
}).catch((err) => {
|
|
||||||
$("#pushNotificationsUnsupported span").text(err);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function onPushButton() {
|
function onPushButton() {
|
||||||
pushNotificationsButton.prop("disabled", true);
|
pushNotificationsButton.prop("disabled", true);
|
||||||
|
|
||||||
navigator.serviceWorker.ready.then((registration) =>
|
navigator.serviceWorker.ready
|
||||||
registration.pushManager.getSubscription().then((existingSubscription) => {
|
.then((registration) =>
|
||||||
if (existingSubscription) {
|
registration.pushManager
|
||||||
socket.emit("push:unregister");
|
.getSubscription()
|
||||||
|
.then((existingSubscription) => {
|
||||||
|
if (existingSubscription) {
|
||||||
|
socket.emit("push:unregister");
|
||||||
|
|
||||||
return existingSubscription.unsubscribe();
|
return existingSubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
return registration.pushManager.subscribe({
|
return registration.pushManager
|
||||||
applicationServerKey: urlBase64ToUint8Array(applicationServerKey),
|
.subscribe({
|
||||||
userVisibleOnly: true,
|
applicationServerKey: urlBase64ToUint8Array(applicationServerKey),
|
||||||
}).then((subscription) => {
|
userVisibleOnly: true,
|
||||||
const rawKey = subscription.getKey ? subscription.getKey("p256dh") : "";
|
})
|
||||||
const key = rawKey ? window.btoa(String.fromCharCode(...new Uint8Array(rawKey))) : "";
|
.then((subscription) => {
|
||||||
const rawAuthSecret = subscription.getKey ? subscription.getKey("auth") : "";
|
const rawKey = subscription.getKey ? subscription.getKey("p256dh") : "";
|
||||||
const authSecret = rawAuthSecret ? window.btoa(String.fromCharCode(...new Uint8Array(rawAuthSecret))) : "";
|
const key = rawKey
|
||||||
|
? window.btoa(String.fromCharCode(...new Uint8Array(rawKey)))
|
||||||
|
: "";
|
||||||
|
const rawAuthSecret = subscription.getKey
|
||||||
|
? subscription.getKey("auth")
|
||||||
|
: "";
|
||||||
|
const authSecret = rawAuthSecret
|
||||||
|
? window.btoa(String.fromCharCode(...new Uint8Array(rawAuthSecret)))
|
||||||
|
: "";
|
||||||
|
|
||||||
socket.emit("push:register", {
|
socket.emit("push:register", {
|
||||||
token: storage.get("token"),
|
token: storage.get("token"),
|
||||||
endpoint: subscription.endpoint,
|
endpoint: subscription.endpoint,
|
||||||
keys: {
|
keys: {
|
||||||
p256dh: key,
|
p256dh: key,
|
||||||
auth: authSecret,
|
auth: authSecret,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}).then((successful) => {
|
})
|
||||||
if (successful) {
|
.then((successful) => {
|
||||||
alternatePushButton().prop("disabled", false);
|
if (successful) {
|
||||||
}
|
alternatePushButton().prop("disabled", false);
|
||||||
})
|
}
|
||||||
).catch((err) => {
|
})
|
||||||
$("#pushNotificationsUnsupported")
|
)
|
||||||
.find("span").text(`An error has occurred: ${err}`).end()
|
.catch((err) => {
|
||||||
.show();
|
$("#pushNotificationsUnsupported")
|
||||||
});
|
.find("span")
|
||||||
|
.text(`An error has occurred: ${err}`)
|
||||||
|
.end()
|
||||||
|
.show();
|
||||||
|
});
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -127,10 +145,8 @@ function alternatePushButton() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function urlBase64ToUint8Array(base64String) {
|
function urlBase64ToUint8Array(base64String) {
|
||||||
const padding = "=".repeat((4 - base64String.length % 4) % 4);
|
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
|
||||||
const base64 = (base64String + padding)
|
const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
|
||||||
.replace(/-/g, "+")
|
|
||||||
.replace(/_/g, "/");
|
|
||||||
|
|
||||||
const rawData = window.atob(base64);
|
const rawData = window.atob(base64);
|
||||||
const outputArray = new Uint8Array(rawData.length);
|
const outputArray = new Uint8Array(rawData.length);
|
||||||
@ -143,5 +159,9 @@ function urlBase64ToUint8Array(base64String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isAllowedServiceWorkersHost() {
|
function isAllowedServiceWorkersHost() {
|
||||||
return location.protocol === "https:" || location.hostname === "localhost" || location.hostname === "127.0.0.1";
|
return (
|
||||||
|
location.protocol === "https:" ||
|
||||||
|
location.hostname === "localhost" ||
|
||||||
|
location.hostname === "127.0.0.1"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -10,11 +10,15 @@ self.addEventListener("install", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
self.addEventListener("activate", function(event) {
|
self.addEventListener("activate", function(event) {
|
||||||
event.waitUntil(caches.keys().then((names) => Promise.all(
|
event.waitUntil(
|
||||||
names
|
caches
|
||||||
.filter((name) => name !== cacheName)
|
.keys()
|
||||||
.map((name) => caches.delete(name))
|
.then((names) =>
|
||||||
)));
|
Promise.all(
|
||||||
|
names.filter((name) => name !== cacheName).map((name) => caches.delete(name))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
event.waitUntil(self.clients.claim());
|
event.waitUntil(self.clients.claim());
|
||||||
});
|
});
|
||||||
@ -50,9 +54,7 @@ async function putInCache(request, response) {
|
|||||||
async function cleanRedirect(response) {
|
async function cleanRedirect(response) {
|
||||||
// Not all browsers support the Response.body stream, so fall back
|
// Not all browsers support the Response.body stream, so fall back
|
||||||
// to reading the entire body into memory as a blob.
|
// to reading the entire body into memory as a blob.
|
||||||
const bodyPromise = "body" in response ?
|
const bodyPromise = "body" in response ? Promise.resolve(response.body) : response.blob();
|
||||||
Promise.resolve(response.body) :
|
|
||||||
response.blob();
|
|
||||||
|
|
||||||
const body = await bodyPromise;
|
const body = await bodyPromise;
|
||||||
|
|
||||||
@ -134,29 +136,33 @@ function showNotification(event, payload) {
|
|||||||
self.addEventListener("notificationclick", function(event) {
|
self.addEventListener("notificationclick", function(event) {
|
||||||
event.notification.close();
|
event.notification.close();
|
||||||
|
|
||||||
event.waitUntil(clients.matchAll({
|
event.waitUntil(
|
||||||
includeUncontrolled: true,
|
clients
|
||||||
type: "window",
|
.matchAll({
|
||||||
}).then((clientList) => {
|
includeUncontrolled: true,
|
||||||
if (clientList.length === 0) {
|
type: "window",
|
||||||
if (clients.openWindow) {
|
})
|
||||||
return clients.openWindow(`.#${event.notification.tag}`);
|
.then((clientList) => {
|
||||||
}
|
if (clientList.length === 0) {
|
||||||
|
if (clients.openWindow) {
|
||||||
|
return clients.openWindow(`.#${event.notification.tag}`);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = findSuitableClient(clientList);
|
const client = findSuitableClient(clientList);
|
||||||
|
|
||||||
client.postMessage({
|
client.postMessage({
|
||||||
type: "open",
|
type: "open",
|
||||||
channel: event.notification.tag,
|
channel: event.notification.tag,
|
||||||
});
|
});
|
||||||
|
|
||||||
if ("focus" in client) {
|
if ("focus" in client) {
|
||||||
client.focus();
|
client.focus();
|
||||||
}
|
}
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
function findSuitableClient(clientList) {
|
function findSuitableClient(clientList) {
|
||||||
|
@ -13,15 +13,18 @@ module.exports = requireViews.keys().reduce((acc, path) => {
|
|||||||
// Split path by folders, and create a new property if necessary/
|
// Split path by folders, and create a new property if necessary/
|
||||||
// First 2 characters are "./"/
|
// First 2 characters are "./"/
|
||||||
// Last element in the array ends with `.tpl` and needs to be `require`d.
|
// Last element in the array ends with `.tpl` and needs to be `require`d.
|
||||||
path.substr(2).split("/").forEach((key) => {
|
path.substr(2)
|
||||||
if (key.endsWith(".tpl")) { //
|
.split("/")
|
||||||
tmp[key.substr(0, key.length - 4)] = requireViews(path);
|
.forEach((key) => {
|
||||||
} else {
|
if (key.endsWith(".tpl")) {
|
||||||
tmp[key] = tmp[key] || {};
|
//
|
||||||
}
|
tmp[key.substr(0, key.length - 4)] = requireViews(path);
|
||||||
|
} else {
|
||||||
|
tmp[key] = tmp[key] || {};
|
||||||
|
}
|
||||||
|
|
||||||
tmp = tmp[key];
|
tmp = tmp[key];
|
||||||
});
|
});
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
8
index.js
8
index.js
@ -11,7 +11,13 @@ const pkg = require("./package.json");
|
|||||||
|
|
||||||
if (!require("semver").satisfies(process.version, pkg.engines.node)) {
|
if (!require("semver").satisfies(process.version, pkg.engines.node)) {
|
||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
console.error("The Lounge requires Node.js " + pkg.engines.node + " (current version: " + process.version + ")");
|
console.error(
|
||||||
|
"The Lounge requires Node.js " +
|
||||||
|
pkg.engines.node +
|
||||||
|
" (current version: " +
|
||||||
|
process.version +
|
||||||
|
")"
|
||||||
|
);
|
||||||
console.error("Please upgrade Node.js in order to use The Lounge");
|
console.error("Please upgrade Node.js in order to use The Lounge");
|
||||||
console.error("See https://thelounge.chat/docs/install-and-upgrade");
|
console.error("See https://thelounge.chat/docs/install-and-upgrade");
|
||||||
console.error();
|
console.error();
|
||||||
|
@ -81,13 +81,21 @@ if (!version) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isValidVersion(str) {
|
function isValidVersion(str) {
|
||||||
return (/^[0-9]+\.[0-9]+\.[0-9]+(-(pre|rc)+\.[0-9]+)?$/.test(str));
|
return /^[0-9]+\.[0-9]+\.[0-9]+(-(pre|rc)+\.[0-9]+)?$/.test(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isValidVersion(version)) {
|
if (!isValidVersion(version)) {
|
||||||
log.error(`Argument ${colors.bold("version")} is incorrect It must be either:`);
|
log.error(`Argument ${colors.bold("version")} is incorrect It must be either:`);
|
||||||
log.error(`- A keyword among: ${colors.green("major")}, ${colors.green("minor")}, ${colors.green("patch")}, ${colors.green("prerelease")}, ${colors.green("pre")}`);
|
log.error(
|
||||||
log.error(`- An explicit version of format ${colors.green("x.y.z")} (stable) or ${colors.green("x.y.z-(pre|rc).n")} (pre-release).`);
|
`- A keyword among: ${colors.green("major")}, ${colors.green("minor")}, ${colors.green(
|
||||||
|
"patch"
|
||||||
|
)}, ${colors.green("prerelease")}, ${colors.green("pre")}`
|
||||||
|
);
|
||||||
|
log.error(
|
||||||
|
`- An explicit version of format ${colors.green("x.y.z")} (stable) or ${colors.green(
|
||||||
|
"x.y.z-(pre|rc).n"
|
||||||
|
)} (pre-release).`
|
||||||
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,11 +107,15 @@ function prereleaseTemplate(items) {
|
|||||||
|
|
||||||
[See the full changelog](${items.fullChangelogUrl})
|
[See the full changelog](${items.fullChangelogUrl})
|
||||||
|
|
||||||
${prereleaseType(items.version) === "rc" ?
|
${
|
||||||
`This is a release candidate (RC) for v${stableVersion(items.version)} to ensure maximum stability for public release.
|
prereleaseType(items.version) === "rc"
|
||||||
Bugs may be fixed, but no further features will be added until the next stable version.` :
|
? `This is a release candidate (RC) for v${stableVersion(
|
||||||
|
items.version
|
||||||
`This is a pre-release for v${stableVersion(items.version)} to offer latest changes without having to wait for a stable release.
|
)} to ensure maximum stability for public release.
|
||||||
|
Bugs may be fixed, but no further features will be added until the next stable version.`
|
||||||
|
: `This is a pre-release for v${stableVersion(
|
||||||
|
items.version
|
||||||
|
)} 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.`
|
At this stage, features may still be added or modified until the first release candidate for this version gets released.`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +140,9 @@ function stableTemplate(items) {
|
|||||||
return `
|
return `
|
||||||
## v${items.version} - ${items.date}
|
## v${items.version} - ${items.date}
|
||||||
|
|
||||||
For more details, [see the full changelog](${items.fullChangelogUrl}) and [milestone](${items.milestone.url}?closed=1).
|
For more details, [see the full changelog](${items.fullChangelogUrl}) and [milestone](${
|
||||||
|
items.milestone.url
|
||||||
|
}?closed=1).
|
||||||
|
|
||||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||||
@@ DESCRIPTION, ANNOUNCEMENT, ETC. @@
|
@@ DESCRIPTION, ANNOUNCEMENT, ETC. @@
|
||||||
@ -138,8 +152,10 @@ For more details, [see the full changelog](${items.fullChangelogUrl}) and [miles
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
${isEmpty(items.dependencies) ? "" :
|
${
|
||||||
`- Update production dependencies to their latest versions:
|
isEmpty(items.dependencies)
|
||||||
|
? ""
|
||||||
|
: `- Update production dependencies to their latest versions:
|
||||||
${printDependencyList(items.dependencies)}`
|
${printDependencyList(items.dependencies)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,23 +173,31 @@ ${printList(items.security)}
|
|||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
|
|
||||||
${items.documentation.length === 0 ? "" :
|
${
|
||||||
`In the main repository:
|
items.documentation.length === 0
|
||||||
|
? ""
|
||||||
|
: `In the main repository:
|
||||||
|
|
||||||
${printList(items.documentation)}`
|
${printList(items.documentation)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
${items.websiteDocumentation.length === 0 ? "" :
|
${
|
||||||
`On the [website repository](https://github.com/thelounge/thelounge.github.io):
|
items.websiteDocumentation.length === 0
|
||||||
|
? ""
|
||||||
|
: `On the [website repository](https://github.com/thelounge/thelounge.github.io):
|
||||||
|
|
||||||
${printList(items.websiteDocumentation)}`
|
${printList(items.websiteDocumentation)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
### Internals
|
### Internals
|
||||||
|
|
||||||
${printList(items.internals)}${isEmpty(items.devDependencies) ? "" : `
|
${printList(items.internals)}${
|
||||||
|
isEmpty(items.devDependencies)
|
||||||
|
? ""
|
||||||
|
: `
|
||||||
- Update development dependencies to their latest versions:
|
- Update development dependencies to their latest versions:
|
||||||
${printDependencyList(items.devDependencies)}`}
|
${printDependencyList(items.devDependencies)}`
|
||||||
|
}
|
||||||
|
|
||||||
@@@@@@@@@@@@@@@@@@@
|
@@@@@@@@@@@@@@@@@@@
|
||||||
@@ UNCATEGORIZED @@
|
@@ UNCATEGORIZED @@
|
||||||
@ -375,7 +399,9 @@ class RepositoryFetcher {
|
|||||||
|
|
||||||
const prQuery = `query fetchPullRequests($repositoryName: String!) {
|
const prQuery = `query fetchPullRequests($repositoryName: String!) {
|
||||||
repository(owner: "thelounge", name: $repositoryName) {
|
repository(owner: "thelounge", name: $repositoryName) {
|
||||||
${numbers.map((number) => `
|
${numbers
|
||||||
|
.map(
|
||||||
|
(number) => `
|
||||||
PR${number}: pullRequest(number: ${number}) {
|
PR${number}: pullRequest(number: ${number}) {
|
||||||
__typename
|
__typename
|
||||||
title
|
title
|
||||||
@ -398,7 +424,9 @@ class RepositoryFetcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`).join("")}
|
`
|
||||||
|
)
|
||||||
|
.join("")}
|
||||||
}
|
}
|
||||||
}`;
|
}`;
|
||||||
const data = await this.fetch(prQuery);
|
const data = await this.fetch(prQuery);
|
||||||
@ -458,9 +486,8 @@ function printAuthorLink({login, url}) {
|
|||||||
|
|
||||||
// Builds a Markdown link for a given pull request or commit object
|
// Builds a Markdown link for a given pull request or commit object
|
||||||
function printEntryLink(entry) {
|
function printEntryLink(entry) {
|
||||||
const label = entry.__typename === "PullRequest"
|
const label =
|
||||||
? `#${entry.number}`
|
entry.__typename === "PullRequest" ? `#${entry.number}` : `\`${entry.abbreviatedOid}\``;
|
||||||
: `\`${entry.abbreviatedOid}\``;
|
|
||||||
|
|
||||||
return `[${label}](${entry.url})`;
|
return `[${label}](${entry.url})`;
|
||||||
}
|
}
|
||||||
@ -476,12 +503,16 @@ function printLine(entry) {
|
|||||||
|
|
||||||
// Builds a Markdown list item for a given pull request
|
// Builds a Markdown list item for a given pull request
|
||||||
function printPullRequest(pullRequest) {
|
function printPullRequest(pullRequest) {
|
||||||
return `- ${pullRequest.title} (${printEntryLink(pullRequest)} ${printAuthorLink(pullRequest.author)})`;
|
return `- ${pullRequest.title} (${printEntryLink(pullRequest)} ${printAuthorLink(
|
||||||
|
pullRequest.author
|
||||||
|
)})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Builds a Markdown list item for a commit made directly in `master`
|
// Builds a Markdown list item for a commit made directly in `master`
|
||||||
function printCommit(commit) {
|
function printCommit(commit) {
|
||||||
return `- ${commit.messageHeadline} (${printEntryLink(commit)} ${printAuthorLink(commit.author.user)})`;
|
return `- ${commit.messageHeadline} (${printEntryLink(commit)} ${printAuthorLink(
|
||||||
|
commit.author.user
|
||||||
|
)})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Builds a Markdown list of all given items
|
// Builds a Markdown list of all given items
|
||||||
@ -543,20 +574,23 @@ function hasLabel(labels, expected) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function hasAnnotatedComment(comments, expected) {
|
function hasAnnotatedComment(comments, expected) {
|
||||||
return comments && comments.nodes.some(({authorAssociation, body}) =>
|
return (
|
||||||
["OWNER", "MEMBER"].includes(authorAssociation) &&
|
comments &&
|
||||||
body.split("\r\n").includes(`[${expected}]`)
|
comments.nodes.some(
|
||||||
|
({authorAssociation, body}) =>
|
||||||
|
["OWNER", "MEMBER"].includes(authorAssociation) &&
|
||||||
|
body.split("\r\n").includes(`[${expected}]`)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSkipped(entry) {
|
function isSkipped(entry) {
|
||||||
return (
|
return (
|
||||||
(entry.__typename === "Commit" && (
|
(entry.__typename === "Commit" &&
|
||||||
// Version bump commits created by `yarn version`
|
// Version bump commits created by `yarn version`
|
||||||
isValidVersion(entry.messageHeadline) ||
|
(isValidVersion(entry.messageHeadline) ||
|
||||||
// Commit message suggested by this script
|
// Commit message suggested by this script
|
||||||
entry.messageHeadline.startsWith("Add changelog entry for v")
|
entry.messageHeadline.startsWith("Add changelog entry for v"))) ||
|
||||||
)) ||
|
|
||||||
hasLabelOrAnnotatedComment(entry, "Meta: Skip Changelog")
|
hasLabelOrAnnotatedComment(entry, "Meta: Skip Changelog")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -608,76 +642,85 @@ function extractPackages({title, url}) {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return extracted[1]
|
return extracted[1].replace(/`/g, "").split(/, and |, | and /);
|
||||||
.replace(/`/g, "")
|
|
||||||
.split(/, and |, | and /);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Given an array of entries (PRs or commits), separates them into sections,
|
// Given an array of entries (PRs or commits), separates them into sections,
|
||||||
// based on different information that describes them.
|
// based on different information that describes them.
|
||||||
function parse(entries) {
|
function parse(entries) {
|
||||||
return entries.reduce((result, entry) => {
|
return entries.reduce(
|
||||||
let deps;
|
(result, entry) => {
|
||||||
|
let deps;
|
||||||
|
|
||||||
if (isSkipped(entry)) {
|
if (isSkipped(entry)) {
|
||||||
result.skipped.push(entry);
|
result.skipped.push(entry);
|
||||||
} else if (isDependency(entry) && (deps = extractPackages(entry))) {
|
} else if (isDependency(entry) && (deps = extractPackages(entry))) {
|
||||||
deps.forEach((packageName) => {
|
deps.forEach((packageName) => {
|
||||||
const dependencyType = whichDependencyType(packageName);
|
const dependencyType = whichDependencyType(packageName);
|
||||||
|
|
||||||
if (dependencyType) {
|
if (dependencyType) {
|
||||||
if (!result[dependencyType][packageName]) {
|
if (!result[dependencyType][packageName]) {
|
||||||
result[dependencyType][packageName] = [];
|
result[dependencyType][packageName] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
result[dependencyType][packageName].push(entry);
|
||||||
|
} else {
|
||||||
|
log.info(
|
||||||
|
`${colors.bold(packageName)} was updated in ${colors.green(
|
||||||
|
"#" + entry.number
|
||||||
|
)} then removed since last release. Skipping. ${colors.gray(
|
||||||
|
entry.url
|
||||||
|
)}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
result[dependencyType][packageName].push(entry);
|
} else if (isDocumentation(entry)) {
|
||||||
} else {
|
result.documentation.push(entry);
|
||||||
log.info(`${colors.bold(packageName)} was updated in ${colors.green("#" + entry.number)} then removed since last release. Skipping. ${colors.gray(entry.url)}`);
|
} else if (isDeprecation(entry)) {
|
||||||
}
|
result.deprecations.push(entry);
|
||||||
});
|
} else if (isSecurity(entry)) {
|
||||||
} else if (isDocumentation(entry)) {
|
result.security.push(entry);
|
||||||
result.documentation.push(entry);
|
} else if (isInternal(entry)) {
|
||||||
} else if (isDeprecation(entry)) {
|
result.internals.push(entry);
|
||||||
result.deprecations.push(entry);
|
|
||||||
} else if (isSecurity(entry)) {
|
|
||||||
result.security.push(entry);
|
|
||||||
} else if (isInternal(entry)) {
|
|
||||||
result.internals.push(entry);
|
|
||||||
} else {
|
|
||||||
if (isFeature(entry)) {
|
|
||||||
result.uncategorized.feature.push(entry);
|
|
||||||
} else if (isBug(entry)) {
|
|
||||||
result.uncategorized.bug.push(entry);
|
|
||||||
} else {
|
} else {
|
||||||
result.uncategorized.other.push(entry);
|
if (isFeature(entry)) {
|
||||||
|
result.uncategorized.feature.push(entry);
|
||||||
|
} else if (isBug(entry)) {
|
||||||
|
result.uncategorized.bug.push(entry);
|
||||||
|
} else {
|
||||||
|
result.uncategorized.other.push(entry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}, {
|
|
||||||
skipped: [],
|
|
||||||
dependencies: {},
|
|
||||||
devDependencies: {},
|
|
||||||
deprecations: [],
|
|
||||||
documentation: [],
|
|
||||||
internals: [],
|
|
||||||
security: [],
|
|
||||||
uncategorized: {
|
|
||||||
feature: [],
|
|
||||||
bug: [],
|
|
||||||
other: [],
|
|
||||||
},
|
},
|
||||||
unknownDependencies: new Set(),
|
{
|
||||||
});
|
skipped: [],
|
||||||
|
dependencies: {},
|
||||||
|
devDependencies: {},
|
||||||
|
deprecations: [],
|
||||||
|
documentation: [],
|
||||||
|
internals: [],
|
||||||
|
security: [],
|
||||||
|
uncategorized: {
|
||||||
|
feature: [],
|
||||||
|
bug: [],
|
||||||
|
other: [],
|
||||||
|
},
|
||||||
|
unknownDependencies: new Set(),
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function dedupeEntries(changelog, items) {
|
function dedupeEntries(changelog, items) {
|
||||||
const dedupe = (entries) =>
|
const dedupe = (entries) =>
|
||||||
entries.filter((entry) => !changelog.includes(printEntryLink(entry)));
|
entries.filter((entry) => !changelog.includes(printEntryLink(entry)));
|
||||||
|
|
||||||
["deprecations", "documentation", "websiteDocumentation", "internals", "security"].forEach((type) => {
|
["deprecations", "documentation", "websiteDocumentation", "internals", "security"].forEach(
|
||||||
items[type] = dedupe(items[type]);
|
(type) => {
|
||||||
});
|
items[type] = dedupe(items[type]);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
["dependencies", "devDependencies", "uncategorized"].forEach((type) => {
|
["dependencies", "devDependencies", "uncategorized"].forEach((type) => {
|
||||||
Object.entries(items[type]).forEach(([name, entries]) => {
|
Object.entries(items[type]).forEach(([name, entries]) => {
|
||||||
@ -692,8 +735,8 @@ function extractContributors(entries) {
|
|||||||
const set = Object.values(entries).reduce((memo, {__typename, author}) => {
|
const set = Object.values(entries).reduce((memo, {__typename, author}) => {
|
||||||
if (__typename === "PullRequest" && author.__typename !== "Bot") {
|
if (__typename === "PullRequest" && author.__typename !== "Bot") {
|
||||||
memo.add("@" + author.login);
|
memo.add("@" + author.login);
|
||||||
// Commit authors are *always* of type "User", so have to discriminate some
|
// Commit authors are *always* of type "User", so have to discriminate some
|
||||||
// other way. Making the assumption of a suffix for now, see how that goes.
|
// other way. Making the assumption of a suffix for now, see how that goes.
|
||||||
} else if (__typename === "Commit" && !author.user.login.endsWith("-bot")) {
|
} else if (__typename === "Commit" && !author.user.login.endsWith("-bot")) {
|
||||||
memo.add("@" + author.user.login);
|
memo.add("@" + author.user.login);
|
||||||
}
|
}
|
||||||
@ -701,8 +744,7 @@ function extractContributors(entries) {
|
|||||||
return memo;
|
return memo;
|
||||||
}, new Set());
|
}, new Set());
|
||||||
|
|
||||||
return Array.from(set)
|
return Array.from(set).sort((a, b) => a.localeCompare(b, "en", {sensitivity: "base"}));
|
||||||
.sort((a, b) => a.localeCompare(b, "en", {sensitivity: "base"}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = new GraphQLClient("https://api.github.com/graphql", {
|
const client = new GraphQLClient("https://api.github.com/graphql", {
|
||||||
@ -727,13 +769,17 @@ async function generateChangelogEntry(changelog, targetVersion) {
|
|||||||
} else {
|
} else {
|
||||||
template = stableTemplate;
|
template = stableTemplate;
|
||||||
|
|
||||||
const codeCommitsAndPullRequests = await codeRepo.fetchCommitsAndPullRequestsSince("v" + previousVersion);
|
const codeCommitsAndPullRequests = await codeRepo.fetchCommitsAndPullRequestsSince(
|
||||||
|
"v" + previousVersion
|
||||||
|
);
|
||||||
items = parse(codeCommitsAndPullRequests);
|
items = parse(codeCommitsAndPullRequests);
|
||||||
items.milestone = await codeRepo.fetchMilestone(targetVersion);
|
items.milestone = await codeRepo.fetchMilestone(targetVersion);
|
||||||
|
|
||||||
const websiteRepo = new RepositoryFetcher(client, "thelounge.github.io");
|
const websiteRepo = new RepositoryFetcher(client, "thelounge.github.io");
|
||||||
const previousWebsiteVersion = await websiteRepo.fetchPreviousVersion(targetVersion);
|
const previousWebsiteVersion = await websiteRepo.fetchPreviousVersion(targetVersion);
|
||||||
const websiteCommitsAndPullRequests = await websiteRepo.fetchCommitsAndPullRequestsSince("v" + previousWebsiteVersion);
|
const websiteCommitsAndPullRequests = await websiteRepo.fetchCommitsAndPullRequestsSince(
|
||||||
|
"v" + previousWebsiteVersion
|
||||||
|
);
|
||||||
items.websiteDocumentation = websiteCommitsAndPullRequests;
|
items.websiteDocumentation = websiteCommitsAndPullRequests;
|
||||||
|
|
||||||
contributors = extractContributors([
|
contributors = extractContributors([
|
||||||
@ -781,11 +827,18 @@ function addToChangelog(changelog, newEntry) {
|
|||||||
const changelog = await readFile(changelogPath, "utf8");
|
const changelog = await readFile(changelogPath, "utf8");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
({changelogEntry, skipped, contributors} = await generateChangelogEntry(changelog, version));
|
({changelogEntry, skipped, contributors} = await generateChangelogEntry(
|
||||||
|
changelog,
|
||||||
|
version
|
||||||
|
));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.response && error.response.status === 401) {
|
if (error.response && error.response.status === 401) {
|
||||||
log.error(`GitHub returned an error: ${colors.red(error.response.message)}`);
|
log.error(`GitHub returned an error: ${colors.red(error.response.message)}`);
|
||||||
log.error(`Make sure your personal access token is set with ${colors.bold("public_repo")} scope.`);
|
log.error(
|
||||||
|
`Make sure your personal access token is set with ${colors.bold(
|
||||||
|
"public_repo"
|
||||||
|
)} scope.`
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
log.error(error);
|
log.error(error);
|
||||||
}
|
}
|
||||||
@ -806,10 +859,14 @@ function addToChangelog(changelog, newEntry) {
|
|||||||
|
|
||||||
// Step 3 (optional): Print a list of skipped entries if there are any
|
// Step 3 (optional): Print a list of skipped entries if there are any
|
||||||
if (skipped.length > 0) {
|
if (skipped.length > 0) {
|
||||||
const pad = Math.max(...skipped.map((entry) => (entry.title || entry.messageHeadline).length));
|
const pad = Math.max(
|
||||||
|
...skipped.map((entry) => (entry.title || entry.messageHeadline).length)
|
||||||
|
);
|
||||||
log.warn(`${skipped.length} ${skipped.length > 1 ? "entries were" : "entry was"} skipped:`);
|
log.warn(`${skipped.length} ${skipped.length > 1 ? "entries were" : "entry was"} skipped:`);
|
||||||
skipped.forEach((entry) => {
|
skipped.forEach((entry) => {
|
||||||
log.warn(`- ${(entry.title || entry.messageHeadline).padEnd(pad)} ${colors.gray(entry.url)}`);
|
log.warn(
|
||||||
|
`- ${(entry.title || entry.messageHeadline).padEnd(pad)} ${colors.gray(entry.url)}`
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -819,7 +876,11 @@ function addToChangelog(changelog, newEntry) {
|
|||||||
if (isPrerelease(version)) {
|
if (isPrerelease(version)) {
|
||||||
log.info(`You can now run: ${colors.bold(commitCommand)}`);
|
log.info(`You can now run: ${colors.bold(commitCommand)}`);
|
||||||
} else {
|
} else {
|
||||||
log.info(`Please edit ${colors.bold("CHANGELOG.md")} to your liking then run: ${colors.bold(commitCommand)}`);
|
log.info(
|
||||||
|
`Please edit ${colors.bold("CHANGELOG.md")} to your liking then run: ${colors.bold(
|
||||||
|
commitCommand
|
||||||
|
)}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info(`Finished in ${colors.bold(Date.now() - startTime)}ms.`);
|
log.info(`Finished in ${colors.bold(Date.now() - startTime)}ms.`);
|
||||||
|
@ -15,15 +15,10 @@ const {join} = require("path");
|
|||||||
const {spawnSync} = require("child_process");
|
const {spawnSync} = require("child_process");
|
||||||
|
|
||||||
function getGitUsername() {
|
function getGitUsername() {
|
||||||
return spawnSync("git", ["config", "user.name"], {encoding: "utf8"})
|
return spawnSync("git", ["config", "user.name"], {encoding: "utf8"}).stdout.trim();
|
||||||
.stdout
|
|
||||||
.trim();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const configContent = readFileSync(
|
const configContent = readFileSync(join(__dirname, "..", "defaults", "config.js"), "utf8");
|
||||||
join(__dirname, "..", "defaults", "config.js"),
|
|
||||||
"utf8"
|
|
||||||
);
|
|
||||||
|
|
||||||
const docPath = join(process.argv[2], "_includes", "config.js.md");
|
const docPath = join(process.argv[2], "_includes", "config.js.md");
|
||||||
|
|
||||||
@ -42,7 +37,8 @@ const extractedDoc = configContent
|
|||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, []).join("\n");
|
}, [])
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
const infoBlockHeader = `<!--
|
const infoBlockHeader = `<!--
|
||||||
DO NOT EDIT THIS FILE MANUALLY.
|
DO NOT EDIT THIS FILE MANUALLY.
|
||||||
@ -62,10 +58,13 @@ writeFileSync(docPath, generatedContent);
|
|||||||
|
|
||||||
log.info(
|
log.info(
|
||||||
`${colors.bold(generatedContent.split("\n").length)} lines ` +
|
`${colors.bold(generatedContent.split("\n").length)} lines ` +
|
||||||
`(${colors.bold(generatedContent.length)} characters) ` +
|
`(${colors.bold(generatedContent.length)} characters) ` +
|
||||||
`were written in ${colors.green(docPath)}.`
|
`were written in ${colors.green(docPath)}.`
|
||||||
);
|
);
|
||||||
|
|
||||||
function getPrettyDate() {
|
function getPrettyDate() {
|
||||||
return (new Date()).toISOString().split(".")[0].replace("T", " ");
|
return new Date()
|
||||||
|
.toISOString()
|
||||||
|
.split(".")[0]
|
||||||
|
.replace("T", " ");
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,9 @@ const path = require("path");
|
|||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
const response = await got("https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json");
|
const response = await got(
|
||||||
|
"https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json"
|
||||||
|
);
|
||||||
const emojiStrategy = JSON.parse(response.body);
|
const emojiStrategy = JSON.parse(response.body);
|
||||||
const emojiMap = {};
|
const emojiMap = {};
|
||||||
const fullNameEmojiMap = {};
|
const fullNameEmojiMap = {};
|
||||||
@ -21,21 +23,13 @@ const fs = require("fs");
|
|||||||
const emojiMapOutput = JSON.stringify(emojiMap, null, 2) + "\n";
|
const emojiMapOutput = JSON.stringify(emojiMap, null, 2) + "\n";
|
||||||
const fullNameEmojiMapOutput = JSON.stringify(fullNameEmojiMap, null, 2) + "\n";
|
const fullNameEmojiMapOutput = JSON.stringify(fullNameEmojiMap, null, 2) + "\n";
|
||||||
|
|
||||||
fs.writeFileSync(path.resolve(path.join(
|
fs.writeFileSync(
|
||||||
__dirname,
|
path.resolve(path.join(__dirname, "..", "client", "js", "libs", "simplemap.json")),
|
||||||
"..",
|
emojiMapOutput
|
||||||
"client",
|
);
|
||||||
"js",
|
|
||||||
"libs",
|
|
||||||
"simplemap.json"
|
|
||||||
)), emojiMapOutput);
|
|
||||||
|
|
||||||
fs.writeFileSync(path.resolve(path.join(
|
fs.writeFileSync(
|
||||||
__dirname,
|
path.resolve(path.join(__dirname, "..", "client", "js", "libs", "fullnamemap.json")),
|
||||||
"..",
|
fullNameEmojiMapOutput
|
||||||
"client",
|
);
|
||||||
"js",
|
|
||||||
"libs",
|
|
||||||
"fullnamemap.json"
|
|
||||||
)), fullNameEmojiMapOutput);
|
|
||||||
})();
|
})();
|
||||||
|
200
src/client.js
200
src/client.js
@ -159,18 +159,26 @@ Client.prototype.connect = function(args) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
channels.push(client.createChannel({
|
channels.push(
|
||||||
name: chan.name,
|
client.createChannel({
|
||||||
key: chan.key || "",
|
name: chan.name,
|
||||||
type: chan.type,
|
key: chan.key || "",
|
||||||
}));
|
type: chan.type,
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (badName && client.name) {
|
if (badName && client.name) {
|
||||||
log.warn("User '" + client.name + "' on network '" + args.name + "' has an invalid channel which has been ignored");
|
log.warn(
|
||||||
|
"User '" +
|
||||||
|
client.name +
|
||||||
|
"' on network '" +
|
||||||
|
args.name +
|
||||||
|
"' has an invalid channel which has been ignored"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// `join` is kept for backwards compatibility when updating from versions <2.0
|
// `join` is kept for backwards compatibility when updating from versions <2.0
|
||||||
// also used by the "connect" window
|
// also used by the "connect" window
|
||||||
} else if (args.join) {
|
} else if (args.join) {
|
||||||
channels = args.join
|
channels = args.join
|
||||||
.replace(/,/g, " ")
|
.replace(/,/g, " ")
|
||||||
@ -188,7 +196,9 @@ Client.prototype.connect = function(args) {
|
|||||||
|
|
||||||
const network = new Network({
|
const network = new Network({
|
||||||
uuid: args.uuid,
|
uuid: args.uuid,
|
||||||
name: String(args.name || (Helper.config.displayNetwork ? "" : Helper.config.defaults.name) || ""),
|
name: String(
|
||||||
|
args.name || (Helper.config.displayNetwork ? "" : Helper.config.defaults.name) || ""
|
||||||
|
),
|
||||||
host: String(args.host || ""),
|
host: String(args.host || ""),
|
||||||
port: parseInt(args.port, 10),
|
port: parseInt(args.port, 10),
|
||||||
tls: !!args.tls,
|
tls: !!args.tls,
|
||||||
@ -218,16 +228,18 @@ Client.prototype.connect = function(args) {
|
|||||||
network.createIrcFramework(client);
|
network.createIrcFramework(client);
|
||||||
|
|
||||||
events.forEach((plugin) => {
|
events.forEach((plugin) => {
|
||||||
require(`./plugins/irc-events/${plugin}`).apply(client, [
|
require(`./plugins/irc-events/${plugin}`).apply(client, [network.irc, network]);
|
||||||
network.irc,
|
|
||||||
network,
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (network.userDisconnected) {
|
if (network.userDisconnected) {
|
||||||
network.channels[0].pushMessage(client, new Msg({
|
network.channels[0].pushMessage(
|
||||||
text: "You have manually disconnected from this network before, use the /connect command to connect again.",
|
client,
|
||||||
}), true);
|
new Msg({
|
||||||
|
text:
|
||||||
|
"You have manually disconnected from this network before, use the /connect command to connect again.",
|
||||||
|
}),
|
||||||
|
true
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
network.irc.connect();
|
network.irc.connect();
|
||||||
}
|
}
|
||||||
@ -248,7 +260,10 @@ Client.prototype.generateToken = function(callback) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Client.prototype.calculateTokenHash = function(token) {
|
Client.prototype.calculateTokenHash = function(token) {
|
||||||
return crypto.createHash("sha512").update(token).digest("hex");
|
return crypto
|
||||||
|
.createHash("sha512")
|
||||||
|
.update(token)
|
||||||
|
.digest("hex");
|
||||||
};
|
};
|
||||||
|
|
||||||
Client.prototype.updateSession = function(token, ip, request) {
|
Client.prototype.updateSession = function(token, ip, request) {
|
||||||
@ -284,16 +299,20 @@ Client.prototype.updateSession = function(token, ip, request) {
|
|||||||
Client.prototype.setPassword = function(hash, callback) {
|
Client.prototype.setPassword = function(hash, callback) {
|
||||||
const client = this;
|
const client = this;
|
||||||
|
|
||||||
client.manager.updateUser(client.name, {
|
client.manager.updateUser(
|
||||||
password: hash,
|
client.name,
|
||||||
}, function(err) {
|
{
|
||||||
if (err) {
|
password: hash,
|
||||||
return callback(false);
|
},
|
||||||
}
|
function(err) {
|
||||||
|
if (err) {
|
||||||
|
return callback(false);
|
||||||
|
}
|
||||||
|
|
||||||
client.config.password = hash;
|
client.config.password = hash;
|
||||||
return callback(true);
|
return callback(true);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Client.prototype.input = function(data) {
|
Client.prototype.input = function(data) {
|
||||||
@ -321,10 +340,13 @@ Client.prototype.inputLine = function(data) {
|
|||||||
// This is either a normal message or a command escaped with a leading '/'
|
// This is either a normal message or a command escaped with a leading '/'
|
||||||
if (text.charAt(0) !== "/" || text.charAt(1) === "/") {
|
if (text.charAt(0) !== "/" || text.charAt(1) === "/") {
|
||||||
if (target.chan.type === Chan.Type.LOBBY) {
|
if (target.chan.type === Chan.Type.LOBBY) {
|
||||||
target.chan.pushMessage(this, new Msg({
|
target.chan.pushMessage(
|
||||||
type: Msg.Type.ERROR,
|
this,
|
||||||
text: "Messages can not be sent to lobbies.",
|
new Msg({
|
||||||
}));
|
type: Msg.Type.ERROR,
|
||||||
|
text: "Messages can not be sent to lobbies.",
|
||||||
|
})
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -351,17 +373,25 @@ Client.prototype.inputLine = function(data) {
|
|||||||
|
|
||||||
if (typeof plugin.input === "function" && (connected || plugin.allowDisconnected)) {
|
if (typeof plugin.input === "function" && (connected || plugin.allowDisconnected)) {
|
||||||
connected = true;
|
connected = true;
|
||||||
plugin.input(new PublicClient(client), {network: target.network, chan: target.chan}, cmd, args);
|
plugin.input(
|
||||||
|
new PublicClient(client),
|
||||||
|
{network: target.network, chan: target.chan},
|
||||||
|
cmd,
|
||||||
|
args
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else if (connected) {
|
} else if (connected) {
|
||||||
irc.raw(text);
|
irc.raw(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!connected) {
|
if (!connected) {
|
||||||
target.chan.pushMessage(this, new Msg({
|
target.chan.pushMessage(
|
||||||
type: Msg.Type.ERROR,
|
this,
|
||||||
text: "You are not connected to the IRC network, unable to send your command.",
|
new Msg({
|
||||||
}));
|
type: Msg.Type.ERROR,
|
||||||
|
text: "You are not connected to the IRC network, unable to send your command.",
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -385,7 +415,10 @@ Client.prototype.compileCustomHighlights = function() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
client.highlightRegex = new RegExp(`(?:^|[ .,+!?|/:<>(){}'"@&~-])(?:${highlightsTokens.join("|")})(?:$|[ .,+!?|/:<>(){}'"-])`, "i");
|
client.highlightRegex = new RegExp(
|
||||||
|
`(?:^|[ .,+!?|/:<>(){}'"@&~-])(?:${highlightsTokens.join("|")})(?:$|[ .,+!?|/:<>(){}'"-])`,
|
||||||
|
"i"
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Client.prototype.more = function(data) {
|
Client.prototype.more = function(data) {
|
||||||
@ -458,45 +491,45 @@ Client.prototype.sort = function(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case "networks":
|
case "networks":
|
||||||
this.networks.sort((a, b) => order.indexOf(a.uuid) - order.indexOf(b.uuid));
|
this.networks.sort((a, b) => order.indexOf(a.uuid) - order.indexOf(b.uuid));
|
||||||
|
|
||||||
// Sync order to connected clients
|
// Sync order to connected clients
|
||||||
this.emit("sync_sort", {
|
this.emit("sync_sort", {
|
||||||
order: this.networks.map((obj) => obj.uuid),
|
order: this.networks.map((obj) => obj.uuid),
|
||||||
type: data.type,
|
type: data.type,
|
||||||
});
|
});
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "channels": {
|
case "channels": {
|
||||||
const network = _.find(this.networks, {uuid: data.target});
|
const network = _.find(this.networks, {uuid: data.target});
|
||||||
|
|
||||||
if (!network) {
|
if (!network) {
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
network.channels.sort((a, b) => {
|
|
||||||
// Always sort lobby to the top regardless of what the client has sent
|
|
||||||
// Because there's a lot of code that presumes channels[0] is the lobby
|
|
||||||
if (a.type === Chan.Type.LOBBY) {
|
|
||||||
return -1;
|
|
||||||
} else if (b.type === Chan.Type.LOBBY) {
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return order.indexOf(a.id) - order.indexOf(b.id);
|
network.channels.sort((a, b) => {
|
||||||
});
|
// Always sort lobby to the top regardless of what the client has sent
|
||||||
|
// Because there's a lot of code that presumes channels[0] is the lobby
|
||||||
|
if (a.type === Chan.Type.LOBBY) {
|
||||||
|
return -1;
|
||||||
|
} else if (b.type === Chan.Type.LOBBY) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
// Sync order to connected clients
|
return order.indexOf(a.id) - order.indexOf(b.id);
|
||||||
this.emit("sync_sort", {
|
});
|
||||||
order: network.channels.map((obj) => obj.id),
|
|
||||||
type: data.type,
|
|
||||||
target: network.uuid,
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
// Sync order to connected clients
|
||||||
}
|
this.emit("sync_sort", {
|
||||||
|
order: network.channels.map((obj) => obj.id),
|
||||||
|
type: data.type,
|
||||||
|
target: network.uuid,
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.save();
|
this.save();
|
||||||
@ -581,9 +614,14 @@ Client.prototype.clientDetach = function(socketId) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Client.prototype.registerPushSubscription = function(session, subscription, noSave) {
|
Client.prototype.registerPushSubscription = function(session, subscription, noSave) {
|
||||||
if (!_.isPlainObject(subscription) || !_.isPlainObject(subscription.keys)
|
if (
|
||||||
|| typeof subscription.endpoint !== "string" || !/^https?:\/\//.test(subscription.endpoint)
|
!_.isPlainObject(subscription) ||
|
||||||
|| typeof subscription.keys.p256dh !== "string" || typeof subscription.keys.auth !== "string") {
|
!_.isPlainObject(subscription.keys) ||
|
||||||
|
typeof subscription.endpoint !== "string" ||
|
||||||
|
!/^https?:\/\//.test(subscription.endpoint) ||
|
||||||
|
typeof subscription.keys.p256dh !== "string" ||
|
||||||
|
typeof subscription.keys.auth !== "string"
|
||||||
|
) {
|
||||||
session.pushSubscription = null;
|
session.pushSubscription = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -614,13 +652,17 @@ Client.prototype.unregisterPushSubscription = function(token) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Client.prototype.save = _.debounce(function SaveClient() {
|
Client.prototype.save = _.debounce(
|
||||||
if (Helper.config.public) {
|
function SaveClient() {
|
||||||
return;
|
if (Helper.config.public) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const client = this;
|
const client = this;
|
||||||
const json = {};
|
const json = {};
|
||||||
json.networks = this.networks.map((n) => n.export());
|
json.networks = this.networks.map((n) => n.export());
|
||||||
client.manager.updateUser(client.name, json);
|
client.manager.updateUser(client.name, json);
|
||||||
}, 1000, {maxWait: 10000});
|
},
|
||||||
|
1000,
|
||||||
|
{maxWait: 10000}
|
||||||
|
);
|
||||||
|
@ -31,7 +31,9 @@ ClientManager.prototype.findClient = function(name) {
|
|||||||
|
|
||||||
ClientManager.prototype.autoloadUsers = function() {
|
ClientManager.prototype.autoloadUsers = function() {
|
||||||
const users = this.getUsers();
|
const users = this.getUsers();
|
||||||
const noUsersWarning = `There are currently no users. Create one with ${colors.bold("thelounge add <name>")}.`;
|
const noUsersWarning = `There are currently no users. Create one with ${colors.bold(
|
||||||
|
"thelounge add <name>"
|
||||||
|
)}.`;
|
||||||
|
|
||||||
if (users.length === 0) {
|
if (users.length === 0) {
|
||||||
log.info(noUsersWarning);
|
log.info(noUsersWarning);
|
||||||
@ -39,28 +41,35 @@ ClientManager.prototype.autoloadUsers = function() {
|
|||||||
|
|
||||||
users.forEach((name) => this.loadUser(name));
|
users.forEach((name) => this.loadUser(name));
|
||||||
|
|
||||||
fs.watch(Helper.getUsersPath(), _.debounce(() => {
|
fs.watch(
|
||||||
const loaded = this.clients.map((c) => c.name);
|
Helper.getUsersPath(),
|
||||||
const updatedUsers = this.getUsers();
|
_.debounce(
|
||||||
|
() => {
|
||||||
|
const loaded = this.clients.map((c) => c.name);
|
||||||
|
const updatedUsers = this.getUsers();
|
||||||
|
|
||||||
if (updatedUsers.length === 0) {
|
if (updatedUsers.length === 0) {
|
||||||
log.info(noUsersWarning);
|
log.info(noUsersWarning);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload all users. Existing users will only have their passwords reloaded.
|
// Reload all users. Existing users will only have their passwords reloaded.
|
||||||
updatedUsers.forEach((name) => this.loadUser(name));
|
updatedUsers.forEach((name) => this.loadUser(name));
|
||||||
|
|
||||||
// Existing users removed since last time users were loaded
|
// Existing users removed since last time users were loaded
|
||||||
_.difference(loaded, updatedUsers).forEach((name) => {
|
_.difference(loaded, updatedUsers).forEach((name) => {
|
||||||
const client = _.find(this.clients, {name});
|
const client = _.find(this.clients, {name});
|
||||||
|
|
||||||
if (client) {
|
if (client) {
|
||||||
client.quit(true);
|
client.quit(true);
|
||||||
this.clients = _.without(this.clients, client);
|
this.clients = _.without(this.clients, client);
|
||||||
log.info(`User ${colors.bold(name)} disconnected and removed.`);
|
log.info(`User ${colors.bold(name)} disconnected and removed.`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, 1000, {maxWait: 10000}));
|
},
|
||||||
|
1000,
|
||||||
|
{maxWait: 10000}
|
||||||
|
)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
ClientManager.prototype.loadUser = function(name) {
|
ClientManager.prototype.loadUser = function(name) {
|
||||||
|
@ -8,7 +8,8 @@ const program = require("commander");
|
|||||||
const Helper = require("../helper");
|
const Helper = require("../helper");
|
||||||
const Utils = require("./utils");
|
const Utils = require("./utils");
|
||||||
|
|
||||||
program.version(Helper.getVersion(), "-v, --version")
|
program
|
||||||
|
.version(Helper.getVersion(), "-v, --version")
|
||||||
.option(
|
.option(
|
||||||
"-c, --config <key=value>",
|
"-c, --config <key=value>",
|
||||||
"override entries of the configuration file, must be specified for each entry that needs to be overriden",
|
"override entries of the configuration file, must be specified for each entry that needs to be overriden",
|
||||||
@ -26,13 +27,21 @@ if (process.getuid) {
|
|||||||
const uid = process.getuid();
|
const uid = process.getuid();
|
||||||
|
|
||||||
if (uid === 0) {
|
if (uid === 0) {
|
||||||
log.warn(`You are currently running The Lounge as root. ${colors.bold.red("We highly discourage running as root!")}`);
|
log.warn(
|
||||||
|
`You are currently running The Lounge as root. ${colors.bold.red(
|
||||||
|
"We highly discourage running as root!"
|
||||||
|
)}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.stat(path.join(Helper.getHomePath(), "config.js"), (err, stat) => {
|
fs.stat(path.join(Helper.getHomePath(), "config.js"), (err, stat) => {
|
||||||
if (!err && stat.uid !== uid) {
|
if (!err && stat.uid !== uid) {
|
||||||
log.warn("Config file owner does not match the user you are currently running The Lounge as.");
|
log.warn(
|
||||||
log.warn("To avoid issues, you should execute The Lounge commands under the same user.");
|
"Config file owner does not match the user you are currently running The Lounge as."
|
||||||
|
);
|
||||||
|
log.warn(
|
||||||
|
"To avoid issues, you should execute The Lounge commands under the same user."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -30,41 +30,63 @@ program
|
|||||||
packageJson(packageName, {
|
packageJson(packageName, {
|
||||||
fullMetadata: true,
|
fullMetadata: true,
|
||||||
version: packageVersion,
|
version: packageVersion,
|
||||||
}).then((json) => {
|
})
|
||||||
if (!("thelounge" in json)) {
|
.then((json) => {
|
||||||
log.error(`${colors.red(json.name + " v" + json.version)} does not have The Lounge metadata.`);
|
if (!("thelounge" in json)) {
|
||||||
|
log.error(
|
||||||
|
`${colors.red(
|
||||||
|
json.name + " v" + json.version
|
||||||
|
)} does not have The Lounge metadata.`
|
||||||
|
);
|
||||||
|
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info(`Installing ${colors.green(json.name + " v" + json.version)}...`);
|
||||||
|
|
||||||
|
const packagesPath = Helper.getPackagesPath();
|
||||||
|
const packagesConfig = path.join(packagesPath, "package.json");
|
||||||
|
|
||||||
|
// Create node_modules folder, otherwise yarn will start walking upwards to find one
|
||||||
|
fsextra.ensureDirSync(path.join(packagesPath, "node_modules"));
|
||||||
|
|
||||||
|
// Create package.json with private set to true, if it doesn't exist already
|
||||||
|
if (!fs.existsSync(packagesConfig)) {
|
||||||
|
fs.writeFileSync(
|
||||||
|
packagesConfig,
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
private: true,
|
||||||
|
description:
|
||||||
|
"Packages for The Lounge. All packages in node_modules directory will be automatically loaded.",
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
"\t"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Utils.executeYarnCommand(
|
||||||
|
"add",
|
||||||
|
"--production",
|
||||||
|
"--exact",
|
||||||
|
`${json.name}@${json.version}`
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
log.info(
|
||||||
|
`${colors.green(
|
||||||
|
json.name + " v" + json.version
|
||||||
|
)} has been successfully installed.`
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch((code) => {
|
||||||
|
throw `Failed to install ${colors.green(
|
||||||
|
json.name + " v" + json.version
|
||||||
|
)}. Exit code: ${code}`;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
log.error(`${e}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
|
||||||
|
|
||||||
log.info(`Installing ${colors.green(json.name + " v" + json.version)}...`);
|
|
||||||
|
|
||||||
const packagesPath = Helper.getPackagesPath();
|
|
||||||
const packagesConfig = path.join(packagesPath, "package.json");
|
|
||||||
|
|
||||||
// Create node_modules folder, otherwise yarn will start walking upwards to find one
|
|
||||||
fsextra.ensureDirSync(path.join(packagesPath, "node_modules"));
|
|
||||||
|
|
||||||
// Create package.json with private set to true, if it doesn't exist already
|
|
||||||
if (!fs.existsSync(packagesConfig)) {
|
|
||||||
fs.writeFileSync(packagesConfig, JSON.stringify({
|
|
||||||
private: true,
|
|
||||||
description: "Packages for The Lounge. All packages in node_modules directory will be automatically loaded.",
|
|
||||||
}, null, "\t"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Utils.executeYarnCommand(
|
|
||||||
"add",
|
|
||||||
"--production",
|
|
||||||
"--exact",
|
|
||||||
`${json.name}@${json.version}`
|
|
||||||
).then(() => {
|
|
||||||
log.info(`${colors.green(json.name + " v" + json.version)} has been successfully installed.`);
|
|
||||||
}).catch((code) => {
|
|
||||||
throw `Failed to install ${colors.green(json.name + " v" + json.version)}. Exit code: ${code}`;
|
|
||||||
});
|
});
|
||||||
}).catch((e) => {
|
|
||||||
log.error(`${e}`);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -24,13 +24,10 @@ function initalizeConfig() {
|
|||||||
if (!fs.existsSync(Helper.getConfigPath())) {
|
if (!fs.existsSync(Helper.getConfigPath())) {
|
||||||
fsextra.ensureDirSync(Helper.getHomePath());
|
fsextra.ensureDirSync(Helper.getHomePath());
|
||||||
fs.chmodSync(Helper.getHomePath(), "0700");
|
fs.chmodSync(Helper.getHomePath(), "0700");
|
||||||
fsextra.copySync(path.resolve(path.join(
|
fsextra.copySync(
|
||||||
__dirname,
|
path.resolve(path.join(__dirname, "..", "..", "defaults", "config.js")),
|
||||||
"..",
|
Helper.getConfigPath()
|
||||||
"..",
|
);
|
||||||
"defaults",
|
|
||||||
"config.js"
|
|
||||||
)), Helper.getConfigPath());
|
|
||||||
log.info(`Configuration file created at ${colors.green(Helper.getConfigPath())}.`);
|
log.info(`Configuration file created at ${colors.green(Helper.getConfigPath())}.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,18 +32,20 @@ program
|
|||||||
|
|
||||||
const packages = JSON.parse(fs.readFileSync(packagesConfig, "utf-8"));
|
const packages = JSON.parse(fs.readFileSync(packagesConfig, "utf-8"));
|
||||||
|
|
||||||
if (!packages.dependencies || !Object.prototype.hasOwnProperty.call(packages.dependencies, packageName)) {
|
if (
|
||||||
|
!packages.dependencies ||
|
||||||
|
!Object.prototype.hasOwnProperty.call(packages.dependencies, packageName)
|
||||||
|
) {
|
||||||
log.warn(packageWasNotInstalled);
|
log.warn(packageWasNotInstalled);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Utils.executeYarnCommand(
|
return Utils.executeYarnCommand("remove", packageName)
|
||||||
"remove",
|
.then(() => {
|
||||||
packageName
|
log.info(`${colors.green(packageName)} has been successfully uninstalled.`);
|
||||||
).then(() => {
|
})
|
||||||
log.info(`${colors.green(packageName)} has been successfully uninstalled.`);
|
.catch((code) => {
|
||||||
}).catch((code) => {
|
log.error(`Failed to uninstall ${colors.green(packageName)}. Exit code: ${code}`);
|
||||||
log.error(`Failed to uninstall ${colors.green(packageName)}. Exit code: ${code}`);
|
process.exit(1);
|
||||||
process.exit(1);
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -18,11 +18,7 @@ program
|
|||||||
const packagesPath = Helper.getPackagesPath();
|
const packagesPath = Helper.getPackagesPath();
|
||||||
const packagesConfig = path.join(packagesPath, "package.json");
|
const packagesConfig = path.join(packagesPath, "package.json");
|
||||||
const packagesList = JSON.parse(fs.readFileSync(packagesConfig)).dependencies;
|
const packagesList = JSON.parse(fs.readFileSync(packagesConfig)).dependencies;
|
||||||
const argsList = [
|
const argsList = ["upgrade", "--production", "--latest"];
|
||||||
"upgrade",
|
|
||||||
"--production",
|
|
||||||
"--latest",
|
|
||||||
];
|
|
||||||
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
|
||||||
@ -54,9 +50,11 @@ program
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Utils.executeYarnCommand(...argsList).then(() => {
|
return Utils.executeYarnCommand(...argsList)
|
||||||
log.info("Package(s) have been successfully upgraded.");
|
.then(() => {
|
||||||
}).catch((code) => {
|
log.info("Package(s) have been successfully upgraded.");
|
||||||
throw `Failed to upgrade package(s). Exit code ${code}`;
|
})
|
||||||
});
|
.catch((code) => {
|
||||||
|
throw `Failed to upgrade package(s). Exit code ${code}`;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -21,7 +21,8 @@ program
|
|||||||
const manager = new ClientManager();
|
const manager = new ClientManager();
|
||||||
const users = manager.getUsers();
|
const users = manager.getUsers();
|
||||||
|
|
||||||
if (users === undefined) { // There was an error, already logged
|
if (users === undefined) {
|
||||||
|
// There was an error, already logged
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,31 +31,37 @@ program
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.prompt({
|
log.prompt(
|
||||||
text: "Enter password:",
|
{
|
||||||
silent: true,
|
text: "Enter password:",
|
||||||
}, function(err, password) {
|
silent: true,
|
||||||
if (!password) {
|
},
|
||||||
log.error("Password cannot be empty.");
|
function(err, password) {
|
||||||
return;
|
if (!password) {
|
||||||
}
|
log.error("Password cannot be empty.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!err) {
|
if (!err) {
|
||||||
log.prompt({
|
log.prompt(
|
||||||
text: "Save logs to disk?",
|
{
|
||||||
default: "yes",
|
text: "Save logs to disk?",
|
||||||
}, function(err2, enableLog) {
|
default: "yes",
|
||||||
if (!err2) {
|
},
|
||||||
add(
|
function(err2, enableLog) {
|
||||||
manager,
|
if (!err2) {
|
||||||
name,
|
add(
|
||||||
password,
|
manager,
|
||||||
enableLog.charAt(0).toLowerCase() === "y"
|
name,
|
||||||
);
|
password,
|
||||||
}
|
enableLog.charAt(0).toLowerCase() === "y"
|
||||||
});
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
function add(manager, name, password, enableLog) {
|
function add(manager, name, password, enableLog) {
|
||||||
|
@ -21,7 +21,8 @@ program
|
|||||||
const ClientManager = require("../../clientManager");
|
const ClientManager = require("../../clientManager");
|
||||||
const users = new ClientManager().getUsers();
|
const users = new ClientManager().getUsers();
|
||||||
|
|
||||||
if (users === undefined) { // There was an error, already logged
|
if (users === undefined) {
|
||||||
|
// There was an error, already logged
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,6 +37,10 @@ program
|
|||||||
{stdio: "inherit"}
|
{stdio: "inherit"}
|
||||||
);
|
);
|
||||||
child_spawn.on("error", function() {
|
child_spawn.on("error", function() {
|
||||||
log.error(`Unable to open ${colors.green(Helper.getUserConfigPath(name))}. ${colors.bold("$EDITOR")} is not set, and ${colors.bold("vi")} was not found.`);
|
log.error(
|
||||||
|
`Unable to open ${colors.green(Helper.getUserConfigPath(name))}. ${colors.bold(
|
||||||
|
"$EDITOR"
|
||||||
|
)} is not set, and ${colors.bold("vi")} was not found.`
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -20,7 +20,8 @@ program
|
|||||||
const ClientManager = require("../../clientManager");
|
const ClientManager = require("../../clientManager");
|
||||||
const users = new ClientManager().getUsers();
|
const users = new ClientManager().getUsers();
|
||||||
|
|
||||||
if (users === undefined) { // There was an error, already logged
|
if (users === undefined) {
|
||||||
|
// There was an error, already logged
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,6 +31,10 @@ program
|
|||||||
log.info(`${i + 1}. ${colors.bold(user)}`);
|
log.info(`${i + 1}. ${colors.bold(user)}`);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
log.info(`There are currently no users. Create one with ${colors.bold("thelounge add <name>")}.`);
|
log.info(
|
||||||
|
`There are currently no users. Create one with ${colors.bold(
|
||||||
|
"thelounge add <name>"
|
||||||
|
)}.`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -20,7 +20,8 @@ program
|
|||||||
const ClientManager = require("../../clientManager");
|
const ClientManager = require("../../clientManager");
|
||||||
const users = new ClientManager().getUsers();
|
const users = new ClientManager().getUsers();
|
||||||
|
|
||||||
if (users === undefined) { // There was an error, already logged
|
if (users === undefined) {
|
||||||
|
// There was an error, already logged
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,20 +32,20 @@ program
|
|||||||
|
|
||||||
const file = Helper.getUserConfigPath(name);
|
const file = Helper.getUserConfigPath(name);
|
||||||
const user = require(file);
|
const user = require(file);
|
||||||
log.prompt({
|
log.prompt(
|
||||||
text: "Enter new password:",
|
{
|
||||||
silent: true,
|
text: "Enter new password:",
|
||||||
}, function(err, password) {
|
silent: true,
|
||||||
if (err) {
|
},
|
||||||
return;
|
function(err, password) {
|
||||||
}
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
user.password = Helper.password.hash(password);
|
user.password = Helper.password.hash(password);
|
||||||
user.sessions = {};
|
user.sessions = {};
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(file, JSON.stringify(user, null, "\t"));
|
||||||
file,
|
log.info(`Successfully reset password for ${colors.bold(name)}.`);
|
||||||
JSON.stringify(user, null, "\t")
|
}
|
||||||
);
|
);
|
||||||
log.info(`Successfully reset password for ${colors.bold(name)}.`);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -16,7 +16,9 @@ class Utils {
|
|||||||
"",
|
"",
|
||||||
" Environment variable:",
|
" Environment variable:",
|
||||||
"",
|
"",
|
||||||
` THELOUNGE_HOME Path for all configuration files and folders. Defaults to ${colors.green(Helper.expandHome(Utils.defaultHome()))}.`,
|
` THELOUNGE_HOME Path for all configuration files and folders. Defaults to ${colors.green(
|
||||||
|
Helper.expandHome(Utils.defaultHome())
|
||||||
|
)}.`,
|
||||||
"",
|
"",
|
||||||
].forEach((e) => log.raw(e));
|
].forEach((e) => log.raw(e));
|
||||||
}
|
}
|
||||||
@ -32,8 +34,16 @@ class Utils {
|
|||||||
|
|
||||||
console.log(); // eslint-disable-line no-console
|
console.log(); // eslint-disable-line no-console
|
||||||
log.warn(`Folder ${colors.bold.red(oldHome)} still exists.`);
|
log.warn(`Folder ${colors.bold.red(oldHome)} still exists.`);
|
||||||
log.warn(`In v3, we renamed the default configuration folder to ${colors.bold.green(".thelounge")} for consistency.`);
|
log.warn(
|
||||||
log.warn(`You might want to rename the folder from ${colors.bold.red(".lounge")} to ${colors.bold.green(".thelounge")} to keep existing configuration.`);
|
`In v3, we renamed the default configuration folder to ${colors.bold.green(
|
||||||
|
".thelounge"
|
||||||
|
)} for consistency.`
|
||||||
|
);
|
||||||
|
log.warn(
|
||||||
|
`You might want to rename the folder from ${colors.bold.red(
|
||||||
|
".lounge"
|
||||||
|
)} to ${colors.bold.green(".thelounge")} to keep existing configuration.`
|
||||||
|
);
|
||||||
log.warn("Make sure to look at the release notes to see other breaking changes.");
|
log.warn("Make sure to look at the release notes to see other breaking changes.");
|
||||||
console.log(); // eslint-disable-line no-console
|
console.log(); // eslint-disable-line no-console
|
||||||
}
|
}
|
||||||
@ -43,12 +53,7 @@ class Utils {
|
|||||||
return home;
|
return home;
|
||||||
}
|
}
|
||||||
|
|
||||||
const distConfig = path.resolve(path.join(
|
const distConfig = path.resolve(path.join(__dirname, "..", "..", ".thelounge_home"));
|
||||||
__dirname,
|
|
||||||
"..",
|
|
||||||
"..",
|
|
||||||
".thelounge_home"
|
|
||||||
));
|
|
||||||
|
|
||||||
home = fs.readFileSync(distConfig, "utf-8").trim();
|
home = fs.readFileSync(distConfig, "utf-8").trim();
|
||||||
|
|
||||||
@ -71,9 +76,11 @@ class Utils {
|
|||||||
return undefined;
|
return undefined;
|
||||||
} else if (value === "null") {
|
} else if (value === "null") {
|
||||||
return null;
|
return null;
|
||||||
} else if (/^-?[0-9]+$/.test(value)) { // Numbers like port
|
} else if (/^-?[0-9]+$/.test(value)) {
|
||||||
|
// Numbers like port
|
||||||
value = parseInt(value, 10);
|
value = parseInt(value, 10);
|
||||||
} else if (/^\[.*\]$/.test(value)) { // Arrays
|
} else if (/^\[.*\]$/.test(value)) {
|
||||||
|
// Arrays
|
||||||
// Supporting arrays `[a,b]` and `[a, b]`
|
// Supporting arrays `[a,b]` and `[a, b]`
|
||||||
const array = value.slice(1, -1).split(/,\s*/);
|
const array = value.slice(1, -1).split(/,\s*/);
|
||||||
|
|
||||||
@ -134,23 +141,29 @@ class Utils {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
add.stdout.on("data", (data) => {
|
add.stdout.on("data", (data) => {
|
||||||
data.toString().trim().split("\n").forEach((line) => {
|
data.toString()
|
||||||
line = JSON.parse(line);
|
.trim()
|
||||||
|
.split("\n")
|
||||||
|
.forEach((line) => {
|
||||||
|
line = JSON.parse(line);
|
||||||
|
|
||||||
if (line.type === "success") {
|
if (line.type === "success") {
|
||||||
success = true;
|
success = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
add.stderr.on("data", (data) => {
|
add.stderr.on("data", (data) => {
|
||||||
data.toString().trim().split("\n").forEach((line) => {
|
data.toString()
|
||||||
const json = JSON.parse(line);
|
.trim()
|
||||||
|
.split("\n")
|
||||||
|
.forEach((line) => {
|
||||||
|
const json = JSON.parse(line);
|
||||||
|
|
||||||
if (json.type === "error") {
|
if (json.type === "error") {
|
||||||
log.error(json.data);
|
log.error(json.data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
add.on("error", (e) => {
|
add.on("error", (e) => {
|
||||||
|
@ -50,12 +50,7 @@ const Helper = {
|
|||||||
|
|
||||||
module.exports = Helper;
|
module.exports = Helper;
|
||||||
|
|
||||||
Helper.config = require(path.resolve(path.join(
|
Helper.config = require(path.resolve(path.join(__dirname, "..", "defaults", "config.js")));
|
||||||
__dirname,
|
|
||||||
"..",
|
|
||||||
"defaults",
|
|
||||||
"config.js"
|
|
||||||
)));
|
|
||||||
|
|
||||||
function getVersion() {
|
function getVersion() {
|
||||||
const gitCommit = getGitCommit();
|
const gitCommit = getGitCommit();
|
||||||
@ -92,7 +87,10 @@ function getGitCommit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getVersionCacheBust() {
|
function getVersionCacheBust() {
|
||||||
const hash = crypto.createHash("sha256").update(Helper.getVersion()).digest("hex");
|
const hash = crypto
|
||||||
|
.createHash("sha256")
|
||||||
|
.update(Helper.getVersion())
|
||||||
|
.digest("hex");
|
||||||
|
|
||||||
return hash.substring(0, 10);
|
return hash.substring(0, 10);
|
||||||
}
|
}
|
||||||
@ -111,8 +109,16 @@ function setHome(newPath) {
|
|||||||
const userConfig = require(configPath);
|
const userConfig = require(configPath);
|
||||||
|
|
||||||
if (_.isEmpty(userConfig)) {
|
if (_.isEmpty(userConfig)) {
|
||||||
log.warn(`The file located at ${colors.green(configPath)} does not appear to expose anything.`);
|
log.warn(
|
||||||
log.warn(`Make sure it is non-empty and the configuration is exported using ${colors.bold("module.exports = { ... }")}.`);
|
`The file located at ${colors.green(
|
||||||
|
configPath
|
||||||
|
)} does not appear to expose anything.`
|
||||||
|
);
|
||||||
|
log.warn(
|
||||||
|
`Make sure it is non-empty and the configuration is exported using ${colors.bold(
|
||||||
|
"module.exports = { ... }"
|
||||||
|
)}.`
|
||||||
|
);
|
||||||
log.warn("Using default configuration...");
|
log.warn("Using default configuration...");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,14 +128,24 @@ function setHome(newPath) {
|
|||||||
if (!this.config.displayNetwork && !this.config.lockNetwork) {
|
if (!this.config.displayNetwork && !this.config.lockNetwork) {
|
||||||
this.config.lockNetwork = true;
|
this.config.lockNetwork = true;
|
||||||
|
|
||||||
log.warn(`${colors.bold("displayNetwork")} and ${colors.bold("lockNetwork")} are false, setting ${colors.bold("lockNetwork")} to true.`);
|
log.warn(
|
||||||
|
`${colors.bold("displayNetwork")} and ${colors.bold(
|
||||||
|
"lockNetwork"
|
||||||
|
)} are false, setting ${colors.bold("lockNetwork")} to true.`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const manifestPath = path.resolve(path.join(__dirname, "..", "public", "thelounge.webmanifest"));
|
const manifestPath = path.resolve(
|
||||||
|
path.join(__dirname, "..", "public", "thelounge.webmanifest")
|
||||||
|
);
|
||||||
|
|
||||||
// Check if manifest exists, if not, the app most likely was not built
|
// Check if manifest exists, if not, the app most likely was not built
|
||||||
if (!fs.existsSync(manifestPath)) {
|
if (!fs.existsSync(manifestPath)) {
|
||||||
log.error(`The client application was not built. Run ${colors.bold("NODE_ENV=production yarn build")} to resolve this.`);
|
log.error(
|
||||||
|
`The client application was not built. Run ${colors.bold(
|
||||||
|
"NODE_ENV=production yarn build"
|
||||||
|
)} to resolve this.`
|
||||||
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,13 +156,27 @@ function setHome(newPath) {
|
|||||||
// TODO: Remove in future release
|
// TODO: Remove in future release
|
||||||
if (["example", "crypto", "zenburn"].includes(this.config.theme)) {
|
if (["example", "crypto", "zenburn"].includes(this.config.theme)) {
|
||||||
if (this.config.theme === "example") {
|
if (this.config.theme === "example") {
|
||||||
log.warn(`The default theme ${colors.red("example")} was renamed to ${colors.green("default")} as of The Lounge v3.`);
|
log.warn(
|
||||||
|
`The default theme ${colors.red("example")} was renamed to ${colors.green(
|
||||||
|
"default"
|
||||||
|
)} as of The Lounge v3.`
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
log.warn(`The theme ${colors.red(this.config.theme)} was moved to a separate theme as of The Lounge v3.`);
|
log.warn(
|
||||||
log.warn(`Install it with ${colors.bold("thelounge install thelounge-theme-" + this.config.theme)}.`);
|
`The theme ${colors.red(
|
||||||
|
this.config.theme
|
||||||
|
)} was moved to a separate theme as of The Lounge v3.`
|
||||||
|
);
|
||||||
|
log.warn(
|
||||||
|
`Install it with ${colors.bold(
|
||||||
|
"thelounge install thelounge-theme-" + this.config.theme
|
||||||
|
)}.`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
log.warn(`Falling back to theme ${colors.green("default")} will be removed in a future release.`);
|
log.warn(
|
||||||
|
`Falling back to theme ${colors.green("default")} will be removed in a future release.`
|
||||||
|
);
|
||||||
log.warn("Please update your configuration file accordingly.");
|
log.warn("Please update your configuration file accordingly.");
|
||||||
|
|
||||||
this.config.theme = "default";
|
this.config.theme = "default";
|
||||||
@ -195,15 +225,18 @@ function ip2hex(address) {
|
|||||||
return "00000000";
|
return "00000000";
|
||||||
}
|
}
|
||||||
|
|
||||||
return address.split(".").map(function(octet) {
|
return address
|
||||||
let hex = parseInt(octet, 10).toString(16);
|
.split(".")
|
||||||
|
.map(function(octet) {
|
||||||
|
let hex = parseInt(octet, 10).toString(16);
|
||||||
|
|
||||||
if (hex.length === 1) {
|
if (hex.length === 1) {
|
||||||
hex = "0" + hex;
|
hex = "0" + hex;
|
||||||
}
|
}
|
||||||
|
|
||||||
return hex;
|
return hex;
|
||||||
}).join("");
|
})
|
||||||
|
.join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expand ~ into the current user home dir.
|
// Expand ~ into the current user home dir.
|
||||||
@ -246,7 +279,11 @@ function mergeConfig(oldConfig, newConfig) {
|
|||||||
|
|
||||||
return _.mergeWith(oldConfig, newConfig, (objValue, srcValue, key) => {
|
return _.mergeWith(oldConfig, newConfig, (objValue, srcValue, key) => {
|
||||||
// Do not override config variables if the type is incorrect (e.g. object changed into a string)
|
// Do not override config variables if the type is incorrect (e.g. object changed into a string)
|
||||||
if (typeof objValue !== "undefined" && objValue !== null && typeof objValue !== typeof srcValue) {
|
if (
|
||||||
|
typeof objValue !== "undefined" &&
|
||||||
|
objValue !== null &&
|
||||||
|
typeof objValue !== typeof srcValue
|
||||||
|
) {
|
||||||
log.warn(`Incorrect type for "${colors.bold(key)}", please verify your config.`);
|
log.warn(`Incorrect type for "${colors.bold(key)}", please verify your config.`);
|
||||||
|
|
||||||
return objValue;
|
return objValue;
|
||||||
@ -296,5 +333,9 @@ function parseHostmask(hostmask) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function compareHostmask(a, b) {
|
function compareHostmask(a, b) {
|
||||||
return (a.nick.toLowerCase() === b.nick.toLowerCase() || a.nick === "*") && (a.ident.toLowerCase() === b.ident.toLowerCase() || a.ident === "*") && (a.hostname.toLowerCase() === b.hostname.toLowerCase() || a.hostname === "*");
|
return (
|
||||||
|
(a.nick.toLowerCase() === b.nick.toLowerCase() || a.nick === "*") &&
|
||||||
|
(a.ident.toLowerCase() === b.ident.toLowerCase() || a.ident === "*") &&
|
||||||
|
(a.hostname.toLowerCase() === b.hostname.toLowerCase() || a.hostname === "*")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -20,22 +20,31 @@ class Identification {
|
|||||||
|
|
||||||
if (Helper.config.identd.enable) {
|
if (Helper.config.identd.enable) {
|
||||||
if (this.oidentdFile) {
|
if (this.oidentdFile) {
|
||||||
log.warn("Using both identd and oidentd at the same time, this is most likely not intended.");
|
log.warn(
|
||||||
|
"Using both identd and oidentd at the same time, this is most likely not intended."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const server = net.createServer(this.serverConnection.bind(this));
|
const server = net.createServer(this.serverConnection.bind(this));
|
||||||
|
|
||||||
server.on("error", (err) => log.error(`Identd server error: ${err}`));
|
server.on("error", (err) => log.error(`Identd server error: ${err}`));
|
||||||
|
|
||||||
server.listen({
|
server.listen(
|
||||||
port: Helper.config.identd.port || 113,
|
{
|
||||||
host: Helper.config.bind,
|
port: Helper.config.identd.port || 113,
|
||||||
}, () => {
|
host: Helper.config.bind,
|
||||||
const address = server.address();
|
},
|
||||||
log.info(`Identd server available on ${colors.green(address.address + ":" + address.port)}`);
|
() => {
|
||||||
|
const address = server.address();
|
||||||
|
log.info(
|
||||||
|
`Identd server available on ${colors.green(
|
||||||
|
address.address + ":" + address.port
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
|
||||||
startedCallback(this);
|
startedCallback(this);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
startedCallback(this);
|
startedCallback(this);
|
||||||
}
|
}
|
||||||
@ -61,7 +70,9 @@ class Identification {
|
|||||||
|
|
||||||
for (const connection of this.connections.values()) {
|
for (const connection of this.connections.values()) {
|
||||||
if (connection.socket.remotePort === fport && connection.socket.localPort === lport) {
|
if (connection.socket.remotePort === fport && connection.socket.localPort === lport) {
|
||||||
return socket.write(`${lport}, ${fport} : USERID : TheLounge : ${connection.user}\r\n`);
|
return socket.write(
|
||||||
|
`${lport}, ${fport} : USERID : TheLounge : ${connection.user}\r\n`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,9 +103,10 @@ class Identification {
|
|||||||
let file = "# Warning: file generated by The Lounge: changes will be overwritten!\n";
|
let file = "# Warning: file generated by The Lounge: changes will be overwritten!\n";
|
||||||
|
|
||||||
this.connections.forEach((connection) => {
|
this.connections.forEach((connection) => {
|
||||||
file += `fport ${connection.socket.remotePort}`
|
file +=
|
||||||
+ ` lport ${connection.socket.localPort}`
|
`fport ${connection.socket.remotePort}` +
|
||||||
+ ` { reply "${connection.user}" }\n`;
|
` lport ${connection.socket.localPort}` +
|
||||||
|
` { reply "${connection.user}" }\n`;
|
||||||
});
|
});
|
||||||
|
|
||||||
fs.writeFile(this.oidentdFile, file, {flag: "w+"}, function(err) {
|
fs.writeFile(this.oidentdFile, file, {flag: "w+"}, function(err) {
|
||||||
|
@ -4,7 +4,10 @@ const colors = require("chalk");
|
|||||||
const read = require("read");
|
const read = require("read");
|
||||||
|
|
||||||
function timestamp() {
|
function timestamp() {
|
||||||
const datetime = (new Date()).toISOString().split(".")[0].replace("T", " ");
|
const datetime = new Date()
|
||||||
|
.toISOString()
|
||||||
|
.split(".")[0]
|
||||||
|
.replace("T", " ");
|
||||||
|
|
||||||
return colors.dim(datetime);
|
return colors.dim(datetime);
|
||||||
}
|
}
|
||||||
|
@ -176,14 +176,13 @@ Chan.prototype.getFilteredClone = function(lastActiveChannel, lastMessage) {
|
|||||||
if (lastMessage > -1) {
|
if (lastMessage > -1) {
|
||||||
// When reconnecting, always send up to 100 messages to prevent message gaps on the client
|
// When reconnecting, always send up to 100 messages to prevent message gaps on the client
|
||||||
// See https://github.com/thelounge/thelounge/issues/1883
|
// See https://github.com/thelounge/thelounge/issues/1883
|
||||||
newChannel[prop] = this[prop]
|
newChannel[prop] = this[prop].filter((m) => m.id > lastMessage).slice(-100);
|
||||||
.filter((m) => m.id > lastMessage)
|
|
||||||
.slice(-100);
|
|
||||||
newChannel.moreHistoryAvailable = this[prop].length > 100;
|
newChannel.moreHistoryAvailable = this[prop].length > 100;
|
||||||
} else {
|
} else {
|
||||||
// If channel is active, send up to 100 last messages, for all others send just 1
|
// If channel is active, send up to 100 last messages, for all others send just 1
|
||||||
// Client will automatically load more messages whenever needed based on last seen messages
|
// Client will automatically load more messages whenever needed based on last seen messages
|
||||||
const messagesToSend = lastActiveChannel === true || this.id === lastActiveChannel ? 100 : 1;
|
const messagesToSend =
|
||||||
|
lastActiveChannel === true || this.id === lastActiveChannel ? 100 : 1;
|
||||||
|
|
||||||
newChannel[prop] = this[prop].slice(-messagesToSend);
|
newChannel[prop] = this[prop].slice(-messagesToSend);
|
||||||
newChannel.moreHistoryAvailable = this[prop].length > messagesToSend;
|
newChannel.moreHistoryAvailable = this[prop].length > messagesToSend;
|
||||||
|
@ -42,12 +42,14 @@ class Msg {
|
|||||||
return !!this.from.nick;
|
return !!this.from.nick;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.type !== Msg.Type.MOTD &&
|
return (
|
||||||
|
this.type !== Msg.Type.MOTD &&
|
||||||
this.type !== Msg.Type.ERROR &&
|
this.type !== Msg.Type.ERROR &&
|
||||||
this.type !== Msg.Type.TOPIC_SET_BY &&
|
this.type !== Msg.Type.TOPIC_SET_BY &&
|
||||||
this.type !== Msg.Type.MODE_CHANNEL &&
|
this.type !== Msg.Type.MODE_CHANNEL &&
|
||||||
this.type !== Msg.Type.RAW &&
|
this.type !== Msg.Type.RAW &&
|
||||||
this.type !== Msg.Type.WHOIS;
|
this.type !== Msg.Type.WHOIS
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,11 +89,20 @@ Network.prototype.validate = function(client) {
|
|||||||
|
|
||||||
if (Helper.config.lockNetwork) {
|
if (Helper.config.lockNetwork) {
|
||||||
// This check is needed to prevent invalid user configurations
|
// This check is needed to prevent invalid user configurations
|
||||||
if (!Helper.config.public && this.host && this.host.length > 0 && this.host !== Helper.config.defaults.host) {
|
if (
|
||||||
this.channels[0].pushMessage(client, new Msg({
|
!Helper.config.public &&
|
||||||
type: Msg.Type.ERROR,
|
this.host &&
|
||||||
text: "Hostname you specified is not allowed.",
|
this.host.length > 0 &&
|
||||||
}), true);
|
this.host !== Helper.config.defaults.host
|
||||||
|
) {
|
||||||
|
this.channels[0].pushMessage(
|
||||||
|
client,
|
||||||
|
new Msg({
|
||||||
|
type: Msg.Type.ERROR,
|
||||||
|
text: "Hostname you specified is not allowed.",
|
||||||
|
}),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -105,10 +114,14 @@ Network.prototype.validate = function(client) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.host.length === 0) {
|
if (this.host.length === 0) {
|
||||||
this.channels[0].pushMessage(client, new Msg({
|
this.channels[0].pushMessage(
|
||||||
type: Msg.Type.ERROR,
|
client,
|
||||||
text: "You must specify a hostname to connect.",
|
new Msg({
|
||||||
}), true);
|
type: Msg.Type.ERROR,
|
||||||
|
text: "You must specify a hostname to connect.",
|
||||||
|
}),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -149,7 +162,10 @@ Network.prototype.createIrcFramework = function(client) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Network.prototype.createWebIrc = function(client) {
|
Network.prototype.createWebIrc = function(client) {
|
||||||
if (!Helper.config.webirc || !Object.prototype.hasOwnProperty.call(Helper.config.webirc, this.host)) {
|
if (
|
||||||
|
!Helper.config.webirc ||
|
||||||
|
!Object.prototype.hasOwnProperty.call(Helper.config.webirc, this.host)
|
||||||
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,7 +229,11 @@ Network.prototype.edit = function(client, args) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connected && this.realname !== oldRealname && this.irc.network.cap.isEnabled("draft/setname")) {
|
if (
|
||||||
|
connected &&
|
||||||
|
this.realname !== oldRealname &&
|
||||||
|
this.irc.network.cap.isEnabled("draft/setname")
|
||||||
|
) {
|
||||||
this.irc.raw("SETNAME", this.realname);
|
this.irc.raw("SETNAME", this.realname);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,12 +261,10 @@ Network.prototype.setNick = function(nick) {
|
|||||||
this.highlightRegex = new RegExp(
|
this.highlightRegex = new RegExp(
|
||||||
// Do not match characters and numbers (unless IRC color)
|
// Do not match characters and numbers (unless IRC color)
|
||||||
"(?:^|[^a-z0-9]|\x03[0-9]{1,2})" +
|
"(?:^|[^a-z0-9]|\x03[0-9]{1,2})" +
|
||||||
|
// Escape nickname, as it may contain regex stuff
|
||||||
// Escape nickname, as it may contain regex stuff
|
_.escapeRegExp(nick) +
|
||||||
_.escapeRegExp(nick) +
|
// Do not match characters and numbers
|
||||||
|
"(?:[^a-z0-9]|$)",
|
||||||
// Do not match characters and numbers
|
|
||||||
"(?:[^a-z0-9]|$)",
|
|
||||||
|
|
||||||
// Case insensitive search
|
// Case insensitive search
|
||||||
"i"
|
"i"
|
||||||
@ -266,7 +284,9 @@ Network.prototype.getFilteredClone = function(lastActiveChannel, lastMessage) {
|
|||||||
const filteredNetwork = Object.keys(this).reduce((newNetwork, prop) => {
|
const filteredNetwork = Object.keys(this).reduce((newNetwork, prop) => {
|
||||||
if (prop === "channels") {
|
if (prop === "channels") {
|
||||||
// Channels objects perform their own cloning
|
// Channels objects perform their own cloning
|
||||||
newNetwork[prop] = this[prop].map((channel) => channel.getFilteredClone(lastActiveChannel, lastMessage));
|
newNetwork[prop] = this[prop].map((channel) =>
|
||||||
|
channel.getFilteredClone(lastActiveChannel, lastMessage)
|
||||||
|
);
|
||||||
} else if (!filteredFromClient[prop]) {
|
} else if (!filteredFromClient[prop]) {
|
||||||
// Some properties that are not useful for the client are skipped
|
// Some properties that are not useful for the client are skipped
|
||||||
newNetwork[prop] = this[prop];
|
newNetwork[prop] = this[prop];
|
||||||
@ -311,8 +331,10 @@ Network.prototype.addChannel = function(newChan) {
|
|||||||
const compareChan = this.channels[i];
|
const compareChan = this.channels[i];
|
||||||
|
|
||||||
// Negative if the new chan is alphabetically before the next chan in the list, positive if after
|
// Negative if the new chan is alphabetically before the next chan in the list, positive if after
|
||||||
if (newChan.name.localeCompare(compareChan.name, {sensitivity: "base"}) <= 0
|
if (
|
||||||
|| (compareChan.type !== Chan.Type.CHANNEL && compareChan.type !== Chan.Type.QUERY)) {
|
newChan.name.localeCompare(compareChan.name, {sensitivity: "base"}) <= 0 ||
|
||||||
|
(compareChan.type !== Chan.Type.CHANNEL && compareChan.type !== Chan.Type.QUERY)
|
||||||
|
) {
|
||||||
index = i;
|
index = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,9 @@ function advancedLdapAuth(user, password, callback) {
|
|||||||
res.on("searchEntry", function(entry) {
|
res.on("searchEntry", function(entry) {
|
||||||
found = true;
|
found = true;
|
||||||
const bindDN = entry.objectName;
|
const bindDN = entry.objectName;
|
||||||
log.info(`Auth against LDAP ${config.ldap.url} with found bindDN ${bindDN}`);
|
log.info(
|
||||||
|
`Auth against LDAP ${config.ldap.url} with found bindDN ${bindDN}`
|
||||||
|
);
|
||||||
ldapclient.unbind();
|
ldapclient.unbind();
|
||||||
|
|
||||||
ldapAuthCommon(user, bindDN, password, callback);
|
ldapAuthCommon(user, bindDN, password, callback);
|
||||||
@ -105,7 +107,9 @@ function advancedLdapAuth(user, password, callback) {
|
|||||||
ldapclient.unbind();
|
ldapclient.unbind();
|
||||||
|
|
||||||
if (!found) {
|
if (!found) {
|
||||||
log.warn(`LDAP Search did not find anything for: ${userDN} (${result.status})`);
|
log.warn(
|
||||||
|
`LDAP Search did not find anything for: ${userDN} (${result.status})`
|
||||||
|
);
|
||||||
callback(false);
|
callback(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -13,7 +13,11 @@ function localAuth(manager, client, user, password, callback) {
|
|||||||
|
|
||||||
// If this user has no password set, fail the authentication
|
// If this user has no password set, fail the authentication
|
||||||
if (!client.config.password) {
|
if (!client.config.password) {
|
||||||
log.error(`User ${colors.bold(user)} with no local password set tried to sign in. (Probably a LDAP user)`);
|
log.error(
|
||||||
|
`User ${colors.bold(
|
||||||
|
user
|
||||||
|
)} with no local password set tried to sign in. (Probably a LDAP user)`
|
||||||
|
);
|
||||||
return callback(false);
|
return callback(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,13 +29,18 @@ function localAuth(manager, client, user, password, callback) {
|
|||||||
|
|
||||||
client.setPassword(hash, (success) => {
|
client.setPassword(hash, (success) => {
|
||||||
if (success) {
|
if (success) {
|
||||||
log.info(`User ${colors.bold(client.name)} logged in and their hashed password has been updated to match new security requirements`);
|
log.info(
|
||||||
|
`User ${colors.bold(
|
||||||
|
client.name
|
||||||
|
)} logged in and their hashed password has been updated to match new security requirements`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
callback(matching);
|
callback(matching);
|
||||||
}).catch((error) => {
|
})
|
||||||
|
.catch((error) => {
|
||||||
log.error(`Error while checking users password. Error: ${error}`);
|
log.error(`Error while checking users password. Error: ${error}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -40,4 +49,3 @@ module.exports = {
|
|||||||
auth: localAuth,
|
auth: localAuth,
|
||||||
isEnabled: () => true,
|
isEnabled: () => true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ async function fetch() {
|
|||||||
try {
|
try {
|
||||||
const response = await got("https://api.github.com/repos/thelounge/thelounge/releases", {
|
const response = await got("https://api.github.com/repos/thelounge/thelounge/releases", {
|
||||||
headers: {
|
headers: {
|
||||||
"Accept": "application/vnd.github.v3.html", // Request rendered markdown
|
Accept: "application/vnd.github.v3.html", // Request rendered markdown
|
||||||
"User-Agent": pkg.name + "; +" + pkg.repository.git, // Identify the client
|
"User-Agent": pkg.name + "; +" + pkg.repository.git, // Identify the client
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -7,10 +7,13 @@ exports.commands = ["slap", "me"];
|
|||||||
|
|
||||||
exports.input = function({irc}, chan, cmd, args) {
|
exports.input = function({irc}, chan, cmd, args) {
|
||||||
if (chan.type !== Chan.Type.CHANNEL && chan.type !== Chan.Type.QUERY) {
|
if (chan.type !== Chan.Type.CHANNEL && chan.type !== Chan.Type.QUERY) {
|
||||||
chan.pushMessage(this, new Msg({
|
chan.pushMessage(
|
||||||
type: Msg.Type.ERROR,
|
this,
|
||||||
text: `${cmd} command can only be used in channels and queries.`,
|
new Msg({
|
||||||
}));
|
type: Msg.Type.ERROR,
|
||||||
|
text: `${cmd} command can only be used in channels and queries.`,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -18,27 +21,27 @@ exports.input = function({irc}, chan, cmd, args) {
|
|||||||
let text;
|
let text;
|
||||||
|
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case "slap":
|
case "slap":
|
||||||
text = "slaps " + args[0] + " around a bit with a large trout";
|
text = "slaps " + args[0] + " around a bit with a large trout";
|
||||||
/* fall through */
|
/* fall through */
|
||||||
case "me":
|
case "me":
|
||||||
if (args.length === 0) {
|
if (args.length === 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
text = text || args.join(" ");
|
||||||
|
|
||||||
|
irc.action(chan.name, text);
|
||||||
|
|
||||||
|
if (!irc.network.cap.isEnabled("echo-message")) {
|
||||||
|
irc.emit("action", {
|
||||||
|
nick: irc.user.nick,
|
||||||
|
target: chan.name,
|
||||||
|
message: text,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
text = text || args.join(" ");
|
|
||||||
|
|
||||||
irc.action(chan.name, text);
|
|
||||||
|
|
||||||
if (!irc.network.cap.isEnabled("echo-message")) {
|
|
||||||
irc.emit("action", {
|
|
||||||
nick: irc.user.nick,
|
|
||||||
target: chan.name,
|
|
||||||
message: text,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -9,7 +9,8 @@ exports.input = function(network, chan, cmd, args) {
|
|||||||
reason = args.join(" ") || " ";
|
reason = args.join(" ") || " ";
|
||||||
|
|
||||||
network.irc.raw("AWAY", reason);
|
network.irc.raw("AWAY", reason);
|
||||||
} else { // back command
|
} else {
|
||||||
|
// back command
|
||||||
network.irc.raw("AWAY");
|
network.irc.raw("AWAY");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,42 +3,44 @@
|
|||||||
const Chan = require("../../models/chan");
|
const Chan = require("../../models/chan");
|
||||||
const Msg = require("../../models/msg");
|
const Msg = require("../../models/msg");
|
||||||
|
|
||||||
exports.commands = [
|
exports.commands = ["ban", "unban", "banlist"];
|
||||||
"ban",
|
|
||||||
"unban",
|
|
||||||
"banlist",
|
|
||||||
];
|
|
||||||
|
|
||||||
exports.input = function({irc}, chan, cmd, args) {
|
exports.input = function({irc}, chan, cmd, args) {
|
||||||
if (chan.type !== Chan.Type.CHANNEL) {
|
if (chan.type !== Chan.Type.CHANNEL) {
|
||||||
chan.pushMessage(this, new Msg({
|
chan.pushMessage(
|
||||||
type: Msg.Type.ERROR,
|
this,
|
||||||
text: `${cmd} command can only be used in channels.`,
|
new Msg({
|
||||||
}));
|
type: Msg.Type.ERROR,
|
||||||
|
text: `${cmd} command can only be used in channels.`,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cmd !== "banlist" && args.length === 0) {
|
if (cmd !== "banlist" && args.length === 0) {
|
||||||
if (args.length === 0) {
|
if (args.length === 0) {
|
||||||
chan.pushMessage(this, new Msg({
|
chan.pushMessage(
|
||||||
type: Msg.Type.ERROR,
|
this,
|
||||||
text: `Usage: /${cmd} <nick>`,
|
new Msg({
|
||||||
}));
|
type: Msg.Type.ERROR,
|
||||||
|
text: `Usage: /${cmd} <nick>`,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case "ban":
|
case "ban":
|
||||||
irc.ban(chan.name, args[0]);
|
irc.ban(chan.name, args[0]);
|
||||||
break;
|
break;
|
||||||
case "unban":
|
case "unban":
|
||||||
irc.unban(chan.name, args[0]);
|
irc.unban(chan.name, args[0]);
|
||||||
break;
|
break;
|
||||||
case "banlist":
|
case "banlist":
|
||||||
irc.banlist(chan.name);
|
irc.banlist(chan.name);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -17,10 +17,13 @@ exports.input = function(network, chan, cmd, args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (irc.connection && irc.connection.connected) {
|
if (irc.connection && irc.connection.connected) {
|
||||||
chan.pushMessage(this, new Msg({
|
chan.pushMessage(
|
||||||
type: Msg.Type.ERROR,
|
this,
|
||||||
text: "You are already connected.",
|
new Msg({
|
||||||
}));
|
type: Msg.Type.ERROR,
|
||||||
|
text: "You are already connected.",
|
||||||
|
})
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,18 +6,24 @@ exports.commands = ["ctcp"];
|
|||||||
|
|
||||||
exports.input = function({irc}, chan, cmd, args) {
|
exports.input = function({irc}, chan, cmd, args) {
|
||||||
if (args.length < 2) {
|
if (args.length < 2) {
|
||||||
chan.pushMessage(this, new Msg({
|
chan.pushMessage(
|
||||||
type: Msg.Type.ERROR,
|
this,
|
||||||
text: "Usage: /ctcp <nick> <ctcp_type>",
|
new Msg({
|
||||||
}));
|
type: Msg.Type.ERROR,
|
||||||
|
text: "Usage: /ctcp <nick> <ctcp_type>",
|
||||||
|
})
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
chan.pushMessage(this, new Msg({
|
chan.pushMessage(
|
||||||
type: Msg.Type.CTCP_REQUEST,
|
this,
|
||||||
ctcpMessage: `"${args.slice(1).join(" ")}" to ${args[0]}`,
|
new Msg({
|
||||||
from: chan.getUser(irc.user.nick),
|
type: Msg.Type.CTCP_REQUEST,
|
||||||
}));
|
ctcpMessage: `"${args.slice(1).join(" ")}" to ${args[0]}`,
|
||||||
|
from: chan.getUser(irc.user.nick),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
irc.ctcpRequest(...args);
|
irc.ctcpRequest(...args);
|
||||||
};
|
};
|
||||||
|
@ -4,11 +4,7 @@ const Chan = require("../../models/chan");
|
|||||||
const Msg = require("../../models/msg");
|
const Msg = require("../../models/msg");
|
||||||
const Helper = require("../../helper");
|
const Helper = require("../../helper");
|
||||||
|
|
||||||
exports.commands = [
|
exports.commands = ["ignore", "unignore", "ignorelist"];
|
||||||
"ignore",
|
|
||||||
"unignore",
|
|
||||||
"ignorelist",
|
|
||||||
];
|
|
||||||
|
|
||||||
exports.input = function(network, chan, cmd, args) {
|
exports.input = function(network, chan, cmd, args) {
|
||||||
const client = this;
|
const client = this;
|
||||||
@ -16,10 +12,13 @@ exports.input = function(network, chan, cmd, args) {
|
|||||||
let hostmask;
|
let hostmask;
|
||||||
|
|
||||||
if (cmd !== "ignorelist" && (args.length === 0 || args[0].trim().length === 0)) {
|
if (cmd !== "ignorelist" && (args.length === 0 || args[0].trim().length === 0)) {
|
||||||
chan.pushMessage(client, new Msg({
|
chan.pushMessage(
|
||||||
type: Msg.Type.ERROR,
|
client,
|
||||||
text: `Usage: /${cmd} <nick>[!ident][@host]`,
|
new Msg({
|
||||||
}));
|
type: Msg.Type.ERROR,
|
||||||
|
text: `Usage: /${cmd} <nick>[!ident][@host]`,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -31,95 +30,115 @@ exports.input = function(network, chan, cmd, args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case "ignore": {
|
case "ignore": {
|
||||||
// IRC nicks are case insensitive
|
// IRC nicks are case insensitive
|
||||||
if (hostmask.nick.toLowerCase() === network.nick.toLowerCase()) {
|
if (hostmask.nick.toLowerCase() === network.nick.toLowerCase()) {
|
||||||
chan.pushMessage(client, new Msg({
|
chan.pushMessage(
|
||||||
type: Msg.Type.ERROR,
|
client,
|
||||||
text: "You can't ignore yourself",
|
new Msg({
|
||||||
}));
|
type: Msg.Type.ERROR,
|
||||||
} else if (!network.ignoreList.some(function(entry) {
|
text: "You can't ignore yourself",
|
||||||
return Helper.compareHostmask(entry, hostmask);
|
})
|
||||||
})) {
|
);
|
||||||
hostmask.when = Date.now();
|
} else if (
|
||||||
network.ignoreList.push(hostmask);
|
!network.ignoreList.some(function(entry) {
|
||||||
|
return Helper.compareHostmask(entry, hostmask);
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
hostmask.when = Date.now();
|
||||||
|
network.ignoreList.push(hostmask);
|
||||||
|
|
||||||
client.save();
|
client.save();
|
||||||
chan.pushMessage(client, new Msg({
|
chan.pushMessage(
|
||||||
type: Msg.Type.ERROR,
|
client,
|
||||||
text: `\u0002${hostmask.nick}!${hostmask.ident}@${hostmask.hostname}\u000f added to ignorelist`,
|
new Msg({
|
||||||
}));
|
type: Msg.Type.ERROR,
|
||||||
} else {
|
text: `\u0002${hostmask.nick}!${hostmask.ident}@${hostmask.hostname}\u000f added to ignorelist`,
|
||||||
chan.pushMessage(client, new Msg({
|
})
|
||||||
type: Msg.Type.ERROR,
|
);
|
||||||
text: "The specified user/hostmask is already ignored",
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "unignore": {
|
|
||||||
const idx = network.ignoreList.findIndex(function(entry) {
|
|
||||||
return Helper.compareHostmask(entry, hostmask);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check if the entry exists before removing it, otherwise
|
|
||||||
// let the user know.
|
|
||||||
if (idx !== -1) {
|
|
||||||
network.ignoreList.splice(idx, 1);
|
|
||||||
client.save();
|
|
||||||
|
|
||||||
chan.pushMessage(client, new Msg({
|
|
||||||
type: Msg.Type.ERROR,
|
|
||||||
text: `Successfully removed \u0002${hostmask.nick}!${hostmask.ident}@${hostmask.hostname}\u000f from ignorelist`,
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
chan.pushMessage(client, new Msg({
|
|
||||||
type: Msg.Type.ERROR,
|
|
||||||
text: "The specified user/hostmask is not ignored",
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "ignorelist":
|
|
||||||
if (network.ignoreList.length === 0) {
|
|
||||||
chan.pushMessage(client, new Msg({
|
|
||||||
type: Msg.Type.ERROR,
|
|
||||||
text: "Ignorelist is empty",
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
const chanName = "Ignored users";
|
|
||||||
const ignored = network.ignoreList.map((data) => ({
|
|
||||||
hostmask: `${data.nick}!${data.ident}@${data.hostname}`,
|
|
||||||
when: data.when,
|
|
||||||
}));
|
|
||||||
let newChan = network.getChannel(chanName);
|
|
||||||
|
|
||||||
if (typeof newChan === "undefined") {
|
|
||||||
newChan = client.createChannel({
|
|
||||||
type: Chan.Type.SPECIAL,
|
|
||||||
special: Chan.SpecialType.IGNORELIST,
|
|
||||||
name: chanName,
|
|
||||||
data: ignored,
|
|
||||||
});
|
|
||||||
client.emit("join", {
|
|
||||||
network: network.uuid,
|
|
||||||
chan: newChan.getFilteredClone(true),
|
|
||||||
index: network.addChannel(newChan),
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
newChan.data = ignored;
|
chan.pushMessage(
|
||||||
|
client,
|
||||||
client.emit("msg:special", {
|
new Msg({
|
||||||
chan: newChan.id,
|
type: Msg.Type.ERROR,
|
||||||
data: ignored,
|
text: "The specified user/hostmask is already ignored",
|
||||||
});
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
case "unignore": {
|
||||||
|
const idx = network.ignoreList.findIndex(function(entry) {
|
||||||
|
return Helper.compareHostmask(entry, hostmask);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if the entry exists before removing it, otherwise
|
||||||
|
// let the user know.
|
||||||
|
if (idx !== -1) {
|
||||||
|
network.ignoreList.splice(idx, 1);
|
||||||
|
client.save();
|
||||||
|
|
||||||
|
chan.pushMessage(
|
||||||
|
client,
|
||||||
|
new Msg({
|
||||||
|
type: Msg.Type.ERROR,
|
||||||
|
text: `Successfully removed \u0002${hostmask.nick}!${hostmask.ident}@${hostmask.hostname}\u000f from ignorelist`,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
chan.pushMessage(
|
||||||
|
client,
|
||||||
|
new Msg({
|
||||||
|
type: Msg.Type.ERROR,
|
||||||
|
text: "The specified user/hostmask is not ignored",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "ignorelist":
|
||||||
|
if (network.ignoreList.length === 0) {
|
||||||
|
chan.pushMessage(
|
||||||
|
client,
|
||||||
|
new Msg({
|
||||||
|
type: Msg.Type.ERROR,
|
||||||
|
text: "Ignorelist is empty",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const chanName = "Ignored users";
|
||||||
|
const ignored = network.ignoreList.map((data) => ({
|
||||||
|
hostmask: `${data.nick}!${data.ident}@${data.hostname}`,
|
||||||
|
when: data.when,
|
||||||
|
}));
|
||||||
|
let newChan = network.getChannel(chanName);
|
||||||
|
|
||||||
|
if (typeof newChan === "undefined") {
|
||||||
|
newChan = client.createChannel({
|
||||||
|
type: Chan.Type.SPECIAL,
|
||||||
|
special: Chan.SpecialType.IGNORELIST,
|
||||||
|
name: chanName,
|
||||||
|
data: ignored,
|
||||||
|
});
|
||||||
|
client.emit("join", {
|
||||||
|
network: network.uuid,
|
||||||
|
chan: newChan.getFilteredClone(true),
|
||||||
|
index: network.addChannel(newChan),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
newChan.data = ignored;
|
||||||
|
|
||||||
|
client.emit("msg:special", {
|
||||||
|
chan: newChan.id,
|
||||||
|
data: ignored,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -3,10 +3,7 @@
|
|||||||
const Chan = require("../../models/chan");
|
const Chan = require("../../models/chan");
|
||||||
const Msg = require("../../models/msg");
|
const Msg = require("../../models/msg");
|
||||||
|
|
||||||
exports.commands = [
|
exports.commands = ["invite", "invitelist"];
|
||||||
"invite",
|
|
||||||
"invitelist",
|
|
||||||
];
|
|
||||||
|
|
||||||
exports.input = function({irc}, chan, cmd, args) {
|
exports.input = function({irc}, chan, cmd, args) {
|
||||||
if (cmd === "invitelist") {
|
if (cmd === "invitelist") {
|
||||||
@ -16,12 +13,15 @@ exports.input = function({irc}, chan, cmd, args) {
|
|||||||
|
|
||||||
if (args.length === 2) {
|
if (args.length === 2) {
|
||||||
irc.raw("INVITE", args[0], args[1]); // Channel provided in the command
|
irc.raw("INVITE", args[0], args[1]); // Channel provided in the command
|
||||||
} else if (args.length === 1 && chan.type === Chan.Type.CHANNEL) {
|
} else if (args.length === 1 && chan.type === Chan.Type.CHANNEL) {
|
||||||
irc.raw("INVITE", args[0], chan.name); // Current channel
|
irc.raw("INVITE", args[0], chan.name); // Current channel
|
||||||
} else {
|
} else {
|
||||||
chan.pushMessage(this, new Msg({
|
chan.pushMessage(
|
||||||
type: Msg.Type.ERROR,
|
this,
|
||||||
text: `${cmd} command can only be used in channels or by specifying a target.`,
|
new Msg({
|
||||||
}));
|
type: Msg.Type.ERROR,
|
||||||
|
text: `${cmd} command can only be used in channels or by specifying a target.`,
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -7,10 +7,13 @@ exports.commands = ["kick"];
|
|||||||
|
|
||||||
exports.input = function({irc}, chan, cmd, args) {
|
exports.input = function({irc}, chan, cmd, args) {
|
||||||
if (chan.type !== Chan.Type.CHANNEL) {
|
if (chan.type !== Chan.Type.CHANNEL) {
|
||||||
chan.pushMessage(this, new Msg({
|
chan.pushMessage(
|
||||||
type: Msg.Type.ERROR,
|
this,
|
||||||
text: `${cmd} command can only be used in channels.`,
|
new Msg({
|
||||||
}));
|
type: Msg.Type.ERROR,
|
||||||
|
text: `${cmd} command can only be used in channels.`,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -3,32 +3,30 @@
|
|||||||
const Chan = require("../../models/chan");
|
const Chan = require("../../models/chan");
|
||||||
const Msg = require("../../models/msg");
|
const Msg = require("../../models/msg");
|
||||||
|
|
||||||
exports.commands = [
|
exports.commands = ["mode", "op", "deop", "hop", "dehop", "voice", "devoice"];
|
||||||
"mode",
|
|
||||||
"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 !== "mode") {
|
||||||
if (chan.type !== Chan.Type.CHANNEL) {
|
if (chan.type !== Chan.Type.CHANNEL) {
|
||||||
chan.pushMessage(this, new Msg({
|
chan.pushMessage(
|
||||||
type: Msg.Type.ERROR,
|
this,
|
||||||
text: `${cmd} command can only be used in channels.`,
|
new Msg({
|
||||||
}));
|
type: Msg.Type.ERROR,
|
||||||
|
text: `${cmd} command can only be used in channels.`,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.length === 0) {
|
if (args.length === 0) {
|
||||||
chan.pushMessage(this, new Msg({
|
chan.pushMessage(
|
||||||
type: Msg.Type.ERROR,
|
this,
|
||||||
text: `Usage: /${cmd} <nick> [...nick]`,
|
new Msg({
|
||||||
}));
|
type: Msg.Type.ERROR,
|
||||||
|
text: `Usage: /${cmd} <nick> [...nick]`,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -50,7 +48,9 @@ exports.input = function({irc, nick}, chan, cmd, args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (args.length === 0 || args[0][0] === "+" || args[0][0] === "-") {
|
if (args.length === 0 || args[0][0] === "+" || args[0][0] === "-") {
|
||||||
args.unshift(chan.type === Chan.Type.CHANNEL || chan.type === Chan.Type.QUERY ? chan.name : nick);
|
args.unshift(
|
||||||
|
chan.type === Chan.Type.CHANNEL || chan.type === Chan.Type.QUERY ? chan.name : nick
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
irc.raw("MODE", ...args);
|
irc.raw("MODE", ...args);
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user