Compare commits
15 Commits
8c8e06b015
...
0bc2c12eec
Author | SHA1 | Date | |
---|---|---|---|
0bc2c12eec | |||
3330683ad6 | |||
0015d26dc8 | |||
7d5e63be1f | |||
e7d0f2380e | |||
36529a46fd | |||
632ac992ab | |||
d29e2cbfe8 | |||
240fac4d90 | |||
c06894b291 | |||
9eaf78f823 | |||
5f02dddd11 | |||
6f4a3f4657 | |||
6387145adc | |||
f3cc137342 |
@ -110,11 +110,11 @@ function codegen_enum(name, cg, ctype) {
|
||||
|
||||
# XXX: This should also check if it isn't out-of-range for any reason,
|
||||
# but our usage of sprintf() stands in the way a bit.
|
||||
CodegenSerialize[name] = "\tstr_pack_i32(w, %s);\n"
|
||||
CodegenSerialize[name] = "\tstr_pack_i8(w, %s);\n"
|
||||
CodegenDeserialize[name] = \
|
||||
"\t{\n" \
|
||||
"\t\tint32_t v = 0;\n" \
|
||||
"\t\tif (!msg_unpacker_i32(r, &v) || !v)\n" \
|
||||
"\t\tint8_t v = 0;\n" \
|
||||
"\t\tif (!msg_unpacker_i8(r, &v) || !v)\n" \
|
||||
"\t\t\treturn false;\n" \
|
||||
"\t\t%s = v;\n" \
|
||||
"\t}\n"
|
||||
|
@ -161,7 +161,7 @@ function codegen_begin() {
|
||||
print "\tvar n int64"
|
||||
print "\tif err := json.Unmarshal(data, &n); err != nil {"
|
||||
print "\t\treturn 0, err"
|
||||
print "\t} else if n > math.MaxInt32 || n < math.MinInt32 {"
|
||||
print "\t} else if n > math.MaxInt8 || n < math.MinInt8 {"
|
||||
print "\t\treturn 0, errors.New(`integer out of range`)"
|
||||
print "\t} else {"
|
||||
print "\t\treturn n, nil"
|
||||
@ -191,7 +191,7 @@ function codegen_enum_value(name, subname, value, cg, goname) {
|
||||
|
||||
function codegen_enum(name, cg, gotype, fields) {
|
||||
gotype = PrefixCamel name
|
||||
print "type " gotype " int"
|
||||
print "type " gotype " int8"
|
||||
print ""
|
||||
|
||||
print "const ("
|
||||
@ -239,12 +239,10 @@ function codegen_enum(name, cg, gotype, fields) {
|
||||
|
||||
# XXX: This should also check if it isn't out-of-range for any reason,
|
||||
# but our usage of sprintf() stands in the way a bit.
|
||||
CodegenSerialize[name] = \
|
||||
"\tdata = binary.BigEndian.AppendUint32(data, uint32(%s))\n"
|
||||
CodegenSerialize[name] = "\tdata = append(data, uint8(%s))\n"
|
||||
CodegenDeserialize[name] = \
|
||||
"\tif len(data) >= 4 {\n" \
|
||||
"\t\t%s = " gotype "(int32(binary.BigEndian.Uint32(data)))\n" \
|
||||
"\t\tdata = data[4:]\n" \
|
||||
"\tif len(data) >= 1 {\n" \
|
||||
"\t\t%s, data = " gotype "(data[0]), data[1:]\n" \
|
||||
"\t} else {\n" \
|
||||
"\t\treturn nil, false\n" \
|
||||
"\t}\n"
|
||||
|
@ -15,7 +15,7 @@
|
||||
# Booleans are one byte each.
|
||||
# Strings must be valid UTF-8, use u8<> to lift that restriction.
|
||||
# String and array lengths are encoded as u32.
|
||||
# Enumeration values automatically start at 1, and are encoded as i32.
|
||||
# Enumeration values automatically start at 1, and are encoded as i8.
|
||||
# Any struct or union field may be a variable-length array.
|
||||
#
|
||||
# Message framing is done externally, but also happens to prefix u32 lengths.
|
||||
@ -189,6 +189,8 @@ function defenum( name, ident, value, cg) {
|
||||
value = readnumber()
|
||||
if (!value)
|
||||
fatal("enumeration values cannot be zero")
|
||||
if (value < -128 || value > 127)
|
||||
fatal("enumeration value out of range")
|
||||
expect(accept(","))
|
||||
append(EnumValues, name, SUBSEP ident)
|
||||
if (EnumValues[name, ident]++)
|
||||
|
7
xC-proto
7
xC-proto
@ -19,7 +19,7 @@ struct CommandMessage {
|
||||
case HELLO:
|
||||
u32 version;
|
||||
// If the version check succeeds, the client will receive
|
||||
// an initial stream of BUFFER_UPDATE, BUFFER_LINE,
|
||||
// an initial stream of BUFFER_UPDATE, BUFFER_STATS, BUFFER_LINE,
|
||||
// and finally a BUFFER_ACTIVATE message.
|
||||
case ACTIVE:
|
||||
void;
|
||||
@ -50,6 +50,7 @@ struct EventMessage {
|
||||
union EventData switch (enum Event {
|
||||
PING,
|
||||
BUFFER_UPDATE,
|
||||
BUFFER_STATS,
|
||||
BUFFER_RENAME,
|
||||
BUFFER_REMOVE,
|
||||
BUFFER_ACTIVATE,
|
||||
@ -61,13 +62,15 @@ struct EventMessage {
|
||||
case PING:
|
||||
void;
|
||||
case BUFFER_UPDATE:
|
||||
string buffer_name;
|
||||
bool hide_unimportant;
|
||||
case BUFFER_STATS:
|
||||
string buffer_name;
|
||||
// These are cumulative, even for lines flushed out from buffers.
|
||||
// Updates to these values aren't broadcasted, thus handle:
|
||||
// - BUFFER_LINE by bumping/setting them as appropriate,
|
||||
// - BUFFER_ACTIVATE by clearing them for the previous buffer
|
||||
// (this way, they can be used to mark unread messages).
|
||||
// Any updates received after the initial sync should be ignored.
|
||||
u32 new_messages;
|
||||
u32 new_unimportant_messages;
|
||||
bool highlighted;
|
||||
|
83
xC.c
83
xC.c
@ -3068,6 +3068,16 @@ relay_prepare_buffer_update (struct app_context *ctx, struct buffer *buffer)
|
||||
struct relay_event_data_buffer_update *e = &m->data.buffer_update;
|
||||
e->event = RELAY_EVENT_BUFFER_UPDATE;
|
||||
e->buffer_name = str_from_cstr (buffer->name);
|
||||
e->hide_unimportant = buffer->hide_unimportant;
|
||||
}
|
||||
|
||||
static void
|
||||
relay_prepare_buffer_stats (struct app_context *ctx, struct buffer *buffer)
|
||||
{
|
||||
struct relay_event_message *m = relay_prepare (ctx);
|
||||
struct relay_event_data_buffer_stats *e = &m->data.buffer_stats;
|
||||
e->event = RELAY_EVENT_BUFFER_STATS;
|
||||
e->buffer_name = str_from_cstr (buffer->name);
|
||||
e->new_messages = MIN (UINT32_MAX,
|
||||
buffer->new_messages_count - buffer->new_unimportant_count);
|
||||
e->new_unimportant_messages = MIN (UINT32_MAX,
|
||||
@ -8171,7 +8181,7 @@ irc_try_parse_welcome_for_userhost (struct server *s, const char *m)
|
||||
strv_free (&v);
|
||||
}
|
||||
|
||||
static bool process_input_utf8
|
||||
static bool process_input_line
|
||||
(struct app_context *, struct buffer *, const char *, int);
|
||||
static void on_autoaway_timer (struct app_context *ctx);
|
||||
|
||||
@ -8200,7 +8210,7 @@ irc_on_registered (struct server *s, const char *nickname)
|
||||
if (command)
|
||||
{
|
||||
log_server_debug (s, "Executing \"#s\"", command);
|
||||
(void) process_input_utf8 (s->ctx, s->buffer, command, 0);
|
||||
(void) process_input_line (s->ctx, s->buffer, command, 0);
|
||||
}
|
||||
|
||||
int64_t command_delay = get_config_integer (s->config, "command_delay");
|
||||
@ -9066,12 +9076,21 @@ irc_autosplit_message (struct server *s, const char *message,
|
||||
- 1 - (int) strlen (s->irc_user_host)
|
||||
- 1 - fixed_part;
|
||||
|
||||
// However we don't always have the full info for message splitting
|
||||
// Multiline messages can be triggered through hooks and plugins.
|
||||
struct strv lines = strv_make ();
|
||||
cstr_split (message, "\r\n", false, &lines);
|
||||
bool success = true;
|
||||
for (size_t i = 0; i < lines.len; i++)
|
||||
{
|
||||
// We don't always have the full info for message splitting.
|
||||
if (!space_in_one_message)
|
||||
strv_append (output, message);
|
||||
else if (!wrap_message (message, space_in_one_message, output, e))
|
||||
return false;
|
||||
return true;
|
||||
strv_append (output, lines.vector[i]);
|
||||
else if (!(success =
|
||||
wrap_message (lines.vector[i], space_in_one_message, output, e)))
|
||||
break;
|
||||
}
|
||||
strv_free (&lines);
|
||||
return success;
|
||||
}
|
||||
|
||||
static void
|
||||
@ -9080,12 +9099,11 @@ send_autosplit_message (struct server *s,
|
||||
const char *prefix, const char *suffix)
|
||||
{
|
||||
struct buffer *buffer = str_map_find (&s->irc_buffer_map, target);
|
||||
|
||||
// "COMMAND target * :prefix*suffix"
|
||||
int fixed_part = strlen (command) + 1 + strlen (target) + 1 + 1
|
||||
+ strlen (prefix) + strlen (suffix);
|
||||
|
||||
// We might also want to preserve attributes across splits but
|
||||
// that would make this code a lot more complicated
|
||||
|
||||
struct strv lines = strv_make ();
|
||||
struct error *e = NULL;
|
||||
if (!irc_autosplit_message (s, message, fixed_part, &lines, &e))
|
||||
@ -9831,7 +9849,7 @@ lua_buffer_execute (lua_State *L)
|
||||
struct lua_weak *wrapper = lua_weak_deref (L, &lua_buffer_info);
|
||||
struct buffer *buffer = wrapper->object;
|
||||
const char *line = lua_plugin_check_utf8 (L, 2);
|
||||
(void) process_input_utf8 (wrapper->plugin->ctx, buffer, line, 0);
|
||||
(void) process_input_line (wrapper->plugin->ctx, buffer, line, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -13261,13 +13279,13 @@ process_alias (struct app_context *ctx, struct buffer *buffer,
|
||||
log_global_debug (ctx, "Alias expanded to: ###d: \"#s\"",
|
||||
(int) i, commands->vector[i]);
|
||||
for (size_t i = 0; i < commands->len; i++)
|
||||
if (!process_input_utf8 (ctx, buffer, commands->vector[i], ++level))
|
||||
if (!process_input_line (ctx, buffer, commands->vector[i], ++level))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
process_input_utf8_posthook (struct app_context *ctx, struct buffer *buffer,
|
||||
process_input_line_posthook (struct app_context *ctx, struct buffer *buffer,
|
||||
char *input, int alias_level)
|
||||
{
|
||||
if (*input != '/' || *++input == '/')
|
||||
@ -13316,35 +13334,27 @@ process_input_hooks (struct app_context *ctx, struct buffer *buffer,
|
||||
}
|
||||
|
||||
static bool
|
||||
process_input_utf8 (struct app_context *ctx, struct buffer *buffer,
|
||||
process_input_line (struct app_context *ctx, struct buffer *buffer,
|
||||
const char *input, int alias_level)
|
||||
{
|
||||
// Note that this also gets called on expanded aliases,
|
||||
// which might or might not be desirable (we can forward "alias_level")
|
||||
char *processed = process_input_hooks (ctx, buffer, xstrdup (input));
|
||||
bool result = !processed
|
||||
|| process_input_utf8_posthook (ctx, buffer, processed, alias_level);
|
||||
|| process_input_line_posthook (ctx, buffer, processed, alias_level);
|
||||
free (processed);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
process_input (struct app_context *ctx, char *user_input)
|
||||
process_input (struct app_context *ctx, struct buffer *buffer,
|
||||
const char *input)
|
||||
{
|
||||
char *input;
|
||||
if (!(input = iconv_xstrdup (ctx->term_to_utf8, user_input, -1, NULL)))
|
||||
print_error ("character conversion failed for: %s", "user input");
|
||||
else
|
||||
{
|
||||
struct strv lines = strv_make ();
|
||||
cstr_split (input, "\r\n", false, &lines);
|
||||
for (size_t i = 0; i < lines.len; i++)
|
||||
(void) process_input_utf8 (ctx,
|
||||
ctx->current_buffer, lines.vector[i], 0);
|
||||
|
||||
(void) process_input_line (ctx, buffer, lines.vector[i], 0);
|
||||
strv_free (&lines);
|
||||
}
|
||||
free (input);
|
||||
}
|
||||
|
||||
// --- Word completion ---------------------------------------------------------
|
||||
@ -14193,6 +14203,10 @@ on_toggle_unimportant (int count, int key, void *user_data)
|
||||
(void) key;
|
||||
struct app_context *ctx = user_data;
|
||||
ctx->current_buffer->hide_unimportant ^= true;
|
||||
|
||||
relay_prepare_buffer_update (ctx, ctx->current_buffer);
|
||||
relay_broadcast (ctx);
|
||||
|
||||
buffer_print_backlog (ctx, ctx->current_buffer);
|
||||
return true;
|
||||
}
|
||||
@ -14580,7 +14594,7 @@ on_editline_return (EditLine *editline, int key)
|
||||
}
|
||||
free (line);
|
||||
|
||||
// process_input() expects a multibyte string
|
||||
// on_pending_input() expects a multibyte string
|
||||
const LineInfo *info_mb = el_line (editline);
|
||||
strv_append_owned (&ctx->pending_input,
|
||||
xstrndup (info_mb->buffer, info_mb->lastchar - info_mb->buffer));
|
||||
@ -15170,7 +15184,15 @@ on_pending_input (struct app_context *ctx)
|
||||
{
|
||||
poller_idle_reset (&ctx->input_event);
|
||||
for (size_t i = 0; i < ctx->pending_input.len; i++)
|
||||
process_input (ctx, ctx->pending_input.vector[i]);
|
||||
{
|
||||
char *input = iconv_xstrdup
|
||||
(ctx->term_to_utf8, ctx->pending_input.vector[i], -1, NULL);
|
||||
if (input)
|
||||
process_input (ctx, ctx->current_buffer, input);
|
||||
else
|
||||
print_error ("character conversion failed for: %s", "user input");
|
||||
free (input);
|
||||
}
|
||||
strv_reset (&ctx->pending_input);
|
||||
}
|
||||
|
||||
@ -15223,6 +15245,8 @@ client_resync (struct client *c)
|
||||
{
|
||||
relay_prepare_buffer_update (c->ctx, buffer);
|
||||
relay_send (c);
|
||||
relay_prepare_buffer_stats (c->ctx, buffer);
|
||||
relay_send (c);
|
||||
|
||||
LIST_FOR_EACH (struct buffer_line, line, buffer->lines)
|
||||
{
|
||||
@ -15372,8 +15396,7 @@ client_process_message (struct client *c,
|
||||
&m->data.buffer_complete);
|
||||
break;
|
||||
case RELAY_COMMAND_BUFFER_INPUT:
|
||||
(void) process_input_utf8 (c->ctx,
|
||||
buffer, m->data.buffer_input.text.str, 0);
|
||||
process_input (c->ctx, buffer, m->data.buffer_input.text.str);
|
||||
break;
|
||||
case RELAY_COMMAND_BUFFER_ACTIVATE:
|
||||
buffer_activate (c->ctx, buffer);
|
||||
|
@ -89,15 +89,19 @@ body {
|
||||
}
|
||||
.buffer {
|
||||
display: grid;
|
||||
grid-template-columns: max-content auto;
|
||||
grid-template-columns: max-content minmax(0, 1fr);
|
||||
overflow-y: auto;
|
||||
}
|
||||
.log {
|
||||
padding: .1em .3em;
|
||||
font-family: monospace;
|
||||
white-space: pre-wrap;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.log, .content {
|
||||
padding: .1em .3em;
|
||||
/* Note: https://bugs.chromium.org/p/chromium/issues/detail?id=1261435 */
|
||||
white-space: break-spaces;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.leaked {
|
||||
opacity: 50%;
|
||||
@ -136,10 +140,6 @@ body {
|
||||
.mark.action {
|
||||
color: darkred;
|
||||
}
|
||||
.content {
|
||||
padding: .1em .3em;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.content .b {
|
||||
font-weight: bold;
|
||||
}
|
||||
@ -162,6 +162,7 @@ textarea {
|
||||
margin: 0;
|
||||
border: 2px inset #eee;
|
||||
flex-shrink: 0;
|
||||
resize: vertical;
|
||||
}
|
||||
textarea:focus {
|
||||
outline: none;
|
||||
|
149
xP/public/xP.js
149
xP/public/xP.js
@ -110,8 +110,9 @@ class RelayRpc extends EventTarget {
|
||||
if (typeof params !== 'object')
|
||||
throw "Method parameters must be an object"
|
||||
|
||||
// Left shifts in Javascript convert to a 32-bit signed number.
|
||||
let seq = ++this.commandSeq
|
||||
if (seq >= 1 << 32)
|
||||
if ((seq << 0) != seq)
|
||||
seq = this.commandSeq = 0
|
||||
|
||||
this.ws.send(JSON.stringify({commandSeq: seq, data: params}))
|
||||
@ -131,13 +132,25 @@ class RelayRpc extends EventTarget {
|
||||
let rpc = new RelayRpc(proxy)
|
||||
|
||||
let buffers = new Map()
|
||||
let bufferLast = undefined
|
||||
let bufferCurrent = undefined
|
||||
let bufferLog = undefined
|
||||
let bufferAutoscroll = true
|
||||
|
||||
function resetBufferStats(b) {
|
||||
b.newMessages = 0
|
||||
b.newUnimportantMessages = 0
|
||||
b.highlighted = false
|
||||
}
|
||||
|
||||
function bufferActivate(name) {
|
||||
rpc.send({command: 'BufferActivate', bufferName: name})
|
||||
}
|
||||
|
||||
let connecting = true
|
||||
rpc.connect().then(result => {
|
||||
buffers.clear()
|
||||
bufferLast = undefined
|
||||
bufferCurrent = undefined
|
||||
bufferLog = undefined
|
||||
bufferAutoscroll = true
|
||||
@ -161,13 +174,20 @@ rpc.addEventListener('Ping', event => {
|
||||
rpc.addEventListener('BufferUpdate', event => {
|
||||
let e = event.detail, b = buffers.get(e.bufferName)
|
||||
if (b === undefined) {
|
||||
buffers.set(e.bufferName, {
|
||||
lines: [],
|
||||
newMessages: e.newMessages,
|
||||
newUnimportantMessages: e.newUnimportantMessages,
|
||||
highlighted: e.highlighted,
|
||||
})
|
||||
buffers.set(e.bufferName, (b = {lines: []}))
|
||||
resetBufferStats(b)
|
||||
}
|
||||
b.hideUnimportant = e.hideUnimportant
|
||||
})
|
||||
|
||||
rpc.addEventListener('BufferStats', event => {
|
||||
let e = event.detail, b = buffers.get(e.bufferName)
|
||||
if (b === undefined)
|
||||
return
|
||||
|
||||
b.newMessages = e.newMessages,
|
||||
b.newUnimportantMessages = e.newUnimportantMessages
|
||||
b.highlighted = e.highlighted
|
||||
})
|
||||
|
||||
rpc.addEventListener('BufferRename', event => {
|
||||
@ -179,16 +199,16 @@ rpc.addEventListener('BufferRename', event => {
|
||||
rpc.addEventListener('BufferRemove', event => {
|
||||
let e = event.detail
|
||||
buffers.delete(e.bufferName)
|
||||
if (e.bufferName === bufferLast)
|
||||
bufferLast = undefined
|
||||
})
|
||||
|
||||
rpc.addEventListener('BufferActivate', event => {
|
||||
let old = buffers.get(bufferCurrent)
|
||||
if (old !== undefined) {
|
||||
old.newMessages = 0
|
||||
old.newUnimportantMessages = 0
|
||||
old.highlighted = false
|
||||
}
|
||||
if (old !== undefined)
|
||||
resetBufferStats(old)
|
||||
|
||||
bufferLast = bufferCurrent
|
||||
let e = event.detail, b = buffers.get(e.bufferName)
|
||||
bufferCurrent = e.bufferName
|
||||
bufferLog = undefined
|
||||
@ -221,18 +241,21 @@ rpc.addEventListener('BufferLine', event => {
|
||||
return
|
||||
}
|
||||
|
||||
let visible = e.bufferName == bufferCurrent || e.leakToActive
|
||||
let visible = !document.hidden && bufferLog === undefined &&
|
||||
(e.bufferName == bufferCurrent || e.leakToActive)
|
||||
b.lines.push({...line})
|
||||
if (!visible || b.newMessages || b.newUnimportantMessages) {
|
||||
if (!(visible || e.leakToActive) ||
|
||||
b.newMessages || b.newUnimportantMessages) {
|
||||
if (line.isUnimportant)
|
||||
b.newUnimportantMessages++
|
||||
else
|
||||
b.newMessages++
|
||||
}
|
||||
|
||||
if (e.leakToActive) {
|
||||
let bc = buffers.get(bufferCurrent)
|
||||
bc.lines.push({...line, leaked: true})
|
||||
if (bc.newMessages || bc.newUnimportantMessages) {
|
||||
if (!visible || bc.newMessages || bc.newUnimportantMessages) {
|
||||
if (line.isUnimportant)
|
||||
bc.newUnimportantMessages++
|
||||
else
|
||||
@ -273,6 +296,12 @@ for (let i = 0; i < 24; i++) {
|
||||
|
||||
// ---- UI ---------------------------------------------------------------------
|
||||
|
||||
let linkRE = [
|
||||
/https?:\/\//,
|
||||
/([^\[\](){}<>"'\s]|\([^\[\](){}<>"'\s]*\))+/,
|
||||
/[^\[\](){}<>"'\s,.:]/,
|
||||
].map(r => r.source).join('')
|
||||
|
||||
let Toolbar = {
|
||||
toggleAutoscroll: () => {
|
||||
bufferAutoscroll = !bufferAutoscroll
|
||||
@ -304,10 +333,6 @@ let Toolbar = {
|
||||
}
|
||||
|
||||
let BufferList = {
|
||||
activate: name => {
|
||||
rpc.send({command: 'BufferActivate', bufferName: name})
|
||||
},
|
||||
|
||||
view: vnode => {
|
||||
let items = Array.from(buffers, ([name, b]) => {
|
||||
let classes = [], displayName = name
|
||||
@ -322,7 +347,7 @@ let BufferList = {
|
||||
}
|
||||
}
|
||||
return m('.item', {
|
||||
onclick: event => BufferList.activate(name),
|
||||
onclick: event => bufferActivate(name),
|
||||
class: classes.join(' '),
|
||||
}, displayName)
|
||||
})
|
||||
@ -345,17 +370,11 @@ let Content = {
|
||||
},
|
||||
|
||||
linkify: (text, attrs) => {
|
||||
let re = new RegExp([
|
||||
/https?:\/\//,
|
||||
/([^\[\](){}<>"'\s]|\([^\[\](){}<>"'\s]*\))+/,
|
||||
/[^\[\](){}<>"'\s,.:]/,
|
||||
].map(r => r.source).join(''), 'g')
|
||||
|
||||
let a = [], end = 0, match
|
||||
let re = new RegExp(linkRE, 'g'), a = [], end = 0, match
|
||||
while ((match = re.exec(text)) !== null) {
|
||||
if (end < match.index)
|
||||
a.push(m('span', attrs, text.substring(end, match.index)))
|
||||
a.push(m('a', {href: match[0], ...attrs}, match[0]))
|
||||
a.push(m('a[target=_blank]', {href: match[0], ...attrs}, match[0]))
|
||||
end = re.lastIndex
|
||||
}
|
||||
if (end < text.length)
|
||||
@ -425,25 +444,38 @@ let Content = {
|
||||
}
|
||||
|
||||
let Buffer = {
|
||||
oncreate: vnode => {
|
||||
if (vnode.dom !== undefined && bufferAutoscroll)
|
||||
vnode.dom.scrollTop = vnode.dom.scrollHeight
|
||||
controller: new AbortController(),
|
||||
|
||||
onbeforeremove: vnode => {
|
||||
Buffer.controller.abort()
|
||||
},
|
||||
|
||||
onupdate: vnode => {
|
||||
Buffer.oncreate(vnode)
|
||||
if (bufferAutoscroll)
|
||||
vnode.dom.scrollTop = vnode.dom.scrollHeight
|
||||
},
|
||||
|
||||
oncreate: vnode => {
|
||||
Buffer.onupdate(vnode)
|
||||
window.addEventListener('resize', event => Buffer.onupdate(vnode),
|
||||
{signal: Buffer.controller.signal})
|
||||
},
|
||||
|
||||
view: vnode => {
|
||||
let lines = []
|
||||
let b = buffers.get(bufferCurrent)
|
||||
if (b === undefined)
|
||||
return
|
||||
return m('.buffer')
|
||||
|
||||
let lastDateMark = undefined
|
||||
let markBefore = b.lines.length
|
||||
- b.newMessages - b.newUnimportantMessages
|
||||
b.lines.forEach((line, i) => {
|
||||
if (i == markBefore)
|
||||
lines.push(m('.unread'))
|
||||
if (line.isUnimportant && b.hideUnimportant)
|
||||
return
|
||||
|
||||
let date = new Date(line.when)
|
||||
let dateMark = date.toLocaleDateString()
|
||||
if (dateMark !== lastDateMark) {
|
||||
@ -451,14 +483,10 @@ let Buffer = {
|
||||
lastDateMark = dateMark
|
||||
}
|
||||
|
||||
if (i == markBefore)
|
||||
lines.push(m('.unread'))
|
||||
|
||||
let attrs = {}
|
||||
if (line.leaked)
|
||||
attrs.class = 'leaked'
|
||||
|
||||
// TODO: Make use of isUnimportant.
|
||||
lines.push(m('.time', {...attrs}, date.toLocaleTimeString()))
|
||||
lines.push(m(Content, {...attrs}, line))
|
||||
})
|
||||
@ -476,8 +504,21 @@ let Log = {
|
||||
vnode.dom.scrollTop = vnode.dom.scrollHeight
|
||||
},
|
||||
|
||||
linkify: text => {
|
||||
let re = new RegExp(linkRE, 'g'), a = [], end = 0, match
|
||||
while ((match = re.exec(text)) !== null) {
|
||||
if (end < match.index)
|
||||
a.push(text.substring(end, match.index))
|
||||
a.push(m('a[target=_blank]', {href: match[0]}, match[0]))
|
||||
end = re.lastIndex
|
||||
}
|
||||
if (end < text.length)
|
||||
a.push(text.substring(end))
|
||||
return a
|
||||
},
|
||||
|
||||
view: vnode => {
|
||||
return m(".log", {}, bufferLog)
|
||||
return m(".log", {}, Log.linkify(bufferLog))
|
||||
},
|
||||
}
|
||||
|
||||
@ -542,11 +583,13 @@ let Input = {
|
||||
let handled = false
|
||||
switch (event.keyCode) {
|
||||
case 9:
|
||||
if (!event.shiftKey)
|
||||
if (!event.ctrlKey && !event.metaKey && !event.altKey &&
|
||||
!event.shiftKey)
|
||||
handled = Input.complete(textarea)
|
||||
break
|
||||
case 13:
|
||||
if (!event.shiftKey)
|
||||
if (!event.ctrlKey && !event.metaKey && !event.altKey &&
|
||||
!event.shiftKey)
|
||||
handled = Input.submit(textarea)
|
||||
break
|
||||
}
|
||||
@ -570,6 +613,7 @@ let Main = {
|
||||
return m('.xP', {}, [
|
||||
m('.title', {}, [`xP (${state})`, m(Toolbar)]),
|
||||
m('.middle', {}, [m(BufferList), m(BufferContainer)]),
|
||||
// TODO: Indicate hideUnimportant.
|
||||
m('.status', {}, bufferCurrent),
|
||||
m(Input),
|
||||
])
|
||||
@ -577,3 +621,28 @@ let Main = {
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => m.mount(document.body, Main))
|
||||
|
||||
document.addEventListener('keydown', event => {
|
||||
if (rpc.ws == undefined || event.ctrlKey || event.metaKey)
|
||||
return
|
||||
|
||||
if (event.altKey && event.key == 'Tab') {
|
||||
if (bufferLast !== undefined)
|
||||
bufferActivate(bufferLast)
|
||||
} else if (event.altKey && event.key == 'a') {
|
||||
for (const [name, b] of buffers)
|
||||
if (name !== bufferCurrent && b.newMessages) {
|
||||
bufferActivate(name)
|
||||
break
|
||||
}
|
||||
} else if (event.altKey && event.key == '!') {
|
||||
for (const [name, b] of buffers)
|
||||
if (name !== bufferCurrent && b.highlighted) {
|
||||
bufferActivate(name)
|
||||
break
|
||||
}
|
||||
} else
|
||||
return
|
||||
|
||||
event.preventDefault()
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user