Page MenuHomePhorge

D443.1775218530.diff
No OneTemporary

Authored By
Unknown
Size
26 KB
Referenced Files
None
Subscribers
None

D443.1775218530.diff

diff --git a/assets/js/api.js b/assets/js/api.js
--- a/assets/js/api.js
+++ b/assets/js/api.js
@@ -2,8 +2,10 @@
import {Socket, LongPoll, Presence} from "phoenix"
import UserListWidget from "./widgets/userlist"
import UserStatusWidget from "./widgets/userstatus"
+import ChannelListWidget from "./widgets/channellist"
import ChatInputWidget from "./widgets/chatinput"
import ChatRoomWidget from "./widgets/chatroom"
+import ChatDetailsWidget from "./widgets/chatdetails"
class KolabChat
{
@@ -16,6 +18,8 @@
* - 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
+ * - chatDetailsElement: Id of HTML element where to put chat room details widget
+ * - channelListElement: Id of HTML element where to put channellist widget
*/
constructor(config)
{
@@ -64,6 +68,7 @@
if (this.config.userListElement && $('#' + this.config.userListElement).length) {
config = {
username: this.username,
+ title: true,
openChat: (e, user) => { this.openChat(e, user) }
}
this.userListWidget = new UserListWidget(this.config.userListElement, config)
@@ -77,6 +82,23 @@
this.userStatusWidget = new UserStatusWidget(this.config.userStatusElement, config)
}
+ if (this.config.channelListElement && $('#' + this.config.channelListElement).length) {
+ config = {
+ username: this.username,
+ title: true,
+ openChat: (e, roomId) => { this.openExistingChat(roomId) },
+ createChat: (e) => { this.openChat(e); }
+ }
+ this.channelListWidget = new ChannelListWidget(this.config.channelListElement, config)
+ }
+
+ if (this.config.chatDetailsElement && $('#' + this.config.chatDetailsElement).length) {
+ config = {
+ submit: (e, data) => { this.updateChatDetails(data); }
+ }
+ this.chatDetailsWidget = new ChatDetailsWidget(this.config.chatDetailsElement, config)
+ }
+
if (this.config.chatRoomElement && $('#' + this.config.chatRoomElement).length) {
this.chatRoomWidget = new ChatRoomWidget(this.config.chatRoomElement)
}
@@ -131,7 +153,8 @@
{
let channelName = roomId.startsWith("room:") ? roomId
: "room:" + roomId
- this.chat = this.socket.channel(channelName)
+
+ this.chat = this.socket.channel(channelName, {context: this.config.context})
if (this.chatRoomWidget) {
this.chat.on("new:message", message => {
@@ -139,6 +162,12 @@
})
}
+ if (this.chatDetailsWidget) {
+ this.chat.on("info", message => {
+ this.chatDetailsWidget.setRoom(message)
+ })
+ }
+
let join = this.chat.join()
if (invitees) {
@@ -151,7 +180,7 @@
*/
initNotifications(userId)
{
- this.notifications = this.socket.channel("user:" + userId)
+ this.notifications = this.socket.channel("user:" + userId, {context: this.config.context})
this.notifications.on("notify:invite", message => {
console.log("Invite from " + message.user + " to room " + message.room)
@@ -231,7 +260,11 @@
openChat(event, user)
{
let windowName = 'KolabChat' + new Date().getTime()
- let url = "/chat/?token=" + encodeURIComponent(this.config.token) + "&invite=" + encodeURIComponent(user)
+ let url = "/chat/?token=" + encodeURIComponent(this.config.token)
+
+ if (user) {
+ url += "&invite=" + encodeURIComponent(user)
+ }
var extwin = window.open(url, windowName);
}
@@ -248,6 +281,14 @@
var extwin = window.open(url, windowName);
}
+ /**
+ * Update chat room details (e.g. name)
+ */
+ updateChatDetails(data)
+ {
+ // TODO
+ }
+
static extend(obj, src)
{
Object.keys(src).forEach(function(key) { obj[key] = src[key] })
diff --git a/assets/js/app.js b/assets/js/app.js
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -24,7 +24,9 @@
let chat = new KolabChat({
userListElement: "userlist",
userStatusElement: "userstatus",
+ channelListElement: "channellist",
chatRoomElement: "chat_txt",
+ chatDetailsElement: "chat_details",
chatInputElement: "chat_txt_input"
})
diff --git a/assets/js/widgets/channellist.js b/assets/js/widgets/channellist.js
new file mode 100644
--- /dev/null
+++ b/assets/js/widgets/channellist.js
@@ -0,0 +1,87 @@
+
+class ChannelListWidget
+{
+ /**
+ * Configuration:
+ * - username: Current user name
+ * - openChat: Callback for "Open" button
+ * - createChat: Callback for "Create" button
+ * - title: Enable list header with Create button
+ */
+ constructor(id, config)
+ {
+ this.config = config || {}
+ this.id = id
+ this.render([])
+ }
+
+ setUser(username, userId)
+ {
+ this.config.username = username
+ this.config.userId = userId
+ }
+
+
+ /**
+ * Render channels list
+ */
+ render(channels)
+ {
+ let title = ''
+ let btn = ''
+ let list = $('#' + this.id)
+ let config = this.config
+ let html = channels.map(channel => {
+ // TODO: List only public channels and channels the user has been invited to
+ let buttons = this.buttons(channel)
+ return `
+ <li class="list-group-item" data-channel="${channel.id}">
+ <span class="glyphicon glyphicon-channel"></span> #${channel.name}
+ ${buttons}
+ </li>`
+ })
+ .join("")
+
+ if (config.title === true) {
+ if (config.createChat) {
+ btn = `
+ <button type="button" class="btn btn-primary btn-xs" data-action="createChat">
+ <span class="glyphicon glyphicon-plus-sign"></span> Create
+ </button>
+ `
+ }
+
+ title = `<div class="channellist-head">Channels ${btn}</div>` // TODO: Localization
+ }
+
+ list.html(title + '<ul class="channellist list-group">' + html + '</ul>')
+
+ $('button', list).on('click', function(e) {
+ let action = $(this).data('action')
+ if (action && config[action]) {
+ config[action](e, $(this).parents('li').data('channel'))
+ }
+ })
+ }
+
+ /**
+ * Render channel list record buttons
+ */
+ buttons(channel)
+ {
+ let buttons = ''
+
+ if (this.config.openChat) {
+ let btn_name = '<span class="glyphicon glyphicon-comment"></span> Open' // TODO: localization
+ 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 ChannelListWidget
diff --git a/assets/js/widgets/chatdetails.js b/assets/js/widgets/chatdetails.js
new file mode 100644
--- /dev/null
+++ b/assets/js/widgets/chatdetails.js
@@ -0,0 +1,51 @@
+
+class ChatDetailsWidget
+{
+ /**
+ * Configuration:
+ * - submit: Callback for chat details update (only name for now)
+ */
+ constructor(id, config)
+ {
+ this.config = config || {}
+ this.id = id
+ this.render()
+ }
+
+ /**
+ * Set chat room details and re-render the widget
+ */
+ setRoom(room)
+ {
+ this.render(room)
+ }
+
+ /**
+ * Renders text chat input widget
+ */
+ render(room)
+ {
+ if (!room || !room.id) {
+ return $('#' + this.id).html('')
+ }
+
+ // TODO: Localization
+
+ let room_name = room.name || '';
+ let html = `
+ <div>Room id: ${room.id}</div>
+ <div>Room name: <input name="chat_name" value="${room_name}" class="form-control" /></div>
+ `
+
+ $('#' + 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)
+ }
+ })
+ }
+}
+
+export default ChatDetailsWidget
diff --git a/assets/js/widgets/userlist.js b/assets/js/widgets/userlist.js
--- a/assets/js/widgets/userlist.js
+++ b/assets/js/widgets/userlist.js
@@ -4,12 +4,14 @@
/**
* Configuration:
* - username: Current user name
- * - openChat: callback for "Open chat" button
+ * - openChat: Callback for "Open chat" button
+ * - title: Enables list header
*/
constructor(id, config)
{
this.config = config || {}
this.id = id
+ this.render([])
}
setUser(username, userId)
@@ -24,6 +26,7 @@
*/
render(presences)
{
+ let title = ''
let list = $('#' + this.id)
let config = this.config
let html = presences.map(presence => {
@@ -38,7 +41,11 @@
})
.join("")
- list.html(html)
+ if (config.title === true) {
+ title = '<div class="userlist-head">Users</div>' // TODO: Localization
+ }
+
+ list.html(title + '<ul class="userlist list-group">' + html + '</ul>')
$('button', list).on('click', function(e) {
let action = $(this).data('action')
@@ -56,7 +63,7 @@
let buttons = ''
if (this.config.openChat) {
- let btn_name = '<span class="glyphicon glyphicon-comment"></span> Open chat'
+ let btn_name = '<span class="glyphicon glyphicon-comment"></span> Open chat' // TODO: Localization
buttons += `<button type="button" class="btn btn-primary btn-xs" data-action="openChat">${btn_name}</button>`
}
diff --git a/assets/js/widgets/userstatus.js b/assets/js/widgets/userstatus.js
--- a/assets/js/widgets/userstatus.js
+++ b/assets/js/widgets/userstatus.js
@@ -22,6 +22,8 @@
"invisible",
"offline"
];
+
+ // TODO: render() with defalt state here
}
/**
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.Web.Endpoint, []),
# Start phoenix presence module
supervisor(KolabChat.Web.Presence, []),
+ # Start Rooms database
+ worker(KolabChat.Rooms, []),
# Start your own worker by calling: KolabChat.Worker.start_link(arg1, arg2, arg3)
# worker(KolabChat.Worker, [arg1, arg2, arg3]),
]
diff --git a/lib/kolab_chat/rooms.ex b/lib/kolab_chat/rooms.ex
new file mode 100644
--- /dev/null
+++ b/lib/kolab_chat/rooms.ex
@@ -0,0 +1,59 @@
+defmodule KolabChat.Rooms do
+ use GenServer
+
+ def get(roomId) do
+ GenServer.call(__MODULE__, {:get, roomId})
+ end
+
+ def find(room) do
+ GenServer.call(__MODULE__, {:find, room})
+ end
+
+ def set(roomId, room) do
+ GenServer.call(__MODULE__, {:set, roomId, room})
+ end
+
+ def start_link do
+ :ets.new(:rooms, [:set, :named_table, :public])
+ GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
+ end
+
+ def handle_call({:get, roomId}, _from, state) do
+ room =
+ case lookup_rooms(roomId) do
+ nil -> nil
+ room -> Map.put(room, :id, roomId)
+ end
+
+ {:reply, room, state}
+ end
+
+ # Finding a room by any attribute value, supported: id, name
+ def handle_call({:find, room}, _from, state) do
+ has_id = Map.has_key?(room, :id)
+ room =
+ case has_id and lookup_rooms(room.id) do
+ # TODO: searching by name alias
+ nil -> nil
+ metadata -> Map.merge(room, metadata)
+ end
+
+ {:reply, room, state}
+ end
+
+ def handle_call({:set, roomId, metadata}, _from, state) do
+ metadata = lookup_rooms(roomId, %{})
+ |> Map.merge(metadata)
+ |> Map.delete(:id)
+
+ :ets.insert(:rooms, {roomId, metadata})
+ {:reply, :ok, state}
+ end
+
+ defp lookup_rooms(roomId, default \\ nil) do
+ case :ets.lookup(:rooms, roomId) do
+ [{_, room}] -> room
+ _ -> default
+ end
+ end
+end
diff --git a/lib/kolab_chat/web/channels/presence.ex b/lib/kolab_chat/web/channels/presence.ex
--- a/lib/kolab_chat/web/channels/presence.ex
+++ b/lib/kolab_chat/web/channels/presence.ex
@@ -74,4 +74,83 @@
"""
use Phoenix.Presence, otp_app: :kolab_chat,
pubsub_server: KolabChat.PubSub
+
+ require Amnesia
+ require Amnesia.Helper
+
+ alias KolabChat.Database
+
+ @status [
+ # user is available for chat
+ :online,
+ :away,
+ # user is connected and visible, but not available
+ :busy,
+ :unavailable,
+ # user is shown as offline
+ :invisible,
+ :offline
+ ]
+
+ def track_presence(socket) do
+ track(socket, socket.assigns.user.id, %{
+ username: socket.assigns.user.username,
+ status: get_status(socket),
+ context: socket.assigns.context
+ })
+ end
+
+ def update_status(socket, status) do
+ case check_status(status) do
+ :invalid ->
+ socket
+ status ->
+ update(socket, socket.assigns.user.id, %{
+ username: socket.assigns.user.username,
+ status: status,
+ context: socket.assigns.context
+ })
+ socket
+ end
+ end
+ # Get the last user/context status from the database
+ def get_status(socket) do
+ key = Integer.to_string(socket.assigns.user.id) <> ":" <> socket.assigns.context
+
+ Amnesia.transaction do
+ case Database.Status.read(key) do
+ # use last status
+ %Database.Status{status: status} -> status
+ # otherwise set status to online
+ _ -> :online
+ end
+ end
+ end
+
+ # Save the current user/context status to the database
+ def set_status(socket, status) do
+ case check_status(status) do
+ :invalid ->
+ socket
+ status ->
+ key = Integer.to_string(socket.assigns.user.id) <> ":" <> socket.assigns.context
+ Amnesia.transaction do
+ Database.Status.write(%Database.Status{key: key, status: status})
+ end
+ socket
+ end
+ end
+
+ # Makes sure the provided status name is supported
+ # Returns status name as an atom
+ defp check_status(status) do
+ status = String.to_atom(status)
+
+ if Enum.member?(@status, status) do
+ status
+ else
+ :invalid
+ end
+ end
+
end
diff --git a/lib/kolab_chat/web/channels/room_channel.ex b/lib/kolab_chat/web/channels/room_channel.ex
--- a/lib/kolab_chat/web/channels/room_channel.ex
+++ b/lib/kolab_chat/web/channels/room_channel.ex
@@ -2,9 +2,16 @@
use KolabChat.Web, :channel
alias KolabChat.Web.Endpoint
+ alias KolabChat.Rooms
@spec join(topic :: binary(), args :: map(), socket :: pid()) :: {:ok, socket :: pid()}
- def join("room:" <> room_name, _, socket) do
+ def join("room:" <> room_name, args, socket) do
+ room = Rooms.find(%{:id => room_name, :name => room_name})
+ socket = socket
+ |> assign(:room, room)
+ |> assign(:context, get_context(args))
+
+ send self(), :after_join
{:ok, socket}
end
@@ -27,4 +34,19 @@
def handle_in("ctl:invite", params, socket) do
{:noreply, socket}
end
+
+ @spec handle_info(:after_join, socket :: pid()) :: {:noreply, socket :: pid()}
+ def handle_info(:after_join, socket) do
+ push socket, "info", socket.assigns.room
+ # Presence.track_presence(socket)
+ {:noreply, socket}
+ end
+
+ defp get_context(args) do
+ case args do
+ %{:context => context} -> context
+ _ -> "default"
+ end
+ end
+
end
diff --git a/lib/kolab_chat/web/channels/system_channel.ex b/lib/kolab_chat/web/channels/system_channel.ex
--- a/lib/kolab_chat/web/channels/system_channel.ex
+++ b/lib/kolab_chat/web/channels/system_channel.ex
@@ -1,25 +1,9 @@
defmodule KolabChat.Web.SystemChannel do
use KolabChat.Web, :channel
- @status [
- # user is available for chat
- :online,
- :away,
- # user is connected and visible, but not available
- :busy,
- :unavailable,
- # user is shown as offline
- :invisible,
- :offline
- ]
-
@spec join(topic :: binary(), args :: map(), socket :: pid()) :: {:ok, socket :: pid()}
- def join("system", %{"context" => context}, socket) do
- perform_join(context, socket)
- end
-
- def join("system", _args, socket) do
- perform_join("default", socket)
+ def join("system", args, socket) do
+ perform_join(get_context(args), socket)
end
@spec handle_info(:after_join, socket :: pid()) :: {:noreply, socket :: pid()}
@@ -27,21 +11,16 @@
push socket, "presence_state", Presence.list(socket)
push socket, "info", %{user: socket.assigns.user.username, userId: socket.assigns.user.id}
- Presence.track(socket, socket.assigns.user.id, %{
- username: socket.assigns.user.username,
- status: get_user_status(socket),
- context: socket.assigns.context
- })
+ Presence.track_presence(socket)
{:noreply, socket}
end
@spec handle_in(topic :: binary, args :: map(), socket :: pid()) :: {:noreply, socket :: pid()}
def handle_in("set-status", %{"status" => status}, socket) do
- status = check_status(status)
socket
- |> update_presence_status(status)
- |> set_user_status(status)
+ |> Presence.update_status(status)
+ |> Presence.set_status(status)
{:noreply, socket}
end
@@ -53,58 +32,11 @@
{:ok, socket}
end
- defp update_presence_status(socket, :invalid), do: socket
- defp update_presence_status(socket, status) do
- Presence.update(socket, socket.assigns.user.id, %{
- username: socket.assigns.user.username,
- status: status,
- context: socket.assigns.context
- })
-
- socket
- end
-
- # Makes sure the provided status name is supported
- # Returns status name as an atom
- defp check_status(status) do
- status = String.to_atom(status)
-
- if Enum.member?(@status, status) do
- status
- else
- :invalid
+ defp get_context(args) do
+ case args do
+ %{:context => context} -> context
+ _ -> "default"
end
end
- # Get the last user/context status from the database
- defp get_user_status(socket) do
- require Amnesia
- require Amnesia.Helper
-
- key = Integer.to_string(socket.assigns.user.id) <> ":" <> socket.assigns.context
-
- Amnesia.transaction do
- case Database.Status.read(key) do
- # use last status
- %Database.Status{status: status} -> status
- # otherwise set status to online
- _ -> :online
- end
- end
- end
-
- # Save the current user/context status to the database
- defp set_user_status(socket, :invalid), do: socket
- defp set_user_status(socket, status) do
- require Amnesia
- require Amnesia.Helper
-
- key = Integer.to_string(socket.assigns.user.id) <> ":" <> socket.assigns.context
-
- Amnesia.transaction do
- Database.Status.write(%Database.Status{key: key, status: status})
- end
-
- socket
- end
end
diff --git a/lib/kolab_chat/web/controllers/chat_controller.ex b/lib/kolab_chat/web/controllers/chat_controller.ex
--- a/lib/kolab_chat/web/controllers/chat_controller.ex
+++ b/lib/kolab_chat/web/controllers/chat_controller.ex
@@ -1,9 +1,12 @@
defmodule KolabChat.Web.ChatController do
use KolabChat.Web, :controller
+ alias KolabChat.Rooms
+
plug :put_layout, "chat.html"
def index(conn, %{"room" => room} = params) do
+ metadata = Rooms.find(%{:id => room, :name => room})
create_room(conn, params, room)
end
@@ -11,9 +14,16 @@
create_room(conn, params, UUID.uuid4())
end
- defp create_room(conn, params, roomId) do
+ defp create_room(conn, params, roomId, room \\ %{}) do
+ room = %{
+ :id => roomId,
+ :creator => conn.assigns.user.id
+ }
+
+ Rooms.set(roomId, room)
+
conn
- |> assign(:room, roomId)
+ |> assign(:room, room)
|> assign_invitees(params)
|> render("index.html")
end
diff --git a/lib/kolab_chat/web/templates/chat/index.html.eex b/lib/kolab_chat/web/templates/chat/index.html.eex
--- a/lib/kolab_chat/web/templates/chat/index.html.eex
+++ b/lib/kolab_chat/web/templates/chat/index.html.eex
@@ -1,3 +1,3 @@
-Room id is: <%= @room %>
+<div id="chat_details"></div>
<div id="chat_txt" class="textchat"></div>
<div id="chat_txt_input" class="textchatinput input-group"></div>
diff --git a/lib/kolab_chat/web/templates/layout/chat.html.eex b/lib/kolab_chat/web/templates/layout/chat.html.eex
--- a/lib/kolab_chat/web/templates/layout/chat.html.eex
+++ b/lib/kolab_chat/web/templates/layout/chat.html.eex
@@ -19,7 +19,7 @@
</div> <!-- /container -->
<%= if @conn.assigns[:user] do %>
<script>window.userToken = "<%= Phoenix.Token.sign(@conn, "user", @conn.assigns[:user].id) %>"</script>
- <script>window.roomId = "<%= @room %>"</script>
+ <script>window.roomId = "<%= @room.id %>"</script>
<script>window.invitees =
<%= if @conn.assigns[:invitees] do %>
"<%= @invitees %>"
diff --git a/lib/kolab_chat/web/templates/page/index.html.eex b/lib/kolab_chat/web/templates/page/index.html.eex
--- a/lib/kolab_chat/web/templates/page/index.html.eex
+++ b/lib/kolab_chat/web/templates/page/index.html.eex
@@ -1,7 +1,7 @@
<div class="jumbotron">
<%= if @conn.assigns[:user] do %>
- Users List:
- <ul id="userlist" class="userlist list-group"></ul>
+ <div id="userlist"></div>
+ <div id="channellist"></div>
<% else %>
<h2><%= gettext "Welcome to %{name}", name: "Kolab Chat!" %></h2>
<p class="lead"><%= gettext "Real-time communication for the Kolab groupware system." %></p>
diff --git a/mix.lock b/mix.lock
--- a/mix.lock
+++ b/mix.lock
@@ -1,18 +1,18 @@
-%{"amnesia": {:hex, :amnesia, "0.2.7", "ffc2221bf72da4cfafbbb497adf9cf7e52138f1333cec5836187a53f94ae0665", [:mix], [{:exquisite, "~> 0.1.7", [hex: :exquisite, repo: "hexpm", optional: false]}], "hexpm"},
- "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
- "cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
- "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [], [], "hexpm"},
- "credo": {:hex, :credo, "0.7.4", "0c33bcce4d574ce6df163cbc7d1ecb22de65713184355bd3be81cc4ab0ecaafa", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}], "hexpm"},
- "exquisite": {:hex, :exquisite, "0.1.8", "ee8f56aae477287ce5e7dfcbc163a420cccbb73e680a6d80a09203e9ef514fa4", [], [], "hexpm"},
- "fs": {:hex, :fs, "0.9.2", "ed17036c26c3f70ac49781ed9220a50c36775c6ca2cf8182d123b6566e49ec59", [], [], "hexpm"},
- "gettext": {:hex, :gettext, "0.13.1", "5e0daf4e7636d771c4c71ad5f3f53ba09a9ae5c250e1ab9c42ba9edccc476263", [], [], "hexpm"},
- "mime": {:hex, :mime, "1.1.0", "01c1d6f4083d8aa5c7b8c246ade95139620ef8effb009edde934e0ec3b28090a", [:mix], [], "hexpm"},
- "phoenix": {:hex, :phoenix, "1.3.0-rc.2", "53104ada25ba85fe160268c0dc826fe038bc074293730b4522fb9aca28d8aa13", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.2 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
- "phoenix_html": {:hex, :phoenix_html, "2.9.3", "1b5a2122cbf743aa242f54dced8a4f1cc778b8bd304f4b4c0043a6250c58e258", [], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
- "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.0.8", "4333f9c74190f485a74866beff2f9304f069d53f047f5fbb0fb8d1ee4c495f73", [:mix], [{:fs, "~> 0.9.1", [hex: :fs, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.0 or ~> 1.2-rc", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm"},
- "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.1", "c10ddf6237007c804bf2b8f3c4d5b99009b42eca3a0dfac04ea2d8001186056a", [], [], "hexpm"},
- "plug": {:hex, :plug, "1.3.5", "7503bfcd7091df2a9761ef8cecea666d1f2cc454cbbaf0afa0b6e259203b7031", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"},
- "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
- "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"},
- "remix": {:hex, :remix, "0.0.2", "f06115659d8ede8d725fae1708920ef73353a1b39efe6a232d2a38b1f2902109", [:mix], [], "hexpm"},
- "uuid": {:hex, :uuid, "1.1.7", "007afd58273bc0bc7f849c3bdc763e2f8124e83b957e515368c498b641f7ab69", [:mix], [], "hexpm"}}
+%{"amnesia": {:hex, :amnesia, "0.2.7", "ffc2221bf72da4cfafbbb497adf9cf7e52138f1333cec5836187a53f94ae0665", [:mix], [{:exquisite, "~> 0.1.7", [hex: :exquisite, optional: false]}]},
+ "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], []},
+ "cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, optional: false]}]},
+ "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], []},
+ "credo": {:hex, :credo, "0.7.4", "0c33bcce4d574ce6df163cbc7d1ecb22de65713184355bd3be81cc4ab0ecaafa", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, optional: false]}]},
+ "exquisite": {:hex, :exquisite, "0.1.8", "ee8f56aae477287ce5e7dfcbc163a420cccbb73e680a6d80a09203e9ef514fa4", [:mix], []},
+ "fs": {:hex, :fs, "0.9.2", "ed17036c26c3f70ac49781ed9220a50c36775c6ca2cf8182d123b6566e49ec59", [:rebar], []},
+ "gettext": {:hex, :gettext, "0.13.1", "5e0daf4e7636d771c4c71ad5f3f53ba09a9ae5c250e1ab9c42ba9edccc476263", [:mix], []},
+ "mime": {:hex, :mime, "1.1.0", "01c1d6f4083d8aa5c7b8c246ade95139620ef8effb009edde934e0ec3b28090a", [:mix], []},
+ "phoenix": {:hex, :phoenix, "1.3.0-rc.2", "53104ada25ba85fe160268c0dc826fe038bc074293730b4522fb9aca28d8aa13", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, optional: false]}, {:plug, "~> 1.3.2 or ~> 1.4", [hex: :plug, optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, optional: false]}]},
+ "phoenix_html": {:hex, :phoenix_html, "2.9.3", "1b5a2122cbf743aa242f54dced8a4f1cc778b8bd304f4b4c0043a6250c58e258", [:mix], [{:plug, "~> 1.0", [hex: :plug, optional: false]}]},
+ "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.0.8", "4333f9c74190f485a74866beff2f9304f069d53f047f5fbb0fb8d1ee4c495f73", [:mix], [{:fs, "~> 0.9.1", [hex: :fs, optional: false]}, {:phoenix, "~> 1.0 or ~> 1.2-rc", [hex: :phoenix, optional: false]}]},
+ "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.1", "c10ddf6237007c804bf2b8f3c4d5b99009b42eca3a0dfac04ea2d8001186056a", [:mix], []},
+ "plug": {:hex, :plug, "1.3.5", "7503bfcd7091df2a9761ef8cecea666d1f2cc454cbbaf0afa0b6e259203b7031", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, optional: true]}, {:mime, "~> 1.0", [hex: :mime, optional: false]}]},
+ "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], []},
+ "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], []},
+ "remix": {:hex, :remix, "0.0.2", "f06115659d8ede8d725fae1708920ef73353a1b39efe6a232d2a38b1f2902109", [:mix], []},
+ "uuid": {:hex, :uuid, "1.1.7", "007afd58273bc0bc7f849c3bdc763e2f8124e83b957e515368c498b641f7ab69", [:mix], []}}

File Metadata

Mime Type
text/plain
Expires
Fri, Apr 3, 12:15 PM (48 m, 51 s ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18818730
Default Alt Text
D443.1775218530.diff (26 KB)

Event Timeline