Convert message actions to Vue

This commit is contained in:
Pavel Djundik 2018-07-09 13:44:12 +03:00 committed by Pavel Djundik
parent dee76adc0c
commit 0730825185
41 changed files with 498 additions and 267 deletions

View file

@ -5,6 +5,7 @@
<div
id="chat"
ref="chat"
:data-id="channel.id"
:class="{
'hide-motd': !settings.motd,
'hide-status-messages': settings.statusMessages === 'hidden',

View file

@ -8,7 +8,6 @@
<span
:aria-label="message.time | localetime"
class="time tooltipped tooltipped-e">{{ message.time | tz }}</span>
<template v-if="message.type === 'unhandled'">
<span class="from">[{{ message.command }}]</span>
<span class="content">
@ -17,7 +16,13 @@
:key="id">{{ param }}</span>
</span>
</template>
<template v-elseif="message.type === 'message'">
<template v-else-if="isAction()">
<span class="from"/>
<component
:is="messageComponent"
:message="message"/>
</template>
<template v-else>
<span class="from">
<template v-if="message.from && message.from.nick">
<Username :user="message.from"/>
@ -40,14 +45,25 @@
<script>
import Username from "./Username.vue";
import MessageTypes from "./MessageTypes";
MessageTypes.Username = Username;
export default {
name: "Message",
components: {
Username,
},
components: MessageTypes,
props: {
message: Object,
},
computed: {
messageComponent() {
return "message-" + this.message.type;
},
},
methods: {
isAction() {
return typeof MessageTypes["message-" + this.message.type] !== "undefined";
},
},
};
</script>

View file

@ -0,0 +1,22 @@
<template v-elseif="message.type === 'message'">
<span class="content">
<Username :user="message.from"/>
<span
class="text"
v-html="$options.filters.parse(message.text, message.users)"/>
</span>
</template>
<script>
import Username from "../Username.vue";
export default {
name: "MessageTypeAction",
components: {
Username,
},
props: {
message: Object,
},
};
</script>

View file

@ -0,0 +1,28 @@
<template v-elseif="message.type === 'message'">
<span class="content">
<template v-if="message.self">
<i v-html="$options.filters.parse(message.text)"/>
</template>
<template v-else>
<Username :user="message.from"/>
is away
<i
class="away-message"
v-html="'(' + $options.filters.parse(message.text) + ')'"/>
</template>
</span>
</template>
<script>
import Username from "../Username.vue";
export default {
name: "MessageTypeAway",
components: {
Username,
},
props: {
message: Object,
},
};
</script>

View file

@ -0,0 +1,25 @@
<template v-elseif="message.type === 'message'">
<span class="content">
<template v-if="message.self">
<i v-html="$options.filters.parse(message.text)"/>
</template>
<template v-else>
<Username :user="message.from"/>
is back
</template>
</span>
</template>
<script>
import Username from "../Username.vue";
export default {
name: "MessageTypeBack",
components: {
Username,
},
props: {
message: Object,
},
};
</script>

View file

@ -0,0 +1,22 @@
<template v-elseif="message.type === 'message'">
<span class="content">
<Username :user="message.from"/>
has changed
<span v-if="message.new_ident">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>
</template>
<script>
import Username from "../Username.vue";
export default {
name: "MessageTypeChangeHost",
components: {
Username,
},
props: {
message: Object,
},
};
</script>

View file

@ -0,0 +1,22 @@
<template v-elseif="message.type === 'message'">
<span class="content">
<Username :user="message.from"/>
<span
class="ctcp-message"
v-html="$options.filters.parse(message.ctcpMessage)"/>
</span>
</template>
<script>
import Username from "../Username.vue";
export default {
name: "MessageTypeCTCP",
components: {
Username,
},
props: {
message: Object,
},
};
</script>

View file

@ -0,0 +1,23 @@
<template v-elseif="message.type === 'message'">
<span class="content">
<Username :user="message.from"/>
sent a <abbr title="Client-to-client protocol">CTCP</abbr> request:
<span
class="ctcp-message"
v-html="$options.filters.parse(message.ctcpMessage)"/>
</span>
</template>
<script>
import Username from "../Username.vue";
export default {
name: "MessageTypeRequestCTCP",
components: {
Username,
},
props: {
message: Object,
},
};
</script>

View file

@ -0,0 +1,13 @@
"use strict";
// This creates a version of `require()` in the context of the current
// directory, so we iterate over its content, which is a map statically built by
// Webpack.
// Second argument says it's recursive, third makes sure we only load templates.
const requireViews = require.context(".", false, /\.vue$/);
module.exports = requireViews.keys().reduce((acc, path) => {
acc["message-" + path.substring(2, path.length - 4)] = requireViews(path).default;
return acc;
}, {});

View file

@ -0,0 +1,25 @@
<template v-elseif="message.type === 'message'">
<span class="content">
<Username :user="message.from"/>
invited
<span v-if="message.invitedYou">you</span>
<Username
v-else
:user="message.target"/>
to <span v-html="$options.filters.parse(message.channel)"/>
</span>
</template>
<script>
import Username from "../Username.vue";
export default {
name: "MessageTypeInvite",
components: {
Username,
},
props: {
message: Object,
},
};
</script>

View file

@ -0,0 +1,21 @@
<template v-elseif="message.type === 'message'">
<span class="content">
<Username :user="message.from"/>
<i class="hostmask">({{ message.hostmask }})</i>
has joined the channel
</span>
</template>
<script>
import Username from "../Username.vue";
export default {
name: "MessageTypeJoin",
components: {
Username,
},
props: {
message: Object,
},
};
</script>

View file

@ -0,0 +1,25 @@
<template v-elseif="message.type === 'message'">
<span class="content">
<Username :user="message.from"/>
has kicked
<Username :user="message.target"/>
<i
v-if="message.text"
class="part-reason"
v-html="'(' + $options.filters.parse(message.text) + ')'"/>
</span>
</template>
<script>
import Username from "../Username.vue";
export default {
name: "MessageTypeKick",
components: {
Username,
},
props: {
message: Object,
},
};
</script>

View file

@ -0,0 +1,21 @@
<template v-elseif="message.type === 'message'">
<span class="content">
<Username :user="message.from"/>
sets mode
<span v-html="$options.filters.parse(message.text)"/>
</span>
</template>
<script>
import Username from "../Username.vue";
export default {
name: "MessageTypeMode",
components: {
Username,
},
props: {
message: Object,
},
};
</script>

View file

@ -0,0 +1,21 @@
<template v-elseif="message.type === 'message'">
<span class="content">
<Username :user="message.from"/>
is now known as
<Username :user="{nick: message.new_nick, mode: message.from.mode}"/>
</span>
</template>
<script>
import Username from "../Username.vue";
export default {
name: "MessageTypeNick",
components: {
Username,
},
props: {
message: Object,
},
};
</script>

View file

@ -0,0 +1,25 @@
<template v-elseif="message.type === 'message'">
<span class="content">
<Username :user="message.from"/>
<i class="hostmask">({{ message.hostmask }})</i>
has left the channel
<i
v-if="message.text"
class="part-reason"
v-html="'(' + $options.filters.parse(message.text) + ')'"/>
</span>
</template>
<script>
import Username from "../Username.vue";
export default {
name: "MessageTypePart",
components: {
Username,
},
props: {
message: Object,
},
};
</script>

View file

@ -0,0 +1,25 @@
<template v-elseif="message.type === 'message'">
<span class="content">
<Username :user="message.from"/>
<i class="hostmask">({{ message.hostmask }})</i>
has quit
<i
v-if="message.text"
class="quit-reason"
v-html="'(' + $options.filters.parse(message.text) + ')'"/>
</span>
</template>
<script>
import Username from "../Username.vue";
export default {
name: "MessageTypeQuit",
components: {
Username,
},
props: {
message: Object,
},
};
</script>

View file

@ -0,0 +1,29 @@
<template v-elseif="message.type === 'message'">
<span class="content">
<template v-if="message.from && message.from.nick">
<Username :user="message.from"/>
has changed the topic to:
</template>
<template v-else>
The topic is:
</template>
<span
v-if="message.text"
class="new-topic"
v-html="$options.filters.parse(message.text)"/>
</span>
</template>
<script>
import Username from "../Username.vue";
export default {
name: "MessageTypeTopic",
components: {
Username,
},
props: {
message: Object,
},
};
</script>

View file

@ -0,0 +1,21 @@
<template v-elseif="message.type === 'message'">
<span class="content">
Topic set by
<Username :user="message.from"/>
on {{ message.when | localetime }}
</span>
</template>
<script>
import Username from "../Username.vue";
export default {
name: "MessageTypeTopicSetBy",
components: {
Username,
},
props: {
message: Object,
},
};
</script>

View file

@ -0,0 +1,108 @@
<template v-elseif="message.type === 'message'">
<span class="content">
<p>
<Username :user="{nick: message.whois.nick}"/>
<span v-if="message.whois.whowas"> is offline, last information:</span>
</p>
<dl class="whois">
<template v-if="message.whois.account">
<dt>Logged in as:</dt>
<dd>{{ message.whois.account }}</dd>
</template>
<dt>Host mask:</dt>
<dd class="hostmask">{{ message.whois.ident }}@{{ message.whois.hostname }}</dd>
<template v-if="message.whois.actual_hostname">
<dt>Actual host:</dt>
<dd class="hostmask">
<a
:href="'https://ipinfo.io/' + message.whois.actual_ip"
target="_blank"
rel="noopener">{{ message.whois.actual_ip }}</a>
<i v-if="message.whois.actual_hostname != message.whois.actual_ip"> ({{ message.whois.actual_hostname }})</i>
</dd>
</template>
<template v-if="message.whois.real_name">
<dt>Real name:</dt>
<dd v-html="$options.filters.parse(message.whois.real_name)"/>
</template>
<template v-if="message.whois.registered_nick">
<dt>Registered nick:</dt>
<dd>{{ message.whois.registered_nick }}</dd>
</template>
<template v-if="message.whois.channels">
<dt>Channels:</dt>
<dd v-html="$options.filters.parse(message.whois.channels)"/>
</template>
<template v-if="message.whois.modes">
<dt>Modes:</dt>
<dd>{{ message.whois.modes }}</dd>
</template>
<template v-if="message.whois.special">
<dt>Special:</dt>
<dd>{{ message.whois.special }}</dd>
</template>
<template v-if="message.whois.operator">
<dt>Operator:</dt>
<dd>{{ message.whois.operator }}</dd>
</template>
<template v-if="message.whois.helpop">
<dt>Available for help:</dt>
<dd>Yes</dd>
</template>
<template v-if="message.whois.bot">
<dt>Is a bot:</dt>
<dd>Yes</dd>
</template>
<template v-if="message.whois.away">
<dt>Away:</dt>
<dd v-html="$options.filters.parse(message.whois.away)"/>
</template>
<template v-if="message.whois.secure">
<dt>Secure connection:</dt>
<dd>Yes</dd>
</template>
<template v-if="message.whois.server">
<dt>Connected to:</dt>
<dd>{{ message.whois.server }} <i>({{ message.whois.server_info }})</i></dd>
</template>
<template v-if="message.whois.logonTime">
<dt>Connected at:</dt>
<dd>{{ message.whois.logonTime | localetime }}</dd>
</template>
<template v-if="message.whois.idle">
<dt>Idle since:</dt>
<dd>{{ message.whois.idleTime | localetime }}</dd>
</template>
</dl>
</span>
</template>
<script>
import Username from "../Username.vue";
export default {
name: "MessageTypeWhois",
components: {
Username,
},
props: {
message: Object,
},
};
</script>

View file

@ -1,18 +0,0 @@
"use strict";
module.exports = function(a, b, opt) {
if (arguments.length !== 3) {
throw new Error("Handlebars helper `notEqual` expects 3 arguments");
}
a = a.toString();
b = b.toString();
if (a !== b) {
return opt.fn(this);
}
if (opt.inverse(this) !== "") {
throw new Error("Handlebars helper `notEqual` does not take an `else` block");
}
};

View file

@ -1,6 +0,0 @@
{{> ../user_name from}}
<span class="text">{{{parse text users}}}</span>
{{#each previews}}
<div class="preview" data-url="{{link}}"></div>
{{/each}}

View file

@ -1,7 +0,0 @@
{{#if self}}
{{{parse text}}}
{{else}}
{{> ../user_name from}}
is away
<i class="away-message">({{{parse text}}})</i>
{{/if}}

View file

@ -1,6 +0,0 @@
{{#if self}}
{{{parse text}}}
{{else}}
{{> ../user_name from}}
is back
{{/if}}

View file

@ -1,4 +0,0 @@
{{> ../user_name nick=from.nick mode=from.mode}}
has changed
{{#if new_ident}}username to <b>{{new_ident}}</b>{{#if new_host}}, and{{/if}}{{/if}}
{{#if new_host}}hostname to <i class="hostmask">{{new_host}}</i>{{/if}}

View file

@ -1,2 +0,0 @@
{{> ../user_name from}}
<span class="ctcp-message">{{{parse ctcpMessage}}}</span>

View file

@ -1,3 +0,0 @@
{{> ../user_name from}}
sent a <abbr title="Client-to-client protocol">CTCP</abbr> request:
<span class="ctcp-message">{{{parse ctcpMessage}}}</span>

View file

@ -1,9 +0,0 @@
{{> ../user_name from}}
invited
{{#if invitedYou}}
you
{{else}}
{{> ../user_name target}}
{{/if}}
to
{{{parse channel}}}

View file

@ -1,3 +0,0 @@
{{> ../user_name from}}
<i class="hostmask">({{hostmask}})</i>
has joined the channel

View file

@ -1,6 +0,0 @@
{{> ../user_name from}}
has kicked
{{> ../user_name target}}
{{#if text}}
<i class="part-reason">({{{parse text}}})</i>
{{/if}}

View file

@ -1,3 +0,0 @@
{{> ../user_name from}}
sets mode
{{{parse text}}}

View file

@ -1,3 +0,0 @@
{{> ../user_name from}}
is now known as
{{> ../user_name nick=new_nick mode=from.mode}}

View file

@ -1,6 +0,0 @@
{{> ../user_name from}}
<i class="hostmask">({{hostmask}})</i>
has left the channel
{{#if text}}
<i class="part-reason">({{{parse text}}})</i>
{{/if}}

View file

@ -1,6 +0,0 @@
{{> ../user_name from}}
<i class="hostmask">({{hostmask}})</i>
has quit
{{#if text}}
<i class="quit-reason">({{{parse text}}})</i>
{{/if}}

View file

@ -1,8 +0,0 @@
{{#if from.nick}}
{{> ../user_name from}}
has changed the topic to:
{{else}}
The topic is:
{{/if}}
<span class="new-topic">{{{parse text}}}</span>

View file

@ -1,3 +0,0 @@
Topic set by
{{> ../user_name from}}
on {{localetime when}}

View file

@ -1,86 +0,0 @@
<p>
{{> ../user_name nick=whois.nick}}
{{#if whois.whowas}} is offline, last information:{{/if}}
</p>
<dl class="whois">
{{#if whois.account}}
<dt>Logged in as:</dt>
<dd>{{whois.account}}</dd>
{{/if}}
<dt>Host mask:</dt>
<dd class="hostmask">{{whois.ident}}@{{whois.hostname}}</dd>
{{#if whois.actual_hostname}}
<dt>Actual host:</dt>
<dd class="hostmask"><a href="https://ipinfo.io/{{whois.actual_ip}}" target="_blank" rel="noopener">{{whois.actual_ip}}</a>{{#notEqual whois.actual_hostname whois.actual_ip}} ({{whois.actual_hostname}}){{/notEqual}}</dd>
{{/if}}
{{#if whois.real_name}}
<dt>Real name:</dt>
<dd>{{{parse whois.real_name}}}</dd>
{{/if}}
{{#if whois.registered_nick}}
<dt>Registered nick:</dt>
<dd>{{whois.registered_nick}}</dd>
{{/if}}
{{#if whois.channels}}
<dt>Channels:</dt>
<dd>{{{parse whois.channels}}}</dd>
{{/if}}
{{#if whois.modes}}
<dt>Modes:</dt>
<dd>{{whois.modes}}</dd>
{{/if}}
{{#if whois.special}}
{{#each whois.special}}
<dt>Special:</dt>
<dd>{{this}}</dd>
{{/each}}
{{/if}}
{{#if whois.operator}}
<dt>Operator:</dt>
<dd>{{whois.operator}}</dd>
{{/if}}
{{#if whois.helpop}}
<dt>Available for help:</dt>
<dd>Yes</dd>
{{/if}}
{{#if whois.bot}}
<dt>Is a bot:</dt>
<dd>Yes</dd>
{{/if}}
{{#if whois.away}}
<dt>Away:</dt>
<dd>{{{parse whois.away}}}</dd>
{{/if}}
{{#if whois.secure}}
<dt>Secure connection:</dt>
<dd>Yes</dd>
{{/if}}
{{#if whois.server}}
<dt>Connected to:</dt>
<dd>{{whois.server}} <i>({{whois.server_info}})</i></dd>
{{/if}}
{{#if whois.logonTime}}
<dt>Connected at:</dt>
<dd>{{localetime whois.logonTime}}</dd>
{{/if}}
{{#if whois.idle}}
<dt>Idle since:</dt>
<dd>{{localetime whois.idleTime}}</dd>
{{/if}}
</dl>

View file

@ -1,8 +1 @@
<div class="msg {{type}}{{#if self}} self{{/if}}{{#if highlight}} highlight{{/if}}"
data-type="{{type}}" id="msg-{{id}}" data-time="{{time}}"{{#if from.nick}} data-from="{{from.nick}}"{{/if}}>
<span class="time tooltipped tooltipped-e" aria-label="{{localetime time}}">
{{tz time}}
</span>
<span class="from"></span>
<span class="content"></span>
</div>

View file

@ -1,23 +0,0 @@
"use strict";
const expect = require("chai").expect;
const modes = require("../../../../../client/js/libs/handlebars/modes");
describe("modes Handlebars helper", function() {
it("should return text modes based on symbols", function() {
expect(modes("~")).to.equal("owner");
expect(modes("&")).to.equal("admin");
expect(modes("!")).to.equal("admin");
expect(modes("@")).to.equal("op");
expect(modes("%")).to.equal("half-op");
expect(modes("+")).to.equal("voice");
});
it("should return no special mode when given an empty string", function() {
expect(modes("")).to.equal("normal");
});
it("should return nothing if the symbol does not exist", function() {
expect(modes("?")).to.be.undefined;
});
});

View file

@ -1,30 +0,0 @@
"use strict";
const expect = require("chai").expect;
const notEqual = require("../../../../../client/js/libs/handlebars/notEqual");
describe("notEqual Handlebars helper", function() {
const block = {
fn: () => "fn",
};
it("should render the block if both values are equal", function() {
expect(notEqual("foo", "bar", block)).to.equal("fn");
});
it("should throw if too few or too many arguments are given", function() {
expect(() => notEqual("foo", block)).to.throw(Error, /expects 3 arguments/);
expect(() => notEqual("foo", "bar", "baz", block))
.to.throw(Error, /expects 3 arguments/);
});
it("should throw if too few or too many arguments are given", function() {
const blockWithElse = {
fn: () => "fn",
inverse: () => "inverse",
};
expect(() => notEqual("foo", "foo", blockWithElse)).to.throw(Error, /else/);
});
});

View file

@ -1,10 +0,0 @@
"use strict";
const expect = require("chai").expect;
const slugify = require("../../../../../client/js/libs/handlebars/slugify");
describe("slugify Handlebars helper", function() {
it("should only produce lowercase strings", function() {
expect(slugify("#TheLounge")).to.equal("\\#thelounge");
});
});

View file

@ -1,13 +0,0 @@
"use strict";
const expect = require("chai").expect;
const tojson = require("../../../../../client/js/libs/handlebars/tojson");
describe("tojson Handlebars helper", function() {
it("should return JSON strings", function() {
expect(tojson([])).to.equal("[]");
expect(tojson({})).to.equal("{}");
expect(tojson("")).to.equal('""');
expect(tojson({foo: "bar"})).to.be.equal('{"foo":"bar"}');
});
});