Delete unused public dir

This commit is contained in:
James Allen 2016-12-15 15:30:00 +00:00
parent 457fdd7fd6
commit 2a1e82ce46
20 changed files with 0 additions and 39644 deletions

View file

@ -1,25 +0,0 @@
requirejs.config({
baseUrl: "./build",
out: "./build/chat.js",
inlineText:true,
preserveLicenseComments:false,
shim: {
"libs/underscore": {
init: function() {
return _.noConflict();
}
},
"libs/backbone": {
deps: ["libs/underscore"],
init: function() {
return Backbone.noConflict();
}
}
},
paths: {
"moment": "libs/moment",
},
name:"chat",
optimize: 'none',
skipDirOptimize: true
})

View file

@ -1,109 +0,0 @@
define [
"utils/staticLoader"
"libs/underscore"
"libs/backbone"
"libs/jquery.storage"
"models/room"
"models/user"
"views/chatWindowView"
], (staticLoader, _, Backbone, jqueryStorage, Room, User, ChatWindowView) ->
staticLoader.appendAssets()
_.templateSettings = escape : /\{\{(.+?)\}\}/g
class GlobalNotificationManager
constructor: (@chat) ->
@focussed = true
$(window).on "focus", () =>
@clearNewMessageNotification()
@focussed = true
$(window).on "blur", () => @focussed = false
@chat.on "joinedRoom", (room) =>
notifyIfAppropriate = (message) =>
if message.get("user") != @chat.user and !message.get("preloaded")
@notifyAboutNewMessage()
room.get("messages").on "add", notifyIfAppropriate
room.on "disconnect", () ->
room.get("messages").off "add", notifyIfAppropriate
notifyAboutNewMessage: () ->
if !@focussed and !@newMessageNotificationTimeout?
@originalTitle ||= window.document.title
do changeTitle = () =>
if window.document.title == @originalTitle
window.document.title = "New Message"
else
window.document.title = @originalTitle
@newMessageNotificationTimeout = setTimeout changeTitle, 800
clearNewMessageNotification: () ->
clearTimeout @newMessageNotificationTimeout
delete @newMessageNotificationTimeout
if @originalTitle?
window.document.title = @originalTitle
class Chat
constructor: (options) ->
_.extend(@, Backbone.Events)
window.chat = @
@rooms = {}
project_id = window.location.pathname.split( '/' )[2]
@socket = socket = io.connect options.url, {
resource: "chat/socket.io",
"force new connection": true
query:"project_id=#{project_id}"
}
@socket.on "connect", () =>
@connected = true
@getAuthToken (error, auth_token) =>
return @handleError(error) if error?
@socket.emit "auth", {auth_token: auth_token}, (error, user_info) =>
return @handleError(error) if error?
@user = User.findOrCreate(user_info)
@joinProjectRoom(options.room.project_id)
@trigger "authed"
@socket.on "disconnect", () =>
@connected = false
@trigger "disconnected"
@socket.on "messageReceived", (data) =>
@getRoom(data.message.room.id)?.onMessageReceived(data)
@socket.on "userJoined", (data) =>
@getRoom(data.room.id).addConnectedUser(data.user)
@socket.on "userLeft", (data) =>
@getRoom(data.room.id)?.removeConnectedUser(data.user)
@globalNotificationManager = new GlobalNotificationManager(@)
getRoom: (room_id) ->
@rooms[room_id]
joinProjectRoom: (project_id) ->
if !@room?
@room = new Room(
project_id: project_id
chat: @
)
@window = new ChatWindowView({
room: @room
chat: @
})
@room.on "joined", => @trigger("joinedRoom", @room)
getAuthToken: (callback = (error, auth_token) ->) ->
$.ajax "/user/auth_token", {
success: (data, status, xhr) ->
callback null, data
error: (xhr, status, error) ->
callback error
}
handleError: (error) ->
console.error error

View file

@ -1,9 +0,0 @@
define [
"libs/backbone"
"models/user"
], (Backbone, User) ->
ConnectedUsers = Backbone.Collection.extend
model: User
initialize: (models, options) ->
{@chat, @room} = options

View file

@ -1,45 +0,0 @@
define [
"libs/backbone"
"models/message"
"models/user"
], (Backbone, Message, User) ->
Messages = Backbone.Collection.extend
model: Message
initialize: (models, options) ->
{@chat, @room} = options
fetchMoreMessages: (options = { preloading: false }, callback = (error) ->) ->
limit = Messages.DEFAULT_MESSAGE_LIMIT
@room.fetchMessages @_buildMessagesQuery(limit), (error, messages) =>
if error?
callback(error)
return @chat.handleError(error)
if messages.length < limit
@trigger "noMoreMessages"
@_parseAndAddMessages(messages, options)
callback()
_parseAndAddMessages: (messages, options) ->
for message in messages
user = User.findOrCreate message.user
@add new Message(
content : message.content
timestamp : message.timestamp
user : user
preloaded : !!options.preloading
), at: 0
_buildMessagesQuery: (limit) ->
query =
limit: limit
firstMessage = @at(0)
if firstMessage?
query.before = firstMessage.get("timestamp")
return query
Messages.DEFAULT_MESSAGE_LIMIT = 50
return Messages

View file

@ -1,5 +0,0 @@
define [
"libs/backbone"
], (Backbone) ->
Message = Backbone.Model.extend {}

View file

@ -1,85 +0,0 @@
define [
"libs/underscore"
"libs/backbone"
"collections/messages"
"collections/connectedUsers"
"models/user"
"models/message"
], (_, Backbone, Messages, ConnectedUsers, User, Message) ->
Room = Backbone.Model.extend
initialize: () ->
@chat = @get("chat")
@set "messages", new Messages([], chat: @chat, room: @)
@set "connectedUsers", new ConnectedUsers([], chat: @chat, room: @)
@get("connectedUsers").on "change", () ->
@get("connectedUsers").on "add", () ->
@get("connectedUsers").on "remove", () ->
@connected = false
@chat.on "authed", () => @join()
@chat.on "disconnected", () => @_onDisconnect()
join: () ->
@chat.socket.emit "joinRoom", room: project_id: @get("project_id"), (error, data) =>
return @chat.handleError(error) if error?
room = data.room
@set("id", room.id)
@chat.rooms[room.id] = @
@addConnectedUsers(room.connectedUsers)
@_onJoin()
_onJoin: () ->
@trigger "joined"
@connected = true
if @get("messages").models.length == 0
@get("messages").fetchMoreMessages preloading: true, () =>
@trigger("afterMessagesPreloaded")
_onDisconnect: () ->
@trigger "disconnected"
@connected = false
addConnectedUsers: (users) ->
for user in users
@addConnectedUser(user)
addConnectedUser: (user) ->
if user not instanceof User
user = User.findOrCreate(user)
@get("connectedUsers").add user
removeConnectedUser: (user) ->
if user not instanceof User
user = User.findOrCreate(user)
@get("connectedUsers").remove user
sendMessage: (content, callback = (error) ->) ->
if !@connected
return callback(new Error("Not connected"))
@chat.socket.emit "sendMessage", {
message:
content: content
room:
id: @get("id")
}
fetchMessages: (query, callback = (error, messages) ->) ->
if !@connected
return callback(new Error("Not connected"))
query.room = id: @get("id")
@chat.socket.emit "getMessages", query, callback
onMessageReceived: (data) ->
message = data.message
user = User.findOrCreate message.user
message = new Message(
content : data.message.content
timestamp : data.message.timestamp
user : user
)
@get("messages").add message

View file

@ -1,13 +0,0 @@
define [
"libs/backbone"
], (Backbone, room) ->
User = Backbone.Model.extend {},
findOrCreate: (attributes) ->
User.cache ||= {}
if User.cache[attributes.id]?
return User.cache[attributes.id]
else
user = new User(attributes)
User.cache[attributes.id] = user
return user

View file

@ -1,10 +0,0 @@
define [
"text!html/templates.html"
"text!css/chat.css"
], (templates, css)->
appendAssets : ->
$(document.body).append($(templates))
style = $("<style/>")
style.html(css)
$(document.body).append(style)

View file

@ -1,219 +0,0 @@
define [
"libs/underscore"
"libs/backbone"
"views/userMessageBlockView"
"views/timeMessageBlockView"
], (_, Backbone, UserMessageBlockView, TimeMessageBlockView) ->
FIVE_MINS = 5 * 60 * 1000
ONE_HOUR = 60 * 60 * 1000
TWELVE_HOURS = ONE_HOUR * 12
ONE_DAY = ONE_HOUR * 24
ChatWindowView = Backbone.View.extend
events:
"keydown textarea" : "_onTextAreaKeyDown"
"click .js-load-older-messages" : "_loadOlderMessages"
"click .js-minimize-toggle" : "_toggleMinimizeState"
"click h3" : "_toggleMinimizeState"
"click .js-new-message-alert" : "_toggleMinimizeState"
"click" : "_removeNotification"
initialize: () ->
@template = $("#chatWindowTemplate").html()
@chat = @options.chat
@room = @options.room
@listenTo @room.get("messages"), "add", (model, collection) -> @_onNewMessage(model, collection)
@listenTo @room.get("messages"), "noMoreMessages", () -> @$(".js-loading").hide()
@listenTo @room.get("connectedUsers"), "add", (user, collection) -> @_renderConnectedUsers()
@listenTo @room.get("connectedUsers"), "remove", (user, collection) -> @_renderConnectedUsers()
@listenTo @room.get("connectedUsers"), "change", (user, collection) -> @_renderConnectedUsers()
@listenTo @room, "joined", -> @_onJoin()
@listenTo @room, "disconnected", -> @_onDisconnect()
@listenTo @room, "afterMessagesPreloaded", -> @_scrollToBottomOfMessages()
render: () ->
@setElement($(@template))
$(document.body).append(@$el)
@_renderConnectedUsers()
@_initializeMinimizedState()
_onJoin: () ->
if !@rendered?
@render()
@rendered = true
@$el.removeClass("disconnected")
@$("textarea").removeAttr("disabled")
_onDisconnect: () ->
@$el.addClass("disconnected")
@$("textarea").attr("disabled", "disabled")
_onNewMessage: (message, collection) ->
@_renderMessage(message, collection)
@_notifyAboutNewMessage(message)
_renderMessage: (message, collection) ->
@messageBlocks ||= []
scrollEl = @$(".sent-message-area")
isOldestMessage = (message, collection)->
collection.indexOf(message) == 0
ismessageFromNewUser = (messageView, message)->
!messageView? or messageView.user != message.get("user")
isTimeForNewBlockBackwards = (message, previousUserMessageBlockView)->
if !message? or !previousUserMessageBlockView?
return true
timeSinceMessageWasSent = new Date().getTime() - message.get("timestamp")
if timeSinceMessageWasSent < ONE_HOUR
timeBlockSize = FIVE_MINS
else if timeSinceMessageWasSent > ONE_HOUR and timeSinceMessageWasSent < (ONE_DAY + TWELVE_HOURS)
timeBlockSize = ONE_HOUR
else
timeBlockSize = ONE_DAY
timeSinceLastPrinted = previousUserMessageBlockView.getTime() - message.get("timestamp")
if timeSinceLastPrinted > timeBlockSize
return true
else
return false
isTimeForNewBlock = (message, previousUserMessageBlockView)->
(message.get("timestamp") - previousUserMessageBlockView.getTime()) > FIVE_MINS
if isOldestMessage(message, collection)
oldScrollTopFromBottom = scrollEl[0].scrollHeight - scrollEl.scrollTop()
userMessageBlockView = @messageBlocks[0]
if ismessageFromNewUser(userMessageBlockView, message) or isTimeForNewBlockBackwards(message, userMessageBlockView)
userMessageBlockView = new UserMessageBlockView(user: message.get("user"))
@$(".sent-messages").prepend(userMessageBlockView.$el)
@messageBlocks.unshift userMessageBlockView
userMessageBlockView.prependMessage(message)
scrollEl.scrollTop(scrollEl[0].scrollHeight - oldScrollTopFromBottom)
else
oldScrollBottom = @_getScrollBottom()
userMessageBlockView = @messageBlocks[@messageBlocks.length - 1]
if ismessageFromNewUser(userMessageBlockView, message) or isTimeForNewBlock(message, userMessageBlockView)
userMessageBlockView = new UserMessageBlockView(user: message.get("user"))
@$(".sent-messages").append(userMessageBlockView.$el)
@messageBlocks.push userMessageBlockView
userMessageBlockView.appendMessage(message)
if oldScrollBottom <= 0
@_scrollToBottomOfMessages()
_renderConnectedUsers: () ->
users = @room.get("connectedUsers")
names = users
.reject((user) => user == @chat.user)
.map((user) -> "#{user.get("first_name")} #{user.get("last_name")}")
if names.length == 0
text = "No one else is online :("
else if names.length == 1
text = "#{names[0]} is also online"
else
text = "#{names.slice(0, -1).join(", ")} and #{names[names.length - 1]} are also online"
@$(".js-connected-users").text(text)
@_resizeSentMessageArea()
_resizeSentMessageArea: () ->
marginTop = @$(".js-header").outerHeight() + @$(".js-connected-users").outerHeight()
@$(".js-sent-message-area").css({
top: marginTop + "px"
})
_getScrollBottom: () ->
scrollEl = @$(".sent-message-area")
return scrollEl[0].scrollHeight - scrollEl.scrollTop() - scrollEl.innerHeight()
_scrollToBottomOfMessages: () ->
scrollEl = @$(".sent-message-area")
doScroll = ->
return scrollEl.scrollTop(scrollEl[0].scrollHeight - scrollEl.innerHeight())
MathJax.Hub.Queue(["Typeset", doScroll])
_notifyAboutNewMessage: (message) ->
isMessageNewToUser = message.get("user") != @chat.user and !message.get("preloaded")
isTextAreaFocused = @$("textarea").is(":focus")
if !isTextAreaFocused and isMessageNewToUser
@unseenMessages ||= 0
@unseenMessages += 1
@$el.addClass("new-messages")
@$(".new-message-alert").text(@unseenMessages)
_removeNotification: () ->
@unseenMessages = 0
@$el.removeClass("new-messages")
@$(".new-message-alert").text("")
_onTextAreaKeyDown: (e) ->
if e.keyCode == 13 # Enter
e.preventDefault()
message = @$("textarea").val()
@$("textarea").val("")
@_sendMessage(message)
_loadOlderMessages: (e) ->
e.preventDefault()
@room.get("messages").fetchMoreMessages()
_sendMessage: (content) ->
@room.sendMessage(content)
isMinimized: () ->
minimized = $.localStorage "chat.rooms.project-chat.minimized"
if !minimized?
minimized = false
return minimized
_setMinimizedState: (state) ->
$.localStorage "chat.rooms.project-chat.minimized", state
_initializeMinimizedState: () ->
minimized = @isMinimized()
if minimized
@_minimize(false)
_toggleMinimizeState: (e) ->
e.preventDefault()
minimized = @isMinimized()
if !minimized
@_setMinimizedState(true)
@_minimize()
else
@_setMinimizedState(false)
@_unminimize()
_minimize: (animate = true) ->
@$(".new-message-area").hide()
@$(".js-connected-users").hide()
@$el.addClass("minimized")
if animate
@$el.animate height: 20, width: 80
else
@$el.css height: 20, width: 80
_unminimize: () ->
@$(".new-message-area").show()
@$(".js-connected-users").show()
@$el.removeClass("minimized")
@$el.animate height: 260, width: 220, () =>
@_resizeSentMessageArea()
@_scrollToBottomOfMessages()

View file

@ -1,40 +0,0 @@
define [
"libs/underscore"
"libs/backbone"
"moment"
], (_, Backbone, moment) ->
ONE_WEEK = 7 * 24 * 60 * 60 * 1000
TimeMessageBlockView = Backbone.View.extend
className : "timeSinceMessage"
initialize: () ->
@autoRefresh()
setTimeOnce: (timestamp)->
if !@timestamp?
@timestamp = timestamp
@render()
return @
setTime: (@timestamp)->
@render()
return @
autoRefresh: ->
if @timestamp?
@render()
self = @
doIt = =>
self.autoRefresh()
setTimeout doIt, 60 * 1000
render: () ->
milisecondsSince = new Date().getTime() - @timestamp
if milisecondsSince > ONE_WEEK
time = moment(@timestamp).format("D/MMM/YY, h:mm:ss a")
else
time = moment(@timestamp).fromNow()
this.$el.html(time)

View file

@ -1,64 +0,0 @@
define [
"libs/underscore"
"libs/backbone"
"views/timeMessageBlockView"
"moment"
"https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=TeX-AMS_HTML"
], (_, Backbone, TimeMessageBlockView, moment) ->
mathjaxConfig =
"HTML-CSS": { availableFonts: ["TeX"] },
TeX:
equationNumbers: { autoNumber: "AMS" },
useLabelIDs: false
tex2jax:
inlineMath: [ ['$','$'], ["\\(","\\)"] ],
displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
processEscapes: true
MathJax.Hub.Config(mathjaxConfig);
UserMessageBlockView = Backbone.View.extend
initialize: () ->
@template = _.template($("#messageBlockTemplate").html())
@user = @options.user
@timeMessageBlock = new TimeMessageBlockView()
@render()
render: () ->
@setElement $(@template(
first_name: @user.get("first_name")
last_name: @user.get("last_name")
gravatar_url: @user.get("gravatar_url")
))
@$(".timeArea").html(@timeMessageBlock.$el)
getTime: ->
return @timeMessageBlock.timestamp
appendMessage: (message) ->
el = @buildHtml(message)
@$(".messages").append(el)
@_renderMathJax(el)
@timeMessageBlock.setTimeOnce message.get("timestamp")
prependMessage: (message) ->
el = @buildHtml(message)
@$(".messages").prepend(el)
@_renderMathJax(el)
@timeMessageBlock.setTime message.get("timestamp")
buildHtml : (message)->
time = moment(message.get("timestamp")).format("dddd, MMMM Do YYYY, h:mm:ss a")
el = $("<div class='message' title='#{time}'>")
el.text(message.get("content"))
return el
_renderMathJax: (element)->
if element?
MathJax.Hub.Queue(["Typeset", MathJax.Hub, element.get(0)])

View file

@ -1,24 +0,0 @@
script(type="text/templates")#chatWindowTemplate
.chat-window
.header.js-header
h3 Chat
.new-message-alert.js-new-message-alert
.window-controls
.js-minimize-toggle.minimize-toggle
.connected-users.js-connected-users
.sent-message-area.js-sent-message-area
.loading.js-loading
a(href="#").load-older-messages.js-load-older-messages Load older messages
.sent-messages
.new-message-area
textarea(placeholder="Your message")
script(type="text/templates")#messageBlockTemplate
.chat-block
.message-block
.timeArea
.avatar
img(src="{{ gravatar_url }}?d=mm&s=40", alt="{{first_name}} {{last_name}}")
span.name {{first_name}} {{last_name}}
.messages

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,85 +0,0 @@
/*!
* jquery.storage.js 0.0.3 - https://github.com/yckart/jquery.storage.js
* The client-side storage for every browser, on any device.
*
* Copyright (c) 2012 Yannick Albert (http://yckart.com)
* Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.php).
* 2013/02/10
**/
define([], function() {
;(function($, window, document) {
'use strict';
$.map(['localStorage', 'sessionStorage'], function( method ) {
var defaults = {
cookiePrefix : 'fallback:' + method + ':',
cookieOptions : {
path : '/',
domain : document.domain,
expires : ('localStorage' === method) ? { expires: 365 } : undefined
}
};
try {
$.support[method] = method in window && window[method] !== null;
} catch (e) {
$.support[method] = false;
}
$[method] = function(key, value) {
var options = $.extend({}, defaults, $[method].options);
this.getItem = function( key ) {
var returns = function(key){
return JSON.parse($.support[method] ? window[method].getItem(key) : $.cookie(options.cookiePrefix + key));
};
if(typeof key === 'string') return returns(key);
var arr = [],
i = key.length;
while(i--) arr[i] = returns(key[i]);
return arr;
};
this.setItem = function( key, value ) {
value = JSON.stringify(value);
return $.support[method] ? window[method].setItem(key, value) : $.cookie(options.cookiePrefix + key, value, options.cookieOptions);
};
this.removeItem = function( key ) {
return $.support[method] ? window[method].removeItem(key) : $.cookie(options.cookiePrefix + key, null, $.extend(options.cookieOptions, {
expires: -1
}));
};
this.clear = function() {
if($.support[method]) {
return window[method].clear();
} else {
var reg = new RegExp('^' + options.cookiePrefix, ''),
opts = $.extend(options.cookieOptions, {
expires: -1
});
if(document.cookie && document.cookie !== ''){
$.map(document.cookie.split(';'), function( cookie ){
if(reg.test(cookie = $.trim(cookie))) {
$.cookie( cookie.substr(0,cookie.indexOf('=')), null, opts);
}
});
}
}
};
if (typeof key !== "undefined") {
return typeof value !== "undefined" ? ( value === null ? this.removeItem(key) : this.setItem(key, value) ) : this.getItem(key);
}
return this;
};
$[method].options = defaults;
});
}(jQuery, window, document));
});

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -1,373 +0,0 @@
/**
* @license RequireJS text 2.0.7 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
* Available via the MIT or new BSD license.
* see: http://github.com/requirejs/text for details
*/
/*jslint regexp: true */
/*global require, XMLHttpRequest, ActiveXObject,
define, window, process, Packages,
java, location, Components, FileUtils */
define(['module'], function (module) {
'use strict';
var text, fs, Cc, Ci,
progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'],
xmlRegExp = /^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im,
bodyRegExp = /<body[^>]*>\s*([\s\S]+)\s*<\/body>/im,
hasLocation = typeof location !== 'undefined' && location.href,
defaultProtocol = hasLocation && location.protocol && location.protocol.replace(/\:/, ''),
defaultHostName = hasLocation && location.hostname,
defaultPort = hasLocation && (location.port || undefined),
buildMap = {},
masterConfig = (module.config && module.config()) || {};
text = {
version: '2.0.7',
strip: function (content) {
//Strips <?xml ...?> declarations so that external SVG and XML
//documents can be added to a document without worry. Also, if the string
//is an HTML document, only the part inside the body tag is returned.
if (content) {
content = content.replace(xmlRegExp, "");
var matches = content.match(bodyRegExp);
if (matches) {
content = matches[1];
}
} else {
content = "";
}
return content;
},
jsEscape: function (content) {
return content.replace(/(['\\])/g, '\\$1')
.replace(/[\f]/g, "\\f")
.replace(/[\b]/g, "\\b")
.replace(/[\n]/g, "\\n")
.replace(/[\t]/g, "\\t")
.replace(/[\r]/g, "\\r")
.replace(/[\u2028]/g, "\\u2028")
.replace(/[\u2029]/g, "\\u2029");
},
createXhr: masterConfig.createXhr || function () {
//Would love to dump the ActiveX crap in here. Need IE 6 to die first.
var xhr, i, progId;
if (typeof XMLHttpRequest !== "undefined") {
return new XMLHttpRequest();
} else if (typeof ActiveXObject !== "undefined") {
for (i = 0; i < 3; i += 1) {
progId = progIds[i];
try {
xhr = new ActiveXObject(progId);
} catch (e) {}
if (xhr) {
progIds = [progId]; // so faster next time
break;
}
}
}
return xhr;
},
/**
* Parses a resource name into its component parts. Resource names
* look like: module/name.ext!strip, where the !strip part is
* optional.
* @param {String} name the resource name
* @returns {Object} with properties "moduleName", "ext" and "strip"
* where strip is a boolean.
*/
parseName: function (name) {
var modName, ext, temp,
strip = false,
index = name.indexOf("."),
isRelative = name.indexOf('./') === 0 ||
name.indexOf('../') === 0;
if (index !== -1 && (!isRelative || index > 1)) {
modName = name.substring(0, index);
ext = name.substring(index + 1, name.length);
} else {
modName = name;
}
temp = ext || modName;
index = temp.indexOf("!");
if (index !== -1) {
//Pull off the strip arg.
strip = temp.substring(index + 1) === "strip";
temp = temp.substring(0, index);
if (ext) {
ext = temp;
} else {
modName = temp;
}
}
return {
moduleName: modName,
ext: ext,
strip: strip
};
},
xdRegExp: /^((\w+)\:)?\/\/([^\/\\]+)/,
/**
* Is an URL on another domain. Only works for browser use, returns
* false in non-browser environments. Only used to know if an
* optimized .js version of a text resource should be loaded
* instead.
* @param {String} url
* @returns Boolean
*/
useXhr: function (url, protocol, hostname, port) {
var uProtocol, uHostName, uPort,
match = text.xdRegExp.exec(url);
if (!match) {
return true;
}
uProtocol = match[2];
uHostName = match[3];
uHostName = uHostName.split(':');
uPort = uHostName[1];
uHostName = uHostName[0];
return (!uProtocol || uProtocol === protocol) &&
(!uHostName || uHostName.toLowerCase() === hostname.toLowerCase()) &&
((!uPort && !uHostName) || uPort === port);
},
finishLoad: function (name, strip, content, onLoad) {
content = strip ? text.strip(content) : content;
if (masterConfig.isBuild) {
buildMap[name] = content;
}
onLoad(content);
},
load: function (name, req, onLoad, config) {
//Name has format: some.module.filext!strip
//The strip part is optional.
//if strip is present, then that means only get the string contents
//inside a body tag in an HTML string. For XML/SVG content it means
//removing the <?xml ...?> declarations so the content can be inserted
//into the current doc without problems.
// Do not bother with the work if a build and text will
// not be inlined.
if (config.isBuild && !config.inlineText) {
onLoad();
return;
}
masterConfig.isBuild = config.isBuild;
var parsed = text.parseName(name),
nonStripName = parsed.moduleName +
(parsed.ext ? '.' + parsed.ext : ''),
url = req.toUrl(nonStripName),
useXhr = (masterConfig.useXhr) ||
text.useXhr;
//Load the text. Use XHR if possible and in a browser.
if (!hasLocation || useXhr(url, defaultProtocol, defaultHostName, defaultPort)) {
text.get(url, function (content) {
text.finishLoad(name, parsed.strip, content, onLoad);
}, function (err) {
if (onLoad.error) {
onLoad.error(err);
}
});
} else {
//Need to fetch the resource across domains. Assume
//the resource has been optimized into a JS module. Fetch
//by the module name + extension, but do not include the
//!strip part to avoid file system issues.
req([nonStripName], function (content) {
text.finishLoad(parsed.moduleName + '.' + parsed.ext,
parsed.strip, content, onLoad);
});
}
},
write: function (pluginName, moduleName, write, config) {
if (buildMap.hasOwnProperty(moduleName)) {
var content = text.jsEscape(buildMap[moduleName]);
write.asModule(pluginName + "!" + moduleName,
"define(function () { return '" +
content +
"';});\n");
}
},
writeFile: function (pluginName, moduleName, req, write, config) {
var parsed = text.parseName(moduleName),
extPart = parsed.ext ? '.' + parsed.ext : '',
nonStripName = parsed.moduleName + extPart,
//Use a '.js' file name so that it indicates it is a
//script that can be loaded across domains.
fileName = req.toUrl(parsed.moduleName + extPart) + '.js';
//Leverage own load() method to load plugin value, but only
//write out values that do not have the strip argument,
//to avoid any potential issues with ! in file names.
text.load(nonStripName, req, function (value) {
//Use own write() method to construct full module value.
//But need to create shell that translates writeFile's
//write() to the right interface.
var textWrite = function (contents) {
return write(fileName, contents);
};
textWrite.asModule = function (moduleName, contents) {
return write.asModule(moduleName, fileName, contents);
};
text.write(pluginName, nonStripName, textWrite, config);
}, config);
}
};
if (masterConfig.env === 'node' || (!masterConfig.env &&
typeof process !== "undefined" &&
process.versions &&
!!process.versions.node)) {
//Using special require.nodeRequire, something added by r.js.
fs = require.nodeRequire('fs');
text.get = function (url, callback, errback) {
try {
var file = fs.readFileSync(url, 'utf8');
//Remove BOM (Byte Mark Order) from utf8 files if it is there.
if (file.indexOf('\uFEFF') === 0) {
file = file.substring(1);
}
callback(file);
} catch (e) {
errback(e);
}
};
} else if (masterConfig.env === 'xhr' || (!masterConfig.env &&
text.createXhr())) {
text.get = function (url, callback, errback, headers) {
var xhr = text.createXhr(), header;
xhr.open('GET', url, true);
//Allow plugins direct access to xhr headers
if (headers) {
for (header in headers) {
if (headers.hasOwnProperty(header)) {
xhr.setRequestHeader(header.toLowerCase(), headers[header]);
}
}
}
//Allow overrides specified in config
if (masterConfig.onXhr) {
masterConfig.onXhr(xhr, url);
}
xhr.onreadystatechange = function (evt) {
var status, err;
//Do not explicitly handle errors, those should be
//visible via console output in the browser.
if (xhr.readyState === 4) {
status = xhr.status;
if (status > 399 && status < 600) {
//An http 4xx or 5xx error. Signal an error.
err = new Error(url + ' HTTP status: ' + status);
err.xhr = xhr;
errback(err);
} else {
callback(xhr.responseText);
}
if (masterConfig.onXhrComplete) {
masterConfig.onXhrComplete(xhr, url);
}
}
};
xhr.send(null);
};
} else if (masterConfig.env === 'rhino' || (!masterConfig.env &&
typeof Packages !== 'undefined' && typeof java !== 'undefined')) {
//Why Java, why is this so awkward?
text.get = function (url, callback) {
var stringBuffer, line,
encoding = "utf-8",
file = new java.io.File(url),
lineSeparator = java.lang.System.getProperty("line.separator"),
input = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(file), encoding)),
content = '';
try {
stringBuffer = new java.lang.StringBuffer();
line = input.readLine();
// Byte Order Mark (BOM) - The Unicode Standard, version 3.0, page 324
// http://www.unicode.org/faq/utf_bom.html
// Note that when we use utf-8, the BOM should appear as "EF BB BF", but it doesn't due to this bug in the JDK:
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4508058
if (line && line.length() && line.charAt(0) === 0xfeff) {
// Eat the BOM, since we've already found the encoding on this file,
// and we plan to concatenating this buffer with others; the BOM should
// only appear at the top of a file.
line = line.substring(1);
}
if (line !== null) {
stringBuffer.append(line);
}
while ((line = input.readLine()) !== null) {
stringBuffer.append(lineSeparator);
stringBuffer.append(line);
}
//Make sure we return a JavaScript string and not a Java string.
content = String(stringBuffer.toString()); //String
} finally {
input.close();
}
callback(content);
};
} else if (masterConfig.env === 'xpconnect' || (!masterConfig.env &&
typeof Components !== 'undefined' && Components.classes &&
Components.interfaces)) {
//Avert your gaze!
Cc = Components.classes,
Ci = Components.interfaces;
Components.utils['import']('resource://gre/modules/FileUtils.jsm');
text.get = function (url, callback) {
var inStream, convertStream,
readData = {},
fileObj = new FileUtils.File(url);
//XPCOM, you so crazy
try {
inStream = Cc['@mozilla.org/network/file-input-stream;1']
.createInstance(Ci.nsIFileInputStream);
inStream.init(fileObj, 1, 0, false);
convertStream = Cc['@mozilla.org/intl/converter-input-stream;1']
.createInstance(Ci.nsIConverterInputStream);
convertStream.init(inStream, "utf-8", inStream.available(),
Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
convertStream.readString(inStream.available(), readData);
convertStream.close();
inStream.close();
callback(readData.value);
} catch (e) {
throw new Error((fileObj && fileObj.path || '') + ': ' + e);
}
};
}
return text;
});

View file

@ -1,198 +0,0 @@
@border-color: #999;
.chat-window {
border-bottom: none;
position: absolute;
bottom: 0px;
right: 10px;
height: 260px;
width: 220px;
background-color: white;
z-index: 10;
.header {
h3 {
background-color: rgb(40,40,40);
border: 1px solid #222;
border-bottom: none;
color: white;
font-size: 12px;
line-height: 12px;
padding: 4px 6px 5px;
font-weight: normal;
cursor: pointer;
}
position: relative;
.window-controls {
position: absolute;
top: 0px;
right: 4px;
bottom: 0px;
color: white;
cursor: pointer;
&:hover {
background-color: black;
}
.minimize-toggle {
width: 12px;
height: 100%;
padding: 0 4px;
background-repeat: no-repeat;
background-position: 4px center;
background-image: url(data:image/png;base64,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);
}
}
.new-message-alert {
display: none;
cursor: pointer;
position: absolute;
padding: 4px;
top: -16px;
left: 35px;
background-color: red;
color: white;
font-weight: bold;
padding: 1px 6px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
border-radius: 3px;
&:after {
border: 3px solid red;
border-right: 3px solid transparent;
border-bottom: 3px solid transparent;
content: '';
position: absolute;
left: 6px;
bottom: -6px;
}
}
}
.connected-users {
font-size: 10px;
padding: 4px;
background-color: #ddd;
border: 1px solid @border-color;
border-top: none;
}
.sent-message-area {
border: 1px solid @border-color;
border-top: none;
border-bottom: none;
position: absolute;
top: 22px;
bottom: 28px;
left: 0px;
right: 0px;
font-size: 12px;
font-family: arial, san-serif;
overflow-y: scroll;
.chat-block {
position: relative;
padding-top: 4px;
padding-bottom: 4px;
.message-block {
min-height: 40px;
margin-bottom: 5px;
}
.timeSinceMessage {
color: @border-color;
text-align:center;
padding-bottom: 8px;
}
.avatar {
float: left;
width: 44px;
height: 44px;
}
.name {
font-weight: bold;
color: #999;
margin-top: 0;
}
.message {
margin: 3px;
}
}
a.load-older-messages {
padding: 10px;
background-color: #eee;
text-align:center;
display: block;
&:hover {
background-color: #ddd;
}
}
}
@new-message-area-height: 27px;
@new-message-area-padding-top: 3px;
.new-message-area {
position: absolute;
bottom: 0px;
height: @new-message-area-height;
left: 0px;
right: 0px;
border: 1px solid @border-color;
border-bottom: none;
textarea {
height: @new-message-area-height - 2 * @new-message-area-padding-top;
width: 212px;
border: none;
padding: @new-message-area-padding-top;
margin: 0;
resize: none;
font-size: 12px;
font-family: arial, san-serif;
color: #333;
}
}
}
.chat-window.disconnected {
.sent-message-area {
opacity: 0.6;
}
}
.chat-window.new-messages {
.header {
h3 {
background-color: #049cdb;
border-color: darken(#049cdb, 10%);
}
.new-message-alert {
display: block;
}
.window-controls {
&:hover {
background-color: darken(#049cdb, 10%);
}
}
}
}
.chat-window.minimized {
.header {
.window-controls {
.minimize-toggle {
background-image: url(data:image/png;base64,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);
}
}
}
}