0
0
Fork 0
mirror of https://github.com/matrix-construct/construct synced 2025-03-16 22:41:46 +01:00

client: Fixes/improvements to client.

This commit is contained in:
Jason Volk 2017-11-15 17:34:42 -08:00
parent b832d15ad3
commit 94aac875f1
16 changed files with 756 additions and 589 deletions

File diff suppressed because it is too large Load diff

View file

@ -39,6 +39,7 @@ mc.ng.app.controller('mc', class extends mc.ng.controller
super("mc", $scope, $timeout);
$scope['$'] = $;
$scope.exists = (id) => $(id).length > 0;
$scope.Object = Object;
$scope.JSON = JSON;
$scope.parseInt = parseInt;

View file

@ -29,7 +29,7 @@
* Authentication flow
*
*/
mc.auth = async function(flows = {})
mc.auth = async function(flows = {}, opts = {})
{
let request = mc.m.login.get();
let login = await request.response;
@ -41,7 +41,7 @@ mc.auth = async function(flows = {})
let handler = mc.auth[type];
if(handler === undefined)
{
flow.result = new mc.error("unsupported");
flow.result = new mc.error("authentication flow type unsupported");
}
else try
{
@ -84,6 +84,7 @@ mc.auth["m.login.password"] = async function(opts = {})
}
catch(error)
{
console.warn("The user's password failed; deleting.");
delete mc.local.wasspord;
throw error;
}

View file

@ -120,16 +120,26 @@ window.addEventListener("error", (msg, url, line, column, error) => mc.abort(
*/
mc.unhandled = function(error)
{
if(!(error instanceof mc.error))
error = new mc.error(error);
if(error.element === undefined)
error.element = $("#charybdis");
try
{
let root = mc.ng.root();
root.error = error;
if(error.name == "timeout")
return;
if(!error.element)
{
console.warn("No element found to place unhandled exception...");
error.element = angular.element("#charybdis");
}
if(!(error instanceof mc.error))
error = new mc.error(error);
let scope = maybe(() => error.element.scope());
if(!scope)
scope = mc.ng.mc();
if(scope)
scope.error = error;
}
catch(e)
{

View file

@ -45,8 +45,12 @@ mc.main = async function()
let sopts = {};
if(await mc.main.init()) while(1) try
{
// longpolls and processes data from a /sync request
// Calling apply() before and after the await is insufficient because
// we want an application of what mc.sync() also did before waiting a
// long time for the response. The apply.later() pushes an async apply
// to the js queue which paints once after this stack starts to wait.
mc.ng.apply.later();
await mc.sync(sopts);
}
catch(error)
@ -56,8 +60,9 @@ mc.main = async function()
// here (double fault) results in program abort.
if((await mc.main.fault(error)) === true)
continue;
else
break;
console.error("Unable to recover from main fault: shutting down...");
break;
}
// The destruction sequence should always be reached except on abort.
@ -91,9 +96,28 @@ mc.main.init = async function()
});
// Fault this manually to ensure authenticity for now.
console.log("Logging in...");
let errors = {};
return await mc.main.fault["M_MISSING_TOKEN"](errors);
console.log("Initial login attempt...");
let errors =
{
m:
{
errcode: "M_MISSING_TOKEN",
},
};
mc.apply.later();
if(!(await mc.main.fault(errors)))
{
let catch_elem = (idx, flow) =>
flow.result.element = $("#charybdis_login_form");
Object.each(errors.auth, catch_elem);
return false;
}
mc.storage.sync();
mc.apply();
return true;
};
/**
@ -152,6 +176,8 @@ mc.main.fini = async function()
console.log("Final angular repaint..."); try
{
delete mc.ng.root().error;
delete mc.ng.mc().error;
mc.ng.apply();
}
catch(error)
@ -208,8 +234,11 @@ mc.main.fault = async function(error)
if(error.name == "timeout")
{
console.warn("client timeout");
delete mc.ng.root().error;
delete mc.ng.mc().error;
mc.ng.root().error = undefined;
mc.ng.mc().error = undefined;
mc.ng.apply.later();
return true;
}
@ -375,7 +404,7 @@ mc.main.menu =
hide: true,
click: (event) =>
{
mc.auth.logout.event = event;
mc.auth.logout();
},
},
};

View file

@ -64,7 +64,7 @@ mc.opts =
style: "charybdis.css",
// Quick setting to debug incoming sync messages on the console
sync_debug: 10,
sync_debug: 0,
// Option for how events are ordered on the timeline
//

View file

@ -109,7 +109,7 @@ room.control =
{
show_members: false,
show_main_input: true,
show_status_indicator: true,
show_status_frieze: true,
show_event_detail: false,
show_event_info: {},

View file

@ -31,9 +31,13 @@ mc.ng.app.controller('room', class extends mc.ng.controller
super("room", $scope);
$scope.open_bracket = this.open_bracket;
$scope.close_bracket = this.close_bracket;
$scope.sender_domid = this.sender_domid;
$scope.sender_sid = this.sender_sid;
$scope.delta = this.delta;
$scope.should_show_target = this.should_show_target;
$scope.should_show_avatar = this.should_show_avatar;
$scope.dots_to_underscores = this.dots_to_underscores;
$scope.handler_exists = this.handler_exists;
}
destructor()
@ -78,6 +82,27 @@ mc.ng.app.controller('room', class extends mc.ng.controller
url.startsWith("https://");
}
sender_domid(sender)
{
let sid = this.sender_sid(sender);
let domid = mc.m.domid(sender);
let remain = 26 - sid.length;
let start = remain < domid.length? domid.length - remain : 0;
let ret = domid.substr(start, remain);
if(ret.length < domid.length)
ret = "..." + ret;
return ret;
}
sender_sid(sender)
{
let displayname = maybe(() => mc.users[sender].displayname);
let str = displayname? displayname : mc.m.sid(sender);
let len = 28 - 2;
return str.substr(0, len);
}
open_bracket(event)
{
if(event.state_key !== undefined)
@ -127,4 +152,15 @@ mc.ng.app.controller('room', class extends mc.ng.controller
return '>';
}
}
dots_to_underscores(str)
{
return str.replace(/\./g, '_');
}
handler_exists(type)
{
let sel = "#ircd_room_event__" + this.dots_to_underscores(type);
return $(sel).length > 0;
}
});

View file

@ -111,5 +111,9 @@ room.events.prototype.can_show = function(event, $index)
if($index < this.room.timeline.horizon)
return false;
if(this.room.timeline.length > this.room.timeline.opts.limit)
if($index < this.room.timeline.opts.limit)
return false;
return true;
};

View file

@ -90,7 +90,7 @@ room.membership.count = function(type = "join")
};
/**
* List mxids for a certain membership type, i.e "join", "left" etc.
* List mxids for a certain membership type, i.e "join", "leave" etc.
*/
room.membership.list = function(type = "join")
{
@ -146,6 +146,7 @@ room.membership.controller = class extends mc.ng.controller
{
super("room.membership", $scope);
this.room = this.$scope.room;
this.room.membership.control = this;
this.filter = "join";
this.frame = 64;
@ -205,6 +206,21 @@ room.membership.controller.prototype.scroll.down = function()
this.begin = this.list.length - this.step;
};
/**
* Select the first membership type which will show a non-empty
* list. This is so the members list doesn't default to "join"
* with an empty pane.
*/
room.membership.first_nonempty_type = function(precedence =
[
"join", "leave", "invite", "ban",
])
{
for(let i in precedence)
if(this.membership.count(precedence[i]))
return precedence[i];
};
/**
* TODO: XXX: repurpose
*/
@ -220,6 +236,10 @@ room.membership.controller.prototype.fetch = async function(opts = {})
// Remove the temporary member count now that we have valid data.
delete this.room.membership._num_joined_members;
// Select a non-empty list type for when the membership pane first
// opens. We can do that now because the lists are downloaded.
this.filter = this.room.membership.first_nonempty_type();
return true;
};
@ -232,10 +252,10 @@ value:
"NAME":
{
show: (room, member) =>
!empty(member.content.displayname),
!empty(maybe(() => member.content.displayname)),
value: (room, member) =>
member.content.displayname? member.content.displayname : "",
maybe(() => member.content.displayname)? member.content.displayname : "",
},
"POWER":
@ -318,10 +338,10 @@ value:
}});
/**
* Drives the status indicator bar at the top of the room
* Drives the status frieze bar at the top of the room
* apropos member information.
*/
Object.defineProperty(room.membership, 'indicator', {
Object.defineProperty(room.membership, 'frieze', {
writable: false,
enumerable: false,
configurable: false,
@ -332,7 +352,7 @@ value:
count: (room) => room.membership.count('knock'),
click: (room, $event) =>
{
room.control.member_filter = 'knock';
//room.control.show_members_filter = 'knock';
room.control.show_members = true;
},
},
@ -342,7 +362,7 @@ value:
count: (room) => room.membership.count('invite'),
click: (room, $event) =>
{
room.control.member_filter = 'invite';
//room.control.show_members_filter = 'invite';
room.control.show_members = true;
},
},
@ -352,7 +372,7 @@ value:
count: (room) => room.membership.count('join'),
click: (room, $event) =>
{
room.control.member_filter = 'join';
//room.membership.control.filter = 'join';
room.control.show_members = true;
},
},
@ -360,9 +380,9 @@ value:
"LEFT":
{
count: (room) => room.membership.count('leave'),
click: (room, $event) =>
click: (room, $event, members) =>
{
room.control.member_filter = 'leave';
//members.list = room.membership.list('leave');
room.control.show_members = true;
},
},
@ -372,7 +392,7 @@ value:
count: (room) => room.membership.count('ban'),
click: (room, $event) =>
{
room.control.member_filter = 'ban';
//room.membership.control.filter = 'ban';
room.control.show_members = true;
},
},

View file

@ -108,6 +108,10 @@ room.sync.handle["state"] = function(data, action)
});
this.timeline.insert(events);
events.forEach((event) =>
{
this.oracle.invalidate(event.type);
});
};
room.sync.handle["timeline"] = function(data, action)

View file

@ -179,8 +179,8 @@ room.timeline.prototype.only = function(condition, closure)
*/
room.timeline.prototype.count = function(condition)
{
let mapper = (...event) =>
condition(...event)? true : false;
let mapper = (event) =>
condition(event)? true : false;
let reducer = (acc, val) =>
acc + val;

View file

@ -95,7 +95,7 @@ mc.rooms.menu["JOINED"] =
order: 1,
icon: "fa-thumbs-up",
selected: () => mc.rooms.mode == "JOINED",
tooltip: "List the rooms you have joined",
tooltip: "List the rooms you have joined.",
};
mc.rooms.menu["JOINED"].click = function($event)
@ -110,12 +110,14 @@ mc.rooms.menu["LEFT"] =
order: 2,
icon: "fa-thumbs-down",
selected: () => mc.rooms.mode == "LEFT",
tooltip: "List the rooms from which you have parted company.",
};
mc.rooms.menu["LEFT"].click = function($event)
{
let rooms = Object.values(mc.rooms.left);
mc.rooms.list = mc.rooms.sort.focused(rooms);
mc.rooms.mode = "LEFT";
mc.rooms.list = [];
};
mc.rooms.menu["FEED"] =

View file

@ -36,40 +36,49 @@ mc.rooms.sync = function(action, room_id, data)
if(!handler)
return;
handler(room_id, data);
};
mc.rooms.sync["join"] = function(room_id, data)
{
if(!(room_id in mc.rooms))
mc.rooms[room_id] = new mc.room({room_id: room_id});
let room = mc.rooms[room_id];
if(!(room_id in mc.rooms.joined))
{
handler(room, data);
};
mc.rooms.sync["join"] = function(room, data)
{
if(!(room.id in mc.rooms.joined))
mc.rooms.joined[room.id] = room;
mc.rooms.menu["JOINED"].click();
}
room.sync(data, "join");
if((mc.rooms.current.empty() && empty(mc.session.rooms.history))
|| (mc.session.rooms.history[0] == room_id))
|| (mc.session.rooms.history[0] == room.id))
{
mc.rooms.current.add(room.id);
room.scroll.to.bottom();
}
if(!mc.rooms.current_mode)
delete mc.rooms.invited[room.id];
delete mc.rooms.left[room.id];
if(!mc.rooms.mode || mc.rooms.mode == "JOINED")
mc.rooms.menu["JOINED"].click();
};
mc.rooms.sync["invite"] = function(room_id, data)
mc.rooms.sync["leave"] = function(room, data)
{
if(!(room.id in mc.rooms.left))
mc.rooms.left[room.id] = room;
room.sync(data, "leave");
delete mc.rooms.invited[room.id];
delete mc.rooms.joined[room.id];
if(!mc.rooms.mode)
mc.rooms.menu["LEFT"].click();
};
mc.rooms.sync["invite"] = function(room, data)
{
debug.object({invite: data}, 5);
};
mc.rooms.sync["leave"] = function(room_id, data)
{
debug.object({leave: data}, 5);
};

View file

@ -560,7 +560,7 @@ const Class =
};
/** Bind a tree of functions organized into namespaces (using
* a nested object heirarchy) to a this value
* a nested object hierarchy) to a this value
*/
Function.bindtree = function(obj, that, ...args)
{
@ -620,7 +620,7 @@ Function.bindtree = function(obj, that, ...args)
};
/** Bind a tree of functions organized into namespaces (using
* a nested object heirarchy) to a this value
* a nested object hierarchy) to a this value
*/
Function.bindtree.self = function(self, obj, that = self)
{

View file

@ -110,6 +110,7 @@ type="text/ng-template"
class="login_ pane"
ng-show="mc.show['#charybdis_login']"
ng-include="'charybdis_login'"
ircd-catch
>
</div>
</script>
@ -149,7 +150,6 @@ type="text/ng-template"
<div
id="charybdis_rooms_list"
class="list"
ng-if="!empty(mc.rooms.list)"
ng-controller="rooms.list as list"
ang-scroll="list.scroll($event)"
ng-include="'ircd_rooms_list'"
@ -467,9 +467,9 @@ type="text/ng-template"
>
</div>
<div
class="status indicator bar"
ng-show="room.control.show_status_indicator"
ng-include="'ircd_room_control_indicator'"
class="status frieze bar"
ng-show="room.control.show_status_frieze"
ng-include="'ircd_room_control_frieze'"
>
</div>
<div
@ -493,7 +493,7 @@ type="text/ng-template"
<div
class="members"
ng-if="room.control.show_members"
ng-controller="room.membership as members"
ng-controller="mc.room.membership.controller as members"
ng-include="'ircd_room_members'"
>
</div>
@ -523,6 +523,10 @@ type="text/ng-template"
>
</form>
<div
class="filler grow"
>
</div>
<div
class="events"
ng-controller="room.events as events"
ang-scroll="room.scroll.on($event)"
@ -574,6 +578,9 @@ type="text/ng-template"
"
room.control.show_members = members.filter != name;
members.filter = name;
members.info = undefined;
members.list = room.membership.sorted(name);
item.click(room, $event, members);
"
ng-class="
{
@ -1667,7 +1674,7 @@ type="text/ng-template"
</script>
<script
id="ircd_room_control_indicator"
id="ircd_room_control_frieze"
type="text/ng-template"
>
<div
@ -1741,18 +1748,6 @@ type="text/ng-template"
</i>
</button>
<div
class="state"
>
<p
class="count"
>
{{ Object.keys(room.state).length }}
</p>
<h5>
ST
</h5>
</div>
<div
class="timeline"
>
<p
@ -1765,6 +1760,30 @@ type="text/ng-template"
</h5>
</div>
<div
class="state"
>
<p
class="count"
>
{{ room.timeline.stats().state }}
</p>
<h5>
ST
</h5>
</div>
<div
class="servers"
>
<p
class="count"
>
{{ room.timeline.servers().length }}
</p>
<h5>
HS
</h5>
</div>
<div
class="activity"
>
<i
@ -1818,7 +1837,7 @@ type="text/ng-template"
</i>
</button>
<div
ng-repeat="(name, item) in mc.room.membership.indicator"
ng-repeat="(name, item) in mc.room.membership.frieze"
ng-if="item.show !== false && item.count(room)"
ng-click="item.click(room, $event); $event.stopPropagation();"
>
@ -1853,13 +1872,13 @@ type="text/ng-template"
<div
class="menu_toggle"
ng-hide="mc.show['#charybdis_menu']"
ng-include="'ircd_room_control_indicator_charybdis_menu'"
ng-include="'ircd_room_control_frieze_charybdis_menu'"
>
</div>
</script>
<script
id="ircd_room_control_indicator_charybdis_menu"
id="ircd_room_control_frieze_charybdis_menu"
type="text/ng-template"
>
<button
@ -1967,19 +1986,17 @@ type="text/ng-template"
<span
ng-if="!mc.users[event.sender].displayname"
>
{{ mc.m.sid(event.sender) }}
{{this.sender_sid(event.sender)}}
</span>
</span>
&nbsp;
<span
class="domain"
>{{ mc.m.domid(event.sender) }}</span>
>{{this.sender_domid(event.sender)}}</span>
<span
class="close bracket"
>{{ this.close_bracket(event) }}</span>
>{{this.close_bracket(event)}}</span>
</script>
@ -2008,88 +2025,86 @@ type="text/ng-template"
</script>
<script
id="ircd_room_event__member"
type="text/ng-template"
>
<p
class="join"
ng-switch-when="join"
>
{{ event.content.displayname }} joined the channel.
</p>
<p
class="leave"
ng-switch-when="leave"
ng-if="!event.target || event.target == event.sender"
>
{{ event.content.displayname }} left the channel.
</p>
<p
class="leave"
ng-switch-when="leave"
ng-if="event.target && event.target != event.sender"
>
<b>kicked</b> {{ event.content.displayname }}
</p>
<p
class="invite"
ng-switch-when="invite"
>
<b>invited</b> {{ event.content.displayname }}
</p>
<p
class="default unhandled"
ng-switch-default
>
unhandled m.room.member type '{{ event.content.membership }}'
</p>
</script>
<script
id="ircd_room_event__message"
type="text/ng-template"
>
</script>
<script
id="ircd_room_event__create"
id="ircd_room_event__m_room_member"
type="text/ng-template"
>
<div
class="changed"
ng-class="event.content.membership"
>
<h5>
created
</h5>
<h3>
ROOM
<h1>
{{event.content.membership.toUpperCase()}}
</h1>
<h3
ng-switch on="event.content.membership"
>
<span
class="state_key"
ng-if="empty(event.content.displayname)"
>
{{mc.m.sid(event.state_key)}}
</span>
<span
class="displayname state_key"
ng-if="!empty(event.content.displayname)"
>
{{event.content.displayname}}&nbsp; a.k.a &nbsp;{{mc.m.sid(event.state_key)}}
</span>
&nbsp;of&nbsp;
<span
class="domain"
>
{{mc.m.domid(event.state_key)}}
</span>
&nbsp;
<span ng-switch-when="join">joined</span>
<span ng-switch-when="leave">left</span>
<span ng-switch-when="invite">invited to</span>
<span
class="default unhandled"
ng-switch-default
>
{{event.content.membership}}
</span>
&nbsp;the room.
</h3>
</div>
<h1>
{{ id }}
</h1>
</script>
<script
id="ircd_room_event__power_levels"
id="ircd_room_event__m_room_create"
type="text/ng-template"
>
<div
class="changed"
>
<h5>
changed
</h5>
<h3>
PRIVILEGE LEVELS
</h3>
<h1>
CREATE
</h1>
<h2>
{{ room.id }}
</h2>
</div>
</script>
<script
id="ircd_room_event__m_room_power_levels"
type="text/ng-template"
>
<div
class="changed"
>
<h1>
POWER LEVELS
</h1>
<h2>
&nbsp;
</h2>
</div>
<div
class="new levels"
style="display: none"
>
<div
class="level"
@ -2128,40 +2143,38 @@ type="text/ng-template"
</script>
<script
id="ircd_room_event__canonical_alias"
id="ircd_room_event__m_room_canonical_alias"
type="text/ng-template"
>
<div
class="changed"
>
<h5>
changed
</h5>
<h3>
<h1>
CANONICAL ALIAS
</h3>
</h1>
<h2>
{{ event.content.alias }}
</h2>
</div>
<h2>
{{ event.content.alias }}
</h2>
</script>
<script
id="ircd_room_event__join_rules"
id="ircd_room_event__m_room_join_rules"
type="text/ng-template"
>
<div
class="changed"
>
<h5>
changed
</h5>
<h3>
<h1>
JOIN RULES
</h3>
</h1>
<h2>
{{ event.content.join_rule }}
</h2>
</div>
<div
class="join_rules menu"
style="display: none"
>
<button
class="item"
@ -2185,102 +2198,178 @@ type="text/ng-template"
</script>
<script
id="ircd_room_event__history_visibility"
id="ircd_room_event__m_room_history_visibility"
type="text/ng-template"
>
<div
class="changed"
>
<h5>
changed
</h5>
<h3>
<h1>
HISTORY VISIBILITY
</h3>
</h1>
<h2>
{{ event.content.history_visibility }}
</h2>
</div>
<h2>
{{ event.content.history_visibility }}
</h2>
</script>
<script
id="ircd_room_event__guest_access"
id="ircd_room_event__m_room_guest_access"
type="text/ng-template"
>
<div
class="changed"
>
<h5>
changed
</h5>
<h3>
<h1>
GUEST ACCESS
</h3>
</h1>
<h2>
{{ event.content.guest_access }}
</h2>
</div>
<h2>
{{ event.content.guest_access }}
</h2>
</script>
<script
id="ircd_room_event__aliases"
id="ircd_room_event__m_room_aliases"
type="text/ng-template"
>
<div
class="changed"
>
<h5>
changed
</h5>
<h3>
<h1>
ALIASES
</h3>
</h1>
<h2>
&nbsp;
</h2>
</div>
<div
class="aliases row nowrap grow center"
class="aliases"
>
<h2
class="alias full-center"
<h3
class="alias"
ng-repeat="alias in event.content.aliases"
>
{{ alias }}
</h2>
</h3>
</div>
</script>
<script
id="ircd_room_event__name"
id="ircd_room_event__m_room_name"
type="text/ng-template"
>
<div
class="changed"
>
<h3
ng-if="room.name"
>
Changed the room name
</h3>
<h3
ng-if="!room.name"
>
Named the room
</h3>
</div>
<div
class="name row nowrap grow center"
>
<h2
class="name full-center"
ng-if="room.name"
>
from {{ room.name }} to {{ event.content.name }}
</h2>
<h1>
ROOM NAME
</h1>
<h2
class="name full-center"
ng-if="!room.name"
>
{{ event.content.name }}
</h2>
<h3
class="name full-center"
ng-if="room.name"
>
from <b>{{ room.name }}</b> to <b>{{ event.content.name }}</b>
</h3>
</div>
</script>
<script
id="ircd_room_event__m_room_message"
type="text/ng-template"
>
<div
class="message"
ng-switch on="event.content.msgtype"
>
<div
class="notice"
ng-switch-when="m.notice"
>{{event.content.body}}</div>
<p
class="text"
ng-switch-when="m.text"
>{{event.content.body}}</p>
<p
class="emote"
ng-switch-when="m.emote"
>{{event.content.body}}</p>
<div
class="image"
ng-switch-when="m.image"
ng-class="
{
zoom: room.control.content.zoom == event.event_id,
}">
<h6
class="label"
>
{{ event.content.body }}
</h6>
<img
ng-click="room.dom.zoom($event, event);"
ng-src="{{mc.m.xc(event.content.url)}}"
alt="{{event.content.body}}"
/>
</div>
<a
class="video"
ng-switch-when="m.video"
>
<img
ng-src="{{event.content.info.thumbnail_url}}"
ng-if="event.content.info.thumbnail_url !== undefined"
ng-show="event.content.info"
alt="{{event.content.body}}"
/>
<iframe
ng-if="false"
class="ytplayer"
type="text/html"
width="480"
height="260"
ng-src="{{mc.embed(event.content.url)}}"
frameborder="0"
>
</iframe>
</a>
<p
class="default"
ng-switch-default
>
<span
ng-if="empty(event.content)"
>
Message content is empty.
</span>
<span
ng-if="!empty(event.content)"
>
<span
ng-if="!empty(event.content.msgtype)"
>
No msgtype in content.
</span>
<span
ng-if="!empty(event.content.msgtype)"
class="unhandled"
>
Unhandled msgtype: {{event.content.msgtype}}
</span>
</span>
</p>
</div>
</script>
@ -2288,10 +2377,17 @@ type="text/ng-template"
id="ircd_room_event_unhandled"
type="text/ng-template"
>
<h4>
UNHANDLED EVENT
</h4>
<div
class="changed"
>
<h1>
{{event.type}}
</h1>
<h2>
</h2>
</div>
<p
class="guru"
style="white-space: pre-wrap;"
>
{{ debug.stringify(event, 2) }}
@ -2348,13 +2444,13 @@ id="ircd_room_events"
type="text/ng-template"
>
<div
class="event"
ng-repeat="event in room.timeline"
ng-if="events.can_render(event, $index)"
ng-show="events.can_show(event, $index)"
ng-switch on="event.type"
class="event"
ng-class="
{
state: mc.event.is_state(event),
zoom: room.control.content.zoom == event.event_id,
}">
<label
@ -2383,176 +2479,20 @@ type="text/ng-template"
</h4>
<div
class="member"
ng-switch-when="m.room.member"
class="handler"
ng-class="this.dots_to_underscores(event.type)"
ng-if="handler_exists(event.type)"
ng-include="'ircd_room_event__' + this.dots_to_underscores(event.type)"
>
<div
ng-switch on="event.content.membership"
ng-include="'ircd_room_event__member'"
>
</div>
</div>
<div
class="message"
ng-switch-when="m.room.message"
ng-switch on="event.content.msgtype"
>
<div
class="notice"
ng-switch-when="m.notice"
>{{event.content.body}}</div>
<p
class="text"
ng-switch-when="m.text"
>{{event.content.body}}</p>
<p
class="emote"
ng-switch-when="m.emote"
>{{event.content.body}}</p>
<div
class="image"
ng-switch-when="m.image"
ng-class="
{
zoom: room.control.content.zoom == event.event_id,
}">
<h6
class="label"
>
{{ event.content.body }}
</h6>
<img
ng-click="room.dom.zoom($event, event);"
ng-src="{{mc.m.xc(event.content.url)}}"
alt="{{event.content.body}}"
/>
</div>
<a
class="video"
ng-switch-when="m.video"
>
<img
ng-src="{{event.content.info.thumbnail_url}}"
ng-if="event.content.info.thumbnail_url !== undefined"
ng-show="event.content.info"
alt="{{event.content.body}}"
/>
<iframe
ng-if="false"
class="ytplayer"
type="text/html"
width="480"
height="260"
ng-src="{{mc.embed(event.content.url)}}"
frameborder="0"
>
</iframe>
</a>
<p
class="default unhandled"
ng-switch-default
>
unhandled message type '{{ event.content.msgtype }}'
</p>
</div>
<div
class="state create"
ng-switch-when="m.room.create"
class="handler unhandled"
ng-if="!handler_exists(event.type)"
ng-include="'ircd_room_event_unhandled'"
>
<div
ng-include="'ircd_room_event__create'"
>
</div>
</div>
<div
class="state power_levels"
ng-switch-when="m.room.power_levels"
>
<div
ng-include="'ircd_room_event__power_levels'"
>
</div>
</div>
<div
class="state canonical_alias"
ng-switch-when="m.room.canonical_alias"
>
<div
ng-include="'ircd_room_event__canonical_alias'"
>
</div>
</div>
<div
class="state join_rules"
ng-switch-when="m.room.join_rules"
>
<div
ng-include="'ircd_room_event__join_rules'"
>
</div>
</div>
<div
class="state history_visibility"
ng-switch-when="m.room.history_visibility"
>
<div
ng-include="'ircd_room_event__history_visibility'"
>
</div>
</div>
<div
class="state guest_access"
ng-switch-when="m.room.guest_access"
>
<div
ng-include="'ircd_room_event__guest_access'"
>
</div>
</div>
<div
class="state aliases"
ng-switch-when="m.room.aliases"
>
<div
ng-include="'ircd_room_event__aliases'"
>
</div>
</div>
<div
class="state name"
ng-switch-when="m.room.name"
>
<div
ng-include="'ircd_room_event__name'"
>
</div>
</div>
<div
class="default unhandled"
ng-switch-default
>
<div
ng-include="'ircd_room_event_unhandled'"
>
</div>
</div>
<h3
class="target"
ng-if="this.should_show_target(event)"