Page MenuHomePhorge

D351.1775483204.diff
No OneTemporary

Authored By
Unknown
Size
26 KB
Referenced Files
None
Subscribers
None

D351.1775483204.diff

diff --git a/brunch-config.js b/brunch-config.js
--- a/brunch-config.js
+++ b/brunch-config.js
@@ -54,6 +54,9 @@
babel: {
// Do not use ES6 compiler in vendor code
ignore: [/web\/static\/vendor/]
+ },
+ copycat: {
+ fonts: ["node_modules/bootstrap/fonts"]
}
},
@@ -64,6 +67,11 @@
},
npm: {
- enabled: true
+ enabled: true,
+ globals: {
+ $: "jquery",
+ jQuery: "jquery",
+ bootstrap: "bootstrap"
+ }
}
};
diff --git a/lib/kolab_chat.ex b/lib/kolab_chat.ex
--- a/lib/kolab_chat.ex
+++ b/lib/kolab_chat.ex
@@ -12,6 +12,8 @@
supervisor(KolabChat.Repo, []),
# Start the endpoint when the application starts
supervisor(KolabChat.Endpoint, []),
+ # Start phoenix presence module
+ supervisor(KolabChat.Presence, []),
# Start your own worker by calling: KolabChat.Worker.start_link(arg1, arg2, arg3)
# worker(KolabChat.Worker, [arg1, arg2, arg3]),
]
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -7,7 +7,9 @@
},
"dependencies": {
"phoenix": "file:deps/phoenix",
- "phoenix_html": "file:deps/phoenix_html"
+ "phoenix_html": "file:deps/phoenix_html",
+ "jquery": ">=2.1",
+ "bootstrap": "~3.3.7"
},
"devDependencies": {
"babel-brunch": "~6.0.0",
@@ -15,6 +17,7 @@
"clean-css-brunch": "~2.0.0",
"css-brunch": "~2.0.0",
"javascript-brunch": "~2.0.0",
- "uglify-js-brunch": "~2.0.1"
+ "uglify-js-brunch": "~2.0.1",
+ "copycat-brunch": "~1.1.0"
}
}
diff --git a/web/channels/presence.ex b/web/channels/presence.ex
new file mode 100644
--- /dev/null
+++ b/web/channels/presence.ex
@@ -0,0 +1,77 @@
+defmodule KolabChat.Presence do
+ @moduledoc """
+ Provides presence tracking to channels and processes.
+
+ See the [`Phoenix.Presence`](http://hexdocs.pm/phoenix/Phoenix.Presence.html)
+ docs for more details.
+
+ ## Usage
+
+ Presences can be tracked in your channel after joining:
+
+ defmodule KolabChat.MyChannel do
+ use KolabChat.Web, :channel
+ alias KolabChat.Presence
+
+ def join("some:topic", _params, socket) do
+ send(self, :after_join)
+ {:ok, assign(socket, :user_id, ...)}
+ end
+
+ def handle_info(:after_join, socket) do
+ {:ok, _} = Presence.track(socket, socket.assigns.user_id, %{
+ online_at: inspect(System.system_time(:seconds))
+ })
+ push socket, "presence_state", Presence.list(socket)
+ {:noreply, socket}
+ end
+ end
+
+ In the example above, `Presence.track` is used to register this
+ channel's process as a presence for the socket's user ID, with
+ a map of metadata. Next, the current presence list for
+ the socket's topic is pushed to the client as a `"presence_state"` event.
+
+ Finally, a diff of presence join and leave events will be sent to the
+ client as they happen in real-time with the "presence_diff" event.
+ See `Phoenix.Presence.list/2` for details on the presence datastructure.
+
+ ## Fetching Presence Information
+
+ The `fetch/2` callback is triggered when using `list/1`
+ and serves as a mechanism to fetch presence information a single time,
+ before broadcasting the information to all channel subscribers.
+ This prevents N query problems and gives you a single place to group
+ isolated data fetching to extend presence metadata.
+
+ The function receives a topic and map of presences and must return a
+ map of data matching the Presence datastructure:
+
+ %{"123" => %{metas: [%{status: "away", phx_ref: ...}],
+ "456" => %{metas: [%{status: "online", phx_ref: ...}]}
+
+ The `:metas` key must be kept, but you can extend the map of information
+ to include any additional information. For example:
+
+ def fetch(_topic, entries) do
+ query =
+ from u in User,
+ where: u.id in ^Map.keys(entries),
+ select: {u.id, u}
+
+ users = query |> Repo.all |> Enum.into(%{})
+
+ for {key, %{metas: metas}} <- entries, into: %{} do
+ {key, %{metas: metas, user: users[key]}}
+ end
+ end
+
+ The function above fetches all users from the database who
+ have registered presences for the given topic. The fetched
+ information is then extended with a `:user` key of the user's
+ information, while maintaining the required `:metas` field from the
+ original presence data.
+ """
+ use Phoenix.Presence, otp_app: :kolab_chat,
+ pubsub_server: KolabChat.PubSub
+end
diff --git a/web/channels/room_channel.ex b/web/channels/room_channel.ex
new file mode 100644
--- /dev/null
+++ b/web/channels/room_channel.ex
@@ -0,0 +1,12 @@
+defmodule KolabChat.RoomChannel do
+ use KolabChat.Web, :channel
+
+ def join("room:lobby", _, socket) do
+ {:ok, socket}
+ end
+
+ def handle_in("new:message", message, socket) do
+ broadcast! socket, "new:message", %{user: message["user"], body: message["body"]}
+ {:noreply, socket}
+ end
+end
diff --git a/web/channels/system_channel.ex b/web/channels/system_channel.ex
new file mode 100644
--- /dev/null
+++ b/web/channels/system_channel.ex
@@ -0,0 +1,38 @@
+defmodule KolabChat.SystemChannel do
+ use KolabChat.Web, :channel
+
+ alias KolabChat.Presence
+
+ def join("system", _, socket) do
+ Process.flag(:trap_exit, true)
+ :timer.send_interval(10000, :ping)
+ send self(), :after_join
+
+ {:ok, socket}
+ end
+
+ def handle_info(:after_join, socket) do
+ Presence.track(socket, socket.assigns.user.username, %{
+ status: "online"
+ })
+
+ push socket, "info", %{user: socket.assigns.user.username}
+ push socket, "presence_state", Presence.list(socket)
+
+ {:noreply, socket}
+ end
+
+ def handle_info(:ping, socket) do
+ push socket, "new:msg", %{user: "SYSTEM", body: "ping"}
+
+ {:noreply, socket}
+ end
+
+ def handle_in("set-status", %{"status" => status}, socket) do
+ {:ok, _} = Presence.update(socket, socket.assigns.user.username, %{
+ status: status
+ })
+
+ {:noreply, socket}
+ end
+end
diff --git a/web/channels/user_socket.ex b/web/channels/user_socket.ex
--- a/web/channels/user_socket.ex
+++ b/web/channels/user_socket.ex
@@ -1,26 +1,31 @@
defmodule KolabChat.UserSocket do
use Phoenix.Socket
+ alias KolabChat.Repo
+ alias KolabChat.User
+
## Channels
- # channel "room:*", KolabChat.RoomChannel
+ channel "room:*", KolabChat.RoomChannel
+ channel "system", KolabChat.SystemChannel
## Transports
transport :websocket, Phoenix.Transports.WebSocket
- # transport :longpoll, Phoenix.Transports.LongPoll
+ transport :longpoll, Phoenix.Transports.LongPoll
# Socket params are passed from the client and can
# be used to verify and authenticate a user. After
# verification, you can put default assigns into
# the socket that will be set for all channels, ie
- #
# {:ok, assign(socket, :user_id, verified_user_id)}
- #
# To deny connection, return `:error`.
- #
- # See `Phoenix.Token` documentation for examples in
- # performing token verification on connect.
- def connect(_params, socket) do
- {:ok, socket}
+ def connect(%{"token" => token}, socket) do
+ case Phoenix.Token.verify(socket, "user", token, max_age: 86400) do
+ {:ok, user_id} ->
+ socket = assign(socket, :user, Repo.get!(User, user_id))
+ {:ok, socket}
+ {:error, _} ->
+ :error
+ end
end
# Socket id's are topics that allow you to identify all sockets for a given user:
diff --git a/web/controllers/auth_controller.ex b/web/controllers/auth_controller.ex
--- a/web/controllers/auth_controller.ex
+++ b/web/controllers/auth_controller.ex
@@ -1,8 +1,6 @@
defmodule KolabChat.AuthController do
use KolabChat.Web, :controller
- alias KolabChat.User
-
@doc """
Handler for the default logon form
"""
diff --git a/web/controllers/chat_controller.ex b/web/controllers/chat_controller.ex
new file mode 100644
--- /dev/null
+++ b/web/controllers/chat_controller.ex
@@ -0,0 +1,11 @@
+defmodule KolabChat.ChatController do
+ use KolabChat.Web, :controller
+
+ plug :put_layout, "chat.html"
+
+ def index(conn, %{"room" => room} = _params) do
+ conn
+ |> assign(:room, room)
+ |> render("index.html")
+ end
+end
diff --git a/web/controllers/plugs/set_user.ex b/web/controllers/plugs/set_user.ex
--- a/web/controllers/plugs/set_user.ex
+++ b/web/controllers/plugs/set_user.ex
@@ -6,6 +6,17 @@
def init(params), do: params
+ # token authentication
+ def call(%{"params": %{"token" => token}} = conn, _params) do
+ case Phoenix.Token.verify(conn, "user", token, max_age: 86400) do
+ {:ok, user_id} ->
+ assign(conn, :user, Repo.get!(User, user_id))
+ _ ->
+ assign(conn, :user, nil)
+ end
+ end
+
+ # session authentication
def call(conn, _params) do
user_id = get_session(conn, :user_id)
diff --git a/web/router.ex b/web/router.ex
--- a/web/router.ex
+++ b/web/router.ex
@@ -16,11 +16,18 @@
end
scope "/", KolabChat do
- pipe_through :browser # Use the default browser stack
+ pipe_through :browser
get "/", PageController, :index
end
+ scope "/chat", KolabChat do
+ pipe_through :browser
+
+ get "/", ChatController, :index
+ get "/:room", ChatController, :index
+ end
+
scope "/auth", KolabChat do
pipe_through :browser
diff --git a/web/static/css/app.css b/web/static/css/app.css
--- a/web/static/css/app.css
+++ b/web/static/css/app.css
@@ -11,3 +11,23 @@
display: block;
margin-bottom: 3px;
}
+
+ul.userlist li {
+ text-align: left;
+}
+
+ul.userlist li small {
+ color: #666;
+}
+
+#chat_txt {
+ height: 300px;
+ width: 100%;
+ border: 1px solid #ccc;
+ border-radius: 3px;
+ margin-bottom: 5px;
+}
+
+#chat_txt_input {
+ width: 100%;
+}
diff --git a/web/static/css/widgets.css b/web/static/css/widgets.css
new file mode 100644
--- /dev/null
+++ b/web/static/css/widgets.css
@@ -0,0 +1,26 @@
+/* Style for chat application widgets */
+
+.status-online .glyphicon-user {
+ color: green;
+}
+
+.status-away .glyphicon-user {
+ color: grey;
+}
+
+.status-busy .glyphicon-user {
+ color: red;
+}
+
+.userlist .btn-group {
+ float: right;
+}
+
+.textchat {
+ overflow-x: hidden;
+ overflow-y: scroll;
+}
+
+.textchat p {
+ margin: 0;
+}
diff --git a/web/static/js/api.js b/web/static/js/api.js
new file mode 100644
--- /dev/null
+++ b/web/static/js/api.js
@@ -0,0 +1,198 @@
+
+import {Socket, LongPoll, Presence} from "phoenix"
+import UserListWidget from "./widgets/userlist"
+import UserStatusWidget from "./widgets/userstatus"
+import ChatInputWidget from "./widgets/chatinput"
+import ChatRoomWidget from "./widgets/chatroom"
+
+class KolabChat
+{
+ /**
+ * Configuration parameters:
+ * - token: User session token
+ * - roomId: Chat room Id to join in
+ * - userListElement: Id of HTML element where to put userslist widget
+ * - userStatusElement: Id of HTML element where to put users status widget
+ * - chatInputElement: Id of HTML element which is a text chat input
+ * - chatRoomElement: Id of HTML element where to put text conversation
+ */
+ constructor(config)
+ {
+ this.config = config || {}
+ }
+
+ /**
+ * Initialize WebSocket communication
+ */
+ init(config)
+ {
+ if (config)
+ this.config = KolabChat.extend(this.config, config)
+
+ this.initWidgets()
+
+ // TODO: for integration with external systems we'll use configurable full wss:// url
+
+ this.socket = new Socket("/socket", {
+ params: {token: this.config.token},
+ logger: ((kind, msg, data) => { console.log(`${kind}: ${msg}`, data) }),
+ })
+
+ this.socket.onOpen(e => {
+ // when connected start using 'system' channel
+ // for users' presence
+ this.initPresence()
+ if (this.config.roomId) {
+ this.initRoom(this.config.roomId)
+ }
+ })
+
+ this.socket.connect()
+ }
+
+ /**
+ * Initializes configured UI widgets
+ */
+ initWidgets()
+ {
+ let config
+
+ if (this.config.userListElement && $('#' + this.config.userListElement).length) {
+ config = {
+ username: this.username,
+ openChat: (e, user) => { this.openChat(e, user) }
+ }
+ this.userListWidget = new UserListWidget(this.config.userListElement, config)
+ }
+
+ if (this.config.userStatusElement && $('#' + this.config.userStatusElement).length) {
+ config = {
+ username: this.username,
+ statusChange: status => { this.setStatus(status) }
+ }
+ this.userStatusWidget = new UserStatusWidget(this.config.userStatusElement, config)
+ }
+
+ if (this.config.chatRoomElement && $('#' + this.config.chatRoomElement).length) {
+ this.chatRoomWidget = new ChatRoomWidget(this.config.chatRoomElement)
+ }
+
+ if (this.config.chatInputElement && $('#' + this.config.chatInputElement).length) {
+ config = {
+ submit: (e, msg) => { this.sendTxtMessage(e, msg) }
+ }
+ this.chatInputWidget = new ChatInputWidget(this.config.chatInputElement, config)
+ }
+ }
+
+ /**
+ * Initialize user presence
+ * Create users list and status widgets
+ */
+ initPresence()
+ {
+ this.system = this.socket.channel("system")
+
+ this.system.on("info", info => {
+ this.username = info.user
+ })
+
+ this.system.on("presence_state", state => {
+ this.presences = Presence.syncState({}, state)
+ this.renderPresences(this.presences)
+ })
+
+ this.system.on("presence_diff", diff => {
+ // ignore initial presence_diff result, handle presence_state first
+ if (this.presences !== undefined) {
+ this.presences = Presence.syncDiff(this.presences, diff)
+ this.renderPresences(this.presences)
+ }
+ })
+
+ this.system.join()
+ }
+
+ /**
+ * Initialize chat channel
+ */
+ initRoom(roomId)
+ {
+ this.chat = this.socket.channel("room:" + roomId)
+
+ this.chat.on("new:message", message => {
+ this.chatRoomWidget.append(message.user, message.body)
+ })
+
+ this.chat.join()
+ }
+
+ /**
+ * Send text message to the chat room
+ */
+ sendTxtMessage(event, message)
+ {
+ this.chat.push("new:message", {
+ user: this.username, // TODO: this is not really needed
+ body: message
+ })
+ }
+
+ /**
+ * Handler for presence responses
+ */
+ renderPresences(presences)
+ {
+ let userPresence
+
+ if (this.userStatusWidget && (userPresence = presences[this.username])) {
+ userPresence = this.listBy(this.username, userPresence)
+ this.userStatusWidget.render(userPresence)
+ }
+
+ if (this.userListWidget) {
+ presences = Presence.list(presences, this.listBy)
+ this.userListWidget.render(presences)
+ }
+ }
+
+ listBy(user, {metas: metas})
+ {
+ return {
+ user: user,
+ status: metas[0].status
+ }
+ }
+
+ /**
+ * User status change
+ */
+ setStatus(status)
+ {
+ this.system.push('set-status', {status: status});
+ }
+
+ /**
+ * Open chat window (and create a new chat room)
+ */
+ openChat(event, user)
+ {
+ // TODO: Use 'system' channel to create a chat room first
+ let roomId = "lobby"
+
+ let windowName = 'KolabChat' + new Date().getTime()
+ let url = "/chat/" + encodeURIComponent(roomId)
+ + "/?token=" + encodeURIComponent(this.config.token)
+
+ var extwin = window.open(url, windowName);
+ }
+
+
+ static extend(obj, src)
+ {
+ Object.keys(src).forEach(function(key) { obj[key] = src[key] })
+ return obj
+ }
+}
+
+export default KolabChat
diff --git a/web/static/js/app.js b/web/static/js/app.js
--- a/web/static/js/app.js
+++ b/web/static/js/app.js
@@ -18,4 +18,20 @@
// Local files can be imported directly using relative
// paths "./socket" or full ones "web/static/js/socket".
-// import socket from "./socket"
+import KolabChat from "./api"
+
+// Initialize KolabChat API object
+let chat = new KolabChat({
+ userListElement: "userlist",
+ userStatusElement: "userstatus",
+ chatRoomElement: "chat_txt",
+ chatInputElement: "chat_txt_input"
+})
+
+// If user is authenticated start the app
+if (window.userToken) {
+ chat.init({
+ token: window.userToken,
+ roomId: window.roomId
+ })
+}
diff --git a/web/static/js/socket.js b/web/static/js/socket.js
deleted file mode 100644
--- a/web/static/js/socket.js
+++ /dev/null
@@ -1,62 +0,0 @@
-// NOTE: The contents of this file will only be executed if
-// you uncomment its entry in "web/static/js/app.js".
-
-// To use Phoenix channels, the first step is to import Socket
-// and connect at the socket path in "lib/my_app/endpoint.ex":
-import {Socket} from "phoenix"
-
-let socket = new Socket("/socket", {params: {token: window.userToken}})
-
-// When you connect, you'll often need to authenticate the client.
-// For example, imagine you have an authentication plug, `MyAuth`,
-// which authenticates the session and assigns a `:current_user`.
-// If the current user exists you can assign the user's token in
-// the connection for use in the layout.
-//
-// In your "web/router.ex":
-//
-// pipeline :browser do
-// ...
-// plug MyAuth
-// plug :put_user_token
-// end
-//
-// defp put_user_token(conn, _) do
-// if current_user = conn.assigns[:current_user] do
-// token = Phoenix.Token.sign(conn, "user socket", current_user.id)
-// assign(conn, :user_token, token)
-// else
-// conn
-// end
-// end
-//
-// Now you need to pass this token to JavaScript. You can do so
-// inside a script tag in "web/templates/layout/app.html.eex":
-//
-// <script>window.userToken = "<%= assigns[:user_token] %>";</script>
-//
-// You will need to verify the user token in the "connect/2" function
-// in "web/channels/user_socket.ex":
-//
-// def connect(%{"token" => token}, socket) do
-// # max_age: 1209600 is equivalent to two weeks in seconds
-// case Phoenix.Token.verify(socket, "user socket", token, max_age: 1209600) do
-// {:ok, user_id} ->
-// {:ok, assign(socket, :user, user_id)}
-// {:error, reason} ->
-// :error
-// end
-// end
-//
-// Finally, pass the token on connect as below. Or remove it
-// from connect if you don't care about authentication.
-
-socket.connect()
-
-// Now that you are connected, you can join channels with a topic:
-let channel = socket.channel("topic:subtopic", {})
-channel.join()
- .receive("ok", resp => { console.log("Joined successfully", resp) })
- .receive("error", resp => { console.log("Unable to join", resp) })
-
-export default socket
diff --git a/web/static/js/widgets/chatinput.js b/web/static/js/widgets/chatinput.js
new file mode 100644
--- /dev/null
+++ b/web/static/js/widgets/chatinput.js
@@ -0,0 +1,36 @@
+
+class ChatInputWidget
+{
+ /**
+ * Configuration:
+ * - submit: handler function for submit the text to a chat room
+ */
+ constructor(id, config)
+ {
+ this.config = config || {}
+ this.id = id
+ this.render()
+ }
+
+ /**
+ * Renders text chat input widget
+ */
+ render()
+ {
+ let icon = '<span class="glyphicon glyphicon-comment"></span>'
+ let html = `<span class="input-group-addon">${icon}</span>
+ <input id="chat_txt_input" name="txt_input" value="" class="form-control" autofocus=true />`
+
+ $('#' + this.id)
+ .html(html)
+ .on('keypress', 'input', e => {
+ let txt
+ if (e.keyCode == 13 && this.config.submit && (txt = $(e.target).val())) {
+ this.config.submit(e, txt)
+ $(e.target).val('')
+ }
+ })
+ }
+}
+
+export default ChatInputWidget
diff --git a/web/static/js/widgets/chatroom.js b/web/static/js/widgets/chatroom.js
new file mode 100644
--- /dev/null
+++ b/web/static/js/widgets/chatroom.js
@@ -0,0 +1,35 @@
+
+class ChatRoomWidget
+{
+ constructor(id, config)
+ {
+ this.config = config || {}
+ this.id = id
+ this.render()
+ }
+
+ /**
+ * Renders text chat room widget
+ */
+ render()
+ {
+ }
+
+ /**
+ * Appends text message to the chat room widget
+ */
+ append(user, text)
+ {
+ user = ChatRoomWidget.sanitize(user)
+ text = ChatRoomWidget.sanitize(text)
+
+ $("#" + this.id).append(`<p><b>[${user}]</b>: ${text}</p>`)
+ }
+
+ static sanitize(str)
+ {
+ return $("<div/>").text(str).html()
+ }
+}
+
+export default ChatRoomWidget
diff --git a/web/static/js/widgets/userlist.js b/web/static/js/widgets/userlist.js
new file mode 100644
--- /dev/null
+++ b/web/static/js/widgets/userlist.js
@@ -0,0 +1,62 @@
+
+class UserListWidget
+{
+ /**
+ * Configuration:
+ * - username: Current user name
+ * - openChat: callback for "Open chat" button
+ */
+ constructor(id, config)
+ {
+ this.config = config || {}
+ this.id = id
+ }
+
+ /**
+ * Render users list
+ */
+ render(presences)
+ {
+ let list = $('#' + this.id)
+ let config = this.config
+ let html = presences.map(presence => {
+ let buttons = this.buttons(presence)
+ return `
+ <li class="list-group-item status-${presence.status}" data-user="${presence.user}">
+ <span class="glyphicon glyphicon-user"></span> ${presence.user}
+ ${buttons}
+ </li>`
+ })
+ .join("")
+
+ list.html(html)
+
+ $('button', list).on('click', function(e) {
+ let action = $(this).data('action')
+ if (action && config[action]) {
+ config[action](e, $(this).parents('li').data('user'))
+ }
+ })
+ }
+
+ /**
+ * Render users list record buttons
+ */
+ buttons(presence)
+ {
+ let buttons = ''
+
+ if (this.config.openChat) { // && presence.user != this.config.username) {
+ let btn_name = '<span class="glyphicon glyphicon-comment"></span> Open chat'
+ buttons += `<button type="button" class="btn btn-primary btn-xs" data-action="openChat">${btn_name}</button>`
+ }
+
+ if (buttons) {
+ buttons = '<div class="btn-group">' + buttons + '</div>'
+ }
+
+ return buttons
+ }
+}
+
+export default UserListWidget
diff --git a/web/static/js/widgets/userstatus.js b/web/static/js/widgets/userstatus.js
new file mode 100644
--- /dev/null
+++ b/web/static/js/widgets/userstatus.js
@@ -0,0 +1,47 @@
+
+class UserStatusWidget
+{
+ /**
+ * Configuration:
+ * - statusChange: handler function for status change
+ */
+ constructor(id, config)
+ {
+ this.config = config || {}
+ this.id = id
+ }
+
+ /**
+ * Renders user status widget
+ */
+ render(presence)
+ {
+ let userStatusElement = document.getElementById(this.id)
+ let icon = '<span class="glyphicon glyphicon-user"></span>'
+
+ userStatusElement.innerHTML = `
+ <div class="dropdown">
+ <button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
+ <span class="status-${presence.status}">
+ ${icon} ${presence.user}
+ </span>
+ <span class="caret"></span>
+ </button>
+ <ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
+ <li><a href="#" class="status-online">${icon} Online</a></li>
+ <li><a href="#" class="status-away">${icon} Away</a></li>
+ <li><a href="#" class="status-busy">${icon} Busy</a></li>
+ </ul>
+ </div>
+ `
+
+ $('.dropdown-menu > li', userStatusElement).click(e => {
+ let status_class = $(e.target).attr('class')
+ if (this.config.statusChange && !$('button > span:first', userStatusElement).hasClass(status_class)) {
+ this.config.statusChange(status_class.replace(/^status-/, ''))
+ }
+ })
+ }
+}
+
+export default UserStatusWidget
diff --git a/web/templates/chat/index.html.eex b/web/templates/chat/index.html.eex
new file mode 100644
--- /dev/null
+++ b/web/templates/chat/index.html.eex
@@ -0,0 +1,2 @@
+<div id="chat_txt" class="textchat"></div>
+<div id="chat_txt_input" class="textchatinput input-group"></div>
diff --git a/web/templates/layout/app.html.eex b/web/templates/layout/app.html.eex
--- a/web/templates/layout/app.html.eex
+++ b/web/templates/layout/app.html.eex
@@ -18,8 +18,10 @@
<nav role="navigation">
<ul class="nav nav-pills pull-right">
<li><%= link gettext("Logout"), to: "/auth/logout" %></li>
+ <li id="userstatus"></li>
</ul>
</nav>
+ <script>window.userToken = "<%= Phoenix.Token.sign(@conn, "user", @conn.assigns[:user].id) %>"</script>
<% else %>
<%= form_for @conn, "/auth/default/callback", [as: :logon], fn f -> %>
<%= text_input f, :username, placeholder: gettext("Username") %>
diff --git a/web/templates/layout/chat.html.eex b/web/templates/layout/chat.html.eex
new file mode 100644
--- /dev/null
+++ b/web/templates/layout/chat.html.eex
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <meta name="description" content="">
+ <meta name="author" content="">
+
+ <title><%= gettext "Kolab Real Time Communication" %></title>
+ <link rel="stylesheet" href="<%= static_path(@conn, "/css/app.css") %>">
+ </head>
+
+ <body>
+ <div class="container chat">
+ <main role="main">
+ <%= render @view_module, @view_template, assigns %>
+ </main>
+ </div> <!-- /container -->
+ <%= if @conn.assigns[:user] do %>
+ <script>window.userToken = "<%= Phoenix.Token.sign(@conn, "user", @conn.assigns[:user].id) %>"</script>
+ <script>window.roomId = "<%= @conn.assigns[:room] %>"</script>
+ <% end %>
+ <script src="<%= static_path(@conn, "/js/app.js") %>"></script>
+ </body>
+</html>
diff --git a/web/templates/page/index.html.eex b/web/templates/page/index.html.eex
--- a/web/templates/page/index.html.eex
+++ b/web/templates/page/index.html.eex
@@ -1,4 +1,9 @@
<div class="jumbotron">
- <h2><%= gettext "Welcome to %{name}", name: "Kolab Chat!" %></h2>
- <p class="lead"><%= gettext "Real-time communication for the Kolab groupware system." %></p>
+ <%= if @conn.assigns[:user] do %>
+ Users List:
+ <ul id="userlist" class="userlist list-group"></ul>
+ <% else %>
+ <h2><%= gettext "Welcome to %{name}", name: "Kolab Chat!" %></h2>
+ <p class="lead"><%= gettext "Real-time communication for the Kolab groupware system." %></p>
+ <% end %>
</div>
diff --git a/web/views/chat_view.ex b/web/views/chat_view.ex
new file mode 100644
--- /dev/null
+++ b/web/views/chat_view.ex
@@ -0,0 +1,3 @@
+defmodule KolabChat.ChatView do
+ use KolabChat.Web, :view
+end
diff --git a/web/web.ex b/web/web.ex
--- a/web/web.ex
+++ b/web/web.ex
@@ -31,6 +31,8 @@
use Phoenix.Controller
alias KolabChat.Repo
+ alias KolabChat.User
+
import Ecto
import Ecto.Query

File Metadata

Mime Type
text/plain
Expires
Mon, Apr 6, 1:46 PM (6 h, 29 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18827284
Default Alt Text
D351.1775483204.diff (26 KB)

Event Timeline